EnVisageOnline/Main/Source/EnVisage/Code/BLL/MixManagers/MongoMixManager.cs

1040 lines
37 KiB
C#

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using System.Web.Mvc;
using EnVisage.Code.DAL;
using EnVisage.Code.DAL.Mongo;
using EnVisage.Models;
using MongoDB.Bson;
using MongoDB.Driver;
using NLog;
namespace EnVisage.Code.BLL
{
public class MongoMixManager : IMixManager<Mix>
{
protected Logger Logger = LogManager.GetCurrentClassLogger();
private readonly string _userId;
private readonly EnVisageEntities _dbContext;
protected EnVisageEntities DbContext => _dbContext;
// private bool _isContexLocal;
public MongoMixManager(EnVisageEntities dbContext, string userId)
{
if (dbContext == null)
{
_dbContext = new EnVisageEntities();
// _isContexLocal = true;
}
else
{
_dbContext = dbContext;
}
_userId = userId;
}
protected Mix InitInstance()
{
return new Mix();
}
protected Mix RetrieveReadOnlyById(string key)
{
return MongoDataContext.Mixes.Find(t => t.Key == new ObjectId(key)).FirstOrDefaultAsync().GetAwaiter().GetResult();
}
public void Delete(string mixKey)
{
if (!string.IsNullOrWhiteSpace(mixKey))
{
var objectId = ObjectId.Empty;
if (ObjectId.TryParse(mixKey, out objectId) && !ObjectId.Empty.Equals(objectId))
{
Delete(objectId);
}
}
}
/// <summary>
/// Removes Mix ffrom database by its Id (Mix.Id field used for identification)
/// </summary>
/// <param name="mixId">Mix.Id field value in the database</param>
protected void DeleteByMixId(ObjectId mixId)
{
if (!ObjectId.Empty.Equals(mixId))
{
var result = MongoDataContext.Mixes.DeleteOneAsync(t => t.Id == mixId).GetAwaiter().GetResult();
if (!result.IsAcknowledged)
Logger.Warn("An error occurred while removing Mix record");
}
var allocResult = MongoDataContext.ExpenditureAllocations.DeleteManyAsync(t => t.MixId == mixId).GetAwaiter().GetResult();
if (!allocResult.IsAcknowledged)
Logger.Warn("An error occurred while removing Mix expenditure allocations");
allocResult = MongoDataContext.TeamAllocations.DeleteManyAsync(t => t.MixId == mixId).GetAwaiter().GetResult();
if (!allocResult.IsAcknowledged)
Logger.Warn("An error occurred while removing Mix team allocations");
allocResult = MongoDataContext.ResourceAllocations.DeleteManyAsync(t => t.MixId == mixId).GetAwaiter().GetResult();
if (!allocResult.IsAcknowledged)
Logger.Warn("An error occurred while removing Mix resource allocations");
}
public string GetMixIdByKey(string mixKey, bool exceptionOnNotFound)
{
string mixId = String.Empty;
if (!String.IsNullOrWhiteSpace(mixKey))
try
{
var mix = RetrieveReadOnlyById(mixKey);
if ((mix == null) && exceptionOnNotFound)
{
string message = $"Mix not found (Key = {mixKey})";
Logger.Warn(message);
throw new Exception(message);
}
if (mix != null)
mixId = mix.Id.ToString();
}
catch
{
string message = $"An error occurred while trying to find Mix record: Mix not found (Key = {mixKey})";
Logger.Warn(message);
throw new Exception(message);
}
return mixId;
}
public void Delete(ObjectId mixKey)
{
string mixId = GetMixIdByKey(mixKey.ToString(), true);
DeleteByMixId(new ObjectId(mixId));
}
private Mix Save(Mix dbObj, string oldKey)
{
dbObj.CreatedAtUtc = DateTime.UtcNow;
dbObj.CreatedBy = _userId;
MongoDataContext.Mixes.InsertOneAsync(dbObj).GetAwaiter().GetResult();
ObjectId oKey;
dbObj.Key = ObjectId.TryParse(oldKey, out oKey) ? oKey : dbObj.Id;
var filter = Builders<Mix>.Filter.Eq(t => t.Id, dbObj.Id);
var update = Builders<Mix>.Update.Set(t => t.Key, dbObj.Key);
MongoDataContext.Mixes.UpdateOneAsync(filter, update).GetAwaiter().GetResult();
return dbObj;
}
public Mix SaveWithChildren(MixSaveModel model)
{
string previousMixId = String.Empty;
if (!String.IsNullOrEmpty(model.Id))
{
string mixKey = model.Id;
previousMixId = GetMixIdByKey(mixKey, false);
}
//_logger.Debug("SaveWithChildren method started at " + DateTime.Now);
#region step 0. Load old mix object
var savedMix = RetrieveWithChildren(model.Id);
#endregion
//_logger.Debug("SaveWithChildren method loaded old mix at " + DateTime.Now);
#region step 1. Save Mix object
Mix dbObj = InitInstance();
model.CopyTo(dbObj);
dbObj = Save(dbObj, model.Id);
#endregion
//_logger.Debug("SaveWithChildren method saved new mix at " + DateTime.Now);
#region step 2. Save Allocations
try
{
var expAllocations2Insert = new List<MixExpenditureAllocation>();
var teamAllocations2Insert = new List<MixTeamAllocation>();
var resAllocations2Insert = new List<MixResourceAllocation>();
// get all scenarios in mix
var scenariosInMix = model.Calendar.Projects.Where(x => x.Value?.Scenario != null)
.Select(x => x.Value.Scenario.Id).ToList();
// check which scenarios from mix still exist in the live database
var existingScenarios = DbContext.Scenarios.Where(x => scenariosInMix.Contains(x.Id)).Select(x => x.Id).ToList();
var scenarioIds2Load = new List<Guid>();
foreach (var projectId in model.Calendar.Projects.Keys)
{
var project = model.Calendar.Projects[projectId];
if (project.Scenario == null)
continue;
var currentScenario = project.Scenario;
#region Determining expenditures for saving
var expenditures = currentScenario.Expenditures;
// expenditures can be empty if user didn't edit this project befor saving this mix in current moment
if (expenditures == null || expenditures.Count < 1)
{
// if mix has been saved earlier and it contains expenditures we should save them for the current mix
if (savedMix?.Calendar?.Projects != null)
{
if (savedMix.Calendar.Projects.ContainsKey(projectId))
{
var savedProject = savedMix.Calendar.Projects[projectId];
if (savedProject.Scenario != null)
expenditures = savedProject.Scenario.Expenditures;
}
}
}
if (expenditures == null || expenditures.Count < 1)
{
// TODO: review for ability to change GetFullAllocationInfoByScenario method for retrieving information for a couple of scenarios
if (existingScenarios.Contains(currentScenario.Id))
scenarioIds2Load.Add(currentScenario.Id);
//expenditures = (new ScenarioManager(DbContext)).GetFullAllocationInfoByScenario(currentScenario.Id, _userId);
}
#endregion
}
var scenariosData = scenarioIds2Load.Any() ? (new ScenarioManager(DbContext)).GetFullAllocationInfoByScenario(scenarioIds2Load, _userId) : null;
foreach (var projectId in model.Calendar.Projects.Keys)
{
var project = model.Calendar.Projects[projectId];
if (project.Scenario == null)
continue;
var currentScenario = project.Scenario;
#region Determining expenditures for saving
var expenditures = currentScenario.Expenditures;
// expenditures can be empty if user didn't edit this project befor saving this mix in current moment
if (expenditures == null || expenditures.Count <= 0)
{
// if mix has been saved earlier and it contains expenditures we should to save them for the current mix
if (savedMix?.Calendar?.Projects != null)
{
if (savedMix.Calendar.Projects.ContainsKey(projectId))
{
var savedProject = savedMix.Calendar.Projects[projectId];
if (savedProject.Scenario != null)
expenditures = savedProject.Scenario.Expenditures;
}
}
}
if (expenditures == null || expenditures.Count < 1)
{
// TODO: review for ability to change GetFullAllocationInfoByScenario method for retrieving information for a couple of scenarios
if (existingScenarios.Contains(currentScenario.Id))
expenditures = (scenariosData != null) && scenariosData.ContainsKey(currentScenario.Id) ? scenariosData[currentScenario.Id] : null;
}
#endregion
if (expenditures == null || expenditures.Count < 1)
continue;
foreach (var expCat in expenditures)
{
// insert expenditure allocations
var expenditureAllocation = new MixExpenditureAllocation
{
MixId = dbObj.Id,
ScenarioId = currentScenario.Id,
CreatedAtUtc = DateTime.UtcNow,
CreatedBy = _userId
};
expCat.Value.CopyTo(expenditureAllocation);
expAllocations2Insert.Add(expenditureAllocation);
// insert team allocations
foreach (var expenditureDetailsTeam in expCat.Value.Teams)
{
var teamAllocation = new MixTeamAllocation
{
MixId = dbObj.Id,
ScenarioId = currentScenario.Id,
ExpCatId = expenditureAllocation.ExpCatId,
CreatedAtUtc = DateTime.UtcNow,
CreatedBy = _userId
};
expenditureDetailsTeam.Value.CopyTo(teamAllocation);
teamAllocations2Insert.Add(teamAllocation);
foreach (var teamResource in expenditureDetailsTeam.Value.Resources)
{
var resAllocation = new MixResourceAllocation
{
MixId = dbObj.Id,
ScenarioId = currentScenario.Id,
ExpCatId = expenditureAllocation.ExpCatId,
TeamId = teamAllocation.TeamId,
CreatedAtUtc = DateTime.UtcNow,
CreatedBy = _userId
};
teamResource.Value.CopyTo(resAllocation);
resAllocations2Insert.Add(resAllocation);
}
}
}
}
SaveAllocations(expAllocations2Insert, teamAllocations2Insert, resAllocations2Insert);
// serialize all saved data to a file to compare them with other results
//var path = System.Web.HttpContext.Current.Server.MapPath("/output_new.txt");
//using (var writer = System.IO.File.CreateText(path))
//{
// writer.WriteLine("Mix object:");
// dbObj.DebugObjectProperties(writer);
// writer.WriteLine("Allocations:");
// expAllocations2Insert.DebugObjectProperties(writer);
// teamAllocations2Insert.DebugObjectProperties(writer);
// resAllocations2Insert.DebugObjectProperties(writer);
//}
}
catch (Exception exception)
{
Logger.Fatal(exception);
// if any error occurred while saving new Mix then remove it and all it's children.
DeleteByMixId(dbObj.Id);
// Then throw exception to ask user to try again later
throw;
}
#endregion
//_logger.Debug("SaveWithChildren method saved mix allocations at " + DateTime.Now);
#region step 3. Delete previous Mix with children
try
{
if (!ObjectId.Empty.Equals(previousMixId) && !string.IsNullOrWhiteSpace(previousMixId))
// try to delete previous Mix record
DeleteByMixId(new ObjectId(previousMixId));
}
catch (Exception exception)
{
// if any error occurred while removing old Mix then do nothing except error logging
Logger.Fatal(exception);
}
#endregion
//_logger.Debug("SaveWithChildren method deleted old mix at " + DateTime.Now);
return dbObj;
}
private void SaveAllocations(List<MixExpenditureAllocation> expenditures, List<MixTeamAllocation> teamAllocations, List<MixResourceAllocation> resourceAllocations)
{
var tasks = new List<Task>();
if (expenditures != null && expenditures.Count > 0)
tasks.Add(MongoDataContext.ExpenditureAllocations.InsertManyAsync(expenditures));
if (teamAllocations != null && teamAllocations.Count > 0)
tasks.Add(MongoDataContext.TeamAllocations.InsertManyAsync(teamAllocations));
if (resourceAllocations != null && resourceAllocations.Count > 0)
tasks.Add(MongoDataContext.ResourceAllocations.InsertManyAsync(resourceAllocations));
Task.WaitAll(tasks.ToArray());
}
public Mix Retrieve(string mixKey)
{
if (string.IsNullOrWhiteSpace(mixKey))
return null;
return MongoDataContext.Mixes.Find(t => t.Key == new ObjectId(mixKey)).FirstOrDefaultAsync().GetAwaiter().GetResult();
}
public MixSaveModel RetrieveModel(string mixKey)
{
if (string.IsNullOrWhiteSpace(mixKey))
return null;
var mix = Retrieve(mixKey);
if (null == mix)
return null;
MixSaveModel model = new MixSaveModel
{
Id = mix.Key.ToString(),
Name = mix.Name,
StartDate = mix.StartDate,
EndDate = mix.EndDate
};
model.Filter.Selection.StartDate = mix.StartDate;
model.Filter.Selection.EndDate = mix.EndDate;
model.Filter.Selection.TeamsViews = mix.TeamsViews.Select(x => new MixTeamViewModel
{
Id = x.Id,
TVName = x.Name,
Group = new SelectListGroup { Disabled = false, Name = x.Group },
IsNew = x.IsNew,
CapacityTeamId = x.CapacityTeamId,
CompanyId = x.CompanyId,
CopyPlanned = x.CopyPlanned,
CostCenterId = x.CostCenterId,
Data = x.Data,
UserId = x.UserId
}).ToList();
model.Users = mix.Users;
model.Calendar.AssignFrom(mix);
return model;
}
public MixSaveModel RetrieveWithChildren(string mixKey)
{
var model = RetrieveModel(mixKey);
if (model == null)
return null;
#region Load Allocations
var mixId = GetMixIdByKey(mixKey, true);
var scenarios = model.Calendar.Projects.Where(x => x.Value?.Scenario != null)
.Select(x => x.Value.Scenario.Id)
.ToList();
var allocations = GetAllocations4Scenarios(mixId, scenarios);
if (allocations != null && allocations.Count > 0)
{
foreach (var projectId in model.Calendar.Projects.Keys)
{
var project = model.Calendar.Projects[projectId];
if (project.Scenario == null || !allocations.ContainsKey(project.Scenario.Id))
continue;
project.Scenario.Expenditures = allocations[project.Scenario.Id];
}
}
#endregion
return model;
}
public List<MixProjectModel> GetProjectModel(List<Guid> projects)
{
List<MixProjectModel> result;
using (var scenarioManager = new ScenarioManager(DbContext))
{
result = new List<MixProjectModel>();
var availableProjects = DbContext.Projects.Where(x => projects.Contains(x.Id))
.Include(x => x.Team2Project)
.Include(x => x.ParentProject)
.Include(x => x.Scenarios)
.Include(x => x.Scenarios.Select(s => s.CostSavings1))
.ToList();
string defaultColor = "";
var settings = DbContext.SystemSettings.FirstOrDefault(item => item.Type == (int)SystemSettingType.DefaultProjectColorType);
if (settings != null)
{
defaultColor = settings.Value;
}
//for dependancies source project is parent, target is child
var projectsModel = availableProjects.Select(x => new
{
x.Id,
Name = x.ParentProjectId.HasValue ? x.Name + ": " + x.ParentProject.Name : x.Name,
Color = x.ParentProjectId.HasValue && (x.Color == null || x.Color.Trim() == "") ? ((x.ParentProject.Color == null || x.ParentProject.Color.Trim() == "") ? defaultColor : x.ParentProject.Color) : ((x.Color == null || x.Color.Trim() == "") ? defaultColor : x.Color),
x.Deadline,
Teams = x.Team2Project.Select(ts => ts.TeamId).Distinct().ToList(),
ActiveScenario = x.Scenarios.FirstOrDefault(s => s.StartDate.HasValue && s.EndDate.HasValue && s.Type == (int)ScenarioType.Portfolio && s.Status == (int)ScenarioStatus.Active),
InactiveScenarios = x.Scenarios.Where(s => s.StartDate.HasValue && s.EndDate.HasValue && s.Type == (int)ScenarioType.Portfolio && s.Status == (int)ScenarioStatus.Inactive).ToList(),
HasLink = DbContext.ProjectDependencies.Any(y => (y.SourceProjectId == x.Id || y.TargetProjectId == x.Id) && y.Type == (int)ProjectDependencyDisplayType.Link),
HasDependency = DbContext.ProjectDependencies.Any(y => (y.TargetProjectId == x.Id || y.SourceProjectId == x.Id) && y.Type < (int)ProjectDependencyDisplayType.Link),
AllLinksAndDependencies = GetAllLinksAndDependencies(x.Id)
}).ToList();
var activeScenariosIds = projectsModel.Where(x => x.ActiveScenario != null).Select(x => x.ActiveScenario.Id).ToList();
var inactiveScenariosIds = projectsModel.Where(x => x.InactiveScenarios != null).SelectMany(x => x.InactiveScenarios.Select(s => s.Id).ToList()).ToList();
var scenarios = activeScenariosIds.Union(inactiveScenariosIds).ToList();
var rates = new RateManager(_dbContext).Get4Parents(scenarios, RateModel.RateType.Derived);
var actualScenarios = scenarioManager.GetScenarios4Projects(projects, ScenarioType.Actuals, null).ToDictionary(x => x.ParentId.Value);
var scenariosWithActuals = scenarios.Union(actualScenarios.Select(x => x.Value.Id)).ToList();
var scenariosLMInfo = scenarioManager.GetLaborMaterialsSplit(scenariosWithActuals);
Parallel.ForEach(projectsModel, project =>
//foreach (var project in projectsModel)
{
var originalProject = availableProjects.FirstOrDefault(x => x.Id == project.Id);
var strColor = !String.IsNullOrEmpty(project.Color) ? $"#{project.Color}" : String.Empty;
var color = ColorTranslator.FromHtml(strColor);
var activeScenarioLMInfo = project.ActiveScenario != null && scenariosLMInfo.ContainsKey(project.ActiveScenario.Id)
? scenariosLMInfo[project.ActiveScenario.Id]
: new LaborMaterialsCostInfo();
var actualScenario = actualScenarios.ContainsKey(project.Id) ? actualScenarios[project.Id] : null;
var actualScenarioLMInfo = actualScenario != null && scenariosLMInfo.ContainsKey(actualScenario.Id)
? scenariosLMInfo[actualScenario.Id]
: new LaborMaterialsCostInfo();
var costSavingItems = project.ActiveScenario == null
? new List<CostSaving>()
: project.ActiveScenario.CostSavings1;
var activeMixScenario = GetScenarioCalendarMixModel(project.ActiveScenario, actualScenario, originalProject,
costSavingItems, activeScenarioLMInfo, actualScenarioLMInfo);
var inactiveScenarios = new Dictionary<string, ScenarioCalendarMixModel>();
foreach (var scenario in project.InactiveScenarios.OrderBy(z => z.Name))
{
if (inactiveScenarios.ContainsKey(scenario.Id.ToString()))
continue;
var inactiveScenarioLMInfo = scenariosLMInfo.ContainsKey(scenario.Id)
? scenariosLMInfo[scenario.Id]
: new LaborMaterialsCostInfo();
var inactiveScenarioModel = GetScenarioCalendarMixModel(scenario, actualScenario, originalProject,
scenario.CostSavings1, inactiveScenarioLMInfo, actualScenarioLMInfo);
inactiveScenarios.Add(scenario.Id.ToString(), inactiveScenarioModel);
}
if (activeMixScenario != null && rates.ContainsKey(activeMixScenario.Id))
{
var expRates = rates[activeMixScenario.Id]
.GroupBy(x => x.ExpenditureCategoryId)
.ToDictionary(x => x.Key, g => g.ToList());
activeMixScenario.Rates = scenarioManager.GetRatesModel(expRates);
}
if (inactiveScenarios != null && inactiveScenarios.Count > 0)
{
foreach (var scenario in inactiveScenarios)
{
if (rates.ContainsKey(scenario.Value.Id))
{
var expRates = rates[scenario.Value.Id]
.GroupBy(x => x.ExpenditureCategoryId)
.ToDictionary(x => x.Key, g => g.ToList());
scenario.Value.Rates = scenarioManager.GetRatesModel(expRates);
}
}
}
bool dependencyPinned = false;
string PinnedReason = string.Empty;
if (project.HasDependency || project.HasLink)
{
var notLoaded = project.AllLinksAndDependencies.Where(x => !projectsModel.Select(p => p.Id).ToList().Contains(x))
.ToList();
dependencyPinned = (notLoaded.Count > 0);
if (dependencyPinned)
{
PinnedReason = "One or more dependencies are not loaded in the mix for this project";
}
}
result.Add(new MixProjectModel
{
Id = project.Id,
Name = project.Name,
Color = strColor,
ColorRGB = !color.IsEmpty ? $"{color.R}, {color.G}, {color.B}" : String.Empty,
Deadline = project.Deadline.HasValue ? Utils.ConvertToUnixDate(project.Deadline.Value) : (long?) null,
Teams = project.Teams,
Scenario = activeMixScenario,
InactiveScenarios = inactiveScenarios,
HasDependency = project.HasDependency,
HasLink = project.HasLink,
AllLinksAndDependencies = project.AllLinksAndDependencies,
DependencyPinned = dependencyPinned,
DependencyToolTip = PinnedReason
});
});
}
return result;
}
private List<Guid> GetAllLinksAndDependencies(List<Guid> Ids)
{
var sources = DbContext.ProjectDependencies.Where(x => Ids.Contains(x.TargetProjectId)).Select(x => x.SourceProjectId).ToList();
var targets = DbContext.ProjectDependencies.Where(x => Ids.Contains(x.SourceProjectId)).Select(x => x.TargetProjectId).ToList();
sources.AddRange(targets);
var ls = Ids.Count;
Ids.AddRange(sources);
if (ls == Ids.Distinct().ToList().Count)
return Ids.Distinct().ToList();
return GetAllLinksAndDependencies(Ids.Distinct().ToList());
}
private List<Guid> GetAllLinksAndDependencies(Guid id)
{
var idList = new List<Guid> {id};
var list = GetAllLinksAndDependencies(idList);
list.Remove(id);
return list;
}
public List<MixScenarioTimestampModel> ActivateMix(MixSaveModel mix, string mixKey)
{
if (mix.Calendar == null)
return null;
var result = new List<MixScenarioTimestampModel>();
if (mix.Calendar.Teams != null && mix.Calendar.Teams.Any(x => x.IsNew))
{
ActivateTeams(mix.Calendar.Teams);
// we should to save all new teams in one separate transaction because every scenario will be saved in the own transaction
// and it need actual information abuout teams
DbContext.SaveChanges();
}
if (mix.Calendar.Projects != null && mix.Calendar.Projects.Count > 0)
{
// Get list of projects for activation
List<Guid> projectsToActivate = new List<Guid>();
if (mix.Calendar.ManagedProjects != null && mix.Calendar.ManagedProjects.Count > 0)
// All managed projects
projectsToActivate.AddRange(mix.Calendar.ManagedProjects);
if (mix.Calendar.QueuedProjects != null && mix.Calendar.QueuedProjects.Count > 0)
{
// Include queued projects, that have newly created scenarios in the Mix
projectsToActivate.AddRange(mix.Calendar.QueuedProjects.Where(x =>
mix.Calendar.Projects.ContainsKey(x.ToString()) &&
mix.Calendar.Projects[x.ToString()].Scenario != null &&
mix.Calendar.Projects[x.ToString()].Scenario.IsNew));
}
var savedTeamsInProjects = DbContext.Team2Project.Where(x => projectsToActivate.Contains(x.ProjectId))
.Select(x => new
{
x.ProjectId,
x.TeamId
})
.ToList()
.GroupBy(x => x.ProjectId)
.ToDictionary(x => x.Key, g => g.Select(s => s.TeamId).ToList());
foreach (var projectId in projectsToActivate)
{
if (!mix.Calendar.Projects.ContainsKey(projectId.ToString()))
continue;
var project = mix.Calendar.Projects[projectId.ToString()];
if (project?.Scenario == null)
continue;
if (project.Teams == null)
project.Teams = new List<Guid>();
// we should preserve all teams for project and all its scenarios, we can remove only team allocations
if (savedTeamsInProjects.ContainsKey(project.Id))
{
var requiredTeams = savedTeamsInProjects[projectId].Where(x => project.Teams.All(s => s != x)).ToList();
project.Teams.AddRange(requiredTeams);
}
// new scenario should be created with name equals to mix name
project.Scenario.Name = mix.Name;
var scenarioId = ActivateScenario(project.Scenario, project.Teams, mixKey, true);
DbContext.SaveChanges();
result.Add(new MixScenarioTimestampModel
{
ProjectId = projectId,
ScenarioId = scenarioId
});
}
}
return result;
}
public void ActivateTeams(List<MixTeamModel> mixTeams)
{
if (mixTeams == null || mixTeams.Count <= 0 || !mixTeams.Any(x => x.IsNew))
return;
var teamManager = (new TeamManager(DbContext));
var rateManager = (new RateManager(DbContext));
var newTeams = mixTeams.FindAll(x => x.IsNew);
var expCatsInTeams = newTeams.Where(x => x.PlannedCapacity != null).SelectMany(x => x.PlannedCapacity.Select(t => t.ExpCatId)).ToList();
var expCatsInTeamsGuid = new List<Guid>();
foreach (var expCatString in expCatsInTeams)
{
Guid expCatId;
if (string.IsNullOrWhiteSpace(expCatString) || !Guid.TryParse(expCatString, out expCatId) || expCatId == Guid.Empty)
continue;
expCatsInTeamsGuid.Add(expCatId);
}
var rates = rateManager.GetRates(expCatsInTeamsGuid, RateModel.RateType.Global);
var currentUser = !string.IsNullOrWhiteSpace(_userId) ? new Guid(_userId) : Guid.Empty; // SA. ENV-1083
foreach (var team in newTeams)
{
// SA. ENV-1083. Automatically add current user to team contributors.
if (!currentUser.Equals(Guid.Empty))
{
if (team.UserId == null)
team.UserId = new Guid[1] { currentUser };
else
if (!team.UserId.Contains(currentUser))
{
List<Guid> tmpUsersList = team.UserId.ToList();
tmpUsersList.Add(currentUser);
team.UserId = tmpUsersList.ToArray();
}
}
var savedTeam = teamManager.Save((TeamModel)team, true);
if (savedTeam?.PlannedCapacityScenarioId == null)
continue;
if (team.PlannedCapacity == null || team.PlannedCapacity.Count <= 0)
continue;
foreach (var capacity in team.PlannedCapacity)
{
if (capacity.Values == null || capacity.Values.Count <= 0)
continue;
Guid expCatId;
if (string.IsNullOrWhiteSpace(capacity.ExpCatId) || !Guid.TryParse(capacity.ExpCatId, out expCatId) || expCatId == Guid.Empty)
continue;
foreach (var value in capacity.Values)
{
long weekEnding;
if (!long.TryParse(value.Key, out weekEnding) || weekEnding <= 0)
continue;
DbContext.ScenarioDetail.Add(new ScenarioDetail
{
Id = Guid.NewGuid(),
ParentID = savedTeam.PlannedCapacityScenarioId.Value,
ExpenditureCategoryId = expCatId,
WeekEndingDate = Utils.ConvertFromUnixDate(weekEnding),
Quantity = value.Value,
Cost = value.Value * rateManager.GetRateValue(rates, expCatId, Utils.ConvertFromUnixDate(weekEnding))
});
}
}
}
}
public Guid ActivateScenario(ScenarioCalendarMixModel scenario, List<Guid> teams, string mixKey, bool isActive)
{
if (scenario == null)
throw new ArgumentNullException(nameof(scenario));
if ((scenario.Expenditures == null || scenario.Expenditures.Count <= 0) && scenario.Id != Guid.Empty)
scenario.Expenditures = GetFullAllocationInfoByScenario(mixKey, scenario.Id);
var scenarioManager = new ScenarioManager(DbContext);
var scenarioSaveModel = new ScenarioDetailsSnapshotSaveModel(scenario);
if (scenarioSaveModel.Calendar == null)
return Guid.Empty;
scenarioSaveModel.Scenario.IsActiveScenario = isActive;
if (teams != null && teams.Count > 0)
{
scenarioSaveModel.TeamsInScenario = teams.Select(x => new TeamInScenarioModel
{
TeamId = x
}).ToList();
}
var scenarioId = scenarioManager.Save(scenarioSaveModel, _userId);
if (scenarioId == Guid.Empty)
throw new InvalidOperationException("Save scenario method returned empty");
// save local rates for this scenario
if (scenario.Rates != null && scenario.Rates.Count > 0)
{
var rates = scenarioManager.GetRatesFromModel(scenario.Rates);
rates.ForEach(rate =>
{
rate.Id = Guid.NewGuid();
rate.ParentId = scenarioId;
rate.Type = (short) RateModel.RateType.Derived;
});
DbContext.Rates.AddRange(rates);
}
return scenarioId;
}
public Dictionary<string, ExpenditureDetail> GetFullAllocationInfoByScenario(string mixKey, Guid scenarioId)
{
var allocations = new Dictionary<string, ExpenditureDetail>();
if (scenarioId == Guid.Empty)
return allocations;
if (!string.IsNullOrWhiteSpace(mixKey))
{
var mixId = GetMixIdByKey(mixKey, true);
allocations = GetAllocations4Scenario(mixId, scenarioId);
}
if (allocations == null || allocations.Count <= 0)
allocations = (new ScenarioManager(DbContext)).GetFullAllocationInfoByScenario(scenarioId, _userId);
return allocations;
}
/// <summary>
/// Gets people resource allocations for the specified Mix.Id and scenarios.
/// </summary>
/// <param name="mixId">An unique identifier of the Mix.</param>
/// <param name="scenarios">A list of scenario Ids.</param>
/// <returns>Returns a list of resource allocations.</returns>
public List<MixResourceAllocation> GetResourceAllocations(string mixId, List<Guid> scenarios)
{
return GetResourceAllocationsAsync(mixId, scenarios).GetAwaiter().GetResult();
}
/// <summary>
/// Runs a task for loading people resource allocations for the specified Mix.Id and scenarios.
/// </summary>
/// <param name="mixId">An unique identifier of the Mix.</param>
/// <param name="scenarios">A list of scenario Ids.</param>
/// <returns>Returns an asynchrounous task.</returns>
public Task<List<MixResourceAllocation>> GetResourceAllocationsAsync(string mixId, List<Guid> scenarios)
{
return MongoDataContext.ResourceAllocations.Find(t => t.MixId == new ObjectId(mixId) && scenarios.Contains(t.ScenarioId)).ToListAsync();
}
public Dictionary<string, string> GetResourceNames(string mixId, List<Guid> resources)
{
var resIds = resources.Select(t => t.ToString());
return MongoDataContext.ResourceAllocations.Find(t => t.MixId == new ObjectId(mixId) && resIds.Contains(t.ResourceId)).ToListAsync().GetAwaiter().GetResult()
.Select(t => new
{
Id = t.ResourceId,
t.Name
}).Distinct().ToDictionary(key => key.Id, el => el.Name);
}
public List<MixModelBase> GetMixesByUser()
{
Guid userId;
if (!string.IsNullOrWhiteSpace(_userId) && Guid.TryParse(_userId, out userId) && userId != Guid.Empty)
{
return MongoDataContext.GetCollection<MixBase>(MongoDataContext.COLL_NAME_MIXES)
.Find(x => x.Users.Contains(userId))
.SortBy(o => o.Name)
.ToListAsync().GetAwaiter().GetResult()
.Select(t => new MixModelBase
{
Id = t.Key.ToString(),
Name = t.Name,
StartDate = t.StartDate,
EndDate = t.EndDate
}).ToList();
}
return new List<MixModelBase>();
}
public List<SelectListItem> GetMixesAvailable4User()
{
var foundMixes = GetMixesByUser();
if (foundMixes != null)
{
return foundMixes.Select(x => new SelectListItem
{
Value = x.Id.ToString(),
Text = x.Name
}).ToList();
}
return new List<SelectListItem>();
}
public Dictionary<Guid, PeopleResourceAllocationsInfoModel> GetResourceAllocationsInfo(List<Guid> resources)
{
if (resources == null)
throw new ArgumentNullException(nameof(resources));
if (resources.Count < 1)
return new Dictionary<Guid, PeopleResourceAllocationsInfoModel>();
var resourceIdsAsText = resources.Select(x => x.ToString()).ToList();
var foundResourceAllocations = LoadResourceAllocationsRecordCount(resourceIdsAsText);
var result = resourceIdsAsText.AsParallel().ToDictionary(k => new Guid(k), v =>
new PeopleResourceAllocationsInfoModel
{
HasAllocations = foundResourceAllocations.ContainsKey(v) && foundResourceAllocations[v] > 0
});
return result;
}
/// <summary>
/// Loads number of MixResourceAllocation records from database for each of the specified resources.
/// </summary>
/// <param name="resourceIds">A list of resource Ids.</param>
/// <returns>Dictionary with Key=ResourceId, Value=Count of resources.</returns>
private Dictionary<string, long> LoadResourceAllocationsRecordCount(IEnumerable<string> resourceIds)
{
if (resourceIds == null || !resourceIds.Any())
return null;
var filter = Builders<MixResourceAllocation>.Filter.In(x => x.ResourceId, resourceIds);
var query = MongoDataContext.ResourceAllocations.Aggregate()
.Match(filter)
.Group((new BsonDocument { { "_id", "$ResourceId" }, { "count", new BsonDocument("$sum", 1) } }));
var counts = query.ToListAsync().GetAwaiter().GetResult()
.ToDictionary(t => t.GetValue("_id").ToString(), el => el.GetValue("count").ToInt64());
return counts;
}
private Dictionary<string, ExpenditureDetail> GetAllocations4Scenario(string mixId, Guid scenarioId)
{
var allocations = GetAllocations4Scenarios(mixId, new List<Guid> { scenarioId });
if (allocations == null || !allocations.ContainsKey(scenarioId))
return new Dictionary<string, ExpenditureDetail>();
return allocations[scenarioId];
}
private Dictionary<Guid, Dictionary<string, ExpenditureDetail>> GetAllocations4Scenarios(string mixId, List<Guid> scenarios)
{
var allocations = new Dictionary<Guid, Dictionary<string, ExpenditureDetail>>();
if (string.IsNullOrWhiteSpace(mixId) || scenarios == null || scenarios.Count <= 0)
return allocations;
var mid = new ObjectId(mixId);
var tasks = new List<Task>();
// prepare async tasks and run them
var expTask = MongoDataContext.ExpenditureAllocations
.Find(t => t.MixId == mid && scenarios.Contains(t.ScenarioId)).ToListAsync();
var teamTask = MongoDataContext.TeamAllocations
.Find(t => t.MixId == mid && scenarios.Contains(t.ScenarioId)).ToListAsync();
var resTask = GetResourceAllocationsAsync(mixId, scenarios);
// wait for all data loaded
Task.WaitAll(tasks.ToArray());
// group loaded data
var expenditureAllocations = expTask.Result.GroupBy(x => x.ScenarioId)
.ToDictionary(x => x.Key, g => g.ToList());
var teamAllocations = teamTask.Result.GroupBy(x => x.ScenarioId)
.ToDictionary(x => x.Key, g => g.ToList());
var resourceAllocations = resTask.Result.GroupBy(x => x.ScenarioId)
.ToDictionary(x => x.Key, g => g.ToList());
foreach (var scenarioId in scenarios)
{
if (!expenditureAllocations.ContainsKey(scenarioId))
continue;
var expenditures = expenditureAllocations[scenarioId].ToDictionary(x => x.ExpCatId.ToString(), g => (ExpenditureDetail)g);
allocations.Add(scenarioId, expenditures);
if (!teamAllocations.ContainsKey(scenarioId))
continue;
foreach (var expCatId in allocations[scenarioId].Keys)
{
var ec = allocations[scenarioId][expCatId];
ec.Teams = teamAllocations[scenarioId].Where(x => x.ExpCatId.ToString() == expCatId)
.ToDictionary(x => x.TeamId, g => (ExpenditureDetailsTeam)g);
if (!resourceAllocations.ContainsKey(scenarioId))
continue;
foreach (var teamId in ec.Teams.Keys)
{
ec.Teams[teamId].Resources = resourceAllocations[scenarioId].Where(x => x.ExpCatId.ToString() == expCatId && x.TeamId == teamId)
.ToDictionary(x => x.ResourceId, g => (TeamResource)g);
}
}
}
return allocations;
}
private ScenarioCalendarMixModel GetScenarioCalendarMixModel(Scenario scenario, Scenario actualScenario, Project project, IEnumerable<CostSaving> costSavings, LaborMaterialsCostInfo scenarioLMInfo, LaborMaterialsCostInfo actualLMInfo)
{
if (scenario == null)
return null;
var model = new ScenarioCalendarMixModel
{
Id = scenario.Id,
StartDate = scenario.StartDate.HasValue ? Utils.ConvertToUnixDate(scenario.StartDate.Value) : 0,
EndDate = scenario.EndDate.HasValue ? Utils.ConvertToUnixDate(scenario.EndDate.Value) : 0,
TemplateId = scenario.TemplateId ?? Guid.Empty,
ParentId = scenario.ParentId,
GrowthScenario = scenario.GrowthScenario,
Type = (ScenarioType)scenario.Type,
Duration = scenario.Duration ?? 0,
Name = scenario.Name,
IsBottomUp = scenario.IsBottomUp,
VersionInfo = new ItemVersionInfo // SA. ENV-1085
{
SourceVersion = scenario.GetCurrentVersion()
}
};
using (var scenarioManager = new ScenarioManager(DbContext))
{
var finInfo = scenarioManager.GetScenarioFinInfoModel(scenario, actualScenario, project,
costSavings, scenarioLMInfo, actualLMInfo);
if (finInfo != null)
{
model.FinInfo = new ScenarioCalendarMixFinInfoModel
{
IsRevenueGenerating = finInfo.IsRevenueGenerating,
ProjectedRevenue = finInfo.ProjectedRevenue ?? 0,
RevenueAfterCost = finInfo.RevenueAfterCost,
ActualRevenueAfterCost = finInfo.ActualRevenueAfterCost,
GrossMargin = finInfo.GrossMargin,
LMMargin = finInfo.LMMargin,
UseLMMargin = finInfo.UseLMMargin,
CalculatedGrossMargin = finInfo.CalculatedGrossMargin,
CalculatedGrossMarginLM = finInfo.CalculatedGrossMarginLM,
CalculatedGrossMarginActuals = finInfo.CalculatedGrossMarginActuals,
CalculatedGrossMarginLMActuals = finInfo.CalculatedGrossMarginLMActuals,
TDDirectCosts = finInfo.TDDirectCosts,
BUDirectCosts = finInfo.BUDirectCosts,
ActualLabor = finInfo.ActualLabor,
LaborMaterialsSplit = finInfo.LaborMaterialsSplit,
ActualLaborMaterialsSplit = finInfo.ActualLaborMaterialsSplit,
ActualMaterials = finInfo.ActualMaterials,
ActualsTotal = finInfo.ActualsTotal,
EFXSplit = finInfo.EFXSplit,
LaborSplitPercentage = finInfo.LaborSplitPercentage,
CostSaving = finInfo.CostSaving == null
? null
: new CostSavingSnapshotModel
{
CostSavings = finInfo.CostSaving.CostSavings,
CostSavingStartDate = finInfo.CostSaving.CostSavingStartDate.HasValue
? Utils.ConvertToUnixDate(finInfo.CostSaving.CostSavingStartDate.Value)
: (long?) null,
CostSavingEndDate = finInfo.CostSaving.CostSavingEndDate.HasValue
? Utils.ConvertToUnixDate(finInfo.CostSaving.CostSavingEndDate.Value)
: (long?) null,
CostSavingType = finInfo.CostSaving.CostSavingType,
CostSavingDescription = finInfo.CostSaving.CostSavingDescription,
CostSavingItems = scenarioManager.GetScenarioCostSavingModelItems(scenario.CostSavings1)
}
};
}
}
return model;
}
}
}