4323 lines
250 KiB
C#
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<ParentId>
|
|
/// </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
|
|
} |