EnVisageOnline/Main-RMO/Source/EnVisage/Code/BLL/ScenarioManager.cs

4323 lines
250 KiB
C#

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<Scenario, ScenarioModel>
{
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<Guid, List<CalculatedExpenditureCategoryItem>> 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<CalculatedExpenditureCategoryItem>();
var result = new Dictionary<Guid, List<CalculatedExpenditureCategoryItem>>();
foreach (var expenditure2Calculation in query)
{
if (expenditure2Calculation.ParentId != parentId)
{
if (firstTime)
{
firstTime = false;
calcCategories = new List<CalculatedExpenditureCategoryItem>();
}
else
{
result.Add(parentId, calcCategories);
calcCategories = new List<CalculatedExpenditureCategoryItem>();
}
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<Scenario> DataTable
{
get
{
return DbContext.Scenarios;
}
}
/// <summary>
/// Groupped ECs in Template or Not and related to scenario teams and not
/// </summary>
/// <param name="templateId"></param>
/// <param name="teams"></param>
/// <returns></returns>
/// <remarks>SA. ENV-840</remarks>
public IList<ScenarioModel.ExpenditureItem> GetExpenditureCategories(Guid? templateId, List<Guid> teams)
{
IList<ScenarioModel.ExpenditureItem> expCats = GetExpenditureCategories(templateId);
List<Guid> teamExpCats = new List<Guid>();
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<ScenarioModel.ExpenditureItem> expCatsSorted =
expCats.OrderBy(x => x.Group).ThenBy(x => x.Name).ToList();
return expCatsSorted;
}
public IList<ScenarioModel.ExpenditureItem> GetExpenditureCategories(Guid? templateId)
{
if (templateId == null || templateId == Guid.Empty)
return new ScenarioModel.ExpenditureItem[0];
var list = new List<ScenarioModel.ExpenditureItem>();
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<AdditionalExpCatItem>();
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<AdditionalExpCatItem> GetNewCategoriesForScenario(Guid scenarioId, IList<ExpenditureModel> expenditures, List<Guid> existenceExpenditures)
{
var lstIds = new List<Guid>();
var arrCalculatedIds = new List<Guid>();
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<Guid, List<ScenarioDetailsListItem>> PrepareScenarioDetails(ScenarioInfoModel scenario, List<ExpenditureModel> expenditures, List<VW_ScenarioAndProxyDetails> actualsDetailsList, Dictionary<Guid, Dictionary<DateTime, decimal>> moRateTranslate, List<VW_ScenarioAndProxyDetails> currentScenarioDetails, List<FiscalCalendar> 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<VW_ScenarioAndProxyDetails>();
expenditures = new List<ExpenditureModel>();
}
// 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<AdditionalExpCatItem>();
var excludedExpCats = new List<Guid>();
// 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<Guid, List<ScenarioManager.ScenarioDetailsListItem>>(calculatedNewScenario.Count());
foreach (var expcat in calculatedNewScenario.Keys)
resultScenario.Add(expcat, new List<ScenarioManager.ScenarioDetailsListItem>());
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<string, ExpenditureDetailsTeam> PrepareScenarioTeams(Guid expCatId, ScenarioInfoModel scenario, List<FiscalCalendar> fiscalCalendar, Dictionary<string, ExpenditureDetailsTeam> teams, bool isUpdate, int currentPeriods)
{
var result = new Dictionary<string, ExpenditureDetailsTeam>();
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<string, decimal> RecalculateScenarioDetails(Guid expCatId, ScenarioInfoModel scenario, List<FiscalCalendar> fiscalCalendar, Dictionary<string, decimal> 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<string, decimal>();
var adjustHandler = new AdjustScenarioDetailsDistribution(DbContext)
{
CurrentPeriods = currentPeriods,
NewPeriods = scenario.Duration,
FiscalCalendarWeeks = fiscalCalendar.ToArray(),
IsUpdate = isUpdate,
CurrentActuals = new List<VW_ScenarioAndProxyDetails>(),
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;
}
/// <summary>
/// Saves scenario and scenario details.
/// </summary>
/// <param name="model"></param>
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<Rate>();
var gRates = new List<Rate>();
var lstDctEcAddOn = new List<AdditionalExpCatItem>();
var lstExpCatsToExclude = new List<Guid>(); //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<Guid>(); //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<AdditionalExpCatItem>();
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<Guid>());
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<VW_ScenarioAndProxyDetails>();
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<DateTime, decimal>();
var moRateTranslate = new Dictionary<Guid, Dictionary<DateTime, decimal>>();
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<DateTime, decimal>();
}
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<DateTime, decimal>();
}
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>(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);
}
/// <summary>
/// Copying actuals values to scenario details
/// </summary>
private void CopyActuals(Dictionary<Guid, List<ScenarioDetailsListItem>> calculatedNewScenario, List<VW_ScenarioAndProxyDetails> 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;
}
}
}
}
/// <summary>
/// Calculates costs for calculated expenditure
/// <param name="cutOffDate">User defined date to which values need to be preserved</param>
/// </summary>
private void ComputeCalculatedCategories(Dictionary<Guid, List<ScenarioDetailsListItem>> calculatedNewScenario, TemporaryCalculationResults temporaryCalcResult,
DateTime mdtActualsEndWeek, DateTime cutOffDate, int totalMilestones, int? duration, Dictionary<Guid, List<ScenarioDetailsListItem>> moCatRowTranslate, Dictionary<Guid, Dictionary<DateTime, decimal>> 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<Guid, List<ScenarioDetailsListItem>> calculatedNewScenario, TemporaryCalculationResults temporaryCalcResult, DateTime mdtActualsEndWeek, ScenarioModel model, int? duration, Dictionary<Guid, List<ScenarioDetailsListItem>> moCatRowTranslate, Dictionary<Guid, Dictionary<DateTime, decimal>> 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;
}
}
}
}
}
}
/// <summary>
/// Applies labor/materials split factor
/// <param name="cutOffDate">User defined date to which values need to be preserved</param>
/// </summary>
private void ApplyCGEFXFactor(Dictionary<Guid, List<ScenarioDetailsListItem>> 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;
}
}
}
}
}
/// <summary>
///
/// </summary>
/// <param name="calculatedNewScenario"></param>
/// <param name="mdtActualsEndWeek"></param>
/// <param name="cutOffDate">User defined date to which values need to be preserved</param>
/// <param name="moRateTranslate"></param>
/// <param name="temporaryCalcResult"></param>
/// <returns></returns>
private TemporaryCalculationResults ComputeNONCalculatedCategories(Dictionary<Guid, List<ScenarioDetailsListItem>> calculatedNewScenario, DateTime mdtActualsEndWeek, DateTime cutOffDate, Dictionary<Guid, Dictionary<DateTime, decimal>> 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<Guid, Dictionary<DateTime, decimal>> LoadRates(Guid scenarioId)
{
var result = new Dictionary<Guid, Dictionary<DateTime, decimal>>();
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<Guid, Dictionary<DateTime, decimal>> 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;
}
/// <summary>
/// Prepare a list of scenario details templates for each provided expenditure category.
/// </summary>
/// <param name="templateScenarioId">Id of the template scenario.</param>
/// <param name="plstExpCatsToAdd"></param>
/// <param name="newScenarioId"></param>
/// <param name="plstExpCatsToExclude"></param>
/// <returns></returns>
private Dictionary<Guid, List<ScenarioDetailsListItem>> GetTemplateScenarioDetailsPerExpCat(Guid templateScenarioId, IReadOnlyCollection<AdditionalExpCatItem> plstExpCatsToAdd, Guid newScenarioId,
ICollection<Guid> plstExpCatsToExclude, List<VW_ScenarioAndProxyDetails> currentScenarioDetails = null)
{
var result = new Dictionary<Guid, List<ScenarioDetailsListItem>>();
if (templateScenarioId == Guid.Empty)
return result;
var lvaRtnArray = new List<VW_ScenarioAndProxyDetails>();
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<Guid, List<ScenarioDetailsListItem>>(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<ScenarioDetailsListItem>(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<ScenarioDetailsListItem>(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<ScenarioDetailsListItem>(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;
}
/// <summary>
/// Returns a dictionary where Key = ExpenditureCategoryId, Value = List&lt;ParentId&gt;
/// </summary>
/// <returns></returns>
public Dictionary<Guid, List<Guid>> 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;
}
/// <summary>
/// Sets Bottom Up costs upon scenario creation or editing.
/// </summary>
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<CostSaving> 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);
}
/// <summary>
/// Recalculate scenario bottom-up costs for the specified scenario. DbContext submission required.
/// </summary>
/// <param name="scenario">The scenario that should be recalculated.</param>
/// <param name="isRevenueGeneratingScenario">Indicates whether scenario is revenue generating or not.</param>
/// <param name="actualScenarioDetails">A collection of <see cref="ScenarioDetail"/> records of ACTUALS scenario. If <b>null</b> then will be retrieved from database.</param>
/// <remarks>
/// Actuals BU Direct costs for non actuals scenario calculates as Actuals (for actuals scenario period) + Forecast (for remaining period).
/// </remarks>
public void SetBottomUpCosts(Scenario scenario, bool isRevenueGeneratingScenario, List<ScenarioDetailsListItem> actualScenarioDetails = null, List<ScenarioDetailsListItem> 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; }
}
/// <summary>
/// Recalculate scenario bottom-up costs for the specified scenario. DbContext submission required.
/// </summary>
/// <param name="scenario">The scenario which bottom-up costs should be recalculated.</param>
/// <param name="isRevenueGeneratingScenario">Indicates whether scenario is revenue generating or not.</param>
/// <param name="scenarioDetailItems">A list of all scenario detail records of <paramref name="scenario"/>.</param>
/// <param name="actualScenarioDetailItems">A li st of all scenario detail records of actuals scenario referenced to the same project as <paramref name="scenario"/>.</param>
[Obsolete("Remove while next round of code cleaning")]
private void UpdateScenarioBottomUpCosts(Scenario scenario, bool isRevenueGeneratingScenario, List<BottomUpCostsDataItem> scenarioDetailItems, List<BottomUpCostsDataItem> 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;
}
}
/// <summary>
/// Copy scenario
/// </summary>
/// <param name="id">The Id of Scenario to copy</param>
/// <param name="targetProjectId">Project Id</param>
/// <param name="targetStatus">Status of the copied scenario</param>
/// <returns>Id of the new scenario</returns>
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<CostSaving>();
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;
}
/// <summary>
/// Updates costs of referenced scenario details records and recalculate bottom-up costs of related scenarios.
/// </summary>
/// <param name="rate">A <see cref="Rate"/> object that has been created or updated.</param>
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;
}
}
/// <summary>
/// Set teams to the given scenario list (removes old and adds new). Doesn't save results to DB!
/// </summary>
/// <param name="scenariosToUpdate">Updatable scenario ids</param>
/// <param name="newTeamList">New teams to scenarios</param>
/// <remarks>SA. ENV-754</remarks>
public void UpdateScenarioTeams(List<Guid> scenariosToUpdate, List<Guid> newTeamList)
{
// Idea: construct struct, where every scenario has the teams, that must be added to DB table
var teamsToAdd = new Dictionary<Guid, List<Guid>>();
var recsToRemove = new List<Guid>();
var addTheEntireTeamList = new Dictionary<Guid, bool>();
// Init dictionary structure
foreach (Guid scenId in scenariosToUpdate)
{
teamsToAdd.Add(scenId, new List<Guid>());
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);
}
}
}
/// <summary>
/// Checks the ability to remove teams from scenarios. Team can't be removed, if it has alloc > 0%
/// </summary>
/// <param name="scenariosToCheck">Scenarios to check (ids)</param>
/// <param name="newTeamList">New teams to scenarios</param>
/// <returns>Teams, that can't be removed (key = team id, val = list of scenarios)</returns>
/// <remarks>SA. ENV-754</remarks>
public Dictionary<Guid, List<Guid>> GetViolatedUpdateScenarioTeams(List<Guid> scenariosToCheck, List<Guid> newTeamList)
{
// Idea: construct struct, where every scenario has the teams, that must be added to DB table
Dictionary<Guid, List<Guid>> violatedRecords = new Dictionary<Guid, List<Guid>>();
// 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<Guid>());
violatedRecords[teamRecord.TeamId].Add(teamRecord.ScenarioId);
}
return violatedRecords;
}
/// <summary>
/// Returns all non actuals scenarios for given project or project part
/// </summary>
/// <param name="projectOrPartId">Project or part id</param>
/// <returns>SA. ENV-754</returns>
public List<Guid> GetProjectNonActualsScenarios(Guid projectOrPartId)
{
List<Guid> scenarioParents = new List<Guid>() { 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<Guid> 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<ScenarioDetail>();
var teamAllocations = new List<TeamAllocation>();
var resourceAllocations = new List<PeopleResourceAllocation>();
var teams2Scenario = new Dictionary<Guid, short>();
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<CostSaving> costSavingItems = new List<CostSaving>();
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<ScenarioDetail> 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<TeamAllocation> 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;
}
/// <summary>
/// Updates team allocation on the given scenario, all other scenarios of the project and the project
/// </summary>
/// <param name="scenarioId">Current updatable scenario id</param>
/// <param name="teamallocations">Incoming team allocation</param>
public void UpdateTeamsAllocation(Scenario currentScenario, Dictionary<Guid, short> 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<Guid> incomingTeams = new List<Guid>();
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<Guid> 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<ScenarioDetail> GetScenarioDetails(List<Guid> scenarios, List<Guid> expenditureCategories)
{
if (scenarios == null || scenarios.Count <= 0)
return new List<ScenarioDetail>();
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<VW_ScenarioAndProxyDetails> GetScenarioProxyDetails(ScenarioCalendarFilterModel model,
DateTime? startDate, DateTime? endDate,
IEnumerable<Guid> 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<string, ExpenditureDetail> BuildExpendituresForScenario(List<DateTime> weekEndings, List<VW_ScenarioAndProxyDetails> forecastDetails, List<VW_ScenarioAndProxyDetails> actualsDetails)
{
var uoms = DbContext.UOMs.ToDictionary(x => x.Id, g => g.UOMValue);
if (forecastDetails == null)
forecastDetails = new List<VW_ScenarioAndProxyDetails>();
if (actualsDetails == null)
actualsDetails = new List<VW_ScenarioAndProxyDetails>();
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<TeamInScenarioModel> FillTeamsDetails(Guid scenarioId, Dictionary<string, ExpenditureDetail> expenditures, string userId, Dictionary<Guid, short> 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<Guid>() { scenarioId });
var teamsInfo = new List<TeamInfoModel>();
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<TeamResourceSortable, TeamResource>(x => (TeamResource)x).ToDictionary(x => x.Id.ToString());
teamDetails.AllResources = resourcesInTeam.OrderBy(x => x.LastName).
Select<TeamResourceSortable, TeamResource>(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<string, ExpenditureDetail> GetFullAllocationInfoByScenario(Guid scenarioId, string userId)
{
if (scenarioId == Guid.Empty)
throw new ArgumentNullException("scenarioId");
var forecastDetails = GetScenarioProxyDetails(null, null, null, new List<Guid> { 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;
}
/// <summary>
/// Returns tree-structure of scenario details: Scenarios[ScenarioId, Expenditure Categories[ExpCatId, ScenarioDetails]]
/// </summary>
/// <param name="scenarios">Scenarios Id's for which method will returns scenario details</param>
/// <param name="expenditureCategories">ECs for which method will returns scenario details</param>
/// <returns></returns>
public Dictionary<Guid, Dictionary<Guid, List<ScenarioDetail>>> GetScenarioDetailsTree(List<Guid> scenarios, List<Guid> 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<Guid, VW_ExpenditureCategory> GetCategoriesInScenarios(List<Guid> 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<Scenario> GetScenarios4Projects(List<Guid> 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<ScenarioCalendarRateModel> GetRates(Guid scenarioId)
{
var rates = (new RateManager(DbContext)).GetRates(scenarioId);
return GetRatesModel(rates);
}
public List<ScenarioCalendarRateModel> GetRatesModel(Dictionary<Guid, List<Rate>> rates)
{
if (rates == null || rates.Count <= 0)
return new List<ScenarioCalendarRateModel>();
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<Rate> GetRatesFromModel(List<ScenarioCalendarRateModel> model)
{
if (model == null || model.Count <= 0)
return new List<Rate>();
var rates = new List<Rate>();
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
/// <summary>
/// Interpolates or extrapolates scenario details distribution.
/// </summary>
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<Guid, List<ScenarioManager.ScenarioDetailsListItem>> CurrentScenarioDetails { set; private get; }
public List<VW_ScenarioAndProxyDetails> 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
/// <summary>
/// Distribution recalculation.
/// </summary>
public Dictionary<Guid, List<ScenarioManager.ScenarioDetailsListItem>> 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<Guid, List<ScenarioManager.ScenarioDetailsListItem>>(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<ScenarioManager.ScenarioDetailsListItem>(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<ScenarioManager.ScenarioDetailsListItem>());
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<ScenarioManager.ScenarioDetailsListItem>());
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<ScenarioManager.ScenarioDetailsListItem>());
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<ScenarioManager.ScenarioDetailsListItem>());
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<ScenarioManager.ScenarioDetailsListItem>());
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
}