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 { 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); } } } /// /// Removes Mix ffrom database by its Id (Mix.Id field used for identification) /// /// Mix.Id field value in the database 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.Filter.Eq(t => t.Id, dbObj.Id); var update = Builders.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(); var teamAllocations2Insert = new List(); var resAllocations2Insert = new List(); // 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(); 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 expenditures, List teamAllocations, List resourceAllocations) { var tasks = new List(); 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 GetProjectModel(List projects) { List result; using (var scenarioManager = new ScenarioManager(DbContext)) { result = new List(); 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() : project.ActiveScenario.CostSavings1; var activeMixScenario = GetScenarioCalendarMixModel(project.ActiveScenario, actualScenario, originalProject, costSavingItems, activeScenarioLMInfo, actualScenarioLMInfo); var inactiveScenarios = new Dictionary(); 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 GetAllLinksAndDependencies(List 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 GetAllLinksAndDependencies(Guid id) { var idList = new List {id}; var list = GetAllLinksAndDependencies(idList); list.Remove(id); return list; } public List ActivateMix(MixSaveModel mix, string mixKey) { if (mix.Calendar == null) return null; var result = new List(); 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 projectsToActivate = new List(); 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(); // 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 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(); 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 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 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 GetFullAllocationInfoByScenario(string mixKey, Guid scenarioId) { var allocations = new Dictionary(); 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; } /// /// Gets people resource allocations for the specified Mix.Id and scenarios. /// /// An unique identifier of the Mix. /// A list of scenario Ids. /// Returns a list of resource allocations. public List GetResourceAllocations(string mixId, List scenarios) { return GetResourceAllocationsAsync(mixId, scenarios).GetAwaiter().GetResult(); } /// /// Runs a task for loading people resource allocations for the specified Mix.Id and scenarios. /// /// An unique identifier of the Mix. /// A list of scenario Ids. /// Returns an asynchrounous task. public Task> GetResourceAllocationsAsync(string mixId, List scenarios) { return MongoDataContext.ResourceAllocations.Find(t => t.MixId == new ObjectId(mixId) && scenarios.Contains(t.ScenarioId)).ToListAsync(); } public Dictionary GetResourceNames(string mixId, List 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 GetMixesByUser() { Guid userId; if (!string.IsNullOrWhiteSpace(_userId) && Guid.TryParse(_userId, out userId) && userId != Guid.Empty) { return MongoDataContext.GetCollection(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(); } public List GetMixesAvailable4User() { var foundMixes = GetMixesByUser(); if (foundMixes != null) { return foundMixes.Select(x => new SelectListItem { Value = x.Id.ToString(), Text = x.Name }).ToList(); } return new List(); } public Dictionary GetResourceAllocationsInfo(List resources) { if (resources == null) throw new ArgumentNullException(nameof(resources)); if (resources.Count < 1) return new Dictionary(); 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; } /// /// Loads number of MixResourceAllocation records from database for each of the specified resources. /// /// A list of resource Ids. /// Dictionary with Key=ResourceId, Value=Count of resources. private Dictionary LoadResourceAllocationsRecordCount(IEnumerable resourceIds) { if (resourceIds == null || !resourceIds.Any()) return null; var filter = Builders.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 GetAllocations4Scenario(string mixId, Guid scenarioId) { var allocations = GetAllocations4Scenarios(mixId, new List { scenarioId }); if (allocations == null || !allocations.ContainsKey(scenarioId)) return new Dictionary(); return allocations[scenarioId]; } private Dictionary> GetAllocations4Scenarios(string mixId, List scenarios) { var allocations = new Dictionary>(); if (string.IsNullOrWhiteSpace(mixId) || scenarios == null || scenarios.Count <= 0) return allocations; var mid = new ObjectId(mixId); var tasks = new List(); // 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 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; } } }