1566 lines
68 KiB
C#
1566 lines
68 KiB
C#
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<Guid> { 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<Guid>() { 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<DateTime> 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<Guid> teamsIds,
|
|
List<Guid> viewsIds, List<MixTeamViewModel> 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<Code.DAL.Mongo.Mix> 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<Guid> { 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);
|
|
}
|
|
|
|
/// <summary>Get Scenario details for specified Projects in da mix</summary>
|
|
[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<string, Dictionary<string, ExpenditureDetail>>();
|
|
|
|
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<Guid>();
|
|
|
|
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<Guid>() { 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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
|
|
/// </summary>
|
|
/// <param name="model">Project or scenario ID list</param>
|
|
/// <returns></returns>
|
|
[HttpPost]
|
|
[AreaSecurity(area = Areas.Mixes, level = AccessLevel.Read)]
|
|
[ValidateJsonAntiForgeryToken]
|
|
public ActionResult GetScenariosTimestamps(Dictionary<string, bool> 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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns LIVE database data for specified project list
|
|
/// </summary>
|
|
/// <param name="model">Project or scenario ID list</param>
|
|
/// <returns></returns>
|
|
/// <remarks>SA. ENV-1246</remarks>
|
|
[HttpPost]
|
|
[AreaSecurity(area = Areas.Mixes, level = AccessLevel.Read)]
|
|
[ValidateJsonAntiForgeryToken]
|
|
public ActionResult GetProjectsDataFromLive(List<Guid> model)
|
|
{
|
|
var result = new List<MixProjectModel>();
|
|
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns live DB timestamps for given scenarios
|
|
/// </summary>
|
|
/// <param name="scenarios">Scenario or project Ids</param>
|
|
/// <returns></returns>
|
|
private List<MixScenarioTimestampModel> GetScenariosTimestampsInternal(List<string> scenarios)
|
|
{
|
|
if (scenarios == null)
|
|
return null;
|
|
|
|
if (scenarios.Count < 1)
|
|
return new List<MixScenarioTimestampModel>();
|
|
|
|
var activeScenarios = new List<Guid>();
|
|
|
|
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<SelectListItem> ConvertCostCentersToOptions(IEnumerable<CreditDepartmentModel> costCenters)
|
|
{
|
|
var result = costCenters?.Select(x => new SelectListItem
|
|
{
|
|
Value = x.Id.ToString(),
|
|
Text = x.Name
|
|
}).OrderBy(x => x.Text).ToList();
|
|
|
|
return result;
|
|
}
|
|
|
|
private List<MixFilterVariantsModel.ProjectRoleItem> ConvertExpendituresToOptions(IEnumerable<ExpenditureDetail> 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<Guid>();
|
|
model.Filter.Selection.ProjectRoles = new List<Guid>();
|
|
|
|
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<string, MixAllocationsByECModel> ConvertNeedAllocationsToTree(List<ScenarioDetail> allocations)
|
|
{
|
|
if (allocations == null || allocations.Count <= 0)
|
|
return new Dictionary<string, MixAllocationsByECModel>();
|
|
|
|
var tree = new Dictionary<string, MixAllocationsByECModel>();
|
|
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)
|
|
})
|
|
});
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Extracts expenditures from all the projects in model
|
|
/// </summary>
|
|
/// <param name="model"></param>
|
|
/// <returns></returns>
|
|
protected List<Guid> GetExpendituresFromModel(MixCalendarModel model)
|
|
{
|
|
List<Guid> 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<CreditDepartmentModel> GetCostCenters(Dictionary<string, MixAllocationsByECModel> needAllocations,
|
|
IEnumerable<MixTeamModel> teamAllocations, IEnumerable<ExpenditureDetail> projectRolesInfo)
|
|
{
|
|
if (needAllocations == null)
|
|
return null;
|
|
|
|
// Gather all expenditures in the model and get cost centers
|
|
List<CreditDepartmentModel> 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<Guid>();
|
|
|
|
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<ExpenditureDetail> GetProjectRoles(IEnumerable<Guid> modelExpCats, Dictionary<string, MixAllocationsByECModel> needAllocations,
|
|
IEnumerable<MixTeamModel> teamAllocations, IEnumerable<CreditDepartmentModel> costCenters,
|
|
IEnumerable<ExpenditureDetail> 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<Guid>();
|
|
var result = projectRolesInfo.Where(x => rolesWithNonZeroRemainingNeed.Contains(x.ExpenditureCategoryId) &&
|
|
(costCentersIds.Count < 1 || costCentersIds.Contains(x.CreditId))).ToList();
|
|
|
|
return result;
|
|
}
|
|
|
|
private IEnumerable<Guid> GetRolesWithNonZeroRemainingNeed(IEnumerable<MixTeamModel> teamAllocations, Dictionary<Guid, Dictionary<string, decimal>> 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<Guid>();
|
|
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<ExpenditureDetail> 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<Guid>();
|
|
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<Guid>();
|
|
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<IEnumerable<T>> GetPermutations<T>(IEnumerable<T> 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
|
|
|
|
|
|
}
|
|
} |