using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data.Entity; using System.Linq; using EnVisage.Models; using NLog; using System.Linq.Expressions; using Newtonsoft.Json; namespace EnVisage.Code.BLL { public class ScenarioManager : ManagerBase { public Guid[] materialsECs; #region sub classes public class AdditionalExpCatItem { public Guid ExpenditureCategoryId { get; set; } public DateTime LastUpdate { get; set; } public ExpenditureCategoryModel.CategoryTypes CategoryType { get; set; } public decimal Quantity { get; set; } public decimal DetailCost { get; set; } public int? SortOrder { get; set; } public ExpenditureCategoryModel.UseTypes UseType { get; set; } public int WeekOrdinal { get; set; } public ExpenditureCategoryModel.CgEfx CG_EFX { get; set; } public string CategoryName { get; set; } public Guid? SysField1 { get; set; } public Guid SysField2 { get; set; } public Guid? CreditId { get; set; } public Guid? GlId { get; set; } public Guid? UOMId { get; set; } } public class ScenarioDetailsListItem { public Guid Id { get; set; } public Guid ParentId { get; set; } public Guid ExpenditureCategoryId { get; set; } public DateTime? WeekEndingDate { get; set; } public decimal Quantity { get; set; } public DateTime LastUpdate { get; set; } public int WeekOrdinal { get; set; } public decimal DetailCost { get; set; } public string ExpenditureCategoryName { get; set; } public Guid? GlId { get; set; } public Guid? UOMId { get; set; } public Guid? CreditId { get; set; } public ExpenditureCategoryModel.CategoryTypes CategoryType { get; set; } public ExpenditureCategoryModel.UseTypes UseType { get; set; } public ExpenditureCategoryModel.CgEfx CG_EFX { get; set; } public Guid? SysField1 { get; set; } public Guid SysField2 { get; set; } public int? SortOrder { get; set; } } public class TemporaryCalculationResults { public decimal mvActualCostCG_Fee { get; set; } public decimal mvActualCostEFX_Fee { get; set; } public decimal mvActualCostCG_LM { get; set; } public decimal mvActualCostEFX_LM { get; set; } public decimal mvActualCostCG { get; set; } public decimal mvActualCostEFX { get; set; } public decimal mvUsedCostCG { get; set; } public decimal mvUsedCostEFX { get; set; } public decimal mvUsedCostCG_LM { get; set; } public decimal mvUsedCostEFX_LM { get; set; } public decimal lvCGFactor { get; set; } public decimal lvEFXFactor { get; set; } } public class CalculatedExpenditureCategoryItem { public Guid? ExpenditureId { get; set; } public int ProcessOrder { get; set; } public decimal Factor { get; set; } public int FactorType { get; set; } public static Dictionary> LoadAllCalculatedCategories(EnVisageEntities dbContext) { var query = dbContext.VW_Expenditure2Calculation.OrderBy(t => t.ParentId).ThenBy(t => t.ProcessOrder); var parentId = Guid.Empty; var firstTime = false; var calcCategories = new List(); var result = new Dictionary>(); foreach (var expenditure2Calculation in query) { if (expenditure2Calculation.ParentId != parentId) { if (firstTime) { firstTime = false; calcCategories = new List(); } else { result.Add(parentId, calcCategories); calcCategories = new List(); } parentId = expenditure2Calculation.ParentId; } calcCategories.Add(new CalculatedExpenditureCategoryItem { ExpenditureId = expenditure2Calculation.ExpenditureCategoryID, ProcessOrder = expenditure2Calculation.ProcessOrder ?? 0, Factor = expenditure2Calculation.FactorInt ?? 0, FactorType = expenditure2Calculation.FactorType ?? 0 }); } result.Add(parentId, calcCategories); return result; } } #endregion public ScenarioManager(EnVisageEntities dbContext) : base(dbContext) { } protected override Scenario InitInstance() { return new Scenario { Id = Guid.NewGuid(), LastUpdate = DateTime.Now }; } protected override Scenario RetrieveReadOnlyById(Guid key) { return DataTable.AsNoTracking().FirstOrDefault(t => t.Id == key); } public override DbSet DataTable { get { return DbContext.Scenarios; } } /// /// Groupped ECs in Template or Not and related to scenario teams and not /// /// /// /// /// SA. ENV-840 public IList GetExpenditureCategories(Guid? templateId, List teams) { IList expCats = GetExpenditureCategories(templateId); List teamExpCats = new List(); if ((teams != null) && (teams.Count > 0)) { teamExpCats = (from PeopleResource pr in DbContext.PeopleResources.AsNoTracking() where pr.TeamId.HasValue && teams.Contains(pr.TeamId.Value) select pr.ExpenditureCategoryId).Distinct().ToList(); } expCats.Where(x => teamExpCats.Contains(x.Id)).ToList() .ForEach(x => x.Group = String.Format("{0} - {1}", x.Group, Constants.EXPENDITURE_CURRENT_TEAMS_TITLE)); expCats.Where(x => !teamExpCats.Contains(x.Id)).ToList() .ForEach(x => x.Group = String.Format("{0} - {1}", x.Group, Constants.EXPENDITURE_OTHER_EXPCATS_TITLE)); IList expCatsSorted = expCats.OrderBy(x => x.Group).ThenBy(x => x.Name).ToList(); return expCatsSorted; } public IList GetExpenditureCategories(Guid? templateId) { if (templateId == null || templateId == Guid.Empty) return new ScenarioModel.ExpenditureItem[0]; var list = new List(); var categories = DbContext.VW_ExpCategoriesInScenario.Where(t => t.ScenarioID == templateId).ToArray(); var lstIds = categories.Select(t => t.Id).ToList(); // add checked expenditure categories from template list.AddRange(DbContext.VW_Expenditure2Category.Where(t => lstIds.Contains(t.Id)) .OrderBy(t => t.ExpCategoryWithCcName) // SA. ENV-756 .Select(t => new ScenarioModel.ExpenditureItem { Id = t.Id, Group = Constants.EXPENDITURE_INTEMPLATE_TITLE, Name = t.ExpCategoryWithCcName, // SA. ENV-756. ENV-839 Checked = true }).ToArray()); var arrCalculatedIds = categories.Where(t => t.UseType == (int)ExpenditureCategoryModel.UseTypes.Calculated).Select(t => t.Id).ToArray(); //var additionalExpCatsP = new List(); if (arrCalculatedIds.Length > 0) { foreach (var item in DbContext.VW_Expenditure2Calculation.Where(t => arrCalculatedIds.Contains(t.ParentId))) { if (!item.ExpenditureCategoryID.HasValue) continue; if (!lstIds.Contains(item.ExpenditureCategoryID.Value)) lstIds.Add(item.ExpenditureCategoryID.Value); //additionalExpCatsP.Add(new AdditionalExpCatItem // { // ExpCatId = item.ExpenditureCategoryID.Value, // LastUpdateDate = DateTime.Now, // CategoryType = (ExpenditureCategoryModel.CategoryTypes)(item.Type ?? 0), // Quantity = 0, // DetailCost = 0, // SortOrder = item.SortOrder ?? 0, // UseType = (ExpenditureCategoryModel.UseTypes)(item.UseType ?? 1), // WeekOrdinal = 0, // CG_EFX = item.CGEFX, // CategoryName = item.ExpenditureName, // CreditId = item.CreditId, // GlId = item.GLId, // SysField1 = item.SystemAttributeOne, // SysField2 = Guid.Empty, // UOMId = item.UOMId // }); } } // add not checked expenditure categories for Labor/Materials/Usage types list.AddRange(DbContext.VW_Expenditure2Category.Where(t => !lstIds.Contains(t.Id)) .OrderBy(t => t.Type) .ThenBy(t => t.ExpCategoryWithCcName) // SA. ENV-756 .Select(t => new { t.Id, t.ExpCategoryWithCcName, // SA. ENV-756. ENV-839 t.Type }).ToArray() .Select(t => new ScenarioModel.ExpenditureItem { Id = t.Id, Group = ((ExpenditureCategoryModel.CategoryTypes)(t.Type ?? 0)).ToDisplayValue(), Name = t.ExpCategoryWithCcName, // SA. ENV-756. ENV-839 Checked = false })); return list; } private List GetNewCategoriesForScenario(Guid scenarioId, IList expenditures, List existenceExpenditures) { var lstIds = new List(); var arrCalculatedIds = new List(); if (existenceExpenditures == null || existenceExpenditures.Count <= 0) { var categories = DbContext.VW_ExpCategoriesInScenario.Where(t => t.ScenarioID == scenarioId).ToList(); lstIds = categories.Select(t => t.Id).ToList(); arrCalculatedIds = categories.Where(t => t.UseType == (int)ExpenditureCategoryModel.UseTypes.Calculated).Select(t => t.Id).ToList(); } else { lstIds = existenceExpenditures; arrCalculatedIds = DbContext.ExpenditureCategory.Where(x => x.UseType == (int)ExpenditureCategoryModel.UseTypes.Calculated && lstIds.Contains(x.Id)).Select(x => x.Id).ToList(); } if (arrCalculatedIds.Count > 0) { foreach (var item in DbContext.VW_Expenditure2Calculation.Where(t => arrCalculatedIds.Contains(t.ParentId))) { if (!item.ExpenditureCategoryID.HasValue) continue; if (!lstIds.Contains(item.ExpenditureCategoryID.Value)) lstIds.Add(item.ExpenditureCategoryID.Value); } } var allCheckedItemIds = expenditures.Select(t => t.Id).ToArray(); // add not checked expenditure categories for Labor/Materials/Usage types return DbContext.VW_Expenditure2Category .Where(t => !lstIds.Contains(t.Id) && allCheckedItemIds.Contains(t.Id)) .OrderBy(t => t.Type) .ThenBy(t => t.ExpCategoryWithCcName) // SA. ENV-839 .Select(t => new AdditionalExpCatItem { ExpenditureCategoryId = t.Id, CategoryName = t.ExpCategoryWithCcName, // SA. ENV-839 CG_EFX = t.CGEFX == ExpenditureCategoryModel.CgEfx.CG.ToString() ? ExpenditureCategoryModel.CgEfx.CG : ExpenditureCategoryModel.CgEfx.EFX, CategoryType = (ExpenditureCategoryModel.CategoryTypes)(t.Type ?? 0), CreditId = t.CreditId, DetailCost = 0, GlId = t.GLId, LastUpdate = DateTime.Today, Quantity = 0, SortOrder = t.SortOrder, SysField1 = t.SystemAttributeOne, SysField2 = Guid.Empty, UOMId = t.UOMId, UseType = (ExpenditureCategoryModel.UseTypes)(t.UseType ?? 1) }).ToList(); } public Dictionary> PrepareScenarioDetails(ScenarioInfoModel scenario, List expenditures, List actualsDetailsList, Dictionary> moRateTranslate, List currentScenarioDetails, List fiscalCalendars, out bool isUpdate, out int currentPeriods) { isUpdate = false; currentPeriods = 0; if (scenario == null) throw new ArgumentNullException("scenario"); if (fiscalCalendars == null || fiscalCalendars.Count <= 0) throw new ArgumentNullException("fiscalCalendars"); #region Resolving of scenario id for recalculation of scenario details var templateScenarioId = Guid.Empty; Scenario dbObj = null; int OldDuration = scenario.Duration; var materialsEcs = DbContext.ExpenditureCategory.Where(x => x.Type == (int)ExpenditureCategoryModel.CategoryTypes.Materials).Select(x => x.Id).Distinct().ToArray(); var totalBtUpCostsMaterials = currentScenarioDetails.Where(t => materialsEcs.Contains(t.ExpenditureCategoryId.Value)).Sum(t => t.Cost); var totalActuals = actualsDetailsList.Sum(x => x.Cost.Value); // when we will use it on scenario creating we need to set scenario id for scenario details correct generating if (!scenario.Id.HasValue) scenario.Id = Guid.NewGuid(); else { dbObj = DataTable.Find(scenario.Id); //OldDuration = dbObj.Duration; } // if template was changed need to recalculate all details according to new template if ((scenario.OldTemplateId ?? Guid.Empty) != Guid.Empty && scenario.OldTemplateId != scenario.TemplateId) { templateScenarioId = scenario.TemplateId; currentScenarioDetails = new List(); expenditures = new List(); } // if scenario is new or for any reason does not has scenario details need to recalculate scenario details according to template else if (currentScenarioDetails.Count <= 0 || (scenario.Id ?? Guid.Empty) == Guid.Empty) { templateScenarioId = scenario.TemplateId; } else { templateScenarioId = scenario.Id.Value; isUpdate = true; } #endregion scenario.Duration = fiscalCalendars.Count; var lstDctEcAddOn = new List(); var excludedExpCats = new List(); // prepare a list of additional categories to be added/removed from template category list if (currentScenarioDetails == null || currentScenarioDetails.Count <= 0) { // get categories from template which should be added to scenario lstDctEcAddOn = GetNewCategoriesForScenario(templateScenarioId, expenditures, null); if ((scenario.OldTemplateId ?? Guid.Empty) != Guid.Empty && scenario.OldTemplateId != scenario.TemplateId) { // if template has been changed on Scenario Details -> Header tab // then regenerate entire grid and add all categories from new template } else { // remove categories which were not selected by user in New Scenario wizard var templateCategoryIds = DbContext.VW_ExpCategoriesInScenario.Where(t => t.ScenarioID == templateScenarioId).Select(t => t.Id).Distinct().ToList(); excludedExpCats = templateCategoryIds.Where(t => expenditures.All(checkedCategory => checkedCategory.Id != t)) .ToList(); } } else { var existenceExp = currentScenarioDetails.Where(x => x.ExpenditureCategoryId.HasValue).Select(x => x.ExpenditureCategoryId.Value).Distinct().ToList(); lstDctEcAddOn = GetNewCategoriesForScenario(templateScenarioId, expenditures, existenceExp); excludedExpCats = currentScenarioDetails.Where(x => x.ExpenditureCategoryId.HasValue && expenditures.All(t => t.Id != x.ExpenditureCategoryId)).Select(x => x.ExpenditureCategoryId.Value).Distinct().ToList(); } var lvaCurrScenario = GetTemplateScenarioDetailsPerExpCat(templateScenarioId, lstDctEcAddOn.ToList(), Guid.Empty, excludedExpCats, currentScenarioDetails); if (lvaCurrScenario.Count == 0 || lvaCurrScenario.FirstOrDefault().Value.Count == 0) throw new BLLException("Unable to calculate periods for provided information."); currentPeriods = lvaCurrScenario.FirstOrDefault().Value.Count; var temporaryCalcResult = new TemporaryCalculationResults(); if (!scenario.GrowthScenario) { if (actualsDetailsList != null) { foreach (var actualScenarioDetailItem in actualsDetailsList) { if (ExpenditureCategoryModel.CgEfx.CG.ToString().Equals(actualScenarioDetailItem.CGEFX)) { temporaryCalcResult.mvUsedCostCG += (actualScenarioDetailItem.Cost ?? 0); if ((int)ExpenditureCategoryModel.CategoryTypes.Labor == actualScenarioDetailItem.Type || (int)ExpenditureCategoryModel.CategoryTypes.Materials == actualScenarioDetailItem.Type) temporaryCalcResult.mvUsedCostCG_LM += (actualScenarioDetailItem.Cost ?? 0); } else if (ExpenditureCategoryModel.CgEfx.EFX.ToString().Equals(actualScenarioDetailItem.CGEFX)) { temporaryCalcResult.mvUsedCostEFX += (actualScenarioDetailItem.Cost ?? 0); if ((int)ExpenditureCategoryModel.CategoryTypes.Labor == actualScenarioDetailItem.Type || (int)ExpenditureCategoryModel.CategoryTypes.Materials == actualScenarioDetailItem.Type) temporaryCalcResult.mvUsedCostEFX_LM += (actualScenarioDetailItem.Cost ?? 0); } } } } var cutOffDate = ResolveCutOffDate(scenario.DateForStartOfChanges, scenario.ActualEndDate); var adjustHandler = new AdjustScenarioDetailsDistribution(DbContext) { CurrentPeriods = currentPeriods, NewPeriods = scenario.Duration, CurrentScenarioDetails = lvaCurrScenario, FiscalCalendarWeeks = fiscalCalendars.ToArray(), IsUpdate = isUpdate, CurrentActuals = actualsDetailsList, // need to pass current scenario id, not template id!!! ScenarioId = scenario.Id.Value, StartDate = Utils.ConvertFromUnixDate(scenario.StartDate), EndDate = Utils.ConvertFromUnixDate(scenario.EndDate), UseLMMargin = scenario.UseLMMargin, ActualsEndDate = Utils.ConvertFromUnixDate(scenario.ActualEndDate), CutOffDate = cutOffDate, Type = scenario.DistributionType }; var calculatedNewScenario = adjustHandler.CalculateDistribution(); if (scenario.NeedToAdjustMargin) { #region Calculate the CG/EFX Split // store row of each calculated and fee expenditure category var moCatRowTranslate = calculatedNewScenario.Where( item => ExpenditureCategoryModel.UseTypes.Calculated != item.Value[0].UseType && ExpenditureCategoryModel.UseTypes.Fee != item.Value[0].UseType) .ToDictionary(item => item.Key, item => item.Value); if (scenario.UseLMMargin) { decimal totalCost = 0; if (scenario.DistributionType == AdjustScenarioDetailsDistribution.DistributionType.Original) { totalCost = scenario.TDDirectCosts ?? 0; } else { totalCost = currentScenarioDetails.Where(x => (x.Type == (int)ExpenditureCategoryModel.CategoryTypes.Labor || x.Type == (int)ExpenditureCategoryModel.CategoryTypes.Materials)) .Sum(x => x.Cost ?? 0); } var lmCostInPriorPeriod = currentScenarioDetails.Where(x => (x.Type == (int)ExpenditureCategoryModel.CategoryTypes.Labor || x.Type == (int)ExpenditureCategoryModel.CategoryTypes.Materials) && x.WeekEndingDate <= adjustHandler.CutOffDate) .Sum(x => x.Cost ?? 0); var lmActualsInPriorPeriod = actualsDetailsList.Where(x => (x.Type == (int)ExpenditureCategoryModel.CategoryTypes.Labor || x.Type == (int)ExpenditureCategoryModel.CategoryTypes.Materials) && x.WeekEndingDate <= adjustHandler.CutOffDate) .Sum(x => x.Cost ?? 0); var lmActualsInPostPeriod = actualsDetailsList.Where(x => (x.Type == (int)ExpenditureCategoryModel.CategoryTypes.Labor || x.Type == (int)ExpenditureCategoryModel.CategoryTypes.Materials) && x.WeekEndingDate > adjustHandler.CutOffDate) .Sum(x => x.Cost ?? 0); temporaryCalcResult = ComputeNONCalculatedCategories(calculatedNewScenario, adjustHandler.ActualsEndDate, adjustHandler.CutOffDate, moRateTranslate, temporaryCalcResult); // need to take max from forecast and actuals in prior period to exclude sum spent in prior period // also need to take actuals sum to exclude sum spent in the next period var costForAdjust = Math.Max(totalCost - Math.Max(lmCostInPriorPeriod, lmActualsInPriorPeriod) - lmActualsInPostPeriod, 0); if (temporaryCalcResult.mvActualCostCG_LM == 0) temporaryCalcResult.lvCGFactor = 0; else { decimal durationMult = 1m; if (OldDuration != scenario.Duration) durationMult = (decimal)scenario.Duration / (decimal)(OldDuration); var newBUDCLM = (dbObj.BUDirectCosts_LM - totalBtUpCostsMaterials) * durationMult + totalBtUpCostsMaterials; var modifier = 1 - (decimal)totalBtUpCostsMaterials / (decimal)newBUDCLM;//(dbObj.BUDirectCosts_LM); temporaryCalcResult.lvCGFactor = ((((costForAdjust * scenario.CGSplit) - temporaryCalcResult.mvUsedCostCG_LM) - temporaryCalcResult.mvActualCostCG_LM) / temporaryCalcResult.mvActualCostCG_LM); temporaryCalcResult.lvCGFactor = (temporaryCalcResult.lvCGFactor / modifier); } if (temporaryCalcResult.mvActualCostEFX_LM == 0) temporaryCalcResult.lvEFXFactor = 0; else temporaryCalcResult.lvEFXFactor = ((((costForAdjust * scenario.EFXSplit) - temporaryCalcResult.mvUsedCostEFX_LM) - temporaryCalcResult.mvActualCostEFX_LM) / temporaryCalcResult.mvActualCostEFX_LM); //TODO: Put mdtActualsEndWeek to temporaryCalcResult object in entire class // if it is new scenario we do not need to adjust quantities/costs ApplyCGEFXFactor(calculatedNewScenario, temporaryCalcResult, adjustHandler.ActualsEndDate, adjustHandler.CutOffDate, Guid.Empty.Equals(scenario.Id)); //TODO: need to closely review this functions and get rid of excessive data/parameters ComputeCalculatedCategories(calculatedNewScenario, temporaryCalcResult, adjustHandler.ActualsEndDate, adjustHandler.CutOffDate, scenario.Shots, scenario.Duration, moCatRowTranslate, moRateTranslate); } else { decimal totalCost = 0; if (scenario.DistributionType == AdjustScenarioDetailsDistribution.DistributionType.Original) totalCost = scenario.TDDirectCosts ?? 0; else totalCost = currentScenarioDetails.Sum(x => x.Cost ?? 0); var fullCostInPriorPeriod = currentScenarioDetails.Where(x => x.WeekEndingDate <= adjustHandler.CutOffDate) .Sum(x => x.Cost ?? 0); var fullActualsInPriorPeriod = actualsDetailsList.Where(x => x.WeekEndingDate <= adjustHandler.CutOffDate) .Sum(x => x.Cost ?? 0); var fullActualsInPostPeriod = actualsDetailsList.Where(x => x.WeekEndingDate > adjustHandler.CutOffDate) .Sum(x => x.Cost ?? 0); ComputeNONCalculatedCategories(calculatedNewScenario, adjustHandler.ActualsEndDate, adjustHandler.CutOffDate, moRateTranslate, temporaryCalcResult); //TODO: need to closely review this functions and get rid of excessive data/parameters ComputeCalculatedCategories(calculatedNewScenario, temporaryCalcResult, adjustHandler.ActualsEndDate, adjustHandler.CutOffDate, scenario.Shots, scenario.Duration, moCatRowTranslate, moRateTranslate); // need to take max from forecast and actuals in prior period to exclude sum spent in prior period // also need to take actuals sum to exclude sum spent in the next period var costForAdjust = Math.Max(totalCost - Math.Max(fullCostInPriorPeriod, fullActualsInPriorPeriod) - fullActualsInPostPeriod, 0); if (temporaryCalcResult.mvActualCostCG == 0) temporaryCalcResult.lvCGFactor = 0; else temporaryCalcResult.lvCGFactor = (((costForAdjust * scenario.CGSplit) - temporaryCalcResult.mvUsedCostCG) - temporaryCalcResult.mvActualCostCG - temporaryCalcResult.mvActualCostCG_Fee) / temporaryCalcResult.mvActualCostCG; if (temporaryCalcResult.mvActualCostEFX == 0) temporaryCalcResult.lvEFXFactor = 0; else temporaryCalcResult.lvEFXFactor = (((costForAdjust * scenario.EFXSplit) - temporaryCalcResult.mvUsedCostEFX) - temporaryCalcResult.mvActualCostEFX - temporaryCalcResult.mvActualCostEFX_Fee) / temporaryCalcResult.mvActualCostEFX; // if it is new scenario we do not need to adjust quantities/costs ApplyCGEFXFactor(calculatedNewScenario, temporaryCalcResult, adjustHandler.ActualsEndDate, adjustHandler.CutOffDate, Guid.Empty.Equals(scenario.Id)); } #endregion } #region Committing scenario details to the data store var resultScenario = new Dictionary>(calculatedNewScenario.Count()); foreach (var expcat in calculatedNewScenario.Keys) resultScenario.Add(expcat, new List()); var oldStartDate = DbContext.ScenarioDetail.Where(t => t.ParentID == scenario.Id).Select(x => x.WeekEndingDate).Min(); var _newStartDate = calculatedNewScenario.FirstOrDefault().Value.Select(y => y.WeekEndingDate).Min(); bool startDateChanged = oldStartDate != _newStartDate; var scenarioDetailItems = currentScenarioDetails.ToDictionary(t => t.Id); foreach (var recalculatedDetail in from expCatGroup in calculatedNewScenario from scenarioDetailsItem in expCatGroup.Value select scenarioDetailsItem) { VW_ScenarioAndProxyDetails currentDbItem = null; if (!startDateChanged) { if (scenarioDetailItems.ContainsKey(recalculatedDetail.Id)) currentDbItem = scenarioDetailItems[recalculatedDetail.Id]; } else currentDbItem = scenarioDetailItems.Values.Where(x => x.ExpenditureCategoryId == recalculatedDetail.ExpenditureCategoryId && x.WeekEndingDate == recalculatedDetail.WeekEndingDate).FirstOrDefault(); if (currentDbItem == null) { var currentDbItemToAdd = new ScenarioDetailsListItem { Id = Guid.NewGuid(), ParentId = scenario.Id.Value, ExpenditureCategoryId = recalculatedDetail.ExpenditureCategoryId, WeekEndingDate = recalculatedDetail.WeekEndingDate, Quantity = recalculatedDetail.Quantity, WeekOrdinal = recalculatedDetail.WeekOrdinal, DetailCost = recalculatedDetail.DetailCost, CreditId = recalculatedDetail.CreditId, ExpenditureCategoryName = recalculatedDetail.ExpenditureCategoryName, UOMId = recalculatedDetail.UOMId, UseType = recalculatedDetail.UseType, CategoryType = recalculatedDetail.CategoryType, CG_EFX = recalculatedDetail.CG_EFX }; resultScenario[recalculatedDetail.ExpenditureCategoryId].Add(currentDbItemToAdd); } else { var currentDbItemToAdd = new ScenarioDetailsListItem { Id = currentDbItem.Id, ParentId = scenario.Id.Value, ExpenditureCategoryId = currentDbItem.ExpenditureCategoryId.Value, CreditId = currentDbItem.CreditId, ExpenditureCategoryName = currentDbItem.ExpCategoryWithCcName, // SA. ENV-839 UOMId = currentDbItem.UOMId, UseType = (ExpenditureCategoryModel.UseTypes)currentDbItem.UseType, CategoryType = (ExpenditureCategoryModel.CategoryTypes)(currentDbItem.Type ?? 0), CG_EFX = (currentDbItem.CGEFX ?? string.Empty).Trim().Equals(ExpenditureCategoryModel.CgEfx.CG.ToString()) ? ExpenditureCategoryModel.CgEfx.CG : ExpenditureCategoryModel.CgEfx.EFX, WeekEndingDate = recalculatedDetail.WeekEndingDate, WeekOrdinal = recalculatedDetail.WeekOrdinal }; if (scenario.NeedToAdjustMargin) { currentDbItemToAdd.Quantity = recalculatedDetail.Quantity; currentDbItemToAdd.DetailCost = recalculatedDetail.DetailCost; } else { currentDbItemToAdd.Quantity = currentDbItem.Quantity ?? 0; currentDbItemToAdd.DetailCost = currentDbItem.Cost ?? 0; } resultScenario[recalculatedDetail.ExpenditureCategoryId].Add(currentDbItemToAdd); } } #endregion return resultScenario; } public Dictionary PrepareScenarioTeams(Guid expCatId, ScenarioInfoModel scenario, List fiscalCalendar, Dictionary teams, bool isUpdate, int currentPeriods) { var result = new Dictionary(); if (scenario == null) throw new ArgumentNullException("scenario"); if (fiscalCalendar == null || fiscalCalendar.Count <= 0) throw new ArgumentNullException("fiscalCalendar"); if (teams == null || teams.Count <= 0) return result; foreach (var team in teams) { var clonedTeam = team.Value.Clone(); var oldTeamQuantityValues = clonedTeam.QuantityValues; var newTeamQuantityValues = RecalculateScenarioDetails(expCatId, scenario, fiscalCalendar, oldTeamQuantityValues, isUpdate, currentPeriods); var teamQuantityKeys = oldTeamQuantityValues.Keys.Union(newTeamQuantityValues.Keys).Distinct().ToList(); foreach (var key in teamQuantityKeys) { if (!clonedTeam.RestQuantityValues.ContainsKey(key)) continue; var oldQuantity = oldTeamQuantityValues.ContainsKey(key) ? oldTeamQuantityValues[key] : 0; var newQuantity = newTeamQuantityValues.ContainsKey(key) ? newTeamQuantityValues[key] : 0; clonedTeam.RestQuantityValues[key] -= (newQuantity - oldQuantity); } clonedTeam.QuantityValues = newTeamQuantityValues; if (clonedTeam.Resources != null && clonedTeam.Resources.Count > 0) { foreach (var resource in clonedTeam.Resources) { var oldResourceQuantityValues = resource.Value.QuantityValues; var newResourceQuantityValues = RecalculateScenarioDetails(expCatId, scenario, fiscalCalendar, oldResourceQuantityValues, isUpdate, currentPeriods); var resourceQuantityKeys = oldResourceQuantityValues.Keys.Union(newResourceQuantityValues.Keys).Distinct().ToList(); foreach (var key in resourceQuantityKeys) { if (!resource.Value.RestQuantityValues.ContainsKey(key)) continue; var oldQuantity = oldResourceQuantityValues.ContainsKey(key) ? oldResourceQuantityValues[key] : 0; var newQuantity = newResourceQuantityValues.ContainsKey(key) ? newResourceQuantityValues[key] : 0; resource.Value.RestQuantityValues[key] -= (newQuantity - oldQuantity); } resource.Value.QuantityValues = newResourceQuantityValues; } } result.Add(team.Key, clonedTeam); } return result; } private Dictionary RecalculateScenarioDetails(Guid expCatId, ScenarioInfoModel scenario, List fiscalCalendar, Dictionary scenarioDetails, bool isUpdate, int currentPeriods) { if (scenario == null) throw new ArgumentNullException("scenario"); if (fiscalCalendar == null || fiscalCalendar.Count <= 0) throw new ArgumentNullException("fiscalCalendar"); if (scenarioDetails == null || scenarioDetails.Count <= 0) return new Dictionary(); var adjustHandler = new AdjustScenarioDetailsDistribution(DbContext) { CurrentPeriods = currentPeriods, NewPeriods = scenario.Duration, FiscalCalendarWeeks = fiscalCalendar.ToArray(), IsUpdate = isUpdate, CurrentActuals = new List(), ScenarioId = scenario.Id.Value, StartDate = Utils.ConvertFromUnixDate(scenario.StartDate), EndDate = Utils.ConvertFromUnixDate(scenario.EndDate), UseLMMargin = scenario.UseLMMargin, ActualsEndDate = Utils.ConvertFromUnixDate(scenario.ActualEndDate), CutOffDate = ResolveCutOffDate(scenario.DateForStartOfChanges, scenario.ActualEndDate), Type = scenario.DistributionType }; adjustHandler.CurrentScenarioDetails = scenarioDetails.Select(x => new ScenarioManager.ScenarioDetailsListItem() { ExpenditureCategoryId = expCatId, WeekEndingDate = Utils.ConvertFromUnixDate(long.Parse(x.Key)), Quantity = x.Value }).GroupBy(x => x.ExpenditureCategoryId).ToDictionary(x => x.Key, g => g.ToList()); var result = adjustHandler.CalculateDistribution(); return result[expCatId].Where(x => x.WeekEndingDate.HasValue) .Select(x => new { x.WeekEndingDate, x.Quantity }) .ToDictionary(x => Utils.ConvertToUnixDate(x.WeekEndingDate.Value).ToString(), g => g.Quantity); } private static DateTime ResolveCutOffDate(long? dateForStartOfChange, long actualsEndDate) { var aEndDate = Utils.ConvertFromUnixDate(actualsEndDate); var cutOffDate = dateForStartOfChange.HasValue ? Utils.ConvertFromUnixDate(dateForStartOfChange.Value) : Constants.UnixEpochDate; if (aEndDate > cutOffDate) cutOffDate = aEndDate; return cutOffDate; } /// /// Saves scenario and scenario details. /// /// public override Scenario Save(ScenarioModel model) { if (model == null) throw new ArgumentNullException("model"); #region prepare scenario object Scenario dbObj = null; if (model.Id != Guid.Empty) dbObj = DataTable.Find(model.Id); if (model.Id != Guid.Empty && !ScenarioStatus.Draft.Equals(model.Status)) { model.TemplateId = dbObj.TemplateId ?? Guid.Empty; model.PriorWeekCutOff = model.AllowAdjustment ? new DateTime(1753, 1, 1) : model.PriorWeekCutOff; model.StartDate = model.AllowAdjustment ? model.StartDate : dbObj.StartDate; //if (!model.UseLMMargin) // model.GrossMargin = dbObj.ExpectedGrossMargin * 100.0M; //else // model.LMMargin = dbObj.ExpectedGrossMargin_LM * 100.0M; } if (dbObj == null) { dbObj = InitInstance(); } model.CopyTo(dbObj); #endregion #region Load referenced objects from DB decimal totalBtUpCostsMaterials = 0; var project = DbContext.Projects.Find(model.ParentId); if (project == null) throw new BLLException("Unable to find selected project. Please, reload the page or try again later."); var tempEndDate = model.EndDate.Value.AddDays(6); var fiscalCalendars = DbContext.FiscalCalendars.Where( t => t.Type == (int)FiscalCalendarModel.FiscalYearType.Week && t.EndDate >= model.StartDate && t.EndDate <= tempEndDate && t.NonWorking == 0 && t.AdjustingPeriod == false).OrderBy(t => t.EndDate).ToArray(); var rates = new List(); var gRates = new List(); var lstDctEcAddOn = new List(); var lstExpCatsToExclude = new List(); //this variable will be used to keep track of expenditure categories defined in the template that are not selected... if (!model.AsDraft) if (model.Id == Guid.Empty || ScenarioStatus.Draft.Equals(model.Status)) // if new scenario { #region load referenced data for new scenario // Expenditure Categories related to template scenario (GetExpenditureCategoriesUnique method in old code) var templateExpCats = DbContext.VW_ExpCategoriesInScenario.Where(t => t.ScenarioID == model.TemplateId).Select(t => new { t.Id, t.UseType, t.ExpCategoryWithCcName // SA.ENV-756. ENV-839 }).ToArray(); // list of expenditure category Ids related to the template scenario var lstCatFromTemplate = templateExpCats.Select(t => new { t.Id, t.ExpCategoryWithCcName }).ToList(); // SA. ENV-756 var lsUnqCatList = templateExpCats.Select(t => t.Id).ToList(); // list of template scenario's expenditure category Ids with "Calculated" UseType var arrCalculatedIds = templateExpCats.Where(t => t.UseType == (int)ExpenditureCategoryModel.UseTypes.Calculated).Select(t => t.Id).ToArray(); var calculatedChildren = new VW_Expenditure2Calculation[0]; if (arrCalculatedIds.Length > 0) { calculatedChildren = DbContext.VW_Expenditure2Calculation.Where(t => arrCalculatedIds.Contains(t.ParentId)).ToArray(); } // add children of calculated expenditures to the list of unique expenditure category Ids foreach (var item in calculatedChildren.Where(t => t.ExpenditureCategoryID != null && !lsUnqCatList.Contains(t.ExpenditureCategoryID.Value))) { if (!lsUnqCatList.Contains(item.ExpenditureCategoryID.Value)) { lsUnqCatList.Add(item.ExpenditureCategoryID.Value); lstCatFromTemplate.Add(new { Id = item.ExpenditureCategoryID.Value, ExpCategoryWithCcName = item.ExpCategoryWithCcName }); // SA. ENV-756 lstDctEcAddOn.Add(new AdditionalExpCatItem { ExpenditureCategoryId = item.ExpenditureCategoryID.Value, LastUpdate = DateTime.Today, CategoryType = (ExpenditureCategoryModel.CategoryTypes)(item.Type ?? 0), Quantity = 0, DetailCost = 0, SortOrder = item.SortOrder, UseType = (ExpenditureCategoryModel.UseTypes)(item.UseType ?? 1), WeekOrdinal = 0, CG_EFX = (item.CGEFX ?? string.Empty).Trim().Equals(ExpenditureCategoryModel.CgEfx.CG.ToString()) ? ExpenditureCategoryModel.CgEfx.CG : ExpenditureCategoryModel.CgEfx.EFX, CategoryName = item.ExpCategoryWithCcName, // SA. ENV-756. ENV-839 SysField1 = item.SystemAttributeOne, SysField2 = Guid.Empty, CreditId = item.CreditId, GlId = item.GLId, UOMId = item.UOMId, //IsCalculated = true // stored as "P" + ExpCatId as key field in old code }); } } lstDctEcAddOn.AddRange(DbContext.VW_Expenditure2Category.Where(t => !lsUnqCatList.Contains(t.Id)). Select(t => new AdditionalExpCatItem { ExpenditureCategoryId = t.Id, LastUpdate = DateTime.Today, CategoryType = (ExpenditureCategoryModel.CategoryTypes)(t.Type ?? 0), Quantity = 0, DetailCost = 0, SortOrder = t.SortOrder, UseType = (ExpenditureCategoryModel.UseTypes)(t.UseType ?? 1), WeekOrdinal = 0, CG_EFX = (t.CGEFX ?? string.Empty).Trim().Equals(ExpenditureCategoryModel.CgEfx.CG.ToString()) ? ExpenditureCategoryModel.CgEfx.CG : ExpenditureCategoryModel.CgEfx.EFX, CategoryName = t.ExpCategoryWithCcName, // SA. ENV-756. ENV-839 SysField1 = t.SystemAttributeOne, SysField2 = Guid.Empty, CreditId = t.CreditId, GlId = t.GLId, UOMId = t.UOMId, //IsCalculated = true // stored as "P" + ExpCatId as key field in old code }).ToList()); var lstExpCatsForRates = lstCatFromTemplate.Select(t => t.Id).ToList(); //list of template expenditures and children of calculated expenditures var lstExpCatsForGRates = new List(); //list of unchecked non-template expenditure categories foreach (var expenditureItem in model.Expenditures) { if (!Constants.EXPENDITURE_INTEMPLATE_TITLE.Equals(expenditureItem.Group)) { if (expenditureItem.Checked) lstExpCatsForRates.Add(expenditureItem.Id); else { lstExpCatsForGRates.Add(expenditureItem.Id); var id2Exclude = expenditureItem.Id; lstDctEcAddOn.RemoveAll(t => t.ExpenditureCategoryId == id2Exclude); } } else { if (!expenditureItem.Checked) { lstExpCatsToExclude.Add(expenditureItem.Id); var id2Exclude = expenditureItem.Id; lstDctEcAddOn.RemoveAll(t => t.ExpenditureCategoryId == id2Exclude); } } } if (lstExpCatsForRates.Count > 0) { rates = DbContext.Rates.Where( t => t.Type == (short)RateModel.RateType.Global && lstExpCatsForRates.Contains(t.ExpenditureCategoryId)) .OrderBy(t => t.ExpenditureCategoryId) .ThenBy(t => t.EndDate) .ToList(); } if (lstExpCatsForGRates.Count > 0) { gRates = DbContext.Rates.Where( t => t.Type == (short)RateModel.RateType.Global && lstExpCatsForGRates.Contains(t.ExpenditureCategoryId)) .OrderBy(t => t.ExpenditureCategoryId) .ThenBy(t => t.EndDate) .ToList(); } #endregion } else // if edit scenario { #region load referenced data for existing scenario rates = DbContext.Rates.Where( t => t.Type == (short)RateModel.RateType.Derived && t.ParentId == model.Id) .OrderBy(t => t.ExpenditureCategoryId) .ThenBy(t => t.EndDate).ToList(); var lsExpCatsForLRates = rates.Select(t => t.ExpenditureCategoryId).ToArray(); gRates = lsExpCatsForLRates.Length > 0 ? DbContext.Rates.Where(t => t.Type == (short)RateModel.RateType.Global && !lsExpCatsForLRates.Contains(t.ExpenditureCategoryId)) .OrderBy(t => t.ExpenditureCategoryId) .ThenBy(t => t.EndDate) .ToList() : DbContext.Rates.Where(t => t.Type == (short)RateModel.RateType.Global) .OrderBy(t => t.ExpenditureCategoryId) .ThenBy(t => t.EndDate) .ToList(); var list = new List(); var categories = DbContext.VW_ExpCategoriesInScenario.Where(t => t.ScenarioID == model.Id).ToArray(); materialsECs = categories.Where(x => x.ECType.Value == (int)ExpenditureCategoryModel.CategoryTypes.Materials).Select(x => x.Id).ToArray(); var data = (from sd in DbContext.ScenarioDetail join vw in DbContext.VW_Expenditure2Category on sd.ExpenditureCategoryId equals vw.Id where sd.ParentID == model.Id select new { sd.Id, sd.Cost, ExpenditureType = vw.Type, ScenarioId = sd.ParentID, }).Distinct().ToArray(); //var totalBtUpCosts = data.Sum(t => t.Cost); totalBtUpCostsMaterials = data.Where(t => t.ExpenditureType == (int)ExpenditureCategoryModel.CategoryTypes.Materials).Sum(x => (decimal)x.Cost); var lstIds = categories.Select(t => t.Id).ToList(); var arrCalculatedIds = categories.Where(t => t.UseType == (int)ExpenditureCategoryModel.UseTypes.Calculated).Select(t => t.Id).ToArray(); if (arrCalculatedIds.Length > 0) { foreach (var item in DbContext.VW_Expenditure2Calculation.Where(t => arrCalculatedIds.Contains(t.ParentId))) { if (!item.ExpenditureCategoryID.HasValue) continue; if (!lstIds.Contains(item.ExpenditureCategoryID.Value)) lstIds.Add(item.ExpenditureCategoryID.Value); } } // add not checked expenditure categories for Labor/Materials/Usage types list.AddRange(DbContext.VW_Expenditure2Category.Where(t => !lstIds.Contains(t.Id)) .OrderBy(t => t.Type) .ThenBy(t => t.ExpCategoryWithCcName) // SA. ENV-756 .Select(t => new { t.Id, t.ExpCategoryWithCcName, // SA. ENV-756. ENV-839 t.Type, t.GLId, t.CGEFX, t.CreditId, t.SortOrder, t.SystemAttributeOne, t.SystemAttributeTwo, t.UOMId, t.UseType }).ToArray() .Select(t => new AdditionalExpCatItem { ExpenditureCategoryId = t.Id, CategoryName = t.ExpCategoryWithCcName, // SA. ENV-839 CG_EFX = (t.CGEFX ?? string.Empty).Trim().Equals(ExpenditureCategoryModel.CgEfx.CG.ToString()) ? ExpenditureCategoryModel.CgEfx.CG : ExpenditureCategoryModel.CgEfx.EFX, CategoryType = (ExpenditureCategoryModel.CategoryTypes)(t.Type ?? 0), CreditId = t.CreditId, DetailCost = 0, GlId = t.GLId, LastUpdate = DateTime.Today, Quantity = 0, SortOrder = t.SortOrder, SysField1 = t.SystemAttributeOne, SysField2 = Guid.Empty, UOMId = t.UOMId, UseType = (ExpenditureCategoryModel.UseTypes)(t.UseType ?? 1) })); #endregion var allCheckedItemIds = model.Expenditures.Where(t => t.Checked).Select(t => t.Id).ToList(); if (model.AllowAdjustment) { var actualScenario = DbContext.VW_Scenario2Project.Where( t => t.ParentId == dbObj.ParentId && t.Type == (int)ScenarioType.Actuals).FirstOrDefault(); if (actualScenario != null) { var actualExpCats = DbContext.VW_ScenarioAndProxyDetails.Where(t => t.ParentID == actualScenario.Id) .Select(x => x.ExpenditureCategoryId).Distinct().ToArray(); allCheckedItemIds.AddRange(actualExpCats.Where(x => !allCheckedItemIds.Contains(x.Value)).Select(x => x.Value).ToList()); } } // get added categories (for those who were unchecked and become checked) lstDctEcAddOn = list.Where(t => allCheckedItemIds.Contains(t.ExpenditureCategoryId)).ToList(); } #endregion var OldDuration = dbObj.Duration; #region Save Scenario // setup fields for new scenario if (model.Id == Guid.Empty || ScenarioStatus.Draft.Equals(model.Status)) { if (string.IsNullOrEmpty(model.Color)) dbObj.Color = project.Color; dbObj.FreezeRevenue = true; if (!model.UseLMMargin) { dbObj.ExpectedGrossMargin_LM = 0; dbObj.TDDirectCosts_LM = 0; } else { dbObj.ExpectedGrossMargin = 0; dbObj.TDDirectCosts = 0; } if (!model.IsRevenueGenerating) { dbObj.ProjectedRevenue = 0; dbObj.ExpectedGrossMargin = 0; dbObj.ExpectedGrossMargin_LM = 0; } } if (model.IsRevenueGenerating) { if (!model.UseLMMargin) { dbObj.TDDirectCosts = dbObj.ProjectedRevenue - (dbObj.ProjectedRevenue * dbObj.ExpectedGrossMargin); } else { dbObj.TDDirectCosts_LM = dbObj.ProjectedRevenue - (dbObj.ProjectedRevenue * dbObj.ExpectedGrossMargin_LM); } } else { dbObj.TDDirectCosts = model.TDDirectCosts; dbObj.TDDirectCosts_LM = 0; } if (dbObj.Shots > 0) dbObj.TDRevenueShot = dbObj.ProjectedRevenue / dbObj.Shots; else dbObj.TDRevenueShot = 0; dbObj.Duration = fiscalCalendars.Length; if (model.Id == Guid.Empty) DataTable.Add(dbObj); else DbContext.Entry(dbObj).State = EntityState.Modified; #endregion #region Create Scenario Details if (!model.AsDraft) { var lvaCurrScenario = model.Id == Guid.Empty || ScenarioStatus.Draft.Equals(model.Status) ? GetTemplateScenarioDetailsPerExpCat(model.TemplateId ?? Guid.Empty, lstDctEcAddOn.ToList(), dbObj.Id, lstExpCatsToExclude) : GetTemplateScenarioDetailsPerExpCat(model.Id, lstDctEcAddOn.ToList(), Guid.Empty, new Collection()); if (lvaCurrScenario.Count == 0 || lvaCurrScenario.FirstOrDefault().Value.Count == 0) throw new BLLException("Unable to calculate periods for provided information."); var temporaryCalcResult = new TemporaryCalculationResults(); var mdtActualsEndWeek = new DateTime(1753, 1, 1); var actualScenarioDetails = new List(); if (model.GrowthScenario || !model.UseActuals) mdtActualsEndWeek = new DateTime(1753, 1, 1); else { //retrieve actuals scenario end date... var actualScenarios = DbContext.VW_Scenario2Project.Where( t => t.ParentId == dbObj.ParentId && t.Type == (int)ScenarioType.Actuals).ToArray(); if (actualScenarios.Length > 0) { var actualScenario = actualScenarios[0]; if (actualScenario.EndDate != null) mdtActualsEndWeek = actualScenario.EndDate.Value; if (model.Id == Guid.Empty || ScenarioStatus.Draft.Equals(model.Status)) actualScenarioDetails = DbContext.VW_ScenarioAndProxyDetails.Where(t => t.ParentID == actualScenario.Id) .OrderBy(t => t.ExpenditureCategoryId) .ThenBy(t => t.WeekEndingDate).ToList(); else actualScenarioDetails = DbContext.VW_ScenarioAndProxyDetails.Where(t => t.ParentID == actualScenario.Id) .ToList(); if (actualScenarioDetails.Count() > 0) { foreach (var actualScenarioDetailItem in actualScenarioDetails) { if (ExpenditureCategoryModel.CgEfx.CG.ToString().Equals(actualScenarioDetailItem.CGEFX)) { temporaryCalcResult.mvUsedCostCG += actualScenarioDetailItem.Cost ?? 0; if ((int)ExpenditureCategoryModel.CategoryTypes.Labor == actualScenarioDetailItem.Type || (int)ExpenditureCategoryModel.CategoryTypes.Materials == actualScenarioDetailItem.Type) temporaryCalcResult.mvUsedCostCG_LM += actualScenarioDetailItem.Cost ?? 0; } else if ( ExpenditureCategoryModel.CgEfx.EFX.ToString() .Equals(actualScenarioDetailItem.CGEFX)) { temporaryCalcResult.mvUsedCostEFX += actualScenarioDetailItem.Cost ?? 0; if ((int)ExpenditureCategoryModel.CategoryTypes.Labor == actualScenarioDetailItem.Type || (int)ExpenditureCategoryModel.CategoryTypes.Materials == actualScenarioDetailItem.Type) temporaryCalcResult.mvUsedCostEFX_LM += actualScenarioDetailItem.Cost ?? 0; } } } } else { mdtActualsEndWeek = new DateTime(1753, 1, 1); } } var adjustHandler = new AdjustScenarioDetailsDistribution(DbContext) { CurrentPeriods = lvaCurrScenario.FirstOrDefault().Value.Count, NewPeriods = dbObj.Duration ?? 0, CurrentScenarioDetails = lvaCurrScenario, CurrentActuals = actualScenarioDetails, FiscalCalendarWeeks = fiscalCalendars, IsUpdate = model.Id != Guid.Empty, ScenarioId = dbObj.Id, StartDate = dbObj.StartDate, EndDate = dbObj.EndDate, UseLMMargin = (dbObj.UseLMMargin ?? 0) > 0, CutOffDate = model.PriorWeekCutOff ?? new DateTime(1753, 1, 1), ActualsEndDate = mdtActualsEndWeek, MaterialsEcs = materialsECs //, //MaterialsMultiplyer = 1 - totalBtUpCostsMaterials / (decimal)(dbObj.BUDirectCosts_LM) }; var calculatedNewScenario = adjustHandler.CalculateDistribution(); #region Creating and Indexing derived Rate table var loERates = new Dictionary(); var moRateTranslate = new Dictionary>(); var lsLastExpCatOID = Guid.Empty; if (rates.Count > 0) { foreach (var rate in rates.Where(t => t.EndDate != null)) { if (rate.ExpenditureCategoryId != lsLastExpCatOID) { if (lsLastExpCatOID != Guid.Empty) moRateTranslate.Add(lsLastExpCatOID, loERates); lsLastExpCatOID = rate.ExpenditureCategoryId; loERates = new Dictionary(); } if (!loERates.ContainsKey(rate.EndDate)) loERates.Add(rate.EndDate, rate.Rate1); } moRateTranslate.Add(lsLastExpCatOID, loERates); } #endregion #region Indexing Global Rate table if (gRates.Count > 0) { lsLastExpCatOID = Guid.Empty; foreach (var gRate in gRates.Where(t => t.EndDate != null)) { if (!lsLastExpCatOID.Equals(gRate.ExpenditureCategoryId)) { if (lsLastExpCatOID != Guid.Empty && !moRateTranslate.ContainsKey(lsLastExpCatOID)) moRateTranslate.Add(lsLastExpCatOID, loERates); lsLastExpCatOID = gRate.ExpenditureCategoryId; loERates = new Dictionary(); } if (loERates.ContainsKey(gRate.EndDate)) loERates[gRate.EndDate] = gRate.Rate1; else loERates.Add(gRate.EndDate, gRate.Rate1); } if (moRateTranslate.ContainsKey(lsLastExpCatOID)) moRateTranslate[lsLastExpCatOID] = loERates; else moRateTranslate.Add(lsLastExpCatOID, loERates); } rates.Clear(); gRates.Clear(); #endregion #region Calculate the CG/EFX Split // store row of each calculated and fee expenditure category var moCatRowTranslate = calculatedNewScenario.Where( item => item.Value.Count > 0 && ExpenditureCategoryModel.UseTypes.Calculated != item.Value[0].UseType && ExpenditureCategoryModel.UseTypes.Fee != item.Value[0].UseType) .ToDictionary(item => item.Key, item => item.Value); if (dbObj.UseLMMargin == 1) { temporaryCalcResult = ComputeNONCalculatedCategories(calculatedNewScenario, adjustHandler.ActualsEndDate, adjustHandler.CutOffDate, moRateTranslate, temporaryCalcResult); //var actualsTotal = dbcon.scena if (model.Id == Guid.Empty || ScenarioStatus.Draft.Equals(model.Status)) { if (temporaryCalcResult.mvActualCostCG_LM > 0) temporaryCalcResult.lvCGFactor = (((((dbObj.TDDirectCosts_LM ?? 0) * (dbObj.CGSplit ?? 0)) - temporaryCalcResult.mvUsedCostCG_LM) - temporaryCalcResult.mvActualCostCG_LM) / temporaryCalcResult.mvActualCostCG_LM); else temporaryCalcResult.lvCGFactor = 0; if (temporaryCalcResult.mvActualCostEFX_LM > 0) temporaryCalcResult.lvEFXFactor = (((((dbObj.TDDirectCosts_LM ?? 0) * (dbObj.EFXSplit ?? 0)) - temporaryCalcResult.mvUsedCostEFX_LM) - temporaryCalcResult.mvActualCostEFX_LM) / temporaryCalcResult.mvActualCostEFX_LM); else temporaryCalcResult.lvEFXFactor = 0; } else { if (temporaryCalcResult.mvActualCostCG_LM == 0) temporaryCalcResult.lvCGFactor = 0; else { decimal durationMult = 1m; if (OldDuration != dbObj.Duration) durationMult = (decimal)dbObj.Duration.Value / (decimal)(OldDuration.Value); var newBUDCLM = (dbObj.BUDirectCosts_LM - totalBtUpCostsMaterials) * durationMult + totalBtUpCostsMaterials; var modifier = 1 - totalBtUpCostsMaterials / newBUDCLM;//(dbObj.BUDirectCosts_LM); temporaryCalcResult.lvCGFactor = (((((dbObj.TDDirectCosts_LM ?? 0) * (dbObj.CGSplit ?? 0)) - temporaryCalcResult.mvUsedCostCG_LM) - temporaryCalcResult.mvActualCostCG_LM) / temporaryCalcResult.mvActualCostCG_LM); temporaryCalcResult.lvCGFactor = (temporaryCalcResult.lvCGFactor / (decimal)modifier); } if (temporaryCalcResult.mvActualCostEFX_LM == 0) temporaryCalcResult.lvEFXFactor = 0; else temporaryCalcResult.lvEFXFactor = (((((dbObj.TDDirectCosts_LM ?? 0) * (dbObj.EFXSplit ?? 0)) - temporaryCalcResult.mvUsedCostEFX_LM) - temporaryCalcResult.mvActualCostEFX_LM) / temporaryCalcResult.mvActualCostEFX_LM); } //TODO: Put mdtActualsEndWeek to temporaryCalcResult object in entire class ApplyCGEFXFactor(calculatedNewScenario, temporaryCalcResult, adjustHandler.ActualsEndDate, adjustHandler.CutOffDate, dbObj.FreezeRevenue, model.UseLMMargin); //TODO: need to closely review this functions and get rid of excessive data/parameters ComputeCalculatedCategories(calculatedNewScenario, temporaryCalcResult, adjustHandler.ActualsEndDate, adjustHandler.CutOffDate, model.TotalMilestones, dbObj.Duration, moCatRowTranslate, moRateTranslate); } else { ComputeNONCalculatedCategories(calculatedNewScenario, adjustHandler.ActualsEndDate, adjustHandler.CutOffDate, moRateTranslate, temporaryCalcResult); //TODO: need to closely review this functions and get rid of excessive data/parameters ComputeCalculatedCategories(calculatedNewScenario, temporaryCalcResult, adjustHandler.ActualsEndDate, adjustHandler.CutOffDate, model.TotalMilestones, dbObj.Duration, moCatRowTranslate, moRateTranslate); if (model.Id == Guid.Empty || ScenarioStatus.Draft.Equals(model.Status)) { if (temporaryCalcResult.mvActualCostCG > 0) temporaryCalcResult.lvCGFactor = (((dbObj.TDDirectCosts ?? 0 * dbObj.CGSplit ?? 0) - temporaryCalcResult.mvUsedCostCG) - temporaryCalcResult.mvActualCostCG - temporaryCalcResult.mvActualCostCG_Fee) / temporaryCalcResult.mvActualCostCG; else temporaryCalcResult.lvCGFactor = 0; if (temporaryCalcResult.mvActualCostEFX > 0) temporaryCalcResult.lvEFXFactor = (((dbObj.TDDirectCosts ?? 0 * dbObj.EFXSplit ?? 0) - temporaryCalcResult.mvUsedCostEFX) - temporaryCalcResult.mvActualCostEFX - temporaryCalcResult.mvActualCostEFX_Fee) / temporaryCalcResult.mvActualCostEFX; else temporaryCalcResult.lvEFXFactor = 0; } else { if (temporaryCalcResult.mvActualCostCG == 0) temporaryCalcResult.lvCGFactor = 0; else { if (model.IsRevenueGenerating) temporaryCalcResult.lvCGFactor = (((dbObj.TDDirectCosts * dbObj.CGSplit ?? 0) - temporaryCalcResult.mvUsedCostCG) - temporaryCalcResult.mvActualCostCG - temporaryCalcResult.mvActualCostCG_Fee) / temporaryCalcResult.mvActualCostCG; else { temporaryCalcResult.lvCGFactor = (((dbObj.TDDirectCosts ?? 0 * dbObj.CGSplit ?? 0) - temporaryCalcResult.mvUsedCostCG) - temporaryCalcResult.mvActualCostCG - temporaryCalcResult.mvActualCostCG_Fee) / temporaryCalcResult.mvActualCostCG; } } if (temporaryCalcResult.mvActualCostEFX == 0) temporaryCalcResult.lvEFXFactor = 0; else temporaryCalcResult.lvEFXFactor = (((dbObj.TDDirectCosts ?? 0 * dbObj.EFXSplit ?? 0) - temporaryCalcResult.mvUsedCostEFX) - temporaryCalcResult.mvActualCostEFX - temporaryCalcResult.mvActualCostEFX_Fee) / temporaryCalcResult.mvActualCostEFX; } ApplyCGEFXFactor(calculatedNewScenario, temporaryCalcResult, adjustHandler.ActualsEndDate, adjustHandler.CutOffDate, dbObj.FreezeRevenue, model.UseLMMargin); } if (model.Id == Guid.Empty || ScenarioStatus.Draft.Equals(model.Status)) CopyActuals(calculatedNewScenario, actualScenarioDetails, mdtActualsEndWeek); #endregion #region Committing scenario details to the data store var newId = dbObj.Id; var oldStartDate = DbContext.ScenarioDetail.Where(t => t.ParentID == newId).Select(x => x.WeekEndingDate).Min(); var _newStartDate = calculatedNewScenario.FirstOrDefault().Value.Select(y => y.WeekEndingDate).Min(); bool startDateChanged = oldStartDate != _newStartDate; //if (oldStartDate != _newStartDate) DbContext.ScenarioDetail.RemoveRange(DbContext.ScenarioDetail.Where(t => t.ParentID == newId)); var scenarioDetailItems = DbContext.ScenarioDetail.Where(t => t.ParentID == newId).ToDictionary(t => t.Id); //var newScenarioDetailItems = (from expCatGroup in calculatedNewScenario // from scenarioDetailsItem in expCatGroup.Value // select scenarioDetailsItem).ToList(); //var detailsToDelete = scenarioDetailItems.Where(x => !newScenarioDetailItems.Select(y => y.Id).ToList().Contains(x.Key)).Select(x => x.Key).ToList(); ////delete details to delete to eliminae duplicates //var SDs = scenarioDetailItems.Where(x => detailsToDelete.Contains(x.Key)).Select(x => x.Value).ToArray(); //DbContext.ScenarioDetail.RemoveRange(SDs); foreach (var dbDetailsObj in from expCatGroup in calculatedNewScenario from scenarioDetailsItem in expCatGroup.Value select scenarioDetailsItem) { ScenarioDetail currentDbItem = null; if (model.Id != Guid.Empty) { if (!startDateChanged) { if (scenarioDetailItems.ContainsKey(dbDetailsObj.Id)) currentDbItem = scenarioDetailItems[dbDetailsObj.Id]; } else { currentDbItem = scenarioDetailItems.Values.Where(x => x.ExpenditureCategoryId == dbDetailsObj.ExpenditureCategoryId && x.WeekEndingDate == dbDetailsObj.WeekEndingDate).FirstOrDefault(); } } if (currentDbItem == null) { currentDbItem = new ScenarioDetail { Id = Guid.NewGuid(), ParentID = newId, ExpenditureCategoryId = dbDetailsObj.ExpenditureCategoryId, WeekEndingDate = dbDetailsObj.WeekEndingDate, Quantity = dbDetailsObj.Quantity, WeekOrdinal = dbDetailsObj.WeekOrdinal, Cost = dbDetailsObj.DetailCost, }; DbContext.ScenarioDetail.Add(currentDbItem); } else { currentDbItem.WeekEndingDate = dbDetailsObj.WeekEndingDate; currentDbItem.WeekOrdinal = dbDetailsObj.WeekOrdinal; currentDbItem.Quantity = dbDetailsObj.Quantity; currentDbItem.Cost = dbDetailsObj.DetailCost; DbContext.Entry(currentDbItem).State = EntityState.Modified; } } // delete details where weekending date > end date if (model.Id != Guid.Empty) { var newEndDate = dbObj.EndDate.Value.AddDays(6); var newDuration = dbObj.Duration; var items2Delete = DbContext.ScenarioDetail.Where( t => t.ParentID == model.Id && (t.WeekEndingDate > newEndDate || t.WeekOrdinal < 1)).ToList(); DbContext.ScenarioDetail.RemoveRange(items2Delete); } // delete details where weekending date < start date if (model.Id != Guid.Empty) { var newStartDate = dbObj.StartDate; var newDuration = dbObj.Duration; var items2Delete = DbContext.ScenarioDetail.Where( t => t.ParentID == model.Id && (t.WeekEndingDate < newStartDate)).ToList(); DbContext.ScenarioDetail.RemoveRange(items2Delete); } #endregion DbContext.SaveChanges(); #region Recalculate bottom-up costs // recalculate scenario try { SetBottomUpCosts(dbObj, model.IsRevenueGenerating); } catch (Exception exception) // handle any unexpected error { LogManager.GetCurrentClassLogger().Fatal(exception.Message); } if (model.Id == Guid.Empty || ScenarioStatus.Draft.Equals(model.Status)) { if (model.UseLMMargin) { if (model.IsRevenueGenerating) { dbObj.ExpectedGrossMargin = dbObj.CalculatedGrossMargin; dbObj.TDDirectCosts = dbObj.ProjectedRevenue - (dbObj.ProjectedRevenue * dbObj.ExpectedGrossMargin); } else dbObj.TDDirectCosts = model.TDDirectCosts; } else { if (model.IsRevenueGenerating) { dbObj.ExpectedGrossMargin_LM = dbObj.CalculatedGrossMargin_LM; dbObj.TDDirectCosts_LM = dbObj.ProjectedRevenue - (dbObj.ProjectedRevenue * dbObj.ExpectedGrossMargin_LM); } else dbObj.TDDirectCosts = model.TDDirectCosts; } } else { if (model.IsRevenueGenerating) { dbObj.TDDirectCosts = dbObj.ProjectedRevenue - (dbObj.ProjectedRevenue * dbObj.ExpectedGrossMargin); dbObj.TDDirectCosts_LM = dbObj.ProjectedRevenue - (dbObj.ProjectedRevenue * dbObj.ExpectedGrossMargin_LM); } else dbObj.TDDirectCosts = model.TDDirectCosts; } DbContext.Entry(dbObj).State = EntityState.Modified; if (dbObj.Status == (int?)ScenarioStatus.Active) { if (dbObj.ParentId.HasValue) DeactivateScenarios(dbObj.ParentId.Value, dbObj.Type, dbObj.Id); dbObj.Status = (int?)ScenarioStatus.Active; } #endregion } DbContext.SaveChanges(); #endregion return dbObj; } private void DeactivateScenarios(Guid parentId, int type, params Guid[] excludScenarios) { if (excludScenarios == null) excludScenarios = new Guid[] { }; var activeScenarios = DbContext.Scenarios.Where(x => x.ParentId == parentId && x.Type == type && x.Status == (int?)ScenarioStatus.Active && !excludScenarios.Contains(x.Id)); foreach (var scenario in activeScenarios) { scenario.Status = ScenarioStatus.Inactive.GetHashCode(); DbContext.Entry(scenario).State = EntityState.Modified; } } public ScenarioDetail LoadScenarioDetail(Guid? value, bool isReadOnly = true) { if (value == null || value == Guid.Empty) return new ScenarioDetail(); return isReadOnly ? DbContext.ScenarioDetail.AsNoTracking().FirstOrDefault(t => t.Id == value.Value) : DbContext.ScenarioDetail.Find(value); } /// /// Copying actuals values to scenario details /// private void CopyActuals(Dictionary> calculatedNewScenario, List actualScenarioDetails, DateTime mdtActualsEndWeek) { //zero out scenario details that have actuals..., //prep before copying actuals... foreach (var expCategory in calculatedNewScenario) { foreach (var scenarioDetailsListItem in expCategory.Value) { if (!(scenarioDetailsListItem.WeekEndingDate <= mdtActualsEndWeek)) break; scenarioDetailsListItem.Quantity = 0; scenarioDetailsListItem.DetailCost = 0; } } foreach (var vwScenarioAndProxyDetailse in actualScenarioDetails) { if (calculatedNewScenario.ContainsKey(vwScenarioAndProxyDetailse.ExpenditureCategoryId.Value)) { var row = calculatedNewScenario[vwScenarioAndProxyDetailse.ExpenditureCategoryId.Value]; var item = row.FirstOrDefault(t => t.WeekEndingDate == vwScenarioAndProxyDetailse.WeekEndingDate.Value); if (item != null) { item.Quantity = vwScenarioAndProxyDetailse.Quantity ?? 0; item.DetailCost = vwScenarioAndProxyDetailse.Cost ?? 0; } } } } /// /// Calculates costs for calculated expenditure /// User defined date to which values need to be preserved /// private void ComputeCalculatedCategories(Dictionary> calculatedNewScenario, TemporaryCalculationResults temporaryCalcResult, DateTime mdtActualsEndWeek, DateTime cutOffDate, int totalMilestones, int? duration, Dictionary> moCatRowTranslate, Dictionary> moRateTranslate) { temporaryCalcResult.mvActualCostCG_Fee = 0; temporaryCalcResult.mvActualCostEFX_Fee = 0; var feeCalculations = DbContext.VW_Expenditure2FeeCalculation.Where( t => t.MinShot <= totalMilestones && t.MaxShot >= totalMilestones); var allCalculatedCategories = CalculatedExpenditureCategoryItem.LoadAllCalculatedCategories(DbContext); decimal liWksSubject2Fee = 0; decimal feeFactor = 0; decimal week2StartFee = 0; var uptoDate = (mdtActualsEndWeek > cutOffDate) ? mdtActualsEndWeek : cutOffDate; foreach (var expCatItem in calculatedNewScenario) { if (expCatItem.Value.Count > 0 && expCatItem.Value[0].UseType == ExpenditureCategoryModel.UseTypes.Fee) { foreach (var feeCalculation in feeCalculations) { if (feeCalculation.ExpenditureCategoryId == expCatItem.Key) { liWksSubject2Fee = feeCalculation.WksSubjectToFee ?? 0; feeFactor = feeCalculation.Quantity; week2StartFee = ((100 - liWksSubject2Fee) / 100) * (duration ?? 0); if (week2StartFee > (int)week2StartFee) week2StartFee++; break; } } } for (var colIndex = 0; colIndex < expCatItem.Value.Count; colIndex++) { var scenarioDetailsListItem = expCatItem.Value[colIndex]; if (uptoDate < scenarioDetailsListItem.WeekEndingDate) { switch (expCatItem.Value[0].UseType) { case ExpenditureCategoryModel.UseTypes.Calculated: var lstCalcCats = allCalculatedCategories[scenarioDetailsListItem.ExpenditureCategoryId]; throw new Exception("aaa!"); scenarioDetailsListItem.Quantity = 0; foreach (var calculatedExpenditureCategoryItem in lstCalcCats) { if (!calculatedExpenditureCategoryItem.ExpenditureId.HasValue || !calculatedNewScenario.ContainsKey(calculatedExpenditureCategoryItem.ExpenditureId.Value)) continue; //var row = moCatRowTranslate[calculatedExpenditureCategoryItem.ExpenditureId.Value]; switch ((CalculatesCategoryModel.FactorTypes)calculatedExpenditureCategoryItem.FactorType) { case CalculatesCategoryModel.FactorTypes.Multipiy: scenarioDetailsListItem.Quantity += calculatedNewScenario[calculatedExpenditureCategoryItem.ExpenditureId.Value][colIndex] .Quantity * calculatedExpenditureCategoryItem.Factor; break; case CalculatesCategoryModel.FactorTypes.Divide: scenarioDetailsListItem.Quantity += calculatedNewScenario[calculatedExpenditureCategoryItem.ExpenditureId.Value][colIndex] .Quantity / calculatedExpenditureCategoryItem.Factor; break; } } scenarioDetailsListItem.DetailCost = GetRate(scenarioDetailsListItem.ExpenditureCategoryId, uptoDate, moRateTranslate) * scenarioDetailsListItem.Quantity; if (ExpenditureCategoryModel.CgEfx.CG == scenarioDetailsListItem.CG_EFX) temporaryCalcResult.mvActualCostCG += scenarioDetailsListItem.DetailCost; else if (ExpenditureCategoryModel.CgEfx.EFX == scenarioDetailsListItem.CG_EFX) temporaryCalcResult.mvActualCostEFX += scenarioDetailsListItem.DetailCost; break; case ExpenditureCategoryModel.UseTypes.Fee: if ((colIndex + 1) > week2StartFee) { scenarioDetailsListItem.Quantity = feeFactor; scenarioDetailsListItem.DetailCost = GetRate(scenarioDetailsListItem.ExpenditureCategoryId, scenarioDetailsListItem.WeekEndingDate.Value, moRateTranslate) * scenarioDetailsListItem.Quantity; } else { if (week2StartFee > (int)week2StartFee && (colIndex + 1) == (int)week2StartFee) { scenarioDetailsListItem.Quantity = (1 - (week2StartFee - (int)week2StartFee)) * feeFactor; scenarioDetailsListItem.DetailCost = GetRate(scenarioDetailsListItem.ExpenditureCategoryId, scenarioDetailsListItem.WeekEndingDate.Value, moRateTranslate) * scenarioDetailsListItem.Quantity; } else { scenarioDetailsListItem.DetailCost = 0; scenarioDetailsListItem.Quantity = 0; } } if (ExpenditureCategoryModel.CgEfx.CG == scenarioDetailsListItem.CG_EFX) temporaryCalcResult.mvActualCostCG_Fee += scenarioDetailsListItem.DetailCost; else temporaryCalcResult.mvActualCostEFX_Fee += scenarioDetailsListItem.DetailCost; break; } } } } } private void ComputeNONCalculatedCategories(Dictionary> calculatedNewScenario, TemporaryCalculationResults temporaryCalcResult, DateTime mdtActualsEndWeek, ScenarioModel model, int? duration, Dictionary> moCatRowTranslate, Dictionary> moRateTranslate) { temporaryCalcResult.mvActualCostCG = 0; temporaryCalcResult.mvActualCostEFX = 0; temporaryCalcResult.mvActualCostCG_LM = 0; temporaryCalcResult.mvActualCostEFX_LM = 0; foreach (var expCatItem in calculatedNewScenario) { if (expCatItem.Value[0].UseType == ExpenditureCategoryModel.UseTypes.Calculated || expCatItem.Value[0].UseType == ExpenditureCategoryModel.UseTypes.Fee) { foreach (var scenarioDetailsListItem in expCatItem.Value) { if (mdtActualsEndWeek < scenarioDetailsListItem.WeekEndingDate) { scenarioDetailsListItem.DetailCost = GetRate(scenarioDetailsListItem.ExpenditureCategoryId, mdtActualsEndWeek, moRateTranslate) * scenarioDetailsListItem.Quantity; if (expCatItem.Value[0].CategoryType == ExpenditureCategoryModel.CategoryTypes.Labor || expCatItem.Value[0].CategoryType == ExpenditureCategoryModel.CategoryTypes.Materials) { if (ExpenditureCategoryModel.CgEfx.CG == scenarioDetailsListItem.CG_EFX) { temporaryCalcResult.mvActualCostCG_LM += scenarioDetailsListItem.DetailCost; temporaryCalcResult.mvActualCostCG += scenarioDetailsListItem.DetailCost; } else { temporaryCalcResult.mvActualCostEFX_LM += scenarioDetailsListItem.DetailCost; temporaryCalcResult.mvActualCostEFX += scenarioDetailsListItem.DetailCost; } } else { if (ExpenditureCategoryModel.CgEfx.CG == scenarioDetailsListItem.CG_EFX) temporaryCalcResult.mvActualCostCG += scenarioDetailsListItem.DetailCost; else temporaryCalcResult.mvActualCostEFX += scenarioDetailsListItem.DetailCost; } } } } } } /// /// Applies labor/materials split factor /// User defined date to which values need to be preserved /// private void ApplyCGEFXFactor(Dictionary> calculatedNewScenario, TemporaryCalculationResults temporaryCalcResult, DateTime mdtActualsEndWeek, DateTime cutOffDate, bool freezeResources, bool UseLMMargin = false) { var uptoDate = (mdtActualsEndWeek > cutOffDate) ? mdtActualsEndWeek : cutOffDate; foreach (var expCatItem in calculatedNewScenario) { if (UseLMMargin && materialsECs != null && materialsECs.Contains(expCatItem.Key)) continue; foreach (var scenarioDetailsListItem in expCatItem.Value) { if (scenarioDetailsListItem.UseType != ExpenditureCategoryModel.UseTypes.Fee && uptoDate < scenarioDetailsListItem.WeekEndingDate && !freezeResources) { if (ExpenditureCategoryModel.CgEfx.CG == scenarioDetailsListItem.CG_EFX) { scenarioDetailsListItem.DetailCost = (scenarioDetailsListItem.DetailCost * temporaryCalcResult.lvCGFactor) + scenarioDetailsListItem.DetailCost; scenarioDetailsListItem.Quantity = (scenarioDetailsListItem.Quantity * temporaryCalcResult.lvCGFactor) + scenarioDetailsListItem.Quantity; } else if (ExpenditureCategoryModel.CgEfx.EFX == scenarioDetailsListItem.CG_EFX) { scenarioDetailsListItem.DetailCost = (scenarioDetailsListItem.DetailCost * temporaryCalcResult.lvEFXFactor) + scenarioDetailsListItem.DetailCost; scenarioDetailsListItem.Quantity = (scenarioDetailsListItem.Quantity * temporaryCalcResult.lvEFXFactor) + scenarioDetailsListItem.Quantity; } } } } } /// /// /// /// /// /// User defined date to which values need to be preserved /// /// /// private TemporaryCalculationResults ComputeNONCalculatedCategories(Dictionary> calculatedNewScenario, DateTime mdtActualsEndWeek, DateTime cutOffDate, Dictionary> moRateTranslate, TemporaryCalculationResults temporaryCalcResult) { temporaryCalcResult.mvActualCostCG = 0M; temporaryCalcResult.mvActualCostEFX = 0M; temporaryCalcResult.mvActualCostCG_LM = 0M; temporaryCalcResult.mvActualCostEFX_LM = 0M; var uptoDate = (mdtActualsEndWeek > cutOffDate ? mdtActualsEndWeek : cutOffDate); foreach (var item in calculatedNewScenario) { if (item.Value.Count > 0 && ExpenditureCategoryModel.UseTypes.Calculated != item.Value[0].UseType && ExpenditureCategoryModel.UseTypes.Fee != item.Value[0].UseType) { foreach (var scenarioDetailsListItem in item.Value) { if (uptoDate < scenarioDetailsListItem.WeekEndingDate.Value) { scenarioDetailsListItem.DetailCost = GetRate(scenarioDetailsListItem.ExpenditureCategoryId, scenarioDetailsListItem.WeekEndingDate.Value, moRateTranslate) * scenarioDetailsListItem.Quantity; if (ExpenditureCategoryModel.CategoryTypes.Labor == scenarioDetailsListItem.CategoryType || ExpenditureCategoryModel.CategoryTypes.Materials == scenarioDetailsListItem.CategoryType) { if (ExpenditureCategoryModel.CgEfx.CG == scenarioDetailsListItem.CG_EFX) { temporaryCalcResult.mvActualCostCG_LM += scenarioDetailsListItem.DetailCost; temporaryCalcResult.mvActualCostCG += scenarioDetailsListItem.DetailCost; } else if ( ExpenditureCategoryModel.CgEfx.EFX == scenarioDetailsListItem.CG_EFX) { temporaryCalcResult.mvActualCostEFX_LM += scenarioDetailsListItem.DetailCost; temporaryCalcResult.mvActualCostEFX += scenarioDetailsListItem.DetailCost; } } else { if (ExpenditureCategoryModel.CgEfx.CG == scenarioDetailsListItem.CG_EFX) temporaryCalcResult.mvActualCostCG += scenarioDetailsListItem.DetailCost; else if (ExpenditureCategoryModel.CgEfx.EFX == scenarioDetailsListItem.CG_EFX) temporaryCalcResult.mvActualCostEFX += scenarioDetailsListItem.DetailCost; } } } } } return temporaryCalcResult; } public Dictionary> LoadRates(Guid scenarioId) { var result = new Dictionary>(); if (scenarioId == Guid.Empty) return result; #region loading rates // retrieve all local rates for the specified scenario var localRates = DbContext.Rates.AsNoTracking().Where(t => t.ParentId == scenarioId && t.Type == (int)RateModel.RateType.Derived && t.ExpenditureCategoryId != null) .Select(t => new { t.ExpenditureCategoryId, t.EndDate, t.Rate1 }).OrderBy(t => t.ExpenditureCategoryId) .ThenBy(t => t.EndDate).ToArray(); // group rates by expenditure category id and store them as disctionary by expenditure category id var localRatesLU = localRates.GroupBy(t => t.ExpenditureCategoryId) .ToDictionary(key => key.Key, group => group.ToList()); // retrieve similar dictionary for global rates that does not referenced to already retrieved expenditures var globalRatesQuery = DbContext.Rates.AsNoTracking().Where(t => t.Type == (int)RateModel.RateType.Global); //TODO: we should validate SQL query to make it is correct and optimal if (localRates.Length > 0) globalRatesQuery = globalRatesQuery.Where(t => !localRatesLU.Keys.ToList().Contains(t.ExpenditureCategoryId)); var globalRates = globalRatesQuery .Select(t => new { t.ExpenditureCategoryId, t.EndDate, t.Rate1 }) .OrderBy(t => t.ExpenditureCategoryId).ThenBy(t => t.EndDate) .ToArray() .GroupBy(t => t.ExpenditureCategoryId) .ToDictionary(key => key.Key, group => group.ToList()); // append global rates to local rates foreach (var globalRate in globalRates) { if (localRatesLU.ContainsKey(globalRate.Key)) throw new BLLException("Unable to add global rate to the list because it already contains a local rate with the same key"); localRatesLU.Add(globalRate.Key, globalRate.Value); } #endregion result = localRatesLU.ToDictionary(t => t.Key, pair => pair.Value.ToDictionary(tt => tt.EndDate, arg => arg.Rate1)); return result; } public static decimal GetRate(Guid expenditureCategoryId, DateTime weekEndingDate, IReadOnlyDictionary> moRateTranslate) { if (moRateTranslate.Count > 0) { if (moRateTranslate.ContainsKey(expenditureCategoryId)) { if (moRateTranslate[expenditureCategoryId].Values.Count > 0) { foreach (var rate in moRateTranslate[expenditureCategoryId].Where(rate => weekEndingDate <= rate.Key)) { return rate.Value; } // if not found, use last rate in collection return moRateTranslate[expenditureCategoryId].LastOrDefault().Value; } } } return 0; } /// /// Prepare a list of scenario details templates for each provided expenditure category. /// /// Id of the template scenario. /// /// /// /// private Dictionary> GetTemplateScenarioDetailsPerExpCat(Guid templateScenarioId, IReadOnlyCollection plstExpCatsToAdd, Guid newScenarioId, ICollection plstExpCatsToExclude, List currentScenarioDetails = null) { var result = new Dictionary>(); if (templateScenarioId == Guid.Empty) return result; var lvaRtnArray = new List(); if (currentScenarioDetails == null || currentScenarioDetails.Count <= 0) { var query = DbContext.VW_ScenarioAndProxyDetails.Where(t => t.ParentID == templateScenarioId); if (plstExpCatsToExclude.Count > 0) query = query.Where( t => t.ExpenditureCategoryId != null && !plstExpCatsToExclude.Contains(t.ExpenditureCategoryId.Value)); query = query.OrderBy(t => t.UseType).ThenBy(t => t.ExpenditureCategoryId).ThenBy(t => t.WeekOrdinal); lvaRtnArray = query.ToList(); } else { lvaRtnArray = currentScenarioDetails.Where(x => x.ExpenditureCategoryId.HasValue && !plstExpCatsToExclude.Contains(x.ExpenditureCategoryId.Value)).ToList(); } if (lvaRtnArray.Count + plstExpCatsToAdd.Count == 0) return result; var scenarioDetailsNumber = 0; var scenarioDetailExpCatNumber = lvaRtnArray.Select(t => t.ExpenditureCategoryId).Distinct().Count(); if (lvaRtnArray.Count > 0) { scenarioDetailsNumber = lvaRtnArray.GroupBy(detail => detail.ExpenditureCategoryId) .Select(grouping => new { ExpenditureCategoryId = grouping.Key, ScenarioDetailsCount = grouping.Count() }).Max(item => item.ScenarioDetailsCount); } if (scenarioDetailsNumber == 0) scenarioDetailsNumber = 52; // 52 weeks by default (~1 year) result = new Dictionary>(scenarioDetailExpCatNumber + plstExpCatsToAdd.Count); // first of all fill the result list with not calculated expenditure categories foreach (var item in plstExpCatsToAdd.Where(t => t.UseType != ExpenditureCategoryModel.UseTypes.Calculated)) { var expCatGroup = new List(scenarioDetailsNumber); for (var i = 0; i < scenarioDetailsNumber; i++) { expCatGroup.Add(new ScenarioDetailsListItem { Id = Guid.NewGuid(), ParentId = newScenarioId == Guid.Empty ? templateScenarioId : newScenarioId, ExpenditureCategoryId = item.ExpenditureCategoryId, WeekEndingDate = null, Quantity = item.Quantity, LastUpdate = item.LastUpdate, WeekOrdinal = item.WeekOrdinal, DetailCost = item.DetailCost, ExpenditureCategoryName = item.CategoryName, GlId = item.GlId, UOMId = item.UOMId, CreditId = item.CreditId, CategoryType = item.CategoryType, UseType = item.UseType, CG_EFX = item.CG_EFX, SysField1 = item.SysField1, SysField2 = item.SysField2, SortOrder = item.SortOrder }); } result.Add(item.ExpenditureCategoryId, expCatGroup); } // then fill the result list with scenario details from template foreach (var item in lvaRtnArray) { var details = new ScenarioDetailsListItem { Id = item.Id, ParentId = templateScenarioId, ExpenditureCategoryId = item.ExpenditureCategoryId.Value, WeekEndingDate = item.WeekEndingDate, Quantity = item.Quantity ?? 0, LastUpdate = item.LastUpdate ?? DateTime.Today, WeekOrdinal = item.WeekOrdinal ?? 0, DetailCost = item.Cost ?? 0, ExpenditureCategoryName = item.ExpCategoryWithCcName, // SA. ENV-839 GlId = item.GLId, UOMId = item.UOMId, CreditId = item.CreditId, CategoryType = (ExpenditureCategoryModel.CategoryTypes)(item.Type ?? 0), UseType = (ExpenditureCategoryModel.UseTypes)(item.UseType ?? 1), CG_EFX = (item.CGEFX ?? string.Empty).Trim().Equals(ExpenditureCategoryModel.CgEfx.CG.ToString()) ? ExpenditureCategoryModel.CgEfx.CG : ExpenditureCategoryModel.CgEfx.EFX, SysField1 = item.SystemAttributeOne, SysField2 = item.SystemAttributeTwo ?? Guid.Empty, SortOrder = item.SortOrder }; if (!result.ContainsKey(details.ExpenditureCategoryId)) result.Add(details.ExpenditureCategoryId, new List(scenarioDetailsNumber)); result[details.ExpenditureCategoryId].Add(details); } // then fill the result list with scenario details of calculated template foreach (var item in plstExpCatsToAdd.Where(t => t.UseType == ExpenditureCategoryModel.UseTypes.Calculated)) { var expCatGroup = new List(scenarioDetailsNumber); for (var i = 0; i < scenarioDetailsNumber; i++) { expCatGroup.Add(new ScenarioDetailsListItem { Id = Guid.NewGuid(), ParentId = newScenarioId == Guid.Empty ? templateScenarioId : newScenarioId, ExpenditureCategoryId = item.ExpenditureCategoryId, WeekEndingDate = null, Quantity = item.Quantity, LastUpdate = item.LastUpdate, WeekOrdinal = item.WeekOrdinal, DetailCost = item.DetailCost, ExpenditureCategoryName = item.CategoryName, GlId = item.GlId, UOMId = item.UOMId, CreditId = item.CreditId, CategoryType = item.CategoryType, UseType = item.UseType, CG_EFX = item.CG_EFX, SysField1 = item.SysField1, SysField2 = item.SysField2, SortOrder = item.SortOrder }); } result.Add(item.ExpenditureCategoryId, expCatGroup); } return result; } /// /// Returns a dictionary where Key = ExpenditureCategoryId, Value = List<ParentId> /// /// public Dictionary> GetExpWhereUsedForCalculation() { var items = DbContext.VW_Expenditure2Calculation.AsNoTracking().Select(t => new { t.ExpenditureCategoryID, t.ParentId }) .OrderBy(t => t.ExpenditureCategoryID).ThenBy(t => t.ParentId) .GroupBy(t => t.ExpenditureCategoryID) .ToDictionary(t => t.Key, group => group.ToList()) .ToDictionary(item => item.Key.Value, item => item.Value.Select(t => t.ParentId).ToList()); return items; } /// /// Sets Bottom Up costs upon scenario creation or editing. /// public void SetBottomUpCosts(ScenarioModel model) { if (model == null) throw new ArgumentNullException("model"); if (model.Id == Guid.Empty) throw new ArgumentException("Unable to recalculate bottom-up costs because model does not have Id specified."); var scenario = (from s in DbContext.Scenarios where s.Id == model.Id select s).FirstOrDefault(); SetBottomUpCosts(scenario, model.IsRevenueGenerating); } private DateTime? GetROIDate(Scenario scenario, List items) { if (!scenario.CostSavings.HasValue) return null; if (scenario.BUDirectCosts > scenario.CostSavings) return null; if (items == null || items.Count == 0) return null; decimal costSavingsSum = 0; foreach (var costSaving in items.OrderBy(t => t.Year).ThenBy(t => t.Month)) { if (costSaving.Cost.HasValue) costSavingsSum += costSaving.Cost.Value; if (costSavingsSum >= scenario.BUDirectCosts) { return new DateTime(costSaving.Year, costSaving.Month, 1); } } return null; } public void SetBottomUpCosts(Guid id) { if (id == Guid.Empty) throw new ArgumentException("Unable to recalculate bottom-up costs because Id is Guid.Empty."); var scenario = (from s in DbContext.Scenarios where s.Id == id select s).FirstOrDefault(); var model = (ScenarioModel)scenario; SetBottomUpCosts(scenario, model.IsRevenueGenerating); } public void SetBottomUpCosts(Scenario scenario) { if (scenario == null) throw new ArgumentException("Unable to recalculate bottom-up costs because scenario is null."); var model = (ScenarioModel)scenario; SetBottomUpCosts(scenario, model.IsRevenueGenerating); } /// /// Recalculate scenario bottom-up costs for the specified scenario. DbContext submission required. /// /// The scenario that should be recalculated. /// Indicates whether scenario is revenue generating or not. /// A collection of records of ACTUALS scenario. If null then will be retrieved from database. /// /// Actuals BU Direct costs for non actuals scenario calculates as Actuals (for actuals scenario period) + Forecast (for remaining period). /// public void SetBottomUpCosts(Scenario scenario, bool isRevenueGeneratingScenario, List actualScenarioDetails = null, List forecastScenarioDetails = null) { if (scenario == null) throw new ArgumentNullException("scenario"); if (scenario.Id == Guid.Empty) throw new ArgumentException("Unable to recalculate bottom-up costs because scenario does not have Id specified."); if (scenario.Type == (int)ScenarioType.Portfolio || scenario.Type == (int)ScenarioType.Scheduling) { if (forecastScenarioDetails == null) { forecastScenarioDetails = DbContext.VW_ScenarioAndProxyDetails.Where(x => x.ParentID == scenario.Id) .Select(x => new ScenarioDetailsListItem() { Id = x.Id, DetailCost = x.Cost ?? 0, CategoryType = (ExpenditureCategoryModel.CategoryTypes)(x.Type ?? 0), ParentId = x.ParentID ?? Guid.Empty, WeekEndingDate = x.WeekEndingDate }).Distinct().ToList(); } var totalBtUpCosts = forecastScenarioDetails.Sum(t => t.DetailCost); var totalBtUpCostsLM = forecastScenarioDetails.Where(t => t.CategoryType == ExpenditureCategoryModel.CategoryTypes.Labor || t.CategoryType == ExpenditureCategoryModel.CategoryTypes.Materials).Sum(x => x.DetailCost); scenario.BUDirectCosts = totalBtUpCosts; scenario.BURevenueShot = scenario.BUDirectCosts / (decimal?)(scenario.Shots <= 0 ? (decimal?)null : scenario.Shots); //ENV-458 Suggest Margin When GrossMargin is set only if (isRevenueGeneratingScenario && !scenario.ProjectedRevenue.HasValue && scenario.ExpectedGrossMargin > 0) { scenario.ProjectedRevenue = (scenario.ExpectedGrossMargin * scenario.BUDirectCosts / (1 - scenario.ExpectedGrossMargin)) + scenario.BUDirectCosts; } else if (isRevenueGeneratingScenario && scenario.ProjectedRevenue > 0 && (scenario.ExpectedGrossMargin ?? 0) == 0) { scenario.ExpectedGrossMargin = 1 - scenario.BUDirectCosts / scenario.ProjectedRevenue; } if (scenario.ProjectedRevenue.HasValue && scenario.ProjectedRevenue.Value > 0) { scenario.CalculatedGrossMargin = isRevenueGeneratingScenario ? (scenario.ProjectedRevenue - scenario.BUDirectCosts) / scenario.ProjectedRevenue : 0; } else { scenario.CalculatedGrossMargin = 0; } scenario.BUDirectCosts_LM = totalBtUpCostsLM; scenario.BURevenueShot_LM = scenario.BUDirectCosts_LM / (scenario.Shots <= 0 ? (decimal?)null : scenario.Shots); if (scenario.ProjectedRevenue.HasValue && scenario.ProjectedRevenue.Value > 0) { scenario.CalculatedGrossMargin_LM = isRevenueGeneratingScenario ? (scenario.ProjectedRevenue - scenario.BUDirectCosts_LM) / scenario.ProjectedRevenue : 0; } else { scenario.CalculatedGrossMargin_LM = 0; } var actualsscen = (from p in DbContext.Scenarios where p.ParentId == scenario.ParentId && p.Type == (int)ScenarioType.Actuals select new { ActualsId = p.Id, ActualsSatrtDate = p.StartDate, ActualsEndDate = p.EndDate }).FirstOrDefault(); if (!scenario.GrowthScenario && actualsscen != null) { var actualsId = actualsscen.ActualsId; var actualsStartDate = actualsscen.ActualsSatrtDate; var actualsEndDate = actualsscen.ActualsEndDate; //get actuals scenario details if (actualScenarioDetails == null) { actualScenarioDetails = (from p in DbContext.ScenarioDetail join vw in DbContext.VW_Expenditure2Category on p.ExpenditureCategoryId equals vw.Id where p.ParentID == actualsId && p.WeekEndingDate >= actualsStartDate && p.WeekEndingDate <= actualsEndDate select new ScenarioDetailsListItem { DetailCost = p.Cost ?? 0, CategoryType = (ExpenditureCategoryModel.CategoryTypes)vw.Type }).ToList(); } var forecastSum = forecastScenarioDetails.Where(t => t.WeekEndingDate > actualsEndDate).Sum(t => t.DetailCost); var forecastLMSum = forecastScenarioDetails.Where(t => t.WeekEndingDate > actualsEndDate && (t.CategoryType == ExpenditureCategoryModel.CategoryTypes.Labor || t.CategoryType == ExpenditureCategoryModel.CategoryTypes.Materials)).Sum(t => t.DetailCost); var actualsSum = actualScenarioDetails.Sum(t => t.DetailCost); var actualsLMSum = actualScenarioDetails.Where(t => t.CategoryType == ExpenditureCategoryModel.CategoryTypes.Labor || t.CategoryType == ExpenditureCategoryModel.CategoryTypes.Materials) .Sum(x => x.DetailCost); scenario.Actuals_BUDirectCosts = actualsSum + forecastSum; scenario.Actuals_BUDirectCosts_LM = actualsLMSum + forecastLMSum; } DbContext.Entry(scenario).State = EntityState.Modified; } else if (scenario.Type == (int)ScenarioType.Actuals) { //get actuals scenario details if (actualScenarioDetails == null) { actualScenarioDetails = (from p in DbContext.ScenarioDetail join vw in DbContext.VW_Expenditure2Category on p.ExpenditureCategoryId equals vw.Id where p.ParentID == scenario.Id && p.WeekEndingDate <= scenario.EndDate select new ScenarioDetailsListItem { DetailCost = p.Cost ?? 0, CategoryType = (ExpenditureCategoryModel.CategoryTypes)vw.Type }).ToList(); } var actualTotalBtUpCosts = actualScenarioDetails.Sum(t => t.DetailCost); var actualTotalBtUpCostsLM = actualScenarioDetails.Where(t => t.CategoryType == ExpenditureCategoryModel.CategoryTypes.Labor || t.CategoryType == ExpenditureCategoryModel.CategoryTypes.Materials).Sum(x => x.DetailCost); scenario.BUDirectCosts = actualTotalBtUpCosts; scenario.BUDirectCosts_LM = actualTotalBtUpCostsLM; scenario.Actuals_BUDirectCosts = actualTotalBtUpCosts; scenario.Actuals_BUDirectCosts_LM = actualTotalBtUpCostsLM; DbContext.Entry(scenario).State = EntityState.Modified; //recalculate buttom up costs of all portfolio, efc, cg scenarios under the same show as the actuals scenario. var scenarios = (from s in DbContext.Scenarios where s.ParentId == scenario.ParentId && (s.Type == (int)ScenarioType.Portfolio || s.Type == (int)ScenarioType.Scheduling) && s.Status != (int)ScenarioStatus.Draft && !s.GrowthScenario select s).ToList(); foreach (var scen in scenarios) { var itemScenario = scen; var forecastData = (from sd in DbContext.ScenarioDetail join vw in DbContext.VW_Expenditure2Category on sd.ExpenditureCategoryId equals vw.Id where sd.ParentID == itemScenario.Id && sd.WeekEndingDate > scenario.EndDate select new { sd.Cost, ExpenditureType = vw.Type, }); var sumForecast = forecastData.Sum(t => t.Cost); var sumLMForecast = forecastData.Where(t => t.ExpenditureType == (int)ExpenditureCategoryModel.CategoryTypes.Labor || t.ExpenditureType == (int)ExpenditureCategoryModel.CategoryTypes.Materials).Sum(t => t.Cost); itemScenario.Actuals_BUDirectCosts = actualTotalBtUpCosts + (sumForecast ?? 0); itemScenario.Actuals_BUDirectCosts_LM = actualTotalBtUpCostsLM + (sumLMForecast ?? 0); DbContext.Entry(itemScenario).State = EntityState.Modified; } } } public class BottomUpCostsDataItem { public decimal Cost { get; set; } public ExpenditureCategoryModel.CategoryTypes Type { get; set; } public Guid ScenarioId { get; set; } } /// /// Recalculate scenario bottom-up costs for the specified scenario. DbContext submission required. /// /// The scenario which bottom-up costs should be recalculated. /// Indicates whether scenario is revenue generating or not. /// A list of all scenario detail records of . /// A li st of all scenario detail records of actuals scenario referenced to the same project as . [Obsolete("Remove while next round of code cleaning")] private void UpdateScenarioBottomUpCosts(Scenario scenario, bool isRevenueGeneratingScenario, List scenarioDetailItems, List actualScenarioDetailItems = null) { if (scenario == null) throw new ArgumentNullException("scenario"); if (scenario.Id == Guid.Empty) throw new ArgumentException("Unable to recalculate bottom-up costs because scenario does not have Id specified."); var totalBtUpCosts = scenarioDetailItems.Sum(t => t.Cost); var totalBtUpCostsLM = scenarioDetailItems.Where(t => t.Type == ExpenditureCategoryModel.CategoryTypes.Labor || t.Type == ExpenditureCategoryModel.CategoryTypes.Materials).Sum(x => x.Cost); scenario.BUDirectCosts = totalBtUpCosts; scenario.BURevenueShot = scenario.BUDirectCosts / scenario.Shots; scenario.CalculatedGrossMargin = isRevenueGeneratingScenario ? (scenario.ProjectedRevenue - scenario.BUDirectCosts) / scenario.ProjectedRevenue : 0; scenario.BUDirectCosts_LM = totalBtUpCostsLM; scenario.BURevenueShot_LM = scenario.BUDirectCosts_LM / scenario.Shots; scenario.CalculatedGrossMargin_LM = isRevenueGeneratingScenario ? (scenario.ProjectedRevenue - scenario.BUDirectCosts_LM) / scenario.ProjectedRevenue : 0; if (actualScenarioDetailItems != null) { var actualTotalBtUpCosts = actualScenarioDetailItems.Sum(t => t.Cost); var actualTotalBtUpCostsLM = actualScenarioDetailItems.Where(t => t.Type == ExpenditureCategoryModel.CategoryTypes.Labor || t.Type == ExpenditureCategoryModel.CategoryTypes.Materials).Sum(x => x.Cost); scenario.Actuals_BUDirectCosts = actualTotalBtUpCosts; scenario.Actuals_BUDirectCosts_LM = actualTotalBtUpCostsLM; } } /// /// Copy scenario /// /// The Id of Scenario to copy /// Project Id /// Status of the copied scenario /// Id of the new scenario public Guid CopyTo(Guid id, Guid? targetProjectId, ScenarioStatus targetStatus, bool includeCostSavings) { var newId = Guid.Empty; var scenario = DbContext.Scenarios.FirstOrDefault(x => x.Id == id); if (scenario != null) { var copiedScenario = DbContext.Scenarios.Create(); newId = copiedScenario.Id = Guid.NewGuid(); if (scenario.ParentId == targetProjectId) { copiedScenario.Name = "Copy " + scenario.Name; } else { copiedScenario.Name = scenario.Name; } copiedScenario.ParentId = targetProjectId; copiedScenario.TemplateId = scenario.TemplateId; copiedScenario.Type = scenario.Type; copiedScenario.ProjectedRevenue = scenario.ProjectedRevenue; copiedScenario.ExpectedGrossMargin = scenario.ExpectedGrossMargin; copiedScenario.CalculatedGrossMargin = scenario.CalculatedGrossMargin; copiedScenario.CGSplit = scenario.CGSplit; copiedScenario.EFXSplit = scenario.EFXSplit; copiedScenario.StartDate = scenario.StartDate; copiedScenario.EndDate = scenario.EndDate; copiedScenario.Duration = scenario.Duration; copiedScenario.TDDirectCosts = scenario.TDDirectCosts; copiedScenario.BUDirectCosts = scenario.BUDirectCosts; copiedScenario.Shots = scenario.Shots; copiedScenario.TDRevenueShot = scenario.TDRevenueShot; copiedScenario.BURevenueShot = scenario.BURevenueShot; copiedScenario.FreezeRevenue = scenario.FreezeRevenue; copiedScenario.Color = scenario.Color; if (scenario.Status == (int)ScenarioStatus.Draft) // we cannot copy draft scenario as not draft copiedScenario.Status = (int)ScenarioStatus.Draft; else copiedScenario.Status = (int?)targetStatus; copiedScenario.UseLMMargin = scenario.UseLMMargin; copiedScenario.ExpectedGrossMargin_LM = scenario.ExpectedGrossMargin_LM; copiedScenario.CalculatedGrossMargin_LM = scenario.CalculatedGrossMargin_LM; copiedScenario.TDDirectCosts_LM = scenario.TDDirectCosts_LM; copiedScenario.BUDirectCosts_LM = scenario.BUDirectCosts_LM; copiedScenario.BURevenueShot_LM = scenario.BURevenueShot_LM; copiedScenario.ShotStartDate = scenario.ShotStartDate; copiedScenario.GrowthScenario = scenario.GrowthScenario; copiedScenario.Actuals_BUDirectCosts = scenario.Actuals_BUDirectCosts; copiedScenario.Actuals_BUDirectCosts_LM = scenario.Actuals_BUDirectCosts_LM; copiedScenario.SystemAttributeObjectID = scenario.SystemAttributeObjectID; copiedScenario.Team2Scenario = scenario.Team2Scenario.Select(s => new Team2Scenario() { Id = Guid.NewGuid(), TeamId = s.TeamId, Allocation = s.Allocation, ScenarioId = scenario.Id }).ToList(); //env-700 include cost savings info in new scenario if (includeCostSavings) { copiedScenario.CostSavings = scenario.CostSavings; copiedScenario.CostSavings1 = new Collection(); if (scenario.CostSavings1 != null && scenario.CostSavings1.Count > 0) { foreach (CostSaving c in scenario.CostSavings1) { CostSaving nc = new CostSaving { Id = Guid.NewGuid(), Cost = c.Cost, Month = c.Month, ScenarioId = copiedScenario.Id, Year = c.Year }; copiedScenario.CostSavings1.Add(nc); } } copiedScenario.CostSavingsDescription = scenario.CostSavingsDescription; copiedScenario.CostSavingsEndDate = scenario.CostSavingsEndDate; copiedScenario.CostSavingsStartDate = scenario.CostSavingsEndDate; copiedScenario.CostSavingsType = scenario.CostSavingsType; } DbContext.Entry(copiedScenario).State = EntityState.Added; if (copiedScenario.Status == (int)ScenarioStatus.Active) { if (copiedScenario.ParentId.HasValue) DeactivateScenarios(copiedScenario.ParentId.Value, copiedScenario.Type); } DbContext.SaveChanges(); foreach (var scenarioDetails in DbContext.ScenarioDetail.Where(s => s.ParentID == id).ToList()) { var copiedScenarioDetails = DbContext.ScenarioDetail.Create(); copiedScenarioDetails.Cost = scenarioDetails.Cost; copiedScenarioDetails.Id = Guid.NewGuid(); copiedScenarioDetails.ParentID = copiedScenario.Id; copiedScenarioDetails.ExpenditureCategoryId = scenarioDetails.ExpenditureCategoryId; copiedScenarioDetails.WeekEndingDate = scenarioDetails.WeekEndingDate; copiedScenarioDetails.Quantity = scenarioDetails.Quantity; copiedScenarioDetails.WeekOrdinal = scenarioDetails.WeekOrdinal; DbContext.Entry(copiedScenarioDetails).State = EntityState.Added; } foreach (var scenarioRate in DbContext.Rates.Where(s => s.ParentId == id).ToList()) { var copiedScenarioRate = DbContext.Rates.Create(); copiedScenarioRate.Id = Guid.NewGuid(); copiedScenarioRate.ParentId = copiedScenario.Id; copiedScenarioRate.ExpenditureCategoryId = scenarioRate.ExpenditureCategoryId; copiedScenarioRate.Rate1 = scenarioRate.Rate1; copiedScenarioRate.StartDate = scenarioRate.StartDate; copiedScenarioRate.EndDate = scenarioRate.EndDate; copiedScenarioRate.FreezeRate = scenarioRate.FreezeRate; copiedScenarioRate.Type = scenarioRate.Type; copiedScenarioRate.DerivedId = scenarioRate.DerivedId; DbContext.Entry(copiedScenarioRate).State = EntityState.Added; } //teams allocation foreach (var teamAllocation in DbContext.TeamAllocations.Where(t => t.ScenarioId == id).ToList()) { var copiedTeamAllocation = DbContext.TeamAllocations.Create(); copiedTeamAllocation.Id = Guid.NewGuid(); copiedTeamAllocation.ScenarioId = copiedScenario.Id; copiedTeamAllocation.ExpenditureCategoryId = teamAllocation.ExpenditureCategoryId; copiedTeamAllocation.LastUpdate = DateTime.Now; copiedTeamAllocation.Quantity = teamAllocation.Quantity; copiedTeamAllocation.TeamId = teamAllocation.TeamId; copiedTeamAllocation.WeekEndingDate = teamAllocation.WeekEndingDate; DbContext.Entry(copiedTeamAllocation).State = EntityState.Added; } DbContext.SaveChanges(); } return newId; } /// /// Updates costs of referenced scenario details records and recalculate bottom-up costs of related scenarios. /// /// A object that has been created or updated. public void ApplyRateAndRecalculateScenarios(Rate rate) { if (rate.ExpenditureCategoryId == Guid.Empty) throw new ArgumentException("Rate's expenditure category is not set.", "rate"); if (!rate.ParentId.HasValue) throw new ArgumentException("Rate's parent Id is not set.", "rate"); var expCatId = rate.ExpenditureCategoryId; var parentId = rate.ParentId.Value; var start = rate.StartDate; var end = rate.EndDate; #region update scenario details and gather scenarios to recalculate bottom-up costs // if it's a local rate if (rate.Type == (short)RateModel.RateType.Derived) { var scenario = Load(parentId, false); var isRevenueGenerating = (scenario.Project == null || scenario.Project.Type == null || scenario.Project.IsRevenueGenerating); DateTime? lastRateEndDate = DbContext.Rates.Where(t => t.ExpenditureCategoryId == expCatId && t.Type == (short)RateModel.RateType.Derived && t.ParentId == parentId) .DefaultIfEmpty().Max(t => null == t ? DateTime.MinValue : t.EndDate); var detailItems = (from sd in DbContext.ScenarioDetail join vw in DbContext.VW_Expenditure2Category on sd.ExpenditureCategoryId equals vw.Id where sd.ParentID == parentId & sd.ExpenditureCategoryId == expCatId select new { ScenarioDetail = sd, ExpenditureType = vw.Type }); var totalCostDelta = 0M; var totalCostLMDelta = 0M; foreach (var item in detailItems) { if ( (item.ScenarioDetail.WeekEndingDate >= start && item.ScenarioDetail.WeekEndingDate <= end) || // if week is in rate interval (lastRateEndDate == end && item.ScenarioDetail.WeekEndingDate > end) // if this is a latest rate and week is later than rate's end date ) { var newCost = (item.ScenarioDetail.Quantity ?? 0) * rate.Rate1; if (newCost != item.ScenarioDetail.Cost) { totalCostDelta += newCost - (item.ScenarioDetail.Cost ?? 0); if (item.ExpenditureType == (int)ExpenditureCategoryModel.CategoryTypes.Labor || item.ExpenditureType == (int)ExpenditureCategoryModel.CategoryTypes.Materials) totalCostLMDelta += newCost - (item.ScenarioDetail.Cost ?? 0); item.ScenarioDetail.Cost = newCost; item.ScenarioDetail.LastUpdate = DateTime.Now; DbContext.Entry(item.ScenarioDetail).State = EntityState.Modified; } } } scenario.BUDirectCosts += totalCostDelta; scenario.BURevenueShot = scenario.BUDirectCosts / scenario.Shots; scenario.CalculatedGrossMargin = isRevenueGenerating && scenario.ProjectedRevenue > 0 ? (scenario.ProjectedRevenue - scenario.BUDirectCosts) / scenario.ProjectedRevenue : 0; scenario.BUDirectCosts_LM += totalCostLMDelta; scenario.BURevenueShot_LM = scenario.BUDirectCosts_LM / scenario.Shots; scenario.CalculatedGrossMargin_LM = isRevenueGenerating && scenario.ProjectedRevenue > 0 ? (scenario.ProjectedRevenue - scenario.BUDirectCosts_LM) / scenario.ProjectedRevenue : 0; DbContext.Entry(scenario).State = EntityState.Modified; } else if (rate.Type == (short)RateModel.RateType.Global) { DateTime? lastRateEndDate = DbContext.Rates.Where(t => t.ExpenditureCategoryId == expCatId && t.Type == (short)RateModel.RateType.Global) .DefaultIfEmpty().Max(t => null == t ? DateTime.MinValue : t.EndDate); var scenarioIds = (from sd in DbContext.ScenarioDetail where sd.ExpenditureCategoryId == expCatId select sd.ParentID).Distinct().ToArray(); //TODO: consider ability to update data only for active scenarios var scenarios = (from s in DbContext.Scenarios where s.Status != (int)ScenarioStatus.Draft & (s.Type == (int)ScenarioType.Portfolio || s.Type == (int)ScenarioType.Scheduling) select new { Scenario = s, IsRevenueGenerating = (s.Project == null || s.Project.Type == null || s.Project.IsRevenueGenerating) }).ToDictionary(key => key.Scenario.Id, element => element); var detailItems = (from sd in DbContext.ScenarioDetail join vw in DbContext.VW_Expenditure2Category on sd.ExpenditureCategoryId equals vw.Id where sd.ExpenditureCategoryId == expCatId & scenarioIds.Contains(sd.ParentID) select new { ScenarioDetail = sd, ExpenditureType = vw.Type }).ToArray(); var localRates = (from localRate in DbContext.Rates where localRate.Type == (int)RateModel.RateType.Derived & localRate.ParentId.HasValue & scenarioIds.Contains(localRate.ParentId.Value) & localRate.ExpenditureCategoryId == expCatId select new { localRate.ParentId, localRate.Rate1, localRate.StartDate, localRate.EndDate }).ToArray(); foreach (var scenarioItem in scenarios) { var scenarioId = scenarioItem.Key; var scenario = scenarioItem.Value.Scenario; var scenarioLocalRates = localRates.Where(t => t.ParentId == scenarioId).ToArray(); var scenarioDetailItems = detailItems.Where(t => t.ScenarioDetail.ParentID == scenarioId && !scenarioLocalRates.Any(x => x.StartDate <= t.ScenarioDetail.WeekEndingDate && x.EndDate >= t.ScenarioDetail.WeekEndingDate)); var totalCostDelta = 0M; var totalCostLMDelta = 0M; foreach (var item in scenarioDetailItems) { if ( (item.ScenarioDetail.WeekEndingDate >= start && item.ScenarioDetail.WeekEndingDate <= end) || // if week is in rate interval (lastRateEndDate == end && item.ScenarioDetail.WeekEndingDate > end) // if this is a latest rate and week is later than rate's end date ) { var newCost = (item.ScenarioDetail.Quantity ?? 0) * rate.Rate1; if (newCost != item.ScenarioDetail.Cost) { totalCostDelta += newCost - (item.ScenarioDetail.Cost ?? 0); if (item.ExpenditureType == (int)ExpenditureCategoryModel.CategoryTypes.Labor || item.ExpenditureType == (int)ExpenditureCategoryModel.CategoryTypes.Materials) totalCostLMDelta += newCost - (item.ScenarioDetail.Cost ?? 0); item.ScenarioDetail.Cost = newCost; item.ScenarioDetail.LastUpdate = DateTime.Now; DbContext.Entry(item.ScenarioDetail).State = EntityState.Modified; } } } scenario.BUDirectCosts += totalCostDelta; scenario.BURevenueShot = 0 != scenario.Shots ? scenario.BUDirectCosts / scenario.Shots : 0; scenario.CalculatedGrossMargin = scenarioItem.Value.IsRevenueGenerating && 0 != scenario.ProjectedRevenue ? (scenario.ProjectedRevenue - scenario.BUDirectCosts) / scenario.ProjectedRevenue : 0; scenario.BUDirectCosts_LM += totalCostLMDelta; scenario.BURevenueShot_LM = 0 != scenario.Shots ? scenario.BUDirectCosts_LM / scenario.Shots : 0; scenario.CalculatedGrossMargin_LM = scenarioItem.Value.IsRevenueGenerating && 0 != scenario.ProjectedRevenue ? (scenario.ProjectedRevenue - scenario.BUDirectCosts_LM) / scenario.ProjectedRevenue : 0; DbContext.Entry(scenario).State = EntityState.Modified; } } #endregion } public void RecalculateCapacityScenariosRates(Rate rate) { var scenarioIds = DbContext.Scenarios.Where(x => x.Type == (int)ScenarioType.TeamActualCapacity || x.Type == (int)ScenarioType.TeamPlannedCapacity).Select(x => x.Id).ToArray(); var sds = DbContext.ScenarioDetail.Where(x => scenarioIds.Contains(x.ParentID.Value) && x.ExpenditureCategoryId == rate.ExpenditureCategoryId && x.WeekEndingDate >= rate.StartDate && x.WeekEndingDate <= rate.EndDate).ToList(); foreach (var sd in sds) { sd.Cost = sd.Quantity * rate.Rate1; DbContext.Entry(sd).State = EntityState.Modified; } } public void RecalculateCapacityScenariosUOM(UOMModel uom, UOMModel oldUom) { if (oldUom.UOMValue == 0) return; var scenarioIds = DbContext.Scenarios.Where(x => x.Type == (int)ScenarioType.TeamActualCapacity || x.Type == (int)ScenarioType.TeamPlannedCapacity).Select(x => x.Id).ToArray(); var expCats = DbContext.ExpenditureCategory.Where(x => x.UOMId == uom.Id).Select(x => x.Id).ToArray(); var sds = DbContext.ScenarioDetail.Where(x => scenarioIds.Contains(x.ParentID.Value) && expCats.Contains(x.ExpenditureCategoryId.Value)).ToList(); if (oldUom.UOMValue == 0) return; var multiplyer = uom.UOMValue / oldUom.UOMValue; foreach (var sd in sds) { sd.Quantity *= multiplyer; sd.Cost *= multiplyer; DbContext.Entry(sd).State = EntityState.Modified; } } public void RecalculateCapacityScenariosUOMChanged(ExpenditureCategoryModel exp, ExpenditureCategoryModel oldExp) { var oldUOM = DbContext.UOMs.Where(x => x.Id == oldExp.UOMId).FirstOrDefault(); var newUOM = DbContext.UOMs.Where(x => x.Id == exp.UOMId).FirstOrDefault(); if (oldUOM == null || newUOM == null) return; var scenarioIds = DbContext.Scenarios.Where(x => x.Type == (int)ScenarioType.TeamActualCapacity || x.Type == (int)ScenarioType.TeamPlannedCapacity).Select(x => x.Id).ToArray(); var sds = DbContext.ScenarioDetail.Where(x => scenarioIds.Contains(x.ParentID.Value) && x.ExpenditureCategoryId.Value == exp.Id).ToList(); if (oldUOM.UOMValue == 0) return; var multiplyer = newUOM.UOMValue / oldUOM.UOMValue; foreach (var sd in sds) { sd.Quantity *= multiplyer; sd.Cost *= multiplyer; DbContext.Entry(sd).State = EntityState.Modified; } } /// /// Set teams to the given scenario list (removes old and adds new). Doesn't save results to DB! /// /// Updatable scenario ids /// New teams to scenarios /// SA. ENV-754 public void UpdateScenarioTeams(List scenariosToUpdate, List newTeamList) { // Idea: construct struct, where every scenario has the teams, that must be added to DB table var teamsToAdd = new Dictionary>(); var recsToRemove = new List(); var addTheEntireTeamList = new Dictionary(); // Init dictionary structure foreach (Guid scenId in scenariosToUpdate) { teamsToAdd.Add(scenId, new List()); teamsToAdd[scenId].AddRange(newTeamList); addTheEntireTeamList.Add(scenId, true); } // Get all existing in the DB table records for the given scenario list var scenariosTeamRecords = DbContext.Team2Scenario.Where(x => scenariosToUpdate.Contains(x.ScenarioId)); foreach (var rec in scenariosTeamRecords) { if (!teamsToAdd[rec.ScenarioId].Contains(rec.TeamId)) // Current record is not in then new scenario teams list. Queue record to remove from DB recsToRemove.Add(rec.Id); else { // Current record already exists in the Team2Scenario table. Remove it from the adding queue // Toggle the flag, that shows, the scenario allready has some teams in the table before this update teamsToAdd[rec.ScenarioId].Remove(rec.TeamId); addTheEntireTeamList[rec.ScenarioId] = false; } } if (recsToRemove.Count > 0) { // Process operations var teams2ScenariosForRemove = DbContext.Team2Scenario.Where(x => recsToRemove.Contains(x.Id)).ToList(); if (teams2ScenariosForRemove != null && teams2ScenariosForRemove.Count > 0) { DbContext.Team2Scenario.RemoveRange(teams2ScenariosForRemove); var allocationsTeamsForRemove = teams2ScenariosForRemove.Select(x => x.TeamId).Distinct().ToList(); var allocationsScenariosForRemove = teams2ScenariosForRemove.Select(x => x.ScenarioId).Distinct().ToList(); if ((allocationsTeamsForRemove != null && allocationsTeamsForRemove.Count > 0) && (allocationsScenariosForRemove != null && allocationsScenariosForRemove.Count > 0)) { var allocationsForRemove = DbContext.TeamAllocations.Where(x => allocationsTeamsForRemove.Contains(x.TeamId) && allocationsScenariosForRemove.Contains(x.ScenarioId)) .ToList(); if (allocationsForRemove != null && allocationsForRemove.Count > 0) DbContext.TeamAllocations.RemoveRange(allocationsForRemove); } } } foreach (Guid scenId in teamsToAdd.Keys) { int teamsAdded = 0; int allocationSumm = 0; int initialAllocationValue = 0; if (teamsToAdd[scenId].Count == 0) continue; if (addTheEntireTeamList[scenId]) // Spread 100% through teams, if scenario hasn't teams before we begin this update initialAllocationValue = (int)(100 / teamsToAdd[scenId].Count); foreach (Guid teamId in teamsToAdd[scenId]) { Team2Scenario item = new Team2Scenario() { Id = Guid.NewGuid(), ScenarioId = scenId, TeamId = teamId, Allocation = initialAllocationValue }; if ((initialAllocationValue > 0) && ((teamsToAdd[scenId].Count - teamsAdded) == 1)) // Process the last allocation value for some instance (e.g. 3 teams: 33% 33% 34%) item.Allocation = 100 - allocationSumm; teamsAdded++; allocationSumm += initialAllocationValue; DbContext.Team2Scenario.Add(item); } } } /// /// Checks the ability to remove teams from scenarios. Team can't be removed, if it has alloc > 0% /// /// Scenarios to check (ids) /// New teams to scenarios /// Teams, that can't be removed (key = team id, val = list of scenarios) /// SA. ENV-754 public Dictionary> GetViolatedUpdateScenarioTeams(List scenariosToCheck, List newTeamList) { // Idea: construct struct, where every scenario has the teams, that must be added to DB table Dictionary> violatedRecords = new Dictionary>(); // Get all existing in the DB table records for the given scenario list var teamRecordsCantBeRemoved = DbContext.Team2Scenario.Where(x => scenariosToCheck.Contains(x.ScenarioId) && !newTeamList.Contains(x.TeamId) && x.Allocation > 0).OrderBy(x => x.TeamId).AsNoTracking(); foreach (Team2Scenario teamRecord in teamRecordsCantBeRemoved) { if (!violatedRecords.ContainsKey(teamRecord.TeamId)) violatedRecords.Add(teamRecord.TeamId, new List()); violatedRecords[teamRecord.TeamId].Add(teamRecord.ScenarioId); } return violatedRecords; } /// /// Returns all non actuals scenarios for given project or project part /// /// Project or part id /// SA. ENV-754 public List GetProjectNonActualsScenarios(Guid projectOrPartId) { List scenarioParents = new List() { projectOrPartId }; Project currentProject = DbContext.Projects.FirstOrDefault(x => x.Id.Equals(projectOrPartId)); if (currentProject.Id.Equals(Guid.Empty)) throw new Exception("Project or part is empty"); if (currentProject.HasChildren) scenarioParents = DbContext.Projects.AsNoTracking().Where(x => x.ParentProjectId.HasValue && x.ParentProjectId.Value.Equals(projectOrPartId)).Select(x => x.Id).ToList(); List scenarios = DbContext.Scenarios.AsNoTracking().Where(x => x.ParentId.HasValue && scenarioParents.Contains(x.ParentId.Value) && x.Type != (int)ScenarioType.Actuals) .Select(x => x.Id).ToList(); return scenarios; } public Guid Save(ScenarioDetailsSnapshotSaveModel snapshotModel) { if (snapshotModel == null) throw new ArgumentNullException("snapshotModel"); if (snapshotModel.Scenario == null) throw new ArgumentException("Information about scenario does not exists"); if (snapshotModel.Scenario.StartDate <= 0 || snapshotModel.Scenario.EndDate <= 0) throw new ArgumentException("Scenario has incorrect start or end date"); if (snapshotModel.Scenario.SaveAs != SaveAsScenario.New && snapshotModel.Scenario.SaveAs != SaveAsScenario.Update) throw new ArgumentException(string.Format("Scenario save as parameter has unavailable value {0}", snapshotModel.Scenario.SaveAs)); var expCats = DbContext.ExpenditureCategory.ToDictionary(x => x.Id); var scenarioDetailItems = new List(); var teamAllocations = new List(); var resourceAllocations = new List(); var teams2Scenario = new Dictionary(); Scenario scenario = null; if (snapshotModel.Scenario.Id.HasValue && !Guid.Empty.Equals(snapshotModel.Scenario.Id)) scenario = DbContext.Scenarios.AsNoTracking().FirstOrDefault(x => x.Id == snapshotModel.Scenario.Id.Value); if (scenario != null) { scenarioDetailItems = DbContext.ScenarioDetail.AsNoTracking().Where(t => t.ParentID == scenario.Id).ToList(); teamAllocations = DbContext.TeamAllocations.AsNoTracking().Where(x => x.ScenarioId == scenario.Id).ToList(); resourceAllocations = DbContext.PeopleResourceAllocations.AsNoTracking().Where(x => x.ScenarioId == scenario.Id).ToList(); } if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update) { if (scenario == null) throw new InvalidOperationException(string.Format("Scenario with id [{0}] does not exists", snapshotModel.Scenario.Id)); scenario.ProjectedRevenue = snapshotModel.Scenario.ProjectedRevenue; scenario.TDDirectCosts = snapshotModel.Scenario.TDDirectCosts; scenario.UseLMMargin = Convert.ToInt32(snapshotModel.Scenario.UseLMMargin); scenario.ExpectedGrossMargin = snapshotModel.Scenario.GrossMargin / 100; scenario.ExpectedGrossMargin_LM = snapshotModel.Scenario.LMMargin / 100; } else { if (scenario != null) { scenario.Id = Guid.NewGuid(); scenario.LastUpdate = DateTime.UtcNow; scenario.EntryTimeStamp = DateTime.UtcNow; scenario.ProjectedRevenue = snapshotModel.Scenario.ProjectedRevenue; scenario.TDDirectCosts = snapshotModel.Scenario.TDDirectCosts; scenario.UseLMMargin = Convert.ToInt32(snapshotModel.Scenario.UseLMMargin); scenario.ExpectedGrossMargin = snapshotModel.Scenario.GrossMargin / 100; scenario.ExpectedGrossMargin_LM = snapshotModel.Scenario.LMMargin / 100; } else { var project = DbContext.Projects.AsNoTracking().FirstOrDefault(x => x.Id == snapshotModel.Scenario.ParentId); if (project == null) throw new InvalidOperationException(string.Format("Project with id [{0}] does not exists", snapshotModel.Scenario.ParentId)); scenario = new Scenario() { Id = Guid.NewGuid(), ParentId = snapshotModel.Scenario.ParentId, Type = snapshotModel.Scenario.Type.GetHashCode(), CGSplit = 1, EFXSplit = 0, FreezeRevenue = snapshotModel.Scenario.FreezeRevenue, LastUpdate = DateTime.UtcNow, Color = project.Color, ShotStartDate = null, EntryTimeStamp = DateTime.UtcNow, GrowthScenario = snapshotModel.Scenario.GrowthScenario, SystemAttributeObjectID = null }; if (snapshotModel.Scenario.SaveAsDraft) scenario.Status = ScenarioStatus.Draft.GetHashCode(); if (!snapshotModel.Scenario.SaveAsDraft) { if (project.IsRevenueGenerating) { scenario.ProjectedRevenue = snapshotModel.Scenario.ProjectedRevenue; scenario.UseLMMargin = Convert.ToInt32(snapshotModel.Scenario.UseLMMargin); // SA. ENV-939 if (!snapshotModel.Scenario.UseLMMargin) { scenario.ExpectedGrossMargin = snapshotModel.Scenario.GrossMargin / 100; // SA. ENV-939 scenario.TDDirectCosts = snapshotModel.Scenario.ProjectedRevenue - (snapshotModel.Scenario.ProjectedRevenue * snapshotModel.Scenario.GrossMargin / 100); } else { scenario.ExpectedGrossMargin_LM = snapshotModel.Scenario.LMMargin / 100; // SA. ENV-939 scenario.TDDirectCosts_LM = snapshotModel.Scenario.ProjectedRevenue - (snapshotModel.Scenario.ProjectedRevenue * snapshotModel.Scenario.LMMargin / 100); } } else { scenario.TDDirectCosts = snapshotModel.Scenario.TDDirectCosts; scenario.TDDirectCosts_LM = 0; } } } DbContext.Scenarios.Add(scenario); } scenario.Name = snapshotModel.Scenario.Name; scenario.TemplateId = snapshotModel.Scenario.TemplateId; scenario.StartDate = Constants.UnixEpochDate.AddMilliseconds(snapshotModel.Scenario.StartDate); scenario.EndDate = Constants.UnixEpochDate.AddMilliseconds(snapshotModel.Scenario.EndDate); scenario.Duration = snapshotModel.Scenario.Duration; List costSavingItems = new List(); if (!snapshotModel.Scenario.SaveAsDraft) { if (snapshotModel.Scenario.CostSavings != null) { scenario.CostSavings = snapshotModel.Scenario.CostSavings.CostSavings; if (snapshotModel.Scenario.CostSavings.CostSavingStartDate.HasValue && snapshotModel.Scenario.CostSavings.CostSavingStartDate > 0) scenario.CostSavingsStartDate = Constants.UnixEpochDate.AddMilliseconds(snapshotModel.Scenario.CostSavings.CostSavingStartDate.Value); if (snapshotModel.Scenario.CostSavings.CostSavingEndDate.HasValue && snapshotModel.Scenario.CostSavings.CostSavingEndDate > 0) scenario.CostSavingsEndDate = Constants.UnixEpochDate.AddMilliseconds(snapshotModel.Scenario.CostSavings.CostSavingEndDate.Value); scenario.CostSavingsType = Convert.ToInt16(snapshotModel.Scenario.CostSavings.CostSavingType); scenario.CostSavingsDescription = snapshotModel.Scenario.CostSavings.CostSavingDescription; } if (snapshotModel.Scenario.IsActiveScenario) { if (scenario.ParentId.HasValue) DeactivateScenarios(scenario.ParentId.Value, scenario.Type, scenario.Id); scenario.Status = ScenarioStatus.Active.GetHashCode(); } else { scenario.Status = ScenarioStatus.Inactive.GetHashCode(); } #region Retrieve teams allocation if (snapshotModel.TeamsInScenario != null && snapshotModel.TeamsInScenario.Count > 0) teams2Scenario = snapshotModel.TeamsInScenario.ToDictionary(x => x.TeamId, g => g.Allocation); #endregion if (snapshotModel.Calendar != null) { foreach (var scenarioItem in snapshotModel.Calendar.GetScenarioDetails(true)) { ScenarioDetail currentDbItem = null; if (scenarioItem.Id != Guid.Empty) currentDbItem = scenarioDetailItems.FirstOrDefault(x => x.Id == scenarioItem.Id); // if it is new item we need to check by unique key if (currentDbItem == null) currentDbItem = scenarioDetailItems.FirstOrDefault(x => x.ParentID == snapshotModel.Scenario.Id && x.ExpenditureCategoryId == scenarioItem.ExpenditureCategoryId && x.WeekEndingDate == scenarioItem.WeekEndingDate); if (currentDbItem == null) { currentDbItem = new ScenarioDetail { Id = Guid.NewGuid(), ParentID = scenario.Id, ExpenditureCategoryId = scenarioItem.ExpenditureCategoryId, WeekEndingDate = scenarioItem.WeekEndingDate, Quantity = scenarioItem.Quantity, WeekOrdinal = scenarioItem.WeekOrdinal, Cost = scenarioItem.Cost, }; scenarioDetailItems.Add(currentDbItem); // if we update current scenario we need to add redundant details, // otherwise we need to insert all scenario details list to new scenario if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update) DbContext.ScenarioDetail.Add(currentDbItem); } else if (currentDbItem.Quantity != scenarioItem.Quantity || currentDbItem.Cost != scenarioItem.Cost) { currentDbItem.WeekEndingDate = scenarioItem.WeekEndingDate; currentDbItem.WeekOrdinal = scenarioItem.WeekOrdinal; currentDbItem.Quantity = scenarioItem.Quantity; currentDbItem.Cost = scenarioItem.Cost; if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update) DbContext.Entry(currentDbItem).State = EntityState.Modified; } } foreach (var expGroup in snapshotModel.Calendar.Expenditures) { foreach (var team in expGroup.Value.Teams) { #region Team Allocation var currentTeamAllocations = teamAllocations.FindAll(x => x.TeamId.ToString() == team.Key && x.ExpenditureCategoryId.ToString() == expGroup.Key) .ToDictionary(x => x.WeekEndingDate); foreach (var allocationItem in team.Value.QuantityValues) { long milliseconds; if (!long.TryParse(allocationItem.Key, out milliseconds) || milliseconds <= 0) continue; var date = Constants.UnixEpochDate.AddMilliseconds(milliseconds); var dbAllocationItem = currentTeamAllocations.ContainsKey(date) ? currentTeamAllocations[date] : null; if (allocationItem.Value < 0) team.Value.QuantityValues[allocationItem.Key] = 0; if (dbAllocationItem == null) { dbAllocationItem = new TeamAllocation() { Id = Guid.NewGuid(), ScenarioId = scenario.Id, ExpenditureCategoryId = expGroup.Value.ExpenditureCategoryId, TeamId = new Guid(team.Key), WeekEndingDate = date, Quantity = allocationItem.Value, LastUpdate = DateTime.UtcNow }; if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update) DbContext.TeamAllocations.Add(dbAllocationItem); else teamAllocations.Add(dbAllocationItem); } else if (dbAllocationItem.Quantity != allocationItem.Value) { if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update) { dbAllocationItem.Quantity = allocationItem.Value; DbContext.Entry(dbAllocationItem).State = EntityState.Modified; } else teamAllocations.FirstOrDefault(x => x.Id == dbAllocationItem.Id).Quantity = allocationItem.Value; } } #endregion #region Resource Allocation var currentExpCatAllocations = resourceAllocations.FindAll(x => x.ExpenditureCategoryId == expGroup.Value.ExpenditureCategoryId); var removedResources = currentExpCatAllocations.FindAll(x => team.Value.Resources.Where(r => r.Value.Deleted).Select(r => r.Value.Id).Contains(x.PeopleResourceId)); if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update) { foreach (var resource in removedResources) DbContext.Entry(resource).State = EntityState.Deleted; } else resourceAllocations.RemoveAll(x => removedResources.Any(t => t.Id == x.Id)); foreach (var resource in team.Value.Resources.Where(x => !x.Value.Deleted)) { foreach (var allocationItem in resource.Value.QuantityValues) { long milliseconds; if (!long.TryParse(allocationItem.Key, out milliseconds) || milliseconds <= 0) continue; var date = Constants.UnixEpochDate.AddMilliseconds(milliseconds); var dbAllocationItem = currentExpCatAllocations.FirstOrDefault(x => x.WeekEndingDate == date && x.PeopleResourceId == resource.Value.Id); if (allocationItem.Value < 0) resource.Value.QuantityValues[allocationItem.Key] = 0; if (dbAllocationItem == null) { dbAllocationItem = new PeopleResourceAllocation() { Id = Guid.NewGuid(), ExpenditureCategoryId = expGroup.Value.ExpenditureCategoryId, PeopleResourceId = resource.Value.Id, ScenarioId = scenario.Id, WeekEndingDate = date, Quantity = allocationItem.Value }; if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update) DbContext.PeopleResourceAllocations.Add(dbAllocationItem); else resourceAllocations.Add(dbAllocationItem); } else if (dbAllocationItem.Quantity != allocationItem.Value) { if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update) { dbAllocationItem.Quantity = allocationItem.Value; DbContext.Entry(dbAllocationItem).State = EntityState.Modified; } else resourceAllocations.FirstOrDefault(x => x.Id == dbAllocationItem.Id).Quantity = allocationItem.Value; } } } #endregion } } } var expCatsInScenario = snapshotModel.AvailableExpenditures.Select(x => x.Id).ToList(); var startDate = Constants.UnixEpochDate.AddMilliseconds(snapshotModel.Scenario.StartDate); var endDate = Constants.UnixEpochDate.AddMilliseconds(snapshotModel.Scenario.EndDate).AddDays(7); #region Removing of scenario details Predicate predicate = (t => t.ParentID.HasValue && t.ParentID == snapshotModel.Scenario.Id && (t.WeekEndingDate < startDate || t.WeekEndingDate >= endDate || !expCatsInScenario.Contains(t.ExpenditureCategoryId ?? Guid.Empty))); var items2Delete = scenarioDetailItems.FindAll(predicate); if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update) { foreach (var scenarioDetail in items2Delete) DbContext.Entry(scenarioDetail).State = EntityState.Deleted; } scenarioDetailItems.RemoveAll(predicate); #endregion #region Removing of teams allocations Predicate teamDeletePredicate = (t => t.ScenarioId == snapshotModel.Scenario.Id && (t.WeekEndingDate < startDate || t.WeekEndingDate >= endDate || !expCatsInScenario.Contains(t.ExpenditureCategoryId) || !teams2Scenario.Keys.Contains(t.TeamId))); var teams2Delete = teamAllocations.FindAll(teamDeletePredicate); if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update) { foreach (var allocation in teams2Delete) DbContext.Entry(allocation).State = EntityState.Deleted; } teamAllocations.RemoveAll(teamDeletePredicate); #endregion if (snapshotModel.Scenario.SaveAs == SaveAsScenario.New) { foreach (var scenarioDetail in scenarioDetailItems) { DbContext.ScenarioDetail.Add(new ScenarioDetail() { Id = Guid.NewGuid(), ParentID = scenario.Id, ExpenditureCategoryId = scenarioDetail.ExpenditureCategoryId, Quantity = scenarioDetail.Quantity, Cost = scenarioDetail.Cost, WeekEndingDate = scenarioDetail.WeekEndingDate, WeekOrdinal = scenarioDetail.WeekOrdinal, LastUpdate = DateTime.UtcNow }); } foreach (var resource in resourceAllocations) { DbContext.PeopleResourceAllocations.Add(new PeopleResourceAllocation() { Id = Guid.NewGuid(), ExpenditureCategoryId = resource.ExpenditureCategoryId, PeopleResourceId = resource.PeopleResourceId, Quantity = resource.Quantity, ScenarioId = scenario.Id, WeekEndingDate = resource.WeekEndingDate, LastUpdate = DateTime.UtcNow }); } // if cost savings didn't change need to copy cost savings from original if (snapshotModel.Scenario.CostSavings == null) { var costSavings = DbContext.CostSavings.Where(x => x.ScenarioId == snapshotModel.Scenario.Id).ToList(); foreach (var costSaving in costSavings) { DbContext.CostSavings.Add(new CostSaving() { Id = Guid.NewGuid(), ScenarioId = scenario.Id, Year = costSaving.Year, Month = costSaving.Month, Cost = costSaving.Cost }); } } foreach (var allocation in teamAllocations) { DbContext.TeamAllocations.Add(new TeamAllocation() { Id = Guid.NewGuid(), ScenarioId = scenario.Id, TeamId = allocation.TeamId, ExpenditureCategoryId = allocation.ExpenditureCategoryId, WeekEndingDate = allocation.WeekEndingDate, Quantity = allocation.Quantity, LastUpdate = DateTime.UtcNow }); } } UpdateTeamsAllocation(scenario, teams2Scenario, false); #region Save Cost Saving Info if (snapshotModel.Scenario.CostSavings != null) { if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update) { var costSavings = DbContext.CostSavings.Where(x => x.ScenarioId == scenario.Id); DbContext.CostSavings.RemoveRange(costSavings); } if (snapshotModel.Scenario.CostSavings.CostSavingItems != null) { foreach (var yearItem in snapshotModel.Scenario.CostSavings.CostSavingItems) { for (var month = 1; month < yearItem.Costs.Length; month++) { if (yearItem.Costs[month].HasValue) { var costSaving = new CostSaving { Id = Guid.NewGuid(), Cost = yearItem.Costs[month], Month = (short)month, Year = yearItem.Year, ScenarioId = scenario.Id }; costSavingItems.Add(costSaving); DbContext.CostSavings.Add(costSaving); } } } } } #endregion var parentProject = DbContext.Projects.AsNoTracking().Include(x => x.Type).FirstOrDefault(x => x.Id == scenario.ParentId); var forecast = scenarioDetailItems.Where(x => x.ExpenditureCategoryId.HasValue) .Select(x => new ScenarioDetailsListItem() { Id = x.Id, DetailCost = x.Cost ?? 0, CategoryType = (ExpenditureCategoryModel.CategoryTypes)(expCats[x.ExpenditureCategoryId.Value].Type ?? 0), ParentId = scenario.Id, WeekEndingDate = x.WeekEndingDate }).ToList(); SetBottomUpCosts(scenario, (parentProject == null || parentProject.Type == null || parentProject.IsRevenueGenerating), null, forecast); } if (!snapshotModel.Scenario.SaveAsDraft) scenario.ROIDate = GetROIDate(scenario, costSavingItems); if (snapshotModel.Scenario.SaveAs == SaveAsScenario.New) DbContext.Entry(scenario).State = EntityState.Added; if (IsContextLocal) DbContext.SaveChanges(); return scenario.Id; } /// /// Updates team allocation on the given scenario, all other scenarios of the project and the project /// /// Current updatable scenario id /// Incoming team allocation public void UpdateTeamsAllocation(Scenario currentScenario, Dictionary teamAllocations, bool appendProjectTeamsToScenario) { if (currentScenario == null || currentScenario.Id.Equals(Guid.Empty)) throw new Exception("Scenario not found in database"); if (!currentScenario.ParentId.HasValue || currentScenario.ParentId.Value.Equals(Guid.Empty)) throw new Exception("Scenario has no parent project"); var project = DbContext.Projects.Where(x => x.Id.Equals(currentScenario.ParentId.Value)).FirstOrDefault(); if (project == null || project.Id.Equals(Guid.Empty)) throw new Exception("Project not found"); // Get incoming team allocations and store teams in the separate list to use later List incomingTeams = new List(); incomingTeams.AddRange(teamAllocations.Keys); // Modify teams in current scenario var existingScenarioTeamRecords = DbContext.Team2Scenario.Where(x => x.ScenarioId.Equals(currentScenario.Id)).ToList(); foreach (Team2Scenario rec in existingScenarioTeamRecords) { if (teamAllocations.Keys.Contains(rec.TeamId)) { rec.Allocation = teamAllocations[rec.TeamId]; teamAllocations.Remove(rec.TeamId); } } // Add new teams to current scenario foreach (Guid teamId in teamAllocations.Keys) { Team2Scenario item = new Team2Scenario() { Id = Guid.NewGuid(), TeamId = teamId, ScenarioId = currentScenario.Id, Allocation = teamAllocations[teamId] }; if (incomingTeams.Count == 1) // Set alloca=100%, if the result scenario team list containt only one record item.Allocation = 100; DbContext.Team2Scenario.Add(item); } var teams2ScenarioForRemove = DbContext.Team2Scenario.Where(x => x.ScenarioId == currentScenario.Id && !incomingTeams.Contains(x.TeamId)).ToList(); if (teams2ScenarioForRemove != null && teams2ScenarioForRemove.Count > 0) { var teamsForRemove = teams2ScenarioForRemove.Select(t => t.TeamId).ToList(); var allocacationForRemove = DbContext.TeamAllocations.Where(x => x.ScenarioId == currentScenario.Id && teamsForRemove.Contains(x.TeamId)); DbContext.TeamAllocations.RemoveRange(allocacationForRemove); DbContext.Team2Scenario.RemoveRange(teams2ScenarioForRemove); } var manager = new ProjectManager(DbContext); manager.UpdateTeamsOfProjectAndScenarios(project, incomingTeams, currentScenario.Id, appendProjectTeamsToScenario); } public List GetScenariosByParentProject(Guid scenarioId) { return (from scenario in DbContext.Scenarios join current in DbContext.Scenarios on scenario.ParentId equals current.ParentId where current.Id == scenarioId select scenario.Id).ToList(); } public List GetScenarioDetails(List scenarios, List expenditureCategories) { if (scenarios == null || scenarios.Count <= 0) return new List(); var detailsQuery = DbContext.ScenarioDetail.Where(x => x.ParentID.HasValue && scenarios.Contains(x.ParentID.Value)); if (expenditureCategories != null && expenditureCategories.Count > 0) detailsQuery = detailsQuery.Where(x => x.ExpenditureCategoryId.HasValue && expenditureCategories.Contains(x.ExpenditureCategoryId.Value)); return detailsQuery.ToList(); } public List GetScenarioProxyDetails(ScenarioCalendarFilterModel model, DateTime? startDate, DateTime? endDate, IEnumerable scenarioList) { var scenarioDetailsQuery = DbContext.VW_ScenarioAndProxyDetails.AsNoTracking() .Where(x => x.ParentID.HasValue && scenarioList.Contains(x.ParentID.Value)); if (startDate.HasValue) scenarioDetailsQuery = scenarioDetailsQuery.Where(x => x.WeekEndingDate >= startDate.Value); if (endDate.HasValue) scenarioDetailsQuery = scenarioDetailsQuery.Where(x => x.WeekEndingDate <= endDate.Value); if (model != null) { ExpenditureCategoryModel.CategoryTypes? categoryType = null; if (!string.IsNullOrWhiteSpace(model.CategoryType)) { if (Enum.IsDefined(typeof(ExpenditureCategoryModel.CategoryTypes), model.CategoryType)) categoryType = (ExpenditureCategoryModel.CategoryTypes) Enum.Parse(typeof(ExpenditureCategoryModel.CategoryTypes), model.CategoryType); } if (model.CreditDepartment != null) scenarioDetailsQuery = scenarioDetailsQuery.Where(t => t.CreditId == model.CreditDepartment); if (model.GLAccount != null) scenarioDetailsQuery = scenarioDetailsQuery.Where(t => t.GLId == model.GLAccount); if (model.LaborMaterials != null) scenarioDetailsQuery = scenarioDetailsQuery.Where(t => t.CGEFX == model.LaborMaterials.ToString()); if (categoryType.HasValue) scenarioDetailsQuery = scenarioDetailsQuery.Where(t => t.Type == (int?)categoryType); if (model.IncomeType != null) scenarioDetailsQuery = scenarioDetailsQuery.Where(t => t.SystemAttributeOne == model.IncomeType); if (model.SelectedExpCats != null && model.SelectedExpCats.Any()) scenarioDetailsQuery = scenarioDetailsQuery.Where(t => model.SelectedExpCats.Contains(t.ExpenditureCategoryId.Value)); } return scenarioDetailsQuery.OrderBy(x => x.ExpCategoryWithCcName).ThenBy(x => x.WeekEndingDate).ToList(); // SA. ENV-839 } public Dictionary BuildExpendituresForScenario(List weekEndings, List forecastDetails, List actualsDetails) { var uoms = DbContext.UOMs.ToDictionary(x => x.Id, g => g.UOMValue); if (forecastDetails == null) forecastDetails = new List(); if (actualsDetails == null) actualsDetails = new List(); var expCatGroups = forecastDetails.Union(actualsDetails).Select(x => new ExpenditureDetail() { ExpenditureCategoryId = x.ExpenditureCategoryId ?? Guid.Empty, ExpenditureCategoryName = x.ExpCategoryWithCcName, // SA. ENV-839 Type = x.Type ?? 0, UseType = x.UseType ?? 1, CGEFX = x.CGEFX, GLId = x.GLId, UOMId = x.UOMId, CreditId = x.CreditId, SystemAttributeOne = x.SystemAttributeOne, SystemAttributeTwo = x.SystemAttributeTwo, SortOrder = x.SortOrder, UOMValue = uoms[x.UOMId] }).Distinct().ToDictionary(x => x.ExpenditureCategoryId.ToString()); #region Preparing scenario details foreach (var ec in expCatGroups) { for (int i = 0; i < weekEndings.Count; i++) { var timeKey = weekEndings[i].Subtract(Constants.UnixEpochDate).TotalMilliseconds.ToString(); var detailsItem = new ScenarioDetailsItem(); var forecastValue = forecastDetails.FirstOrDefault(x => x.WeekEndingDate == weekEndings[i] && x.ExpenditureCategoryId.ToString() == ec.Key); if (forecastValue != null) { detailsItem.ForecastId = forecastValue.Id; detailsItem.ForecastCost = forecastValue.Cost; detailsItem.ForecastQuantity = forecastValue.Quantity; detailsItem.WeekOrdinal = forecastValue.WeekOrdinal ?? 0; } var actualsValue = actualsDetails.FirstOrDefault(x => x.WeekEndingDate == weekEndings[i] && x.ExpenditureCategoryId.ToString() == ec.Key); if (actualsValue != null) { detailsItem.ActualsId = actualsValue.Id; detailsItem.ActualsCost = actualsValue.Cost; detailsItem.ActualsQuantity = actualsValue.Quantity; detailsItem.WeekOrdinal = actualsValue.WeekOrdinal ?? 0; } ec.Value.Details.Add(timeKey, detailsItem); } } #endregion return expCatGroups; } public List FillTeamsDetails(Guid scenarioId, Dictionary expenditures, string userId, Dictionary teamsWithAllocation = null) { var prManager = (new PeopleResourcesManager(DbContext)); var teamManager = (new TeamManager(DbContext)); #region Retrieving necessary information from the database var scenario = Load(scenarioId); if (scenario == null) throw new InvalidOperationException(string.Format("Scenario with Id={0} doesn't exists. Operation has been terminated.", scenarioId)); var scenariosInParent = GetScenariosByParentProject(scenarioId).Except(new List() { scenarioId }); var teamsInfo = new List(); var assesibleTeams = DbContext.User2Team.Where(x => x.UserId == userId).Select(x => x.TeamId).ToList(); if (teamsWithAllocation == null || teamsWithAllocation.Count <= 0) { // need to take teams by project because if scenario saved as draft it has no saved teams 2 scenario teamsInfo = DbContext.Team2Project .Where(x => x.Project.Scenarios.Any(s => s.Id == scenarioId)) .Select(x => new TeamInfoModel() { Id = x.TeamId, Name = x.Team.Name, PlannedCapacityScenarioId = x.Team.PlannedCapacityScenarioId, Allocation = DbContext.Team2Scenario.Any(t => t.TeamId == x.TeamId && t.ScenarioId == scenarioId) ? DbContext.Team2Scenario.FirstOrDefault(t => t.TeamId == x.TeamId && t.ScenarioId == scenarioId).Allocation : 0, CanBeDeleted = !DbContext.Team2Scenario.Any(t => scenariosInParent.Contains(t.ScenarioId) && t.Allocation > 0 && t.TeamId == x.TeamId), Resources = x.Team.PeopleResources, IsAccessible = assesibleTeams.Contains(x.TeamId) }).ToList(); } else { teamsInfo = DbContext.Teams .Include(x => x.PeopleResources) .Where(x => teamsWithAllocation.Keys.Contains(x.Id)) .Select(x => new TeamInfoModel() { Id = x.Id, Name = x.Name, PlannedCapacityScenarioId = x.PlannedCapacityScenarioId, Allocation = 0, CanBeDeleted = !DbContext.Team2Scenario.Any(t => scenariosInParent.Contains(t.ScenarioId) && t.Allocation > 0 && t.TeamId == x.Id), Resources = x.PeopleResources, IsAccessible = assesibleTeams.Contains(x.Id) }).ToList(); foreach (var teamInfo in teamsInfo) teamInfo.Allocation = teamsWithAllocation[teamInfo.Id]; } #region Retrieving expenditures for teams var requiredTeams = teamsInfo.Select(x => x.Id).ToList(); var expCatsInTeams = (from team in DbContext.Teams from detail in DbContext.ScenarioDetail where (team.ActualCapacityScenarioId == detail.ParentID || team.PlannedCapacityScenarioId == detail.ParentID) && requiredTeams.Contains(team.Id) select new { TeamId = team.Id, ExpCatId = detail.ExpenditureCategoryId.Value }) .Distinct() .GroupBy(x => x.ExpCatId) .ToDictionary(x => x.Key.ToString(), g => g.Select(s => s.TeamId).ToList()); #endregion var allResources = teamsInfo.SelectMany(x => x.Resources) .Select(x => new { x.Id, Name = x.FirstName + " " + x.LastName, LastName = x.LastName, TeamId = x.TeamId.Value, ExpedentureCategoryId = x.ExpenditureCategoryId, x.IsActiveEmployee, x.StartDate, x.EndDate }).Distinct().ToDictionary(t => t.Id); var allResIds = allResources.Select(t => t.Key); var vacations = prManager.GetVacations(allResIds) .Select(t => new ResourceLeavingInfoModel() { PeopleResourceId = t.PeopleResourceId, HoursOff = t.HoursOff, WeekEndingDate = t.WeekEndingDate }).ToList(); var trainings = prManager.GetTrainings(allResIds) .Select(t => new ResourceLeavingInfoModel() { PeopleResourceId = t.PeopleResourceId, HoursOff = t.HoursOff, WeekEndingDate = t.WeekEndingDate }).ToList(); // we should retrieve all resources allocations in all active scenarios except active scenario of current project // but we should take into consideration allocations in current scenario no matter it is active or inactive // it is important, because current scenario can become active scenario but project's active scenario will become inactive after that // contact EN for more details var resourcesAllocation = DbContext.PeopleResourceAllocations.Where(x => allResIds.Contains(x.PeopleResourceId) && ((x.Scenario.Status == (int)ScenarioStatus.Active && x.Scenario.ParentId != scenario.ParentId) || x.ScenarioId == scenarioId)) .ToList(); var requiredCategories = expCatsInTeams.Select(x => Guid.Parse(x.Key)).ToList(); var teamsAllocation = teamManager.GetTeamsAllocation(requiredTeams, null, requiredCategories); var plannedScenarios = teamsInfo.Where(x => x.PlannedCapacityScenarioId.HasValue).Select(x => x.PlannedCapacityScenarioId.Value).ToList(); var teamsPlannedCapacity = GetScenarioDetailsTree(plannedScenarios, requiredCategories); var allResourcesInScenario = resourcesAllocation.Where(x => x.ScenarioId == scenarioId) .Select(x => x.PeopleResourceId).Distinct().ToList(); #endregion #region Preparing team details if (expenditures != null && expenditures.Count > 0) { foreach (var ec in expenditures) { if (!expCatsInTeams.ContainsKey(ec.Key)) continue; foreach (var team in teamsInfo.FindAll(x => expCatsInTeams[ec.Key].Contains(x.Id))) { var resourcesInTeam = allResources.Where(x => x.Value.ExpedentureCategoryId.ToString() == ec.Key && x.Value.TeamId == team.Id) .Distinct().Select(x => new TeamResourceSortable { Id = x.Key, Name = x.Value.Name, LastName = x.Value.LastName, IsActiveEmployee = x.Value.IsActiveEmployee, }).ToList(); var teamDetails = new ExpenditureDetailsTeam() { Id = team.Id, Name = team.Name, CanBeDeleted = team.CanBeDeleted, IsAccessible = assesibleTeams.Contains(team.Id) }; var resourcesAllocationByTeam = resourcesAllocation.Where(x => resourcesInTeam.Any(r => r.Id == x.PeopleResourceId) && x.ExpenditureCategoryId.ToString() == ec.Key) .ToList(); foreach (var detail in ec.Value.Details) { var weekEnding = Constants.UnixEpochDate.AddMilliseconds(long.Parse(detail.Key)); var allocation = resourcesAllocationByTeam.Where(x => x.WeekEndingDate == weekEnding).ToList(); #region Filling resource capacity and quantity according to allocation in current scenario foreach (var resource in resourcesInTeam) { // if resource does'n exists in this period we should fill all quantities with 0 var isReadOnlyResource = weekEnding < allResources[resource.Id].StartDate || weekEnding > allResources[resource.Id].EndDate; if (isReadOnlyResource) { resource.CapacityQuantityValues.Add(detail.Key, 0); resource.RestQuantityValues.Add(detail.Key, 0); resource.ReadOnly.Add(detail.Key, true); } else { var vacationsSum = prManager.ResolveResourceLeaving(resource.Id, weekEnding, vacations); var trainingsSum = prManager.ResolveResourceLeaving(resource.Id, weekEnding, trainings); var capacity = ec.Value.UOMValue - vacationsSum - trainingsSum; var totalAllocation = allocation.Where(x => x.PeopleResourceId == resource.Id).Sum(x => x.Quantity ?? 0); resource.CapacityQuantityValues.Add(detail.Key, Math.Max(capacity, 0)); // it is OK to have a negative value for the rest quantity resource.RestQuantityValues.Add(detail.Key, capacity - totalAllocation); resource.ReadOnly.Add(detail.Key, false); } // we should show original value even if resource is unavailable on this week (for ability to see corrupted data) var quantity = allocation.Where(x => x.PeopleResourceId == resource.Id && x.ScenarioId == scenarioId) .Sum(x => x.Quantity ?? 0); resource.QuantityValues.Add(detail.Key, Math.Max(quantity, 0)); } #endregion #region Calculation team's allocation in current scenario and rest quantity according to team's allocation in all active scenarios decimal teamCapacity = 0; if (weekEnding < DateTime.UtcNow.Date) { teamCapacity = resourcesInTeam.Sum(x => x.CapacityQuantityValues[detail.Key]); } else { if (team.PlannedCapacityScenarioId.HasValue) { var teamPlannedCapacity = teamsPlannedCapacity[team.PlannedCapacityScenarioId.Value]; if (teamPlannedCapacity.ContainsKey(new Guid(ec.Key))) { var plannedCapacity = teamPlannedCapacity[new Guid(ec.Key)].FirstOrDefault(x => x.WeekEndingDate == weekEnding); if (plannedCapacity != null) teamCapacity = plannedCapacity.Quantity ?? 0; } } } var teamAllocation4Week = teamsAllocation.FindAll(x => x.ExpenditureCategoryId.ToString() == ec.Key && x.TeamId == team.Id && x.WeekEndingDate == weekEnding); var teamAllocationInScenario = teamAllocation4Week.FirstOrDefault(x => x.ScenarioId == scenario.Id); var teamAllocationInOtherScenarios = teamAllocation4Week.Where(x => x.ScenarioId != scenario.Id).Sum(x => x.Quantity); decimal teamQuantity = 0; if (teamAllocationInScenario != null) { teamQuantity = teamAllocationInScenario.Quantity; } else { // by default team should take quantity value according to team 2 scenario allocation teamQuantity = (detail.Value.ForecastQuantity ?? 0) * (decimal)(0.01 * team.Allocation); // if the team allocation is not yet saved we need to mark it as changed for future saving teamDetails.Changed = true; } var restQuantity = teamCapacity - teamAllocationInOtherScenarios - teamQuantity; teamDetails.CapacityQuantityValues.Add(detail.Key, Math.Max(teamCapacity, 0)); teamDetails.QuantityValues.Add(detail.Key, Math.Max(teamQuantity, 0)); // it is OK to have a negative value for the rest quantity teamDetails.RestQuantityValues.Add(detail.Key, restQuantity); #endregion } teamDetails.Resources = resourcesInTeam.FindAll(x => allResourcesInScenario.Contains(x.Id)). Select(x => (TeamResource)x).ToDictionary(x => x.Id.ToString()); teamDetails.AllResources = resourcesInTeam.OrderBy(x => x.LastName). Select(x => (TeamResource)x).ToDictionary(x => x.Id.ToString()); if (!ec.Value.Teams.ContainsKey(teamDetails.Id.ToString())) ec.Value.Teams.Add(teamDetails.Id.ToString(), teamDetails); } } } #endregion return teamsInfo.Select(x => new TeamInScenarioModel { TeamId = x.Id, IsNew = false, IsAccessible = x.IsAccessible }).ToList(); } public Dictionary GetFullAllocationInfoByScenario(Guid scenarioId, string userId) { if (scenarioId == Guid.Empty) throw new ArgumentNullException("scenarioId"); var forecastDetails = GetScenarioProxyDetails(null, null, null, new List { scenarioId }); var weekEndings = forecastDetails.Where(x => x.WeekEndingDate.HasValue).Select(x => x.WeekEndingDate.Value).Distinct().ToList(); var expenditures = BuildExpendituresForScenario(weekEndings, forecastDetails, null); FillTeamsDetails(scenarioId, expenditures, userId); return expenditures; } /// /// Returns tree-structure of scenario details: Scenarios[ScenarioId, Expenditure Categories[ExpCatId, ScenarioDetails]] /// /// Scenarios Id's for which method will returns scenario details /// ECs for which method will returns scenario details /// public Dictionary>> GetScenarioDetailsTree(List scenarios, List expenditureCategories) { var scenarioDetails = GetScenarioDetails(scenarios, expenditureCategories); return scenarioDetails .GroupBy(sd => sd.ParentID.Value) .ToDictionary(scenario => scenario.Key, ecs => ecs.GroupBy(ec => ec.ExpenditureCategoryId.Value) .ToDictionary(ec => ec.Key, details => details.ToList())); } public Dictionary GetCategoriesInScenarios(List scenarios) { var expCatsInScenarios = DbContext.ScenarioDetail .Where(x => x.ParentID.HasValue && x.ExpenditureCategoryId.HasValue && scenarios.Contains(x.ParentID.Value)) .Select(x => x.ExpenditureCategoryId.Value); //return DbContext.ExpenditureCategory // .Where(x => expCatsInScenarios.Contains(x.Id)) // .Include(x => x.UOM) // .ToList() // .ToDictionary(x => x.Id); return DbContext.VW_ExpenditureCategory .Where(x => expCatsInScenarios.Contains(x.Id)) // .Include(x => x.UOM) .ToList() .ToDictionary(x => x.Id); } public List GetScenarios4Projects(List projects, ScenarioType? scenarioType, ScenarioStatus? scenarioStatus, bool includeCostSavings = false) { var scenarios = DbContext.Scenarios.Where(x => x.ParentId.HasValue && projects.Contains(x.ParentId.Value)); if (scenarioType.HasValue) scenarios = scenarios.Where(x => x.Type == (int)scenarioType.Value); if (scenarioStatus.HasValue) scenarios = scenarios.Where(x => x.Status == (int)scenarioStatus.Value); if (includeCostSavings) scenarios = scenarios.Include(x => x.CostSavings1); return scenarios.ToList(); } public List GetRates(Guid scenarioId) { var rates = (new RateManager(DbContext)).GetRates(scenarioId); return GetRatesModel(rates); } public List GetRatesModel(Dictionary> rates) { if (rates == null || rates.Count <= 0) return new List(); return rates.Select(t => new ScenarioCalendarRateModel() { expCatId = t.Key, rateValues = t.Value.Select(rate => new RateValueModel() { weekStartDate = Utils.ConvertToUnixDate(rate.StartDate), weekEndDate = Utils.ConvertToUnixDate(rate.EndDate), rateValue = rate.Rate1 }).ToList() }).ToList(); } public List GetRatesFromModel(List model) { if (model == null || model.Count <= 0) return new List(); var rates = new List(); foreach (var expCatRates in model) { foreach (var rate in expCatRates.rateValues) { rates.Add(new Rate() { ExpenditureCategoryId = expCatRates.expCatId, StartDate = Utils.ConvertFromUnixDate(rate.weekStartDate), EndDate = Utils.ConvertFromUnixDate(rate.weekEndDate), Rate1 = rate.rateValue }); } } return rates; } } #region Class AdjustScenarioDetailsDistribution /// /// Interpolates or extrapolates scenario details distribution. /// public class AdjustScenarioDetailsDistribution { #region Internal Types public enum DistributionType { Original = 0, KeepTotal = 1, ChangeDuration = 2 } #endregion #region Private variables private decimal periodScale; private readonly EnVisageEntities dbContext; #endregion #region Public properties public DateTime CutOffDate { get; set; } public DateTime ActualsEndDate { get; set; } public bool IsUpdate { get; set; } public FiscalCalendar[] FiscalCalendarWeeks { set; private get; } public Dictionary> CurrentScenarioDetails { set; private get; } public List CurrentActuals { set; private get; } public int CurrentPeriods { get; set; } public int NewPeriods { get; set; } public Guid[] MaterialsEcs { get; set; } public Guid ScenarioId { get; set; } public DateTime? StartDate { get; set; } public DateTime? EndDate { get; set; } public bool UseLMMargin { get; set; } public DistributionType Type { get; set; } #endregion #region Constructors public AdjustScenarioDetailsDistribution(EnVisageEntities dbcontext) { dbContext = dbcontext; } #endregion #region Methods /// /// Distribution recalculation. /// public Dictionary> CalculateDistribution() { if (MaterialsEcs == null) MaterialsEcs = new Guid[] { }; if (CurrentScenarioDetails == null) throw new NullReferenceException("Unable to calculate distribution because CurrentScenarioDetails is null;"); if (!(StartDate.HasValue || StartDate.HasValue)) throw new NullReferenceException("Unable to calculate distribution because StartDate or EndDate is null;"); var result = new Dictionary>(CurrentScenarioDetails.Count); //Creating result set of details, same ExpCat number // load fiscal years if empty collection if (FiscalCalendarWeeks == null || FiscalCalendarWeeks.Length == 0) { var tempEndDate = EndDate.HasValue ? EndDate.Value.AddDays(6) : (DateTime?)null; FiscalCalendarWeeks = dbContext.FiscalCalendars.Where( t => t.Type == (int)FiscalCalendarModel.FiscalYearType.Week && t.StartDate >= StartDate && t.EndDate <= tempEndDate && t.NonWorking == 0 && t.AdjustingPeriod == false).OrderBy(t => t.EndDate).ToArray(); } //transfer all columns > than cut-off date (system date for now) var PreviousWeeks = 0; // number of weeks for CutOff if (IsUpdate) { // if (CutOffDate > FiscalCalendarWeeks.Last().EndDate) PreviousWeeks = FiscalCalendarWeeks.Length; // else for (var i = FiscalCalendarWeeks.Length; i >= 0; i--) { if (i == 0) { PreviousWeeks = 0; break; } if ((CutOffDate > ActualsEndDate ? CutOffDate : ActualsEndDate) >= FiscalCalendarWeeks[i - 1].EndDate) { PreviousWeeks = i; break; } } foreach (var currentItem in CurrentScenarioDetails)//handling previous weeks { var newExpCatItem = new List(NewPeriods); for (var weekIndex = 0; weekIndex < PreviousWeeks; weekIndex++) { newExpCatItem.Add(new ScenarioManager.ScenarioDetailsListItem { ExpenditureCategoryId = currentItem.Value[0].ExpenditureCategoryId, ParentId = currentItem.Value[0].ParentId, WeekOrdinal = weekIndex + 1, WeekEndingDate = FiscalCalendarWeeks[weekIndex].EndDate, LastUpdate = DateTime.Today, ExpenditureCategoryName = currentItem.Value[0].ExpenditureCategoryName, CG_EFX = currentItem.Value[0].CG_EFX, UseType = currentItem.Value[0].UseType, CategoryType = currentItem.Value[0].CategoryType, UOMId = currentItem.Value[0].UOMId, GlId = currentItem.Value[0].GlId, CreditId = currentItem.Value[0].CreditId, SortOrder = currentItem.Value[0].SortOrder, SysField1 = currentItem.Value[0].SysField1, SysField2 = currentItem.Value[0].SysField2, }); if (weekIndex >= CurrentPeriods) { newExpCatItem[weekIndex].Quantity = 0; newExpCatItem[weekIndex].DetailCost = 0; newExpCatItem[weekIndex].Id = Guid.Empty; } else { if (FiscalCalendarWeeks[weekIndex].EndDate <= ActualsEndDate) { var currActuals = CurrentActuals.Where(x => x.ExpenditureCategoryId == (Guid?)currentItem.Value[0].ExpenditureCategoryId && x.WeekEndingDate == FiscalCalendarWeeks[weekIndex].EndDate).FirstOrDefault(); if (currActuals != null) { newExpCatItem[weekIndex].Quantity = currActuals.Quantity.Value; newExpCatItem[weekIndex].DetailCost = currActuals.Cost.Value; newExpCatItem[weekIndex].Id = currentItem.Value[weekIndex].Id; } else { newExpCatItem[weekIndex].Quantity = 0; newExpCatItem[weekIndex].DetailCost = 0; newExpCatItem[weekIndex].Id = currentItem.Value[weekIndex].Id; } } else { newExpCatItem[weekIndex].Quantity = currentItem.Value[weekIndex].Quantity; newExpCatItem[weekIndex].DetailCost = currentItem.Value[weekIndex].DetailCost; newExpCatItem[weekIndex].Id = currentItem.Value[weekIndex].Id; } } } result.Add(currentItem.Key, newExpCatItem); } if (NewPeriods == PreviousWeeks) periodScale = 0; else periodScale = (decimal)(CurrentPeriods - PreviousWeeks) / (decimal)(NewPeriods - PreviousWeeks); } else periodScale = (NewPeriods == 0) ? 0 : (decimal)CurrentPeriods / (decimal)NewPeriods; if (Type == DistributionType.Original || Type == DistributionType.KeepTotal) { foreach (var currentItem in CurrentScenarioDetails) { var preserveQuantity = (MaterialsEcs.Contains(currentItem.Key) && UseLMMargin) || Type == DistributionType.KeepTotal; var dbFact = 0M; var dbNewVal = 0M; var distributionStep = 1M; var processingWeek = PreviousWeeks; var parentId = IsUpdate ? currentItem.Value[0].ParentId : ScenarioId; var expCatId = currentItem.Key; var expCatName = currentItem.Value[0].ExpenditureCategoryName; var ecSplit = currentItem.Value[0].CG_EFX; var cost = 0; var useType = currentItem.Value[0].UseType; var expCatType = currentItem.Value[0].CategoryType; if (periodScale == 0) { periodScale = 1M; //if(ActualsEndDate >= StartDate) // PreviousWeeks = 0; } if (periodScale == 1.0M) //number of weeks in old an new periods match - no need to rescale { for (var weekIndex = PreviousWeeks; weekIndex < NewPeriods; weekIndex++) { if (ActualsEndDate > StartDate && ActualsEndDate < CutOffDate && FiscalCalendarWeeks[weekIndex].EndDate > ActualsEndDate && FiscalCalendarWeeks[weekIndex].EndDate <= CutOffDate) { continue; } if (!result.ContainsKey(currentItem.Key)) result.Add(currentItem.Key, new List()); if (result[currentItem.Key].Count <= weekIndex) result[currentItem.Key].Add(new ScenarioManager.ScenarioDetailsListItem()); var newListItem = result[currentItem.Key][weekIndex]; if (weekIndex < currentItem.Value.Count) { newListItem.Quantity = currentItem.Value[weekIndex].Quantity; newListItem.DetailCost = currentItem.Value[weekIndex].DetailCost; newListItem.Id = IsUpdate && weekIndex <= CurrentPeriods ? currentItem.Value[weekIndex].Id : Guid.Empty; } else { newListItem.Quantity = 0; newListItem.DetailCost = cost; newListItem.Id = Guid.Empty; } newListItem.ParentId = parentId; newListItem.ExpenditureCategoryId = expCatId; newListItem.WeekOrdinal = weekIndex + 1; //newListItem.DetailCost = cost; newListItem.WeekEndingDate = FiscalCalendarWeeks[weekIndex].EndDate; newListItem.LastUpdate = DateTime.Now; newListItem.ExpenditureCategoryName = expCatName; newListItem.CG_EFX = ecSplit; newListItem.UseType = useType; newListItem.CategoryType = expCatType; newListItem.UOMId = currentItem.Value[0].UOMId; newListItem.GlId = currentItem.Value[0].GlId; newListItem.CreditId = currentItem.Value[0].CreditId; newListItem.SortOrder = currentItem.Value[0].SortOrder; newListItem.SysField1 = currentItem.Value[0].SysField1; newListItem.SysField2 = currentItem.Value[0].SysField2; } } else if (periodScale < 1) // If we stretch { for (var weekIndex = PreviousWeeks; weekIndex < NewPeriods; weekIndex++) { if (!result.ContainsKey(currentItem.Key)) result.Add(currentItem.Key, new List()); if (result[currentItem.Key].Count <= weekIndex) result[currentItem.Key].Add(new ScenarioManager.ScenarioDetailsListItem()); var newListItem = result[currentItem.Key][weekIndex]; if ((dbFact + periodScale) < 1) { if (currentItem.Value.Count > processingWeek) newListItem.Quantity = currentItem.Value[processingWeek].Quantity * periodScale; else newListItem.Quantity = 0; dbFact = dbFact + periodScale; } else { if (processingWeek < (CurrentPeriods - 1)) newListItem.Quantity = (currentItem.Value[processingWeek].Quantity * (1 - dbFact)) + (currentItem.Value[processingWeek + 1].Quantity * ((dbFact + periodScale) - 1)); else newListItem.Quantity = (currentItem.Value[processingWeek].Quantity * (1 - dbFact)); processingWeek++; if (processingWeek > CurrentPeriods) { if (IsUpdate && weekIndex < CurrentPeriods) newListItem.Id = currentItem.Value[weekIndex].Id; else newListItem.Id = Guid.Empty; newListItem.ParentId = parentId; newListItem.ExpenditureCategoryId = expCatId; newListItem.WeekOrdinal = weekIndex + 1; newListItem.DetailCost = cost; newListItem.WeekEndingDate = FiscalCalendarWeeks[weekIndex].EndDate; newListItem.LastUpdate = DateTime.Now; newListItem.ExpenditureCategoryName = expCatName; newListItem.CG_EFX = ecSplit; newListItem.UseType = useType; newListItem.CategoryType = expCatType; newListItem.UOMId = currentItem.Value[0].UOMId; newListItem.GlId = currentItem.Value[0].GlId; newListItem.CreditId = currentItem.Value[0].CreditId; newListItem.SortOrder = currentItem.Value[0].SortOrder; newListItem.SysField1 = currentItem.Value[0].SysField1; newListItem.SysField2 = currentItem.Value[0].SysField2; break; } dbFact = ((dbFact + periodScale) - 1); } if (IsUpdate && weekIndex < CurrentPeriods) newListItem.Id = currentItem.Value[weekIndex].Id; else newListItem.Id = Guid.Empty; newListItem.Quantity = (preserveQuantity) ? newListItem.Quantity : newListItem.Quantity / periodScale; newListItem.ParentId = parentId; newListItem.ExpenditureCategoryId = expCatId; newListItem.WeekOrdinal = weekIndex + 1; newListItem.DetailCost = cost; newListItem.WeekEndingDate = FiscalCalendarWeeks[weekIndex].EndDate; newListItem.LastUpdate = DateTime.Now; newListItem.ExpenditureCategoryName = expCatName; newListItem.CG_EFX = ecSplit; newListItem.UseType = useType; newListItem.CategoryType = expCatType; newListItem.UOMId = currentItem.Value[0].UOMId; newListItem.GlId = currentItem.Value[0].GlId; newListItem.CreditId = currentItem.Value[0].CreditId; newListItem.SortOrder = currentItem.Value[0].SortOrder; newListItem.SysField1 = currentItem.Value[0].SysField1; newListItem.SysField2 = currentItem.Value[0].SysField2; } } else if (periodScale > 1) // If we shrink { for (var weekIndex = PreviousWeeks; weekIndex < CurrentPeriods; weekIndex++) { if (weekIndex == CurrentPeriods - 1 && distributionStep < 0.0001M) continue; //fix for demimal precision ceiling //if (processingWeek == FiscalCalendarWeeks.Count()) continue; if (!result.ContainsKey(currentItem.Key)) result.Add(currentItem.Key, new List()); if (result[currentItem.Key].Count <= processingWeek) result[currentItem.Key].Add(new ScenarioManager.ScenarioDetailsListItem()); var newListItem = result[currentItem.Key][processingWeek]; if (distributionStep < periodScale) { if (distributionStep < 1) dbNewVal += currentItem.Value[weekIndex - 1].Quantity * distributionStep; if (distributionStep == 1 || ((distributionStep % 1) == 0 && distributionStep < periodScale)) { dbNewVal += currentItem.Value[weekIndex].Quantity; distributionStep++; } else { if (periodScale - distributionStep >= 1) { dbNewVal += currentItem.Value[weekIndex].Quantity; distributionStep++; if (distributionStep == periodScale) { newListItem.Quantity = (preserveQuantity) ? dbNewVal : dbNewVal / periodScale; newListItem.Id = (IsUpdate && processingWeek <= CurrentPeriods) ? currentItem.Value[processingWeek].Id : Guid.Empty; newListItem.ParentId = parentId; newListItem.ExpenditureCategoryId = expCatId; newListItem.WeekOrdinal = processingWeek + 1; newListItem.DetailCost = cost; newListItem.WeekEndingDate = FiscalCalendarWeeks[processingWeek].EndDate; newListItem.LastUpdate = DateTime.Now; newListItem.ExpenditureCategoryName = expCatName; newListItem.CG_EFX = ecSplit; newListItem.UseType = useType; newListItem.CategoryType = expCatType; newListItem.UOMId = currentItem.Value[0].UOMId; newListItem.GlId = currentItem.Value[0].GlId; newListItem.CreditId = currentItem.Value[0].CreditId; newListItem.SortOrder = currentItem.Value[0].SortOrder; newListItem.SysField1 = currentItem.Value[0].SysField1; newListItem.SysField2 = currentItem.Value[0].SysField2; distributionStep = (NewPeriods < 4) ? 0 : 1; if (processingWeek < NewPeriods) // -1 { processingWeek++; dbNewVal = 0; } } } else { dbNewVal += ((periodScale - distributionStep) * currentItem.Value[weekIndex].Quantity); newListItem.Quantity = (preserveQuantity) ? dbNewVal : dbNewVal / periodScale; newListItem.Id = (IsUpdate && processingWeek <= CurrentPeriods) ? currentItem.Value[processingWeek].Id : Guid.Empty; newListItem.ParentId = parentId; newListItem.ExpenditureCategoryId = expCatId; newListItem.WeekOrdinal = processingWeek + 1; newListItem.DetailCost = cost; newListItem.WeekEndingDate = FiscalCalendarWeeks[processingWeek].EndDate; newListItem.LastUpdate = DateTime.Now; newListItem.ExpenditureCategoryName = expCatName; newListItem.CG_EFX = ecSplit; newListItem.UseType = useType; newListItem.CategoryType = expCatType; newListItem.UOMId = currentItem.Value[0].UOMId; newListItem.GlId = currentItem.Value[0].GlId; newListItem.CreditId = currentItem.Value[0].CreditId; newListItem.SortOrder = currentItem.Value[0].SortOrder; newListItem.SysField1 = currentItem.Value[0].SysField1; newListItem.SysField2 = currentItem.Value[0].SysField2; if (processingWeek < NewPeriods) // -1 { processingWeek++; dbNewVal = 0; } distributionStep = 1 - (periodScale - distributionStep); } } } else { if (periodScale != distributionStep) dbNewVal += ((periodScale - (distributionStep - 1)) * currentItem.Value[weekIndex].Quantity); newListItem.Quantity = (preserveQuantity) ? dbNewVal : dbNewVal / periodScale; newListItem.Id = (IsUpdate && processingWeek <= CurrentPeriods) ? currentItem.Value[processingWeek].Id : Guid.Empty; newListItem.ParentId = parentId; newListItem.ExpenditureCategoryId = expCatId; newListItem.WeekOrdinal = processingWeek + 1; newListItem.DetailCost = cost; newListItem.WeekEndingDate = FiscalCalendarWeeks[processingWeek].EndDate; newListItem.LastUpdate = DateTime.Now; newListItem.ExpenditureCategoryName = expCatName; newListItem.CG_EFX = ecSplit; newListItem.UseType = useType; newListItem.CategoryType = expCatType; newListItem.UOMId = currentItem.Value[0].UOMId; newListItem.GlId = currentItem.Value[0].GlId; newListItem.CreditId = currentItem.Value[0].CreditId; newListItem.SortOrder = currentItem.Value[0].SortOrder; newListItem.SysField1 = currentItem.Value[0].SysField1; newListItem.SysField2 = currentItem.Value[0].SysField2; distributionStep = distributionStep - periodScale; if (distributionStep == 0) distributionStep = 1; //if (processingWeek < NewPeriods) // -1 { processingWeek++; dbNewVal = 0; } } } if (!result.ContainsKey(currentItem.Key)) result.Add(currentItem.Key, new List()); if ((dbNewVal > 0 || result[currentItem.Key][result[currentItem.Key].Count - 1].ExpenditureCategoryId == Guid.Empty) && result[currentItem.Key].Count <= (processingWeek + 1)) { //result[currentItem.Key].Add(new ScenarioManager.ScenarioDetailsListItem()); if (result[currentItem.Key].Count > processingWeek) { result[currentItem.Key][processingWeek].Quantity = (preserveQuantity) ? dbNewVal : dbNewVal / periodScale; result[currentItem.Key][processingWeek].Id = (IsUpdate && processingWeek <= CurrentPeriods) ? currentItem.Value[processingWeek].Id : Guid.Empty; result[currentItem.Key][processingWeek].ParentId = parentId; result[currentItem.Key][processingWeek].ExpenditureCategoryId = expCatId; result[currentItem.Key][processingWeek].WeekOrdinal = processingWeek + 1; result[currentItem.Key][processingWeek].DetailCost = cost; result[currentItem.Key][processingWeek].WeekEndingDate = FiscalCalendarWeeks[processingWeek].EndDate; result[currentItem.Key][processingWeek].LastUpdate = DateTime.Now; result[currentItem.Key][processingWeek].ExpenditureCategoryName = expCatName; result[currentItem.Key][processingWeek].CG_EFX = ecSplit; result[currentItem.Key][processingWeek].UseType = useType; result[currentItem.Key][processingWeek].CategoryType = expCatType; result[currentItem.Key][processingWeek].UOMId = currentItem.Value[0].UOMId; result[currentItem.Key][processingWeek].GlId = currentItem.Value[0].GlId; result[currentItem.Key][processingWeek].CreditId = currentItem.Value[0].CreditId; result[currentItem.Key][processingWeek].SortOrder = currentItem.Value[0].SortOrder; result[currentItem.Key][processingWeek].SysField1 = currentItem.Value[0].SysField1; result[currentItem.Key][processingWeek].SysField2 = currentItem.Value[0].SysField2; } else { result[currentItem.Key][processingWeek - 1].Quantity = (preserveQuantity) ? dbNewVal : dbNewVal / periodScale; result[currentItem.Key][processingWeek - 1].Id = (IsUpdate && processingWeek <= CurrentPeriods) ? currentItem.Value[processingWeek].Id : Guid.Empty; result[currentItem.Key][processingWeek - 1].ParentId = parentId; result[currentItem.Key][processingWeek - 1].ExpenditureCategoryId = expCatId; result[currentItem.Key][processingWeek - 1].WeekOrdinal = processingWeek + 1; result[currentItem.Key][processingWeek - 1].DetailCost = cost; result[currentItem.Key][processingWeek - 1].WeekEndingDate = FiscalCalendarWeeks[processingWeek - 1].EndDate; result[currentItem.Key][processingWeek - 1].LastUpdate = DateTime.Now; result[currentItem.Key][processingWeek - 1].ExpenditureCategoryName = expCatName; result[currentItem.Key][processingWeek - 1].CG_EFX = ecSplit; result[currentItem.Key][processingWeek - 1].UseType = useType; result[currentItem.Key][processingWeek - 1].CategoryType = expCatType; result[currentItem.Key][processingWeek - 1].UOMId = currentItem.Value[0].UOMId; result[currentItem.Key][processingWeek - 1].GlId = currentItem.Value[0].GlId; result[currentItem.Key][processingWeek - 1].CreditId = currentItem.Value[0].CreditId; result[currentItem.Key][processingWeek - 1].SortOrder = currentItem.Value[0].SortOrder; result[currentItem.Key][processingWeek - 1].SysField1 = currentItem.Value[0].SysField1; result[currentItem.Key][processingWeek - 1].SysField2 = currentItem.Value[0].SysField2; } //if (result[currentItem.Key].Count > processingWeek) // result[currentItem.Key].RemoveAt(processingWeek); } } } } else if (Type == DistributionType.ChangeDuration) { foreach (var currentItem in CurrentScenarioDetails) { if (!result.ContainsKey(currentItem.Key)) result.Add(currentItem.Key, new List()); var leftValue = currentItem.Value.OrderBy(x => x.WeekEndingDate).FirstOrDefault(); var rightValue = currentItem.Value.OrderBy(x => x.WeekEndingDate).LastOrDefault(); for (var weekIndex = PreviousWeeks; weekIndex < NewPeriods; weekIndex++) { var weekEnding = FiscalCalendarWeeks[weekIndex].EndDate; if (result[currentItem.Key].Count <= weekIndex) { result[currentItem.Key].Add(new ScenarioManager.ScenarioDetailsListItem() { Id = Guid.NewGuid(), ParentId = ScenarioId, ExpenditureCategoryId = currentItem.Value[0].ExpenditureCategoryId, WeekOrdinal = weekIndex + 1, WeekEndingDate = weekEnding, LastUpdate = DateTime.UtcNow, ExpenditureCategoryName = currentItem.Value[0].ExpenditureCategoryName, CG_EFX = currentItem.Value[0].CG_EFX, UseType = currentItem.Value[0].UseType, CategoryType = currentItem.Value[0].CategoryType, UOMId = currentItem.Value[0].UOMId, GlId = currentItem.Value[0].GlId, CreditId = currentItem.Value[0].CreditId, SortOrder = currentItem.Value[0].SortOrder, SysField1 = currentItem.Value[0].SysField1, SysField2 = currentItem.Value[0].SysField2 }); } var newListItem = result[currentItem.Key][weekIndex]; var oldListItem = currentItem.Value.FirstOrDefault(x => x.WeekEndingDate == weekEnding); if (oldListItem == null) { if (leftValue != null && weekEnding < leftValue.WeekEndingDate) { newListItem.Quantity = leftValue.Quantity; newListItem.DetailCost = leftValue.DetailCost; } else if (rightValue != null && weekEnding > rightValue.WeekEndingDate) { newListItem.Quantity = rightValue.Quantity; newListItem.DetailCost = rightValue.DetailCost; } } else { newListItem.Quantity = oldListItem.Quantity; newListItem.DetailCost = oldListItem.DetailCost; } } } } return result; } #endregion } #endregion }