6672 lines
387 KiB
C#
6672 lines
387 KiB
C#
using EnVisage.Code.Audit;
|
|
using EnVisage.Code.Integration;
|
|
using EnVisage.Code.ThreadedProcessing;
|
|
using EnVisage.Models;
|
|
using EnVisage.Models.ProjectDependencies;
|
|
using Newtonsoft.Json;
|
|
using NLog;
|
|
using Prevu.Core.Audit.Model;
|
|
using Resources;
|
|
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Collections.ObjectModel;
|
|
using System.Data.Common;
|
|
using System.Data.Entity;
|
|
using System.Data.Entity.Infrastructure;
|
|
using System.Data.SqlClient;
|
|
using System.Linq;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using static EnVisage.Code.AuditProxy;
|
|
|
|
namespace EnVisage.Code.BLL
|
|
{
|
|
public class ScenarioManager : ManagerBase<Scenario, ScenarioModel>
|
|
{
|
|
#region Properties
|
|
|
|
public Guid[] materialsECs;
|
|
|
|
#endregion
|
|
|
|
#region Models
|
|
|
|
public class BottomUpCostsDataItem
|
|
{
|
|
public decimal Cost { get; set; }
|
|
public ExpenditureCategoryModel.CategoryTypes Type { get; set; }
|
|
public Guid ScenarioId { get; set; }
|
|
}
|
|
|
|
private class AdditionalExpCatItem
|
|
{
|
|
public Guid ExpenditureCategoryId { 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 bool AllowResourceAssignment { get; set; }
|
|
}
|
|
|
|
private 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; }
|
|
}
|
|
|
|
private 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;
|
|
}
|
|
}
|
|
|
|
public struct AllocationItem
|
|
{
|
|
public decimal CurrentScenarioQuantity { get; set; }
|
|
public decimal OtherScenariosQuantity { get; set; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Constructors and Overrides
|
|
|
|
public ScenarioManager()
|
|
: base(null)
|
|
{
|
|
}
|
|
|
|
public ScenarioManager(EnVisageEntities dbContext)
|
|
: base(dbContext)
|
|
{
|
|
}
|
|
|
|
protected override Scenario InitInstance()
|
|
{
|
|
return new Scenario
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
};
|
|
}
|
|
|
|
protected override Scenario RetrieveReadOnlyById(Guid key)
|
|
{
|
|
return DataTable.AsNoTracking().FirstOrDefault(t => t.Id == key);
|
|
}
|
|
|
|
public override DbSet<Scenario> DataTable
|
|
{
|
|
get
|
|
{
|
|
return DbContext.Scenarios;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
#region Scenario
|
|
|
|
public ScenarioDetailModel LoadScenarioDetailModel(Guid scenarioId)
|
|
{
|
|
var scenario = DataTable.Include(x => x.Project)
|
|
.Include(x => x.CostSavings1)
|
|
.FirstOrDefault(x => x.Id == scenarioId);
|
|
if (scenario == null)
|
|
return null;
|
|
|
|
var scenarioLMInfo = GetLaborMaterialsSplit(scenarioId);
|
|
var scenarioModel = LoadScenarioDetailModel(scenario, scenario.Project, scenario.CostSavings1, scenarioLMInfo);
|
|
|
|
return scenarioModel;
|
|
}
|
|
|
|
public ScenarioDetailModel LoadScenarioDetailModel(Scenario scenario, Project project, IEnumerable<CostSaving> costSavings, LaborMaterialsCostInfo scenarioLMInfo)
|
|
{
|
|
if (scenario == null)
|
|
return null;
|
|
|
|
if (project == null)
|
|
throw new ArgumentNullException(nameof(project));
|
|
|
|
ScenarioDetailModel model = null;
|
|
ProjectDependencyListModel depInfo;
|
|
using (var dependencyMngr = new ProjectDependencyManager(DbContext))
|
|
{
|
|
var actualScenario = GetActualScenario(project.Id);
|
|
var actualScenarioLInfo = actualScenario != null ? GetLaborMaterialsSplit(actualScenario.Id) : new LaborMaterialsCostInfo();
|
|
model = new ScenarioDetailModel
|
|
{
|
|
Id = scenario.Id,
|
|
Name = scenario.Name,
|
|
ProjectName = (project.ParentProjectId.HasValue ? project.ParentProject.Name : project.Name),
|
|
ProjectId = (project.ParentProjectId.HasValue ? project.ParentProjectId.Value : project.Id),
|
|
ProjectDeadline = project.Deadline,
|
|
PartName = (project.ParentProjectId.HasValue ? project.Name : string.Empty),
|
|
Color = scenario.Color,
|
|
TemplateId = scenario.TemplateId ?? Guid.Empty,
|
|
ParentId = scenario.ParentId ?? Guid.Empty,
|
|
Type = (ScenarioType)scenario.Type,
|
|
EndDate = scenario.EndDate.HasValue ? scenario.EndDate.Value.Date : (DateTime?)null,
|
|
StartDate = scenario.StartDate.HasValue ? scenario.StartDate.Value.Date : (DateTime?)null,
|
|
Status = scenario.Status == null ? (ScenarioStatus?)null : (ScenarioStatus)scenario.Status,
|
|
IsBottomUp = scenario.IsBottomUp,
|
|
|
|
GrowthScenario = scenario.GrowthScenario,
|
|
IsActiveScenario = scenario.Status == ScenarioStatus.Active.GetHashCode(),
|
|
MilestoneStartDate = scenario.ShotStartDate,
|
|
TotalMilestones = scenario.Shots ?? 0,
|
|
Duration = scenario.Duration,
|
|
RatesModel = new RatesModel()
|
|
{
|
|
ScenarioId = scenario.Id
|
|
},
|
|
FinInfo = GetScenarioFinInfoModel(scenario, actualScenario, project, costSavings, scenarioLMInfo, actualScenarioLInfo),
|
|
Expenditures = Utils.GetScenarioExpenditures(scenario.Id),
|
|
ScenarioExpenditures = GetExpenditureCategories(scenario.Id)
|
|
};
|
|
var udfMan = new UDFManager(new EnVisageEntities());
|
|
model.UserDefinedFields = udfMan.LoadCollection(model.Id, UserDefinedFieldDomain.Scenario);
|
|
|
|
if (actualScenario != null)
|
|
{
|
|
if (actualScenario.StartDate.HasValue)
|
|
model.ActualsStartDate = Utils.ConvertToUnixDate(actualScenario.StartDate.Value.Date);
|
|
if (actualScenario.EndDate.HasValue)
|
|
model.ActualsEndDate = Utils.ConvertToUnixDate(actualScenario.EndDate.Value.Date);
|
|
model.HasActuals = actualScenario.EndDate.HasValue;
|
|
}
|
|
|
|
// Load Project dependency info and fill model with date constraints
|
|
depInfo = dependencyMngr.GetDependencies(project.Id);
|
|
}
|
|
model.ProjectHasDependencies = (depInfo != null) && (depInfo.Count > 0);
|
|
|
|
if (model.ProjectHasDependencies)
|
|
{
|
|
var prevProjectsInfo = depInfo.FirstOrDefault(x => x.Type == ProjectDependencyDisplayType.StartsAfter);
|
|
var succProjectsInfo = depInfo.FirstOrDefault(x => x.Type == ProjectDependencyDisplayType.StartsBefore);
|
|
model.StartDateConstraint = (prevProjectsInfo != null) && prevProjectsInfo.dtEndDate.HasValue ? Utils.ConvertFromUnixDate(prevProjectsInfo.dtEndDate.Value) : (DateTime?)null;
|
|
model.EndDateConstraint = (succProjectsInfo != null) && succProjectsInfo.dtStartDate.HasValue ? Utils.ConvertFromUnixDate(succProjectsInfo.dtStartDate.Value) : (DateTime?)null;
|
|
}
|
|
|
|
model.TrimStringProperties();
|
|
return model;
|
|
}
|
|
|
|
public ScenarioFinInfoModel GetScenarioFinInfoModel(Scenario scenario, Scenario actualScenario, Project project, IEnumerable<CostSaving> costSavings, LaborMaterialsCostInfo scenarioLMInfo, LaborMaterialsCostInfo actualLMInfo)
|
|
{
|
|
if (scenario == null)
|
|
return null;
|
|
|
|
var model = new ScenarioFinInfoModel()
|
|
{
|
|
IsRevenueGenerating = (project == null || project.IsRevenueGenerating),
|
|
UseLMMargin = scenario.UseLMMargin == 1,
|
|
GrossMargin = scenario.ExpectedGrossMargin.HasValue ? Convert.ToInt32(Math.Round(scenario.ExpectedGrossMargin.Value * 100, 0)) : (int?)null,
|
|
LMMargin = scenario.ExpectedGrossMargin_LM.HasValue ? Convert.ToInt32(Math.Round(scenario.ExpectedGrossMargin_LM.Value * 100, 0)) : (int?)null,
|
|
CalculatedGrossMargin = Convert.ToInt32(Math.Round((scenario.CalculatedGrossMargin ?? 0) * 100, 0)),
|
|
CalculatedGrossMarginLM = Convert.ToInt32(Math.Round((scenario.CalculatedGrossMargin_LM ?? 0) * 100, 0)),
|
|
|
|
CalculatedGrossMarginActuals =
|
|
(scenario.Actuals_BUDirectCosts.HasValue && scenario.ProjectedRevenue.HasValue && (scenario.ProjectedRevenue.Value != 0)) ?
|
|
Convert.ToInt32(Math.Round(((scenario.ProjectedRevenue.Value - scenario.Actuals_BUDirectCosts.Value) /
|
|
scenario.ProjectedRevenue.Value * 100), 0)) : 0,
|
|
CalculatedGrossMarginLMActuals =
|
|
(scenario.Actuals_BUDirectCosts_LM.HasValue && scenario.ProjectedRevenue.HasValue && (scenario.ProjectedRevenue.Value != 0)) ?
|
|
Convert.ToInt32(Math.Round(((scenario.ProjectedRevenue.Value - scenario.Actuals_BUDirectCosts_LM.Value) /
|
|
scenario.ProjectedRevenue.Value * 100), 0)) : 0,
|
|
|
|
TDDirectCosts = Math.Round(scenario.TDDirectCosts ?? 0),
|
|
BUDirectCosts = Math.Round(scenario.BUDirectCosts ?? 0),
|
|
EFXSplit = scenario.EFXSplit ?? 0,
|
|
LaborSplitPercentage = Convert.ToInt16((scenario.CGSplit ?? 0) * 100),
|
|
CostSaving = new CostSavingModel()
|
|
{
|
|
ScenarioId = scenario.Id,
|
|
ScenarioStartDate = scenario.StartDate,
|
|
ScenarioEndDate = scenario.EndDate,
|
|
}
|
|
};
|
|
|
|
model.ProjectedRevenue = model.IsRevenueGenerating && scenario.ProjectedRevenue.HasValue
|
|
? scenario.ProjectedRevenue.Value
|
|
: 0;
|
|
|
|
if (scenario.CostSavingsStartDate.HasValue && scenario.CostSavingsEndDate.HasValue)
|
|
{
|
|
model.CostSaving.CostSavings = scenario.CostSavings;
|
|
model.CostSaving.CostSavingStartDate = scenario.CostSavingsStartDate;
|
|
model.CostSaving.CostSavingEndDate = scenario.CostSavingsEndDate;
|
|
model.CostSaving.CostSavingType = scenario.CostSavingsType == 1;
|
|
model.CostSaving.CostSavingDescription = scenario.CostSavingsDescription;
|
|
model.CostSaving.CostSavingsPanelExpanded = true;
|
|
model.CostSaving.CostSavingItems = JsonConvert.SerializeObject(GetScenarioCostSavingModelItems(costSavings));
|
|
model.CostSaving.ROIDate = scenario.ROIDate;
|
|
|
|
if (scenario.CostSavings.HasValue && model.IsRevenueGenerating)
|
|
{
|
|
model.RevenueAfterCost = model.ProjectedRevenue ?? 0;
|
|
model.ActualRevenueAfterCost = model.ProjectedRevenue;
|
|
|
|
if (scenario.BUDirectCosts.HasValue && scenario.CostSavings.Value < scenario.BUDirectCosts.Value)
|
|
model.RevenueAfterCost += (scenario.CostSavings.Value - scenario.BUDirectCosts.Value);
|
|
|
|
if (scenario.Actuals_BUDirectCosts.HasValue && scenario.CostSavings.Value < scenario.Actuals_BUDirectCosts.Value)
|
|
model.ActualRevenueAfterCost += (scenario.CostSavings.Value - scenario.Actuals_BUDirectCosts.Value);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (model.IsRevenueGenerating)
|
|
{
|
|
model.RevenueAfterCost = model.ProjectedRevenue ?? 0;
|
|
model.ActualRevenueAfterCost = model.ProjectedRevenue;
|
|
|
|
if (scenario.BUDirectCosts.HasValue)
|
|
model.RevenueAfterCost -= scenario.BUDirectCosts.Value;
|
|
|
|
if (scenario.Actuals_BUDirectCosts.HasValue)
|
|
model.ActualRevenueAfterCost -= scenario.Actuals_BUDirectCosts.Value;
|
|
}
|
|
}
|
|
|
|
if (scenarioLMInfo != null)
|
|
model.LaborMaterialsSplit = scenarioLMInfo.LaborAndMaterialSplit;
|
|
|
|
#region Override Actuals fields if there are no actuals data
|
|
|
|
// Show Forecast value as Actual Bottom-Up Direct Costs, if Project has no actuals.
|
|
bool actualDataExists = (actualScenario != null && actualScenario.EndDate.HasValue);
|
|
|
|
model.DateForStartOfChanges = scenario.StartDate > DateTime.Today ? scenario.StartDate.Value.Date : DateTime.Today;
|
|
if (!actualDataExists)
|
|
{
|
|
// Show nulls and forecast values as actuals, if actuals not found
|
|
model.CalculatedGrossMarginActuals = null;
|
|
model.CalculatedGrossMarginLMActuals = null;
|
|
model.ActualRevenueAfterCost = null;
|
|
model.ActualLabor = null;
|
|
model.ActualLaborMaterialsSplit = null;
|
|
model.ActualMaterials = null;
|
|
}
|
|
else
|
|
{
|
|
if (actualLMInfo != null)
|
|
{
|
|
model.ActualLabor = actualLMInfo.Labor;
|
|
model.ActualMaterials = actualLMInfo.Materials;
|
|
model.ActualsTotal = actualLMInfo.Total;
|
|
model.ActualLaborMaterialsSplit = actualLMInfo.LaborAndMaterialSplit;
|
|
}
|
|
|
|
// date for start of changes should be bigger than actuals end date
|
|
if (actualScenario.EndDate >= model.DateForStartOfChanges)
|
|
model.DateForStartOfChanges = actualScenario.EndDate.Value.AddDays(1);
|
|
}
|
|
|
|
#endregion
|
|
|
|
return model;
|
|
}
|
|
|
|
public ScenarioFinInfoModel GetScenarioFinInfoModel(ScenarioCalendarMixModel model)
|
|
{
|
|
if (model == null)
|
|
return null;
|
|
|
|
var scenario = GetScenario(model);
|
|
Project project;
|
|
using (var projectManager = new ProjectManager(DbContext))
|
|
{
|
|
project = projectManager.Load(model.ParentId);
|
|
}
|
|
var actualScenario = model.ParentId.HasValue ? GetActualScenario(model.ParentId.Value) : null;
|
|
var forecast = model.GetScenarioDetails(true);
|
|
var costSavingItems = model.FinInfo != null ? GetCostSavings(model.FinInfo.CostSaving) : new List<CostSaving>();
|
|
var scenarioLMInfo = new LaborMaterialsCostInfo()
|
|
{
|
|
Labor = forecast == null ? 0 : forecast.Where(x => x.Type == (int)ExpenditureCategoryModel.CategoryTypes.Labor).Sum(x => x.Cost ?? 0),
|
|
Materials = forecast == null ? 0 : forecast.Where(x => x.Type == (int)ExpenditureCategoryModel.CategoryTypes.Materials).Sum(x => x.Cost ?? 0),
|
|
Total = forecast == null ? 0 : forecast.Sum(x => x.Cost ?? 0)
|
|
};
|
|
var actualLMInfo = actualScenario != null ? GetLaborMaterialsSplit(actualScenario.Id) : new LaborMaterialsCostInfo();
|
|
var finInfo = GetScenarioFinInfoModel(scenario, actualScenario, project, costSavingItems, scenarioLMInfo, actualLMInfo);
|
|
|
|
return finInfo;
|
|
}
|
|
|
|
public bool Delete(ScenarioModel model, string transactionId, string userId)
|
|
{
|
|
var dbObj = Load(model.Id, false);
|
|
if (dbObj == null)
|
|
return false;
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = model.Id,
|
|
ClassificationKey = "ScenarioDeleted",
|
|
UserId = userId,
|
|
OldValue = dbObj.Name,
|
|
ScenarioId = dbObj.Id,
|
|
ProjectId = dbObj.ParentId
|
|
});
|
|
var noteMan = new NoteManager(DbContext);
|
|
noteMan.RemoveNotesForDomain(model.Id);
|
|
(DbContext as IObjectContextAdapter).ObjectContext.ExecuteStoreCommand($"exec sp_DeleteScenario '{dbObj.Id}'");
|
|
return true;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Expenditure Categories
|
|
|
|
/// <summary>
|
|
/// Grouped ECs in Template or Not and related to scenario teams and not
|
|
/// </summary>
|
|
/// <param name="templateId"></param>
|
|
/// <param name="teams"></param>
|
|
/// <returns></returns>
|
|
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()
|
|
join pr2t in DbContext.PeopleResource2Team.AsNoTracking() on pr.Id equals pr2t.PeopleResourceId
|
|
where teams.Contains(pr2t.TeamId)
|
|
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)
|
|
.Select(t => new ScenarioModel.ExpenditureItem
|
|
{
|
|
Id = t.Id,
|
|
Group = Constants.EXPENDITURE_INTEMPLATE_TITLE,
|
|
Name = t.ExpCategoryWithCcName,
|
|
Checked = true
|
|
}).ToArray());
|
|
|
|
var arrCalculatedIds = categories.Where(t => t.UseType == (int)ExpenditureCategoryModel.UseTypes.Calculated).Select(t => t.Id).ToArray();
|
|
if (arrCalculatedIds.Length > 0)
|
|
{
|
|
foreach (var expCat in DbContext.VW_Expenditure2Calculation.Where(t => arrCalculatedIds.Contains(t.ParentId)).Select(p => p.ExpenditureCategoryID).Distinct())
|
|
{
|
|
if (!lstIds.Contains(expCat))
|
|
lstIds.Add(expCat);
|
|
}
|
|
}
|
|
// 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)
|
|
.Select(t => new
|
|
{
|
|
t.Id,
|
|
t.ExpCategoryWithCcName,
|
|
t.Type
|
|
}).ToArray()
|
|
.Select(t => new ScenarioModel.ExpenditureItem
|
|
{
|
|
Id = t.Id,
|
|
Group = ((ExpenditureCategoryModel.CategoryTypes)(t.Type ?? 0)).ToDisplayValue(),
|
|
Name = t.ExpCategoryWithCcName,
|
|
Checked = false
|
|
}));
|
|
|
|
return list;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Rates
|
|
|
|
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;
|
|
}
|
|
// ToDo: consider to merge RateManager.GetRateModel with this method because seems like they do almost the same.
|
|
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;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Log
|
|
|
|
public void LogScenarioStatusChange(Scenario scenario, ScenarioStatus newStatus, string userId, string transactionId = null)
|
|
{
|
|
if (newStatus != (ScenarioStatus)scenario.Status)
|
|
{
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = scenario.Id,
|
|
ClassificationKey = newStatus == ScenarioStatus.Active ? "ScenarioActivate" : "ScenarioDeactivate",
|
|
UserId = userId,
|
|
OldValue = newStatus == ScenarioStatus.Active ? string.Empty : scenario.Name,
|
|
NewValue = newStatus == ScenarioStatus.Active ? scenario.Name : string.Empty,
|
|
ScenarioId = scenario.Id,
|
|
ProjectId = scenario.ParentId
|
|
});
|
|
}
|
|
}
|
|
|
|
public void LogScenarioRecalculateEvent(string userId, ScenarioInfoModel model, string transactionId = null)
|
|
{
|
|
transactionId = Utils.GetOrCreateTransactionId(DbContext, transactionId);
|
|
if (string.IsNullOrEmpty(userId))
|
|
userId = SecurityManager.GetUserPrincipal().ToString();
|
|
var scenario = Load(model.Id);
|
|
if (model.AdjustMarginRased.HasValue && model.AdjustMarginRased.Value == true)
|
|
{
|
|
|
|
var oldModel = Load(model.Id);
|
|
if (oldModel != null)
|
|
{
|
|
Decimal oldValue = 0;
|
|
Decimal newValue = 0;
|
|
if (oldModel.Project.IsRevenueGenerating)
|
|
{
|
|
oldValue = oldModel.ProjectedRevenue ?? 0;
|
|
newValue = model.ProjectedRevenue ?? 0;
|
|
}
|
|
else
|
|
{
|
|
oldValue = oldModel.TDDirectCosts ?? 0;
|
|
newValue = model.TDDirectCosts ?? 0;
|
|
}
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = scenario.Id,
|
|
ScenarioId = scenario.Id,
|
|
ProjectId = scenario.Project.Id,
|
|
ClassificationKey = "ScenarioAdjustToDesiredRevenueOrExpensesChange",
|
|
UserId = userId,
|
|
NewValue = newValue.ToString("C0"), //model.AdjustMarginRased.Value ? "Yes" : "No"
|
|
OldValue = oldValue.ToString("C0")
|
|
});
|
|
}
|
|
}
|
|
|
|
if (model.DateForStartOfChanges != model.DateForStartOfChangesOld)
|
|
{
|
|
var oldValue = Utils.ConvertFromUnixDate(model.DateForStartOfChangesOld ?? 0).Date;
|
|
var newValue = Utils.ConvertFromUnixDate(model.DateForStartOfChanges ?? 0).Date;
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = scenario.Id,
|
|
ScenarioId = scenario.Id,
|
|
ProjectId = scenario.Project.Id,
|
|
ClassificationKey = "ScenarioStartDateChangeForFinancials",
|
|
UserId = userId,
|
|
NewValue = newValue.ToShortDateString(),
|
|
OldValue = oldValue.ToShortDateString()
|
|
});
|
|
}
|
|
}
|
|
|
|
public void LogScenarioEvents(string userId, Scenario newScenario, ScenarioDetailsSnapshotSaveModel srcModel, string transactionId = null)
|
|
{
|
|
transactionId = Utils.GetOrCreateTransactionId(DbContext, transactionId);
|
|
if (string.IsNullOrEmpty(userId))
|
|
userId = SecurityManager.GetUserPrincipal().ToString();
|
|
|
|
var oldScenario = Load(newScenario.Id);
|
|
|
|
if ((srcModel != null) && (srcModel.Scenario != null))
|
|
{
|
|
if (!srcModel.Scenario.SaveAsDraft)
|
|
{
|
|
// Log scenario saving mode
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = newScenario.Id,
|
|
ClassificationKey = (srcModel.Scenario.SaveAs == SaveAsScenario.New) ? "ScenarioDetailsSaveAsNewScenario" : "ScenarioDetailsSaveAsCurrentScenario",
|
|
ProjectId = newScenario.ParentId,
|
|
ScenarioId = srcModel.Scenario.Id,
|
|
UserId = userId,
|
|
NewValue = (srcModel.Scenario.SaveAs == SaveAsScenario.New) ? newScenario.Name : null,
|
|
OldValue = (srcModel.Scenario.SaveAs == SaveAsScenario.Update) && (oldScenario != null) ? oldScenario.Name : null
|
|
});
|
|
}
|
|
}
|
|
|
|
if (oldScenario != null)
|
|
{
|
|
if (newScenario.Name != oldScenario.Name)
|
|
{
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = newScenario.Id,
|
|
ScenarioId = newScenario.Id,
|
|
ProjectId = newScenario.Project.Id,
|
|
ClassificationKey = "ScenarioNameChange",
|
|
UserId = userId,
|
|
NewValue = newScenario.Name,
|
|
OldValue = oldScenario.Name,
|
|
});
|
|
}
|
|
|
|
if (newScenario.Status != oldScenario.Status)
|
|
{
|
|
var oldValue = ((ScenarioStatus)oldScenario.Status).ToDisplayValue();
|
|
var newValue = ((ScenarioStatus)newScenario.Status).ToDisplayValue();
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = newScenario.Id,
|
|
ScenarioId = newScenario.Id,
|
|
ProjectId = newScenario.Project.Id,
|
|
ClassificationKey = "ScenarioActiveInactive",
|
|
UserId = userId,
|
|
NewValue = newValue,
|
|
OldValue = oldValue
|
|
});
|
|
}
|
|
|
|
if (newScenario.StartDate != oldScenario.StartDate)
|
|
{
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = newScenario.Id,
|
|
ScenarioId = newScenario.Id,
|
|
ProjectId = newScenario.Project.Id,
|
|
ClassificationKey = "ScenarioStartDateChange",
|
|
UserId = userId,
|
|
NewValue = newScenario.StartDate?.ToShortDateString(),
|
|
OldValue = oldScenario.StartDate?.ToShortDateString()
|
|
});
|
|
}
|
|
|
|
if (newScenario.EndDate != oldScenario.EndDate)
|
|
{
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = newScenario.Id,
|
|
ScenarioId = newScenario.Id,
|
|
ProjectId = newScenario.Project.Id,
|
|
ClassificationKey = "ScenarioEndDateChange",
|
|
UserId = userId,
|
|
NewValue = newScenario.EndDate?.ToShortDateString(),
|
|
OldValue = oldScenario.EndDate?.ToShortDateString()
|
|
});
|
|
}
|
|
|
|
if (newScenario.TemplateId != oldScenario.TemplateId)
|
|
{
|
|
var oldTemplate = Load(oldScenario.TemplateId);
|
|
var newTemplate = Load(newScenario.TemplateId);
|
|
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = newScenario.Id,
|
|
ScenarioId = newScenario.Id,
|
|
ProjectId = newScenario.Project.Id,
|
|
ClassificationKey = "ScenarioTemplateChange",
|
|
UserId = userId,
|
|
NewValue = newTemplate?.Name,
|
|
OldValue = oldTemplate?.Name
|
|
});
|
|
}
|
|
|
|
if (newScenario.Type != oldScenario.Type)
|
|
{
|
|
var oldType = ((ScenarioType)oldScenario.Type).ToDisplayValue();
|
|
var newType = ((ScenarioType)newScenario.Type).ToDisplayValue();
|
|
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = newScenario.Id,
|
|
ScenarioId = newScenario.Id,
|
|
ProjectId = newScenario.Project.Id,
|
|
ClassificationKey = "ScenarioTypeChange",
|
|
UserId = userId,
|
|
NewValue = newType,
|
|
OldValue = oldType
|
|
});
|
|
}
|
|
|
|
if (newScenario.TDDirectCosts != oldScenario.TDDirectCosts)
|
|
{
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = newScenario.Id,
|
|
ScenarioId = newScenario.Id,
|
|
ProjectId = newScenario.Project.Id,
|
|
ClassificationKey = "ScenarioTopDownExpenseChange",
|
|
UserId = userId,
|
|
NewValue = (newScenario.TDDirectCosts ?? 0).ToString("C0"),
|
|
OldValue = (oldScenario.TDDirectCosts ?? 0).ToString("C0")
|
|
});
|
|
}
|
|
|
|
if ((newScenario.CostSavings ?? 0) != (oldScenario.CostSavings ?? 0))
|
|
{
|
|
if (!oldScenario.CostSavings.HasValue || oldScenario.CostSavings.Value == 0)
|
|
{
|
|
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = newScenario.Id,
|
|
ScenarioId = newScenario.Id,
|
|
ProjectId = newScenario.Project.Id,
|
|
ClassificationKey = "ScenarioCostSavingsAdd",
|
|
UserId = userId,
|
|
NewValue = (newScenario.CostSavings ?? 0).ToString("C0"),
|
|
|
|
});
|
|
}
|
|
else if (!newScenario.CostSavings.HasValue || newScenario.CostSavings.Value == 0)
|
|
{
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = newScenario.Id,
|
|
ScenarioId = newScenario.Id,
|
|
ProjectId = newScenario.Project.Id,
|
|
ClassificationKey = "ScenarioCostSavingsDelete",
|
|
UserId = userId,
|
|
OldValue = (oldScenario.CostSavings ?? 0).ToString("C0"),
|
|
|
|
});
|
|
}
|
|
else
|
|
{
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = newScenario.Id,
|
|
ScenarioId = newScenario.Id,
|
|
ProjectId = newScenario.Project.Id,
|
|
ClassificationKey = "ScenarioCostSavingsEdit",
|
|
UserId = userId,
|
|
OldValue = (oldScenario.CostSavings ?? 0).ToString("C0"),
|
|
NewValue = (newScenario.CostSavings ?? 0).ToString("C0"),
|
|
});
|
|
}
|
|
}
|
|
|
|
|
|
if ((!oldScenario.CostSavingsStartDate.HasValue && newScenario.CostSavingsStartDate.HasValue) ||
|
|
(!oldScenario.CostSavingsEndDate.HasValue && newScenario.CostSavingsEndDate.HasValue))
|
|
{
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = newScenario.Id,
|
|
ScenarioId = newScenario.Id,
|
|
ProjectId = newScenario.Project.Id,
|
|
ClassificationKey = "ScenarioCostSavingsStartEndDateAdd",
|
|
UserId = userId,
|
|
NewValue = newScenario.CostSavingsStartDate?.ToShortDateString() + " - " + newScenario.CostSavingsEndDate?.ToShortDateString()
|
|
});
|
|
}
|
|
|
|
if (oldScenario.CostSavingsStartDate.HasValue && newScenario.CostSavingsStartDate.HasValue &&
|
|
oldScenario.CostSavingsStartDate.Value != newScenario.CostSavingsStartDate.Value)
|
|
{
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = newScenario.Id,
|
|
ScenarioId = newScenario.Id,
|
|
ProjectId = newScenario.Project.Id,
|
|
ClassificationKey = "ScenarioCostSavingsStartDateChange",
|
|
UserId = userId,
|
|
OldValue = oldScenario.CostSavingsStartDate?.ToShortDateString(),
|
|
NewValue = newScenario.CostSavingsStartDate?.ToShortDateString()
|
|
});
|
|
}
|
|
|
|
if (oldScenario.CostSavingsEndDate.HasValue && newScenario.CostSavingsEndDate.HasValue &&
|
|
oldScenario.CostSavingsEndDate.Value != newScenario.CostSavingsEndDate.Value)
|
|
{
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = newScenario.Id,
|
|
ScenarioId = newScenario.Id,
|
|
ProjectId = newScenario.Project.Id,
|
|
ClassificationKey = "ScenarioCostSavingsEndDateChange",
|
|
UserId = userId,
|
|
OldValue = oldScenario.CostSavingsEndDate?.ToShortDateString(),
|
|
NewValue = newScenario.CostSavingsEndDate?.ToShortDateString()
|
|
});
|
|
}
|
|
|
|
if (newScenario.CostSavingsType != oldScenario.CostSavingsType)
|
|
{
|
|
var oldType = oldScenario.CostSavingsType == 1 ? "Hard" : "Soft";
|
|
var newType = newScenario.CostSavingsType == 1 ? "Hard" : "Soft";
|
|
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = newScenario.Id,
|
|
ScenarioId = newScenario.Id,
|
|
ProjectId = newScenario.Project.Id,
|
|
ClassificationKey = "ScenarioCostSavingsTypeChange",
|
|
UserId = userId,
|
|
NewValue = newType,
|
|
OldValue = oldType
|
|
});
|
|
}
|
|
|
|
if (newScenario.CostSavingsDescription != oldScenario.CostSavingsDescription)
|
|
{
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = newScenario.Id,
|
|
ScenarioId = newScenario.Id,
|
|
ProjectId = newScenario.Project.Id,
|
|
ClassificationKey = "ScenarioCostSavingsDescriptionChange",
|
|
UserId = userId,
|
|
NewValue = newScenario.CostSavingsDescription,
|
|
OldValue = oldScenario.CostSavingsDescription
|
|
});
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
private Dictionary<string, decimal> RecalculateScenarioDetails(Guid expCatId, ScenarioInfoModel scenario, List<FiscalCalendar> fiscalCalendar,
|
|
Dictionary<string, decimal> scenarioDetails, bool isUpdate, int currentPeriods, bool keepForecastValues = false)
|
|
{
|
|
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 forecastScenarioDetails = scenarioDetails.Select(x => new ScenarioDetailsListItem()
|
|
{
|
|
ExpenditureCategoryId = expCatId,
|
|
WeekEndingDate = Utils.ConvertFromUnixDate(long.Parse(x.Key)),
|
|
Quantity = x.Value
|
|
}).ToList();
|
|
var adjustHandler = new AdjustScenarioDetailsDistribution(DbContext)
|
|
{
|
|
CurrentPeriods = currentPeriods,
|
|
NewPeriods = scenario.Duration,
|
|
FiscalCalendarWeeks = fiscalCalendar.ToArray(),
|
|
IsUpdate = isUpdate,
|
|
CurrentScenarioDetails = forecastScenarioDetails.GroupBy(x => x.ExpenditureCategoryId).ToDictionary(x => x.Key, g => g.ToList()),
|
|
CurrentActuals = new List<VW_ScenarioAndProxyDetails>(),
|
|
KeepForecastValues = keepForecastValues,
|
|
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
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
private void DeactivateScenarios(Guid parentId, int type, string transactionId, 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();
|
|
LogScenarioEvents(null, scenario, null, transactionId);
|
|
}
|
|
}
|
|
|
|
/// <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];
|
|
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;
|
|
}
|
|
|
|
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 expCatId in DbContext.VW_Expenditure2Calculation.Where(t => arrCalculatedIds.Contains(t.ParentId)).Select(p => p.ExpenditureCategoryID).Distinct())
|
|
{
|
|
if (!lstIds.Contains(expCatId))
|
|
lstIds.Add(expCatId);
|
|
}
|
|
}
|
|
|
|
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)
|
|
.Select(t => new AdditionalExpCatItem
|
|
{
|
|
ExpenditureCategoryId = t.Id,
|
|
CategoryName = t.ExpCategoryWithCcName,
|
|
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,
|
|
Quantity = 0,
|
|
SortOrder = t.SortOrder,
|
|
SysField1 = t.SystemAttributeOne,
|
|
SysField2 = Guid.Empty,
|
|
UOMId = t.UOMId,
|
|
UseType = (ExpenditureCategoryModel.UseTypes)(t.UseType ?? 1),
|
|
AllowResourceAssignment = t.AllowResourceAssignment
|
|
}).ToList();
|
|
}
|
|
|
|
#endregion
|
|
|
|
|
|
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)
|
|
return new Dictionary<Guid, List<ScenarioDetailsListItem>>();
|
|
|
|
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<ScenarioDetailsListItem>>(calculatedNewScenario.Count());
|
|
foreach (var expcat in calculatedNewScenario.Keys)
|
|
resultScenario.Add(expcat, new List<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,
|
|
ExpenditureCatagoryAllowsResource = recalculatedDetail.ExpenditureCatagoryAllowsResource
|
|
};
|
|
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,
|
|
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,
|
|
ExpenditureCatagoryAllowsResource = currentDbItem.AllowResourceAssignment
|
|
|
|
};
|
|
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, true);
|
|
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;
|
|
clonedTeam.Changed = true;
|
|
|
|
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, true);
|
|
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;
|
|
resource.Value.Changed = true;
|
|
}
|
|
}
|
|
|
|
result.Add(team.Key, clonedTeam);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
/// <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,
|
|
ExpenditureCatagoryAllowsResource = item.AllowResourceAssignment,
|
|
WeekEndingDate = null,
|
|
Quantity = item.Quantity,
|
|
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,
|
|
ExpenditureCatagoryAllowsResource = item.AllowResourceAssignment,
|
|
WeekEndingDate = item.WeekEndingDate,
|
|
Quantity = item.Quantity ?? 0,
|
|
WeekOrdinal = item.WeekOrdinal ?? 0,
|
|
DetailCost = item.Cost ?? 0,
|
|
ExpenditureCategoryName = item.ExpCategoryWithCcName,
|
|
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,
|
|
ExpenditureCatagoryAllowsResource = item.AllowResourceAssignment,
|
|
WeekEndingDate = null,
|
|
Quantity = item.Quantity,
|
|
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, item => item.Value.Select(t => t.ParentId).ToList());
|
|
return items;
|
|
}
|
|
|
|
public void SetBottomUpCosts(Guid id)
|
|
{
|
|
if (id == Guid.Empty)
|
|
throw new ArgumentException(Messages.Scenario_UnableToRecalculate_BottomUpCosts_IdIsGuidEmpty);
|
|
|
|
var scenario = DataTable.FirstOrDefault(s => s.Id == id);
|
|
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, Scenario actualScenario = null)
|
|
{
|
|
if (scenario == null)
|
|
throw new ArgumentNullException("scenario");
|
|
|
|
if (scenario.Id == Guid.Empty)
|
|
throw new ArgumentException(Messages.Scenario_UnableToRecalculate_BottomUpCosts_ScenarioDoesNotHaveIdSpecified);
|
|
|
|
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);
|
|
scenario.BUDirectCosts_LM = totalBtUpCostsLM;
|
|
scenario.BURevenueShot_LM = scenario.BUDirectCosts_LM / (scenario.Shots <= 0 ? (decimal?)null : scenario.Shots);
|
|
|
|
// Calculate Expected Revenue if only Margin was set
|
|
FillEmptyMarginOrRevenue(scenario, isRevenueGeneratingScenario);
|
|
|
|
if (scenario.ProjectedRevenue.HasValue && scenario.ProjectedRevenue.Value > 0)
|
|
{
|
|
scenario.CalculatedGrossMargin = isRevenueGeneratingScenario ? (scenario.ProjectedRevenue - scenario.BUDirectCosts) / scenario.ProjectedRevenue : 0;
|
|
}
|
|
else
|
|
{
|
|
scenario.CalculatedGrossMargin = 0;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
if (actualScenario == null)
|
|
{
|
|
actualScenario = GetActualScenario(scenario.ParentId.Value);
|
|
}
|
|
if (!scenario.GrowthScenario && actualScenario != null)
|
|
{
|
|
var actualsId = actualScenario.Id;
|
|
var actualsStartDate = actualScenario.StartDate;
|
|
var actualsEndDate = actualScenario.EndDate;
|
|
|
|
//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;
|
|
}
|
|
}
|
|
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;
|
|
|
|
//recalculate buttom up costs of all portfolio, efc, cg scenarios under the same show as the actuals scenario.
|
|
var scenarios = DataTable.Where(s =>
|
|
s.ParentId == scenario.ParentId &&
|
|
(s.Type == (int)ScenarioType.Portfolio || s.Type == (int)ScenarioType.Scheduling) &&
|
|
s.Status != (int)ScenarioStatus.Draft &&
|
|
!s.GrowthScenario).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);
|
|
}
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Both Expected Margin and Projected Revenue should be not empty. If one of them is empty then system should recalculate it based on another one.
|
|
/// </summary>
|
|
/// <param name="scenario">An instance of the <see cref="Scenario"/> class with prefilled UseLMMargin, ExpectedGrossMargin_LM, ExpectedGrossMargin,
|
|
/// BUDirectCosts, BUDirectCosts_LM and ProjectedRevenue fields. This object will be changed after calculation of empty field.</param>
|
|
/// <param name="isRevenueGeneratingScenario">A value indicating whether related project is revenue generating or expense only.</param>
|
|
public static void FillEmptyMarginOrRevenue(Scenario scenario, bool isRevenueGeneratingScenario)
|
|
{
|
|
var useLMMargin = (scenario.UseLMMargin ?? 0) == 1;
|
|
var margin = (useLMMargin ? scenario.ExpectedGrossMargin_LM : scenario.ExpectedGrossMargin) ?? 0;
|
|
var buCosts = (useLMMargin ? scenario.BUDirectCosts_LM : scenario.BUDirectCosts) ?? 0;
|
|
// Calculate Expected Revenue if only Margin was set
|
|
if (isRevenueGeneratingScenario && (scenario.ProjectedRevenue ?? 0) == 0 && margin > 0)
|
|
{
|
|
scenario.ProjectedRevenue = margin == 1 ? 0 : (margin * buCosts / (1 - margin)) + buCosts;
|
|
}
|
|
else if (isRevenueGeneratingScenario && (scenario.ProjectedRevenue ?? 0) > 0 && margin == 0)
|
|
{
|
|
if (useLMMargin)
|
|
scenario.ExpectedGrossMargin_LM = 1 - buCosts / scenario.ProjectedRevenue;
|
|
else
|
|
scenario.ExpectedGrossMargin = 1 - buCosts / scenario.ProjectedRevenue;
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Both Expected Margin and Projected Revenue should be not empty. If one of them is empty then system should recalculate it based on another one.
|
|
/// </summary>
|
|
/// <param name="scenario">An instance of the <see cref="ScenarioInfoModel"/> class with prefilled UseLMMargin, ExpectedGrossMargin_LM, ExpectedGrossMargin,
|
|
/// and ProjectedRevenue fields.</param>
|
|
/// <param name="isRevenueGeneratingScenario">A value indicating whether related project is revenue generating or expense only.</param>
|
|
public static void FeelEmptyMarginOrRevenue(ScenarioInfoModel scenario, bool isRevenueGeneratingScenario, decimal totalBtUpCosts, decimal totalBtUpCostsLM)
|
|
{
|
|
var useLMMargin = scenario.UseLMMargin;
|
|
var margin = ((useLMMargin ? scenario.LMMargin : scenario.GrossMargin) ?? 0) / 100.0M;
|
|
var buCosts = (useLMMargin ? totalBtUpCostsLM : totalBtUpCosts);
|
|
// Calculate Expected Revenue if only Margin was set
|
|
if (isRevenueGeneratingScenario && (scenario.ProjectedRevenue ?? 0) == 0 && margin > 0)
|
|
{
|
|
// margin equals to 100% leads to infinite projected revenue
|
|
scenario.ProjectedRevenue = margin == 1 ? 0 : Math.Round((margin * buCosts / (1 - margin)) + buCosts, 2);
|
|
}
|
|
else if (isRevenueGeneratingScenario && (scenario.ProjectedRevenue ?? 0) > 0 && margin == 0)
|
|
{
|
|
if (useLMMargin)
|
|
scenario.LMMargin = 1 - buCosts / scenario.ProjectedRevenue;
|
|
else
|
|
scenario.GrossMargin = 1 - buCosts / scenario.ProjectedRevenue;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/// <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(Messages.Scenario_UnableToRecalculate_BottomUpCosts_ScenarioDoesNotHaveIdSpecified);
|
|
|
|
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 Scenario CopyTo(Guid id, Guid? targetProjectId, ScenarioStatus targetStatus, bool includeCostSavings, bool includePeopleResources = false, string scenarioName = "", ScenarioSaveType saveType = ScenarioSaveType.KeepCurrent, Scenario scenario = null)
|
|
{
|
|
#if DEBUG
|
|
var watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
ProjectManager prMngr = new ProjectManager(DbContext);
|
|
Project targetProject = null;
|
|
|
|
if (scenario == null)
|
|
scenario = DbContext.Scenarios.AsNoTracking().FirstOrDefault(x => x.Id == id);
|
|
|
|
if (scenario != null)
|
|
{
|
|
targetProjectId = targetProjectId ?? scenario.ParentId;
|
|
bool copyingToOtherProject = scenario.ParentId != targetProjectId;
|
|
|
|
if (copyingToOtherProject && !scenario.ParentId.HasValue)
|
|
throw new BLLException("Source scenario has no parent project");
|
|
|
|
if (copyingToOtherProject)
|
|
{
|
|
targetProject = DbContext.Projects.FirstOrDefault(x => x.Id == targetProjectId);
|
|
if (targetProject == null)
|
|
throw new BLLException("Target project not found in the database");
|
|
}
|
|
|
|
var copiedScenario = new Scenario();
|
|
copiedScenario.Id = Guid.NewGuid();
|
|
|
|
if (string.IsNullOrEmpty(scenarioName))
|
|
{
|
|
if (!copyingToOtherProject)
|
|
copiedScenario.Name = "Copy " + scenario.Name;
|
|
else
|
|
copiedScenario.Name = scenario.Name;
|
|
}
|
|
else
|
|
{
|
|
copiedScenario.Name = scenarioName;
|
|
}
|
|
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios CopyTo GetScenarui and validate {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios CopyTo GetScenarui and validate {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
|
|
copiedScenario.ParentId = targetProjectId;
|
|
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;
|
|
// we cannot copy draft scenario as not draft
|
|
copiedScenario.Status = scenario.Status == (int)ScenarioStatus.Draft ? (int)ScenarioStatus.Draft : (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;
|
|
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios CopyTo add scenario parametrs {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios CopyTo add scenario parametrs {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
//env-700 include cost savings info in new scenario
|
|
if (includeCostSavings)
|
|
{
|
|
copiedScenario.CostSavings = scenario.CostSavings;
|
|
copiedScenario.ROIDate = scenario.ROIDate;
|
|
copiedScenario.CostSavings1 = new Collection<CostSaving>();
|
|
if (scenario.CostSavings1 != null && scenario.CostSavings1.Count > 0)
|
|
{
|
|
var costSavings = new List<CostSaving>(scenario.CostSavings1.Count);
|
|
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
|
|
};
|
|
costSavings.Add(nc);
|
|
}
|
|
copiedScenario.CostSavings1 = copiedScenario.CostSavings1?.Union(costSavings).ToList();
|
|
}
|
|
copiedScenario.CostSavingsDescription = scenario.CostSavingsDescription;
|
|
copiedScenario.CostSavingsEndDate = scenario.CostSavingsEndDate;
|
|
copiedScenario.CostSavingsStartDate = scenario.CostSavingsEndDate;
|
|
copiedScenario.CostSavingsType = scenario.CostSavingsType;
|
|
}
|
|
DbContext.Scenarios.Add(copiedScenario);
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios CopyTo if (includeCostSavings) {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios CopyTo if (includeCostSavings) {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
|
|
if (copiedScenario.Status == (int)ScenarioStatus.Active)
|
|
{
|
|
var transactionId = Utils.GetOrCreateTransactionId(DbContext, string.Empty);
|
|
if (copiedScenario.ParentId.HasValue)
|
|
DeactivateScenarios(copiedScenario.ParentId.Value, copiedScenario.Type, transactionId);
|
|
}
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios CopyTo if (copiedScenario.Status == (int)ScenarioStatus.Active) {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios CopyTo if (copiedScenario.Status == (int)ScenarioStatus.Active) {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
|
|
if (saveType == ScenarioSaveType.KeepCurrent)
|
|
{
|
|
copiedScenario.TemplateId = scenario.TemplateId;
|
|
copiedScenario.IsBottomUp = scenario.IsBottomUp;
|
|
}
|
|
else
|
|
{
|
|
copiedScenario.IsBottomUp = (saveType == ScenarioSaveType.BottomUp);
|
|
|
|
// we need to change scenario template if user changes BU to TD scenario and vice versa
|
|
var changeTemplate = (copiedScenario.IsBottomUp != scenario.IsBottomUp);
|
|
if (changeTemplate)
|
|
{
|
|
var template = GetBlankTemplate(copiedScenario.IsBottomUp);
|
|
if (template == null)
|
|
throw new InvalidOperationException(string.Format("Cannot change template of copied scenario to blank one because it does not exist for {0} scenario type", saveType));
|
|
|
|
copiedScenario.TemplateId = template.Id;
|
|
}
|
|
else
|
|
{
|
|
copiedScenario.TemplateId = scenario.TemplateId;
|
|
}
|
|
}
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios CopyTo if (saveType == ScenarioSaveType.KeepCurrent) {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios CopyTo if (saveType == ScenarioSaveType.KeepCurrent) {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
|
|
DbContext.BulkSaveChanges();
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios CopyTo if (saveType == ScenarioSaveType.KeepCurrent) DbContext.SaveChanges(); {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios CopyTo if (saveType == ScenarioSaveType.KeepCurrent) DbContext.SaveChanges(); {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
|
|
var scenarioDetail = DbContext.ScenarioDetail.AsNoTracking().Where(s => s.ParentID == id).ToList();
|
|
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios CopyTo DbContext.ScenarioDetail.AsNoTracking().Where(s => s.ParentID == id).ToList() {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios CopyTo DbContext.ScenarioDetail.AsNoTracking().Where(s => s.ParentID == id).ToList() {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
var newScenarioDetails = new List<ScenarioDetail>();
|
|
foreach (var scenarioDetails in scenarioDetail)
|
|
{
|
|
var copiedScenarioDetails = new ScenarioDetail();
|
|
copiedScenarioDetails.Cost = scenarioDetails.Cost;
|
|
copiedScenarioDetails.Id = Guid.NewGuid();
|
|
copiedScenarioDetails.ParentID = copiedScenario.Id;
|
|
copiedScenarioDetails.ExpenditureCategoryId = scenarioDetails.ExpenditureCategoryId;
|
|
copiedScenarioDetails.WeekEndingDate = scenarioDetails.WeekEndingDate;
|
|
if (copiedScenario.IsBottomUp != scenario.IsBottomUp && saveType == ScenarioSaveType.BottomUp)
|
|
copiedScenarioDetails.Quantity = 0;
|
|
else
|
|
copiedScenarioDetails.Quantity = scenarioDetails.Quantity;
|
|
copiedScenarioDetails.WeekOrdinal = scenarioDetails.WeekOrdinal;
|
|
//DbContext.Entry(copiedScenarioDetails).State = EntityState.Added;
|
|
newScenarioDetails.Add(copiedScenarioDetails);
|
|
}
|
|
DbContext.ScenarioDetail.AddRange(newScenarioDetails);
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios CopyTo foreach (var scenarioDetails in scenarioDetail) {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios CopyTo foreach (var scenarioDetails in scenarioDetail) {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
|
|
var rates = DbContext.Rates.AsNoTracking().Where(s => s.ParentId == id).ToList();
|
|
var newRates = new List<Rate>();
|
|
foreach (var scenarioRate in rates)
|
|
{
|
|
var copiedScenarioRate = new Rate();
|
|
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;
|
|
newRates.Add(copiedScenarioRate);
|
|
//DbContext.Entry(copiedScenarioRate).State = EntityState.Added;
|
|
}
|
|
DbContext.Rates.AddRange(newRates);
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios CopyTo foreach (var scenarioRate in DbContext.Rates.Where(s => s.ParentId == id).ToList()) {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios CopyTo foreach (var scenarioRate in DbContext.Rates.Where(s => s.ParentId == id).ToList()) {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
|
|
//teams allocation
|
|
var teamAllocations = DbContext.TeamAllocations.AsNoTracking().Where(t => t.ScenarioId == id).ToList();
|
|
var newTeamAllocations = new List<TeamAllocation>();
|
|
foreach (var teamAllocation in teamAllocations)
|
|
{
|
|
var copiedTeamAllocation = new TeamAllocation();
|
|
copiedTeamAllocation.Id = Guid.NewGuid();
|
|
copiedTeamAllocation.ScenarioId = copiedScenario.Id;
|
|
copiedTeamAllocation.ExpenditureCategoryId = teamAllocation.ExpenditureCategoryId;
|
|
if (copiedScenario.IsBottomUp != scenario.IsBottomUp && saveType == ScenarioSaveType.BottomUp)
|
|
copiedTeamAllocation.Quantity = 0;
|
|
else
|
|
copiedTeamAllocation.Quantity = teamAllocation.Quantity;
|
|
copiedTeamAllocation.TeamId = teamAllocation.TeamId;
|
|
copiedTeamAllocation.WeekEndingDate = teamAllocation.WeekEndingDate;
|
|
//DbContext.Entry(copiedTeamAllocation).State = EntityState.Added;
|
|
newTeamAllocations.Add(copiedTeamAllocation);
|
|
}
|
|
DbContext.TeamAllocations.AddRange(newTeamAllocations);
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios CopyTo foreach (var teamAllocation in DbContext.TeamAllocations.Where(t => t.ScenarioId == id).ToList()) {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios CopyTo foreach (var teamAllocation in DbContext.TeamAllocations.Where(t => t.ScenarioId == id).ToList()) {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
|
|
//resouces allocation
|
|
if (includePeopleResources)
|
|
{
|
|
var resourceAllocations = DbContext.PeopleResourceAllocations.Where(t => t.ScenarioId == id).ToList();
|
|
var newResourceAllocations = new List<PeopleResourceAllocation>();
|
|
foreach (var resourceAllocation in resourceAllocations)
|
|
{
|
|
var copiedPeopleResourceAllocation = DbContext.PeopleResourceAllocations.Create();
|
|
copiedPeopleResourceAllocation.Id = Guid.NewGuid();
|
|
copiedPeopleResourceAllocation.PeopleResourceId = resourceAllocation.PeopleResourceId;
|
|
copiedPeopleResourceAllocation.ScenarioId = copiedScenario.Id;
|
|
copiedPeopleResourceAllocation.ExpenditureCategoryId = resourceAllocation.ExpenditureCategoryId;
|
|
copiedPeopleResourceAllocation.TeamId = resourceAllocation.TeamId;
|
|
copiedPeopleResourceAllocation.Quantity = resourceAllocation.Quantity;
|
|
copiedPeopleResourceAllocation.WeekEndingDate = resourceAllocation.WeekEndingDate;
|
|
//DbContext.Entry(copiedPeopleResourceAllocation).State = EntityState.Added;
|
|
newResourceAllocations.Add(copiedPeopleResourceAllocation);
|
|
}
|
|
DbContext.PeopleResourceAllocations.AddRange(newResourceAllocations);
|
|
}
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios CopyTo if (includePeopleResources) {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios CopyTo if (includePeopleResources) {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
|
|
DbContext.BulkSaveChanges();
|
|
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios CopyTo if (includePeopleResources) DbContext.SaveChanges(); {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios CopyTo if (includePeopleResources) DbContext.SaveChanges(); {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
|
|
if (copyingToOtherProject)
|
|
{
|
|
// Adding of source scenario teams to target project and all it's scenarios
|
|
var sourceProjectTeams = DbContext.Team2Project.Where(x => x.ProjectId == scenario.ParentId)
|
|
.Select(x => x.TeamId).ToList();
|
|
var targetProjectTeams = DbContext.Team2Project.Where(x => x.ProjectId == targetProjectId)
|
|
.Select(x => x.TeamId).ToList();
|
|
|
|
if ((sourceProjectTeams.Except(targetProjectTeams).Count() > 0) ||
|
|
(targetProjectTeams.Except(sourceProjectTeams).Count() > 0))
|
|
{
|
|
// Team lists differ
|
|
prMngr.UpdateProjectTeams(targetProject, sourceProjectTeams, copiedScenario.Id, true);
|
|
DbContext.BulkSaveChanges();
|
|
}
|
|
|
|
}
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios CopyTo if (copyingToOtherProject) {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios CopyTo if (copyingToOtherProject) {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
|
|
return copiedScenario;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates costs of referenced scenario details records and recalculate bottom-up costs of related scenarios.
|
|
/// </summary>
|
|
/// <param name="rate">A <see cref="RateModel"/> object that has been created or updated.</param>
|
|
public bool ApplyRateAndRecalculateScenarios(RateModel rate)
|
|
{
|
|
if (rate.ExpenditureCategoryId == Guid.Empty)
|
|
{
|
|
throw new ArgumentException("Rate's expenditure category is not set.", "rate");
|
|
// return false;
|
|
}
|
|
if (!rate.ParentId.HasValue)
|
|
{
|
|
throw new ArgumentException("Rate's parent Id is not set.", "rate");
|
|
// return false;
|
|
}
|
|
|
|
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 == 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
|
|
}).ToList();
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
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().ToList();
|
|
//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;
|
|
}
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
return true;
|
|
#endregion
|
|
}
|
|
|
|
public bool RecalculateCapacityScenariosRates(RateModel 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;
|
|
}
|
|
return true;
|
|
}
|
|
public void RecalculateCapacityScenariosUOM(UOMModel uom, UOMModel oldUom)
|
|
{
|
|
var sb = new StringBuilder();
|
|
sb.AppendLine("ScenarioManager.RecalculateCapacityScenariosUOM method. uom:");
|
|
uom.DebugObjectProperties(sb);
|
|
sb.AppendLine("oldUom:");
|
|
oldUom.DebugObjectProperties(sb);
|
|
Logger.Debug(sb);
|
|
|
|
var scenarioIds = DbContext.Scenarios.Where(x => x.Type == (int)ScenarioType.TeamActualCapacity || x.Type == (int)ScenarioType.TeamPlannedCapacity).Select(x => x.Id).ToList();
|
|
var expCats = DbContext.ExpenditureCategory.Where(x => x.UOMId == uom.Id).Select(x => x.Id).ToList();
|
|
var sds = DbContext.ScenarioDetail.Where(x => scenarioIds.Contains(x.ParentID.Value) && expCats.Contains(x.ExpenditureCategoryId.Value)).ToList();
|
|
|
|
List<Rate> rates = null;
|
|
Dictionary<Guid?, List<PeopleResourceModel>> resources = null;
|
|
if (oldUom.UOMValue == 0)
|
|
{
|
|
//Method should first of all find all GLOBAL rates for the category.
|
|
rates = DbContext.Rates.AsNoTracking().Where(x => expCats.Contains(x.ExpenditureCategoryId) && x.Type == 0).ToList(); // Global = 0
|
|
resources = RecalculateActualCapacityForExpCat(expCats, scenarioIds);
|
|
}
|
|
var multiplyer = (oldUom.UOMValue == 0 ? uom.UOMValue : uom.UOMValue / oldUom.UOMValue);
|
|
foreach (var sd in sds)
|
|
{
|
|
if (oldUom.UOMValue == 0)
|
|
{
|
|
var resourceCount = resources.ContainsKey(sd.ParentID) ? resources[sd.ParentID].Where(r =>
|
|
r.StartDate <= sd.WeekEndingDate.Value && sd.WeekEndingDate.Value <= r.EndDate &&
|
|
r.ExpenditureCategoryId == sd.ExpenditureCategoryId).Count() : 0;
|
|
var rate = rates.FirstOrDefault(x =>
|
|
x.StartDate <= sd.WeekEndingDate.Value && sd.WeekEndingDate.Value <= x.EndDate &&
|
|
x.ExpenditureCategoryId == sd.ExpenditureCategoryId);
|
|
if (rate != null)
|
|
{
|
|
sd.Cost = resourceCount * rate.Rate1;
|
|
}
|
|
sd.Quantity = resourceCount * uom.UOMValue;
|
|
}
|
|
else
|
|
{
|
|
sd.Quantity *= multiplyer;
|
|
sd.Cost *= multiplyer;
|
|
}
|
|
DbContext.Entry(sd).State = EntityState.Modified;
|
|
}
|
|
}
|
|
private Dictionary<Guid?, List<PeopleResourceModel>> RecalculateActualCapacityForExpCat(List<Guid> expCatIds, List<Guid> actualCapacityScenarioIds)
|
|
{
|
|
var teams = new TeamManager(DbContext).LoadTeamsWithResourcesByActualCapacityScenarios(expCatIds, actualCapacityScenarioIds).GroupBy(g => g.ActualCapacityScenarioId);
|
|
return teams.ToDictionary(k => k.Key, t => t.SelectMany(r => r.PeopleResources.Where(x => x.IsActiveEmployee && expCatIds.Contains(x.ExpenditureCategoryId))).ToList());
|
|
}
|
|
|
|
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 sb = new StringBuilder();
|
|
sb.AppendLine("/ExpenditureCategory/Edit method (post). Model:");
|
|
exp.DebugObjectProperties(sb);
|
|
sb.AppendLine("Old Model:");
|
|
oldExp.DebugObjectProperties(sb);
|
|
Logger.Debug(sb);
|
|
|
|
var scenarioIds = DbContext.Scenarios.Where(x => x.Type == (int)ScenarioType.TeamActualCapacity || x.Type == (int)ScenarioType.TeamPlannedCapacity).Select(x => x.Id).ToList();
|
|
List<Rate> rates = null;
|
|
Dictionary<Guid?, List<PeopleResourceModel>> resources = null;
|
|
if (oldUOM.UOMValue == 0)
|
|
{
|
|
//Method should first of all find all GLOBAL rates for the category.
|
|
rates = DbContext.Rates.AsNoTracking().Where(x => x.ExpenditureCategoryId == exp.Id && x.Type == 0).ToList(); // Global = 0
|
|
resources = RecalculateActualCapacityForExpCat(new List<Guid>() { exp.Id }, scenarioIds);
|
|
}
|
|
var sds = DbContext.ScenarioDetail.Where(x => scenarioIds.Contains(x.ParentID.Value) && x.ExpenditureCategoryId.Value == exp.Id).ToList();
|
|
var multiplyer = (oldUOM.UOMValue == 0 ? 0 : newUOM.UOMValue / oldUOM.UOMValue);
|
|
foreach (var sd in sds)
|
|
{
|
|
if (oldUOM.UOMValue == 0)
|
|
{
|
|
var resourceCount = resources.ContainsKey(sd.ParentID) ? resources[sd.ParentID].Where(r =>
|
|
r.StartDate <= sd.WeekEndingDate.Value && sd.WeekEndingDate.Value <= r.EndDate &&
|
|
r.ExpenditureCategoryId == sd.ExpenditureCategoryId).Count() : 0;
|
|
var rate = rates.FirstOrDefault(x =>
|
|
x.StartDate <= sd.WeekEndingDate.Value && sd.WeekEndingDate.Value <= x.EndDate);
|
|
|
|
if (rate != null)
|
|
{
|
|
sd.Cost = resourceCount * rate.Rate1;
|
|
}
|
|
sd.Quantity = resourceCount * newUOM.UOMValue;
|
|
}
|
|
else
|
|
{
|
|
sd.Quantity *= multiplyer;
|
|
sd.Cost *= multiplyer;
|
|
}
|
|
DbContext.Entry(sd).State = EntityState.Modified;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes team allocations and resource allocations for the specified teams, which are not in scenario new teams list.
|
|
/// </summary>
|
|
/// <param name="scenariosToUpdate">Scenario Ids to perform update to.</param>
|
|
/// <param name="newTeamList">A full list of Team Ids referenced to the project and to all it's scenarios.</param>
|
|
/// <param name="rates">Rates info loaded from DB and grouped by Scenario.Id then by ExpenditureCategory.Id</param>
|
|
public void RecalculateScenarioAllocationsForRemovedTeams(List<Guid> scenariosToUpdate, List<Guid> newTeamList, Dictionary<Guid, Dictionary<Guid, IEnumerable<RateModel>>> rates)
|
|
{
|
|
if (scenariosToUpdate == null)
|
|
throw new ArgumentNullException("scenariosToUpdate");
|
|
|
|
if (newTeamList == null)
|
|
throw new ArgumentNullException("newTeamList");
|
|
|
|
if (scenariosToUpdate.Count < 1)
|
|
return;
|
|
|
|
// Remove resource allocations for teams, which are not in new project teams list
|
|
var resourceAllocationsToDelete = DbContext.PeopleResourceAllocations.Where(x =>
|
|
scenariosToUpdate.Contains(x.ScenarioId) && !newTeamList.Contains(x.TeamId));
|
|
var delResTeamsAffected = DbContext.PeopleResourceAllocations.RemoveRange(resourceAllocationsToDelete).Select(t => new Tuple<Guid, Guid>(t.TeamId, t.ScenarioId)).Distinct();
|
|
|
|
// Remove team allocations for teams, which are not in new project teams list
|
|
var teamAllocationsToDelete = DbContext.TeamAllocations.Where(x =>
|
|
scenariosToUpdate.Contains(x.ScenarioId) && !newTeamList.Contains(x.TeamId));
|
|
var delTeamAffected = DbContext.TeamAllocations.RemoveRange(teamAllocationsToDelete).Select(t => new Tuple<Guid, Guid>(t.TeamId, t.ScenarioId)).Distinct();
|
|
|
|
// recalculate SD for BU scenarios to keep data consistency
|
|
var scenarioIdsToUpdate = delResTeamsAffected.Union(delTeamAffected).Select(t => t.Item2).Distinct();
|
|
scenarioIdsToUpdate = DbContext.Scenarios.Where(t => scenarioIdsToUpdate.Contains(t.Id) && t.IsBottomUp).Select(t => t.Id).ToArray();
|
|
if (scenarioIdsToUpdate.Any())
|
|
{
|
|
DbContext.SaveChanges();
|
|
var scenarioDetailsToRecalculate = DbContext.ScenarioDetail.Where(t => t.ParentID.HasValue && scenarioIdsToUpdate.Contains(t.ParentID.Value))
|
|
.GroupBy(x => x.ParentID.Value).ToDictionary(s => s.Key, sGrp =>
|
|
sGrp.GroupBy(x => x.ExpenditureCategoryId.Value).ToDictionary(e => e.Key, eGrp =>
|
|
eGrp.GroupBy(x => x.WeekEndingDate.Value).ToDictionary(w => w.Key, a => a.FirstOrDefault())));
|
|
var teamAllocationsToRecalculate = DbContext.TeamAllocations.AsNoTracking().Where(t => scenarioIdsToUpdate.Contains(t.ScenarioId))
|
|
.GroupBy(x => x.ScenarioId).ToDictionary(s => s.Key, sGrp =>
|
|
sGrp.GroupBy(x => x.ExpenditureCategoryId).ToDictionary(e => e.Key, eGrp =>
|
|
eGrp.GroupBy(x => x.WeekEndingDate).ToDictionary(w => w.Key, a => a.Select(ta => ta.Quantity).ToArray())));
|
|
var resourcesExistInScenario = DbContext.PeopleResourceAllocations.AsNoTracking().Where(t => scenarioIdsToUpdate.Contains(t.ScenarioId))
|
|
.GroupBy(t => t.ScenarioId).ToDictionary(sGr => sGr.Key, sElems => sElems
|
|
.GroupBy(t => t.ExpenditureCategoryId).ToDictionary(ecGr => ecGr.Key, ecElems => ecElems.Any()));
|
|
|
|
// the following code does not assume that we could have TA record but do not have SD record
|
|
// if we should consider this case as well then get delTeamAffected and delResTeamsAffected as tuples<ScenarioId, ECId, Week>
|
|
// and iterate through them
|
|
foreach (var scenarioPair in scenarioDetailsToRecalculate)
|
|
{
|
|
var scenarioTeamAllocations = teamAllocationsToRecalculate.ContainsKey(scenarioPair.Key) ?
|
|
teamAllocationsToRecalculate[scenarioPair.Key] : new Dictionary<Guid, Dictionary<DateTime, decimal[]>>();
|
|
var scenarioRates = rates.ContainsKey(scenarioPair.Key) ? rates[scenarioPair.Key] : new Dictionary<Guid, IEnumerable<RateModel>>();
|
|
var scenarioResources = resourcesExistInScenario.ContainsKey(scenarioPair.Key) ? resourcesExistInScenario[scenarioPair.Key] : new Dictionary<Guid, bool>();
|
|
foreach (var ecpair in scenarioPair.Value)
|
|
{
|
|
var ecTeamAllocations = scenarioTeamAllocations.ContainsKey(ecpair.Key) ?
|
|
scenarioTeamAllocations[ecpair.Key] : new Dictionary<DateTime, decimal[]>();
|
|
var isRemoveSD = !(scenarioResources.ContainsKey(ecpair.Key) ? scenarioResources[ecpair.Key] : false);
|
|
// if there are any resources for this EC then system should recalculate related SD quantity and cost
|
|
// but if there are no resources for this EC in ALL weeks, then we should totaly remove SD for this EC
|
|
// because there is no need in this category anymore
|
|
foreach (var week in ecpair.Value)
|
|
{
|
|
if (isRemoveSD)
|
|
DbContext.Entry(week.Value).State = EntityState.Deleted;
|
|
else
|
|
{
|
|
var weekTeamAllocations = ecTeamAllocations.ContainsKey(week.Key) ?
|
|
ecTeamAllocations[week.Key] : new decimal[0];
|
|
week.Value.Quantity = weekTeamAllocations.Any() ? weekTeamAllocations.Sum() : 0;
|
|
week.Value.Cost = RateManager.GetRateValue(scenarioRates, week.Value.ExpenditureCategoryId.Value,
|
|
week.Value.WeekEndingDate.Value) * week.Value.Quantity;
|
|
DbContext.Entry(week.Value).State = EntityState.Modified;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
DbContext.SaveChanges();
|
|
foreach (var scenarioPair in scenarioDetailsToRecalculate)
|
|
{
|
|
SetBottomUpCosts(scenarioPair.Key);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns all non actuals scenarios for given project or project part.
|
|
/// </summary>
|
|
/// <param name="projectOrPartId">Project or part id</param>
|
|
public List<Guid> GetProjectNonActualsScenarios(Guid projectOrPartId)
|
|
{
|
|
if (null == projectOrPartId || Guid.Empty.Equals(projectOrPartId))
|
|
return new List<Guid>();
|
|
|
|
var query = (from p in DbContext.Projects
|
|
join sc in DbContext.Scenarios on p.Id equals sc.ParentId
|
|
where (p.Id == projectOrPartId || p.ParentProjectId == projectOrPartId)
|
|
&& sc.Type != (int)ScenarioType.Actuals
|
|
select sc.Id);
|
|
List<Guid> scenarios = query.ToList();
|
|
|
|
return scenarios;
|
|
}
|
|
|
|
public Guid Save(ScenarioDetailsSnapshotSaveModel snapshotModel, string userId)
|
|
{
|
|
|
|
if (snapshotModel == null)
|
|
throw new ArgumentNullException(nameof(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));
|
|
|
|
|
|
Scenario scenario = null;
|
|
|
|
var transactionId = Utils.GetOrCreateTransactionId(DbContext, string.Empty);
|
|
BackgroundProcessManager bpm = new BackgroundProcessManager();
|
|
var crmDal = IntergrationHelper.GetIntergrationClass(IntergrationAccessType.ResourceExport, null);
|
|
Hashtable ResourceRemoves = new Hashtable();
|
|
Hashtable ResourceAdds = new Hashtable();
|
|
Hashtable TeamAdds = new Hashtable();
|
|
|
|
var prManager = new ProjectManager(DbContext);
|
|
var depManager = new ProjectDependencyManager(DbContext);
|
|
var teamManager = new TeamManager(DbContext);
|
|
var peopleResourceManager = new PeopleResourcesManager(DbContext);
|
|
var allocationChangesTracker = new ScenarioDetailsEventsTracker(ScenarioDetailsEventsTrackerUsageMode.ScenarioDetails, DbContext, userId);
|
|
|
|
var expCats = DbContext.ExpenditureCategory.ToDictionary(x => x.Id);
|
|
|
|
var scenarioDetailItems = new List<ScenarioDetail>();
|
|
var teamAllocations = new List<TeamAllocation>();
|
|
var resourceAllocations = new Dictionary<Guid, Dictionary<Guid, List<PeopleResourceAllocation>>>();
|
|
var teams2Scenario = new Dictionary<Guid, short>();
|
|
var costSavings = new List<CostSaving>();
|
|
var scenarioTeamIds = snapshotModel.TeamsInScenario.Select(t => t.TeamId).ToArray();
|
|
var resource2teams = DbContext.VW_TeamResource.AsNoTracking()
|
|
.Where(t => scenarioTeamIds.Contains(t.TeamId))
|
|
.GroupBy(k => k.TeamId)
|
|
.ToDictionary(k => k.Key, t => t.GroupBy(tg => tg.Id).ToDictionary(r => r.Key, elem => elem.ToList()));
|
|
|
|
|
|
|
|
var newScenario = true;
|
|
if (snapshotModel.Scenario.Id.HasValue && !Guid.Empty.Equals(snapshotModel.Scenario.Id))
|
|
{
|
|
var readOnly = snapshotModel.Scenario.SaveAs == SaveAsScenario.New;
|
|
scenario = Load(snapshotModel.Scenario.Id, readOnly);
|
|
newScenario = scenario == null;
|
|
if (!newScenario)
|
|
{
|
|
scenarioDetailItems = GetScenarioDetails(scenario.Id, null, null, readOnly);
|
|
teamAllocations = teamManager.GetTeamsAllocation(scenario.Id, readOnly: readOnly);
|
|
resourceAllocations = peopleResourceManager.GetPeopleResourceAllocations(scenario.Id, readOnly)
|
|
.GroupBy(ec => ec.ExpenditureCategoryId)
|
|
.ToDictionary(k => k.Key, e => e.GroupBy(t => t.TeamId).ToDictionary(tk => tk.Key, tl => tl.ToList()));
|
|
costSavings = GetCostSavings(scenario.Id, readOnly);
|
|
}
|
|
}
|
|
|
|
if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update)
|
|
{
|
|
if (newScenario)
|
|
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 (!newScenario)
|
|
{
|
|
scenario.Id = Guid.NewGuid();
|
|
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 = prManager.Load(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,
|
|
Color = project.Color,
|
|
ShotStartDate = null,
|
|
GrowthScenario = snapshotModel.Scenario.GrowthScenario,
|
|
SystemAttributeObjectID = null,
|
|
IsBottomUp = snapshotModel.Scenario.IsBottomUp
|
|
};
|
|
if (snapshotModel.Scenario.SaveAsDraft)
|
|
{
|
|
scenario.Status = ScenarioStatus.Draft.GetHashCode();
|
|
}
|
|
else
|
|
{
|
|
if (project.IsRevenueGenerating)
|
|
{
|
|
scenario.ProjectedRevenue = snapshotModel.Scenario.ProjectedRevenue;
|
|
scenario.UseLMMargin = Convert.ToInt32(snapshotModel.Scenario.UseLMMargin);
|
|
|
|
if (!snapshotModel.Scenario.UseLMMargin)
|
|
{
|
|
scenario.ExpectedGrossMargin = snapshotModel.Scenario.GrossMargin / 100;
|
|
scenario.TDDirectCosts = snapshotModel.Scenario.ProjectedRevenue -
|
|
(snapshotModel.Scenario.ProjectedRevenue * snapshotModel.Scenario.GrossMargin /
|
|
100);
|
|
}
|
|
else
|
|
{
|
|
scenario.ExpectedGrossMargin_LM = snapshotModel.Scenario.LMMargin / 100;
|
|
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 = Utils.ConvertFromUnixDate(snapshotModel.Scenario.StartDate).Date;
|
|
scenario.EndDate = Utils.ConvertFromUnixDate(snapshotModel.Scenario.EndDate).Date;
|
|
scenario.Duration = snapshotModel.Scenario.Duration;
|
|
|
|
// Set scenario Id for changes tracker
|
|
allocationChangesTracker.StartTracking(scenario.Id, scenario.ParentId.Value);
|
|
|
|
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 = Utils.ConvertFromUnixDate(snapshotModel.Scenario.CostSavings.CostSavingStartDate.Value);
|
|
if (snapshotModel.Scenario.CostSavings.CostSavingEndDate.HasValue && snapshotModel.Scenario.CostSavings.CostSavingEndDate > 0)
|
|
scenario.CostSavingsEndDate = Utils.ConvertFromUnixDate(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, transactionId, 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.Scenario.SaveAs == SaveAsScenario.Update)
|
|
{
|
|
// Add client-side actions audit
|
|
allocationChangesTracker.AddClientSideActions(snapshotModel.Audit);
|
|
}
|
|
|
|
var expCatsInScenario = snapshotModel.AvailableExpenditures.Select(x => x.Id).ToList();
|
|
var startDate = Utils.ConvertFromUnixDate(snapshotModel.Scenario.StartDate);
|
|
var endDate = Utils.ConvertFromUnixDate(snapshotModel.Scenario.EndDate).AddDays(7);
|
|
|
|
List<Guid> addedExpenditures = new List<Guid>();
|
|
List<Guid> addedTeams = new List<Guid>();
|
|
|
|
if (snapshotModel.Calendar != null)
|
|
{
|
|
#region Save Scenario Details
|
|
|
|
// Get and track Expenditures recently added to scenario
|
|
var scenarioExpCatsInDatabase = scenarioDetailItems.Where(x => x.ExpenditureCategoryId.HasValue).Select(x => x.ExpenditureCategoryId.Value).Distinct();
|
|
var scenarioExpCatsInSnapshot = snapshotModel.Calendar.Expenditures.Keys.Select(x => new Guid(x));
|
|
addedExpenditures = scenarioExpCatsInSnapshot.Except(scenarioExpCatsInDatabase).ToList();
|
|
allocationChangesTracker.ExpenditureCategoriesAddedToScenario(addedExpenditures);
|
|
|
|
var shapShotDetailsItems = snapshotModel.Calendar.GetScenarioDetails(true);
|
|
foreach (var scenarioItem in shapShotDetailsItems)
|
|
{
|
|
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 (scenarioItem.Quantity < 0)
|
|
{
|
|
scenarioItem.Quantity = 0;
|
|
scenarioItem.Cost = 0;
|
|
}
|
|
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);
|
|
allocationChangesTracker.ScenarioDetailsValueAdded(currentDbItem);
|
|
}
|
|
}
|
|
else if (currentDbItem.Quantity != scenarioItem.Quantity || currentDbItem.Cost != scenarioItem.Cost)
|
|
{
|
|
// Store old values to track changes
|
|
var oldQuantityValue = currentDbItem.Quantity;
|
|
var oldCostValue = currentDbItem.Cost;
|
|
|
|
currentDbItem.WeekEndingDate = scenarioItem.WeekEndingDate;
|
|
currentDbItem.WeekOrdinal = scenarioItem.WeekOrdinal;
|
|
currentDbItem.Quantity = scenarioItem.Quantity;
|
|
currentDbItem.Cost = scenarioItem.Cost;
|
|
|
|
if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update)
|
|
{
|
|
allocationChangesTracker.ScenarioDetailsValueChanged(currentDbItem, oldQuantityValue, oldCostValue);
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
// Get recently added to scenario teams
|
|
var scenarioTeamsInDatabase = teamAllocations.Select(x => x.TeamId).Distinct();
|
|
var scenarioTeamsInSnapshot = snapshotModel.Calendar.Expenditures.Values.SelectMany(x => x.Teams.Keys.Select(t => new Guid(t))).Distinct();
|
|
addedTeams = scenarioTeamsInSnapshot.Except(scenarioTeamsInDatabase).ToList();
|
|
allocationChangesTracker.TeamsAddedToScenario(addedTeams);
|
|
|
|
foreach (var expGroup in snapshotModel.Calendar.Expenditures)
|
|
{
|
|
Dictionary<Guid, List<PeopleResourceAllocation>> ecResAllocations = null;
|
|
if (resourceAllocations.ContainsKey(expGroup.Value.ExpenditureCategoryId))
|
|
ecResAllocations = resourceAllocations[expGroup.Value.ExpenditureCategoryId];
|
|
else
|
|
{
|
|
ecResAllocations = new Dictionary<Guid, List<PeopleResourceAllocation>>();
|
|
resourceAllocations.Add(expGroup.Value.ExpenditureCategoryId, ecResAllocations);
|
|
}
|
|
|
|
foreach (var team in expGroup.Value.Teams)
|
|
{
|
|
var currentTeamId = new Guid(team.Key);
|
|
|
|
#region Team Allocation
|
|
|
|
var currentTeamAllocations = teamAllocations.FindAll(x => x.TeamId.ToString() == team.Key && x.ExpenditureCategoryId.ToString() == expGroup.Key)
|
|
.ToDictionary(x => x.WeekEndingDate);
|
|
var teamItems2Iterate = team.Value.QuantityValues.Clone();
|
|
foreach (var allocationItem in teamItems2Iterate)
|
|
{
|
|
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;
|
|
var qVal = allocationItem.Value;
|
|
if (qVal < 0)
|
|
team.Value.QuantityValues[allocationItem.Key] = qVal = 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 = qVal
|
|
};
|
|
if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update)
|
|
{
|
|
DbContext.TeamAllocations.Add(dbAllocationItem);
|
|
allocationChangesTracker.TeamAllocationValueAdded(dbAllocationItem);
|
|
}
|
|
else
|
|
teamAllocations.Add(dbAllocationItem);
|
|
}
|
|
else if (dbAllocationItem.Quantity != qVal)
|
|
{
|
|
if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update)
|
|
{
|
|
var oldTeamAllocationValue = dbAllocationItem.Quantity;
|
|
dbAllocationItem.Quantity = qVal;
|
|
allocationChangesTracker.TeamAllocationValueChanged(dbAllocationItem, oldTeamAllocationValue);
|
|
}
|
|
else
|
|
teamAllocations.FirstOrDefault(x => x.Id == dbAllocationItem.Id).Quantity = qVal;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Resource Allocation
|
|
|
|
List<PeopleResourceAllocation> teamExpCatAllocations = null;
|
|
if (ecResAllocations.ContainsKey(currentTeamId))
|
|
teamExpCatAllocations = ecResAllocations[currentTeamId];
|
|
else
|
|
{
|
|
teamExpCatAllocations = new List<PeopleResourceAllocation>();
|
|
ecResAllocations.Add(currentTeamId, teamExpCatAllocations);
|
|
}
|
|
Dictionary<Guid, List<VW_TeamResource>> teamResourceRelations =
|
|
resource2teams.ContainsKey(currentTeamId) ?
|
|
resource2teams[currentTeamId] :
|
|
new Dictionary<Guid, List<VW_TeamResource>>();
|
|
|
|
|
|
var removedResourcesFromCategory = team.Value.Resources.Values
|
|
.Where(x => x.Deleted)
|
|
.Select(x => x.Id)
|
|
.ToList();
|
|
|
|
#region Removing people resource allocations
|
|
|
|
Predicate<PeopleResourceAllocation> resourceAllocationDeletePredicate =
|
|
(t => t.WeekEndingDate < startDate || t.WeekEndingDate > endDate ||
|
|
removedResourcesFromCategory.Contains(t.PeopleResourceId));
|
|
var resourceAllocationToDelete = teamExpCatAllocations.FindAll(resourceAllocationDeletePredicate);
|
|
|
|
if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update)
|
|
{
|
|
allocationChangesTracker.ResourceAllocationValuesRemoved(resourceAllocationToDelete);
|
|
DbContext.PeopleResourceAllocations.RemoveRange(resourceAllocationToDelete);
|
|
//foreach (var allocation in resourceAllocationToDelete)
|
|
// DbContext.Entry(allocation).State = EntityState.Deleted;
|
|
}
|
|
teamExpCatAllocations.RemoveAll(resourceAllocationDeletePredicate);
|
|
|
|
#region Remove Resource from CRM
|
|
if (crmDal != null)
|
|
{
|
|
foreach (var allocation in resourceAllocationToDelete)
|
|
{
|
|
if (snapshotModel.Scenario.ParentId.HasValue && !ResourceRemoves.ContainsKey(allocation.PeopleResourceId))
|
|
{
|
|
string email = DbContext.PeopleResources.Where(x => x.Id == allocation.PeopleResourceId).Select(x => x.Email).FirstOrDefault();
|
|
var ProjectObj = DbContext.Projects.Where(x => x.Id == snapshotModel.Scenario.ParentId).FirstOrDefault();
|
|
Guid ProjectNbr = Guid.Empty;
|
|
if (Guid.TryParse(ProjectObj.ProjectNumber, out ProjectNbr) && !string.IsNullOrEmpty(email))
|
|
{
|
|
ResourceRemoves.Add(allocation.PeopleResourceId, "");
|
|
bpm.RemoveResourceCRMAsync(ProjectNbr, email, crmDal);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
foreach (var resource in team.Value.Resources.Where(x => !x.Value.Deleted))
|
|
{
|
|
bool someAllocationsWereAddedForResource = false;
|
|
bool someAllocationsWereUpdatedForResource = false;
|
|
|
|
List<VW_TeamResource> resourceRelations = null;
|
|
if (teamResourceRelations.ContainsKey(resource.Value.Id))
|
|
{
|
|
resourceRelations = teamResourceRelations[resource.Value.Id];
|
|
}
|
|
else
|
|
{
|
|
resourceRelations = new List<VW_TeamResource>();
|
|
}
|
|
var rValues = resource.Value.QuantityValues.Clone();
|
|
foreach (var allocationItem in rValues)
|
|
{
|
|
long milliseconds;
|
|
if (!long.TryParse(allocationItem.Key, out milliseconds) || milliseconds <= 0)
|
|
continue;
|
|
|
|
var date = Constants.UnixEpochDate.AddMilliseconds(milliseconds);
|
|
var dbAllocationItem =
|
|
teamExpCatAllocations.FirstOrDefault(x => x.WeekEndingDate == date &&
|
|
x.PeopleResourceId == resource.Value.Id);
|
|
var currentR2TRelation =
|
|
resourceRelations.FirstOrDefault(t => t.TeamStartDate < date &&
|
|
(!t.TeamEndDate.HasValue || t.TeamEndDate >= date));
|
|
|
|
if (currentR2TRelation == null) // if there is no resource2team record for this date
|
|
{
|
|
if (dbAllocationItem != null) // then remove any existing db record because it should not be
|
|
{
|
|
allocationChangesTracker.ResourceAllocationValueRemoved(dbAllocationItem);
|
|
DbContext.Entry(dbAllocationItem).State = EntityState.Deleted;
|
|
teamExpCatAllocations.Remove(dbAllocationItem);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var rVal = allocationItem.Value;
|
|
if (rVal < 0)
|
|
rVal = 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 = rVal,
|
|
TeamId = currentTeamId
|
|
};
|
|
if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update)
|
|
{
|
|
allocationChangesTracker.ResourceAllocationValueAdded(dbAllocationItem);
|
|
DbContext.PeopleResourceAllocations.Add(dbAllocationItem);
|
|
someAllocationsWereAddedForResource = true;
|
|
}
|
|
else
|
|
teamExpCatAllocations.Add(dbAllocationItem);
|
|
|
|
#region Add resource to CRM
|
|
try
|
|
{
|
|
if (snapshotModel.Scenario.ParentId.HasValue && crmDal != null && !ResourceAdds.ContainsKey(dbAllocationItem.PeopleResourceId))
|
|
{
|
|
string email = DbContext.PeopleResources.Where(x => x.Id == dbAllocationItem.PeopleResourceId).Select(x => x.Email).FirstOrDefault();
|
|
if (!string.IsNullOrEmpty(email))
|
|
{
|
|
var ProjectObj = DbContext.Projects.Where(x => x.Id == snapshotModel.Scenario.ParentId).FirstOrDefault();
|
|
try
|
|
{
|
|
Guid ProjectNbr = Guid.Parse(ProjectObj.ProjectNumber);
|
|
ResourceAdds.Add(dbAllocationItem.PeopleResourceId, "");
|
|
bpm.AddResourceCRMAsync(ProjectNbr, email, crmDal);
|
|
}
|
|
catch
|
|
{
|
|
}
|
|
}
|
|
}
|
|
}
|
|
catch
|
|
{
|
|
|
|
}
|
|
#endregion
|
|
}
|
|
else if (dbAllocationItem.Quantity != rVal)
|
|
{
|
|
if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update)
|
|
{
|
|
var oldValue = dbAllocationItem.Quantity;
|
|
dbAllocationItem.Quantity = rVal;
|
|
allocationChangesTracker.ResourceAllocationValueChanged(dbAllocationItem, oldValue);
|
|
someAllocationsWereUpdatedForResource = true;
|
|
}
|
|
else
|
|
teamExpCatAllocations.FirstOrDefault(x => x.Id == dbAllocationItem.Id).Quantity = rVal;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (someAllocationsWereAddedForResource && !someAllocationsWereUpdatedForResource)
|
|
{
|
|
// The only ADDING action was performed For all allocation values for current resource in current team & EC.
|
|
// Treat this situation as resource was recently assigned in screnario
|
|
allocationChangesTracker.ResourceAddedToScenario(resource.Value.Id, currentTeamId, expGroup.Value.ExpenditureCategoryId);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
}
|
|
|
|
#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)
|
|
{
|
|
allocationChangesTracker.ScenarioDetailsValuesRemoved(items2Delete);
|
|
DbContext.ScenarioDetail.RemoveRange(items2Delete);
|
|
//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)
|
|
{
|
|
allocationChangesTracker.TeamAllocationValuesRemoved(teams2Delete);
|
|
DbContext.TeamAllocations.RemoveRange(teams2Delete);
|
|
//foreach (var allocation in teams2Delete)
|
|
// DbContext.Entry(allocation).State = EntityState.Deleted;
|
|
}
|
|
|
|
teamAllocations.RemoveAll(teamDeletePredicate);
|
|
|
|
#endregion
|
|
|
|
#region Removing people resource allocations
|
|
|
|
Predicate<PeopleResourceAllocation> removedTeamsAndExpendituresPredicate =
|
|
(resourceAllocation => resourceAllocation.ScenarioId == snapshotModel.Scenario.Id &&
|
|
(!expCatsInScenario.Contains(resourceAllocation.ExpenditureCategoryId) ||
|
|
!scenarioTeamIds.Contains(resourceAllocation.TeamId)));
|
|
|
|
var resourceAllocationsList = resourceAllocations.SelectMany(t => t.Value.SelectMany(x => x.Value)).ToList();
|
|
var resAlloc2Delete = resourceAllocationsList.FindAll(removedTeamsAndExpendituresPredicate);
|
|
|
|
if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update)
|
|
{
|
|
allocationChangesTracker.ResourceAllocationValuesRemoved(resAlloc2Delete);
|
|
DbContext.PeopleResourceAllocations.RemoveRange(resAlloc2Delete);
|
|
//foreach (var allocation in resAlloc2Delete)
|
|
// DbContext.Entry(allocation).State = EntityState.Deleted;
|
|
}
|
|
|
|
resourceAllocationsList.RemoveAll(removedTeamsAndExpendituresPredicate);
|
|
|
|
#endregion
|
|
|
|
if (snapshotModel.Scenario.SaveAs == SaveAsScenario.New)
|
|
{
|
|
// Treat all scenario expenditures and teams as recently added due to scenario's selected saving mode (as New)
|
|
addedExpenditures = scenarioDetailItems.Where(x => x.ExpenditureCategoryId.HasValue).Select(x => x.ExpenditureCategoryId.Value).Distinct().ToList();
|
|
allocationChangesTracker.ExpenditureCategoriesAddedToScenario(addedExpenditures);
|
|
|
|
addedTeams = snapshotModel.Calendar.Expenditures.Values.SelectMany(x => x.Teams.Keys.Select(t => new Guid(t))).Distinct().ToList();
|
|
allocationChangesTracker.TeamsAddedToScenario(addedTeams);
|
|
|
|
var scenarioDetails = scenarioDetailItems.Select(p => new ScenarioDetail()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ParentID = scenario.Id,
|
|
ExpenditureCategoryId = p.ExpenditureCategoryId,
|
|
Quantity = p.Quantity,
|
|
Cost = p.Cost,
|
|
WeekEndingDate = p.WeekEndingDate,
|
|
WeekOrdinal = p.WeekOrdinal
|
|
});
|
|
DbContext.ScenarioDetail.AddRange(scenarioDetails);
|
|
|
|
|
|
var weeklyAllocations = resourceAllocationsList.Select(p => new PeopleResourceAllocation()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ExpenditureCategoryId = p.ExpenditureCategoryId,
|
|
PeopleResourceId = p.PeopleResourceId,
|
|
Quantity = p.Quantity,
|
|
ScenarioId = scenario.Id,
|
|
WeekEndingDate = p.WeekEndingDate,
|
|
TeamId = p.TeamId
|
|
});
|
|
|
|
DbContext.PeopleResourceAllocations.AddRange(weeklyAllocations);
|
|
|
|
// if cost savings didn't change need to copy cost savings from original
|
|
if (snapshotModel.Scenario.CostSavings == null)
|
|
{
|
|
if (costSavings != null && costSavings.Count > 0)
|
|
{
|
|
|
|
var costSavingsForSave = costSavings.Select(p => new CostSaving()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ScenarioId = scenario.Id,
|
|
Year = p.Year,
|
|
Month = p.Month,
|
|
Cost = p.Cost
|
|
});
|
|
|
|
DbContext.CostSavings.AddRange(costSavingsForSave);
|
|
}
|
|
}
|
|
|
|
var allocations = teamAllocations.Select(p => new TeamAllocation()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ScenarioId = scenario.Id,
|
|
TeamId = p.TeamId,
|
|
ExpenditureCategoryId = p.ExpenditureCategoryId,
|
|
WeekEndingDate = p.WeekEndingDate,
|
|
Quantity = p.Quantity
|
|
});
|
|
|
|
DbContext.TeamAllocations.AddRange(allocations);
|
|
|
|
// Track audit events for added SD, Team allocations & Resource Allocations
|
|
allocationChangesTracker.ScenarioDetailsValuesAdded(scenarioDetailItems);
|
|
allocationChangesTracker.TeamAllocationValuesAdded(teamAllocations);
|
|
allocationChangesTracker.ResourceAllocationValuesAdded(resourceAllocationsList);
|
|
}
|
|
|
|
var parentProject = DbContext.Projects.AsNoTracking().Include(x => x.Type).FirstOrDefault(x => x.Id == scenario.ParentId);
|
|
prManager.UpdateProjectTeams(parentProject, scenarioTeamIds.ToList(), scenario.Id, false, false);
|
|
|
|
// Write audit events for added to projects teams
|
|
if (addedTeams.Count > 0)
|
|
{
|
|
var teamNames = teamManager.GetTeamNames(addedTeams);
|
|
|
|
if (teamNames != null)
|
|
{
|
|
var teamAddEvents = addedTeams.Where(x => teamNames.ContainsKey(x)).Select(x =>
|
|
new EventRequestBaseModel()
|
|
{
|
|
ClassificationKey = "ProjectTeamsAdd",
|
|
EntityId = parentProject.Id,
|
|
ProjectId = parentProject.Id,
|
|
UserId = userId,
|
|
NewValue = teamNames[x]
|
|
}).ToList();
|
|
|
|
LogEvent(transactionId, teamAddEvents);
|
|
}
|
|
}
|
|
|
|
#region Save Cost Saving Info
|
|
|
|
if (snapshotModel.Scenario.CostSavings != null)
|
|
{
|
|
if (snapshotModel.Scenario.SaveAs == SaveAsScenario.Update)
|
|
{
|
|
if (costSavings != null)
|
|
{
|
|
//foreach (var item in costSavings)
|
|
// DbContext.Entry(item).State = EntityState.Deleted;
|
|
DbContext.CostSavings.RemoveRange(costSavings);
|
|
|
|
}
|
|
}
|
|
|
|
costSavings = GetCostSavings(snapshotModel.Scenario.CostSavings);
|
|
if (costSavings != null && costSavings.Count > 0)
|
|
{
|
|
costSavings.ForEach(p => p.ScenarioId = scenario.Id);
|
|
DbContext.CostSavings.AddRange(costSavings);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
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);
|
|
|
|
// we need to calculate ROI date only after BU costs is calculated
|
|
scenario.ROIDate = GetROIDate(scenario, costSavings);
|
|
}
|
|
|
|
if (snapshotModel.Dependencies != null)
|
|
depManager.ResolveConflicts(snapshotModel.Dependencies);
|
|
|
|
// Compile audit events and queue events to save
|
|
var allocationEvents = allocationChangesTracker.GetEvents();
|
|
LogEvent(transactionId, allocationEvents);
|
|
|
|
if (!newScenario)
|
|
{
|
|
|
|
LogScenarioRecalculateEvent(null, snapshotModel.Scenario, transactionId);
|
|
LogScenarioEvents(null, scenario, snapshotModel, transactionId);
|
|
}
|
|
else
|
|
{
|
|
LogEvent(transactionId, new EventRequestBaseModel
|
|
{
|
|
EntityId = scenario.Id,
|
|
ClassificationKey = "ScenarioCreate",
|
|
UserId = userId,
|
|
NewValue = scenario.Name,
|
|
ScenarioId = scenario.Id,
|
|
ProjectId = scenario.ParentId
|
|
});
|
|
}
|
|
|
|
if (IsContextLocal)
|
|
{
|
|
DbContext.SaveChanges();
|
|
Task.Run(() => CommitEventChanges(transactionId));
|
|
}
|
|
|
|
if (allocationChangesTracker != null)
|
|
allocationChangesTracker.Dispose();
|
|
|
|
return scenario.Id;
|
|
}
|
|
|
|
public List<Guid> GetScenariosByParentProject(Guid projectId, ScenarioType? type)
|
|
{
|
|
var scenarios = DataTable.AsNoTracking().Where(x => x.ParentId == projectId);
|
|
|
|
if (type.HasValue)
|
|
scenarios = scenarios.Where(x => x.Type == (int)type.Value);
|
|
|
|
return scenarios.Select(x => x.Id).ToList();
|
|
}
|
|
public Dictionary<Guid, List<Guid>> LoadScenarioIdsByParentProject(IEnumerable<Guid> projectIds, ScenarioType? type)
|
|
{
|
|
var scenarios = DataTable.AsNoTracking()
|
|
.Where(x => x.ParentId.HasValue && projectIds.Contains(x.ParentId.Value));
|
|
|
|
if (type.HasValue)
|
|
scenarios = scenarios.Where(x => x.Type == (int)type.Value);
|
|
|
|
return scenarios.Select(x => new
|
|
{
|
|
ProjectId = x.ParentId.Value,
|
|
ScenarioId = x.Id
|
|
}).GroupBy(t => t.ProjectId).ToDictionary(t => t.Key, el => el.Select(x => x.ScenarioId).ToList());
|
|
}
|
|
|
|
public List<ScenarioDetail> GetScenarioDetails(Guid scenarioId, IEnumerable<Guid> expenditureCategories, IEnumerable<DateTime> weekEndings, bool readOnly = false)
|
|
{
|
|
if (scenarioId == Guid.Empty)
|
|
return new List<ScenarioDetail>();
|
|
|
|
return GetScenarioDetails(new List<Guid>() { scenarioId }, expenditureCategories, weekEndings, readOnly);
|
|
}
|
|
|
|
public List<ScenarioDetail> GetScenarioDetails(IEnumerable<Guid> scenarios, IEnumerable<Guid> expenditureCategories, IEnumerable<DateTime> weekEndings, bool readOnly = false)
|
|
{
|
|
if (scenarios == null || !scenarios.Any())
|
|
return new List<ScenarioDetail>();
|
|
|
|
var detailsQuery = DbContext.ScenarioDetail.Where(x => x.ParentID.HasValue && scenarios.Contains(x.ParentID.Value));
|
|
if (readOnly)
|
|
detailsQuery = detailsQuery.AsNoTracking();
|
|
if (expenditureCategories != null && expenditureCategories.Any())
|
|
detailsQuery = detailsQuery.Where(x => x.ExpenditureCategoryId.HasValue && expenditureCategories.Contains(x.ExpenditureCategoryId.Value));
|
|
if (weekEndings != null && weekEndings.Any())
|
|
detailsQuery = detailsQuery.Where(x => x.WeekEndingDate.HasValue && weekEndings.Contains(x.WeekEndingDate.Value));
|
|
|
|
return detailsQuery.ToList();
|
|
}
|
|
|
|
public List<ScenarioDetail> GetScenarioDetails(Guid scenarioId, IEnumerable<Guid> expenditureCategories = null, DateTime? startDate = null, DateTime? endDate = null, bool readOnly = false)
|
|
{
|
|
if (scenarioId == Guid.Empty)
|
|
return new List<ScenarioDetail>();
|
|
|
|
return GetScenarioDetails(new List<Guid> { scenarioId }, expenditureCategories, startDate, endDate, readOnly);
|
|
}
|
|
|
|
// this method call is much faster than executing auto generated query by EF and it preserves tracking ability
|
|
public List<ScenarioDetail> GetScenarioDetails(IEnumerable<Guid> scenarios, IEnumerable<Guid> expenditureCategories = null, DateTime? startDate = null, DateTime? endDate = null, bool readOnly = false)
|
|
{
|
|
if (scenarios == null || !scenarios.Any())
|
|
return new List<ScenarioDetail>();
|
|
|
|
var parameters = new List<DbParameter>();
|
|
var scenarioParams = string.Join(", ", scenarios.Select(x => $"'{x}'"));
|
|
var scenarioCondition = $"ParentID IN ({scenarioParams})";
|
|
var conditions = new List<string> { scenarioCondition };
|
|
if (expenditureCategories != null && expenditureCategories.Any())
|
|
{
|
|
var ecParams = string.Join(", ", expenditureCategories.Select(x => $"'{x}'"));
|
|
var ecCondition = $"ExpenditureCategoryId IN ({ecParams})";
|
|
conditions.Add(ecCondition);
|
|
}
|
|
if (startDate.HasValue)
|
|
{
|
|
conditions.Add("WeekEndingDate >= @StartDate");
|
|
parameters.Add(new SqlParameter("@StartDate", startDate.Value));
|
|
}
|
|
if (endDate.HasValue)
|
|
{
|
|
conditions.Add("WeekEndingDate <= @EndDate");
|
|
parameters.Add(new SqlParameter("@EndDate", endDate.Value));
|
|
}
|
|
|
|
var properties = typeof(ScenarioDetail).GetSimpleProperties();
|
|
var query = $@"SELECT {string.Join(", ", properties)} FROM dbo.ScenarioDetail WHERE {string.Join(" AND ", conditions)}";
|
|
var details = DbContext.ScenarioDetail.SqlQuery(query, parameters.ToArray()).ToList();
|
|
|
|
return details;
|
|
}
|
|
|
|
public List<CostSaving> GetCostSavings(Guid scenarioId, bool readOnly = false)
|
|
{
|
|
if (scenarioId == Guid.Empty)
|
|
return new List<CostSaving>();
|
|
|
|
return GetCostSavings(new List<Guid>() { scenarioId }, readOnly);
|
|
}
|
|
|
|
public List<CostSaving> GetCostSavings(IEnumerable<Guid> scenarios, bool readOnly = false)
|
|
{
|
|
if (scenarios == null || !scenarios.Any())
|
|
return new List<CostSaving>();
|
|
|
|
var detailsQuery = DbContext.CostSavings.Where(x => scenarios.Contains(x.ScenarioId));
|
|
if (readOnly)
|
|
detailsQuery = detailsQuery.AsNoTracking();
|
|
|
|
return detailsQuery.ToList();
|
|
}
|
|
|
|
public List<VW_ProjectNeedUnassigned> GetScenarioNeedUnassigned(IEnumerable<Guid> scenarios, IEnumerable<Guid> expenditureCategories,
|
|
IEnumerable<DateTime> weekEndings, bool nonZeroValuesOnly)
|
|
{
|
|
if (scenarios == null || !scenarios.Any())
|
|
return new List<VW_ProjectNeedUnassigned>();
|
|
|
|
var qry = DbContext.VW_ProjectNeedUnassigned.AsNoTracking().Where(x => scenarios.Contains(x.ScenarioId));
|
|
|
|
if (expenditureCategories != null && expenditureCategories.Any())
|
|
qry = qry.Where(x => x.ExpenditureCategoryId.HasValue && expenditureCategories.Contains(x.ExpenditureCategoryId.Value));
|
|
|
|
if (weekEndings != null && weekEndings.Any())
|
|
qry = qry.Where(x => x.WeekEndingDate.HasValue && weekEndings.Contains(x.WeekEndingDate.Value));
|
|
|
|
if (nonZeroValuesOnly)
|
|
qry = qry.Where(x => x.RemainingQuantity != 0);
|
|
|
|
return qry.ToList();
|
|
}
|
|
|
|
public List<ScenarioDetailWithProxyItemModel> GetScenarioProxyDetails(List<Guid> scenarios, List<Guid> expenditureCategories)
|
|
{
|
|
var filterModel = new ScenarioCalendarFilterModel()
|
|
{
|
|
SelectedExpCats = expenditureCategories
|
|
};
|
|
return GetScenarioProxyDetails(filterModel, null, null, scenarios).Select(t => (ScenarioDetailWithProxyItemModel)t).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();
|
|
}
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="weekEndings"></param>
|
|
/// <param name="forecastDetails"></param>
|
|
/// <param name="actualsDetails"></param>
|
|
/// <param name="uoms">Dictionary of UOM values. Key = UOMValue.Id, Value = UOMValue.Value.</param>
|
|
/// <returns></returns>
|
|
public Dictionary<string, ExpenditureDetail> BuildExpendituresForScenario(List<DateTime> weekEndings, List<VW_ScenarioAndProxyDetails> forecastDetails,
|
|
List<VW_ScenarioAndProxyDetails> actualsDetails, Dictionary<Guid, decimal> uoms)
|
|
{
|
|
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,
|
|
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],
|
|
AllowResourceAssignment = x.AllowResourceAssignment,
|
|
}).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;
|
|
}
|
|
/// <summary>
|
|
/// Loads data for the specified scenarios from database and builds a data structure acceptable by scenario grids for each scenario separately.
|
|
/// Contains capacity data for resources for entire calendar range (used in Mixes).
|
|
/// </summary>
|
|
/// <param name="scenarioIds">A list if scenario Ids.</param>
|
|
/// <param name="userId">Currently logged-in user.</param>
|
|
/// <param name="expandSuperCatgory"></param>
|
|
/// <param name="filterStartDate"></param>
|
|
/// <param name="filterEndDate"></param>
|
|
/// <returns></returns>
|
|
public Dictionary<Guid, Dictionary<string, ExpenditureDetail>> GetFullAllocationInfoByScenario(IEnumerable<Guid> scenarioIds, string userId, bool expandSuperCatgory = false,
|
|
DateTime? filterStartDate = null, DateTime? filterEndDate = null)
|
|
{
|
|
//Logger.Debug("GetFullAllocationInfoByScenario method started");
|
|
if (scenarioIds == null || !scenarioIds.Any())
|
|
throw new ArgumentNullException("scenarioIds");
|
|
|
|
Dictionary<Guid, Dictionary<string, ExpenditureDetail>> result = new Dictionary<Guid, Dictionary<string, ExpenditureDetail>>();
|
|
var fcManager = new FiscalCalendarManager();
|
|
var resourceManager = new PeopleResourcesManager(DbContext);
|
|
var nptManager = new NonProjectTimeManager(DbContext);
|
|
var uomManager = new UOMManager(DbContext);
|
|
|
|
#region Load data from Database
|
|
//Logger.Debug("Load data for aggregation started");
|
|
|
|
#region scenario details
|
|
var allForecastDetails = GetScenarioProxyDetails(null, null, null, scenarioIds).GroupBy(t => t.ParentID.Value).ToDictionary(t => t.Key);
|
|
var scenarios = DbContext.Scenarios.AsNoTracking().Where(t => scenarioIds.Contains(t.Id)).Select(t => new
|
|
{
|
|
ScenarioId = t.Id,
|
|
ProjectId = t.ParentId
|
|
}).ToDictionary(t => t.ScenarioId, el => el.ProjectId);
|
|
var uoms = DbContext.UOMs.ToDictionary(x => x.Id, g => g.UOMValue);
|
|
var expenditureUoms = uomManager.GetUOMsByExpenditureCategory();
|
|
|
|
var scenarioResources =
|
|
(from ra in DbContext.PeopleResourceAllocations
|
|
join pr in DbContext.PeopleResources on ra.PeopleResourceId equals pr.Id
|
|
join ec in DbContext.VW_ExpenditureCategory on pr.ExpenditureCategoryId equals ec.Id
|
|
where scenarioIds.Contains(ra.ScenarioId)
|
|
select new
|
|
{
|
|
RAExpenditureCategoryId = ra.ExpenditureCategoryId,
|
|
ExpenditureCategoryId = ec.Id,
|
|
ExpenditureCategoryName = ec.ExpCategoryWithCcName,
|
|
Type = ec.Type ?? 0,
|
|
UseType = ec.UseType ?? 1,
|
|
CGEFX = ec.CGEFX,
|
|
GLId = ec.GLId,
|
|
UOMId = ec.UOMId,
|
|
CreditId = ec.CreditId,
|
|
SystemAttributeOne = ec.SystemAttributeOne,
|
|
SystemAttributeTwo = ec.SystemAttributeTwo,
|
|
SortOrder = ec.SortOrder,
|
|
AllowResourceAssignment = ec.AllowResourceAssignment,
|
|
})
|
|
.Distinct()
|
|
.ToArray();
|
|
#endregion
|
|
|
|
#region load teams info
|
|
var allTeamModels = GetScenarioTeamsInfo(scenarios, userId);
|
|
var allScenarioTeams = allTeamModels.SelectMany(t => t.Value).Select(t => t.Id).Distinct();
|
|
var expCatsInTeams = (from team in DbContext.Teams
|
|
from detail in DbContext.ScenarioDetail
|
|
where (team.ActualCapacityScenarioId == detail.ParentID || team.PlannedCapacityScenarioId == detail.ParentID) &&
|
|
allScenarioTeams.Contains(team.Id)
|
|
select new { TeamId = team.Id, ExpCatId = detail.ExpenditureCategoryId.Value })
|
|
.Distinct()
|
|
.GroupBy(x => x.ExpCatId)
|
|
.ToDictionary(x => x.Key, g => g.Select(s => s.TeamId).ToList());
|
|
|
|
#endregion
|
|
|
|
#region load people resource info
|
|
var teamResources = resourceManager.LoadPeopleResourcesByTeams(allScenarioTeams);
|
|
var teamResourcesDict = teamResources.GroupBy(t => t.TeamId ?? Guid.Empty).ToDictionary(t => t.Key, el => el.AsEnumerable());
|
|
var allResourceIds = teamResources.Select(t => t.Id).Distinct();
|
|
#endregion
|
|
|
|
#region load allocations
|
|
var resourceAllocations = DbContext.PeopleResourceAllocations.AsNoTracking().Include(t => t.Scenario).Where(x => allResourceIds.Contains(x.PeopleResourceId) &&
|
|
(x.Scenario.Status == (int)ScenarioStatus.Active || scenarioIds.Contains(x.ScenarioId))).ToList();
|
|
var teamAllocations = DbContext.TeamAllocations.AsNoTracking().Include(t => t.Scenario).Where(x => allScenarioTeams.Contains(x.TeamId) &&
|
|
(x.Scenario.Status == (int)ScenarioStatus.Active || scenarioIds.Contains(x.ScenarioId))).ToList();
|
|
|
|
var minResourceStartDate = teamResources.Any() ? teamResources.Min(t => t.StartDate) : DateTime.UtcNow.Date;
|
|
var minResourceEndDate = teamResources.Any() ? teamResources.Max(t => (t.EndDate)) : DateTime.UtcNow.Date;
|
|
var fiscalWeeks = fcManager.GetFiscalCalendar(FiscalCalendarModel.FiscalYearType.Week, minResourceStartDate, minResourceEndDate, true, null, false).ToDictionary(t => t.EndDate, val => val.StartDate);
|
|
var allResHolidays = fcManager.GetHolidayAllocationsByResource(null, null, resourceIds: allResourceIds);
|
|
var npTimeAllocations = nptManager.GetNonProjectTimesByResources(allResourceIds)
|
|
.GroupBy(t => t.PeopleResourceId).ToDictionary(pGr => pGr.Key, pElems => pElems
|
|
.GroupBy(t => t.WeekEndingDate).ToDictionary(wGr => wGr.Key, wElems => wElems.Sum(w => w.HoursOff)));
|
|
var plannedScenarios = allTeamModels.SelectMany(t => t.Value).Where(x => x.PlannedCapacityScenarioId.HasValue).Select(x => new Tuple<Guid, Guid>(x.Id, x.PlannedCapacityScenarioId.Value)).ToList();
|
|
var teamsPlannedCapacity = GetPlanningCapacityAdjusted(plannedScenarios.Select(t => t.Item2).ToList(), null, null, null);
|
|
#endregion
|
|
|
|
//Logger.Debug("Load data for aggregation ended");
|
|
#endregion
|
|
|
|
#region Fill result data model with loaded data
|
|
var bag = new Dictionary<Guid, Dictionary<string, ExpenditureDetail>>();
|
|
foreach (var scenarioId in scenarioIds)
|
|
{
|
|
var scenarioTeamModels = allTeamModels.ContainsKey(scenarioId) ? allTeamModels[scenarioId] : null;
|
|
var scenarioTeamModelIds = scenarioTeamModels.Select(t => t.Id);
|
|
var scenarioTeamsResources = teamResourcesDict.Where(t => scenarioTeamModels.Select(x => x.Id).Contains(t.Key)).SelectMany(t => t.Value).Distinct();
|
|
var scenarioTeamsResourceIds = scenarioTeamsResources.Select(t => t.Id);
|
|
var forecastDetails = allForecastDetails.ContainsKey(scenarioId) ? allForecastDetails[scenarioId] : null;
|
|
var weekEndings = forecastDetails != null ? forecastDetails.Where(x => x.WeekEndingDate.HasValue).Select(x => x.WeekEndingDate.Value).Distinct().ToList() : null;
|
|
var scenarioResourcesHolidays = allResHolidays.Where(t => scenarioTeamsResourceIds.Contains(t.Key)).ToDictionary(t => t.Key, el => el.Value);
|
|
var scenarioResourcesNPTAllocations = npTimeAllocations.Where(t => scenarioTeamsResourceIds.Contains(t.Key)).ToDictionary(t => t.Key, el => el.Value);
|
|
var plannedCapacityScenarios = plannedScenarios.Where(x => scenarioTeamModelIds.Contains(x.Item1)).Select(t => t.Item2);
|
|
var scenarioTeamsPlannedCapacity = teamsPlannedCapacity.Where(t => plannedCapacityScenarios.Contains(t.Key)).ToDictionary(t => t.Key, el => el.Value);
|
|
var expCatsInScenarioTeams = expCatsInTeams.ToDictionary(t => t.Key, el => el.Value.Where(elTeam => scenarioTeamModelIds.Contains(elTeam)).ToList());
|
|
Dictionary<string, ExpenditureDetail> expenditures = null;
|
|
if (forecastDetails != null && weekEndings != null)
|
|
expenditures = BuildExpendituresForScenario(weekEndings, forecastDetails.ToList(), null, uoms);
|
|
if (!scenarios.ContainsKey(scenarioId))
|
|
bag.Add(scenarioId, expenditures);
|
|
var projectId = scenarios[scenarioId];
|
|
|
|
#region expand calculated expenditures to the result dataset
|
|
IEnumerable<Guid> superCategoryIds = null;
|
|
if (expandSuperCatgory)
|
|
{
|
|
superCategoryIds = expenditures
|
|
.Where(x => x.Value.AllowResourceAssignment == false)
|
|
.Select(x => x.Value.ExpenditureCategoryId)
|
|
.ToList();
|
|
var query = scenarioResources.Where(t => superCategoryIds.Contains(t.RAExpenditureCategoryId)
|
|
&& !expenditures.ContainsKey(t.ExpenditureCategoryId.ToString())).Distinct()
|
|
.ToArray()
|
|
.Where(x => !expenditures.ContainsKey(x.ExpenditureCategoryId.ToString()))
|
|
.Select(x => new ExpenditureDetail()
|
|
{
|
|
ExpenditureCategoryId = x.ExpenditureCategoryId,
|
|
ExpenditureCategoryName = x.ExpenditureCategoryName,
|
|
Type = x.Type,
|
|
UseType = x.UseType,
|
|
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],
|
|
AllowResourceAssignment = x.AllowResourceAssignment,
|
|
});
|
|
foreach (var category in query)
|
|
{
|
|
expenditures.Add(category.ExpenditureCategoryId.ToString(), category);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region prepare allocations
|
|
var scenarioResourceAllocations = resourceAllocations.Where(x => scenarioTeamsResourceIds.Contains(x.PeopleResourceId) &&
|
|
((x.Scenario.Status == (int)ScenarioStatus.Active && x.Scenario.ParentId != projectId) || x.ScenarioId == scenarioId));
|
|
var resourceAllocationsDict = scenarioResourceAllocations
|
|
.GroupBy(t => t.TeamId).ToDictionary(tGr => tGr.Key, tElems => tElems
|
|
.GroupBy(t => t.WeekEndingDate).ToDictionary(wGr => wGr.Key, wElems => wElems
|
|
.GroupBy(t => t.PeopleResourceId).ToDictionary(rGr => rGr.Key, rElems => rElems
|
|
.GroupBy(t => t.ExpenditureCategoryId).ToDictionary(ecGr => ecGr.Key, ecElems => new AllocationItem
|
|
{
|
|
CurrentScenarioQuantity = ecElems.Where(ra => ra.ScenarioId == scenarioId).Sum(t => t.Quantity),
|
|
OtherScenariosQuantity = ecElems.Where(ra => ra.ScenarioId != scenarioId).Sum(t => t.Quantity)
|
|
}))));
|
|
var scenarioResourcesByTeam = scenarioResourceAllocations.Where(t => t.ScenarioId == scenarioId)
|
|
.GroupBy(t => t.TeamId).ToDictionary(tGr => tGr.Key, tElems => tElems
|
|
.GroupBy(t => t.ExpenditureCategoryId).ToDictionary(ecGr => ecGr.Key, ecElems => ecElems.Select(t => t.PeopleResourceId).Distinct()));
|
|
|
|
var teamAllocationsDict = teamAllocations.Where(x => scenarioTeamModelIds.Contains(x.TeamId) &&
|
|
((x.Scenario.Status == (int)ScenarioStatus.Active && x.Scenario.ParentId != projectId) || x.ScenarioId == scenarioId))
|
|
.GroupBy(t => t.TeamId).ToDictionary(tGr => tGr.Key, tElems => tElems
|
|
.GroupBy(t => t.WeekEndingDate).ToDictionary(wGr => wGr.Key, wElems => wElems
|
|
.GroupBy(t => t.ExpenditureCategoryId).ToDictionary(ecGr => ecGr.Key, ecElems => new AllocationItem
|
|
{
|
|
CurrentScenarioQuantity = ecElems.Where(ra => ra.ScenarioId == scenarioId).Sum(t => t.Quantity),
|
|
OtherScenariosQuantity = ecElems.Where(ra => ra.ScenarioId != scenarioId).Sum(t => t.Quantity)
|
|
})));
|
|
#endregion
|
|
|
|
//Logger.Debug("FillTeamsDetails method started");
|
|
BuildTeamsDetails(scenarioId, projectId ?? Guid.Empty, expenditures, userId, false,
|
|
resourceAllocationsDict, expCatsInScenarioTeams, teamAllocationsDict, fiscalWeeks, scenarioTeamModels, scenarioResourcesByTeam, scenarioResourcesNPTAllocations,
|
|
scenarioResourcesHolidays, scenarioTeamsPlannedCapacity, scenarioTeamsResources, expenditureUoms, filterStartDate, filterEndDate);
|
|
//Logger.Debug("FillTeamsDetails method completed");
|
|
|
|
#region rollup calculated expenditures values from children expenditures
|
|
if (expandSuperCatgory && superCategoryIds != null)
|
|
{
|
|
var superCategories = expenditures
|
|
.Where(x => superCategoryIds.Contains(Guid.Parse(x.Key)))
|
|
.ToArray();
|
|
|
|
foreach (var superCategory in superCategories.Select(x => x.Value))
|
|
{
|
|
foreach (var superTeam in superCategory.Teams.Select(x => x.Value))
|
|
{
|
|
foreach (var superResource in superTeam.Resources.Select(x => x.Value))
|
|
{
|
|
if (superResource.ExpenditureCategoryId != superCategory.ExpenditureCategoryId)
|
|
{
|
|
var resources = expenditures[superResource.ExpenditureCategoryId.ToString()].Teams[superTeam.Id.ToString()].Resources;
|
|
if (!resources.ContainsKey(superResource.Id.ToString()))
|
|
{
|
|
resources.Add(superResource.Id.ToString(), superResource);
|
|
}
|
|
else
|
|
{
|
|
var resource = resources[superResource.Id.ToString()];
|
|
foreach (var weekEnding in superResource.QuantityValues.Keys)
|
|
{
|
|
resource.QuantityValues[weekEnding] += superResource.QuantityValues[weekEnding];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
expenditures.Remove(superCategory.ExpenditureCategoryId.ToString());
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
bag.Add(scenarioId, expenditures);
|
|
}
|
|
result = bag.ToDictionary(t => t.Key, el => el.Value);
|
|
#endregion
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// The method fills expenditure category models with data passed through parameters.
|
|
/// </summary>
|
|
/// <param name="scenarioId">Scenario.Id.</param>
|
|
/// <param name="parentId">Project ID for to which scenario related. Do not remove this parameter from the method, because we can not take this parameter from the scenario when we are creating new one</param>
|
|
/// Temp solution, will be removed after teams and ECs will be add on client side.
|
|
/// If is true team & resource allocations for current scenario do not include in calculation.
|
|
/// It is useful in case when user removes EC from scenario and add it back again. So database still contains these data, but we should not show them to user
|
|
/// </param>
|
|
/// <param name="expenditures"></param>
|
|
/// <param name="userId"></param>
|
|
/// <param name="ignoreCurrentScenario"></param>
|
|
/// <param name="resourceAllocationsDict">Resource allocations grouped into tree-like structure. Key1=TeamId, Key2=Week, Key3=PeopleResourceId, Key4=ExpenditureCategoryId.
|
|
/// Value=AllocationItem struct with info about current scenario and other scenarios.</param>
|
|
/// <param name="teamAllocationsDict">Team allocations grouped into tree-like structure. Key1=TeamId, Key2=Week, Key3=ExpenditureCategoryId.
|
|
/// Value=AllocationItem struct with info about current scenario and other scenarios.</param>
|
|
/// <param name="scenarioResourcesAllocated">Resource Ids grouped by Team.Id and then by ExpenditureCateogry.Id for those resources which have allocations.</param>
|
|
/// <param name="nptAllocations">NPT allocations grouped by PeopleResource.Id then by week. Value is HoursOff of NPT allocation.</param>
|
|
/// <param name="holidayAllocations">Weekly holiday allocations grouped by PeopleResourceId and then by weekending date.</param>
|
|
/// <param name="teamsPlannedCapacity">Tree-structure of scenario details: Scenarios[ScenarioId, Expenditure Categories[ExpCatId, ScenarioDetails]].
|
|
/// Quantity values adjusted based on Holidays</param>
|
|
/// <param name="teamsWithAllocation">Collection of teams with percentage allocation for the scenario</param>
|
|
/// <param name="filterStartDate"></param>
|
|
/// <param name="filterEndDate"></param>
|
|
/// <returns></returns>
|
|
public List<TeamInScenarioModel> BuildTeamsDetails(Guid scenarioId, Guid parentId, Dictionary<string, ExpenditureDetail> expenditures, string userId, bool ignoreCurrentScenario,
|
|
Dictionary<Guid, Dictionary<DateTime, Dictionary<Guid, Dictionary<Guid, AllocationItem>>>> resourceAllocationsDict, Dictionary<Guid, List<Guid>> expCatsInTeams,
|
|
Dictionary<Guid, Dictionary<DateTime, Dictionary<Guid, AllocationItem>>> teamAllocationsDict, Dictionary<DateTime, DateTime> fiscalWeeks, List<ScenarioTeamInfoModel> teamsInfo,
|
|
Dictionary<Guid, Dictionary<Guid, IEnumerable<Guid>>> scenarioResourcesAllocated, Dictionary<Guid, Dictionary<DateTime, int>> nptAllocations,
|
|
Dictionary<Guid, Dictionary<DateTime, decimal>> holidayAllocations, Dictionary<Guid, Dictionary<Guid, List<ScenarioDetailWithProxyItemModel>>> teamsPlannedCapacity,
|
|
IEnumerable<PeopleResourceModel> teamResources, Dictionary<Guid, decimal> ecUOMs, DateTime? filterStartDate = null, DateTime? filterEndDate = null)
|
|
{
|
|
#region Prepare data
|
|
|
|
//Logger.Debug(string.Format("FillTeamsDetails method started to load and prepare data for scenario ({0})", scenarioId));
|
|
|
|
var allTeamResources = new Dictionary<Guid, List<PeopleResourceModel>>();
|
|
foreach (var item in teamResources)
|
|
{
|
|
if (item.TeamId.HasValue)
|
|
{
|
|
if (!allTeamResources.ContainsKey(item.TeamId.Value))
|
|
{
|
|
allTeamResources.Add(item.TeamId.Value, new List<PeopleResourceModel> { item });
|
|
}
|
|
else
|
|
{
|
|
allTeamResources[item.TeamId.Value].Add(item);
|
|
}
|
|
var modelTeam = teamsInfo.FirstOrDefault(t => t.Id == item.TeamId.Value);
|
|
if (modelTeam != null)
|
|
{
|
|
modelTeam.Resources.Add(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
var requiredTeams = teamsInfo.Select(x => x.Id).ToList();
|
|
|
|
// a list of all team resource
|
|
var allTeamResourceList = allTeamResources.SelectMany(t => t.Value).Distinct().ToList();
|
|
|
|
var startDate = allTeamResourceList.Count > 0 ? allTeamResourceList.Min(t => t.StartDate) : DateTime.UtcNow.Date;
|
|
var endDate = allTeamResourceList.Count > 0 ? allTeamResourceList.Max(t => (t.EndDate)) : DateTime.UtcNow.Date;
|
|
|
|
var allResIds = allTeamResourceList.Select(t => t.Id).Distinct();
|
|
// 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 resourcesAllocationWeekEnding = resourceAllocationsDict.SelectMany(x => x.Value.Keys).Distinct();
|
|
var teamsAllocationWeekEnding = teamAllocationsDict.SelectMany(x => x.Value.Keys).Distinct();
|
|
|
|
//Logger.Debug("FillTeamsDetails method finished to load and prepare data");
|
|
#endregion
|
|
|
|
#region Preparing team details
|
|
|
|
//Logger.Debug("FillTeamsDetails method started to fill expenditures info");
|
|
|
|
if (expenditures != null && expenditures.Count > 0)
|
|
{
|
|
foreach (var ec in expenditures)
|
|
{
|
|
if (!ec.Value.AllowResourceAssignment)
|
|
{
|
|
var expCatId = Guid.Parse(ec.Key);
|
|
if (!expCatsInTeams.ContainsKey(expCatId))
|
|
{
|
|
List<Guid> tid = new List<Guid>();
|
|
foreach (var t in teamsInfo)
|
|
{
|
|
if (!tid.Contains(t.Id))
|
|
tid.Add(t.Id);
|
|
}
|
|
expCatsInTeams.Add(expCatId, tid);
|
|
}
|
|
|
|
}
|
|
}
|
|
var scenarioWeekEndings = expenditures.SelectMany(t => t.Value.Details.Select(dt => dt.Key)).Distinct().OrderBy(o => o);
|
|
// load capacity adjusted by holidays
|
|
var requiredCategories = expCatsInTeams.Select(x => x.Key).ToList();
|
|
// we need all holidays and capacity, but not only for scenario range, example:
|
|
// we have scenario 01.01.2016 - 31.01.2016
|
|
// we change scenario dates to 01.03.2016 - 31.03.2016
|
|
// we shift scenario details, team & resource allocations to 01.03.2016 - 31.03.2016 as well
|
|
// but we do not affect capacity and rest capacity collections by shifting, so we should to store all capacity and rest capacity for teams and resources
|
|
var teamsPlannedCapacityWeekEndings = teamsPlannedCapacity.SelectMany(x => x.Value.SelectMany(s => s.Value))
|
|
.Where(x => x.WeekEndingDate.HasValue)
|
|
.Select(x => x.WeekEndingDate.Value).ToArray();
|
|
|
|
var allWeekEndings = scenarioWeekEndings.Select(x => Utils.ConvertFromUnixDate(long.Parse(x)))
|
|
.Union(resourcesAllocationWeekEnding)
|
|
.Union(teamsAllocationWeekEnding)
|
|
.Union(teamsPlannedCapacityWeekEndings)
|
|
.OrderBy(x => x)
|
|
.ToArray();
|
|
|
|
foreach (var ec in expenditures)
|
|
{
|
|
var expenditureCategoryId = Guid.Parse(ec.Key);
|
|
if (!expCatsInTeams.ContainsKey(expenditureCategoryId))
|
|
continue;
|
|
|
|
var teams = teamsInfo.FindAll(x => expCatsInTeams[expenditureCategoryId].Contains(x.Id));
|
|
foreach (var team in teams)
|
|
{
|
|
var resourcesInTeam = team.Resources.Where(x => x.ExpenditureCategoryId == expenditureCategoryId || !ec.Value.AllowResourceAssignment)
|
|
.Distinct().Select(x => new TeamResourceSortable
|
|
{
|
|
Id = x.Id,
|
|
ExpenditureCategoryId = x.ExpenditureCategoryId,
|
|
Name = x.FirstName + " " + x.LastName,
|
|
LastName = x.LastName,
|
|
IsActiveEmployee = x.IsActiveEmployee,
|
|
StartDate = Utils.ConvertToUnixDate(x.StartDate),
|
|
EndDate = x.EndDate.HasValue ? Utils.ConvertToUnixDate(x.EndDate.Value) : (long?)null,
|
|
Allocation = x.TeamAllocation,
|
|
Teams = x.Teams
|
|
}).ToList();
|
|
var teamDetails = new ExpenditureDetailsTeam()
|
|
{
|
|
Id = team.Id,
|
|
Name = team.Name,
|
|
CanBeDeleted = team.CanBeDeleted,
|
|
IsAccessible = team.IsAccessible
|
|
};
|
|
var teamResAllocationsDict = resourceAllocationsDict.ContainsKey(team.Id) ? resourceAllocationsDict[team.Id] : new Dictionary<DateTime, Dictionary<Guid, Dictionary<Guid, AllocationItem>>>();
|
|
var teamAllocations4Team = teamAllocationsDict.ContainsKey(team.Id) ? teamAllocationsDict[team.Id] : null;
|
|
var teamPlannedCapacity = team.PlannedCapacityScenarioId.HasValue && teamsPlannedCapacity.ContainsKey(team.PlannedCapacityScenarioId.Value) ?
|
|
teamsPlannedCapacity[team.PlannedCapacityScenarioId.Value] : null;
|
|
var ecTeamPlannedCapacity = teamPlannedCapacity != null && teamPlannedCapacity.ContainsKey(expenditureCategoryId)
|
|
? teamPlannedCapacity[expenditureCategoryId].GroupBy(t => t.WeekEndingDate.Value).ToDictionary(t => t.Key, el => el.FirstOrDefault()) : null;
|
|
|
|
// this loop uses only scenario range allocations and only for current scenario
|
|
foreach (var weekEndingKey in scenarioWeekEndings)
|
|
{
|
|
var detail = ec.Value.Details.ContainsKey(weekEndingKey) ? ec.Value.Details[weekEndingKey] : null;
|
|
var weekEnding = Utils.ConvertFromUnixDate(long.Parse(weekEndingKey));
|
|
var weekResAllocationsDict = teamResAllocationsDict.ContainsKey(weekEnding) ? teamResAllocationsDict[weekEnding] : new Dictionary<Guid, Dictionary<Guid, AllocationItem>>();
|
|
|
|
#region Filling resource quantity according to allocation in current scenario
|
|
|
|
foreach (var resource in resourcesInTeam)
|
|
{
|
|
var resWeekAllocations = weekResAllocationsDict.ContainsKey(resource.Id) ? weekResAllocationsDict[resource.Id] : new Dictionary<Guid, AllocationItem>();
|
|
var allocationInCurrentScenario = resWeekAllocations.ContainsKey(expenditureCategoryId) ? resWeekAllocations[expenditureCategoryId].CurrentScenarioQuantity : 0;
|
|
// we should show original value even if resource is unavailable on this week (for ability to see corrupted data)
|
|
if (ignoreCurrentScenario)
|
|
resource.QuantityValues.Add(weekEndingKey, 0);
|
|
else
|
|
resource.QuantityValues.Add(weekEndingKey, Math.Max(allocationInCurrentScenario, 0));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Calculation team's allocation in current scenario and rest quantity according to team's allocation in all active scenarios
|
|
var teamAllocation4WeekDict = teamAllocations4Team != null && teamAllocations4Team.ContainsKey(weekEnding) ? teamAllocations4Team[weekEnding] : null;
|
|
var teamAllocationInScenario = teamAllocation4WeekDict != null && teamAllocation4WeekDict.ContainsKey(expenditureCategoryId) ?
|
|
teamAllocation4WeekDict[expenditureCategoryId].CurrentScenarioQuantity : (decimal?)null;
|
|
|
|
var teamQuantity = 0M;
|
|
if (teamAllocationInScenario != null && !ignoreCurrentScenario)
|
|
{
|
|
teamQuantity = teamAllocationInScenario ?? 0;
|
|
}
|
|
else
|
|
{
|
|
// by default team should take quantity value according to team 2 scenario allocation
|
|
teamQuantity = (ignoreCurrentScenario || detail == null) ? 0 : ((detail.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;
|
|
}
|
|
teamDetails.QuantityValues.Add(weekEndingKey, Math.Max(teamQuantity, 0));
|
|
|
|
#endregion
|
|
}
|
|
//8.24.16 start tab
|
|
if (filterStartDate.HasValue && filterEndDate.HasValue)
|
|
{
|
|
var StartWE = FiscalCalendarManager.GetFiscalCalendarWeekForDate(filterStartDate.Value, new EnVisageEntities());
|
|
var EndWE = FiscalCalendarManager.GetFiscalCalendarWeekForDate(filterEndDate.Value, new EnVisageEntities());
|
|
allWeekEndings = allWeekEndings.Where(x => x >= StartWE.StartDate && x <= EndWE.EndDate).ToArray();
|
|
}
|
|
//8.24.16 end
|
|
foreach (var weekEnding in allWeekEndings)
|
|
{
|
|
var weekEndingKey = Utils.ConvertToUnixDate(weekEnding).ToString();
|
|
var weekResAllocationsDict = teamResAllocationsDict.ContainsKey(weekEnding) ? teamResAllocationsDict[weekEnding] : null;
|
|
var teamAllocation4WeekDict = teamAllocations4Team != null && teamAllocations4Team.ContainsKey(weekEnding) ? teamAllocations4Team[weekEnding] : null;
|
|
|
|
#region Filling resource capacity and quantity according to allocation in current scenario
|
|
|
|
foreach (var resource in resourcesInTeam)
|
|
{
|
|
var weekStartDate = fiscalWeeks.ContainsKey(weekEnding) ? fiscalWeeks[weekEnding] : weekEnding;
|
|
var resWeekAllocations = weekResAllocationsDict != null && weekResAllocationsDict.ContainsKey(resource.Id) ? weekResAllocationsDict[resource.Id] : null;
|
|
|
|
var allocationInCurrentScenario = resWeekAllocations != null ? resWeekAllocations.Values.Sum(t => t.CurrentScenarioQuantity) : 0;
|
|
// sum total of resource allocations in this week except of current scenario
|
|
var allocationInOtherScenarios = resWeekAllocations != null ? resWeekAllocations.Values.Sum(t => t.OtherScenariosQuantity) : 0;
|
|
// if resource doesn't exist in this period we should fill all quantities with 0
|
|
// consider only full weeks as work weeks
|
|
var isReadOnlyResource = weekStartDate < Utils.ConvertFromUnixDate(resource.StartDate) ||
|
|
(resource.EndDate.HasValue && weekEnding > Utils.ConvertFromUnixDate(resource.EndDate.Value));
|
|
if (isReadOnlyResource)
|
|
{
|
|
resource.CapacityQuantityValues.Add(weekEndingKey, 0);
|
|
resource.ReadOnly.Add(weekEndingKey, true);
|
|
}
|
|
else
|
|
{
|
|
// if there are any non-project time scheduled for this resource in this week then we should reduce capacity by the amount of this NP time
|
|
var rNpt = nptAllocations.ContainsKey(resource.Id) ? nptAllocations[resource.Id] : null;
|
|
var npTime = rNpt != null && rNpt.ContainsKey(weekEnding) ? rNpt[weekEnding] : 0;
|
|
// if there are any holidays for this resource in this week then we should adjust capacity accordingly
|
|
var holidayKoeff = holidayAllocations.ContainsKey(resource.Id) && holidayAllocations[resource.Id].ContainsKey(weekEnding) ? holidayAllocations[resource.Id][weekEnding] : 1;
|
|
// reduce capacity by the amount of resource 2 team involvment
|
|
var uomValue = ec.Value.UOMValue;
|
|
if (resource.ExpenditureCategoryId.ToString() != ec.Key)
|
|
{
|
|
if (expenditures.ContainsKey(resource.ExpenditureCategoryId.ToString()))
|
|
uomValue = expenditures[resource.ExpenditureCategoryId.ToString()].UOMValue;
|
|
else
|
|
{
|
|
if (ecUOMs != null && ecUOMs.ContainsKey(resource.ExpenditureCategoryId))
|
|
uomValue = ecUOMs[resource.ExpenditureCategoryId];
|
|
}
|
|
}
|
|
var capacity = resource.Allocation * (uomValue * holidayKoeff - npTime) / 100.0M;
|
|
resource.CapacityQuantityValues.Add(weekEndingKey, Math.Max(capacity, 0));
|
|
bool isEditable = (resource.Teams != null) && resource.Teams.Any(x => x.IsInRange(weekEnding));
|
|
resource.ReadOnly.Add(weekEndingKey, !isEditable);
|
|
}
|
|
|
|
// it is OK to have a negative value for the rest quantity
|
|
resource.RestQuantityValues.Add(weekEndingKey, resource.CapacityQuantityValues[weekEndingKey] - allocationInOtherScenarios);
|
|
if (!ignoreCurrentScenario)
|
|
resource.RestQuantityValues[weekEndingKey] -= allocationInCurrentScenario;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Calculation team's allocation in current scenario and rest quantity according to team's allocation in all active scenarios
|
|
|
|
if (ec.Value.AllowResourceAssignment)
|
|
{
|
|
decimal teamCapacity = 0;
|
|
if (weekEnding < DateTime.UtcNow.Date)
|
|
{
|
|
teamCapacity = resourcesInTeam.Sum(x => x.CapacityQuantityValues[weekEndingKey]);
|
|
}
|
|
else if (ecTeamPlannedCapacity != null)
|
|
{
|
|
var plannedCapacityItem = ecTeamPlannedCapacity.ContainsKey(weekEnding) ? ecTeamPlannedCapacity[weekEnding] : null;
|
|
if (plannedCapacityItem != null)
|
|
teamCapacity = plannedCapacityItem.Quantity;
|
|
}
|
|
var teamAllocationInOtherScenarios = teamAllocation4WeekDict != null && teamAllocation4WeekDict.ContainsKey(expenditureCategoryId) ?
|
|
teamAllocation4WeekDict[expenditureCategoryId].OtherScenariosQuantity : 0;
|
|
// calculate resource allocation by super ECs
|
|
var resourceAllocationInSuperECs = weekResAllocationsDict != null ? weekResAllocationsDict.Where(res => resourcesInTeam.Any(r => r.Id == res.Key))
|
|
.SelectMany(t => t.Value).Where(exp => exp.Key != expenditureCategoryId).Sum(t => t.Value.CurrentScenarioQuantity + t.Value.OtherScenariosQuantity) : 0;
|
|
|
|
var teamQuantity = teamDetails.QuantityValues.ContainsKey(weekEndingKey) ? teamDetails.QuantityValues[weekEndingKey] : 0M;
|
|
var restQuantity = teamCapacity - teamAllocationInOtherScenarios - teamQuantity - resourceAllocationInSuperECs;
|
|
|
|
teamDetails.CapacityQuantityValues.Add(weekEndingKey, Math.Max(teamCapacity, 0));
|
|
|
|
// it is OK to have a negative value for the rest quantity
|
|
teamDetails.RestQuantityValues.Add(weekEndingKey, restQuantity);
|
|
}
|
|
else
|
|
{
|
|
//// super ECs do not have capacity at all
|
|
//teamDetails.CapacityQuantityValues.Add(weekEndingKey, 0);
|
|
//teamDetails.RestQuantityValues.Add(weekEndingKey, 0);
|
|
decimal teamCapacity = 0;
|
|
if (weekEnding < DateTime.UtcNow.Date)
|
|
{
|
|
teamCapacity = resourcesInTeam.Sum(x => x.CapacityQuantityValues[weekEndingKey]);
|
|
}
|
|
else
|
|
{
|
|
if (teamPlannedCapacity != null)
|
|
{
|
|
foreach (var teamPlannedCapByEC in teamPlannedCapacity)
|
|
{
|
|
var plannedECCapacityItem = teamPlannedCapByEC.Value.FirstOrDefault(x => x.WeekEndingDate == weekEnding);
|
|
if (plannedECCapacityItem != null)
|
|
teamCapacity += plannedECCapacityItem.Quantity;
|
|
}
|
|
}
|
|
}
|
|
|
|
var teamAllocationInOtherScenarios = teamAllocation4WeekDict != null ?
|
|
teamAllocation4WeekDict.Sum(t => t.Value.OtherScenariosQuantity) : 0;
|
|
|
|
// calculate resource allocation by super ECs
|
|
var resourceAllocationInSuperECs = weekResAllocationsDict != null ? weekResAllocationsDict.Where(res => resourcesInTeam.Any(r => r.Id == res.Key))
|
|
.SelectMany(t => t.Value).Where(exp => exp.Key != expenditureCategoryId).Sum(t => t.Value.CurrentScenarioQuantity + t.Value.OtherScenariosQuantity) : 0;
|
|
var teamQuantity = teamDetails.QuantityValues.ContainsKey(weekEndingKey) ? teamDetails.QuantityValues[weekEndingKey] : 0M;
|
|
var restQuantity = teamCapacity - teamAllocationInOtherScenarios - teamQuantity - resourceAllocationInSuperECs;
|
|
|
|
teamDetails.CapacityQuantityValues.Add(weekEndingKey, Math.Max(teamCapacity, 0));
|
|
|
|
// it is OK to have a negative value for the rest quantity
|
|
teamDetails.RestQuantityValues.Add(weekEndingKey, restQuantity);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
#region Fill team.Resources and team.AllResources collections
|
|
var scenarioResources = scenarioResourcesAllocated.ContainsKey(teamDetails.Id) ?
|
|
scenarioResourcesAllocated[teamDetails.Id].ContainsKey(expenditureCategoryId) ?
|
|
scenarioResourcesAllocated[teamDetails.Id][expenditureCategoryId] :
|
|
null :
|
|
null;
|
|
teamDetails.Resources = resourcesInTeam.FindAll(x => scenarioResources != null && scenarioResources.Contains(x.Id))
|
|
.Select(x => (TeamResource)x)
|
|
.ToDictionary(x => x.Id.ToString());
|
|
if (ignoreCurrentScenario)
|
|
{
|
|
foreach (var resource in teamDetails.Resources)
|
|
{
|
|
resource.Value.Changed = true;
|
|
resource.Value.Deleted = true;
|
|
}
|
|
}
|
|
teamDetails.AllResources = resourcesInTeam.OrderBy(x => x.LastName)
|
|
.Select(x => (TeamResource)x)
|
|
.ToDictionary(x => x.Id.ToString());
|
|
if (!ec.Value.Teams.ContainsKey(teamDetails.Id.ToString()))
|
|
ec.Value.Teams.Add(teamDetails.Id.ToString(), teamDetails);
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
}
|
|
|
|
//Logger.Debug("FillTeamsDetails method finished to fill expenditures info");
|
|
|
|
#endregion
|
|
|
|
return teamsInfo.Select(x => new TeamInScenarioModel
|
|
{
|
|
TeamId = x.Id,
|
|
IsNew = false,
|
|
Allocation = (short)x.Allocation,
|
|
IsAccessible = x.IsAccessible,
|
|
CanBeDeleted = x.CanBeDeleted
|
|
}).ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// The method fills expenditure categories with information about teams
|
|
/// </summary>
|
|
/// <param name="scenarioId"></param>
|
|
/// <param name="expenditures"></param>
|
|
/// <param name="userId"></param>
|
|
/// <param name="ignoreCurrentScenario">
|
|
/// <param name="parentId">Project ID for to which scenario related. Do not remove this parameter from the method, because we can not take this parameter from the scenario when we are creating new one</param>
|
|
/// Temp solution, will be removed after teams and ECs will be add on client side.
|
|
/// If is true team & resource allocations for current scenario do not include in calculation.
|
|
/// It is useful in case when user removes EC from scenario and add it back again. So database still contains these data, but we should not show them to user
|
|
/// </param>
|
|
/// <param name="teamsWithAllocation">Collection of teams with percentage allocation for the scenario</param>
|
|
/// <returns></returns>
|
|
//8.24.16 start tab
|
|
public List<TeamInScenarioModel> FillTeamsDetails(Guid scenarioId, Guid parentId, Dictionary<string, ExpenditureDetail> expenditures, string userId, bool ignoreCurrentScenario, Dictionary<Guid, short> teamsWithAllocation = null, DateTime? filterStartDate = null, DateTime? filterEndDate = null)
|
|
//public List<TeamInScenarioModel> FillTeamsDetails(Guid scenarioId, Guid parentId, Dictionary<string, ExpenditureDetail> expenditures, string userId, bool ignoreCurrentScenario, Dictionary<Guid, short> teamsWithAllocation = null)
|
|
//8.24.16 end
|
|
{
|
|
var prManager = new PeopleResourcesManager(DbContext);
|
|
var teamManager = new TeamManager(DbContext);
|
|
var nptManager = new NonProjectTimeManager(DbContext);
|
|
var resourceManager = new PeopleResourcesManager(DbContext);
|
|
|
|
#region Retrieving necessary information from the database
|
|
|
|
//Logger.Debug(string.Format("FillTeamsDetails method started to load teams info for scenario ({0})", scenarioId));
|
|
var teamsInfo = new List<ScenarioTeamInfoModel>();
|
|
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 = GetScenarioTeamsInfo(parentId, scenarioId, userId);
|
|
}
|
|
else
|
|
{
|
|
teamsInfo = GetScenarioTeamsInfo(teamsWithAllocation, parentId, scenarioId, userId);
|
|
}
|
|
//Logger.Debug("FillTeamsDetails method finished to load teams info");
|
|
var allTeamResources = new Dictionary<Guid, List<PeopleResourceModel>>();
|
|
var resourcesByTeams = resourceManager.LoadPeopleResourcesByTeams(teamsInfo.Select(t => t.Id));
|
|
|
|
foreach (var item in resourcesByTeams)
|
|
{
|
|
if (item.TeamId.HasValue)
|
|
{
|
|
if (!allTeamResources.ContainsKey(item.TeamId.Value))
|
|
{
|
|
allTeamResources.Add(item.TeamId.Value, new List<PeopleResourceModel> { item });
|
|
}
|
|
else
|
|
{
|
|
allTeamResources[item.TeamId.Value].Add(item);
|
|
}
|
|
var modelTeam = teamsInfo.FirstOrDefault(t => t.Id == item.TeamId.Value);
|
|
if (modelTeam != null)
|
|
{
|
|
modelTeam.Resources.Add(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
//Logger.Debug("FillTeamsDetails method started to load expcatsinteams info");
|
|
#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
|
|
//Logger.Debug("FillTeamsDetails method finished to load expcatsinteams info");
|
|
|
|
// a list of all team resource
|
|
var allTeamResourceList = allTeamResources.SelectMany(t => t.Value).Distinct().ToList();
|
|
|
|
var startDate = allTeamResourceList.Count > 0 ? allTeamResourceList.Min(t => t.StartDate) : DateTime.UtcNow.Date;
|
|
var endDate = allTeamResourceList.Count > 0 ? allTeamResourceList.Max(t => (t.EndDate)) : DateTime.UtcNow.Date;
|
|
var fcManager = new FiscalCalendarManager();
|
|
var fc = fcManager.GetFiscalCalendar(FiscalCalendarModel.FiscalYearType.Week, startDate, endDate, true, null, false).ToDictionary(t => t.EndDate, val => val.StartDate);
|
|
|
|
var allResIds = allTeamResourceList.Select(t => t.Id).Distinct();
|
|
//Logger.Debug("FillTeamsDetails method started to load NPT info");
|
|
var npTimeAllocations = nptManager.GetNonProjectTimesByResources(allResIds).GroupBy(t => t.PeopleResourceId).ToDictionary(pGr => pGr.Key, pElems => pElems
|
|
.GroupBy(t => t.WeekEndingDate).ToDictionary(wGr => wGr.Key, wElems => wElems.Sum(w => w.HoursOff)));
|
|
//Logger.Debug("FillTeamsDetails method finished to load NPT info");
|
|
|
|
// 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
|
|
//Logger.Debug("FillTeamsDetails method started to load resource & teams allocations info");
|
|
// for entire calendar!!!
|
|
var resourcesAllocation = DbContext.PeopleResourceAllocations.AsNoTracking().Where(x => allResIds.Contains(x.PeopleResourceId) &&
|
|
((x.Scenario.Status == (int)ScenarioStatus.Active && x.Scenario.ParentId != parentId) || x.ScenarioId == scenarioId))
|
|
.ToList();
|
|
var resourceAllocationsDict = resourcesAllocation
|
|
.GroupBy(t => t.TeamId).ToDictionary(tGr => tGr.Key, tElems => tElems
|
|
.GroupBy(t => t.WeekEndingDate).ToDictionary(wGr => wGr.Key, wElems => wElems
|
|
.GroupBy(t => t.PeopleResourceId).ToDictionary(rGr => rGr.Key, rElems => rElems
|
|
.GroupBy(t => t.ExpenditureCategoryId).ToDictionary(ecGr => ecGr.Key, ecElems => new AllocationItem
|
|
{
|
|
CurrentScenarioQuantity = ecElems.Where(ra => ra.ScenarioId == scenarioId).Sum(t => t.Quantity),
|
|
OtherScenariosQuantity = ecElems.Where(ra => ra.ScenarioId != scenarioId).Sum(t => t.Quantity)
|
|
}))));
|
|
var resourcesAllocationWeekEnding = resourcesAllocation.Select(x => x.WeekEndingDate).Distinct().ToArray();
|
|
//var resourcesAllocationWeekEnding = resourceAllocationsDict.SelectMany(x => x.Value.Keys).Distinct().ToArray();
|
|
// for entire calendar!!!
|
|
var teamsAllocation = DbContext.TeamAllocations.AsNoTracking().Where(x => requiredTeams.Contains(x.TeamId) &&
|
|
((x.Scenario.Status == (int)ScenarioStatus.Active && x.Scenario.ParentId != parentId) || x.ScenarioId == scenarioId))
|
|
.ToList();
|
|
var teamAllocationsDict = teamsAllocation.GroupBy(t => t.TeamId).ToDictionary(tGr => tGr.Key, tElems => tElems
|
|
.GroupBy(t => t.WeekEndingDate).ToDictionary(wGr => wGr.Key, wElems => wElems
|
|
.GroupBy(t => t.ExpenditureCategoryId).ToDictionary(ecGr => ecGr.Key, ecElems => new AllocationItem
|
|
{
|
|
CurrentScenarioQuantity = ecElems.Where(ra => ra.ScenarioId == scenarioId).Sum(t => t.Quantity),
|
|
OtherScenariosQuantity = ecElems.Where(ra => ra.ScenarioId != scenarioId).Sum(t => t.Quantity)
|
|
})));
|
|
var teamsAllocationWeekEnding = teamsAllocation.Select(x => x.WeekEndingDate).ToArray();
|
|
|
|
var plannedScenarios = teamsInfo.Where(x => x.PlannedCapacityScenarioId.HasValue).Select(x => x.PlannedCapacityScenarioId.Value).ToList();
|
|
// todo: remove
|
|
//var scenarioResourcesAllocatedByTeam = resourcesAllocation.Where(x => x.ScenarioId == scenarioId)
|
|
// .Select(t => new
|
|
// {
|
|
// TeamId = t.TeamId,
|
|
// PeopleResourceId = t.PeopleResourceId,
|
|
// ExpenditureCategoryId = t.ExpenditureCategoryId
|
|
// }).Distinct().GroupBy(t => t.TeamId)
|
|
// .ToDictionary(t => t.Key, category => category.GroupBy(x => x.ExpenditureCategoryId)
|
|
// .ToDictionary(x => x.Key, elem => elem.Select(x => x.PeopleResourceId).ToList()));
|
|
var scenarioResourcesByTeam = resourcesAllocation.Where(t => t.ScenarioId == scenarioId)
|
|
.GroupBy(t => t.TeamId).ToDictionary(tGr => tGr.Key, tElems => tElems
|
|
.GroupBy(t => t.ExpenditureCategoryId).ToDictionary(ecGr => ecGr.Key, ecElems => ecElems.Select(t => t.PeopleResourceId).Distinct()));
|
|
//Logger.Debug("FillTeamsDetails method finished to load resource & teams allocations info");
|
|
#endregion
|
|
|
|
#region Preparing team details
|
|
|
|
if (expenditures != null && expenditures.Count > 0)
|
|
{
|
|
foreach (var ec in expenditures)
|
|
{
|
|
if (!ec.Value.AllowResourceAssignment)
|
|
{
|
|
if (!expCatsInTeams.ContainsKey(ec.Key))
|
|
{
|
|
List<Guid> tid = new List<Guid>();
|
|
foreach (var t in teamsInfo)
|
|
{
|
|
if (!tid.Contains(t.Id))
|
|
tid.Add(t.Id);
|
|
}
|
|
expCatsInTeams.Add(ec.Key, tid);
|
|
}
|
|
|
|
}
|
|
}
|
|
var scenarioWeekEndings = expenditures.SelectMany(t => t.Value.Details.Select(dt => dt.Key)).Distinct().OrderBy(o => o);
|
|
// load capacity adjusted by holidays
|
|
var requiredCategories = expCatsInTeams.Select(x => Guid.Parse(x.Key)).ToList();
|
|
// we need all holidays and capacity, but not only for scenario range, example:
|
|
// we have scenario 01.01.2016 - 31.01.2016
|
|
// we change scenario dates to 01.03.2016 - 31.03.2016
|
|
// we shift scenario details, team & resource allocations to 01.03.2016 - 31.03.2016 as well
|
|
// but we do not affect capacity and rest capacity collections by shifting, so we should to store all capacity and rest capacity for teams and resources
|
|
//Logger.Debug("FillTeamsDetails method started to load holidays & capacity allocations info");
|
|
// for entire calendar!!!
|
|
var allResHolidays = (new FiscalCalendarManager(DbContext)).GetHolidayAllocationsByResource(null, null, resourceIds: allResIds);
|
|
// for entire calendar!!!
|
|
var teamsPlannedCapacity = GetPlanningCapacityAdjusted(plannedScenarios, requiredCategories, null, null);
|
|
var teamsPlannedCapacityWeekEndings = teamsPlannedCapacity.SelectMany(x => x.Value.SelectMany(s => s.Value))
|
|
.Where(x => x.WeekEndingDate.HasValue)
|
|
.Select(x => x.WeekEndingDate.Value).ToArray();
|
|
|
|
var allWeekEndings = scenarioWeekEndings.Select(x => Utils.ConvertFromUnixDate(long.Parse(x)))
|
|
.Union(resourcesAllocationWeekEnding)
|
|
.Union(teamsAllocationWeekEnding)
|
|
.Union(teamsPlannedCapacityWeekEndings)
|
|
.OrderBy(x => x)
|
|
.ToArray();
|
|
//Logger.Debug("FillTeamsDetails method finished to load holidays & capacity allocations info");
|
|
|
|
//Logger.Debug("FillTeamsDetails method started to fill expenditures info");
|
|
foreach (var ec in expenditures)
|
|
{
|
|
if (!expCatsInTeams.ContainsKey(ec.Key))
|
|
continue;
|
|
|
|
var expenditureCategoryId = Guid.Parse(ec.Key);
|
|
var teams = teamsInfo.FindAll(x => expCatsInTeams[ec.Key].Contains(x.Id));
|
|
foreach (var team in teams)
|
|
{
|
|
var resourcesInTeam = team.Resources.Where(x => x.ExpenditureCategoryId == expenditureCategoryId || !ec.Value.AllowResourceAssignment)
|
|
.Distinct().Select(x => new TeamResourceSortable
|
|
{
|
|
Id = x.Id,
|
|
ExpenditureCategoryId = x.ExpenditureCategoryId,
|
|
Name = x.FirstName + " " + x.LastName,
|
|
LastName = x.LastName,
|
|
IsActiveEmployee = x.IsActiveEmployee,
|
|
StartDate = Utils.ConvertToUnixDate(x.StartDate),
|
|
EndDate = x.EndDate.HasValue ? Utils.ConvertToUnixDate(x.EndDate.Value) : (long?)null,
|
|
Allocation = x.TeamAllocation,
|
|
Teams = x.Teams
|
|
}).ToList();
|
|
var teamDetails = new ExpenditureDetailsTeam()
|
|
{
|
|
Id = team.Id,
|
|
Name = team.Name,
|
|
CanBeDeleted = team.CanBeDeleted,
|
|
IsAccessible = team.IsAccessible
|
|
};
|
|
// todo: remove
|
|
//var resourcesAllocationByTeam = resourcesAllocation.Where(x => resourcesInTeam.Any(r => r.Id == x.PeopleResourceId) && x.TeamId == team.Id).ToList();
|
|
var teamResAllocationsDict = resourceAllocationsDict.ContainsKey(team.Id) ? resourceAllocationsDict[team.Id] : new Dictionary<DateTime, Dictionary<Guid, Dictionary<Guid, AllocationItem>>>();
|
|
var teamAllocations4Team = teamAllocationsDict.ContainsKey(team.Id) ? teamAllocationsDict[team.Id] : null;
|
|
var teamPlannedCapacity = team.PlannedCapacityScenarioId.HasValue && teamsPlannedCapacity.ContainsKey(team.PlannedCapacityScenarioId.Value) ?
|
|
teamsPlannedCapacity[team.PlannedCapacityScenarioId.Value] : null;
|
|
var ecTeamPlannedCapacity = teamPlannedCapacity != null && teamPlannedCapacity.ContainsKey(expenditureCategoryId)
|
|
? teamPlannedCapacity[expenditureCategoryId].GroupBy(t => t.WeekEndingDate.Value).ToDictionary(t => t.Key, el => el.FirstOrDefault()) : null;
|
|
|
|
// this loop uses only scenario range allocations and only for current scenario
|
|
foreach (var weekEndingKey in scenarioWeekEndings)
|
|
{
|
|
var detail = ec.Value.Details.ContainsKey(weekEndingKey) ? ec.Value.Details[weekEndingKey] : null;
|
|
var weekEnding = Utils.ConvertFromUnixDate(long.Parse(weekEndingKey));
|
|
// todo: remove
|
|
//var allocation = resourcesAllocationByTeam.Where(x => x.WeekEndingDate == weekEnding &&
|
|
// x.ScenarioId == scenarioId &&
|
|
// x.ExpenditureCategoryId == expenditureCategoryId)
|
|
// .ToList();
|
|
var weekResAllocationsDict = teamResAllocationsDict.ContainsKey(weekEnding) ? teamResAllocationsDict[weekEnding] : new Dictionary<Guid, Dictionary<Guid, AllocationItem>>();
|
|
|
|
#region Filling resource quantity according to allocation in current scenario
|
|
|
|
foreach (var resource in resourcesInTeam)
|
|
{
|
|
// todo: remove
|
|
//var allocationInCurrentScenario = allocation.Where(x => x.PeopleResourceId == resource.Id)
|
|
// .Sum(x => x.Quantity);
|
|
var resWeekAllocations = weekResAllocationsDict.ContainsKey(resource.Id) ? weekResAllocationsDict[resource.Id] : new Dictionary<Guid, AllocationItem>();
|
|
var allocationInCurrentScenario = resWeekAllocations.ContainsKey(expenditureCategoryId) ? resWeekAllocations[expenditureCategoryId].CurrentScenarioQuantity : 0;
|
|
//if (allocationInCurrentScenario != allocationInCurrentScenario2)
|
|
// Logger.Debug(string.Format("res allocations differ: EC={0};T={1};R={2};W={3}", expenditureCategoryId,
|
|
// team.Id, resource.Id, weekEnding));
|
|
// we should show original value even if resource is unavailable on this week (for ability to see corrupted data)
|
|
if (ignoreCurrentScenario)
|
|
resource.QuantityValues.Add(weekEndingKey, 0);
|
|
else
|
|
resource.QuantityValues.Add(weekEndingKey, Math.Max(allocationInCurrentScenario, 0));
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Calculation team's allocation in current scenario and rest quantity according to team's allocation in all active scenarios
|
|
// todo: remove
|
|
//var teamAllocation4Week = teamsAllocation.FindAll(x => x.ExpenditureCategoryId == expenditureCategoryId &&
|
|
// x.TeamId == team.Id &&
|
|
// x.WeekEndingDate == weekEnding);
|
|
// todo: remove
|
|
//var teamAllocationInScenario = teamAllocation4Week.FirstOrDefault(x => x.ScenarioId == scenarioId);
|
|
var teamAllocation4WeekDict = teamAllocations4Team != null && teamAllocations4Team.ContainsKey(weekEnding) ? teamAllocations4Team[weekEnding] : null;
|
|
var teamAllocationInScenario = teamAllocation4WeekDict != null && teamAllocation4WeekDict.ContainsKey(expenditureCategoryId) ?
|
|
teamAllocation4WeekDict[expenditureCategoryId].CurrentScenarioQuantity : (decimal?)null;
|
|
|
|
var teamQuantity = 0M;
|
|
if (teamAllocationInScenario != null && !ignoreCurrentScenario)
|
|
{
|
|
//teamQuantity = teamAllocationInScenario.Quantity;
|
|
teamQuantity = teamAllocationInScenario ?? 0;
|
|
//if (teamAllocationInScenario.Quantity != teamAllocationInScenario2)
|
|
// Logger.Debug(string.Format("team allocations differ: EC={0};T={1};W={2};V1={3};V2={4}", expenditureCategoryId,
|
|
// team.Id, weekEnding, teamAllocationInScenario.Quantity, teamAllocationInScenario2));
|
|
}
|
|
else
|
|
{
|
|
// by default team should take quantity value according to team 2 scenario allocation
|
|
teamQuantity = (ignoreCurrentScenario || detail == null) ? 0 : ((detail.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;
|
|
|
|
//if ((teamAllocationInScenario != null && teamAllocationInScenario == null) ||
|
|
// (teamAllocationInScenario == null && teamAllocationInScenario != null))
|
|
// Logger.Debug(string.Format("team allocations 2 differ: EC={0};T={1};W={2};V1={3};V2={4}", expenditureCategoryId,
|
|
// team.Id, weekEnding, teamAllocationInScenario, teamAllocationInScenario2));
|
|
}
|
|
teamDetails.QuantityValues.Add(weekEndingKey, Math.Max(teamQuantity, 0));
|
|
|
|
#endregion
|
|
}
|
|
//8.24.16 start tab
|
|
if (filterStartDate.HasValue && filterEndDate.HasValue)
|
|
{
|
|
var StartWE = FiscalCalendarManager.GetFiscalCalendarWeekForDate(filterStartDate.Value, new EnVisageEntities());
|
|
var EndWE = FiscalCalendarManager.GetFiscalCalendarWeekForDate(filterEndDate.Value, new EnVisageEntities());
|
|
allWeekEndings = allWeekEndings.Where(x => x >= StartWE.StartDate && x <= EndWE.EndDate).ToArray();
|
|
}
|
|
//8.24.16 end
|
|
foreach (var weekEnding in allWeekEndings)
|
|
{
|
|
var weekEndingKey = Utils.ConvertToUnixDate(weekEnding).ToString();
|
|
// todo: remove
|
|
//var allocation = resourcesAllocationByTeam.Where(x => x.WeekEndingDate == weekEnding).ToArray();
|
|
var weekResAllocationsDict = teamResAllocationsDict.ContainsKey(weekEnding) ? teamResAllocationsDict[weekEnding] : null;
|
|
var teamAllocation4WeekDict = teamAllocations4Team != null && teamAllocations4Team.ContainsKey(weekEnding) ? teamAllocations4Team[weekEnding] : null;
|
|
|
|
#region Filling resource capacity and quantity according to allocation in current scenario
|
|
|
|
foreach (var resource in resourcesInTeam)
|
|
{
|
|
var weekStartDate = fc.ContainsKey(weekEnding) ? fc[weekEnding] : weekEnding;
|
|
var resWeekAllocations = weekResAllocationsDict != null && weekResAllocationsDict.ContainsKey(resource.Id) ? weekResAllocationsDict[resource.Id] : null;
|
|
|
|
// resource allocations in this week by current scenario only through all ECs (super and not super)
|
|
// todo: remove
|
|
//var allocationInCurrentScenario = allocation.Where(x => x.PeopleResourceId == resource.Id &&
|
|
// x.ScenarioId == scenarioId).Sum(x => x.Quantity);
|
|
var allocationInCurrentScenario = resWeekAllocations != null ? resWeekAllocations.Values.Sum(t => t.CurrentScenarioQuantity) : 0;
|
|
//if (allocationInCurrentScenario != allocationInCurrentScenario2)
|
|
// Logger.Debug(string.Format("res allocations 2 differ: EC={0};T={1};R={2};W={3};V1={4};V2={5}", expenditureCategoryId,
|
|
// team.Id, resource.Id, weekEnding, allocationInCurrentScenario, allocationInCurrentScenario2));
|
|
// sum total of resource allocations in this week except of current scenario
|
|
// todo: remove
|
|
//var allocationInOtherScenarios = allocation.Where(x => x.PeopleResourceId == resource.Id &&
|
|
// x.ScenarioId != scenarioId)
|
|
// .Sum(x => x.Quantity);
|
|
var allocationInOtherScenarios = resWeekAllocations != null ? resWeekAllocations.Values.Sum(t => t.OtherScenariosQuantity) : 0;
|
|
//if (allocationInOtherScenarios != allocationInOtherScenarios2)
|
|
// Logger.Debug(string.Format("res other allocations differ: EC={0};T={1};R={2};W={3};V1={4};V2={5}", expenditureCategoryId,
|
|
// team.Id, resource.Id, weekEnding, allocationInOtherScenarios, allocationInOtherScenarios2));
|
|
|
|
// if resource doesn't exist in this period we should fill all quantities with 0
|
|
// consider only full weeks as work weeks
|
|
var isReadOnlyResource = weekStartDate < Utils.ConvertFromUnixDate(resource.StartDate) ||
|
|
(resource.EndDate.HasValue && weekEnding > Utils.ConvertFromUnixDate(resource.EndDate.Value));
|
|
if (isReadOnlyResource)
|
|
{
|
|
resource.CapacityQuantityValues.Add(weekEndingKey, 0);
|
|
resource.ReadOnly.Add(weekEndingKey, true);
|
|
}
|
|
else
|
|
{
|
|
// if there are any non-project time scheduled for this resource in this week then we should reduce capacity by the amount of this NP time
|
|
var rNpt = npTimeAllocations.ContainsKey(resource.Id) ? npTimeAllocations[resource.Id] : null;
|
|
var npTime = rNpt != null && rNpt.ContainsKey(weekEnding) ? rNpt[weekEnding] : 0;
|
|
// if there are any holidays for this resource in this week then we should adjust capacity accordingly
|
|
var holidayKoeff = allResHolidays.ContainsKey(resource.Id) && allResHolidays[resource.Id].ContainsKey(weekEnding) ? allResHolidays[resource.Id][weekEnding] : 1;
|
|
// reduce capacity by the amount of resource 2 team involvment
|
|
var uomValue = ec.Value.UOMValue;
|
|
if (resource.ExpenditureCategoryId.ToString() != ec.Key)
|
|
{
|
|
if (expenditures.ContainsKey(resource.ExpenditureCategoryId.ToString()))
|
|
uomValue = expenditures[resource.ExpenditureCategoryId.ToString()].UOMValue;
|
|
else
|
|
{
|
|
var ecObj = this.DbContext.ExpenditureCategory.Where(x => x.Id == resource.ExpenditureCategoryId).FirstOrDefault();
|
|
if (ecObj != null)
|
|
if (ecObj.UOM != null)
|
|
uomValue = ecObj.UOM.UOMValue;
|
|
|
|
}
|
|
}
|
|
var capacity = resource.Allocation * (uomValue * holidayKoeff - npTime) / 100.0M;
|
|
|
|
resource.CapacityQuantityValues.Add(weekEndingKey, Math.Max(capacity, 0));
|
|
|
|
bool isEditable = (resource.Teams != null) && resource.Teams.Any(x => x.IsInRange(weekEnding));
|
|
resource.ReadOnly.Add(weekEndingKey, !isEditable);
|
|
}
|
|
|
|
// it is OK to have a negative value for the rest quantity
|
|
resource.RestQuantityValues.Add(weekEndingKey, resource.CapacityQuantityValues[weekEndingKey] - allocationInOtherScenarios);
|
|
if (!ignoreCurrentScenario)
|
|
resource.RestQuantityValues[weekEndingKey] -= allocationInCurrentScenario;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Calculation team's allocation in current scenario and rest quantity according to team's allocation in all active scenarios
|
|
|
|
if (ec.Value.AllowResourceAssignment)
|
|
{
|
|
decimal teamCapacity = 0;
|
|
if (weekEnding < DateTime.UtcNow.Date)
|
|
{
|
|
teamCapacity = resourcesInTeam.Sum(x => x.CapacityQuantityValues[weekEndingKey]);
|
|
}
|
|
else
|
|
{
|
|
var plannedCapacityItem = (ecTeamPlannedCapacity != null && ecTeamPlannedCapacity.ContainsKey(weekEnding)) ? ecTeamPlannedCapacity[weekEnding] : null;
|
|
if (plannedCapacityItem != null)
|
|
teamCapacity = plannedCapacityItem.Quantity;
|
|
}
|
|
|
|
//var teamAllocationInOtherScenarios = teamsAllocation.Where(x => x.ScenarioId != scenarioId &&
|
|
// x.ExpenditureCategoryId == expenditureCategoryId &&
|
|
// x.TeamId == team.Id &&
|
|
// x.WeekEndingDate == weekEnding)
|
|
// .Sum(x => x.Quantity);
|
|
var teamAllocationInOtherScenarios = teamAllocation4WeekDict != null && teamAllocation4WeekDict.ContainsKey(expenditureCategoryId) ?
|
|
teamAllocation4WeekDict[expenditureCategoryId].OtherScenariosQuantity : 0;
|
|
//if (teamAllocationInOtherScenarios != teamAllocationInOtherScenarios2)
|
|
// Logger.Debug(string.Format("team other allocations differ: EC={0};T={1};W={2};V1={3};V2={4}", expenditureCategoryId,
|
|
// team.Id, weekEnding, teamAllocationInOtherScenarios, teamAllocationInOtherScenarios2));
|
|
|
|
// calculate resource allocation by super ECs
|
|
// todo: remove
|
|
//var resourceAllocationInSuperECs = allocation.Where(x => x.ExpenditureCategoryId != expenditureCategoryId)
|
|
// .Sum(x => x.Quantity);
|
|
var resourceAllocationInSuperECs = weekResAllocationsDict != null ? weekResAllocationsDict.Where(res => resourcesInTeam.Any(r => r.Id == res.Key))
|
|
.SelectMany(t => t.Value).Where(exp => exp.Key != expenditureCategoryId).Sum(t => t.Value.CurrentScenarioQuantity + t.Value.OtherScenariosQuantity) : 0;
|
|
//if (resourceAllocationInSuperECs != resourceAllocationInSuperECs2)
|
|
// Logger.Debug(string.Format("super res allocations differ: EC={0};T={1};W={2};V1={3};V2={4}", expenditureCategoryId,
|
|
// team.Id, weekEnding, resourceAllocationInSuperECs, resourceAllocationInSuperECs2));
|
|
|
|
var teamQuantity = teamDetails.QuantityValues.ContainsKey(weekEndingKey) ? teamDetails.QuantityValues[weekEndingKey] : 0M;
|
|
var restQuantity = teamCapacity - teamAllocationInOtherScenarios - teamQuantity - resourceAllocationInSuperECs;
|
|
|
|
teamDetails.CapacityQuantityValues.Add(weekEndingKey, Math.Max(teamCapacity, 0));
|
|
|
|
// it is OK to have a negative value for the rest quantity
|
|
teamDetails.RestQuantityValues.Add(weekEndingKey, restQuantity);
|
|
}
|
|
else
|
|
{
|
|
//// super ECs do not have capacity at all
|
|
//teamDetails.CapacityQuantityValues.Add(weekEndingKey, 0);
|
|
//teamDetails.RestQuantityValues.Add(weekEndingKey, 0);
|
|
decimal teamCapacity = 0;
|
|
if (weekEnding < DateTime.UtcNow.Date)
|
|
{
|
|
teamCapacity = resourcesInTeam.Sum(x => x.CapacityQuantityValues[weekEndingKey]);
|
|
}
|
|
else
|
|
{
|
|
if (teamPlannedCapacity != null)
|
|
{
|
|
foreach (var teamPlannedCapByEC in teamPlannedCapacity)
|
|
{
|
|
var plannedECCapacityItem = teamPlannedCapByEC.Value.FirstOrDefault(x => x.WeekEndingDate == weekEnding);
|
|
if (plannedECCapacityItem != null)
|
|
teamCapacity += plannedECCapacityItem.Quantity;
|
|
}
|
|
}
|
|
}
|
|
|
|
//var teamAllocationInOtherScenarios = teamsAllocation.Where(x => x.ScenarioId != scenarioId &&
|
|
// // x.ExpenditureCategoryId == expenditureCategoryId &&
|
|
// x.TeamId == team.Id &&
|
|
// x.WeekEndingDate == weekEnding)
|
|
// .Sum(x => x.Quantity);
|
|
var teamAllocationInOtherScenarios = teamAllocation4WeekDict != null ?
|
|
teamAllocation4WeekDict.Sum(t => t.Value.OtherScenariosQuantity) : 0;
|
|
//if (teamAllocationInOtherScenarios != teamAllocationInOtherScenarios2)
|
|
// Logger.Debug(string.Format("team other allocations differ: EC={0};T={1};W={2};V1={3};V2={4}", expenditureCategoryId,
|
|
// team.Id, weekEnding, teamAllocationInOtherScenarios, teamAllocationInOtherScenarios2));
|
|
|
|
// calculate resource allocation by super ECs
|
|
// todo: remove
|
|
//var resourceAllocationInSuperECs = allocation.Where(x => x.ExpenditureCategoryId != expenditureCategoryId)
|
|
// .Sum(x => x.Quantity);
|
|
var resourceAllocationInSuperECs = weekResAllocationsDict != null ? weekResAllocationsDict.Where(res => resourcesInTeam.Any(r => r.Id == res.Key))
|
|
.SelectMany(t => t.Value).Where(exp => exp.Key != expenditureCategoryId).Sum(t => t.Value.CurrentScenarioQuantity + t.Value.OtherScenariosQuantity) : 0;
|
|
//if (resourceAllocationInSuperECs != resourceAllocationInSuperECs2)
|
|
// Logger.Debug(string.Format("super res allocations differ: EC={0};T={1};W={2};V1={3};V2={4}", expenditureCategoryId,
|
|
// team.Id, weekEnding, resourceAllocationInSuperECs, resourceAllocationInSuperECs2));
|
|
var teamQuantity = teamDetails.QuantityValues.ContainsKey(weekEndingKey) ? teamDetails.QuantityValues[weekEndingKey] : 0M;
|
|
var restQuantity = teamCapacity - teamAllocationInOtherScenarios - teamQuantity - resourceAllocationInSuperECs;
|
|
|
|
teamDetails.CapacityQuantityValues.Add(weekEndingKey, Math.Max(teamCapacity, 0));
|
|
|
|
// it is OK to have a negative value for the rest quantity
|
|
teamDetails.RestQuantityValues.Add(weekEndingKey, restQuantity);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
#region Fill team.Resources and team.AllResources collections
|
|
//var teamResourcesAllocated = new List<Guid>();
|
|
//if (scenarioResourcesAllocatedByTeam.ContainsKey(teamDetails.Id))
|
|
//{
|
|
// if (scenarioResourcesAllocatedByTeam[teamDetails.Id].ContainsKey(expenditureCategoryId))
|
|
// {
|
|
// teamResourcesAllocated = scenarioResourcesAllocatedByTeam[teamDetails.Id][expenditureCategoryId];
|
|
// }
|
|
//}
|
|
|
|
//teamDetails.Resources = resourcesInTeam.FindAll(x => teamResourcesAllocated.Contains(x.Id))
|
|
var scenarioResources = scenarioResourcesByTeam.ContainsKey(teamDetails.Id) ?
|
|
scenarioResourcesByTeam[teamDetails.Id].ContainsKey(expenditureCategoryId) ?
|
|
scenarioResourcesByTeam[teamDetails.Id][expenditureCategoryId] :
|
|
null :
|
|
null;
|
|
teamDetails.Resources = resourcesInTeam.FindAll(x => scenarioResources != null && scenarioResources.Contains(x.Id))
|
|
.Select(x => (TeamResource)x)
|
|
.ToDictionary(x => x.Id.ToString());
|
|
if (ignoreCurrentScenario)
|
|
{
|
|
foreach (var resource in teamDetails.Resources)
|
|
{
|
|
resource.Value.Changed = true;
|
|
resource.Value.Deleted = true;
|
|
}
|
|
}
|
|
teamDetails.AllResources = resourcesInTeam.OrderBy(x => x.LastName)
|
|
.Select(x => (TeamResource)x)
|
|
.ToDictionary(x => x.Id.ToString());
|
|
if (!ec.Value.Teams.ContainsKey(teamDetails.Id.ToString()))
|
|
ec.Value.Teams.Add(teamDetails.Id.ToString(), teamDetails);
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
}
|
|
//Logger.Debug("FillTeamsDetails method finished to fill expenditures info");
|
|
|
|
#endregion
|
|
|
|
return teamsInfo.Select(x => new TeamInScenarioModel
|
|
{
|
|
TeamId = x.Id,
|
|
IsNew = false,
|
|
Allocation = (short)x.Allocation,
|
|
IsAccessible = x.IsAccessible,
|
|
CanBeDeleted = x.CanBeDeleted
|
|
}).ToList();
|
|
}
|
|
//8.24.16 start tab
|
|
public Dictionary<string, ExpenditureDetail> GetFullAllocationInfoByScenario(Guid scenarioId, string userId, bool expandSuperCatgory = false, DateTime? filterStartDate = null, DateTime? filterEndDate = null)
|
|
//public Dictionary<string, ExpenditureDetail> GetFullAllocationInfoByScenario(Guid scenarioId, string userId, bool expandSuperCatgory = false)
|
|
//8.24.16 end
|
|
{
|
|
//Logger.Debug("GetFullAllocationInfoByScenario method started");
|
|
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 uoms = DbContext.UOMs.ToDictionary(x => x.Id, g => g.UOMValue);
|
|
var expenditures = BuildExpendituresForScenario(weekEndings, forecastDetails, null, uoms);
|
|
|
|
var scenario = Load(scenarioId);
|
|
if (scenario == null)
|
|
return expenditures;
|
|
|
|
IEnumerable<Guid> superCategoryIds = null;
|
|
|
|
if (expandSuperCatgory)
|
|
{
|
|
superCategoryIds = expenditures
|
|
.Where(x => x.Value.AllowResourceAssignment == false)
|
|
.Select(x => x.Value.ExpenditureCategoryId)
|
|
.ToList();
|
|
|
|
|
|
var query =
|
|
(from ra in DbContext.PeopleResourceAllocations
|
|
join pr in DbContext.PeopleResources on ra.PeopleResourceId equals pr.Id
|
|
join ec in DbContext.VW_ExpenditureCategory on pr.ExpenditureCategoryId equals ec.Id
|
|
where
|
|
superCategoryIds.Contains(ra.ExpenditureCategoryId) && ra.ScenarioId == scenarioId
|
|
select ec
|
|
)
|
|
.Distinct()
|
|
.ToArray()
|
|
.Where(x => !expenditures.ContainsKey(x.Id.ToString()))
|
|
.Select(x => new ExpenditureDetail()
|
|
{
|
|
ExpenditureCategoryId = x.Id,
|
|
ExpenditureCategoryName = x.ExpCategoryWithCcName,
|
|
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],
|
|
AllowResourceAssignment = x.AllowResourceAssignment,
|
|
});
|
|
|
|
|
|
foreach (var category in query)
|
|
{
|
|
expenditures.Add(category.ExpenditureCategoryId.ToString(), category);
|
|
}
|
|
}
|
|
//8.24.16 Start TAB
|
|
//Logger.Debug("FillTeamsDetails method started");
|
|
FillTeamsDetails(scenario.Id, scenario.ParentId ?? Guid.Empty, expenditures, userId, false, null, filterStartDate, filterEndDate);
|
|
//Logger.Debug("FillTeamsDetails method compelted");
|
|
//FillTeamsDetails(scenario.Id, scenario.ParentId ?? Guid.Empty, expenditures, userId, false);
|
|
//8.24.16 End
|
|
if (expandSuperCatgory && superCategoryIds != null)
|
|
{
|
|
var superCategories = expenditures
|
|
.Where(x => superCategoryIds.Contains(Guid.Parse(x.Key)))
|
|
.ToArray();
|
|
|
|
foreach (var superCategory in superCategories.Select(x => x.Value))
|
|
{
|
|
foreach (var superTeam in superCategory.Teams.Select(x => x.Value))
|
|
{
|
|
foreach (var superResource in superTeam.Resources.Select(x => x.Value))
|
|
{
|
|
if (superResource.ExpenditureCategoryId != superCategory.ExpenditureCategoryId)
|
|
{
|
|
var resources = expenditures[superResource.ExpenditureCategoryId.ToString()].Teams[superTeam.Id.ToString()].Resources;
|
|
if (!resources.ContainsKey(superResource.Id.ToString()))
|
|
{
|
|
resources.Add(superResource.Id.ToString(), superResource);
|
|
}
|
|
else
|
|
{
|
|
var resource = resources[superResource.Id.ToString()];
|
|
foreach (var weekEnding in superResource.QuantityValues.Keys)
|
|
{
|
|
resource.QuantityValues[weekEnding] += superResource.QuantityValues[weekEnding];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
expenditures.Remove(superCategory.ExpenditureCategoryId.ToString());
|
|
}
|
|
|
|
}
|
|
|
|
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<ScenarioDetailWithProxyItemModel>>> GetScenarioDetailsTree(List<Guid> scenarios, List<Guid> expenditureCategories)
|
|
{
|
|
var scenarioDetails = GetScenarioProxyDetails(scenarios, expenditureCategories);
|
|
return scenarioDetails
|
|
.GroupBy(sd => sd.ScenarioId)
|
|
.ToDictionary(scenario => scenario.Key,
|
|
ecs => ecs.GroupBy(ec => ec.ExpenditureCategoryId)
|
|
.ToDictionary(ec => ec.Key,
|
|
details => details.ToList()));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns tree-structure of scenario details: Scenarios[ScenarioId, Expenditure Categories[ExpCatId, ScenarioDetails]].
|
|
/// Quantity values adjusted based on Holidays
|
|
/// </summary>
|
|
/// <param name="scenarioIds">Scenario Ids for which method will returns scenario details.</param>
|
|
/// <param name="expenditureCategories">ECs for which method will returns scenario details</param>
|
|
/// <param name="startDate">Start date of period to search.</param>
|
|
/// <param name="endDate">End date of period to search.</param>
|
|
/// <returns></returns>
|
|
public Dictionary<Guid, Dictionary<Guid, List<ScenarioDetailWithProxyItemModel>>> GetPlanningCapacityAdjusted(List<Guid> scenarioIds, List<Guid> expenditureCategories, DateTime? startDate, DateTime? endDate)
|
|
{
|
|
// load capacity scenario details for provided scenarios
|
|
var result = GetScenarioDetailsTree(scenarioIds, expenditureCategories);
|
|
// load adjusted capacity koefficients
|
|
var capacityListQuery = DbContext.VW_PlanningCapacityAdjusted.AsNoTracking().Where(t => scenarioIds.Contains(t.ScenarioId));
|
|
if (expenditureCategories != null && expenditureCategories.Count > 0)
|
|
capacityListQuery = capacityListQuery.Where(x => x.ExpenditureCategoryId.HasValue && expenditureCategories.Contains(x.ExpenditureCategoryId.Value));
|
|
if (startDate.HasValue)
|
|
capacityListQuery = capacityListQuery.Where(x => x.WeekEndingDate >= startDate);
|
|
if (endDate.HasValue)
|
|
capacityListQuery = capacityListQuery.Where(x => x.WeekEndingDate <= endDate);
|
|
var capacityList = capacityListQuery.ToList();
|
|
var adjustedCapacity = capacityList.GroupBy(sc => sc.ScenarioId)
|
|
.ToDictionary(sc => sc.Key, expItems => expItems.GroupBy(e => e.ExpenditureCategoryId)
|
|
.ToDictionary(sc => sc.Key, items => items
|
|
.GroupBy(g => g.WeekEndingDate)
|
|
.ToDictionary(w => w.Key, val => val.FirstOrDefault() == null ? 1 : val.FirstOrDefault().AdjustmentKoeff))
|
|
);
|
|
// adjust weekly capacity values because of holidays
|
|
foreach (var scenarioId in adjustedCapacity.Keys)
|
|
{
|
|
if (result.ContainsKey(scenarioId))
|
|
{
|
|
var scenario = result[scenarioId];
|
|
foreach (var expCatId in scenario.Keys)
|
|
{
|
|
var expItemAdjustment = adjustedCapacity[scenarioId].ContainsKey(expCatId) ? adjustedCapacity[scenarioId][expCatId] : null;
|
|
if (expItemAdjustment != null)
|
|
foreach (var detailItem in scenario[expCatId])
|
|
{
|
|
if (expItemAdjustment.ContainsKey(detailItem.WeekEndingDate))
|
|
detailItem.Quantity = detailItem.Quantity * expItemAdjustment[detailItem.WeekEndingDate];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
public Dictionary<DateTime, List<ActualCapacityItemModel>> GetActualCapacityAdjusted(List<Guid> teams, List<Guid> expenditureCategories, DateTime startDate, DateTime? endDate)
|
|
{
|
|
List<DateTime> weekendings = FiscalCalendarManager.GetWeekendingsByRange(startDate, endDate, DbContext);
|
|
var qry = DbContext.VW_ActualCapacityAdjusted.AsNoTracking()
|
|
.Where(x => weekendings.Contains(x.WeekEndingDate))
|
|
.AsQueryable();
|
|
|
|
if ((teams != null) && (teams.Count > 0))
|
|
qry = qry.Where(x => teams.Contains(x.TeamId));
|
|
|
|
if ((expenditureCategories != null) && (expenditureCategories.Count > 0))
|
|
qry = qry.Where(x => expenditureCategories.Contains(x.ExpenditureCategoryId));
|
|
|
|
var result =
|
|
qry.ToList().GroupBy(x => x.WeekEndingDate).ToDictionary(key => key.Key,
|
|
grpWe => grpWe.Select(x => (ActualCapacityItemModel)x).ToList());
|
|
return result;
|
|
}
|
|
|
|
// Dictionary[Weekending, Dictionary[ExpCatId, Dictionary[TeamId, CapacityValuesRecord]]]
|
|
public Dictionary<DateTime, Dictionary<Guid, Dictionary<Guid, ActualCapacityItemModel>>> GetActualCapacityAdjustedByTeamsAndExpCats(List<Guid> teams, List<Guid> expenditureCategories, DateTime startDate, DateTime? endDate)
|
|
{
|
|
List<DateTime> weekendings = FiscalCalendarManager.GetWeekendingsByRange(startDate, endDate, DbContext);
|
|
var qry = DbContext.VW_ActualCapacityByTeamsAdjusted.AsNoTracking()
|
|
.Where(x => weekendings.Contains(x.WeekEndingDate))
|
|
.AsQueryable();
|
|
|
|
if ((teams != null) && (teams.Count > 0))
|
|
qry = qry.Where(x => teams.Contains(x.TeamId));
|
|
|
|
if ((expenditureCategories != null) && (expenditureCategories.Count > 0))
|
|
qry = qry.Where(x => expenditureCategories.Contains(x.ExpenditureCategoryId));
|
|
|
|
var result =
|
|
qry.ToList().GroupBy(x => x.WeekEndingDate).ToDictionary(key => key.Key,
|
|
grpWe => grpWe.GroupBy(x => x.ExpenditureCategoryId).ToDictionary(key => key.Key,
|
|
grpEc => grpEc.GroupBy(x => x.TeamId).ToDictionary(key => key.Key,
|
|
val => (ActualCapacityItemModel)(val.FirstOrDefault()))));
|
|
return result;
|
|
}
|
|
|
|
public Dictionary<DateTime, Dictionary<Guid, List<ActualCapacityItemModel>>> GetActualCapacityAdjustedByTeams(List<Guid> teams, DateTime? startDate, DateTime? endDate)
|
|
{
|
|
throw new NotImplementedException();
|
|
}
|
|
|
|
public Dictionary<Guid, VW_ExpenditureCategory> GetCategoriesInScenario(Guid scenarioId)
|
|
{
|
|
if (scenarioId.Equals(Guid.Empty))
|
|
throw new ArgumentNullException("scenarioId");
|
|
|
|
var result = new Dictionary<Guid, VW_ExpenditureCategory>();
|
|
var scenarios = new List<Guid>();
|
|
scenarios.Add(scenarioId);
|
|
|
|
var scenarioExpCats = GetCategoriesInScenarios(scenarios);
|
|
|
|
if (scenarioExpCats.ContainsKey(scenarioId))
|
|
{
|
|
result = scenarioExpCats[scenarioId];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public Dictionary<Guid, Dictionary<Guid, VW_ExpenditureCategory>> GetCategoriesInScenarios(List<Guid> scenarios, List<Guid> excludeECs = null)
|
|
{
|
|
var excludableExpCats = (excludeECs == null) ? new List<Guid>() : excludeECs;
|
|
var expCatsInScenarios = (from x in DbContext.ScenarioDetail.AsNoTracking()
|
|
join y in DbContext.VW_ExpenditureCategory.AsNoTracking()
|
|
on x.ExpenditureCategoryId equals y.Id
|
|
where x.ParentID.HasValue &&
|
|
x.ExpenditureCategoryId.HasValue &&
|
|
scenarios.Contains(x.ParentID.Value) &&
|
|
!excludableExpCats.Contains(x.ExpenditureCategoryId.Value)
|
|
select new
|
|
{
|
|
ScenarioId = x.ParentID.Value,
|
|
ExpenditureCategory = y
|
|
}).Distinct().ToList();
|
|
|
|
var result = expCatsInScenarios.GroupBy(x => x.ScenarioId)
|
|
.ToDictionary(x => x.Key, g => g.ToDictionary(x => x.ExpenditureCategory.Id, s => s.ExpenditureCategory));
|
|
|
|
// Add dummy records for scenarios without scenario details records
|
|
scenarios.ForEach(x =>
|
|
{
|
|
if (!result.ContainsKey(x))
|
|
result.Add(x, new Dictionary<Guid, VW_ExpenditureCategory>());
|
|
});
|
|
|
|
return result;
|
|
}
|
|
|
|
public List<Scenario> GetScenarios4Project(Guid projectId, ScenarioType? scenarioType, ScenarioStatus? scenarioStatus, bool includeCostSavings = false, bool noTracking = false)
|
|
{
|
|
if (projectId == Guid.Empty)
|
|
return new List<Scenario>();
|
|
|
|
return GetScenarios4Projects(new List<Guid> { projectId }, scenarioType, scenarioStatus, includeCostSavings, noTracking);
|
|
}
|
|
|
|
public List<Scenario> GetScenarios4Projects(IEnumerable<Guid> projects, ScenarioType? scenarioType, ScenarioStatus? scenarioStatus, bool includeCostSavings = false, bool noTracking = false)
|
|
{
|
|
if (projects == null || !projects.Any())
|
|
return new List<Scenario>();
|
|
|
|
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);
|
|
if (noTracking)
|
|
return scenarios.AsNoTracking().ToList();
|
|
else
|
|
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;
|
|
}
|
|
|
|
public Scenario GetScenario(ScenarioCalendarMixModel model)
|
|
{
|
|
if (model == null)
|
|
throw new ArgumentNullException("scenario");
|
|
|
|
var project = (new ProjectManager(DbContext)).Load(model.ParentId.Value);
|
|
if (project == null)
|
|
throw new InvalidOperationException(string.Format("Project {0} does not exist", model.ParentId));
|
|
|
|
var infoModel = (new ScenarioInfoModel(model));
|
|
var scenario = GetScenario(infoModel, project.Color, project.IsRevenueGenerating);
|
|
var forecast = GetScenarioDetailsItems(model.GetScenarioDetails(true));
|
|
|
|
SetBottomUpCosts(scenario, project.IsRevenueGenerating, null, forecast);
|
|
|
|
if (infoModel.CostSavings != null)
|
|
{
|
|
scenario.CostSavings = infoModel.CostSavings.CostSavings;
|
|
if (infoModel.CostSavings.CostSavingStartDate.HasValue && infoModel.CostSavings.CostSavingStartDate > 0)
|
|
scenario.CostSavingsStartDate = Utils.ConvertFromUnixDate(infoModel.CostSavings.CostSavingStartDate.Value);
|
|
if (infoModel.CostSavings.CostSavingEndDate.HasValue && infoModel.CostSavings.CostSavingEndDate > 0)
|
|
scenario.CostSavingsEndDate = Utils.ConvertFromUnixDate(infoModel.CostSavings.CostSavingEndDate.Value);
|
|
scenario.CostSavingsType = Convert.ToInt16(infoModel.CostSavings.CostSavingType);
|
|
scenario.CostSavingsDescription = infoModel.CostSavings.CostSavingDescription;
|
|
scenario.ROIDate = GetROIDate(scenario, GetCostSavings(infoModel.CostSavings));
|
|
}
|
|
|
|
return scenario;
|
|
}
|
|
|
|
public Scenario GetScenario(ScenarioInfoModel model, string color, bool isRevenueGenerating)
|
|
{
|
|
if (model == null)
|
|
return null;
|
|
|
|
var scenario = new Scenario()
|
|
{
|
|
Id = model.Id ?? Guid.NewGuid(),
|
|
ParentId = model.ParentId,
|
|
TemplateId = model.TemplateId,
|
|
Name = model.Name,
|
|
StartDate = Utils.ConvertFromUnixDate(model.StartDate).Date,
|
|
EndDate = Utils.ConvertFromUnixDate(model.EndDate).Date,
|
|
Duration = model.Duration,
|
|
Type = model.Type.GetHashCode(),
|
|
ProjectedRevenue = model.ProjectedRevenue,
|
|
TDDirectCosts = model.TDDirectCosts,
|
|
UseLMMargin = Convert.ToInt32(model.UseLMMargin),
|
|
ExpectedGrossMargin = (model.GrossMargin ?? 0) / 100,
|
|
ExpectedGrossMargin_LM = (model.LMMargin ?? 0) / 100,
|
|
CGSplit = model.CGSplit,
|
|
EFXSplit = model.EFXSplit,
|
|
FreezeRevenue = model.FreezeRevenue,
|
|
Color = color,
|
|
ShotStartDate = null,
|
|
GrowthScenario = model.GrowthScenario,
|
|
SystemAttributeObjectID = null,
|
|
IsBottomUp = model.IsBottomUp
|
|
};
|
|
|
|
if (model.SaveAsDraft)
|
|
scenario.Status = ScenarioStatus.Draft.GetHashCode();
|
|
else
|
|
{
|
|
if (model.IsActiveScenario)
|
|
scenario.Status = ScenarioStatus.Active.GetHashCode();
|
|
else
|
|
scenario.Status = ScenarioStatus.Inactive.GetHashCode();
|
|
}
|
|
|
|
if (isRevenueGenerating)
|
|
{
|
|
if (scenario.UseLMMargin == 0)
|
|
{
|
|
if (!scenario.TDDirectCosts.HasValue)
|
|
scenario.TDDirectCosts = model.ProjectedRevenue - (model.ProjectedRevenue * (scenario.ExpectedGrossMargin ?? 0));
|
|
}
|
|
else
|
|
{
|
|
if (!scenario.TDDirectCosts_LM.HasValue)
|
|
scenario.TDDirectCosts_LM = model.ProjectedRevenue - (model.ProjectedRevenue * (scenario.ExpectedGrossMargin_LM ?? 0));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
scenario.TDDirectCosts = model.TDDirectCosts;
|
|
scenario.TDDirectCosts_LM = 0;
|
|
}
|
|
|
|
return scenario;
|
|
}
|
|
|
|
public Scenario GetScenario(Guid id)
|
|
{
|
|
if (id.Equals(Guid.Empty))
|
|
throw new ArgumentNullException("id");
|
|
|
|
var result = this.Load(id);
|
|
return result;
|
|
}
|
|
|
|
public LaborMaterialsCostInfo GetLaborMaterialsSplit(Guid scenarioId)
|
|
{
|
|
if (scenarioId == Guid.Empty)
|
|
return null;
|
|
|
|
var result = GetLaborMaterialsSplit(new List<Guid>() { scenarioId });
|
|
if (result != null && result.ContainsKey(scenarioId))
|
|
return result[scenarioId];
|
|
|
|
return null;
|
|
}
|
|
|
|
public Dictionary<Guid, LaborMaterialsCostInfo> GetLaborMaterialsSplit(List<Guid> scenarios)
|
|
{
|
|
var result = new Dictionary<Guid, LaborMaterialsCostInfo>();
|
|
if (scenarios == null || scenarios.Count <= 0)
|
|
return result;
|
|
|
|
var data = DbContext.VW_ScenarioAndProxyDetails
|
|
.Where(x => x.ParentID.HasValue && scenarios.Contains(x.ParentID.Value))
|
|
.GroupBy(x => new
|
|
{
|
|
ScenarioId = x.ParentID.Value,
|
|
Type = x.Type
|
|
})
|
|
.Select(x => new
|
|
{
|
|
ScenarioId = x.Key.ScenarioId,
|
|
Type = x.Key.Type,
|
|
BUCost = x.Sum(t => t.Cost)
|
|
}).ToList();
|
|
|
|
foreach (var scenarioId in scenarios)
|
|
{
|
|
if (result.ContainsKey(scenarioId))
|
|
continue;
|
|
|
|
var scenarioData = data.FindAll(x => x.ScenarioId == scenarioId);
|
|
|
|
decimal totalBtUpCostsLabor = 0, totalBtUpCostsMaterials = 0;
|
|
var totalBtUpCostsLaborItem = scenarioData.FirstOrDefault(x => x.Type == ExpenditureCategoryModel.CategoryTypes.Labor.GetHashCode());
|
|
if (totalBtUpCostsLaborItem != null)
|
|
totalBtUpCostsLabor = totalBtUpCostsLaborItem.BUCost ?? 0;
|
|
|
|
var totalBtUpCostsMaterialsItem = scenarioData.FirstOrDefault(x => x.Type == ExpenditureCategoryModel.CategoryTypes.Materials.GetHashCode());
|
|
if (totalBtUpCostsMaterialsItem != null)
|
|
totalBtUpCostsMaterials = totalBtUpCostsMaterialsItem.BUCost ?? 0;
|
|
|
|
result.Add(scenarioId, new LaborMaterialsCostInfo()
|
|
{
|
|
Labor = totalBtUpCostsLabor,
|
|
Materials = totalBtUpCostsMaterials,
|
|
Total = scenarioData.Sum(x => x.BUCost ?? 0)
|
|
});
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public DateTime? GetROIDate(Scenario scenario, List<CostSaving> items)
|
|
{
|
|
if (scenario == null)
|
|
return null;
|
|
|
|
return GetROIDate(scenario.CostSavings, scenario.BUDirectCosts, items);
|
|
}
|
|
|
|
public DateTime? GetROIDate(decimal? scenarioCostSavings, decimal? scenarioBUDirectCosts, List<CostSaving> items)
|
|
{
|
|
if (!scenarioCostSavings.HasValue)
|
|
return null;
|
|
|
|
if (scenarioBUDirectCosts > scenarioCostSavings)
|
|
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 >= scenarioBUDirectCosts)
|
|
{
|
|
return new DateTime(costSaving.Year, costSaving.Month, 1);
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public List<ScenarioCostSavingModel> GetScenarioCostSavingModelItems(IEnumerable<CostSaving> costSavings)
|
|
{
|
|
var costSavingItems = new List<ScenarioCostSavingModel>();
|
|
|
|
if (costSavings != null && costSavings.Count() > 0)
|
|
{
|
|
foreach (var year in costSavings.OrderBy(x => x.Year).Select(x => x.Year).Distinct())
|
|
{
|
|
var yearSaving = new ScenarioCostSavingModel()
|
|
{
|
|
Year = year,
|
|
Costs = new decimal?[13]
|
|
};
|
|
|
|
foreach (var cost in costSavings.Where(x => x.Year == year))
|
|
yearSaving.Costs[cost.Month] = cost.Cost;
|
|
|
|
yearSaving.Costs[0] = yearSaving.Costs.Sum(x => x ?? 0);
|
|
costSavingItems.Add(yearSaving);
|
|
}
|
|
}
|
|
|
|
return costSavingItems;
|
|
}
|
|
|
|
public List<CostSaving> GetCostSavings(CostSavingSnapshotModel model)
|
|
{
|
|
var costSavingItems = new List<CostSaving>();
|
|
|
|
if (model != null && model.CostSavingItems != null)
|
|
{
|
|
foreach (var yearItem in model.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
|
|
};
|
|
costSavingItems.Add(costSaving);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return costSavingItems;
|
|
}
|
|
|
|
public Scenario GetActualScenario(Guid projectId)
|
|
{
|
|
return DbContext.Scenarios.FirstOrDefault(x => x.ParentId == projectId && x.Type == (int)ScenarioType.Actuals);
|
|
}
|
|
|
|
public List<Scenario> GetScenarios(IEnumerable<Guid> scenarios, bool readOnly = false)
|
|
{
|
|
if (scenarios == null || !scenarios.Any())
|
|
return new List<Scenario>();
|
|
|
|
var query = DbContext.Scenarios.Where(x => scenarios.Contains(x.Id));
|
|
if (readOnly)
|
|
query = query.AsNoTracking();
|
|
|
|
return query.ToList();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Return corresponding actual scenarios for specified list of (portfolio or other) scenarios
|
|
/// </summary>
|
|
/// <param name="scenarios">Source scenario list</param>
|
|
/// <returns>Pairs of portfolio-actual scenarios</returns>
|
|
public Dictionary<Guid, Guid> GetActualScenariosByPortfolioScenarios(List<Guid> portfolioScenarios)
|
|
{
|
|
var qry =
|
|
(from sc in DbContext.Scenarios
|
|
join sca in DbContext.Scenarios on sc.ParentId equals sca.ParentId
|
|
where sca.Type == (int)ScenarioType.Actuals
|
|
select new
|
|
{
|
|
ForecastScenarioId = sc.Id,
|
|
ActualScenarioId = sca.Id
|
|
});
|
|
|
|
if ((portfolioScenarios != null) && (portfolioScenarios.Count > 0))
|
|
qry = qry.Where(x => portfolioScenarios.Contains(x.ForecastScenarioId));
|
|
|
|
var result = qry.GroupBy(x => x.ForecastScenarioId)
|
|
.ToDictionary(k => k.Key, grp => grp.Select(a => a.ActualScenarioId).FirstOrDefault());
|
|
return result;
|
|
}
|
|
|
|
public List<ScenarioTeamInfoModel> GetScenarioTeamsInfo(Guid projectId, Guid scenarioId, string userId)
|
|
{
|
|
var scenariosInParent = GetScenariosByParentProject(projectId, ScenarioType.Portfolio).Except(new Guid[] { scenarioId })
|
|
.ToList();
|
|
//get list of teams that user has access to using the standard security
|
|
var teamAccess = SecurityManager.GetTeamPermissionsForUser(DbContext, userId, null);
|
|
|
|
var teamsInfo = DbContext.Team2Project
|
|
.AsNoTracking()
|
|
.Where(x => x.Project.Scenarios.Any(s => s.Id == scenarioId)).ToList()
|
|
.Select(x => new ScenarioTeamInfoModel()
|
|
{
|
|
Id = x.TeamId,
|
|
Name = x.Team.Name,
|
|
PlannedCapacityScenarioId = x.Team.PlannedCapacityScenarioId,
|
|
Allocation = 0,
|
|
CanBeDeleted = !DbContext.TeamAllocations.Any(t => scenariosInParent.Contains(t.ScenarioId) && t.Quantity > 0 && t.TeamId == x.TeamId),
|
|
}).ToList();
|
|
foreach (var team in teamsInfo)
|
|
{
|
|
team.IsAccessible = teamAccess.ContainsKey(team.Id) && teamAccess[team.Id] == AccessLevel.Write;
|
|
team.CanBeDeleted = team.CanBeDeleted && team.IsAccessible;
|
|
}
|
|
|
|
return teamsInfo;
|
|
}
|
|
/// <summary>
|
|
/// Loads from database and returns teams info for each of specified scenarios.
|
|
/// </summary>
|
|
/// <param name="scenarios">
|
|
/// A dictionary of <scenario.Id, project.Id&rt; pairs for those scenarios which info we're going to get.
|
|
/// Key = scenario.Id. Value = project.Id.
|
|
/// </param>
|
|
/// <param name="userId">Currently logged-in user.Id.</param>
|
|
/// <returns>Dictionary with a Key=Scenario.Id, Value=List of teams info for each team referenced to project.</returns>
|
|
public Dictionary<Guid, List<ScenarioTeamInfoModel>> GetScenarioTeamsInfo(Dictionary<Guid, Guid?> scenarios, string userId)
|
|
{
|
|
var result = new Dictionary<Guid, List<ScenarioTeamInfoModel>>();
|
|
if (scenarios == null || !scenarios.Any())
|
|
return result;
|
|
var projectIds = scenarios.Values.Select(t => t ?? Guid.Empty);
|
|
|
|
var scenariosInParent = LoadScenarioIdsByParentProject(projectIds, ScenarioType.Portfolio);
|
|
//get list of teams that user has access to using the standard security
|
|
var teamAccess = SecurityManager.GetTeamPermissionsForUser(DbContext, userId, null);
|
|
|
|
foreach (var scenario in scenarios)
|
|
{
|
|
var projectId = scenario.Value.Value;
|
|
// remove current scenario from all project scenarios
|
|
var otherScenarios = new List<Guid>();
|
|
if (scenariosInParent.ContainsKey(projectId))
|
|
otherScenarios = scenariosInParent[projectId].Except(new List<Guid> { scenario.Key }).ToList();
|
|
|
|
// need to take teams by project because if scenario saved as draft it has no saved teams 2 scenario
|
|
var teamsInfo = DbContext.Team2Project.Include(tp => tp.Team)
|
|
.AsNoTracking().Where(x => x.ProjectId == projectId).ToList()
|
|
.Select(x => new ScenarioTeamInfoModel()
|
|
{
|
|
Id = x.TeamId,
|
|
Name = x.Team.Name,
|
|
PlannedCapacityScenarioId = x.Team.PlannedCapacityScenarioId,
|
|
Allocation = 0,
|
|
CanBeDeleted = !DbContext.TeamAllocations.Any(t => otherScenarios.Contains(t.ScenarioId) && t.Quantity > 0 && t.TeamId == x.TeamId),
|
|
}).ToList();
|
|
foreach (var team in teamsInfo)
|
|
{
|
|
team.IsAccessible = teamAccess.ContainsKey(team.Id) && teamAccess[team.Id] == AccessLevel.Write;
|
|
team.CanBeDeleted = team.CanBeDeleted && team.IsAccessible;
|
|
}
|
|
result.Add(scenario.Key, teamsInfo);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
public List<ScenarioTeamInfoModel> GetScenarioTeamsInfo(Dictionary<Guid, short> teams, Guid projectId, Guid scenarioId, string userId)
|
|
{
|
|
if (teams == null || teams.Count <= 0)
|
|
return new List<ScenarioTeamInfoModel>();
|
|
|
|
var scenariosInParent = GetScenariosByParentProject(projectId, ScenarioType.Portfolio).Except(new Guid[] { scenarioId })
|
|
.ToList();
|
|
|
|
//get list of teams that user has access to using the standard security
|
|
var teamAccess = SecurityManager.GetTeamPermissionsForUser(DbContext, userId, null);
|
|
|
|
var teamsInfo = DbContext.Teams
|
|
.AsNoTracking()
|
|
.Where(x => teams.Keys.Contains(x.Id)).ToList()
|
|
.Select(x => new ScenarioTeamInfoModel()
|
|
{
|
|
Id = x.Id,
|
|
Name = x.Name,
|
|
PlannedCapacityScenarioId = x.PlannedCapacityScenarioId,
|
|
Allocation = 0,
|
|
CanBeDeleted = !DbContext.TeamAllocations.Any(t => scenariosInParent.Contains(t.ScenarioId) && t.Quantity > 0 && t.TeamId == x.Id),
|
|
}).ToList();
|
|
|
|
foreach (var team in teamsInfo)
|
|
{
|
|
team.Allocation = teams[team.Id];
|
|
team.IsAccessible = teamAccess.ContainsKey(team.Id) && teamAccess[team.Id] == AccessLevel.Write;
|
|
team.CanBeDeleted = team.CanBeDeleted && team.IsAccessible;
|
|
}
|
|
|
|
return teamsInfo;
|
|
}
|
|
|
|
public Scenario GetBlankTemplate(bool isBottomUp)
|
|
{
|
|
var scenario = DataTable.FirstOrDefault(x => x.IsBottomUp == isBottomUp &&
|
|
x.Type == (int)ScenarioType.Template &&
|
|
!DbContext.ScenarioDetail.Any(s => s.ParentID == x.Id));
|
|
|
|
return scenario;
|
|
}
|
|
|
|
public List<ScenarioCalendarSelectItemTeamModel> GetTeamsWithExpenditures()
|
|
{
|
|
var teams = DbContext.Teams.AsNoTracking()
|
|
.Select(x => new ScenarioCalendarSelectItemTeamModel()
|
|
{
|
|
Id = x.Id,
|
|
Name = x.Name,
|
|
Expenditures = DbContext.ScenarioDetail
|
|
.Where(s => (x.PlannedCapacityScenarioId.HasValue && s.ParentID == x.PlannedCapacityScenarioId) || (x.ActualCapacityScenarioId.HasValue && s.ParentID == x.ActualCapacityScenarioId))
|
|
.Select(s => s.ExpenditureCategoryId)
|
|
.Distinct()
|
|
.ToList()
|
|
// do not move this filter condition to sql query,
|
|
// because SQL Server tries to filter by expenditure without index and it is quite slow query
|
|
.Where(s => s.HasValue).Select(s => s.Value).ToList()
|
|
})
|
|
.ToList();
|
|
|
|
return teams;
|
|
}
|
|
|
|
private List<ScenarioDetailsListItem> GetScenarioDetailsItems(IEnumerable<VW_ScenarioAndProxyDetails> forecastDetails)
|
|
{
|
|
if (forecastDetails == null || forecastDetails.Count() <= 0)
|
|
return null;
|
|
|
|
var categoriesId = forecastDetails.Where(x => x.ExpenditureCategoryId.HasValue).Select(x => x.ExpenditureCategoryId.Value).Distinct().ToList();
|
|
Dictionary<Guid, ExpenditureDetail> categories;
|
|
using (var expCatManager = new ExpenditureCategoryManager(DbContext))
|
|
{
|
|
categories = expCatManager.GetExpenditureDetails(true, categoriesId);
|
|
}
|
|
return forecastDetails.Select(x => new ScenarioDetailsListItem()
|
|
{
|
|
Id = x.Id,
|
|
DetailCost = x.Cost ?? 0,
|
|
CategoryType = (ExpenditureCategoryModel.CategoryTypes)(categories[x.ExpenditureCategoryId.Value].Type),
|
|
ParentId = x.ParentID ?? Guid.Empty,
|
|
WeekEndingDate = x.WeekEndingDate
|
|
}).ToList();
|
|
}
|
|
|
|
public void RecalculateScenarioActuals(Guid actualScenarioId, List<long> weekEndingDates, List<Guid> expenditureCategoryIds, Dictionary<Guid, ExpenditureDetail> expenditures, Dictionary<Guid, Dictionary<Guid, IEnumerable<RateModel>>> rates)
|
|
{
|
|
var prManager = new PeopleResourcesManager(DbContext);
|
|
var weekEndings = new List<DateTime>();
|
|
|
|
if (weekEndingDates != null && weekEndingDates.Count > 0)
|
|
weekEndings = weekEndingDates.Select(x => Utils.ConvertFromUnixDate(x)).ToList();
|
|
|
|
var actualScenario = DbContext.Scenarios.FirstOrDefault(t => t.Id == actualScenarioId);
|
|
var project = GetProjectByScenario(actualScenarioId, true);
|
|
var forecastScenarios = GetScenarios4Project(project.Id, ScenarioType.Portfolio, null);
|
|
var forecastScenariosIds = forecastScenarios.Select(x => x.Id);
|
|
var forecastScenarioDetails = GetScenarioDetails(forecastScenariosIds, readOnly: true).GroupBy(x => x.ParentID.Value)
|
|
.ToDictionary(x => x.Key, g => g.ToList());
|
|
var actualScenarioDetails = GetScenarioDetails(actualScenarioId);
|
|
var actualScenarioDetails2Recalculate = actualScenarioDetails;
|
|
if (expenditureCategoryIds != null && expenditureCategoryIds.Count > 0)
|
|
actualScenarioDetails2Recalculate = actualScenarioDetails2Recalculate.Where(x => x.ExpenditureCategoryId.HasValue && expenditureCategoryIds.Contains(x.ExpenditureCategoryId.Value)).ToList();
|
|
if (weekEndings != null && weekEndings.Count > 0)
|
|
actualScenarioDetails2Recalculate = actualScenarioDetails2Recalculate.Where(x => x.WeekEndingDate.HasValue && weekEndings.Contains(x.WeekEndingDate.Value)).ToList();
|
|
|
|
var resourceActuals = prManager.GetResourceActualAllocationsByScenario(actualScenarioId);
|
|
|
|
RecalculateScenarioActuals(actualScenario, project, actualScenarioDetails, resourceActuals, actualScenarioDetails2Recalculate, forecastScenarios, forecastScenarioDetails, expenditures, rates);
|
|
}
|
|
|
|
public void RecalculateScenarioActuals(Scenario actualScenario, Project project, IEnumerable<ScenarioDetail> actualScenarioDetails, List<ResourceActualAllocationModel> resourceActuals, IEnumerable<ScenarioDetail> actualScenarioDetails2Recalculate, IEnumerable<Scenario> forecastScenariosToUpdate, Dictionary<Guid, List<ScenarioDetail>> forecastScenariosDetails, Dictionary<Guid, ExpenditureDetail> expenditures, Dictionary<Guid, Dictionary<Guid, IEnumerable<RateModel>>> rates)
|
|
{
|
|
#region Arguments Validation
|
|
|
|
if (actualScenario == null)
|
|
throw new ArgumentNullException(nameof(actualScenario));
|
|
|
|
if (project == null)
|
|
throw new ArgumentNullException(nameof(project));
|
|
|
|
if (expenditures == null)
|
|
throw new ArgumentNullException(nameof(expenditures));
|
|
|
|
#endregion
|
|
|
|
var resourceActualsDictionary = resourceActuals != null ? resourceActuals.GroupBy(x => x.ScenarioDetailId).ToDictionary(x => x.Key, g => g.ToList()) : new Dictionary<Guid, List<ResourceActualAllocationModel>>();
|
|
|
|
#region Recalculate Scenario Details
|
|
|
|
if (actualScenarioDetails2Recalculate != null && actualScenarioDetails2Recalculate.Any())
|
|
{
|
|
var actualScenarioRates = rates.ContainsKey(actualScenario.Id) ? rates[actualScenario.Id] : null;
|
|
foreach (var scenarioDetail in actualScenarioDetails2Recalculate)
|
|
{
|
|
if (resourceActualsDictionary.ContainsKey(scenarioDetail.Id))
|
|
{
|
|
var newQuantity = resourceActualsDictionary[scenarioDetail.Id].Sum(x => x.Quantity);
|
|
var rateValue = RateManager.GetRateValue(actualScenarioRates, scenarioDetail.ExpenditureCategoryId.Value, scenarioDetail.WeekEndingDate.Value);
|
|
|
|
scenarioDetail.Quantity = newQuantity;
|
|
scenarioDetail.Cost = scenarioDetail.Quantity * rateValue;
|
|
}
|
|
else
|
|
{
|
|
scenarioDetail.Quantity = 0;
|
|
scenarioDetail.Cost = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Recalculate Actual Scenario Dates
|
|
|
|
if (actualScenario != null)
|
|
{
|
|
var minDate = actualScenarioDetails != null ? actualScenarioDetails.Min(t => t.WeekEndingDate) : null;
|
|
var maxDate = actualScenarioDetails != null ? actualScenarioDetails.Max(t => t.WeekEndingDate) : null;
|
|
if (minDate.HasValue && minDate < (actualScenario.StartDate ?? DateTime.MaxValue))
|
|
actualScenario.StartDate = minDate.Value.Date;
|
|
if (maxDate.HasValue && maxDate > (actualScenario.EndDate ?? DateTime.MinValue))
|
|
actualScenario.EndDate = maxDate.Value.Date;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Update all forecast scenarios Fin info fields
|
|
if (forecastScenariosToUpdate == null || !forecastScenariosToUpdate.Any())
|
|
return;
|
|
|
|
var actualItems = actualScenarioDetails != null ? actualScenarioDetails.Select(x => new ScenarioDetailsListItem
|
|
{
|
|
Id = x.Id,
|
|
DetailCost = x.Cost ?? 0,
|
|
CategoryType = expenditures.ContainsKey(x.ExpenditureCategoryId.Value) ? (ExpenditureCategoryModel.CategoryTypes)expenditures[x.ExpenditureCategoryId.Value].Type : ExpenditureCategoryModel.CategoryTypes.Undefined,
|
|
ParentId = x.ParentID ?? Guid.Empty,
|
|
WeekEndingDate = x.WeekEndingDate
|
|
}).ToList() : new List<ScenarioDetailsListItem>();
|
|
|
|
foreach (var scenario in forecastScenariosToUpdate)
|
|
{
|
|
var forecastDetails4Scenario = (forecastScenariosDetails != null && forecastScenariosDetails.ContainsKey(scenario.Id)) ? forecastScenariosDetails[scenario.Id].Select(x => new ScenarioDetailsListItem
|
|
{
|
|
Id = x.Id,
|
|
DetailCost = x.Cost ?? 0,
|
|
CategoryType = expenditures.ContainsKey(x.ExpenditureCategoryId.Value) ? (ExpenditureCategoryModel.CategoryTypes)expenditures[x.ExpenditureCategoryId.Value].Type : ExpenditureCategoryModel.CategoryTypes.Undefined,
|
|
ParentId = x.ParentID ?? Guid.Empty,
|
|
WeekEndingDate = x.WeekEndingDate
|
|
}).ToList() : new List<ScenarioDetailsListItem>();
|
|
|
|
SetBottomUpCosts(scenario, project.IsRevenueGenerating, actualItems, forecastDetails4Scenario, actualScenario);
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
public List<ExpenditureCategory> GetExpendituresInScenarios(IEnumerable<Guid> scenarios, bool superECOnly)
|
|
{
|
|
if (scenarios == null || !scenarios.Any())
|
|
return new List<ExpenditureCategory>();
|
|
|
|
var query = DbContext.ScenarioDetail.Where(x => x.ParentID.HasValue &&
|
|
x.ExpenditureCategoryId.HasValue &&
|
|
scenarios.Contains(x.ParentID.Value))
|
|
.Select(x => x.ExpenditureCategory);
|
|
if (superECOnly)
|
|
query = query.Where(x => !x.AllowResourceAssignment);
|
|
|
|
var categories = query.Distinct().ToList();
|
|
|
|
return categories;
|
|
}
|
|
|
|
public bool CheckScenarioHasNonAllocatedEC(IEnumerable<Guid> scenarioIds)
|
|
{
|
|
if (scenarioIds == null || !scenarioIds.Any())
|
|
return false;
|
|
|
|
var ecQuery = DbContext.ScenarioDetail.Where(x => x.ParentID.HasValue && scenarioIds.Contains(x.ParentID.Value))
|
|
.Select(x => new
|
|
{
|
|
ScenarioId = x.ParentID.Value,
|
|
ExpenditureCategoryId = x.ExpenditureCategoryId.Value
|
|
});
|
|
|
|
var anyNonAllocatedEC = ecQuery.Any(x => !DbContext.PeopleResourceAllocations
|
|
.Any(s => s.ScenarioId == x.ScenarioId &&
|
|
s.ExpenditureCategoryId == x.ExpenditureCategoryId));
|
|
|
|
return anyNonAllocatedEC;
|
|
}
|
|
|
|
public IEnumerable<Models.Scenarios.ScenarioInfo> GetScenariosCompareModel(IEnumerable<Guid> scenarioIds, string userId, decimal overUnderCoeff)
|
|
{
|
|
var scenarios = DataTable
|
|
.AsNoTracking()
|
|
.Where(x => scenarioIds.Contains(x.Id)).OrderByDescending(x => x.Status).ThenBy(x => x.Name)
|
|
.Include(x => x.PeopleResourceAllocations)
|
|
.ToList();
|
|
|
|
var scenarioInfos =
|
|
(from scenario in scenarios
|
|
let useLMMargin = (scenario.UseLMMargin ?? 0) == 1
|
|
let costSavings = scenario.CostSavings ?? 0
|
|
let buDirectCosts = (useLMMargin ? scenario.BUDirectCosts_LM : scenario.BUDirectCosts) ?? 0
|
|
let expectedMargin = (useLMMargin ? scenario.ExpectedGrossMargin_LM : scenario.ExpectedGrossMargin) ?? 0
|
|
let calculatedMargin = (useLMMargin ? scenario.CalculatedGrossMargin_LM : scenario.CalculatedGrossMargin) ?? 0
|
|
let projectedRevenue = scenario.ProjectedRevenue ?? 0
|
|
let netImpact = projectedRevenue + costSavings - buDirectCosts
|
|
select new Models.Scenarios.ScenarioInfo
|
|
{
|
|
Id = scenario.Id,
|
|
Name = scenario.Name,
|
|
IsActive = scenario.Status == ScenarioStatus.Active.GetHashCode(),
|
|
BottomUpExpense = buDirectCosts,
|
|
CalculatedMargin = calculatedMargin,
|
|
CalculatedNetImpact = netImpact,
|
|
ScoringNetImpact = netImpact,
|
|
CostSavings = costSavings,
|
|
ExpectedMargin = expectedMargin,
|
|
ProjectedRevenue = projectedRevenue,
|
|
StartDate = scenario.StartDate,
|
|
EndDate = scenario.EndDate,
|
|
Weeks = scenario.Duration ?? 0
|
|
}).ToArray();
|
|
|
|
var projectTeams = scenarios.First().Project.Team2Project.ToList();
|
|
var teamIds = projectTeams.Select(x => x.TeamId).ToList();
|
|
|
|
foreach (var scenarioInfo in scenarioInfos)
|
|
{
|
|
var allocationInfo = GetFullAllocationInfoByScenario(scenarioInfo.Id, userId, true);
|
|
var categories = GetCategoriesInScenario(scenarioInfo.Id);
|
|
var weekendings = FiscalCalendarManager.GetWeekendingsByRange(scenarioInfo.StartDate.Value, scenarioInfo.EndDate, DbContext)
|
|
.Select(x => new { Date = x, UxDate = Utils.ConvertToUnixDate(x), UxDateStr = Utils.ConvertToUnixDate(x).ToString() });
|
|
|
|
decimal overAllocation = 0;
|
|
decimal underAllocation = 0;
|
|
|
|
foreach (var categoryId in categories.Keys)
|
|
foreach (var teamId in teamIds)
|
|
{
|
|
foreach (var weekEndingDate in weekendings)
|
|
{
|
|
if (!allocationInfo.ContainsKey(categoryId.ToString()))
|
|
{
|
|
continue;
|
|
}
|
|
var expenditureDetail = allocationInfo[categoryId.ToString()];
|
|
if (!expenditureDetail.Teams.ContainsKey(teamId.ToString()))
|
|
{
|
|
continue;
|
|
}
|
|
decimal teamProjectAllocation = 0;
|
|
decimal teamAllocation = 0;
|
|
var teamDetails = expenditureDetail.Teams[teamId.ToString()];
|
|
if (teamDetails.QuantityValues.ContainsKey(weekEndingDate.UxDateStr))
|
|
{
|
|
teamProjectAllocation = teamDetails.QuantityValues[weekEndingDate.UxDateStr];
|
|
}
|
|
var unAllocatedResources = teamDetails.AllResources
|
|
.Except(teamDetails.Resources)
|
|
.Where(x =>
|
|
x.Value.StartDate < weekEndingDate.UxDate &&
|
|
(!x.Value.EndDate.HasValue || x.Value.EndDate > weekEndingDate.UxDate)
|
|
);
|
|
var currentWeekEndingDate = weekEndingDate.UxDateStr;
|
|
Func<Dictionary<string, decimal>, decimal> getWeekEndingValue = (values) =>
|
|
{
|
|
return values.ContainsKey(currentWeekEndingDate) ? values[currentWeekEndingDate] : 0;
|
|
};
|
|
var poolCapacity = unAllocatedResources.Sum(x => getWeekEndingValue(x.Value.CapacityQuantityValues));
|
|
var teamResourcesAllocation = teamDetails.Resources.Sum(x => getWeekEndingValue(x.Value.QuantityValues));
|
|
teamAllocation = poolCapacity - (teamProjectAllocation - teamResourcesAllocation);
|
|
|
|
if (teamAllocation < 0)
|
|
{
|
|
overAllocation += Math.Abs(teamAllocation);
|
|
}
|
|
else
|
|
{
|
|
underAllocation += teamAllocation;
|
|
}
|
|
|
|
foreach (var resource in teamDetails.Resources.Select(x => x.Value))
|
|
{
|
|
var resourceCapacity = getWeekEndingValue(resource.CapacityQuantityValues);
|
|
var projectAllocation = getWeekEndingValue(resource.QuantityValues);
|
|
var resourceAllocation = resourceCapacity - projectAllocation;
|
|
|
|
if (resourceAllocation < 0)
|
|
{
|
|
overAllocation += Math.Abs(resourceAllocation);
|
|
}
|
|
else
|
|
{
|
|
underAllocation += resourceAllocation;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
scenarioInfo.X = overUnderCoeff * overAllocation + underAllocation;
|
|
}
|
|
|
|
var MaxN = scenarioInfos.Select(x => x.ScoringNetImpact).Union(new[] { 1m }).Max();
|
|
var MinN = scenarioInfos.Min(x => x.ScoringNetImpact);
|
|
var DN = MaxN - MinN;
|
|
scenarioInfos.ToList().ForEach(x => x.ScoringNetImpact += DN);
|
|
|
|
var MaxNc = scenarioInfos.Max(x => x.ScoringNetImpact);
|
|
|
|
var minX = scenarioInfos.Min(s => s.X);
|
|
var optmialScenarioId = Guid.Empty;
|
|
var maxScore = 0m;
|
|
|
|
foreach (var scenarioInfo in scenarioInfos)
|
|
{
|
|
var netImpactPart = MaxNc != 0 ? (2m / 3m * (scenarioInfo.ScoringNetImpact / MaxNc)) : 0;
|
|
var scorePart = 1m / 3m * (scenarioInfo.X != 0 ? minX / scenarioInfo.X : 0);
|
|
scenarioInfo.Score = (netImpactPart + scorePart) * 100;
|
|
if (scenarioInfo.Score > maxScore)
|
|
{
|
|
maxScore = scenarioInfo.Score;
|
|
optmialScenarioId = scenarioInfo.Id;
|
|
}
|
|
}
|
|
var optimalScenario = scenarioInfos.FirstOrDefault(x => x.Id == optmialScenarioId);
|
|
optimalScenario.IsOptimal = true;
|
|
|
|
return scenarioInfos;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns Types for specified scenarios. Bool value in the result is the value of IsBottomUp prop for scenario
|
|
/// </summary>
|
|
/// <param name="scenarios">Must be specified</param>
|
|
/// <returns></returns>
|
|
public Dictionary<Guid, bool> GetScenarioTypes(List<Guid> scenarios)
|
|
{
|
|
if (scenarios == null)
|
|
throw new ArgumentNullException("scenarios");
|
|
|
|
if (scenarios.Count < 1)
|
|
throw new ArgumentException("scenarios");
|
|
|
|
var result = DbContext.Scenarios.AsNoTracking()
|
|
.Where(x => scenarios.Contains(x.Id))
|
|
.Select(x => new { x.Id, x.IsBottomUp })
|
|
.ToDictionary(x => x.Id, x => x.IsBottomUp);
|
|
return result;
|
|
}
|
|
|
|
public Dictionary<Guid, List<Guid>> GetResourceAllocatedScenarioTeams(List<Guid> scenarios)
|
|
{
|
|
if (scenarios == null)
|
|
throw new ArgumentNullException("scenarios");
|
|
|
|
if (scenarios.Count < 1)
|
|
return new Dictionary<Guid, List<Guid>>();
|
|
|
|
var result = DbContext.VW_ProjectTeamsResourceAllocated.AsNoTracking()
|
|
.Where(x => scenarios.Contains(x.ScenarioId))
|
|
.Select(x => new
|
|
{
|
|
ScenarioId = x.ScenarioId,
|
|
TeamId = x.TeamId
|
|
}).ToList()
|
|
.GroupBy(x => x.ScenarioId).ToDictionary(k => k.Key, v => v.Select(z => z.TeamId).ToList());
|
|
|
|
return result;
|
|
}
|
|
|
|
public Project GetProjectByScenario(Guid scenarioId, bool readOnly = false)
|
|
{
|
|
if (scenarioId == Guid.Empty)
|
|
return null;
|
|
|
|
var projects = GetProjectsByScenarios(new List<Guid> { scenarioId }, readOnly);
|
|
if (projects.ContainsKey(scenarioId))
|
|
return projects[scenarioId];
|
|
|
|
return null;
|
|
}
|
|
|
|
public Dictionary<Guid, Project> GetProjectsByScenarios(IEnumerable<Guid> scenarios, bool readOnly = false)
|
|
{
|
|
if (scenarios == null || !scenarios.Any())
|
|
return new Dictionary<Guid, Project>();
|
|
|
|
var query = from scenario in DbContext.Scenarios
|
|
join project in DbContext.Projects on scenario.ParentId equals project.Id
|
|
where scenarios.Contains(scenario.Id)
|
|
select new
|
|
{
|
|
ScenarioId = scenario.Id,
|
|
Project = project
|
|
};
|
|
|
|
if (readOnly)
|
|
query = query.AsNoTracking();
|
|
|
|
return query.ToArray().ToDictionary(x => x.ScenarioId, g => g.Project);
|
|
}
|
|
}
|
|
|
|
#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<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; }
|
|
/// <summary>
|
|
/// Gets or sets a value indicating that Actuals values should be used for past data if exist, otherwise forecast data will be used.
|
|
/// <b>true</b> - do not replace old forecast values with actual values in the past.
|
|
/// <b>false</b> - replace old forecast values with actual values in the past.
|
|
/// </summary>
|
|
public bool KeepForecastValues { get; set; }
|
|
#endregion
|
|
|
|
#region Constructors
|
|
public AdjustScenarioDetailsDistribution(EnVisageEntities dbcontext)
|
|
{
|
|
dbContext = dbcontext;
|
|
}
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
/// <summary>
|
|
/// Distribution recalculation.
|
|
/// </summary>
|
|
|
|
public Dictionary<Guid, List<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<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<ScenarioDetailsListItem>(NewPeriods);
|
|
for (var weekIndex = 0; weekIndex < PreviousWeeks; weekIndex++)
|
|
{
|
|
var currentItemQuantity = currentItem.Value.Count > weekIndex ? currentItem.Value[weekIndex].Quantity : 0;
|
|
newExpCatItem.Add(new ScenarioDetailsListItem
|
|
{
|
|
ExpenditureCategoryId = currentItem.Value[0].ExpenditureCategoryId,
|
|
ExpenditureCatagoryAllowsResource = currentItem.Value[0].ExpenditureCatagoryAllowsResource,
|
|
ParentId = currentItem.Value[0].ParentId,
|
|
WeekOrdinal = weekIndex + 1,
|
|
WeekEndingDate = FiscalCalendarWeeks[weekIndex].EndDate,
|
|
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 && !KeepForecastValues)
|
|
{
|
|
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 = currentItemQuantity;
|
|
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++)
|
|
{
|
|
var currentItemQuantity = currentItem.Value.Count > weekIndex ? currentItem.Value[weekIndex].Quantity : 0;
|
|
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<ScenarioDetailsListItem>());
|
|
if (result[currentItem.Key].Count <= weekIndex)
|
|
result[currentItem.Key].Add(new ScenarioDetailsListItem());
|
|
var newListItem = result[currentItem.Key][weekIndex];
|
|
if (weekIndex < currentItem.Value.Count)
|
|
{
|
|
newListItem.Quantity = currentItemQuantity; // 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.ExpenditureCatagoryAllowsResource = currentItem.Value[0].ExpenditureCatagoryAllowsResource;
|
|
newListItem.WeekOrdinal = weekIndex + 1;
|
|
//newListItem.DetailCost = cost;
|
|
newListItem.WeekEndingDate = FiscalCalendarWeeks[weekIndex].EndDate;
|
|
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<ScenarioDetailsListItem>());
|
|
if (result[currentItem.Key].Count <= weekIndex)
|
|
result[currentItem.Key].Add(new 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.ExpenditureCatagoryAllowsResource = currentItem.Value[0].ExpenditureCatagoryAllowsResource;
|
|
newListItem.WeekOrdinal = weekIndex + 1;
|
|
newListItem.DetailCost = cost;
|
|
newListItem.WeekEndingDate = FiscalCalendarWeeks[weekIndex].EndDate;
|
|
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.ExpenditureCatagoryAllowsResource = currentItem.Value[0].ExpenditureCatagoryAllowsResource;
|
|
newListItem.WeekOrdinal = weekIndex + 1;
|
|
newListItem.DetailCost = cost;
|
|
newListItem.WeekEndingDate = FiscalCalendarWeeks[weekIndex].EndDate;
|
|
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<ScenarioDetailsListItem>());
|
|
if (result[currentItem.Key].Count <= processingWeek)
|
|
result[currentItem.Key].Add(new ScenarioDetailsListItem());
|
|
var newListItem = result[currentItem.Key][processingWeek];
|
|
|
|
var currentItemQuantity = currentItem.Value.Count > weekIndex ? currentItem.Value[weekIndex].Quantity : 0;
|
|
|
|
if (distributionStep < periodScale)
|
|
{
|
|
if (distributionStep < 1)
|
|
dbNewVal += (currentItem.Value.Count - 1 > weekIndex ? currentItem.Value[weekIndex - 1].Quantity : 0) * distributionStep;
|
|
if (distributionStep == 1 || ((distributionStep % 1) == 0 && distributionStep < periodScale))
|
|
{
|
|
dbNewVal += currentItemQuantity; // currentItem.Value[weekIndex].Quantity;
|
|
distributionStep++;
|
|
}
|
|
else
|
|
{
|
|
if (periodScale - distributionStep >= 1)
|
|
{
|
|
dbNewVal += currentItemQuantity;
|
|
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.ExpenditureCatagoryAllowsResource = currentItem.Value[0].ExpenditureCatagoryAllowsResource;
|
|
newListItem.WeekOrdinal = processingWeek + 1;
|
|
newListItem.DetailCost = cost;
|
|
newListItem.WeekEndingDate = FiscalCalendarWeeks[processingWeek].EndDate;
|
|
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) * currentItemQuantity); //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.ExpenditureCatagoryAllowsResource = currentItem.Value[0].ExpenditureCatagoryAllowsResource;
|
|
newListItem.WeekOrdinal = processingWeek + 1;
|
|
newListItem.DetailCost = cost;
|
|
newListItem.WeekEndingDate = FiscalCalendarWeeks[processingWeek].EndDate;
|
|
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)) * currentItemQuantity); //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.ExpenditureCatagoryAllowsResource = currentItem.Value[0].ExpenditureCatagoryAllowsResource;
|
|
newListItem.WeekOrdinal = processingWeek + 1;
|
|
newListItem.DetailCost = cost;
|
|
newListItem.WeekEndingDate = FiscalCalendarWeeks[processingWeek].EndDate;
|
|
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<ScenarioDetailsListItem>());
|
|
if ((dbNewVal > 0 || result[currentItem.Key][result[currentItem.Key].Count - 1].ExpenditureCategoryId == Guid.Empty) && result[currentItem.Key].Count <= (processingWeek + 1))
|
|
{
|
|
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].ExpenditureCatagoryAllowsResource = currentItem.Value[0].ExpenditureCatagoryAllowsResource;
|
|
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].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].ExpenditureCatagoryAllowsResource = currentItem.Value[0].ExpenditureCatagoryAllowsResource;
|
|
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].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<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 ScenarioDetailsListItem()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ParentId = ScenarioId,
|
|
ExpenditureCategoryId = currentItem.Value[0].ExpenditureCategoryId,
|
|
ExpenditureCatagoryAllowsResource = currentItem.Value[0].ExpenditureCatagoryAllowsResource,
|
|
WeekOrdinal = weekIndex + 1,
|
|
WeekEndingDate = weekEnding,
|
|
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
|
|
} |