using EnVisage.App_Start; using EnVisage.Code; using EnVisage.Code.BLL; using EnVisage.Code.Cache; using EnVisage.Models; using Iniphi.Calculations; using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Net; using System.Web.Mvc; using Resources; using StackExchange.Profiling; namespace EnVisage.Controllers { [Authorize] public class MixController : BaseController { #region Private Members private ScenarioUIManager ScenarioUIManager { get; } #endregion #region Constructors public MixController(ScenarioUIManager scenarioUIManager) { ScenarioUIManager = scenarioUIManager; } #endregion #region Actions [AreaSecurity(area = Areas.Mixes, level = AccessLevel.Read)] public ActionResult Index(string id) { return View("Index", null, id); } [HttpPost] [ValidateJsonAntiForgeryToken] [AreaSecurity(area = Areas.Mixes, level = AccessLevel.Read)] public ActionResult GetPageFilter() { try { var userId = SecurityManager.GetUserPrincipal(); var model = new MixSaveModel { Users = new List { userId } }; LoadMixFilterValues(model.Filter.Variants, userId.ToString()); return Json(model); } catch (Exception exception) { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [HttpPost] [ValidateJsonAntiForgeryToken] [AreaSecurity(area = Areas.Mixes, level = AccessLevel.Read)] public ActionResult LoadMix(string model) { try { #if DEBUG var watch1 = new System.Diagnostics.Stopwatch(); watch1.Start(); #endif var serverModel = LoadMixSaveModel(model); #if DEBUG watch1.Stop(); #endif // Fill client-side filter values LoadClientSideFilterValues(serverModel); UpdateFilterSelection(serverModel, null); return BigJson(serverModel); } catch (Exception exception) { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } #endregion [HttpPost] [ValidateJsonAntiForgeryToken] [AreaSecurity(area = Areas.Mixes, level = AccessLevel.Read)] public ActionResult DeleteMix(string model) { try { if (!string.IsNullOrWhiteSpace(model)) { var mixManager = ResolveMixManager(DbProvider.MongoDb); mixManager.Delete(model); return new HttpStatusCodeResult(HttpStatusCode.OK); } } catch (Exception exception) // handle any unexpected error { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [HttpPost] [ValidateJsonAntiForgeryToken] [AreaSecurity(area = Areas.Mixes, level = AccessLevel.Write)] public ActionResult SaveMix(MixSaveModel model) { if (!string.IsNullOrWhiteSpace(model.Id) && ContentLocker.IsLock("Mix", model.Id, User.Identity.GetUserName())) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); model.TrimStringProperties(); if (ModelState.IsValid) { try { var mixManager = ResolveMixManager(DbProvider.MongoDb); model.CreatedBy = User.Identity.GetID(); var mix = mixManager.SaveWithChildren(model); DbContext.SaveChanges(); return Json(new { Id = mix.Key.ToString() }); } catch (Exception exception) // handle any unexpected error { LogException(exception); } } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [HttpPost] [ValidateJsonAntiForgeryToken] [AreaSecurity(area = Areas.Mixes, level = AccessLevel.Write)] public ActionResult ActivateMix(string model) { if (string.IsNullOrWhiteSpace(model) || ContentLocker.IsLock("Mix", model, User.Identity.GetUserName())) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); try { var mixManager = ResolveMixManager(DbProvider.MongoDb); var mix = mixManager.RetrieveWithChildren(model); if (mix == null) throw new InvalidOperationException($"Mix with Key [{model}] can not be activated because it does not exists."); if (mix.Calendar != null) { #if DEBUG var watch1 = new System.Diagnostics.Stopwatch(); watch1.Start(); #endif var createdScenariosInfo = mixManager.ActivateMix(mix, model); #if DEBUG watch1.Stop(); #endif // Get scenario versions (timestamps) in the mix model var recentlyActivatedProjects = createdScenariosInfo.Select(x => x.ProjectId.ToString()).ToList(); var scenariosVersionInfo = GetScenariosTimestampsInternal(recentlyActivatedProjects); MixModelsMergeManager.SetScenarioVersions(mix, scenariosVersionInfo); // Save mix to Mongo mixManager.SaveWithChildren(mix); DbContext.SaveChanges(); } return new HttpStatusCodeResult(HttpStatusCode.OK); } catch (Exception exception) // handle any unexpected error { LogException(exception); } //actionFinished = DateTime.UtcNow; //totalActionTimeSeconds = Math.Round((actionFinished - actionStarted).TotalSeconds, 1).ToString(); //LogDebugMessage(String.Format("DEBUG: Mix Activation action completed with ERROR (mixId: {0}, Complete time (sec): {1})", model, totalActionTimeSeconds)); return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [HttpPost] [ValidateJsonAntiForgeryToken] [AreaSecurity(area = Areas.Mixes, level = AccessLevel.Read)] public ActionResult HasResourceAssignments(MixCalendarModel model) { try { var result = Json(new { allowResourceRace = HasResourceAssignmentsBool(model), CanDoTeamRace = HasSupperEcUnAssigned(model), canDoRace = ValidateForRace(model) }, JsonRequestBehavior.AllowGet); return result; } catch (Exception exception) { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [HttpPost] [ValidateJsonAntiForgeryToken] [AreaSecurity(area = Areas.Mixes, level = AccessLevel.Read)] public ActionResult LoadCalendar(MixSaveModel model) { var userId = User.Identity.GetID(); MixSaveModel clientModel = model; if (clientModel?.Filter?.Selection == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); if (clientModel.Filter.Selection.StartDate < 1 || clientModel.Filter.Selection.EndDate < 1) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); try { var startDate = Utils.ConvertFromUnixDate(clientModel.Filter.Selection.StartDate); var endDate = Utils.ConvertFromUnixDate(clientModel.Filter.Selection.EndDate); var endDateCorrected = endDate.AddDays(6); var teams = clientModel.Filter.Selection.TeamsViews.Where(t => !t.IsNew && t.Group.Name == "Teams") .Select(t => new Guid(t.Id)).Distinct().ToList(); var views = clientModel.Filter.Selection.TeamsViews.Where(t => t.Group.Name == "Views") .Select(t => new Guid(t.Id)).Distinct().ToList(); var newTeams = clientModel.Filter.Selection.TeamsViews.Where(x => x.IsNew).ToList(); MixSaveModel serverModel = new MixSaveModel(); GetCalendar(serverModel.Calendar, startDate, endDateCorrected, teams, views, newTeams, true); // Construct need data from expenditure's data in model. Do it only for client model, because // server model already got this data inside GetCalendar method BuildNeedAllocations(clientModel.Calendar); // Update filters LoadMixFilterValues(serverModel.Filter.Variants, userId); serverModel.Users = (clientModel.Users != null) && (clientModel.Users.Count > 0) ? clientModel.Users : new List() { new Guid(userId) }; MixModelsMergeManager mergeMngr = new MixModelsMergeManager(DbContext, userId); mergeMngr.MergeFilters(serverModel.Filter, clientModel.Filter); mergeMngr.MergeCalendars(serverModel, clientModel, startDate, endDate); // Fill client-side filter values MixFilterSelectionModel clientSideFiltersSelection = model.Filter?.Selection; LoadClientSideFilterValues(serverModel); UpdateFilterSelection(serverModel, clientSideFiltersSelection); var mix = new Code.DAL.Mongo.Mix(); serverModel.CopyTo(mix); var mixManager = ResolveMixManager(DbProvider.MongoDb); var fullMongoMix = serverModel; if (!string.IsNullOrWhiteSpace(serverModel.Id)) fullMongoMix = mixManager.RetrieveWithChildren(serverModel.Id); if (fullMongoMix == null) fullMongoMix = serverModel; serverModel.CanRunResourceRace = HasResourceAssignmentsBool(fullMongoMix.Calendar); serverModel.CanRunTeamRace = HasSupperEcUnAssigned(fullMongoMix.Calendar); serverModel.canDoRace = ValidateForRace(serverModel); return BigJson(serverModel); } catch (Exception exception) // handle any unexpected error { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [HttpPost] [ValidateJsonAntiForgeryToken] [AreaSecurity(area = Areas.Mixes, level = AccessLevel.Read)] public ActionResult DoRace(MixSaveModel model) { MixSaveModel clientModel = model; if (clientModel?.Filter?.Selection == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); if (clientModel.Filter.Selection.StartDate < 1 || clientModel.Filter.Selection.EndDate < 1) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); try { var startDate = Utils.ConvertFromUnixDate(clientModel.Filter.Selection.StartDate); var endDate = Utils.ConvertFromUnixDate(clientModel.Filter.Selection.EndDate); var endDateCorrected = FiscalCalendarManager.GetFiscalCalendarWeekForDate(endDate, DbContext).EndDate; var teams = clientModel.Filter.Selection.TeamsViews.Where(t => !t.IsNew && t.Group.Name == "Teams") .Select(t => new Guid(t.Id)).Distinct().ToList(); var views = clientModel.Filter.Selection.TeamsViews.Where(t => t.Group.Name == "Views") .Select(t => new Guid(t.Id)).Distinct().ToList(); var newTeams = clientModel.Filter.Selection.TeamsViews.Where(x => x.IsNew).ToList(); List weekendings = FiscalCalendarManager.GetWeekendingsByRange(startDate, endDate, DbContext); MixSaveModel serverModel = new MixSaveModel(); GetCalendar(serverModel.Calendar, startDate, endDateCorrected, teams, views, newTeams, false); MixModelsMergeManager mergeMngr = new MixModelsMergeManager(DbContext, User.Identity.GetID()); mergeMngr.MergeFilters(serverModel.Filter, clientModel.Filter); mergeMngr.MergeCalendars(serverModel, clientModel, startDate, endDate); RaceMatrix rmx = new RaceMatrix(0, GetRaceModule(), clientModel.TeamLevelRace, serverModel, 1); if (rmx.Projects.Length == 0) { throw new Exception("Race was not able to load one or more projects in the mix and can not be run due to errors"); } int nbrProjs = rmx.Projects.Count(x => x.isPinned == false); int cycles = weekendings.Count; var raceIterations = GetPermutations(Enumerable.Range(1, cycles), nbrProjs).ToList(); int onIteration = 1; foreach (var cycle in raceIterations) { rmx.BuildProjectIterationScores(onIteration, cycle.ToArray()); onIteration++; } var raceScores = GetRaceModule().GetBestMix(); var mixData = rmx.GetMixDataForIteration(raceIterations[raceScores.MixIteration - 1].ToArray()); var raceResults = new { mixData, raceScore = Math.Round(raceScores.TotalScore), raceIteration = raceScores.MixIteration, raceScore1 = raceScores.Score1, // raceScore2 = raceScores.Score2, }; JsonResult rs = Json(raceResults, JsonRequestBehavior.AllowGet); return rs; } catch (Exception exception) // handle any unexpected error { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [ValidateJsonAntiForgeryToken] [AreaSecurity(area = Areas.Mixes, level = AccessLevel.Read)] public ActionResult DoRace2(MixSaveModel model) { if (model?.Filter?.Selection == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); if (model.Filter.Selection.StartDate < 1 || model.Filter.Selection.EndDate < 1) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); var mixManager = ResolveMixManager(DbProvider.MongoDb); var fullMongoMix = model; if (!string.IsNullOrWhiteSpace(model.Id)) fullMongoMix = mixManager.RetrieveWithChildren(model.Id); if (fullMongoMix == null) fullMongoMix = model; try { RaceMatrix rmx = new RaceMatrix(0, GetRaceModule(), model.TeamLevelRace, fullMongoMix, 2); if (rmx.Projects.Length == 0) { throw new Exception("Race was not able to load one or more projects in the mix and can not be run due to errors"); } for (int pIdx = 0; pIdx < rmx.Projects.Length; pIdx++) { rmx.BuildProjectIterationRace2Scores(pIdx); rmx.resetRaceModule(); } var mixData = rmx.GetMixDataForRace2(); var raceResults = new { mixData }; JsonResult rs = Json(raceResults, JsonRequestBehavior.AllowGet); return rs; } catch (Exception exception) // handle any unexpected error { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } #region Private Methods private void GetCalendar(MixCalendarModel model, DateTime startDate, DateTime endDate, List teamsIds, List viewsIds, List newTeams, bool retriveExpendituresNeedValues) { using (var eManager = new ExpenditureCategoryManager(DbContext)) using (var fiscalCalendarManager = new FiscalCalendarManager(DbContext)) using (var teamManager = new TeamManager(DbContext)) using (var scenarioManager = new ScenarioManager(DbContext)) { var mixManager = ResolveMixManager(DbProvider.MongoDb); var fiscalCalendar = fiscalCalendarManager.GetFiscalCalendar(FiscalCalendarModel.FiscalYearType.Week, null, null, true, null, false); var weekEndings = fiscalCalendar.Where(x => x.EndDate >= startDate && x.EndDate <= endDate) .Select(x => x.EndDate).OrderBy(x => x).ToList(); model.Teams = teamManager.GetTeamsInfoByUser(User.Identity.GetID(), teamsIds, viewsIds, fiscalCalendar) .Select(x => new MixTeamModel(x)).ToList(); model.FiscalCalendarWeekEndings = fiscalCalendar.Select(x => Utils.ConvertToUnixDate(x.EndDate)).OrderBy(x => x).ToList(); model.WeekEndings = fiscalCalendarManager.CastWeekEndingsToTree(weekEndings); var allTeams = model.Teams.Select(x => Guid.Parse(x.Id)).ToList(); var allTeamsProjects = teamManager.GetProjectsIds(allTeams); #if DEBUG var watch1 = new System.Diagnostics.Stopwatch(); watch1.Start(); #endif model.Projects = mixManager.GetProjectModel(allTeamsProjects).ToDictionary(x => x.Id.ToString()); #if DEBUG watch1.Stop(); #endif #region New Teams Filling if (newTeams != null) { #if DEBUG watch1 = new System.Diagnostics.Stopwatch(); watch1.Start(); #endif var teams2Add = newTeams.FindAll(x => model.Teams.All(t => t.Id.ToString() != x.Id)); var expCatsInTeams = teams2Add.Where(x => x.Data != null) .SelectMany(x => x.Data) .Where(x => x != null) .Select(x => x.Id) .ToList(); var expCats = DbContext.ExpenditureCategory.AsNoTracking() .Include(x => x.UOM) .Where(x => expCatsInTeams.Contains(x.Id)) .ToDictionary(x => x.Id, x => x); var expCatsWithAdjustFactor = DbContext.VW_ExpendituresWithAdjustmentFactor.AsNoTracking() .Where(x => x.ExpenditureCategoryId.HasValue && expCatsInTeams.Contains(x.ExpenditureCategoryId.Value)) .ToList() .GroupBy(x => x.ExpenditureCategoryId.Value) .ToDictionary(x => x.Key, x => x.GroupBy(s => s.WeekEndingDate) .ToDictionary(s => s.Key, s => s.Any() ? (s.FirstOrDefault().AdjustmentFactor ?? 0) : 1)); #if DEBUG watch1.Stop(); #endif #if DEBUG watch1 = new System.Diagnostics.Stopwatch(); watch1.Start(); #endif foreach (var team in teams2Add) { var mixTeam = new MixTeamModel { Id = team.Id, Name = team.TVName, CompanyId = team.CompanyId, UserId = team.UserId, CostCenterId = team.CostCenterId, Group = team.Group, IsNew = true }; if (null != team.Data) { foreach (var expCat in team.Data.Where(t => t != null)) { if (expCat.Positions != null) { foreach (var pos in expCat.Positions) { pos.SDate = Utils.ConvertFromUnixDate(long.Parse(pos.StartDate)); pos.EDate = !string.IsNullOrEmpty(pos.EndDate) ? Utils.ConvertFromUnixDate(long.Parse(pos.EndDate)) : (DateTime?)null; } } var category = expCats.ContainsKey(expCat.Id) ? expCats[expCat.Id] : null; var activityCategory = new ExpCategorySummaryInfoModel { Id = expCat.Id, Name = category != null ? category.Name : string.Empty, UomValue = category?.UOM.UOMValue ?? 0, ECScenario = ExpCategorySummaryInfoModel.EC_Scenario.TEAM }; var adjustmentFactors4Category = expCatsWithAdjustFactor.ContainsKey(expCat.Id) ? expCatsWithAdjustFactor[expCat.Id] : null; foreach (var week in weekEndings) { var capacity = 0M; if (expCat.Positions != null) { var factor = 1M; if (adjustmentFactors4Category != null && adjustmentFactors4Category.ContainsKey(week)) factor = adjustmentFactors4Category[week]; var resourcesNumber = expCat.Positions.Count(x => x.SDate <= week && (!x.EDate.HasValue || x.EDate >= week)); capacity = resourcesNumber * activityCategory.UomValue * factor; } activityCategory.PlannedCapacityValues.Add(Utils.ConvertToUnixDate(week).ToString(), capacity); } mixTeam.ExpCategories.Add(activityCategory.Id.ToString(), activityCategory); // SA. ENV-1025. We need to reset attributes, because the values cause model deserialization problems // on saving Mix operation if (expCat.Positions != null) { foreach (var pos in expCat.Positions) { pos.SDate = null; pos.EDate = null; } } } } model.Teams.Add(mixTeam); } #if DEBUG watch1.Stop(); #endif } #endregion #if DEBUG watch1 = new System.Diagnostics.Stopwatch(); watch1.Start(); #endif model.Teams = model.Teams.OrderBy(x => x.Name).ToList(); model.SuperExpenditures = eManager.GetSuperExpenditureDetails(); if (retriveExpendituresNeedValues && model.Projects != null) { var scenarios = model.Projects.Values.Where(p => p.Scenario != null).Select(x => x.Scenario.Id).ToList(); var needAllocations = scenarioManager.GetScenarioDetails(scenarios, null, weekEndings); model.NeedAllocations = ConvertNeedAllocationsToTree(needAllocations); } #if DEBUG watch1.Stop(); #endif } } private IMixManager ResolveMixManager(DbProvider dbProvider) { switch (dbProvider) { //case DbProvider.SqlServer: // return new MixManager(DbContext); case DbProvider.MongoDb: return new MongoMixManager(DbContext, User.Identity.GetID()); default: throw new InvalidOperationException($"DbProvider with value = {dbProvider.GetHashCode()} not defined"); } } #endregion [HttpGet] [AreaSecurity(area = Areas.Teams, level = AccessLevel.Write)] public ActionResult EditTeam(Guid? id) { bool isNewTeam = !id.HasValue || (id.Value == Guid.Empty); ViewBag.IsNewTeam = isNewTeam; try { var model = new TeamModel(); if (!isNewTeam) { var manager = new TeamManager(DbContext); model = manager.LoadTeamModel(id.Value) ?? new TeamModel(); } else { model.Id = Guid.NewGuid(); } model.UserId = isNewTeam ? new List { SecurityManager.GetUserPrincipal() } : DbContext.User2Team.Where(x => x.TeamId == model.Id).ToList().Select(x => Guid.Parse(x.UserId)).ToList(); return PartialView("_mixTeams", model); } catch (Exception exception) { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } /// Get Scenario details for specified Projects in da mix [HttpPost] [AreaSecurity(area = Areas.Mixes, level = AccessLevel.Read)] [ValidateJsonAntiForgeryToken] public ActionResult GetScenarioDetails(MixScenarioDetailsLoadModel model) { if (model?.Scenarios == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); try { var mixManager = ResolveMixManager(DbProvider.MongoDb); var result = new Dictionary>(); foreach (var scenarioId in model.Scenarios) { if (scenarioId != Guid.Empty) { // Model.MixId is actually the Mix.Key value var allocations = mixManager.GetFullAllocationInfoByScenario(model.MixId, scenarioId); result.Add(scenarioId.ToString(), allocations); } } return BigJson(result); } catch (Exception exception) // handle any unexpected error { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [HttpPost] [AreaSecurity(area = Areas.Mixes, level = AccessLevel.Write)] [ValidateJsonAntiForgeryToken] public ActionResult EditScenarioDetails(MixScenarioDetailsFormLoadModel model) { if (model?.Scenario == null || model.Scenario.Id == Guid.Empty) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); try { Project project; using (var projectManager = new ProjectManager(DbContext)) { project = projectManager.Load(model.Scenario.ParentId); } DateTime? projectDeadline = null; if (project?.Deadline != null) projectDeadline = project.Deadline; var userId = SecurityManager.GetUserPrincipal(); var detailsModel = ScenarioUIManager.CreateScenarioDetailsCalendarModel(userId, model.Scenario, model.TeamsInScenario, projectDeadline); var depMngr = new ProjectDependencyManager(DbContext); if (project != null) { var depInfo = depMngr.GetDependencies(project.Id, model.MixScenarioDependencyData); detailsModel.ProjectHasDependencies = depInfo != null && depInfo.Count > 0; if (detailsModel.ProjectHasDependencies) { var prevProjectsInfo = depInfo.FirstOrDefault(x => x.Type == ProjectDependencyDisplayType.StartsAfter); var succProjectsInfo = depInfo.FirstOrDefault(x => x.Type == ProjectDependencyDisplayType.StartsBefore); detailsModel.StartDateConstraint = prevProjectsInfo?.dtEndDate != null ? Utils.ConvertFromUnixDate(prevProjectsInfo.dtEndDate.Value) : (DateTime?)null; detailsModel.EndDateConstraint = succProjectsInfo?.dtStartDate != null ? Utils.ConvertFromUnixDate(succProjectsInfo.dtStartDate.Value) : (DateTime?)null; } detailsModel.DependencyListModel = depInfo; } return PartialView("~/Views/Scenarios/_scenarioCalendar.cshtml", detailsModel); } catch (Exception exception) // handle any unexpected error { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [HttpPost] [AreaSecurity(area = Areas.Mixes, level = AccessLevel.Write)] [ValidateJsonAntiForgeryToken] public ActionResult RecalculateScenarioFinInfo(ScenarioCalendarMixModel model) { if (model == null || model.Id == Guid.Empty || !model.ParentId.HasValue || model.ParentId == Guid.Empty) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } try { var scenarioManager = (new ScenarioManager(DbContext)); var finInfo = scenarioManager.GetScenarioFinInfoModel(model); if (finInfo == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); var mixScenarioFinInfo = 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 = model.FinInfo?.CostSaving }; return BigJson(mixScenarioFinInfo); } catch (Exception exception) // handle any unexpected error { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [HttpPost] [AreaSecurity(area = Areas.Mixes, level = AccessLevel.Write)] [ValidateJsonAntiForgeryToken] public ActionResult EditScenarioFinInfo(MixScenarioDetailsFormLoadModel model) { if (model?.Scenario == null || model.Scenario.Id == Guid.Empty || model.Scenario.ParentId == null || model.Scenario.ParentId == Guid.Empty || model.Scenario.FinInfo == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } try { var scenarioManager = (new ScenarioManager(DbContext)); var actualScenario = scenarioManager.GetActualScenario(model.Scenario.ParentId.Value); var scenarioStartDate = Utils.ConvertFromUnixDate(model.Scenario.StartDate); var scenarioEndDate = Utils.ConvertFromUnixDate(model.Scenario.EndDate); var date4StartOfChanges = scenarioStartDate > DateTime.Today ? scenarioStartDate.Date : (DateTime.Today > scenarioEndDate ? scenarioEndDate.Date : DateTime.Today.Date); if (actualScenario != null && actualScenario.EndDate > date4StartOfChanges) date4StartOfChanges = actualScenario.EndDate.Value; var finInfo = new ScenarioFinInfoModel { DateForStartOfChanges = date4StartOfChanges, IsRevenueGenerating = model.Scenario.FinInfo.IsRevenueGenerating, ProjectedRevenue = model.Scenario.FinInfo.ProjectedRevenue, RevenueAfterCost = model.Scenario.FinInfo.RevenueAfterCost, ActualRevenueAfterCost = model.Scenario.FinInfo.ActualRevenueAfterCost, GrossMargin = model.Scenario.FinInfo.GrossMargin, LMMargin = model.Scenario.FinInfo.LMMargin, UseLMMargin = model.Scenario.FinInfo.UseLMMargin, CalculatedGrossMargin = model.Scenario.FinInfo.CalculatedGrossMargin, CalculatedGrossMarginLM = model.Scenario.FinInfo.CalculatedGrossMarginLM, CalculatedGrossMarginActuals = model.Scenario.FinInfo.CalculatedGrossMarginActuals, CalculatedGrossMarginLMActuals = model.Scenario.FinInfo.CalculatedGrossMarginLMActuals, TDDirectCosts = model.Scenario.FinInfo.TDDirectCosts, BUDirectCosts = model.Scenario.FinInfo.BUDirectCosts, ActualLabor = model.Scenario.FinInfo.ActualLabor, LaborMaterialsSplit = model.Scenario.FinInfo.LaborMaterialsSplit, ActualLaborMaterialsSplit = model.Scenario.FinInfo.ActualLaborMaterialsSplit, ActualMaterials = model.Scenario.FinInfo.ActualMaterials, ActualsTotal = model.Scenario.FinInfo.ActualsTotal, EFXSplit = model.Scenario.FinInfo.EFXSplit, LaborSplitPercentage = model.Scenario.FinInfo.LaborSplitPercentage, CostSaving = new CostSavingModel() { ScenarioId = model.Scenario.Id, ScenarioStartDate = scenarioStartDate, ScenarioEndDate = scenarioEndDate } }; if (model.Scenario.FinInfo.CostSaving != null) { var costSavings = scenarioManager.GetCostSavings(model.Scenario.FinInfo.CostSaving); var costSavingItems = scenarioManager.GetScenarioCostSavingModelItems(costSavings); finInfo.CostSaving.CostSavingsPanelExpanded = (model.Scenario.FinInfo.CostSaving.CostSavingStartDate.HasValue && model.Scenario.FinInfo.CostSaving.CostSavingEndDate.HasValue); finInfo.CostSaving.CostSavings = model.Scenario.FinInfo.CostSaving.CostSavings; finInfo.CostSaving.CostSavingStartDate = model.Scenario.FinInfo.CostSaving.CostSavingStartDate.HasValue ? Utils.ConvertFromUnixDate(model.Scenario.FinInfo.CostSaving.CostSavingStartDate.Value) : (DateTime?)null; finInfo.CostSaving.CostSavingEndDate = model.Scenario.FinInfo.CostSaving.CostSavingEndDate.HasValue ? Utils.ConvertFromUnixDate(model.Scenario.FinInfo.CostSaving.CostSavingEndDate.Value) : (DateTime?)null; finInfo.CostSaving.CostSavingType = model.Scenario.FinInfo.CostSaving.CostSavingType; finInfo.CostSaving.CostSavingDescription = model.Scenario.FinInfo.CostSaving.CostSavingDescription; finInfo.CostSaving.ROIDate = scenarioManager.GetROIDate(model.Scenario.FinInfo.CostSaving.CostSavings, model.Scenario.FinInfo.BUDirectCosts, costSavings); finInfo.CostSaving.CostSavingItems = JsonConvert.SerializeObject(costSavingItems); finInfo.CostSaving.IsEditable = true; } Project project; using (var projectManager = new ProjectManager(DbContext)) { project = projectManager.Load(model.Scenario.ParentId); } DateTime? deadLine = null; if (project?.Deadline != null) deadLine = project.Deadline; var userId = SecurityManager.GetUserPrincipal(); var scenarioDetailsModel = ScenarioUIManager.CreateScenarioDetailsCalendarModel(userId, model.Scenario, model.TeamsInScenario, deadLine); var editFinInfoModel = new MixEditScenarioFinInfoModel { FinInfo = finInfo, Calendar = scenarioDetailsModel }; return PartialView("_mixScenarioFinInfoEdit", editFinInfoModel); } catch (Exception exception) // handle any unexpected error { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [HttpPost] [AreaSecurity(area = Areas.Mixes, level = AccessLevel.Write)] [ValidateJsonAntiForgeryToken] public ActionResult ActivateScenario(MixScenarioActivationModel model) { if (model?.Project == null || model.Project.Id == Guid.Empty || model.Project.Scenario == null || model.Project.Scenario.Id == Guid.Empty) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } try { if (model.Project.Teams == null) model.Project.Teams = new List(); var mixManager = ResolveMixManager(DbProvider.MongoDb); var savedTeamsInProject = DbContext.Team2Project.AsNoTracking() .Where(x => x.ProjectId == model.Project.Id) .Select(x => x.TeamId).ToList(); var requiredTeams = savedTeamsInProject.Where(x => model.Project.Teams.All(s => s != x)).ToList(); if (requiredTeams.Count > 0) model.Project.Teams.AddRange(requiredTeams); mixManager.ActivateTeams(model.Teams); mixManager.ActivateScenario(model.Project.Scenario, model.Project.Teams, model.MixId, model.IsActive); DbContext.SaveChanges(); // we should return updated collection of inactive scenarios to refresh inactive scenarios list var projectModel = mixManager.GetProjectModel(new List() { model.Project.Id }); if (projectModel != null && projectModel.Count > 0) { var project = projectModel.FirstOrDefault(x => x.Id == model.Project.Id); if (project != null) return Json(project.InactiveScenarios); } } catch (Exception exception) // handle any unexpected error { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } /// /// Get from SQL database current timestamps for specified scenario list and returns it /// to client. The list can contain ID of projects, if their scenario IDs are unknown. In /// this case it returns timestamp for current active scenarios for theese projects /// /// Project or scenario ID list /// [HttpPost] [AreaSecurity(area = Areas.Mixes, level = AccessLevel.Read)] [ValidateJsonAntiForgeryToken] public ActionResult GetScenariosTimestamps(Dictionary model) { if (model == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); try { // Get new timestamps and set marker to change active scenario Id for project in mix model var result = GetScenariosTimestampsInternal(model.Keys.ToList()); result.ForEach(x => { if (model.ContainsKey(x.ScenarioId.ToString())) { x.ChangeActiveScenarioId = model[x.ScenarioId.ToString()]; } else { x.ChangeActiveScenarioId = model.ContainsKey(x.ProjectId.ToString()) && model[x.ProjectId.ToString()]; } }); return Json(result); } catch (Exception exception) // handle any unexpected error { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } /// /// Returns LIVE database data for specified project list /// /// Project or scenario ID list /// /// SA. ENV-1246 [HttpPost] [AreaSecurity(area = Areas.Mixes, level = AccessLevel.Read)] [ValidateJsonAntiForgeryToken] public ActionResult GetProjectsDataFromLive(List model) { var result = new List(); if (model == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); if (model.Count < 1) return Json(result); try { var mixManager = ResolveMixManager(DbProvider.MongoDb); result = mixManager.GetProjectModel(model); string userId = User.Identity.GetID(); var scenarioManager = new ScenarioManager(DbContext); foreach (MixProjectModel mixProject in result) { if (mixProject?.Scenario != null) { var scenarioId = mixProject.Scenario.Id; var scenarioExpCats = scenarioManager.GetFullAllocationInfoByScenario(scenarioId, userId); mixProject.Scenario.Expenditures = scenarioExpCats; } } return BigJson(result); } catch (Exception exception) // handle any unexpected error { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } /// /// Returns live DB timestamps for given scenarios /// /// Scenario or project Ids /// private List GetScenariosTimestampsInternal(List scenarios) { if (scenarios == null) return null; if (scenarios.Count < 1) return new List(); var activeScenarios = new List(); var foundScenarios = DbContext.BLL_Objects.Where(x => x.EntityName == "Scenario" && scenarios.Contains(x.Id.ToString())) .Select(x => x.Id).ToList(); var projects = DbContext.BLL_Objects.Where(x => x.EntityName == "Project" && scenarios.Contains(x.Id.ToString())) .Select(x => x.Id).ToList(); if (projects.Count > 0) { activeScenarios = DbContext.Scenarios.Where(x => x.ParentId.HasValue && projects.Contains(x.ParentId.Value) && (x.Type == (int)ScenarioType.Portfolio) && x.Status.HasValue && (x.Status.Value == (int)ScenarioStatus.Active)) .Select(x => x.Id).Except(foundScenarios).ToList(); } foundScenarios.AddRange(activeScenarios); var result = DbContext.Scenarios.Where(s => foundScenarios.Contains(s.Id) && s.ParentId.HasValue).ToList().Select(r => new MixScenarioTimestampModel { ScenarioId = r.Id, ProjectId = r.ParentId.Value, Timestamp = r.GetCurrentVersion(), ChangeActiveScenarioId = false }).ToList(); return result; } private List ConvertCostCentersToOptions(IEnumerable costCenters) { var result = costCenters?.Select(x => new SelectListItem { Value = x.Id.ToString(), Text = x.Name }).OrderBy(x => x.Text).ToList(); return result; } private List ConvertExpendituresToOptions(IEnumerable expCats) { var result = expCats?.Select(x => new MixFilterVariantsModel.ProjectRoleItem { Value = x.ExpenditureCategoryId.ToString(), Text = x.ExpenditureCategoryName, CostCenterId = x.CreditId.ToString() }).OrderBy(x => x.Text).ToList(); return result; } private void UpdateFilterSelection(MixSaveModel model, MixFilterSelectionModel selection) { if (model?.Filter == null) return; if (model.Filter.Selection == null) model.Filter.Selection = new MixFilterSelectionModel(); model.Filter.Selection.CostCenters = new List(); model.Filter.Selection.ProjectRoles = new List(); if (selection != null && model.Filter.Variants != null) { var availableCostCentersIds = model.Filter.Variants.AvailableCostCenters.Select(x => new Guid(x.Value)).ToList(); if (selection.CostCenters != null && model.Filter.Variants.AvailableCostCenters != null) { model.Filter.Selection.CostCenters = selection.CostCenters.Where(x => availableCostCentersIds.Contains(x)).ToList(); } else { model.Filter.Selection.CostCenters = null; } if (selection.ProjectRoles != null && model.Filter.Variants.AvailableProjectRoles != null) { var availableProjectRolesIds = model.Filter.Variants.AvailableProjectRoles .Where(x => !String.IsNullOrEmpty(x.CostCenterId) && availableCostCentersIds.Contains(new Guid(x.CostCenterId))) .Select(x => new Guid(x.Value)).ToList(); model.Filter.Selection.ProjectRoles = selection.ProjectRoles.Where(x => availableProjectRolesIds.Contains(x)).ToList(); } else { model.Filter.Selection.ProjectRoles = null; } } // Transfer initially filter within result model, because client controller takes dates directily from // model.StartDate & model.EndDate if (selection != null) { model.StartDate = selection.StartDate; model.EndDate = selection.EndDate; } } public Dictionary ConvertNeedAllocationsToTree(List allocations) { if (allocations == null || allocations.Count <= 0) return new Dictionary(); var tree = new Dictionary(); 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 MixAllocationsByECModel()); if (!tree[scenarioKey].Expenditures.ContainsKey(ecKey)) tree[scenarioKey].Expenditures.Add(ecKey, new MixAllocationsModel()); tree[scenarioKey].Expenditures[ecKey].Allocations.Add(weekEndingKey, allocation.Quantity ?? 0); } return tree; } protected void BuildNeedAllocations(MixCalendarModel model) { if (model?.Projects != null) { // Build NeedAllocations from scenario's data of the model model.NeedAllocations = model.Projects.Values.Where(p => p?.Scenario?.Expenditures != null && p.Scenario.Expenditures.Count > 0) .ToDictionary(k => k.Scenario.Id.ToString(), v => new MixAllocationsByECModel { Expenditures = v.Scenario.Expenditures.ToDictionary(e => e.Key, e => new MixAllocationsModel { Allocations = e.Value.Details.ToDictionary(sd => sd.Key, sd => sd.Value.ForecastQuantity ?? 0) }) }); } } /// /// Extracts expenditures from all the projects in model /// /// /// protected List GetExpendituresFromModel(MixCalendarModel model) { List result = null; if (model?.NeedAllocations != null) { result = model.NeedAllocations.Values.Where(x => x.Expenditures != null).SelectMany(x => x.Expenditures.Keys) .Select(x => new Guid(x)).Distinct().ToList(); } return result; } private IEnumerable GetCostCenters(Dictionary needAllocations, IEnumerable teamAllocations, IEnumerable projectRolesInfo) { if (needAllocations == null) return null; // Gather all expenditures in the model and get cost centers List result; using (var costCenterMngr = new CreditDepartmentManager(DbContext)) { var expCatsWithNeed = needAllocations.Where(x => x.Value.Expenditures != null).SelectMany(x => x.Value.Expenditures.Where(e => e.Value.Allocations != null && e.Value.Allocations.Values.Any(a => a > 0)) .Select(e => e.Key)).Distinct().ToList(); // Check team allocations existance only for ordinary ECs var projectRolesInModel = projectRolesInfo?.Select(x => x.ExpenditureCategoryId).ToList() ?? new List(); expCatsWithNeed = expCatsWithNeed.Where(e => projectRolesInModel.Contains(new Guid(e)) || teamAllocations != null && teamAllocations.Any(t => t?.ExpCategories != null && t.ExpCategories.ContainsKey(e) && t.ExpCategories[e].NeedCapacity != null && t.ExpCategories[e].NeedCapacity.Values.Any(a => a > 0))).ToList(); var expCatsWithNeedTyped = expCatsWithNeed.Select(x => new Guid(x)); result = costCenterMngr.GetCreditDepartmentsByExpCats(expCatsWithNeedTyped); } return result; } #region Private Methods private IEnumerable GetProjectRoles(IEnumerable modelExpCats, Dictionary needAllocations, IEnumerable teamAllocations, IEnumerable costCenters, IEnumerable projectRolesInfo) { if (modelExpCats == null || needAllocations == null || projectRolesInfo == null) return null; // Get all Project Roles in model with extended info var allProjectRolesInModel = projectRolesInfo.Where(x => modelExpCats.Contains(x.ExpenditureCategoryId)).Select(x => x.ExpenditureCategoryId).ToList(); // Regroup Need data var projectRolesNeedIndexed = needAllocations.Where(n => n.Value.Expenditures != null).SelectMany(n => n.Value.Expenditures.Where(e => allProjectRolesInModel.Contains(new Guid(e.Key)) && e.Value.Allocations != null) .SelectMany(e => e.Value.Allocations.Select(a => new { ScenarioId = new Guid(n.Key), ExpCatId = new Guid(e.Key), Weekending = a.Key, a.Value }))) .GroupBy(x => x.ExpCatId).ToDictionary(g1 => g1.Key, g1 => g1.GroupBy(x => x.Weekending).ToDictionary(k2 => k2.Key, v2 => v2.Sum(v => v.Value))); // Get project roles with non-zero need and format team allocations for quick access - by scenarios var rolesWithNonZeroRemainingNeed = GetRolesWithNonZeroRemainingNeed(teamAllocations, projectRolesNeedIndexed); var costCentersIds = costCenters?.Select(x => x.Id).ToList() ?? new List(); var result = projectRolesInfo.Where(x => rolesWithNonZeroRemainingNeed.Contains(x.ExpenditureCategoryId) && (costCentersIds.Count < 1 || costCentersIds.Contains(x.CreditId))).ToList(); return result; } private IEnumerable GetRolesWithNonZeroRemainingNeed(IEnumerable teamAllocations, Dictionary> projectRolesNeedIndexed) { var projectRolesWithNonZeroNeed = projectRolesNeedIndexed.Where(x => x.Value.Values.Any(v => v > 0)).Select(z => z.Key).ToList(); var teamAllocationsForRolesIndexed = teamAllocations?.Where(t => t.ExpCategories != null) .SelectMany(t => t.ExpCategories.Where(e => projectRolesWithNonZeroNeed.Contains(new Guid(e.Key)) && e.Value.NeedCapacity != null) .SelectMany(e => e.Value.NeedCapacity.Select(a => new { ExpCatId = new Guid(e.Key), Weekending = a.Key, a.Value }))) .GroupBy(x => x.ExpCatId).ToDictionary(g1 => g1.Key, g1 => g1.GroupBy(y => y.Weekending).ToDictionary(g2 => g2.Key, g2 => g2.Select(z => z.Value).Sum())); // Get Project roles with non-zero remaining need (need != Sum(Team allocations)). Look for roles in every scenario //var rolesWithNonZeroRemainingNeed = new List(); foreach (var projectRoleId in projectRolesWithNonZeroNeed) { var currentRoleNeed = projectRolesNeedIndexed[projectRoleId]; if (teamAllocationsForRolesIndexed.ContainsKey(projectRoleId)) { // Some teams are allocated to current project role var allocationsToCurrentRole = teamAllocationsForRolesIndexed[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) { yield return projectRoleId; break; } } } else { // No teams allocated to project role. But project role has non-zero need yield return projectRoleId; } } } private void LoadMixFilterValues(MixFilterVariantsModel model, string userId) { MongoMixManager mixManager = new MongoMixManager(null, userId); if (model == null) throw new ArgumentNullException(Messages.Common_ModelMustBeNotNull); if (string.IsNullOrWhiteSpace(userId)) throw new ArgumentNullException(nameof(userId)); model.AvailableTeamsAndViews = Utils.GetViewsAndTeams(userId); model.AvailableUsers = Utils.GetUsers().ToList(); model.AvailableMixes = mixManager.GetMixesAvailable4User(); using (var expCatManager = new ExpenditureManager(DbContext)) { model.AvailableExpenditures = expCatManager.GetExpenditures(); } } private void LoadClientSideFilterValues(MixSaveModel model) { if (model == null) throw new ArgumentNullException(Messages.Common_ModelMustBeNotNull); if (model.Filter == null) model.Filter = new MixFilterModel(); if (model.Filter.Variants == null) model.Filter.Variants = new MixFilterVariantsModel(); // Get info abount all project roles List projectRolesInfoTyped; using (var ecManager = new ExpenditureCategoryManager(DbContext)) { var projectRolesInfo = ecManager.GetSuperExpenditureDetails(); projectRolesInfoTyped = projectRolesInfo.Values.ToList(); } var needAllocationsTyped = model.Calendar?.NeedAllocations; var teamAllocationsTyped = model.Calendar?.Teams; var expCatsFromModel = GetExpendituresFromModel(model.Calendar); // Get options for Cost Centers var availableCostCenters = GetCostCenters(needAllocationsTyped, teamAllocationsTyped, projectRolesInfoTyped).ToList(); model.Filter.Variants.AvailableCostCenters = ConvertCostCentersToOptions(availableCostCenters); // Get options for Project Roles var availableProjectRoles = GetProjectRoles(expCatsFromModel, needAllocationsTyped, teamAllocationsTyped, availableCostCenters, projectRolesInfoTyped); model.Filter.Variants.AvailableProjectRoles = ConvertExpendituresToOptions(availableProjectRoles); } private MixSaveModel LoadMixSaveModel(string model) { string mixKey = model; var serverModel = new MixSaveModel(); var mixManager = ResolveMixManager(DbProvider.MongoDb); #if DEBUG var watch1 = new System.Diagnostics.Stopwatch(); watch1.Start(); #endif LoadMixFilterValues(serverModel.Filter.Variants, User.Identity.GetID()); #if DEBUG watch1.Stop(); #endif if (!string.IsNullOrWhiteSpace(mixKey)) { #if DEBUG watch1 = new System.Diagnostics.Stopwatch(); watch1.Start(); #endif // Get mix data from Mongo var mongoModel = mixManager.RetrieveWithChildren(mixKey); #if DEBUG watch1.Stop(); #endif if (mongoModel != null) { var startDate = Utils.ConvertFromUnixDate(mongoModel.StartDate); var endDate = Utils.ConvertFromUnixDate(mongoModel.EndDate); var endDateCorrected = endDate.AddDays(6); #if DEBUG watch1 = new System.Diagnostics.Stopwatch(); watch1.Start(); #endif // Create server Mix model from SQL GetCalendar(serverModel.Calendar, startDate, endDateCorrected, mongoModel.Filter.Selection.TeamsViews.Where(t => t.Group.Name == "Teams").Select(x => new Guid(x.Id)).ToList(), mongoModel.Filter.Selection.TeamsViews.Where(t => t.Group.Name == "Views").Select(x => new Guid(x.Id)).ToList(), mongoModel.Filter.Selection.TeamsViews.Where(x => x.IsNew).ToList(), false); #if DEBUG watch1.Stop(); #endif #if DEBUG watch1 = new System.Diagnostics.Stopwatch(); watch1.Start(); #endif // All expenditure's data must be in mongo mix model. Build EC need from this model BuildNeedAllocations(mongoModel.Calendar); MixModelsMergeManager mergeMngr = new MixModelsMergeManager(DbContext, User.Identity.GetID()); mergeMngr.MergeHeaders(serverModel, mongoModel); mergeMngr.MergeFilters(serverModel.Filter, mongoModel.Filter); mergeMngr.MergeCalendars(serverModel, mongoModel, startDate, endDate); #if DEBUG watch1.Stop(); #endif } } var fullMongoMix = serverModel; if (!string.IsNullOrWhiteSpace(mixKey)) fullMongoMix = mixManager.RetrieveWithChildren(serverModel.Id); if (fullMongoMix == null) fullMongoMix = serverModel; serverModel.CanRunResourceRace = HasResourceAssignmentsBool(fullMongoMix.Calendar); serverModel.CanRunTeamRace = HasSupperEcUnAssigned(fullMongoMix.Calendar); serverModel.canDoRace = ValidateForRace(serverModel); return serverModel; } private bool ValidateForRace(MixSaveModel mainModel) { var startDate = Utils.ConvertFromUnixDate(mainModel.Filter.Selection.StartDate); if (startDate < DateTime.Now.AddDays(-1)) return false; return ValidateForRace(mainModel.Calendar); } private bool ValidateForRace(MixCalendarModel model) { if (model?.ManagedProjects == null) return false; var dateNow = Utils.ConvertToUnixDate(DateTime.Now); return model.Projects.Select(x => x.Value) .Where(p => model.ManagedProjects.Contains(p.Id)) .Any(p => p.Scenario != null && p.Scenario.StartDate >= dateNow && !p.Pinned); } private bool HasSupperEcUnAssigned(MixCalendarModel model) { if (model?.Projects == null || model.ManagedProjects == null) return false; var managedScenarios = new List(); var scenarioManager = new ScenarioManager(DbContext); foreach (var projectId in model.ManagedProjects) { var projectKey = projectId.ToString(); if (!model.Projects.ContainsKey(projectKey)) continue; var project = model.Projects[projectKey]; if (project?.Scenario?.Expenditures == null) continue; var hasSuperEc = project.Scenario.Expenditures.Any(x => x.Value != null && !x.Value.AllowResourceAssignment); if (hasSuperEc) return false; managedScenarios.Add(project.Scenario.Id); } var superECs = scenarioManager.GetExpendituresInScenarios(managedScenarios, true); return superECs == null || !superECs.Any(); } private bool HasResourceAssignmentsBool(MixCalendarModel model) { if (model?.Projects == null || model.ManagedProjects == null) return false; var managedScenarios = new List(); bool hasNonAllocatedEc; using (var scenarioManager = new ScenarioManager(DbContext)) { foreach (var projectId in model.ManagedProjects) { var projectKey = projectId.ToString(); if (!model.Projects.ContainsKey(projectKey)) continue; var project = model.Projects[projectKey]; if (project?.Scenario?.Expenditures == null) continue; foreach (var category in project.Scenario.Expenditures.Values) { if (category.Teams != null && category.Teams.Any()) { var emptyTeamExists = category.Teams.Values.Any(x => x.Resources == null || !x.Resources.Any()); if (emptyTeamExists) return false; } } managedScenarios.Add(project.Scenario.Id); } hasNonAllocatedEc = scenarioManager.CheckScenarioHasNonAllocatedEC(managedScenarios); } return !hasNonAllocatedEc; } Race _calcModule; private Race GetRaceModule() { if (_calcModule == null) { var userId = User.Identity.GetID(); var userRec = DbContext.AspNetUsers.FirstOrDefault(x => x.Id == userId.ToString()); if (userRec != null) _calcModule = new Race(userRec.OverUnderCoefficient); } return _calcModule; } static IEnumerable> GetPermutations(IEnumerable list, int length) { if (length == 1) return list.Select(t => new[] { t }); return GetPermutations(list, length - 1) .SelectMany(t => list.Where(e => true), (t1, t2) => t1.Concat(new[] { t2 })); } //private bool ValidateProject(DateTime filterStartDate, DateTime filterEndDate, DateTime scenarioStartDate, DateTime scenarioEndDate, DateTime projectDeadLine) //{ // if (CheckStartDateWithToday(filterStartDate) && // CheckEndDateScenarioStartDate(filterEndDate, scenarioStartDate) && // CheckScenarioEndDateWithProjectDeadline(scenarioEndDate, projectDeadLine)) // return true; // return false; //} //private bool CheckScenarioEndDateWithProjectDeadline(DateTime scenarioEndDate, DateTime projectDeadLine) //{ // return projectDeadLine == DateTime.MinValue || scenarioEndDate <= projectDeadLine; //} //private bool CheckEndDateScenarioStartDate(DateTime filterEndDate, DateTime scenarioStartDate) //{ // return scenarioStartDate <= filterEndDate; //} //private bool CheckStartDateWithToday(DateTime filterStartDate) //{ // return filterStartDate >= DateTime.Today; //} #endregion } }