using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Web.Mvc; using EnVisage.Code; using System.Data.Entity; using System.Linq; using EnVisage.Models.ValidationAttributes.Scenarios; using EnVisage.Code.Cache; using Microsoft.AspNet.Identity; using EnVisage.Models.Cache; namespace EnVisage.Models { public class ScenarioDetailModel : IBaseModel { #region Enums and subclasses public enum ScenarioProbability { Low = 1, Expected = 2, High = 3 } public enum SavingsType { HardSavings, SoftSavings } #endregion #region Properties public Guid Id { get; set; } [Required] [AllowHtml] [MaxLength(100)] public string Name { get; set; } /// /// Gets or sets an Id of the template. /// [Display(Name = "Template")] public Guid TemplateId { get; set; } [Display(Name = "Template Name")] public string TemplateName { get; set; } [Display(Name = "Project Name")] public string ProjectName { get; set; } [Display(Name = "Project Deadline")] [DataType(DataType.Date)] [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}")] public DateTime? ProjectDeadline { get; set; } [Display(Name = "Part Name")] public string PartName { get; set; } /// /// Gets or sets an Id of the project. /// public Guid ParentId { get; set; } /// /// Gets or sets a color that will be used for scenario in reports. /// [MaxLength(8)] public string Color { get; set; } /// /// Gets or sets a type of scenario. /// [Required] [Display(Name = "Scenario Type")] public ScenarioType? Type { get; set; } /// /// Gets or sets a scenario of scenario. /// [Display(Name = "Scenario Status")] public ScenarioStatus? Status { get; set; } [Display(Name = "Active Scenario")] public bool IsActiveScenario { get; set; } [Display(Name = "Freeze Resources")] public bool FreezeResources { get; set; } [Display(Name = "Use this project's ACTUALS data for weekending dates that apply?")] public bool UseActuals { get; set; } [Display(Name = "Child Scenarios #")] public int ChildScenarios { get; set; } [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yy}", ApplyFormatInEditMode = true)] [Display(Name = "Scenario Start Date")] [Required] [ScenarioStartDateValidator] public DateTime? StartDate { get; set; } [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yy}", ApplyFormatInEditMode = true)] [Display(Name = "Scenario End Date")] [Required] [ScenarioEndDateValidator] public DateTime? EndDate { get; set; } [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yy}", ApplyFormatInEditMode = true)] [Display(Name = "Milestone Start Date")] public DateTime? MilestoneStartDate { get; set; } [Display(Name = "Growth Scenario?")] public bool GrowthScenario { get; set; } [Display(Name = "Revenue Generating")] public bool IsRevenueGenerating { get; set; } [Display(Name = "Projected Revenue")] [DataType(DataType.Currency), DisplayFormat(DataFormatString = "{0:G29}", ApplyFormatInEditMode = true)] public decimal ProjectedRevenue { get; set; } [Display(Name = "Use L&M Margin?")] public bool UseLMMargin { get; set; } [Display(Name = "Projected Margin")] [Range(1, 100)] public int? GrossMargin { get; set; } [Display(Name = "L&M Margin")] [Range(1, 100)] public int? LMMargin { get; set; } [Display(Name = "Calculated Gross Margin (Forecast)")] [Range(1, 100)] public int CalculatedGrossMargin { get; set; } [Display(Name = "Calculated Gross Margin LM (Forecast)")] public int CalculatedGrossMarginLM { get; set; } [Display(Name = "Calculated Gross Margin (Actuals)")] public int? CalculatedGrossMarginActuals { get; set; } [Display(Name = "Calculated Gross Margin LM (Actuals)")] [Range(1, 100)] public int? CalculatedGrossMarginLMActuals { get; set; } [Display(Name = "Top-Down Expense")] [TDDirectCostValidator] public decimal TDDirectCosts { get; set; } [Display(Name = "Top-Down Direct Costs LM")] public decimal TDDirectCostsLM { get; set; } [Display(Name = "Top-Down Revenue Per Milestone")] public decimal TDRevenuePerShot { get; set; } [Display(Name = "Bottom-Up Expense")] public decimal BUDirectCosts { get; set; } [Display(Name = "Bottom-Up Direct Costs LM (Forecast)")] public decimal BUDirectCostsLM { get; set; } [Display(Name = "Bottom-Up Direct Costs (Actuals)")] public decimal BUDirectCostsActuals { get; set; } [Display(Name = "Bottom-Up Direct Costs LM (Actuals)")] public decimal BUDirectCostsLMActuals { get; set; } [Display(Name = "Bottom-Up Costs/Milestones (Forecast)")] public decimal BUCostsShots { get; set; } [Display(Name = "Bottom-Up Costs/Milestones LM (Forecast)")] public decimal BUCostsShotsLM { get; set; } [Display(Name = "Bottom-Up Costs/Milestones (Actuals)")] public decimal BUCostsShotsActuals { get; set; } [Display(Name = "Bottom-Up Costs/Milestones LM (Actuals)")] public decimal BUCostsShotsLMActuals { get; set; } [Display(Name = "Total Milestones")] // not editable //[Range(1, 2147483647)] public int TotalMilestones { get; set; } public string UserPageSettings { get; set; } [Display(Name = "Labor Split Percentage")] // not editable //[Range(0, 100)] [UIHint("Slider")] public short LaborSplitPercentage { get; set; } [Display(Name = "Labor/Materials Split")] public string LaborMaterialsSplit { get; set; } [Display(Name = "Actual Labor")] public decimal? ActualLabor { get; set; } [Display(Name = "Actual Materials")] public decimal? ActualMaterials { get; set; } [Display(Name = "Actual Labor/Materials Split")] public string ActualLaborMaterialsSplit { get; set; } [Display(Name = "Calculated Duration (weeks)")] public int? Duration { get; set; } [Display(Name = "Cost Savings")] public decimal? CostSavings { get; set; } [Display(Name = "Cost Savings Duration")] public int? CostSavingsDuration { get; set; } [Display(Name = "Cost Savings Description")] public string CostSavingsDescription { get; set; } [Display(Name = "ROI Date")] public DateTime? ROIDate { get; set; } [Display(Name = "ROI Date")] public string ROIDateStr { get { return ROIDate.HasValue ? ROIDate.Value.ToShortDateString() : ""; } set { } } [Display(Name = "Hard/Soft Savings")] public string HardSoftSavings { get; set; } [Display(Name = "Savings After Cost")] public decimal? SavingsAfterCost { get; set; } [Display(Name = "Calculated Revenue After Cost")] public decimal RevenueAfterCost { get; set; } [Display(Name = "Actual Revenue After Cost")] public decimal? ActualRevenueAfterCost { get; set; } public decimal EFXSplit { get; set; } public IList Expenditures { get; set; } public IList ScenarioExpenditures { get; set; } public RatesModel RatesModel { get; set; } public string BackUrl { get; set; } public string BackName { get; set; } public string ActiveTab { get; set; } [DataType(DataType.Date), DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)] [Display(Name = "Date for start of change")] [DateForStartOfChangeValidator] public DateTime? DateForStartOfChanges { get; set; } public CostSavingModel CostSaving { get; set; } public string ValidationGroup { get { return "Step2"; } } public bool HasActuals { get; set; } public long ActualsStartDate { get; set; } public long ActualsEndDate { get; set; } public decimal ActualsTotal { get; set; } public bool NeedToRecalculateScenarioDetails { get; set; } #endregion #region Constructors public ScenarioDetailModel() { Expenditures = new List(); RatesModel = new RatesModel(); CostSaving = new CostSavingModel(); } #endregion #region Methods /// /// Casts a obect to the object of type . /// /// A object. /// A object filled with data from db. public static explicit operator ScenarioDetailModel(Scenario obj) { if (obj == null) return null; var model = new ScenarioDetailModel { Id = obj.Id, Name = obj.Name, ProjectName = (obj.Project.ParentProjectId.HasValue ? obj.Project.ParentProject.Name : obj.Project.Name), ProjectDeadline = obj.Project.Deadline, PartName = (obj.Project.ParentProjectId.HasValue ? obj.Project.Name : string.Empty), TemplateName = obj.ParentScenario != null ? obj.ParentScenario.Name : string.Empty, Color = obj.Color, TemplateId = obj.TemplateId ?? Guid.Empty, ParentId = obj.ParentId ?? Guid.Empty, Type = (ScenarioType)obj.Type, ChildScenarios = obj.ChildScenarios.Count, EndDate = obj.EndDate, StartDate = obj.StartDate, Status = obj.Status == null ? (ScenarioStatus?)null : (ScenarioStatus)obj.Status, GrossMargin = obj.ExpectedGrossMargin.HasValue ? Convert.ToInt32(obj.ExpectedGrossMargin * 100) : (int?)null, GrowthScenario = obj.GrowthScenario, IsActiveScenario = obj.Status == Code.ScenarioStatus.Active.GetHashCode(), LMMargin = obj.ExpectedGrossMargin_LM.HasValue ? Convert.ToInt32(obj.ExpectedGrossMargin_LM * 100) : (int?)null, UseLMMargin = obj.UseLMMargin == 1, LaborSplitPercentage = Convert.ToInt16(obj.CGSplit * 100), MilestoneStartDate = obj.ShotStartDate, ProjectedRevenue = (obj.ProjectedRevenue ?? 0), TotalMilestones = obj.Shots ?? 0, Expenditures = new List(), Duration = obj.Duration, CalculatedGrossMargin = (int)((obj.CalculatedGrossMargin ?? 0) * 100), CalculatedGrossMarginLM = (int)((obj.CalculatedGrossMargin_LM ?? 0) * 100), // SA. ENV-698. There was found, that Calculated by Actuals margins are the same as Calculated by Forecast. Begin CalculatedGrossMarginActuals = (obj.Actuals_BUDirectCosts.HasValue && obj.ProjectedRevenue.HasValue && (obj.ProjectedRevenue.Value != 0)) ? (int)((obj.ProjectedRevenue.Value - obj.Actuals_BUDirectCosts.Value) / obj.ProjectedRevenue.Value * 100) : 0, CalculatedGrossMarginLMActuals = (obj.Actuals_BUDirectCosts_LM.HasValue && obj.ProjectedRevenue.HasValue && (obj.ProjectedRevenue.Value != 0)) ? (int)((obj.ProjectedRevenue.Value - obj.Actuals_BUDirectCosts_LM.Value) / obj.ProjectedRevenue.Value * 100) : 0, // SA. ENV-698. End TDDirectCosts = Math.Round(obj.TDDirectCosts ?? 0), TDDirectCostsLM = Math.Round(obj.TDDirectCosts_LM ?? 0), TDRevenuePerShot = Math.Round(obj.TDRevenueShot ?? 0), BUDirectCosts = Math.Round(obj.BUDirectCosts ?? 0), BUDirectCostsLM = Math.Round(obj.BUDirectCosts_LM ?? 0), BUDirectCostsActuals = Math.Round(obj.Actuals_BUDirectCosts ?? 0), BUDirectCostsLMActuals = Math.Round(obj.Actuals_BUDirectCosts_LM ?? 0), BUCostsShots = Math.Round((((obj.Shots ?? 0) > 0) ? (obj.BUDirectCosts ?? 0) / (obj.Shots ?? 0) : (obj.BUDirectCosts ?? 0))), BUCostsShotsLM = Math.Round((((obj.Shots ?? 0) > 0) ? (obj.BUDirectCosts_LM ?? 0) / (obj.Shots ?? 0) : (obj.BUDirectCosts_LM ?? 0))), BUCostsShotsActuals = Math.Round((((obj.Shots ?? 0) > 0) ? (obj.Actuals_BUDirectCosts ?? 0) / (obj.Shots ?? 0) : (obj.Actuals_BUDirectCosts ?? 0))), BUCostsShotsLMActuals = Math.Round((((obj.Shots ?? 0) > 0) ? (obj.Actuals_BUDirectCosts_LM ?? 0) / (obj.Shots ?? 0) : (obj.Actuals_BUDirectCosts_LM ?? 0))), FreezeResources = Convert.ToBoolean(obj.FreezeRevenue), IsRevenueGenerating = obj.Project.IsRevenueGenerating, CostSavings = (obj.CostSavings.HasValue) ? obj.CostSavings.Value : (decimal?)null, CostSavingsDuration = (obj.CostSavingsEndDate.HasValue && obj.CostSavingsStartDate.HasValue) ? (obj.CostSavingsEndDate.Value.Month - obj.CostSavingsStartDate.Value.Month) + 12 * (obj.CostSavingsEndDate.Value.Year - obj.CostSavingsStartDate.Value.Year) + 1 : (int?)null, CostSavingsDescription = obj.CostSavingsDescription, //TODO: replace with loading of cost savings for all scenarios by single request. See ENV-590. ROIDate = obj.ROIDate, // GetROIDate(obj), HardSoftSavings = (obj.CostSavingsType.HasValue) ? (obj.CostSavingsType.Value == 1 ? "Hard" : "Soft") : string.Empty, SavingsAfterCost = (decimal?)null, RevenueAfterCost = 0, EFXSplit = obj.EFXSplit ?? 0 }; model.RatesModel = new RatesModel() { ScenarioId = model.Id }; if (obj.CostSavingsStartDate.HasValue && obj.CostSavingsEndDate.HasValue) { model.CostSaving = new CostSavingModel() { CostSavings = (decimal?)obj.CostSavings, CostSavingStartDate = obj.CostSavingsStartDate, CostSavingEndDate = obj.CostSavingsEndDate, CostSavingType = obj.CostSavingsType == 1, CostSavingsPanelExpanded = true, ScenarioStartDate = obj.StartDate, CostSavingItems = Newtonsoft.Json.JsonConvert.SerializeObject(GetCostSavingItems(obj)), ROIDate = obj.ROIDate // GetROIDate(obj) }; if (obj.CostSavings.HasValue && obj.BUDirectCosts.HasValue && obj.CostSavings.Value >= obj.BUDirectCosts.Value) { model.SavingsAfterCost = (decimal?)(obj.CostSavings.Value - obj.BUDirectCosts.Value); } if (obj.CostSavings.HasValue && model.IsRevenueGenerating) { model.RevenueAfterCost = model.ProjectedRevenue; model.ActualRevenueAfterCost = model.ProjectedRevenue; if (obj.BUDirectCosts.HasValue && obj.CostSavings.Value < obj.BUDirectCosts.Value) model.RevenueAfterCost += (decimal)(obj.CostSavings.Value - obj.BUDirectCosts.Value); if (obj.Actuals_BUDirectCosts.HasValue && obj.CostSavings.Value < obj.Actuals_BUDirectCosts.Value) model.ActualRevenueAfterCost += (decimal)(obj.CostSavings.Value - obj.Actuals_BUDirectCosts.Value); } } else { if (model.IsRevenueGenerating) { model.RevenueAfterCost = model.ProjectedRevenue; model.ActualRevenueAfterCost = model.ProjectedRevenue; if (obj.BUDirectCosts.HasValue) model.RevenueAfterCost -= obj.BUDirectCosts.Value; if (obj.Actuals_BUDirectCosts.HasValue) model.ActualRevenueAfterCost -= obj.Actuals_BUDirectCosts.Value; } } model.TrimStringProperties(); return model; } /// /// Copies data from model to DAL object. /// /// A target DAL object. public void CopyTo(Scenario dbObj) { if (dbObj == null) throw new ArgumentNullException(); dbObj.Name = Name; // SA. Fixed bug ENV-707. Begin fix if (!String.IsNullOrEmpty(Color)) dbObj.Color = Color.Replace("#", ""); else dbObj.Color = null; // End fix ENV-707 dbObj.Type = Type.GetHashCode(); dbObj.Status = IsActiveScenario ? Code.ScenarioStatus.Active.GetHashCode() : Code.ScenarioStatus.Inactive.GetHashCode(); dbObj.ShotStartDate = MilestoneStartDate; } #endregion //private static DateTime? GetROIDate(Scenario scenario) //{ // if (!scenario.CostSavings.HasValue) // return null; // if (scenario.BUDirectCosts > scenario.CostSavings) // return null; // if (scenario.CostSavings1 == null || scenario.CostSavings1.Count == 0) // return null; // var costSavingsSum = 0.0M; // foreach (var costSaving in scenario.CostSavings1.OrderBy(t => t.Year).ThenBy(t => t.Month)) // { // costSavingsSum += costSaving.Cost ?? 0; // if (costSavingsSum >= scenario.BUDirectCosts) // { // return new DateTime(costSaving.Year, costSaving.Month, 1); // } // } // return null; //} public static List GetCostSavingItems(Scenario scenario) { var costSavingItems = new List(); if (scenario != null && scenario.CostSavings1 != null && scenario.CostSavings1.Count > 0) { foreach (var year in scenario.CostSavings1.OrderBy(x => x.Year).Select(x => x.Year).Distinct()) { var yearSaving = new ScenarioCostSavingModel() { Year = year, Costs = new decimal?[13] }; foreach (var cost in scenario.CostSavings1.Where(x => x.Year == year)) yearSaving.Costs[cost.Month] = cost.Cost; yearSaving.Costs[0] = yearSaving.Costs.Sum(x => x ?? 0); costSavingItems.Add(yearSaving); } } return costSavingItems; } } public class LaborMaterialsCostInfo { public decimal Labor { get; set; } public decimal Materials { get; set; } public decimal Total { get; set; } public string LaborAndMaterialSplit { get { if (Labor == 0 && Materials == 0) return "0/0"; decimal laborPart = Math.Round((Labor / (Labor + Materials)) * 100, 2); return string.Format("{0:G29}/{1:G29}", laborPart, 100 - laborPart); } } } public class ScenarioCopyModel { public Guid ScenarioId { get; set; } [Required] [Display(Name = "Target Project")] public Guid TargetProjectId { get; set; } public ScenarioStatus TargetStatus { get; set; } public bool includeCostSavings { get; set; } public bool hasCostSavings { get; set; } } public class ScenarioDetailsCalendarModel { #region Properties public Guid Id { get; set; } public string Name { get; set; } public Guid ParentId { get; set; } public Guid? TemplateId { get; set; } public long StartDate { get; set; } public long EndDate { get; set; } public decimal ProjectedRevenue { get; set; } public bool GrowthScenario { get; set; } public ScenarioType Type { get; set; } public bool UseLMMargin { get; set; } public decimal? GrossMargin { get; set; } public decimal? LMMargin { get; set; } public int Duration { get; set; } public decimal TDDirectCosts { get; set; } public decimal CGSplit { get; set; } public decimal EFXSplit { get; set; } public bool Pinned { get; set; } public int Shots { get; set; } public bool IsActiveScenario { get; set; } public bool ShowAvgTotals { get; set; } public bool IsUOMHours { get; set; } public bool ShowActuals { get; set; } public bool IsTableModeQuantity { get; set; } public bool IsViewModeMonth { get; set; } public bool ShowFilters { get; set; } public string DataSection { get { return "scenarioCalendar"; } } public List AvailableExpenditures { get; set; } public List TeamsInScenario { get; set; } public bool NeedToRecalculateScenarioDetails { get; set; } public bool NeedToAdjustMargin { get; set; } /// /// Rebuild calendar only by request /// public bool InitOnDemand { get; set; } public string PagePreferences { get; private set; } public ScenarioCalendarOpener Opener { get; set; } #endregion #region Constructors public ScenarioDetailsCalendarModel(User user, ScenarioCalendarOpener opener) { AvailableExpenditures = new List(); TeamsInScenario = new List(); Opener = opener; if (user != null) { ShowAvgTotals = user.PreferredTotalsDisplaying; IsUOMHours = !user.PreferredResourceAllocation; PagePreferences = user.GetPreferences("/Scenarios/Details", DataSection); } } public ScenarioDetailsCalendarModel(ScenarioDetailModel model, User user, ScenarioCalendarOpener opener) : this(user, opener) { if (model != null) { Id = model.Id; Name = model.Name; ParentId = model.ParentId; TemplateId = model.TemplateId; StartDate = model.StartDate.HasValue ? Utils.ConvertToUnixDate(model.StartDate.Value) : 0; EndDate = model.EndDate.HasValue ? Utils.ConvertToUnixDate(model.EndDate.Value) : 0; ProjectedRevenue = model.ProjectedRevenue; GrowthScenario = model.GrowthScenario; Type = model.Type ?? ScenarioType.Portfolio; UseLMMargin = model.UseLMMargin; GrossMargin = model.GrossMargin; LMMargin = model.LMMargin; Duration = model.Duration ?? 0; TDDirectCosts = model.TDDirectCosts; CGSplit = model.LaborSplitPercentage / (decimal)100.0; EFXSplit = model.EFXSplit; Shots = model.TotalMilestones; IsActiveScenario = model.IsActiveScenario; ShowActuals = false; IsTableModeQuantity = true; IsViewModeMonth = true; ShowFilters = false; TeamsInScenario = null; NeedToRecalculateScenarioDetails = Guid.Empty.Equals(model.Id); NeedToAdjustMargin = Guid.Empty.Equals(model.Id); if (model.ScenarioExpenditures != null) { AvailableExpenditures = model.ScenarioExpenditures.Select(t => new ExpenditureModel { Id = t.Id, Name = t.Name }).ToList(); } } } public ScenarioDetailsCalendarModel(CreateScenarioModel.GeneralInfoModel model, User user, ScenarioCalendarOpener opener) : this(user, opener) { Id = model.ScenarioId; Name = model.ScenarioName; ParentId = model.PartId ?? model.ProjectId; TemplateId = model.TemplateId; StartDate = model.StartDate.HasValue ? Utils.ConvertToUnixDate(model.StartDate.Value) : 0; EndDate = model.EndDate.HasValue ? Utils.ConvertToUnixDate(model.EndDate.Value) : 0; ProjectedRevenue = 0; GrowthScenario = model.GrowthScenario; Type = ScenarioType.Portfolio; UseLMMargin = false; GrossMargin = 0; LMMargin = null; Duration = 0; TDDirectCosts = 0; CGSplit = model.LaborSplitPercentage / (decimal)100.0; EFXSplit = model.EFXSplit; Shots = 0; IsActiveScenario = model.CreateAsActive; ShowActuals = false; IsTableModeQuantity = true; IsViewModeMonth = true; ShowFilters = false; NeedToRecalculateScenarioDetails = Guid.Empty.Equals(model.ScenarioId); NeedToAdjustMargin = Guid.Empty.Equals(model.ScenarioId); if (model.ScenarioExpenditures != null) { AvailableExpenditures = model.ScenarioExpenditures.Where(t => t.Checked).Select(t => new ExpenditureModel { Id = t.Id, Name = t.Name }).ToList(); } if (model.Teams != null && model.Teams.Sliders != null && model.Teams.Sliders.Count > 0) { TeamsInScenario = model.Teams.Sliders.Select(x => new TeamInScenarioModel() { TeamId = x.EntityId, Allocation = (short)Math.Round(x.AllocatePercentage, 0), IsNew = x.IsExcludable }).ToList(); } } public ScenarioDetailsCalendarModel(ScenarioCalendarMixModel model, User user) : this(user, ScenarioCalendarOpener.RMO) { if (model != null) { Id = model.Id; ParentId = model.ParentId ?? model.TemplateId; TemplateId = model.TemplateId; StartDate = model.StartDate; EndDate = model.EndDate; ProjectedRevenue = model.ProjectedRevenue ?? 0; GrowthScenario = model.GrowthScenario; Type = model.Type; UseLMMargin = model.UseLMMargin; GrossMargin = model.GrossMargin; LMMargin = model.LMMargin; Duration = model.Duration; TDDirectCosts = model.TDDirectCosts ?? 0; CGSplit = model.CGSplit; EFXSplit = model.EFXSplit; Pinned = model.Pinned; Shots = 0; IsActiveScenario = true; ShowActuals = false; IsTableModeQuantity = true; IsViewModeMonth = true; ShowFilters = false; TeamsInScenario = null; NeedToRecalculateScenarioDetails = false; NeedToAdjustMargin = false; InitOnDemand = true; // calendar will be populated with data by client-side request } } #endregion #region Enums public enum ScenarioCalendarOpener { CreateScenarioWizard = 0, Details = 1, RMO = 2 } #endregion } }