2392 lines
116 KiB
C#
2392 lines
116 KiB
C#
using EnVisage.Models;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Data.Entity;
|
|
using System.Web.Mvc;
|
|
using Prevu.Core.Audit.Model;
|
|
using EnVisage.Code.Audit;
|
|
using NLog;
|
|
|
|
namespace EnVisage.Code.BLL
|
|
{
|
|
public class ActivityCalendarManager : IDisposable
|
|
{
|
|
#region Private Variables
|
|
|
|
private readonly EnVisageEntities _dbContext;
|
|
private readonly bool _isContexLocal;
|
|
protected static readonly Logger Logger = LogManager.GetCurrentClassLogger();
|
|
|
|
#endregion
|
|
|
|
#region Properties
|
|
|
|
private EnVisageEntities DbContext => _dbContext;
|
|
|
|
#endregion
|
|
|
|
#region Constructors
|
|
|
|
public ActivityCalendarManager(EnVisageEntities dbContext)
|
|
{
|
|
if (dbContext == null)
|
|
{
|
|
_dbContext = new EnVisageEntities();
|
|
_isContexLocal = true;
|
|
}
|
|
else
|
|
{
|
|
_dbContext = dbContext;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Public Methods
|
|
|
|
public ActivityCalendarSaveModel CopyScenarios(ActivityCalendarSaveModel model, string transactionId)
|
|
{
|
|
#if DEBUG
|
|
var watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
if (model == null)
|
|
throw new ArgumentNullException(nameof(model));
|
|
|
|
if (model.Scenarios == null)
|
|
throw new ArgumentNullException(nameof(model));
|
|
|
|
if (model.SaveAction != SaveAsScenario.New)
|
|
throw new InvalidOperationException($"Incorrect operation for Action = {model.SaveAction} state");
|
|
|
|
if (string.IsNullOrWhiteSpace(model.NewName))
|
|
throw new InvalidOperationException("No names for new scenarios specified");
|
|
|
|
var resultModel = model.Clone();
|
|
|
|
if (model.Scenarios.Count < 1)
|
|
return resultModel;
|
|
|
|
// Variables and data for audit
|
|
EventRequestBaseModel auditRecord;
|
|
|
|
transactionId = Utils.GetOrCreateTransactionId(DbContext, transactionId);
|
|
var userId = SecurityManager.GetUserPrincipal();
|
|
|
|
var scenarioManager = new ScenarioManager(DbContext);
|
|
|
|
var scenarioIds = model.Scenarios.Keys.Select(x => new Guid(x)).ToList();
|
|
var superExpCatsInScenarios = ScenariosContainsSuperExpenditures(scenarioIds);
|
|
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios model validation and ScenariosContainsSuperExpenditures {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios model validation and ScenariosContainsSuperExpenditures {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
var scenarios = scenarioManager.GetScenarios(model.Scenarios.Keys.Select(q => new Guid(q)), true).ToList();
|
|
var scenarioParentIds = scenarios.Select(w => w.ParentId);
|
|
var projectAccessByUserExtended = DbContext.VW_ProjectAccessByUserExtended.Where(q => q.UserId == userId && scenarioParentIds.Contains(q.Id))
|
|
.GroupBy(t=>t.Id).ToDictionary(key=>key.Key, elem=>elem.FirstOrDefault());
|
|
foreach (var scenario in scenarios)
|
|
{
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios foreach first {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios oreach firsts {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
var scenarioId = scenario.Id;
|
|
var targetStatus = model.SetActive ? ScenarioStatus.Active : ScenarioStatus.Inactive;
|
|
var saveType = model.ScenarioType;
|
|
bool updateProjections = model.Scenarios[scenarioId.ToString()].UpdateProjections;
|
|
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios scenarioManager.Loadt {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios scenarioManager.Load {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
|
|
var parentProject = projectAccessByUserExtended.ContainsKey(scenario.ParentId.Value) ? projectAccessByUserExtended[scenario.ParentId.Value] : null;
|
|
if (parentProject == null || !(parentProject.Write > 0))
|
|
continue;
|
|
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios DbContext.VW_ProjectAccessByUserExtended.FirstOrDefault(x => x.Id == scenario.ParentId) {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios DbContext.VW_ProjectAccessByUserExtended.FirstOrDefault(x => x.Id == scenario.ParentId) {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
|
|
// Determine what type of scenario will be set to its copy
|
|
var scenarioContainsSuperExpenditures = superExpCatsInScenarios.ContainsKey(scenarioId) && superExpCatsInScenarios[scenarioId];
|
|
|
|
if (scenarioContainsSuperExpenditures && saveType == ScenarioSaveType.BottomUp && scenario.IsBottomUp == false)
|
|
saveType = ScenarioSaveType.TopDown;
|
|
|
|
// Create copy for scenario
|
|
var newScenario = scenarioManager.CopyTo(scenarioId, null, targetStatus, false, true, model.NewName, saveType, scenario);
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios scenarioManager.CopyTo {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios scenarioManager.CopyTo {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
|
|
if ((newScenario == null) || newScenario.Id.Equals(Guid.Empty))
|
|
throw new BLLException($"Unable to make a copy of the scenario (id = {scenarioId})");
|
|
|
|
var newScenarioIdAsText = newScenario.Id.ToString();
|
|
|
|
if (model.Scenarios.ContainsKey(newScenarioIdAsText))
|
|
throw new BLLException($"A copy of the scenario has the same Id as the original one (id = {scenarioId})");
|
|
|
|
// As a copy of the scenario has new Id, we should move data in the model from source scenario Id to the Id of its copy.
|
|
// Mark copied scenarios as recently created (isNew <- true)
|
|
resultModel.Scenarios.Add(newScenarioIdAsText, resultModel.Scenarios[scenarioId.ToString()]);
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios resultModel.Scenarios.Addo {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios resultModel.Scenarios.Add {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
resultModel.Scenarios[newScenarioIdAsText].isNew = true;
|
|
resultModel.Scenarios.Remove(scenarioId.ToString());
|
|
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges CopyScenarios resultModel.Scenarios.Remove {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges CopyScenarios resultModel.Scenarios.Remove {watch1.ElapsedMilliseconds} ms");
|
|
#endif
|
|
|
|
// Generate audit event records
|
|
auditRecord = new EventRequestBaseModel
|
|
{
|
|
ClassificationKey = "ActivityCalendarSaveAsNewScenario",
|
|
EntityId = newScenario.Id,
|
|
ScenarioId = scenarioId,
|
|
ProjectId = newScenario.ParentId,
|
|
UserId = userId.ToString(),
|
|
NewValue = newScenario.Name,
|
|
Comment = $"Saving from Activity Calendar. New scenario type: '{saveType.ToDisplayValue()}'{(updateProjections ? ". Perform update of Projections" : string.Empty)}"
|
|
};
|
|
AuditProxy.LogEvent(transactionId, auditRecord);
|
|
}
|
|
return resultModel;
|
|
}
|
|
|
|
public void UpdateScenarios(ActivityCalendarSaveModel model, Dictionary<Guid, bool> expenditureTypes,
|
|
Dictionary<Guid, Dictionary<Guid, List<DateRangeNullable>>> resourceTeams, string transactionId)
|
|
{
|
|
#if DEBUG
|
|
var watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
if (model == null)
|
|
throw new ArgumentNullException(nameof(model));
|
|
|
|
if (model.Scenarios == null || model.Scenarios.Count < 1)
|
|
return;
|
|
|
|
// Variables and data for audit
|
|
EventRequestBaseModel auditRecord;
|
|
|
|
transactionId = Utils.GetOrCreateTransactionId(DbContext, transactionId);
|
|
var userId = SecurityManager.GetUserPrincipal().ToString();
|
|
var scenarioEventsTracker = new ScenarioDetailsEventsTracker(ScenarioDetailsEventsTrackerUsageMode.ActivityCalendar, DbContext, userId);
|
|
|
|
var ecManager = (new ExpenditureCategoryManager(DbContext));
|
|
var rateManager = new RateManager(DbContext);
|
|
var scenarioManager = new ScenarioManager(DbContext);
|
|
var projectManager = new ProjectManager(DbContext);
|
|
var teamManager = new TeamManager(DbContext);
|
|
|
|
// Get scenario types and their corresponding actual scenarios
|
|
var scenarioIds = model.Scenarios.Keys.Select(x => new Guid(x)).ToList();
|
|
var scenarioTypes = scenarioManager.GetScenarioTypes(scenarioIds);
|
|
var actualScenarios = scenarioManager.GetActualScenariosByPortfolioScenarios(scenarioIds);
|
|
var expenditures = ecManager.GetExpenditureDetails();
|
|
var allScenarios = scenarioIds.Union(actualScenarios.Values).Distinct().ToList();
|
|
var rates = rateManager.GetRatesDict(allScenarios, rateManager.LoadRatesByScenarios(scenarioIds));
|
|
|
|
// Teams added to projects as new - for all processed projects
|
|
var addedTeamsToProjects = new Dictionary<Scenario, List<Guid>>();
|
|
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges UpdateScenarios Get scenario types and their corresponding actual scenarios has taken {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges UpdateScenarios Get scenario types and their corresponding actual scenarios {watch1.ElapsedMilliseconds} ms");
|
|
#endif
|
|
|
|
#if DEBUG
|
|
var watchAllLoad = new System.Diagnostics.Stopwatch();
|
|
|
|
var watchTeamAllocation = new System.Diagnostics.Stopwatch();
|
|
var watchResourceAllocations = new System.Diagnostics.Stopwatch();
|
|
var watchScenarioDetails = new System.Diagnostics.Stopwatch();
|
|
var watchActuals = new System.Diagnostics.Stopwatch();
|
|
#endif
|
|
|
|
foreach (var scenarioId in scenarioIds)
|
|
{
|
|
#if DEBUG
|
|
watchAllLoad.Start();
|
|
#endif
|
|
if (!scenarioTypes.ContainsKey(scenarioId))
|
|
throw new BLLException($"Unable to get ScenarioType for scenario (id = {scenarioId})");
|
|
|
|
if (!actualScenarios.ContainsKey(scenarioId))
|
|
throw new BLLException($"Cannot find actual scenario for the specified scenarioId: {scenarioId}");
|
|
|
|
// Get scenario data
|
|
var scenario = scenarioManager.Load(scenarioId, true);
|
|
if (scenario == null)
|
|
throw new BLLException($"Scenario not found in the database (id = {scenarioId})");
|
|
|
|
var parentProject = DbContext.VW_ProjectAccessByUserExtended.FirstOrDefault(x => x.Id == scenario.ParentId);
|
|
if (parentProject == null || !(parentProject.Write > 0))
|
|
continue;
|
|
|
|
var weekEndings = FiscalCalendarManager.GetFiscalCalendarWeeksByRange(scenario.StartDate.Value, scenario.EndDate, DbContext).Select(x => x.EndDate).OrderBy(x => x);
|
|
|
|
var scenarioDataSaveModel = model.Scenarios[scenarioId.ToString()];
|
|
var scenarioIsBottomUp = scenarioTypes[scenarioId];
|
|
var actualScenarioId = actualScenarios[scenarioId];
|
|
|
|
#region Generate audit record for scenario saving action
|
|
|
|
if (scenario.ParentId.HasValue)
|
|
scenarioEventsTracker.StartTracking(scenarioId, scenario.ParentId.Value);
|
|
|
|
if (!scenarioDataSaveModel.isNew)
|
|
{
|
|
// Write event. if this scenario was not previously created (by copying of exesting scenario)
|
|
auditRecord = new EventRequestBaseModel
|
|
{
|
|
ClassificationKey = "ActivityCalendarSaveAsCurrentScenario",
|
|
EntityId = scenarioId,
|
|
ScenarioId = scenarioId,
|
|
ProjectId = scenario.ParentId,
|
|
UserId = userId,
|
|
OldValue = scenario.Name,
|
|
Comment = $"Saving from Activity Calendar{(scenarioDataSaveModel.UpdateProjections ? " with update of Projections" : string.Empty)}"
|
|
};
|
|
AuditProxy.LogEvent(transactionId, auditRecord);
|
|
}
|
|
|
|
#endregion
|
|
|
|
// Find out if data save model has changes of actuals and projections for current scenario (look for manual made changes!)
|
|
var scenarioHasActualsToUpdate = AreScenarioActualsChanged(scenarioDataSaveModel);
|
|
var scenarioHasProjectionsToUpdate = AreScenarioProjectionsChanged(scenarioDataSaveModel);
|
|
var scenarioHasTeamAllocationsToUpdate = AreScenarioTeamAllocationsChanged(scenarioDataSaveModel);
|
|
|
|
var scenarioNewTeams = GetScenarioNewTeams(scenario.ParentId.Value, scenarioDataSaveModel);
|
|
var scenarioHasNewTeams = scenarioNewTeams != null && (scenarioNewTeams.Count > 0);
|
|
|
|
// Determine scenario recalculation mode
|
|
var scenarioRecalculationMode = GetScenarioRecalculationMode(model, scenarioId, scenarioIsBottomUp,
|
|
scenarioHasProjectionsToUpdate, scenarioHasTeamAllocationsToUpdate);
|
|
|
|
#if DEBUG
|
|
watchAllLoad.Stop();
|
|
#endif
|
|
|
|
#if DEBUG
|
|
watchTeamAllocation.Start();
|
|
#endif
|
|
if (scenarioHasNewTeams)
|
|
{
|
|
// Add new teams to scenario
|
|
projectManager.UpdateProjectTeams(scenario.ParentId.Value, scenarioNewTeams, null, true);
|
|
CreateTeamAllocations(scenarioId, scenarioDataSaveModel, scenarioNewTeams, weekEndings);
|
|
addedTeamsToProjects.Add(scenario, scenarioNewTeams);
|
|
DbContext.ExecuteBulkSaveChanges();
|
|
}
|
|
#if DEBUG
|
|
watchTeamAllocation.Stop();
|
|
#endif
|
|
#if DEBUG
|
|
watchResourceAllocations.Start();
|
|
#endif
|
|
if (scenarioHasProjectionsToUpdate)
|
|
{
|
|
// Update of resource allocations
|
|
UpdateResourceAllocations(scenarioId, scenarioDataSaveModel, resourceTeams, scenarioEventsTracker);
|
|
DbContext.BulkSaveChanges();
|
|
}
|
|
#if DEBUG
|
|
watchResourceAllocations.Stop();
|
|
#endif
|
|
|
|
if (scenarioRecalculationMode != ScenarioRecalculationMode.None)
|
|
{
|
|
#if DEBUG
|
|
watchScenarioDetails.Start();
|
|
#endif
|
|
var scenarioRates = rates.ContainsKey(scenarioId) ? rates[scenarioId] : null;
|
|
|
|
// Update of Team allocations and Details
|
|
|
|
UpdateScenarioAllocations(scenario, scenarioDataSaveModel, scenarioRecalculationMode, expenditureTypes, scenarioRates, weekEndings, scenarioEventsTracker);
|
|
DbContext.BulkSaveChanges();
|
|
#if DEBUG
|
|
watchScenarioDetails.Stop();
|
|
#endif
|
|
}
|
|
else
|
|
{
|
|
if (scenarioHasTeamAllocationsToUpdate)
|
|
{
|
|
// Update of team allocations with no roll-up changes
|
|
#if DEBUG
|
|
watchTeamAllocation.Start();
|
|
#endif
|
|
|
|
UpdateTeamAllocations(scenarioId, scenarioDataSaveModel, scenarioEventsTracker);
|
|
DbContext.BulkSaveChanges();
|
|
#if DEBUG
|
|
watchTeamAllocation.Start();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
#if DEBUG
|
|
watchActuals.Start();
|
|
#endif
|
|
if (scenarioHasActualsToUpdate)
|
|
{
|
|
var actualScenarioRates = rates.ContainsKey(actualScenarioId) ? rates[actualScenarioId] : null;
|
|
|
|
// Update fo resource actuals
|
|
UpdateResourceActuals(actualScenarioId, scenarioDataSaveModel, actualScenarioRates);
|
|
DbContext.BulkSaveChanges();
|
|
|
|
var actualWeekendings = GetScenarioActualsChangedWeekendings(scenarioDataSaveModel);
|
|
var actualExpenditures = GetScenarioActualsChangedExpenditures(scenarioDataSaveModel);
|
|
scenarioManager.RecalculateScenarioActuals(actualScenarioId, actualWeekendings, actualExpenditures, expenditures, rates);
|
|
DbContext.BulkSaveChanges();
|
|
}
|
|
else
|
|
{
|
|
scenarioManager.SetBottomUpCosts(scenarioId);
|
|
DbContext.BulkSaveChanges();
|
|
}
|
|
#if DEBUG
|
|
watchActuals.Start();
|
|
#endif
|
|
}
|
|
#if DEBUG
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges UpdateScenarios All load Summary {watchAllLoad.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges UpdateScenarios UpdateScenarios All load Summary {watchAllLoad.ElapsedMilliseconds} ms");
|
|
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges UpdateScenarios Team Allocations Save Summary {watchTeamAllocation.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges UpdateScenarios UpdateScenarios Team Allocations Save Summary {watchTeamAllocation.ElapsedMilliseconds} ms");
|
|
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges UpdateScenarios Resource Allocations Save Summary {watchResourceAllocations.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges UpdateScenarios UpdateScenarios Resource Allocations Save Summary {watchResourceAllocations.ElapsedMilliseconds} ms");
|
|
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges UpdateScenarios Scenario Details Save Summary {watchScenarioDetails.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges UpdateScenarios UpdateScenarios Scenario Details Save Summary {watchScenarioDetails.ElapsedMilliseconds} ms");
|
|
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges UpdateScenarios Actuals Save Summary {watchActuals.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges UpdateScenarios UpdateScenarios Actuals Save Summary {watchActuals.ElapsedMilliseconds} ms");
|
|
#endif
|
|
|
|
// Write audit events for added to projects teams
|
|
if (addedTeamsToProjects.Count > 0)
|
|
{
|
|
var allAddedTeams = addedTeamsToProjects.Values.SelectMany(x => x).Distinct();
|
|
var teamNames = teamManager.GetTeamNames(allAddedTeams);
|
|
|
|
if (teamNames != null)
|
|
{
|
|
var teamAddEvents = addedTeamsToProjects.Where(x => x.Value != null).SelectMany(x =>
|
|
x.Value.Where(t => teamNames.ContainsKey(t)).Select(t =>
|
|
new EventRequestBaseModel()
|
|
{
|
|
ClassificationKey = "ProjectTeamsAdd",
|
|
EntityId = x.Key.ParentId,
|
|
ProjectId = x.Key.ParentId,
|
|
UserId = userId,
|
|
NewValue = teamNames[t],
|
|
Comment = String.Format("By '{0}' scenario updates at Activity Calendar", x.Key.Name)
|
|
})).ToList();
|
|
|
|
AuditProxy.LogEvent(transactionId, teamAddEvents);
|
|
}
|
|
}
|
|
|
|
// Compile audit events and queue events to save
|
|
var allocationEvents = scenarioEventsTracker.GetEvents();
|
|
AuditProxy.LogEvent(transactionId, allocationEvents);
|
|
scenarioEventsTracker.Dispose();
|
|
}
|
|
|
|
public ActivityCalendarFilterOptions GetActivityCalendarFilterOptions(Guid userId)
|
|
{
|
|
TeamManager teamMngr = new TeamManager(DbContext);
|
|
ViewManager viewMngr = new ViewManager(DbContext);
|
|
CompanyManager cmpnyMngr = new CompanyManager(DbContext);
|
|
CreditDepartmentManager costCentersMngr = new CreditDepartmentManager(DbContext);
|
|
ExpenditureCategoryManager expCatsMngr = new ExpenditureCategoryManager(DbContext);
|
|
|
|
var model = new ActivityCalendarFilterOptions();
|
|
|
|
#region Teams
|
|
|
|
var availableTeams = teamMngr.LoadTeamsWithResourcesByUser(userId);
|
|
model.Teams = (availableTeams.OrderBy(x => x.Name).Select(x => new SelectListItem()
|
|
{
|
|
Text = x.Name,
|
|
Value = x.Id.ToString()
|
|
})).ToList();
|
|
|
|
#endregion
|
|
#region Views
|
|
|
|
IList<ViewListModel> availableViews = viewMngr.GetViewsByOwner(userId);
|
|
|
|
model.Views = (availableViews.OrderBy(x => x.Name).Select(x => new SelectListItem()
|
|
{
|
|
Text = x.Name,
|
|
Value = x.Id.ToString()
|
|
})).ToList();
|
|
|
|
#endregion
|
|
#region Companies
|
|
|
|
var availableCompanies = cmpnyMngr.GetCompaniesByUser4Display(userId);
|
|
model.Companies = new List<SelectListItem>();
|
|
SelectListItem listItem;
|
|
|
|
var companiesRootLevel = availableCompanies.Where(x =>
|
|
!x.ParentCompanyId.HasValue || !availableCompanies.Any(z => z.Id.Equals(x.ParentCompanyId.Value)))
|
|
.OrderBy(x => x.Name).ToList();
|
|
|
|
foreach (var rootCompany in companiesRootLevel)
|
|
{
|
|
listItem = new SelectListItem
|
|
{
|
|
Text = rootCompany.Name,
|
|
Value = rootCompany.Id.ToString(),
|
|
Group = null
|
|
};
|
|
model.Companies.Add(listItem);
|
|
|
|
// Get child companies
|
|
var childCompanies = availableCompanies.Where(x =>
|
|
x.ParentCompanyId.HasValue && x.ParentCompanyId.Value.Equals(rootCompany.Id))
|
|
.OrderBy(x => x.Name).ToList();
|
|
|
|
foreach (var childCompany in childCompanies)
|
|
{
|
|
listItem = new SelectListItem()
|
|
{
|
|
Text = childCompany.Name,
|
|
Value = childCompany.Id.ToString(),
|
|
Group = new SelectListGroup()
|
|
{
|
|
Name = rootCompany.Name
|
|
}
|
|
};
|
|
model.Companies.Add(listItem);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
#region Cost Centers
|
|
|
|
var availableCostCenters = costCentersMngr.LoadCreditDepartments();
|
|
model.CostCenters = this.ConvertCostCentersToOptions(availableCostCenters);
|
|
|
|
#endregion
|
|
#region Project Roles
|
|
|
|
var availableProjectRoles = expCatsMngr.GetSuperExpenditureDetails();
|
|
model.ProjectRoles = this.ConvertExpendituresToOptions(availableProjectRoles.Values.ToList());
|
|
|
|
#endregion
|
|
|
|
return model;
|
|
}
|
|
|
|
public ActivityCalendarModel GetActivityCalendarModel(ActivityCalendarRequestModel request)
|
|
{
|
|
if (request == null)
|
|
throw new ArgumentNullException(nameof(request));
|
|
|
|
// Get teams, related to the incoming page filter. Then get user access permissions for this
|
|
// team list, and get the only teams, current user has access to.
|
|
// Then get data for the available to user teams only
|
|
var userId = SecurityManager.GetUserPrincipal().ToString();
|
|
var teamAccessPermissions = GetTeams(userId, request.EntityType, request.EntityId);
|
|
var teamAccessPermissionsDict = teamAccessPermissions.GroupBy(t => t.TeamId).ToDictionary(gr => gr.Key, grEl => grEl.FirstOrDefault().AccessLevel);
|
|
var visible2UserTeams = teamAccessPermissionsDict.Keys.ToList();
|
|
|
|
var fcManager = (new FiscalCalendarManager(DbContext));
|
|
var projectManager = (new ProjectManager(DbContext));
|
|
var resourcesManager = (new PeopleResourcesManager(DbContext));
|
|
var scenarioManager = (new ScenarioManager(DbContext));
|
|
var teamManager = (new TeamManager(DbContext));
|
|
var ecManager = (new ExpenditureCategoryManager(DbContext));
|
|
|
|
var fcWeeks = fcManager.GetFiscalCalendar(FiscalCalendarModel.FiscalYearType.Week, request.StartDate, request.EndDate, true, false, true);
|
|
var weekEndings = fcWeeks.Select(x => x.EndDate).ToList();
|
|
if (weekEndings.Count <= 0)
|
|
return null;
|
|
|
|
var startDate = fcWeeks.Min(x => x.StartDate);
|
|
var endDate = fcWeeks.Max(x => x.EndDate);
|
|
|
|
var projects = new List<ProjectWithChildrenView>();
|
|
var filteredByTeamOrView = false;
|
|
|
|
switch (request.EntityType)
|
|
{
|
|
case ActivityCalendarRequestModel.ActivityCalendarEntityType.Resource:
|
|
projects = projectManager.GetProjectsWithChildrenByResource(request.EntityId,
|
|
includedItems: ProjectManager.LoadProjectItems.Scenarios | ProjectManager.LoadProjectItems.Teams);
|
|
break;
|
|
|
|
case ActivityCalendarRequestModel.ActivityCalendarEntityType.Company:
|
|
projects = projectManager.GetProjectsWithChildrenByCompany(request.EntityId,
|
|
includedItems: ProjectManager.LoadProjectItems.Scenarios | ProjectManager.LoadProjectItems.Teams);
|
|
break;
|
|
|
|
case ActivityCalendarRequestModel.ActivityCalendarEntityType.View:
|
|
projects = projectManager.GetProjectsWithChildrenByView(request.EntityId,
|
|
includedItems: ProjectManager.LoadProjectItems.Scenarios | ProjectManager.LoadProjectItems.Teams);
|
|
filteredByTeamOrView = true;
|
|
break;
|
|
|
|
default:
|
|
projects = projectManager.GetProjectsWithChildrenByTeams(visible2UserTeams,
|
|
includedItems: ProjectManager.LoadProjectItems.Scenarios | ProjectManager.LoadProjectItems.Teams);
|
|
filteredByTeamOrView = true;
|
|
break;
|
|
}
|
|
|
|
var resources = new List<PeopleResourceWithAllocationModel>();
|
|
List<ResourceAllocationModel> actuals = null;
|
|
Dictionary<string, ActivityCalendarPeopleResourceAllocationsByScenarioModel> resourceActualsTree = null;
|
|
|
|
if (request.EntityType == ActivityCalendarRequestModel.ActivityCalendarEntityType.Resource)
|
|
{
|
|
// Get allocations and actuals
|
|
resources = resourcesManager.GetResourcesWithAllocationsByResourceId(request.EntityId, startDate, endDate);
|
|
actuals = resourcesManager.GetActualsByResourceId(request.EntityId, startDate, endDate);
|
|
}
|
|
else
|
|
{
|
|
// Get allocations only. No need to load actuals
|
|
resources = resourcesManager.GetResourcesWithAllocationsByTeams(visible2UserTeams, startDate, endDate);
|
|
}
|
|
|
|
var projectsModel = ConvertProjectsToProjectModel(projects, fcWeeks, true).ToDictionary(x => x.ProjectId.ToString());
|
|
var scenarios = projectsModel.Select(x => x.Value.ActiveScenario.Id)
|
|
.ToList();
|
|
|
|
var resourcesECs = resources.Select(x => x.ExpenditureCategoryId)
|
|
.Distinct()
|
|
.ToList();
|
|
var superECsInScenarios = scenarioManager.GetExpendituresInScenarios(scenarios, true).Select(x => x.Id);
|
|
var allECs = resourcesECs.Union(superECsInScenarios).ToList();
|
|
|
|
var needAllocations = scenarioManager.GetScenarioDetails(scenarios, null, weekEndings);
|
|
var needAllocationsTree = ConvertNeedAllocationsToTree(needAllocations);
|
|
|
|
var teamAllocations = teamManager.GetTeamsAllocation(visible2UserTeams, scenarios, allECs, startDate, endDate);
|
|
var teamAllocationsTree = ConvertTeamAllocationsToTree(teamAllocations);
|
|
|
|
var resourceAllocations = resources.SelectMany(x => x.Allocations).ToList();
|
|
var resourceAllocationsTree = ConvertResourceAllocationsToTree(resourceAllocations);
|
|
|
|
var restTeamAllocationsTree = new Dictionary<string, ActivityCalendarAllocationsByECModel>();
|
|
if (request.EntityType != ActivityCalendarRequestModel.ActivityCalendarEntityType.Resource)
|
|
{
|
|
var allUserTeams = teamManager.GetTeamsByUser(Guid.Parse(userId));
|
|
var restTeams = allUserTeams.Where(x => !visible2UserTeams.Contains(x.TeamId)).Select(x => x.TeamId).ToList();
|
|
|
|
var restTeamAllocations = teamManager.GetTeamsAllocation(restTeams, scenarios, allECs, startDate, endDate);
|
|
restTeamAllocationsTree = GroupTeamAllocationsByScenario(restTeamAllocations);
|
|
}
|
|
|
|
var unassignedNeed = scenarioManager.GetScenarioNeedUnassigned(scenarios, null, weekEndings, true);
|
|
var unassignedNeedTree = ConvertUnassignedNeedAllocationsToTree(unassignedNeed);
|
|
|
|
if (actuals != null)
|
|
{
|
|
// Convert resource allocations actuals to tree
|
|
resourceActualsTree = ConvertResourceAllocationsToTree(actuals);
|
|
}
|
|
WorkFlowManager wfman = new WorkFlowManager(this.DbContext);
|
|
List<ActivityCalWorkFlowCommand> workFlowActions = new List<ActivityCalWorkFlowCommand>();
|
|
|
|
foreach (var project in projects)
|
|
{
|
|
if (!projectsModel.ContainsKey(project.Id.ToString()))
|
|
continue;
|
|
|
|
var pModel = projectsModel[project.Id.ToString()];
|
|
var actualTeams = project.Teams?.Intersect(visible2UserTeams) ?? new List<Guid>();
|
|
|
|
foreach (var teamId in actualTeams)
|
|
{
|
|
var assignedResources = resourceAllocations.Where(x => x.ScenarioId == pModel.ActiveScenario.Id && x.TeamId == teamId)
|
|
.GroupBy(x => x.ExpenditureCategoryId)
|
|
.ToDictionary(x => x.Key.ToString(), g => g.Select(s => s.PeopleResourceId).Distinct().ToList());
|
|
|
|
var assignedResourcesModel = new ActivityCalendarProjectTeamWithAssignedResourcesModel()
|
|
{
|
|
Resources = assignedResources
|
|
};
|
|
pModel.Teams.Add(teamId.ToString(), assignedResourcesModel);
|
|
}
|
|
|
|
|
|
if (wfman.isProcessExists(pModel.ActiveScenario.Id))
|
|
{
|
|
var wfRecId = pModel.ActiveScenario.Id;
|
|
var state = wfman.GetCurrentState(wfRecId);
|
|
pModel.Name = "*" + project.Name + " [" + state.state + "]";
|
|
var wfActions = wfman.GetWorkFlowCommands(pModel.ActiveScenario.Id, Guid.Parse(userId));
|
|
if (wfActions.Count > 0)
|
|
{
|
|
var wfAcCommand = new ActivityCalWorkFlowCommand()
|
|
{
|
|
HasChanges = (restTeamAllocationsTree.Keys.All(x => x != wfRecId.ToString())),
|
|
ProjectName = pModel.Name,
|
|
ScenarioId = wfRecId,
|
|
Commands = new List<WorkFlowCommand>()
|
|
|
|
};
|
|
foreach (var wfa in wfActions)
|
|
{
|
|
|
|
wfAcCommand.Commands.Add(wfa);
|
|
}
|
|
if (wfAcCommand.Commands.Count > 0)
|
|
workFlowActions.Add(wfAcCommand);
|
|
if (wfActions.Count <= 1)
|
|
{
|
|
wfAcCommand.HasChanges = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get teams with resources
|
|
var teams = teamManager.GetTeamsInfo(teamAccessPermissions, fcWeeks);
|
|
if (request.EntityType == ActivityCalendarRequestModel.ActivityCalendarEntityType.Resource)
|
|
{
|
|
// We make data model for resource calendar. Keep the current resource info only for every extracted team
|
|
var resourcesToKeep = new List<Guid> {request.EntityId};
|
|
FilterTeamsByResources(teams, resourcesToKeep);
|
|
}
|
|
|
|
// Set write permissions to NPT-items
|
|
SetNonProjectTimeEditPermissions(teams, teamAccessPermissionsDict);
|
|
|
|
// Build Cost centers and Project role lists to update calendar client-side filters
|
|
var projectRolesInfo = ecManager.GetSuperExpenditureDetails();
|
|
var projectRolesInfoTyped = projectRolesInfo.Values.ToList();
|
|
var costCenters = GetCostCenters(needAllocations, teamAllocations, projectRolesInfoTyped, filteredByTeamOrView);
|
|
var costCenterOptions = ConvertCostCentersToOptions(costCenters);
|
|
|
|
var projectRoles = GetProjectRoles(needAllocations, teamAllocations, costCenters, projectRolesInfoTyped);
|
|
var projectRoleOptions = ConvertExpendituresToOptions(projectRoles);
|
|
|
|
var model = new ActivityCalendarModel()
|
|
{
|
|
CostCenters = costCenterOptions,
|
|
ProjectRoles = projectRoleOptions,
|
|
WeekEndings = fcManager.CastWeekEndingsToTree(weekEndings),
|
|
Projects = projectsModel,
|
|
Teams = teams.ToDictionary(x => x.Id.ToString()),
|
|
NeedAllocations = needAllocationsTree,
|
|
TeamAllocations = teamAllocationsTree,
|
|
ResourceAllocations = resourceAllocationsTree,
|
|
UnassignedNeed = unassignedNeedTree,
|
|
ResourceActuals = resourceActualsTree,
|
|
WorkFlowActions = workFlowActions,
|
|
RestData = new ActivityCalendarRestData()
|
|
{
|
|
TeamAllocations = restTeamAllocationsTree
|
|
}
|
|
};
|
|
|
|
return model;
|
|
}
|
|
|
|
public bool ActivityCalendarSavePermitted(ActivityCalendarSaveModel model)
|
|
{
|
|
if (model.CalendarDisplayMode == ActivityCalendarRequestModel.ActivityCalendarEntityType.Resource)
|
|
{
|
|
// Perform special user permissions check for Resource Calendar mode
|
|
bool resourceProjectionsChanged = model.Scenarios.Values.Any(s =>
|
|
s.Expenditures.Values.Any(e =>
|
|
e.Teams.Values.Any(t =>
|
|
t.Resources.Values.Any(r =>
|
|
r.ForecastAllocations.Any() || r.IsRemoved))));
|
|
|
|
bool resourceActualsChanged = model.Scenarios.Values.Any(s =>
|
|
s.Expenditures.Values.Any(e =>
|
|
e.Teams.Values.Any(t =>
|
|
t.Resources.Values.Any(r =>
|
|
r.ActualAllocations.Any() || r.IsRemoved))));
|
|
|
|
bool nptChanged = model.NonProjectTime.Values.Any(n =>
|
|
n.Resources.Values.Any(r =>
|
|
r.Allocations.Any()));
|
|
|
|
var resourceProjectedWritePermission = SecurityManager.CheckSecurityObjectPermission(Areas.RD_ResourceTimeAllocationProjected, AccessLevel.Write);
|
|
var resourceActualWritePermission = SecurityManager.CheckSecurityObjectPermission(Areas.RD_ResourceTimeAllocationActual, AccessLevel.Write);
|
|
var resourceAllocationsWriteParentPermission = SecurityManager.CheckSecurityObjectPermission(Areas.RD_ResourceTimeAllocation, AccessLevel.Write);
|
|
var resourceNptWritePermission = SecurityManager.CheckSecurityObjectPermission(Areas.RD_ResourceNonProjectTime, AccessLevel.Write);
|
|
|
|
if (resourceProjectionsChanged && (!resourceAllocationsWriteParentPermission || !resourceProjectedWritePermission))
|
|
// Projections changed, but no permissions for saving projections
|
|
return false;
|
|
|
|
if (resourceActualsChanged && (!resourceAllocationsWriteParentPermission || !resourceActualWritePermission))
|
|
// Actuals changed, but no permissions for saving actuals
|
|
return false;
|
|
|
|
if (nptChanged && !resourceNptWritePermission)
|
|
// Non-project Time changed, but no permissions for saving NPT
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
public ActivityCalendarSaveModel ValidateCalendarSaveDataModel(ActivityCalendarSaveModel model,
|
|
Dictionary<Guid, bool> expenditureTypes, Dictionary<Guid, Dictionary<Guid, List<DateRangeNullable>>> resourcesTeams)
|
|
{
|
|
var modelCopy = model.Clone();
|
|
if (resourcesTeams == null)
|
|
resourcesTeams = new Dictionary<Guid, Dictionary<Guid, List<DateRangeNullable>>>();
|
|
|
|
var resources = resourcesTeams.Keys;
|
|
var availableTeams = (new TeamManager(DbContext)).GetTeamsByUser(SecurityManager.GetUserPrincipal())
|
|
.Where(x => x.Write)
|
|
.Select(x => x.TeamId).ToList();
|
|
|
|
foreach (var scenario in modelCopy.Scenarios.Values)
|
|
{
|
|
foreach (var category in scenario.Expenditures.Values)
|
|
{
|
|
var teamsInCategory = category.Teams.Keys.ToList();
|
|
foreach (var teamId in teamsInCategory)
|
|
{
|
|
if (availableTeams.All(x => x != new Guid(teamId)))
|
|
category.Teams.Remove(teamId);
|
|
}
|
|
|
|
foreach (var team in category.Teams.Values)
|
|
{
|
|
var resourcesInTeam = team.Resources.Keys.ToList();
|
|
foreach (var resourceId in resourcesInTeam)
|
|
{
|
|
if (resources.All(x => x != new Guid(resourceId)))
|
|
team.Resources.Remove(resourceId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// TODO: Perform checks on model's data
|
|
// 1) Check model pairs scenario-team fit scenario teams in the database
|
|
// 2) Check model pairs expenditure-resource fit resource's own expenditure. Mind resource allocations on SuperEC
|
|
// 3) Check weekendings of the resource allocation in model fit resource's team mebmership dates
|
|
|
|
return modelCopy;
|
|
}
|
|
|
|
public Dictionary<Guid, bool> GetExpenditureTypesFromSaveDataModel(ActivityCalendarSaveModel model)
|
|
{
|
|
if (model == null)
|
|
throw new ArgumentNullException(nameof(model));
|
|
|
|
if ((model.Scenarios == null) || (model.Scenarios.Count < 1))
|
|
return new Dictionary<Guid, bool>();
|
|
|
|
// Get expenditures from the model
|
|
var ecMngr = new ExpenditureCategoryManager(DbContext);
|
|
var modelExpCats = model.Scenarios.Values.SelectMany(s => s.Expenditures.Keys).Distinct()
|
|
.Select(x => new Guid(x)).ToList();
|
|
|
|
var databaseExpCats = ecMngr.GetAsDictionary(modelExpCats);
|
|
var result = databaseExpCats.Values.ToDictionary(x => x.Id, e => e.AllowResourceAssignment);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns team membership dates for specified list of resources
|
|
/// </summary>
|
|
/// <param name="model"></param>
|
|
/// <returns>Result: D[resourceId, D[teamId, L[TeamMembershipPeriod]]]</returns>
|
|
public Dictionary<Guid, Dictionary<Guid, List<DateRangeNullable>>> GetTeamsForResourcesFromSaveDataModel(ActivityCalendarSaveModel model)
|
|
{
|
|
if (model == null)
|
|
throw new ArgumentNullException(nameof(model));
|
|
|
|
// Get resources from model
|
|
var resources = model.Scenarios.Values.SelectMany(s => s.Expenditures.Values.SelectMany(e =>
|
|
e.Teams.Values.SelectMany(t => t.Resources.Keys))).Distinct()
|
|
.Select(x => new Guid(x)).ToList();
|
|
|
|
var prMngr = new PeopleResourcesManager(DbContext);
|
|
var teamManager = new TeamManager(DbContext);
|
|
|
|
var resourceTeamMembership = prMngr.GetResourcesTeams(resources);
|
|
var resourceTeamIds = resourceTeamMembership.SelectMany(x => x.Value).Select(x => x.TeamId).Distinct().ToList();
|
|
var resourceTeams = teamManager.GetTeamsByUserFiltered(SecurityManager.GetUserPrincipal().ToString(), resourceTeamIds, null, null).ToDictionary(x => x.TeamId);
|
|
var result = new Dictionary<Guid, Dictionary<Guid, List<DateRangeNullable>>>();
|
|
|
|
resourceTeamMembership.Keys.ToList().ForEach(resourceId =>
|
|
{
|
|
// Resource may be a member of the same team many times in different date periods
|
|
var teams = resourceTeamMembership[resourceId].Where(x => resourceTeams.ContainsKey(x.TeamId) && resourceTeams[x.TeamId].Write)
|
|
.GroupBy(x => x.TeamId)
|
|
.ToDictionary(key => key.Key, grp => grp.Select(rec => new DateRangeNullable()
|
|
{
|
|
StartDate = rec.StartDate,
|
|
EndDate = rec.EndDate
|
|
}).ToList());
|
|
|
|
if (teams.Any())
|
|
result.Add(resourceId, teams);
|
|
});
|
|
|
|
return result;
|
|
}
|
|
public Dictionary<Guid,List<Guid>> GetNewTeamsFromCalendar(ActivityCalendarSaveModel model)
|
|
{
|
|
var rt = new Dictionary<Guid, List<Guid>>();
|
|
var ScenarioToTeam = model.Scenarios.ToDictionary(key => key.Key, el => el.Value.Expenditures.Values.SelectMany(e => e.Teams.Keys).Distinct());
|
|
foreach (var st in ScenarioToTeam.Keys)
|
|
{
|
|
var scenarioId = Guid.Parse(st);
|
|
var ProjectTeams =
|
|
(from p in DbContext.Projects
|
|
join sc in DbContext.Scenarios on p.Id equals sc.ParentId
|
|
join tp in DbContext.Team2Project on p.Id equals tp.ProjectId into t
|
|
from subtp in t.DefaultIfEmpty()
|
|
where sc.Id == scenarioId
|
|
select new
|
|
{
|
|
Id = scenarioId,
|
|
TeamId = subtp == null ? Guid.Empty : subtp.TeamId
|
|
}).Distinct().AsNoTracking().ToList();
|
|
var teams = ScenarioToTeam[st].Select(Guid.Parse).ToList();
|
|
var newTeams = teams.Where(x => !ProjectTeams.Select(y => y.Id.ToString()).ToList().Contains(x.ToString())).ToList();
|
|
foreach (var t in newTeams)
|
|
{
|
|
var team = this.DbContext.Teams.FirstOrDefault(x => x.Id == t);
|
|
if (!rt.Keys.Contains(scenarioId))
|
|
{
|
|
var teamList = new List<Guid>();
|
|
teamList.Add(team.Id);
|
|
rt.Add(scenarioId, teamList);
|
|
}
|
|
else
|
|
{
|
|
rt[scenarioId].Add(team.Id);
|
|
}
|
|
|
|
}
|
|
}
|
|
return rt;
|
|
}
|
|
public void UpdateNonProjectTime(ActivityCalendarSaveModel model)
|
|
{
|
|
if (model == null)
|
|
throw new ArgumentNullException(nameof(model));
|
|
|
|
if (!model.FilterEntityId.HasValue || model.FilterEntityId.Value.Equals(Guid.Empty))
|
|
throw new BLLException("Information on Calendar EntityId filter not specified");
|
|
|
|
if ((model.NonProjectTime == null) || (model.NonProjectTime.Count < 1))
|
|
return;
|
|
|
|
var nptManager = new NonProjectTimeManager(DbContext);
|
|
var ecManager = new ExpenditureCategoryManager(DbContext);
|
|
var uomManager = new UOMManager(DbContext);
|
|
|
|
var allExpCats = ecManager.GetAsDictionary();
|
|
var allUoms = uomManager.GetAsDictionary();
|
|
|
|
// Get identifiers for resource-level and team-level NPTs to update
|
|
var resourceLevelNpts = model.NonProjectTime.Keys.Where(x => model.NonProjectTime[x].Resources.Values.Any())
|
|
.Select(x => new Guid(x)).ToList();
|
|
var teamLevelNpts = model.NonProjectTime.Keys.Where(x => model.NonProjectTime[x].TeamWideNptAllocations.Any())
|
|
.Select(x => new Guid(x)).ToList();
|
|
|
|
// Get existing NPT-items from the database
|
|
var allNpts = resourceLevelNpts.Union(teamLevelNpts).ToList();
|
|
var nonProjectTimes = DbContext.NonProjectTimes.AsNoTracking().Where(x => allNpts.Contains(x.Id)).ToDictionary(q => q.Id);
|
|
|
|
// Get resources for incoming NPTs and their Expenditures
|
|
var resIds = model.NonProjectTime.Values.SelectMany(t => t.Resources.Keys)
|
|
.Select(x => new Guid(x)).Distinct().ToArray();
|
|
var resourceExpenditures = DbContext.PeopleResources.Where(t => resIds.Contains(t.Id)).Select(t => new { t.Id, t.ExpenditureCategoryId })
|
|
.Distinct().ToDictionary(k => k.Id, el => el.ExpenditureCategoryId);
|
|
|
|
// Get UOMs for teams in team-level NPTs
|
|
var teamNptUOMs = nptManager.GetNPTUOMs(teamLevelNpts);
|
|
|
|
var resourceAllocations = new Dictionary<Guid, List<NonProjectTimeResourceAllocation>>();
|
|
var teamAllocations = new Dictionary<Guid, List<NonProjectTimeTeamAllocation>>();
|
|
|
|
if (model.CalendarDisplayMode == ActivityCalendarRequestModel.ActivityCalendarEntityType.Team)
|
|
{
|
|
// Get NPT allocations for Calendar in TEAM display mode
|
|
resourceAllocations = nptManager.GetNPTResourceAllocationsByTeam(allNpts, model.FilterEntityId.Value);
|
|
teamAllocations = nptManager.GetNPTTeamAllocationsByTeam(allNpts, model.FilterEntityId.Value);
|
|
}
|
|
else if (model.CalendarDisplayMode == ActivityCalendarRequestModel.ActivityCalendarEntityType.View)
|
|
{
|
|
// Get NPT allocations for Calendar in VIEW display mode
|
|
resourceAllocations = nptManager.GetNPTResourceAllocationsByView(allNpts, model.FilterEntityId.Value);
|
|
teamAllocations = nptManager.GetNPTTeamAllocationsByView(allNpts, model.FilterEntityId.Value);
|
|
}
|
|
else if (model.CalendarDisplayMode == ActivityCalendarRequestModel.ActivityCalendarEntityType.Company)
|
|
{
|
|
// Get NPT allocations for Calendar in COMPANY display mode
|
|
resourceAllocations = nptManager.GetNPTResourceAllocationsByCompany(allNpts, model.FilterEntityId.Value);
|
|
teamAllocations = nptManager.GetNPTTeamAllocationsByCompany(allNpts, model.FilterEntityId.Value);
|
|
}
|
|
else if (model.CalendarDisplayMode == ActivityCalendarRequestModel.ActivityCalendarEntityType.Resource)
|
|
{
|
|
// Get NPT allocations for Calendar in RESOURCE display mode
|
|
resourceAllocations = nptManager.GetNPTResourceAllocations(allNpts, model.FilterEntityId.Value);
|
|
}
|
|
|
|
foreach (var nptIdAsText in model.NonProjectTime.Keys)
|
|
{
|
|
var nptId = new Guid(nptIdAsText);
|
|
var nptModel = model.NonProjectTime[nptIdAsText];
|
|
|
|
NonProjectTime nonProjectTime;
|
|
nonProjectTimes.TryGetValue(nptId, out nonProjectTime);
|
|
|
|
if (nonProjectTime.Id == Guid.Empty)
|
|
throw new BLLException($"Non-project time item not found in the database (Id = {nptIdAsText})");
|
|
|
|
if (!nonProjectTime.IsTeamMode)
|
|
{
|
|
#region Update Resource NPT allocations
|
|
|
|
foreach (var resourceIdAsText in nptModel.Resources.Keys)
|
|
{
|
|
var resourceId = new Guid(resourceIdAsText);
|
|
var resourceNptModel = nptModel.Resources[resourceIdAsText];
|
|
|
|
if (!resourceAllocations.ContainsKey(nptId))
|
|
continue;
|
|
|
|
// All NPT for resource
|
|
var resourceNptAllocations = resourceAllocations[nptId].Where(x => x.NonProjectTime2Resource.PeopleResourceId == resourceId);
|
|
|
|
foreach (var weekendingsAsText in resourceNptModel.Allocations.Keys)
|
|
{
|
|
var weekending = Utils.ConvertFromUnixDate(Convert.ToInt64(weekendingsAsText));
|
|
var newVal = resourceNptModel.Allocations[weekendingsAsText];
|
|
UOM uom = null;
|
|
|
|
var oldAllocations = resourceNptAllocations.Where(x => x.WeekEndingDate == weekending).ToList();
|
|
|
|
if (oldAllocations.Count > 0)
|
|
{
|
|
if (allExpCats.Keys.Contains(resourceExpenditures[resourceId]))
|
|
{
|
|
if (allUoms.ContainsKey(allExpCats[resourceExpenditures[resourceId]].UOMId))
|
|
uom = allUoms[allExpCats[resourceExpenditures[resourceId]].UOMId];
|
|
}
|
|
|
|
var uomValue = uom?.UOMValue;
|
|
var coef = newVal == 0 ? 0 : oldAllocations.Select(x => x.HoursOff).Sum() / newVal;
|
|
|
|
foreach (var val in oldAllocations)
|
|
{
|
|
// Update NPT resource allocation
|
|
var nptAllocation = oldAllocations.FirstOrDefault();
|
|
|
|
if (nptAllocation != null)
|
|
{
|
|
var value = nptAllocation.HoursOff == 0 || coef == 0 ? newVal : Convert.ToDecimal(nptAllocation.HoursOff) / coef;
|
|
nptAllocation.HoursOff = Convert.ToInt32(uomValue.HasValue && uomValue.Value < value ? uomValue.Value : value);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
else
|
|
{
|
|
#region Update Team NPT allocations
|
|
|
|
foreach (var weekendingsAsText in nptModel.TeamWideNptAllocations.Keys)
|
|
{
|
|
var weekending = Utils.ConvertFromUnixDate(Convert.ToInt64(weekendingsAsText));
|
|
var newVal = nptModel.TeamWideNptAllocations[weekendingsAsText];
|
|
|
|
if (!teamAllocations.ContainsKey(nptId))
|
|
continue;
|
|
|
|
var uomValue = (decimal?)null;
|
|
var nptAllocations = teamAllocations[nptId];
|
|
var oldAllocations = nptAllocations.Where(x => x.WeekEndingDate == weekending).ToList();
|
|
|
|
if (oldAllocations.Count > 0)
|
|
{
|
|
if (teamNptUOMs.ContainsKey(nptId))
|
|
uomValue = teamNptUOMs[nptId];
|
|
|
|
var coef = newVal == 0 ? 0 : (oldAllocations.Select(x => x.HoursOff).Sum() / oldAllocations.Count) / newVal;
|
|
|
|
foreach (var nptAllocation in oldAllocations)
|
|
{
|
|
var newValue = nptAllocation.HoursOff == 0 || coef == 0 ? newVal : Convert.ToDecimal(nptAllocation.HoursOff) / coef;
|
|
nptAllocation.HoursOff = Convert.ToInt32(uomValue.HasValue && (uomValue < newValue) ? uomValue : newValue);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Convert Methods
|
|
|
|
public List<ActivityCalendarProjectModel> ConvertProjectsToProjectModel(List<ProjectWithChildrenView> projects, List<FiscalCalendar> fcWeeks, bool onlyActiveScenario)
|
|
{
|
|
if (projects == null || projects.Count <= 0)
|
|
return new List<ActivityCalendarProjectModel>();
|
|
|
|
if (fcWeeks == null || fcWeeks.Count <= 0)
|
|
return new List<ActivityCalendarProjectModel>();
|
|
|
|
var result = new List<ActivityCalendarProjectModel>();
|
|
var defaultProjectColor = Utils.GetDefaultProjectColor();
|
|
var startDate = fcWeeks.Min(x => x.EndDate);
|
|
var endDate = fcWeeks.Max(x => x.EndDate);
|
|
|
|
foreach (var project in projects)
|
|
{
|
|
if (project.HasChildren)
|
|
continue;
|
|
|
|
var activeScenario = project.Scenarios.OrderByDescending(x => x.DateCreated)
|
|
.FirstOrDefault(x => x.ParentId == project.Id &&
|
|
(!onlyActiveScenario || x.Status == ScenarioStatus.Active) &&
|
|
x.Type == ScenarioType.Portfolio &&
|
|
x.StartDate.HasValue && x.StartDate.Value.Date <= endDate &&
|
|
x.EndDate.HasValue && x.EndDate.Value.Date >= startDate);
|
|
if (activeScenario == null)
|
|
continue;
|
|
|
|
var nearestFiscalWeek = fcWeeks.FirstOrDefault(x => x.EndDate >= activeScenario.EndDate.Value);
|
|
var nearestWeekEnding = nearestFiscalWeek != null ? nearestFiscalWeek.EndDate : activeScenario.EndDate.Value;
|
|
var inactiveScenarios = project.Scenarios.Where(x => x.ParentId == project.Id && x.Type == ScenarioType.Portfolio &&
|
|
x.StartDate.HasValue && x.EndDate.HasValue && x.Status == (int)ScenarioStatus.Inactive).ToList();
|
|
var model = new ActivityCalendarProjectModel()
|
|
{
|
|
ProjectId = project.Id,
|
|
ParentProjectId = project.ParentProject != null ? project.ParentProject.Id : project.Id,
|
|
ParentName = project.ParentProject != null ? project.ParentProject.Name : null,
|
|
PartId = project.ParentProject != null ? (Guid?)project.Id : null,
|
|
// in any case we must have company id on current project or its parent, otherwise set Guid.Empty to see the corrupted data
|
|
CompanyId = (project.ParentProject != null ? project.ParentProject.CompanyId : project.CompanyId) ?? Guid.Empty,
|
|
// client is always related to the project (in case of simple project) or to the project part (in case of project with parts)
|
|
ClientId = project.ClientId ?? Guid.Empty,
|
|
Name = project.Name,
|
|
TypeName = project.TypeName,
|
|
DeadlineMs = project.Deadline.HasValue ? Utils.ConvertToUnixDate(project.Deadline.Value) : 0,
|
|
Priority = project.Priority,
|
|
Color = Utils.GetProjectColor(project, defaultProjectColor),
|
|
ReadOnly = project.ReadOnly,
|
|
ActiveScenario = new ActivityCalendarScenarioModel
|
|
{
|
|
Id = activeScenario.Id,
|
|
Name = activeScenario.Name,
|
|
IsBottomUp = activeScenario.IsBottomUp,
|
|
StartDate = Utils.ConvertToUnixDate(activeScenario.StartDate.Value),
|
|
EndDate = Utils.ConvertToUnixDate(nearestWeekEnding),
|
|
},
|
|
InactiveScenarios = inactiveScenarios.Select(x => new ActivityCalendarScenarioModel()
|
|
{
|
|
Id = x.Id,
|
|
Name = x.Name,
|
|
IsBottomUp = x.IsBottomUp,
|
|
StartDate = Utils.ConvertToUnixDate(x.StartDate.Value),
|
|
// we use this parameter only for dependency widget, so we may use it as is, without calculation of nearest week ending
|
|
EndDate = Utils.ConvertToUnixDate(x.EndDate.Value),
|
|
}).ToList(),
|
|
};
|
|
|
|
result.Add(model);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
public Dictionary<string, ActivityCalendarAllocationsByECModel> ConvertNeedAllocationsToTree(List<ScenarioDetail> allocations)
|
|
{
|
|
if (allocations == null || allocations.Count <= 0)
|
|
return new Dictionary<string, ActivityCalendarAllocationsByECModel>();
|
|
|
|
var tree = new Dictionary<string, ActivityCalendarAllocationsByECModel>();
|
|
foreach (var allocation in allocations)
|
|
{
|
|
if (!allocation.ParentID.HasValue || !allocation.WeekEndingDate.HasValue)
|
|
continue;
|
|
|
|
var scenarioKey = allocation.ParentID.Value.ToString();
|
|
var ecKey = allocation.ExpenditureCategoryId.ToString();
|
|
var weekEndingKey = Utils.ConvertToUnixDate(allocation.WeekEndingDate.Value).ToString();
|
|
|
|
if (!tree.ContainsKey(scenarioKey))
|
|
tree.Add(scenarioKey, new ActivityCalendarAllocationsByECModel());
|
|
|
|
if (!tree[scenarioKey].Expenditures.ContainsKey(ecKey))
|
|
tree[scenarioKey].Expenditures.Add(ecKey, new ActivityCalendarAllocationsModel());
|
|
|
|
tree[scenarioKey].Expenditures[ecKey].Allocations.Add(weekEndingKey, allocation.Quantity ?? 0);
|
|
}
|
|
|
|
return tree;
|
|
}
|
|
|
|
public Dictionary<string, ActivityCalendarTeamAllocationsModel> ConvertTeamAllocationsToTree(List<TeamAllocation> allocations)
|
|
{
|
|
if (allocations == null || allocations.Count <= 0)
|
|
return new Dictionary<string, ActivityCalendarTeamAllocationsModel>();
|
|
|
|
var tree = new Dictionary<string, ActivityCalendarTeamAllocationsModel>();
|
|
foreach (var allocation in allocations)
|
|
{
|
|
var teamKey = allocation.TeamId.ToString();
|
|
var scenarioKey = allocation.ScenarioId.ToString();
|
|
var ecKey = allocation.ExpenditureCategoryId.ToString();
|
|
var weekEndingKey = Utils.ConvertToUnixDate(allocation.WeekEndingDate).ToString();
|
|
|
|
if (!tree.ContainsKey(teamKey))
|
|
tree.Add(teamKey, new ActivityCalendarTeamAllocationsModel());
|
|
|
|
if (!tree[teamKey].Scenarios.ContainsKey(scenarioKey))
|
|
tree[teamKey].Scenarios.Add(scenarioKey, new ActivityCalendarAllocationsByECModel());
|
|
|
|
if (!tree[teamKey].Scenarios[scenarioKey].Expenditures.ContainsKey(ecKey))
|
|
tree[teamKey].Scenarios[scenarioKey].Expenditures.Add(ecKey, new ActivityCalendarAllocationsModel());
|
|
|
|
tree[teamKey].Scenarios[scenarioKey].Expenditures[ecKey].Allocations.Add(weekEndingKey, allocation.Quantity);
|
|
}
|
|
|
|
return tree;
|
|
}
|
|
|
|
public Dictionary<string, ActivityCalendarAllocationsByECModel> GroupTeamAllocationsByScenario(List<TeamAllocation> allocations)
|
|
{
|
|
if (allocations == null || allocations.Count <= 0)
|
|
return new Dictionary<string, ActivityCalendarAllocationsByECModel>();
|
|
|
|
var tree = new Dictionary<string, ActivityCalendarAllocationsByECModel>();
|
|
foreach (var allocation in allocations)
|
|
{
|
|
var scenarioKey = allocation.ScenarioId.ToString();
|
|
var ecKey = allocation.ExpenditureCategoryId.ToString();
|
|
var weekEndingKey = Utils.ConvertToUnixDate(allocation.WeekEndingDate).ToString();
|
|
|
|
if (!tree.ContainsKey(scenarioKey))
|
|
tree.Add(scenarioKey, new ActivityCalendarAllocationsByECModel());
|
|
|
|
if (!tree[scenarioKey].Expenditures.ContainsKey(ecKey))
|
|
tree[scenarioKey].Expenditures.Add(ecKey, new ActivityCalendarAllocationsModel());
|
|
|
|
if (!tree[scenarioKey].Expenditures[ecKey].Allocations.ContainsKey(weekEndingKey))
|
|
tree[scenarioKey].Expenditures[ecKey].Allocations.Add(weekEndingKey, 0);
|
|
|
|
tree[scenarioKey].Expenditures[ecKey].Allocations[weekEndingKey] += allocation.Quantity;
|
|
}
|
|
|
|
return tree;
|
|
}
|
|
|
|
public Dictionary<string, ActivityCalendarAllocationsByECModel> ConvertUnassignedNeedAllocationsToTree(List<VW_ProjectNeedUnassigned> allocations)
|
|
{
|
|
if (allocations == null || allocations.Count <= 0)
|
|
return new Dictionary<string, ActivityCalendarAllocationsByECModel>();
|
|
|
|
var tree = new Dictionary<string, ActivityCalendarAllocationsByECModel>();
|
|
foreach (var allocation in allocations)
|
|
{
|
|
if (!allocation.WeekEndingDate.HasValue)
|
|
continue;
|
|
|
|
var scenarioKey = allocation.ScenarioId.ToString();
|
|
var ecKey = allocation.ExpenditureCategoryId.ToString();
|
|
var weekEndingKey = Utils.ConvertToUnixDate(allocation.WeekEndingDate.Value).ToString();
|
|
|
|
if (!tree.ContainsKey(scenarioKey))
|
|
tree.Add(scenarioKey, new ActivityCalendarAllocationsByECModel());
|
|
|
|
if (!tree[scenarioKey].Expenditures.ContainsKey(ecKey))
|
|
tree[scenarioKey].Expenditures.Add(ecKey, new ActivityCalendarAllocationsModel());
|
|
|
|
tree[scenarioKey].Expenditures[ecKey].Allocations.Add(weekEndingKey, allocation.RemainingQuantity);
|
|
}
|
|
|
|
return tree;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
private Dictionary<string, ActivityCalendarPeopleResourceAllocationsByScenarioModel> ConvertResourceAllocationsToTree(List<ResourceAllocationModel> allocations)
|
|
{
|
|
if (allocations == null || allocations.Count <= 0)
|
|
return new Dictionary<string, ActivityCalendarPeopleResourceAllocationsByScenarioModel>();
|
|
|
|
var tree = new Dictionary<string, ActivityCalendarPeopleResourceAllocationsByScenarioModel>();
|
|
foreach (var allocation in allocations)
|
|
{
|
|
var resourceKey = allocation.PeopleResourceId.ToString();
|
|
var scenarioKey = allocation.ScenarioId.ToString();
|
|
var teamKey = allocation.TeamId.ToString();
|
|
var ecKey = allocation.ExpenditureCategoryId.ToString();
|
|
var weekEndingKey = Utils.ConvertToUnixDate(allocation.WeekEndingDate).ToString();
|
|
|
|
if (!tree.ContainsKey(resourceKey))
|
|
tree.Add(resourceKey, new ActivityCalendarPeopleResourceAllocationsByScenarioModel());
|
|
|
|
if (!tree[resourceKey].Scenarios.ContainsKey(scenarioKey))
|
|
tree[resourceKey].Scenarios.Add(scenarioKey, new ActivityCalendarPeopleResourceAllocationsByTeamModel());
|
|
|
|
if (!tree[resourceKey].Scenarios[scenarioKey].Teams.ContainsKey(teamKey))
|
|
tree[resourceKey].Scenarios[scenarioKey].Teams.Add(teamKey, new ActivityCalendarAllocationsByECModel());
|
|
|
|
if (!tree[resourceKey].Scenarios[scenarioKey].Teams[teamKey].Expenditures.ContainsKey(ecKey))
|
|
tree[resourceKey].Scenarios[scenarioKey].Teams[teamKey].Expenditures.Add(ecKey, new ActivityCalendarAllocationsModel());
|
|
|
|
tree[resourceKey].Scenarios[scenarioKey].Teams[teamKey].Expenditures[ecKey].Allocations.Add(weekEndingKey, allocation.Quantity);
|
|
}
|
|
|
|
return tree;
|
|
}
|
|
|
|
private List<TeamWithPermissionsModel> GetTeams(string userId, ActivityCalendarRequestModel.ActivityCalendarEntityType entityType, Guid entityId)
|
|
{
|
|
if (entityType == 0 || entityId == Guid.Empty)
|
|
return new List<TeamWithPermissionsModel>();
|
|
|
|
var teams = new List<TeamWithPermissionsModel>();
|
|
var teamManager = new TeamManager(DbContext);
|
|
|
|
switch (entityType)
|
|
{
|
|
case ActivityCalendarRequestModel.ActivityCalendarEntityType.Team:
|
|
teams = teamManager.GetTeamsByUserFiltered(userId, new List<Guid>() { entityId }, null, null);
|
|
break;
|
|
|
|
case ActivityCalendarRequestModel.ActivityCalendarEntityType.View:
|
|
teams = teamManager.GetTeamsByUserFiltered(userId, null, new List<Guid>() { entityId }, null);
|
|
break;
|
|
|
|
// for filtering by company we need to show all teams that are related to selected company
|
|
case ActivityCalendarRequestModel.ActivityCalendarEntityType.Company:
|
|
teams = teamManager.GetTeamsByCompanies(new Guid(userId), new List<Guid>() { entityId });
|
|
break;
|
|
|
|
case ActivityCalendarRequestModel.ActivityCalendarEntityType.Resource:
|
|
teams = teamManager.GetTeamsByResources(userId, new List<Guid>() { entityId });
|
|
break;
|
|
}
|
|
|
|
return teams;
|
|
}
|
|
|
|
private void CreateTeamAllocations(Guid scenarioId, ActivityCalendarSaveModel.ScenarioSaveModel model, List<Guid> newTeams, IEnumerable<DateTime> weekEndings)
|
|
{
|
|
if (scenarioId == Guid.Empty)
|
|
throw new ArgumentOutOfRangeException("scenarioId");
|
|
|
|
if (model == null)
|
|
throw new ArgumentNullException("model");
|
|
|
|
if (newTeams == null || !newTeams.Any())
|
|
throw new ArgumentNullException("newTeams");
|
|
|
|
if (weekEndings == null || !weekEndings.Any())
|
|
throw new ArgumentNullException("weekEndings");
|
|
|
|
if (model.Expenditures == null || !model.Expenditures.Any())
|
|
return;
|
|
|
|
var teamAllocationCollection = new List<TeamAllocation>();
|
|
foreach (var teamId in newTeams)
|
|
{
|
|
foreach (var category in model.Expenditures)
|
|
{
|
|
if (category.Value.Teams == null || !category.Value.Teams.ContainsKey(teamId.ToString()))
|
|
continue;
|
|
|
|
foreach (var weekEnding in weekEndings)
|
|
{
|
|
var allocation = new TeamAllocation()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ScenarioId = scenarioId,
|
|
TeamId = teamId,
|
|
ExpenditureCategoryId = Guid.Parse(category.Key),
|
|
Quantity = 0,
|
|
WeekEndingDate = weekEnding
|
|
};
|
|
teamAllocationCollection.Add(allocation);
|
|
}
|
|
}
|
|
}
|
|
DbContext.TeamAllocations.AddRange(teamAllocationCollection);
|
|
}
|
|
|
|
private void UpdateResourceAllocations(Guid scenarioId, ActivityCalendarSaveModel.ScenarioSaveModel model,
|
|
Dictionary<Guid, Dictionary<Guid, List<DateRangeNullable>>> resourceTeams, ScenarioDetailsEventsTracker tracker = null)
|
|
{
|
|
#if DEBUG
|
|
var watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
// Resources, that have changed allocations
|
|
var resourcesWithChangedAllocations = model.Expenditures.Values.SelectMany(e =>
|
|
e.Teams.Values.SelectMany(t =>
|
|
t.Resources.Where(r => (r.Value.ForecastAllocations.Count > 0) || r.Value.IsRemoved)
|
|
.Select(r => new Guid(r.Key)))).ToList();
|
|
|
|
// Get existing resource allocations from DB
|
|
var resourceAllocations = DbContext.PeopleResourceAllocations.Where(x =>
|
|
x.ScenarioId.Equals(scenarioId) && resourcesWithChangedAllocations.Contains(x.PeopleResourceId))
|
|
.GroupBy(x => x.ExpenditureCategoryId).ToDictionary(e => e.Key, eGrp =>
|
|
eGrp.GroupBy(x => x.TeamId).ToDictionary(t => t.Key, tGrp =>
|
|
tGrp.GroupBy(x => x.PeopleResourceId).ToDictionary(r => r.Key, rGrp =>
|
|
rGrp.GroupBy(x => x.WeekEndingDate).ToDictionary(w => w.Key, x => x.FirstOrDefault()))));
|
|
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges UpdateScenarios UpdateResourceAllocations DbContext.PeopleResourceAllocations.Where(x => {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges UpdateScenarios UpdateResourceAllocations DbContext.PeopleResourceAllocations.Where(x => {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
|
|
var peopleResourceAllocationToAdd = new List<PeopleResourceAllocation>();
|
|
var peopleResourceAllocationToDelete= new List<PeopleResourceAllocation>();
|
|
foreach (var expCatIdAsText in model.Expenditures.Keys)
|
|
{
|
|
var expCatId = new Guid(expCatIdAsText);
|
|
var expCatModel = model.Expenditures[expCatIdAsText];
|
|
|
|
if (!resourceAllocations.ContainsKey(expCatId))
|
|
resourceAllocations.Add(expCatId, new Dictionary<Guid, Dictionary<Guid, Dictionary<DateTime, PeopleResourceAllocation>>>());
|
|
|
|
foreach (var teamIdAsText in expCatModel.Teams.Keys)
|
|
{
|
|
var teamId = new Guid(teamIdAsText);
|
|
var teamModel = expCatModel.Teams[teamIdAsText];
|
|
|
|
if (!resourceAllocations[expCatId].ContainsKey(teamId))
|
|
resourceAllocations[expCatId].Add(teamId, new Dictionary<Guid, Dictionary<DateTime, PeopleResourceAllocation>>());
|
|
|
|
foreach (var resourceIdAsText in teamModel.Resources.Keys)
|
|
{
|
|
var resourceId = new Guid(resourceIdAsText);
|
|
var resourceModel = teamModel.Resources[resourceIdAsText];
|
|
|
|
if (!resourceAllocations[expCatId][teamId].ContainsKey(resourceId))
|
|
resourceAllocations[expCatId][teamId].Add(resourceId, new Dictionary<DateTime, PeopleResourceAllocation>());
|
|
|
|
var currentResourceAllocations = resourceAllocations[expCatId][teamId][resourceId];
|
|
|
|
if (resourceModel.IsRemoved)
|
|
{
|
|
// Remove allocations for resource
|
|
if (currentResourceAllocations.Count > 0)
|
|
{
|
|
// Log changes for audit events
|
|
if (tracker != null)
|
|
tracker.ResourceAllocationValuesRemoved(currentResourceAllocations.Values);
|
|
|
|
//currentResourceAllocations.Values.ToList().ForEach(x => DbContext.Entry(x).State = EntityState.Deleted);
|
|
peopleResourceAllocationToDelete.AddRange(currentResourceAllocations.Values);
|
|
resourceAllocations[expCatId][teamId][resourceId].Clear();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Perform update or create for resource allocations and actuals
|
|
foreach (var weekendingAsText in resourceModel.ForecastAllocations.Keys)
|
|
{
|
|
// Get allocations to update
|
|
var weekending = Utils.ConvertFromUnixDate(Convert.ToInt64(weekendingAsText));
|
|
PeopleResourceAllocation allocationItem;
|
|
|
|
decimal newValue = resourceModel.ForecastAllocations[weekendingAsText] > 0 ? resourceModel.ForecastAllocations[weekendingAsText] : 0;
|
|
|
|
// Check resource allocation is out of the resource team membership dates
|
|
bool isAllocationCorrect =
|
|
(resourceTeams != null) && resourceTeams.ContainsKey(resourceId) &&
|
|
resourceTeams[resourceId].ContainsKey(teamId) &&
|
|
resourceTeams[resourceId][teamId].Any(x => x.IsInRange(weekending));
|
|
|
|
if (!currentResourceAllocations.ContainsKey(weekending))
|
|
{
|
|
if (isAllocationCorrect)
|
|
{
|
|
// Create new allocation record, if it is correct
|
|
allocationItem = new PeopleResourceAllocation()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ScenarioId = scenarioId,
|
|
ExpenditureCategoryId = expCatId,
|
|
TeamId = teamId,
|
|
PeopleResourceId = resourceId,
|
|
WeekEndingDate = weekending,
|
|
Quantity = newValue
|
|
};
|
|
peopleResourceAllocationToAdd.Add(allocationItem);
|
|
resourceAllocations[expCatId][teamId][resourceId].Add(weekending, allocationItem);
|
|
|
|
// Log changes for audit events
|
|
if (tracker != null)
|
|
tracker.ResourceAllocationValueAdded(allocationItem);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (isAllocationCorrect)
|
|
{
|
|
// Update existing allocation record
|
|
allocationItem = currentResourceAllocations[weekending];
|
|
var oldValue = allocationItem.Quantity;
|
|
allocationItem.Quantity = newValue;
|
|
//DbContext.Entry(allocationItem).State = EntityState.Modified;
|
|
|
|
// Log changes for audit events
|
|
if (tracker != null)
|
|
tracker.ResourceAllocationValueChanged(allocationItem, oldValue);
|
|
}
|
|
else
|
|
{
|
|
// Allocations is incorrect. Remove record from the database
|
|
allocationItem = currentResourceAllocations[weekending];
|
|
//DbContext.Entry(allocationItem).State = EntityState.Deleted;
|
|
peopleResourceAllocationToDelete.Add(allocationItem);
|
|
// Log changes for audit events
|
|
if (tracker != null)
|
|
tracker.ResourceAllocationValueRemoved(allocationItem);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges UpdateScenarios UpdateResourceAllocations foreach (var expCatIdAsText in model.Expenditures.Keys) {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges UpdateScenarios UpdateResourceAllocations foreach (var expCatIdAsText in model.Expenditures.Keys) {watch1.ElapsedMilliseconds} ms");
|
|
watch1 = new System.Diagnostics.Stopwatch();
|
|
watch1.Start();
|
|
#endif
|
|
|
|
DbContext.PeopleResourceAllocations.AddRange(peopleResourceAllocationToAdd);
|
|
DbContext.PeopleResourceAllocations.RemoveRange(peopleResourceAllocationToDelete);
|
|
DbContext.BulkSaveChanges();
|
|
#if DEBUG
|
|
watch1.Stop();
|
|
System.Diagnostics.Debug.WriteLine($"SaveChanges UpdateScenarios UpdateResourceAllocations DbContext.BulkSaveChanges(); {watch1.ElapsedMilliseconds} ms");
|
|
Logger.Debug($"SaveChanges UpdateScenarios UpdateResourceAllocations DbContext.BulkSaveChanges(); {watch1.ElapsedMilliseconds} ms");
|
|
#endif
|
|
}
|
|
|
|
private void UpdateResourceActuals(Guid actualScenarioId, ActivityCalendarSaveModel.ScenarioSaveModel model,
|
|
Dictionary<Guid, IEnumerable<RateModel>> rates)
|
|
{
|
|
// Resources, that have changed actuals
|
|
var resourcesWithChangedActuals = model.Expenditures.Values.SelectMany(e =>
|
|
e.Teams.Values.SelectMany(t =>
|
|
t.Resources.Where(r => r.Value.ActualAllocations.Count > 0).Select(r => new Guid(r.Key)))).ToList();
|
|
|
|
// Get existing resource actuals and details actuals from DB
|
|
var databaseActuals =
|
|
(from pra in DbContext.PeopleResourceActuals
|
|
join sda in DbContext.ScenarioDetail on pra.ParentId equals sda.Id
|
|
where resourcesWithChangedActuals.Contains(pra.PeopleResourceId) && sda.ParentID == actualScenarioId
|
|
select new
|
|
{
|
|
resourceActuals = pra,
|
|
detailsActuals = sda
|
|
}).Distinct().ToList();
|
|
|
|
// D[ExpCatId, D[ResourceId, D[Weekending, PeopleResourceActual]]]
|
|
var resourceActuals = databaseActuals.Where(x => x.detailsActuals.ExpenditureCategoryId.HasValue &&
|
|
x.detailsActuals.WeekEndingDate.HasValue)
|
|
.GroupBy(x => x.detailsActuals.ExpenditureCategoryId.Value).ToDictionary(e => e.Key, eGrp =>
|
|
eGrp.GroupBy(x => x.resourceActuals.PeopleResourceId).ToDictionary(r => r.Key, rGrp =>
|
|
rGrp.GroupBy(x => x.detailsActuals.WeekEndingDate.Value).ToDictionary(w => w.Key,
|
|
x => x.Select(a => a.resourceActuals).FirstOrDefault())));
|
|
|
|
// D[ExpCatId, D[Weekending, ScenarioDetail]]
|
|
var detailsActuals = databaseActuals.Select(x => x.detailsActuals)
|
|
.Where(x => x.ExpenditureCategoryId.HasValue && x.WeekEndingDate.HasValue).Distinct()
|
|
.GroupBy(x => x.ExpenditureCategoryId.Value).ToDictionary(e => e.Key, eGrp =>
|
|
eGrp.GroupBy(x => x.WeekEndingDate.Value).ToDictionary(w => w.Key, sd => sd.FirstOrDefault()));
|
|
|
|
databaseActuals.Clear();
|
|
|
|
#region Update resource actuals
|
|
|
|
var scenarioDetailsToAdd = new List<ScenarioDetail>();
|
|
var peopleResourceActualsToAdd = new List<PeopleResourceActual>();
|
|
|
|
foreach (var expCatIdAsText in model.Expenditures.Keys)
|
|
{
|
|
var expCatId = new Guid(expCatIdAsText);
|
|
var expCat = model.Expenditures[expCatIdAsText];
|
|
|
|
if (!resourceActuals.ContainsKey(expCatId))
|
|
resourceActuals.Add(expCatId, new Dictionary<Guid, Dictionary<DateTime, PeopleResourceActual>>());
|
|
|
|
if (!detailsActuals.ContainsKey(expCatId))
|
|
detailsActuals.Add(expCatId, new Dictionary<DateTime, ScenarioDetail>());
|
|
|
|
foreach (var teamIdAsText in expCat.Teams.Keys)
|
|
{
|
|
var team = expCat.Teams[teamIdAsText];
|
|
|
|
foreach (var resourceIdAsText in team.Resources.Keys)
|
|
{
|
|
var resourceId = new Guid(resourceIdAsText);
|
|
var resource = team.Resources[resourceIdAsText];
|
|
|
|
if (!resourceActuals[expCatId].ContainsKey(resourceId))
|
|
resourceActuals[expCatId].Add(resourceId, new Dictionary<DateTime, PeopleResourceActual>());
|
|
|
|
var currentResourceActuals = resourceActuals[expCatId][resourceId];
|
|
|
|
if (!resource.IsRemoved)
|
|
{
|
|
// Perform update or create for resource allocations and actuals
|
|
foreach (var weekendingAsText in resource.ActualAllocations.Keys)
|
|
{
|
|
var weekending = Utils.ConvertFromUnixDate(Convert.ToInt64(weekendingAsText));
|
|
decimal newValue = resource.ActualAllocations[weekendingAsText] > 0 ?
|
|
resource.ActualAllocations[weekendingAsText] : 0;
|
|
|
|
// Check existance of ScenarioDetail item for resource actuals records
|
|
if (!detailsActuals[expCatId].ContainsKey(weekending))
|
|
{
|
|
// Create ScenarioDetails item
|
|
var detailsItem = new ScenarioDetail()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ParentID = actualScenarioId,
|
|
ExpenditureCategoryId = expCatId,
|
|
WeekEndingDate = weekending,
|
|
WeekOrdinal = 0,
|
|
Quantity = 0, // Will be calculated later
|
|
Cost = 0 // Will be calculated later
|
|
};
|
|
scenarioDetailsToAdd.Add(detailsItem);
|
|
detailsActuals[expCatId].Add(weekending, detailsItem);
|
|
}
|
|
|
|
var parentScenarioDetailsItemId = detailsActuals[expCatId][weekending].Id;
|
|
PeopleResourceActual actualItem = null;
|
|
|
|
if (!currentResourceActuals.ContainsKey(weekending))
|
|
{
|
|
// Create new allocation record
|
|
actualItem = new PeopleResourceActual()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ParentId = parentScenarioDetailsItemId,
|
|
PeopleResourceId = resourceId,
|
|
Quantity = newValue,
|
|
Cost = newValue * RateManager.GetRateValue(rates, expCatId, weekending),
|
|
RowCreateDate = DateTime.UtcNow
|
|
};
|
|
peopleResourceActualsToAdd.Add(actualItem);
|
|
resourceActuals[expCatId][resourceId].Add(weekending, actualItem);
|
|
}
|
|
else
|
|
{
|
|
// Update existing resource actuals record
|
|
actualItem = currentResourceActuals[weekending];
|
|
actualItem.Quantity = newValue;
|
|
actualItem.Cost = newValue * RateManager.GetRateValue(rates, expCatId, weekending);
|
|
DbContext.Entry(actualItem).State = EntityState.Modified;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
DbContext.ScenarioDetail.AddRange(scenarioDetailsToAdd);
|
|
DbContext.PeopleResourceActuals.AddRange(peopleResourceActualsToAdd);
|
|
DbContext.BulkSaveChanges();
|
|
}
|
|
|
|
#endregion
|
|
#region Recalculate Details actuals
|
|
|
|
foreach (var expCatIdAsText in model.Expenditures.Keys)
|
|
{
|
|
var expCatId = new Guid(expCatIdAsText);
|
|
var expCat = model.Expenditures[expCatIdAsText];
|
|
|
|
var weekendingsToRecalculate =
|
|
expCat.Teams.Values.SelectMany(t =>
|
|
t.Resources.Values.Where(r => !r.IsRemoved).SelectMany(a => a.ActualAllocations.Keys))
|
|
.Distinct().Select(x => Utils.ConvertFromUnixDate(Convert.ToInt64(x))).OrderBy(x => x).ToList();
|
|
|
|
foreach (var we in weekendingsToRecalculate)
|
|
{
|
|
if (detailsActuals.ContainsKey(expCatId) && detailsActuals[expCatId].ContainsKey(we))
|
|
{
|
|
var detailsItem = detailsActuals[expCatId][we];
|
|
var foundResourceActualsRecords = new List<PeopleResourceActual>();
|
|
decimal newQuantityValue = 0;
|
|
decimal newCostValue = 0;
|
|
|
|
if (resourceActuals.ContainsKey(expCatId))
|
|
{
|
|
foundResourceActualsRecords = resourceActuals[expCatId].Values
|
|
.Where(r => r.ContainsKey(we)).Select(a => a[we]).ToList();
|
|
}
|
|
|
|
if (foundResourceActualsRecords.Count > 0)
|
|
{
|
|
newQuantityValue = foundResourceActualsRecords.Sum(x => x.Quantity);
|
|
newCostValue = foundResourceActualsRecords.Sum(x => x.Cost);
|
|
}
|
|
|
|
detailsItem.Quantity = newQuantityValue > 0 ? newQuantityValue : 0;
|
|
detailsItem.Cost = newCostValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
|
|
private void UpdateTeamAllocations(Guid scenarioId, ActivityCalendarSaveModel.ScenarioSaveModel model, ScenarioDetailsEventsTracker tracker = null)
|
|
{
|
|
// Teams, that have changed allocations
|
|
var teamsWithChangedAllocations = model.Expenditures.Values.SelectMany(e =>
|
|
e.Teams.Where(t => t.Value.Allocations.Count > 0).Select(t => new Guid(t.Key))).ToList();
|
|
|
|
// Get existing team allocations from DB
|
|
var teamAllocations = DbContext.TeamAllocations.Where(x =>
|
|
x.ScenarioId.Equals(scenarioId) && teamsWithChangedAllocations.Contains(x.TeamId))
|
|
.GroupBy(x => x.ExpenditureCategoryId).ToDictionary(e => e.Key, eGrp =>
|
|
eGrp.GroupBy(x => x.TeamId).ToDictionary(t => t.Key, tGrp =>
|
|
tGrp.GroupBy(x => x.WeekEndingDate).ToDictionary(w => w.Key, x => x.FirstOrDefault())));
|
|
|
|
var teamAllocationToAddCollection = new List<TeamAllocation>();
|
|
foreach (var expCatIdAsText in model.Expenditures.Keys)
|
|
{
|
|
var expCatId = new Guid(expCatIdAsText);
|
|
var expCatModel = model.Expenditures[expCatIdAsText];
|
|
|
|
if (!teamAllocations.ContainsKey(expCatId))
|
|
teamAllocations.Add(expCatId, new Dictionary<Guid, Dictionary<DateTime, TeamAllocation>>());
|
|
|
|
foreach (var teamIdAsText in expCatModel.Teams.Keys)
|
|
{
|
|
var teamId = new Guid(teamIdAsText);
|
|
var teamModel = expCatModel.Teams[teamIdAsText];
|
|
|
|
if (!teamAllocations[expCatId].ContainsKey(teamId))
|
|
teamAllocations[expCatId].Add(teamId, new Dictionary<DateTime, TeamAllocation>());
|
|
|
|
var currentTeamAllocations = teamAllocations[expCatId][teamId];
|
|
|
|
// Perform update or create for team allocations
|
|
foreach (var weekendingAsText in teamModel.Allocations.Keys)
|
|
{
|
|
// Get allocations to update
|
|
var weekending = Utils.ConvertFromUnixDate(Convert.ToInt64(weekendingAsText));
|
|
TeamAllocation allocationItem;
|
|
|
|
decimal newValue = teamModel.Allocations[weekendingAsText] > 0 ? teamModel.Allocations[weekendingAsText] : 0;
|
|
|
|
// Check resource allocation is out of the resource team membership dates
|
|
if (!currentTeamAllocations.ContainsKey(weekending))
|
|
{
|
|
// Create new allocation record, if it is correct
|
|
allocationItem = new TeamAllocation
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ScenarioId = scenarioId,
|
|
ExpenditureCategoryId = expCatId,
|
|
TeamId = teamId,
|
|
WeekEndingDate = weekending,
|
|
Quantity = newValue
|
|
};
|
|
teamAllocationToAddCollection.Add(allocationItem);
|
|
teamAllocations[expCatId][teamId].Add(weekending, allocationItem);
|
|
|
|
// Track changes for audit
|
|
tracker?.TeamAllocationValueAdded(allocationItem);
|
|
}
|
|
else
|
|
{
|
|
// Update existing allocation record
|
|
allocationItem = currentTeamAllocations[weekending];
|
|
var oldValue = allocationItem.Quantity;
|
|
allocationItem.Quantity = newValue;
|
|
|
|
// Track changes for audit
|
|
tracker?.TeamAllocationValueChanged(allocationItem, oldValue);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
DbContext.TeamAllocations.AddRange(teamAllocationToAddCollection);
|
|
}
|
|
|
|
private void UpdateScenarioAllocations(Scenario scenario, ActivityCalendarSaveModel.ScenarioSaveModel model,
|
|
ScenarioRecalculationMode recalculationMode, IReadOnlyDictionary<Guid, bool> expenditureTypes,
|
|
Dictionary<Guid, IEnumerable<RateModel>> rates, IEnumerable<DateTime> weekEndings,
|
|
ScenarioDetailsEventsTracker tracker = null)
|
|
{
|
|
if (recalculationMode == ScenarioRecalculationMode.None)
|
|
// No need to recalculate allocations
|
|
return;
|
|
|
|
if (scenario == null)
|
|
throw new ArgumentNullException(nameof(scenario));
|
|
|
|
if (weekEndings == null)
|
|
throw new ArgumentNullException(nameof(weekEndings));
|
|
|
|
var recalculateEntireScenario = recalculationMode == ScenarioRecalculationMode.Complete;
|
|
|
|
if (!scenario.StartDate.HasValue)
|
|
throw new BLLException($"Scenario have no values for Start Date (id = {scenario.Id})");
|
|
|
|
// Get scenario details from DB
|
|
var scenarioDetails = DbContext.ScenarioDetail
|
|
.Where(x => (x.ParentID == scenario.Id) && (x.ExpenditureCategoryId.HasValue) && (x.WeekEndingDate.HasValue))
|
|
.GroupBy(x => x.ExpenditureCategoryId.Value).ToDictionary(e => e.Key, eGrp =>
|
|
eGrp.GroupBy(x => x.WeekEndingDate.Value).ToDictionary(w => w.Key, a => a.FirstOrDefault()));
|
|
|
|
// Get scenario allocations and resource allocations from DB
|
|
var teamAllocations = DbContext.TeamAllocations.Where(x => x.ScenarioId.Equals(scenario.Id))
|
|
.GroupBy(x => x.ExpenditureCategoryId).ToDictionary(e => e.Key, eGrp =>
|
|
eGrp.GroupBy(x => x.TeamId).ToDictionary(t => t.Key, tGrp =>
|
|
tGrp.GroupBy(x => x.WeekEndingDate).ToDictionary(w => w.Key, a => a.FirstOrDefault())));
|
|
|
|
var resourceAllocations = DbContext.PeopleResourceAllocations.AsNoTracking().Where(x => x.ScenarioId.Equals(scenario.Id)).ToList();
|
|
|
|
var requiredECs = recalculateEntireScenario ? scenarioDetails.Keys.Select(x => x.ToString()).ToList() : model.Expenditures.Keys.ToList();
|
|
|
|
var teamAllocationsToAdd = new List<TeamAllocation>();
|
|
var scenarioDetailsToAdd = new List<ScenarioDetail>();
|
|
|
|
foreach (var expCatIdAsText in requiredECs)
|
|
{
|
|
var expCatId = new Guid(expCatIdAsText);
|
|
var expCatModel = model.Expenditures.ContainsKey(expCatIdAsText) ? model.Expenditures[expCatIdAsText] : null;
|
|
var isSuperExpCat = expenditureTypes.ContainsKey(expCatId) && !expenditureTypes[expCatId];
|
|
|
|
var hasChangedAllocations = expCatModel != null && expCatModel.Teams.Values.Any(t =>
|
|
((t.Allocations.Count > 0) ||
|
|
t.Resources.Values.Any(r => (r.ForecastAllocations.Count > 0) || r.IsRemoved)));
|
|
|
|
if ((recalculationMode == ScenarioRecalculationMode.SuperExpendituresOnly) &&
|
|
!isSuperExpCat)
|
|
continue;
|
|
|
|
if ((recalculationMode == ScenarioRecalculationMode.SuperExpendituresAndChangedAllocations) &&
|
|
!isSuperExpCat && !hasChangedAllocations)
|
|
continue;
|
|
|
|
var detailsWeekendingsToUpdate = new List<DateTime>();
|
|
var requiredTeams = new List<string>();
|
|
|
|
if (recalculateEntireScenario && teamAllocations.ContainsKey(expCatId))
|
|
requiredTeams.AddRange(teamAllocations[expCatId].Keys.Select(x => x.ToString()));
|
|
|
|
if (expCatModel != null && expCatModel.Teams != null)
|
|
requiredTeams.AddRange(expCatModel.Teams.Keys.ToList());
|
|
|
|
foreach (var teamIdAsText in requiredTeams.Distinct())
|
|
{
|
|
var teamId = new Guid(teamIdAsText);
|
|
var teamModel = expCatModel != null && expCatModel.Teams.ContainsKey(teamIdAsText) ? expCatModel.Teams[teamIdAsText] : null;
|
|
Dictionary<DateTime, string> teamWeekendingsToUpdate = null;
|
|
|
|
#region Recalculation of team allocations
|
|
|
|
if (recalculateEntireScenario || (teamModel != null && teamModel.Resources.Values.Any(x => x.IsRemoved)))
|
|
{
|
|
// Allocations should be recalculated for all team weekendings
|
|
teamWeekendingsToUpdate = weekEndings.ToDictionary(k => k, v => Convert.ToString(Utils.ConvertToUnixDate(v)));
|
|
}
|
|
else
|
|
{
|
|
// Allocations should be recalculated for a list of weekendings, scenario has changes on
|
|
teamWeekendingsToUpdate = teamModel?.Resources.Values.SelectMany(x => x.ForecastAllocations.Keys)
|
|
.Union(teamModel.Allocations.Keys) // Add weekending for changed team allocations
|
|
.Distinct().ToDictionary(k => Utils.ConvertFromUnixDate(Convert.ToInt64(k)), v => v) ?? new Dictionary<DateTime, string>();
|
|
}
|
|
|
|
foreach (var we in teamWeekendingsToUpdate.Keys)
|
|
{
|
|
TeamAllocation allocationItem = null;
|
|
string weAsText = teamWeekendingsToUpdate[we];
|
|
var allocationItemWasCreated = false;
|
|
|
|
if (teamAllocations.ContainsKey(expCatId) && teamAllocations[expCatId].ContainsKey(teamId) &&
|
|
teamAllocations[expCatId][teamId].ContainsKey(we))
|
|
{
|
|
// Allocation records exists in DB
|
|
allocationItem = teamAllocations[expCatId][teamId][we];
|
|
}
|
|
else
|
|
{
|
|
allocationItem = new TeamAllocation()
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ScenarioId = scenario.Id,
|
|
ExpenditureCategoryId = expCatId,
|
|
TeamId = teamId,
|
|
WeekEndingDate = we,
|
|
Quantity = 0
|
|
};
|
|
teamAllocationsToAdd.Add(allocationItem);
|
|
allocationItemWasCreated = true;
|
|
|
|
// Add created team allocation records to indexed struct for use in details recalculations
|
|
if (!teamAllocations.ContainsKey(expCatId))
|
|
teamAllocations.Add(expCatId, new Dictionary<Guid, Dictionary<DateTime, TeamAllocation>>());
|
|
|
|
if (!teamAllocations[expCatId].ContainsKey(teamId))
|
|
teamAllocations[expCatId].Add(teamId, new Dictionary<DateTime, TeamAllocation>());
|
|
|
|
if (!teamAllocations[expCatId][teamId].ContainsKey(we))
|
|
teamAllocations[expCatId][teamId].Add(we, allocationItem);
|
|
}
|
|
|
|
// Perform agregation from resource level to team level
|
|
var foundResourceRecords = resourceAllocations.Where(x =>
|
|
x.ExpenditureCategoryId.Equals(expCatId) && x.TeamId.Equals(teamId) && (x.WeekEndingDate == we))
|
|
.ToList();
|
|
|
|
decimal newValue = 0;
|
|
|
|
if (foundResourceRecords.Count > 0)
|
|
// Scenario Team contains resources assigned. Get Team allocation value as summ of the assigned resource allocations
|
|
newValue = foundResourceRecords.Sum(x => x.Quantity);
|
|
else
|
|
{
|
|
if (teamModel != null && teamModel.Allocations.ContainsKey(weAsText))
|
|
// Team allocations were changed. As this team has no resources assigned, get incoming model value
|
|
newValue = teamModel.Allocations[weAsText];
|
|
}
|
|
|
|
var oldValue = allocationItem.Quantity;
|
|
allocationItem.Quantity = newValue > 0 ? newValue : 0;
|
|
|
|
if (tracker != null)
|
|
{
|
|
// Track changes for audit
|
|
if (allocationItemWasCreated)
|
|
tracker.TeamAllocationValueAdded(allocationItem, true);
|
|
else
|
|
tracker.TeamAllocationValueChanged(allocationItem, oldValue, true);
|
|
}
|
|
}
|
|
|
|
detailsWeekendingsToUpdate.AddRange(teamWeekendingsToUpdate.Keys);
|
|
|
|
#endregion
|
|
}
|
|
|
|
#region Recalculation of Scenario Details
|
|
|
|
detailsWeekendingsToUpdate = detailsWeekendingsToUpdate.Distinct().OrderBy(x => x).ToList();
|
|
|
|
foreach (var we in detailsWeekendingsToUpdate)
|
|
{
|
|
ScenarioDetail detailItem;
|
|
var detailsRecordWasCreated = false;
|
|
|
|
if (scenarioDetails.ContainsKey(expCatId) && scenarioDetails[expCatId].ContainsKey(we))
|
|
{
|
|
// Details records exists in DB
|
|
detailItem = scenarioDetails[expCatId][we];
|
|
}
|
|
else
|
|
{
|
|
detailItem = new ScenarioDetail
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ParentID = scenario.Id,
|
|
ExpenditureCategoryId = expCatId,
|
|
WeekEndingDate = we,
|
|
WeekOrdinal = 0,
|
|
Quantity = 0,
|
|
Cost = 0
|
|
};
|
|
scenarioDetailsToAdd.Add(detailItem);
|
|
detailsRecordWasCreated = true;
|
|
}
|
|
|
|
// Perform agregation from team level to EC level
|
|
var foundTeamAllocationRecords = new List<TeamAllocation>();
|
|
|
|
if (teamAllocations.ContainsKey(expCatId))
|
|
{
|
|
foundTeamAllocationRecords = teamAllocations[expCatId].Values
|
|
.SelectMany(t => t.Values.Where(a => (a.WeekEndingDate == we))).ToList();
|
|
}
|
|
|
|
decimal newQuantityValue = (foundTeamAllocationRecords.Count > 0) ? foundTeamAllocationRecords.Sum(x => x.Quantity) : 0;
|
|
|
|
if (newQuantityValue < 0)
|
|
newQuantityValue = 0;
|
|
|
|
decimal newCostValue = newQuantityValue * RateManager.GetRateValue(rates, expCatId, we);
|
|
|
|
var oldQuantityValue = detailItem.Quantity;
|
|
var oldCostValue = detailItem.Cost;
|
|
detailItem.Quantity = newQuantityValue;
|
|
detailItem.Cost = newCostValue;
|
|
|
|
if (tracker != null)
|
|
{
|
|
// Track changes for audit
|
|
if (detailsRecordWasCreated)
|
|
tracker.ScenarioDetailsValueAdded(detailItem, true);
|
|
else
|
|
tracker.ScenarioDetailsValueChanged(detailItem, oldQuantityValue, oldCostValue, true);
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
DbContext.TeamAllocations.AddRange(teamAllocationsToAdd);
|
|
DbContext.ScenarioDetail.AddRange(scenarioDetailsToAdd);
|
|
DbContext.BulkSaveChanges();
|
|
}
|
|
|
|
private bool AreScenarioActualsChanged(ActivityCalendarSaveModel.ScenarioSaveModel model)
|
|
{
|
|
if (model == null)
|
|
throw new ArgumentNullException("model");
|
|
|
|
bool result = model.Expenditures.Values
|
|
.Any(e => e.Teams.Values.Any(t => t.Resources.Values.Any(r => (r.ActualAllocations.Count > 0) || r.IsRemoved)));
|
|
return result;
|
|
}
|
|
|
|
private bool AreScenarioProjectionsChanged(ActivityCalendarSaveModel.ScenarioSaveModel model)
|
|
{
|
|
if (model == null)
|
|
throw new ArgumentNullException("model");
|
|
|
|
bool result = model.Expenditures.Values
|
|
.Any(e => e.Teams.Values.Any(t => t.Resources.Values.Any(r =>
|
|
(r.ForecastAllocations.Count > 0) || r.IsRemoved)));
|
|
return result;
|
|
}
|
|
|
|
private bool AreScenarioTeamAllocationsChanged(ActivityCalendarSaveModel.ScenarioSaveModel model)
|
|
{
|
|
if (model == null)
|
|
throw new ArgumentNullException("model");
|
|
|
|
bool result = model.Expenditures.Values
|
|
.Any(e => e.Teams.Values.Any(t => t.Allocations.Count > 0));
|
|
return result;
|
|
}
|
|
|
|
private List<Guid> GetScenarioNewTeams(Guid parentProjectId, ActivityCalendarSaveModel.ScenarioSaveModel model)
|
|
{
|
|
if (model == null)
|
|
throw new ArgumentNullException("model");
|
|
|
|
var changedTeams = model.Expenditures.Values.SelectMany(x => x.Teams.Select(t => t.Key))
|
|
.Select(x => new Guid(x)).ToList();
|
|
|
|
var projectTeams = DbContext.Team2Project.Where(x => x.ProjectId == parentProjectId).Select(x => x.TeamId).ToList();
|
|
var newTeams = changedTeams.Except(projectTeams).ToList();
|
|
|
|
return newTeams;
|
|
}
|
|
|
|
private List<Guid> GetScenarioChangedTeams(ActivityCalendarSaveModel.ScenarioSaveModel model)
|
|
{
|
|
if (model == null)
|
|
throw new ArgumentNullException("model");
|
|
|
|
var changedTeams = model.Expenditures.Values.SelectMany(x => x.Teams.Keys).Select(x => new Guid(x)).ToList();
|
|
return changedTeams;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns D[scenarioId, bool]. True for scenario, if it contains at least one SuperExpenditure, otherwise, False
|
|
/// </summary>
|
|
private Dictionary<Guid, bool> ScenariosContainsSuperExpenditures(List<Guid> scenarios)
|
|
{
|
|
if (scenarios == null)
|
|
throw new ArgumentNullException(nameof(scenarios));
|
|
|
|
if (scenarios.Count < 1)
|
|
return new Dictionary<Guid, bool>();
|
|
|
|
var scenariosHasSuperExpCats =
|
|
DbContext.ScenarioDetail.AsNoTracking().Include(x => x.ExpenditureCategory)
|
|
.Where(x => x.ParentID.HasValue && scenarios.Contains(x.ParentID.Value) && !x.ExpenditureCategory.AllowResourceAssignment)
|
|
.GroupBy(x => x.ParentID.Value).ToDictionary(k => k.Key, grp => grp.Any());
|
|
|
|
return scenariosHasSuperExpCats;
|
|
}
|
|
|
|
private List<long> GetScenarioActualsChangedWeekendings(ActivityCalendarSaveModel.ScenarioSaveModel scenarioModel)
|
|
{
|
|
if (scenarioModel == null)
|
|
throw new ArgumentNullException("model");
|
|
|
|
var result = scenarioModel.Expenditures.Values
|
|
.SelectMany(e => e.Teams.Values.SelectMany(t => t.Resources.Values.SelectMany(r => r.ActualAllocations.Keys)))
|
|
.Distinct().Select(x => Convert.ToInt64(x)).ToList();
|
|
return result;
|
|
}
|
|
|
|
private List<Guid> GetScenarioActualsChangedExpenditures(ActivityCalendarSaveModel.ScenarioSaveModel scenarioModel)
|
|
{
|
|
if (scenarioModel == null)
|
|
throw new ArgumentNullException("model");
|
|
|
|
var result = scenarioModel.Expenditures.Keys.Where(expCatId =>
|
|
scenarioModel.Expenditures[expCatId].Teams.Values.Any(t =>
|
|
t.Resources.Values.Any(r => r.ActualAllocations.Count > 0)))
|
|
.Select(x => new Guid(x)).ToList();
|
|
return result;
|
|
}
|
|
|
|
private ScenarioRecalculationMode GetScenarioRecalculationMode(ActivityCalendarSaveModel model,
|
|
Guid scenarioId, bool scenarioIsBottomUp, bool scenarioHasProjectionsToUpdate, bool scenarioHasTeamAllocationsToUpdate)
|
|
{
|
|
if (model == null)
|
|
throw new ArgumentNullException("model");
|
|
|
|
if (scenarioId.Equals(Guid.Empty))
|
|
throw new ArgumentNullException("scenarioId");
|
|
|
|
var scenarioIdAsText = scenarioId.ToString();
|
|
|
|
if (!model.Scenarios.ContainsKey(scenarioIdAsText))
|
|
throw new BLLException(String.Format("Scenario not found in data save model (Id = {0})", scenarioIdAsText));
|
|
|
|
var scenarioModel = model.Scenarios[scenarioIdAsText];
|
|
|
|
// Determine scenario recalculation mode
|
|
var scenarioUpdateMode = ScenarioRecalculationMode.None;
|
|
|
|
if ((scenarioHasProjectionsToUpdate || scenarioHasTeamAllocationsToUpdate) && scenarioModel.UpdateProjections)
|
|
scenarioUpdateMode = ScenarioRecalculationMode.SuperExpendituresAndChangedAllocations;
|
|
|
|
if (scenarioIsBottomUp)
|
|
scenarioUpdateMode = ScenarioRecalculationMode.Complete;
|
|
|
|
return scenarioUpdateMode;
|
|
}
|
|
|
|
private void FilterTeamsByResources(List<TeamSummaryInfoModel> teams, List<Guid> resourcesToKeep)
|
|
{
|
|
if (teams == null)
|
|
throw new ArgumentNullException("teams");
|
|
|
|
if ((resourcesToKeep == null) || (resourcesToKeep.Count < 1))
|
|
return;
|
|
|
|
var keepIdsAsText = resourcesToKeep.Select(x => x.ToString()).ToList();
|
|
|
|
for (var tIndex = teams.Count - 1; tIndex >= 0; tIndex--)
|
|
{
|
|
var team = teams[tIndex];
|
|
if (team.ExpCategories != null)
|
|
{
|
|
var expCatKeys = team.ExpCategories.Keys.ToList();
|
|
foreach (var expCatId in expCatKeys)
|
|
{
|
|
var expCat = team.ExpCategories[expCatId];
|
|
if (expCat.Resources != null)
|
|
{
|
|
var resourceKeysToRemove = expCat.Resources.Keys.ToList().Except(keepIdsAsText).ToList();
|
|
foreach (var resourceKey in resourceKeysToRemove)
|
|
{
|
|
expCat.Resources.Remove(resourceKey);
|
|
}
|
|
}
|
|
|
|
if ((expCat.Resources == null) || (expCat.Resources.Count < 1))
|
|
{
|
|
team.ExpCategories.Remove(expCatId);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((team.ExpCategories == null) || (team.ExpCategories.Count < 1))
|
|
teams.RemoveAt(tIndex);
|
|
}
|
|
}
|
|
|
|
private void SetNonProjectTimeEditPermissions(List<TeamSummaryInfoModel> teams, Dictionary<Guid, AccessLevel> teamsAccess)
|
|
{
|
|
if ((teams == null) || (teamsAccess == null) || (teams.Count < 1) || (teamsAccess.Count < 1))
|
|
return;
|
|
|
|
var teamIds = teamsAccess.Keys.ToList();
|
|
var prMngr = new PeopleResourcesManager(DbContext);
|
|
var nptMngr = new NonProjectTimeManager(DbContext);
|
|
|
|
// Get list of team-wide NPTs in teams collection and extract team list for every NPT-item.
|
|
// Compare team list for every NPT-item with incoming teamIds to find out the NPT-item is
|
|
// editable or not. NPT-item seems to be editable if it's team list equals to incoming teamIds list
|
|
var teamWideNpts =
|
|
teams.Where(t => (t.ExpCategories != null)).SelectMany(t => t.ExpCategories.Values)
|
|
.Where(e => (e.Resources != null)).SelectMany(e => e.Resources.Values)
|
|
.Where(r => (r.NonProjectTime != null)).SelectMany(r => r.NonProjectTime.Values)
|
|
.Where(n => (n != null) && n.isTeamWide).Select(n => n.Id).Distinct().ToList();
|
|
|
|
var teamWideNptItems = nptMngr.GetNonProjectTimes(teamWideNpts, true);
|
|
var teamWideNptsEditableStatus = teamWideNptItems.Values.Select(x => new
|
|
{
|
|
Id = x.Id,
|
|
IsEditable = (x.Teams != null) && (x.Teams.Count > 0) && (x.Teams.Except(teamIds).Count() < 1)
|
|
&& x.Teams.TrueForAll(t => teamsAccess.ContainsKey(t) && (teamsAccess[t] == AccessLevel.Write))
|
|
}).GroupBy(x => x.Id).ToDictionary(grp => grp.Key, v => v.Select(z => z.IsEditable).Min());
|
|
|
|
// Get access permissions for resources in teams and set resource-level NPT editable, if
|
|
// the corresponding resource is editable for this team
|
|
var resourcesWithTeams = prMngr.GetPeopleResourceWithTeams4Teams(teamIds);
|
|
var resourcesInTeamsEditableStatus =
|
|
resourcesWithTeams.Where(x => (x.Teams != null)).ToDictionary(x => x.Id, x =>
|
|
x.Teams.Select(t => new
|
|
{
|
|
Id = t.TeamId,
|
|
IsEditable = !t.ReadOnly
|
|
}).GroupBy(t => t.Id).ToDictionary(k => k.Key, grp => grp.Select(r => r.IsEditable).FirstOrDefault()));
|
|
|
|
foreach (var team in teams)
|
|
{
|
|
if (team.ExpCategories != null)
|
|
{
|
|
var teamId = new Guid(team.Id);
|
|
|
|
foreach (var expCat in team.ExpCategories.Values)
|
|
{
|
|
if (expCat.Resources != null)
|
|
{
|
|
foreach (var resource in expCat.Resources.Values)
|
|
{
|
|
if (resource.NonProjectTime != null)
|
|
{
|
|
foreach (string nptKey in resource.NonProjectTime.Keys)
|
|
{
|
|
var nptItem = resource.NonProjectTime[nptKey];
|
|
|
|
if (nptItem.isTeamWide)
|
|
{
|
|
nptItem.isReadOnly = teamWideNptsEditableStatus.ContainsKey(nptItem.Id) ?
|
|
!teamWideNptsEditableStatus[nptItem.Id] : true;
|
|
}
|
|
else
|
|
{
|
|
nptItem.isReadOnly = resourcesInTeamsEditableStatus.ContainsKey(resource.Id) &&
|
|
resourcesInTeamsEditableStatus[resource.Id].ContainsKey(teamId) ?
|
|
!resourcesInTeamsEditableStatus[resource.Id][teamId] : true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private IEnumerable<CreditDepartmentModel> GetCostCenters(IEnumerable<ScenarioDetail> needAllocations,
|
|
IEnumerable<TeamAllocation> teamAllocations, IEnumerable<ExpenditureDetail> projectRolesInfo,
|
|
bool ignoreExpendituresWithoutTeamAllocations)
|
|
{
|
|
// Gather all expenditures in the model and get cost centers
|
|
var costCenterMngr = (new CreditDepartmentManager(DbContext));
|
|
|
|
var expCatsWithNeed = (needAllocations != null) ?
|
|
needAllocations.Where(x => x.ExpenditureCategoryId.HasValue && x.WeekEndingDate.HasValue &&
|
|
x.Quantity.HasValue && (x.Quantity.Value > 0))
|
|
.Select(x => x.ExpenditureCategoryId.Value).Distinct().ToList() : null;
|
|
|
|
if (expCatsWithNeed != null)
|
|
{
|
|
// Check team allocations existance only for ordinary ECs
|
|
var projectRolesInfoIds = (projectRolesInfo != null) ? projectRolesInfo.Select(x => x.ExpenditureCategoryId).ToList() : new List<Guid>();
|
|
var teamAllocationsFiltered = (teamAllocations != null) ?
|
|
teamAllocations.Where(x => expCatsWithNeed.Contains(x.ExpenditureCategoryId)).ToList() :
|
|
new List<TeamAllocation>();
|
|
|
|
expCatsWithNeed = expCatsWithNeed.Where(e =>
|
|
projectRolesInfoIds.Contains(e) ||
|
|
teamAllocationsFiltered.Any(ta => (ta.ExpenditureCategoryId == e) && (ta.Quantity > 0))).ToList();
|
|
}
|
|
|
|
var result = costCenterMngr.GetCreditDepartmentsByExpCats(expCatsWithNeed);
|
|
return result;
|
|
}
|
|
|
|
private IEnumerable<ExpenditureDetail> GetProjectRoles(IEnumerable<ScenarioDetail> needAllocations, IEnumerable<TeamAllocation> teamAllocations,
|
|
IEnumerable<CreditDepartmentModel> costCenters, IEnumerable<ExpenditureDetail> projectRolesInfo)
|
|
{
|
|
if ((needAllocations == null) || (projectRolesInfo == null))
|
|
return null;
|
|
|
|
// Get all Project Role from data model, for which we have extended info
|
|
var infoForProjectRoleIds = projectRolesInfo.Select(x => x.ExpenditureCategoryId).ToList();
|
|
|
|
// Get project roles with non-zero Need values - by scenarios
|
|
var projectRolesNeedIndexed = needAllocations.Where(x => x.ParentID.HasValue && x.ExpenditureCategoryId.HasValue && x.WeekEndingDate.HasValue &&
|
|
infoForProjectRoleIds.Contains(x.ExpenditureCategoryId.Value))
|
|
.GroupBy(x => x.ParentID.Value).ToDictionary(g0 => g0.Key, g0 =>
|
|
g0.GroupBy(x => x.ExpenditureCategoryId.Value).ToDictionary(g1 => g1.Key, g1 =>
|
|
g1.ToDictionary(k2 => k2.WeekEndingDate.Value, v2 => v2.Quantity.HasValue ? v2.Quantity.Value : 0)));
|
|
|
|
// Get project roles with non-zero need and format team allocations for quick access - by scenarios
|
|
var projectRolesWithNonZeroNeed = projectRolesNeedIndexed.SelectMany(x => x.Value.Where(z => z.Value.Values.Any(v => v > 0)).Select(z => z.Key)).Distinct().ToList();
|
|
var teamAllocationsForRolesIndexed = teamAllocations.Where(x => projectRolesWithNonZeroNeed.Contains(x.ExpenditureCategoryId))
|
|
.GroupBy(x => x.ScenarioId).ToDictionary(g0 => g0.Key, g0 =>
|
|
g0.GroupBy(x => x.ExpenditureCategoryId).ToDictionary(g1 => g1.Key, g1 =>
|
|
g1.GroupBy(y => y.WeekEndingDate).ToDictionary(g2 => g2.Key, g2 =>
|
|
g2.Select(z => z.Quantity).Sum())));
|
|
|
|
// Get Project roles with non-zero remaining need (need != Sum(Team allocations)). Look for roles in every scenario
|
|
var rolesWithNonZeroRemainingNeedDuplicated = new List<Guid>();
|
|
foreach (var scenarioId in projectRolesNeedIndexed.Keys)
|
|
{
|
|
var scenarioNeedData = projectRolesNeedIndexed[scenarioId];
|
|
var projectRolesToLook = scenarioNeedData.Keys.Intersect(projectRolesWithNonZeroNeed).ToList();
|
|
|
|
if (!teamAllocationsForRolesIndexed.ContainsKey(scenarioId))
|
|
{
|
|
rolesWithNonZeroRemainingNeedDuplicated.AddRange(projectRolesToLook);
|
|
}
|
|
else
|
|
{
|
|
var scenarioTeamAllocationsData = teamAllocationsForRolesIndexed[scenarioId];
|
|
foreach (var projectRoleId in projectRolesToLook)
|
|
{
|
|
var hasNonZeroRemainingNeed = false;
|
|
var currentRoleNeed = scenarioNeedData[projectRoleId];
|
|
|
|
if (scenarioTeamAllocationsData.ContainsKey(projectRoleId))
|
|
{
|
|
// Some teams are allocated to current project role
|
|
var allocationsToCurrentRole = scenarioTeamAllocationsData[projectRoleId];
|
|
var weekendings = currentRoleNeed.Keys;
|
|
|
|
foreach (var we in weekendings)
|
|
{
|
|
var currentWeekendingNeed = currentRoleNeed[we];
|
|
var currentWeekendingAllocations = allocationsToCurrentRole.ContainsKey(we) ? allocationsToCurrentRole[we] : 0;
|
|
|
|
if (currentWeekendingNeed > currentWeekendingAllocations)
|
|
{
|
|
hasNonZeroRemainingNeed = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// No teams allocated to project role. But project role has non-zero need
|
|
hasNonZeroRemainingNeed = true;
|
|
}
|
|
|
|
if (hasNonZeroRemainingNeed)
|
|
{
|
|
rolesWithNonZeroRemainingNeedDuplicated.Add(projectRoleId);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
var rolesWithNonZeroRemainingNeed = rolesWithNonZeroRemainingNeedDuplicated.Distinct().ToList();
|
|
var costCentersIds = (costCenters != null) ? costCenters.Select(x => x.Id).ToList() : new List<Guid>();
|
|
var result = projectRolesInfo.Where(x => rolesWithNonZeroRemainingNeed.Contains(x.ExpenditureCategoryId) &&
|
|
((costCentersIds.Count < 1) || costCentersIds.Contains(x.CreditId))).ToList();
|
|
|
|
return result;
|
|
}
|
|
|
|
private List<SelectListItem> ConvertCostCentersToOptions(IEnumerable<CreditDepartmentModel> costCenters)
|
|
{
|
|
if (costCenters == null)
|
|
return null;
|
|
|
|
var result = costCenters.Select(x => new SelectListItem()
|
|
{
|
|
Value = x.Id.ToString(),
|
|
Text = x.Name
|
|
}).OrderBy(x => x.Text).ToList();
|
|
|
|
return result;
|
|
}
|
|
|
|
private List<ActivityCalendarFilterOptions.ProjectRoleItem> ConvertExpendituresToOptions(IEnumerable<ExpenditureDetail> expCats)
|
|
{
|
|
if (expCats == null)
|
|
return null;
|
|
|
|
var result = expCats.Select(x => new ActivityCalendarFilterOptions.ProjectRoleItem()
|
|
{
|
|
Value = x.ExpenditureCategoryId.ToString(),
|
|
Text = x.ExpenditureCategoryName,
|
|
CostCenterId = x.CreditId.ToString()
|
|
}).OrderBy(x => x.Text).ToList();
|
|
|
|
return result;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Overrides
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_isContexLocal)
|
|
_dbContext.Dispose();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
} |