EnVisageOnline/Main/Source/EnVisage/Code/BLL/ActivityCalendarManager.cs

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
}
}