using EnVisage.App_Start; using EnVisage.Code; using EnVisage.Code.BLL; using EnVisage.Code.Cache; using EnVisage.Code.Validation; using EnVisage.Models; using jQuery.DataTables.Mvc; using System; using System.Collections.Generic; using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Linq; using System.Net; using System.Web.Mvc; using Resources; using System.Threading.Tasks; namespace EnVisage.Controllers { [Authorize] public class ScenariosController : BaseController { #region Private Members private ScenarioUIManager ScenarioUIManager { get; } #endregion #region Constructors public ScenariosController(ScenarioUIManager scenarioUIManager) { ScenarioUIManager = scenarioUIManager; } #endregion #region Actions // GET: /Scenarios/Templates [HttpGet] [AreaSecurity(area = Areas.ScenarioTemplates, level = AccessLevel.Read)] public ActionResult Templates() { if (!SecurityManager.CheckSecurityObjectPermission(Areas.ScenarioTemplates, AccessLevel.Read)) return Redirect("/"); return View(); } [HttpGet] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult LoadScenario(ScenarioLoadModel loadModel) { if (loadModel == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); CreateScenarioModel model = new CreateScenarioModel { SaveCallback = loadModel.SaveCallback, CanSaveDraft = loadModel.CanSaveDraft }; var depMngr = new ProjectDependencyManager(DbContext); model.ScenarioId = Guid.Empty; model.Step1.TemplateId = Guid.Empty; model.Step1.HideName = loadModel.HideName ?? false; model.Step1.Project = DbContext.Projects.AsNoTracking().FirstOrDefault(p => p.Id == loadModel.Id); if (null == model.Step1.Project) return new HttpNotFoundResult(); model.Step1.ProjectDeadline = model.Step1.Project.Deadline; model.Step1.StartDate = loadModel.StartDate; // Set scenario teams model.Step1.Teams = new SlidersGroupModel { GroupId = Guid.NewGuid(), Options = Utils.GetTeams() }; int slidersTotalSumm = 100; double remainingValue = slidersTotalSumm; int remainingItems = model.Step1.Project.Team2Project.Count; List projectTeamModels = new List(model.Step1.Project.Team2Project.Count); for (var index = 0; index < model.Step1.Project.Team2Project.Count; index++) { var teamRec = model.Step1.Project.Team2Project.ElementAt(index); var item = new SliderModel { Id = teamRec.Id, EntityId = teamRec.TeamId, Name = teamRec.Team.Name, ParentId = model.Step1.Teams.GroupId, AllocatePercentage = remainingItems > 1 ? Math.Round(remainingValue / remainingItems--, 0) : remainingValue }; item.AllocatePercentage = Math.Max(item.AllocatePercentage, 0); projectTeamModels.Add(item); remainingValue = Math.Max(remainingValue - item.AllocatePercentage, 0); } if (loadModel.TeamId.HasValue && !projectTeamModels.Exists(x => x.EntityId == loadModel.TeamId.Value)) { projectTeamModels.Add(new SliderModel { Id = loadModel.TeamId.Value, EntityId = loadModel.TeamId.Value, Name = loadModel.TeamName, ParentId = model.Step1.Teams.GroupId, AllocatePercentage = 0 }); } model.Step1.Teams.Sliders = projectTeamModels; // Load Project dependency info and fill model with date constraints var depInfo = depMngr.GetDependencies(loadModel.Id); model.Step1.ProjectHasDependencies = depInfo != null && depInfo.Count > 0; if (model.Step1.ProjectHasDependencies && depInfo != null) { var prevProjectsInfo = depInfo.FirstOrDefault(x => x.Type == ProjectDependencyDisplayType.StartsAfter); var succProjectsInfo = depInfo.FirstOrDefault(x => x.Type == ProjectDependencyDisplayType.StartsBefore); model.Step1.StartDateConstraint = prevProjectsInfo?.dtEndDate != null ? Utils.ConvertFromUnixDate(prevProjectsInfo.dtEndDate.Value) : (DateTime?)null; model.Step1.EndDateConstraint = succProjectsInfo?.dtStartDate != null ? Utils.ConvertFromUnixDate(succProjectsInfo.dtStartDate.Value) : (DateTime?)null; } if (model.Step1.Project.ParentProjectId.HasValue) { model.Step1.ProjectId = model.Step1.Project.ParentProjectId.Value; model.Step1.ProjectName = $"{model.Step1.Project.Name}: {model.Step1.Project.ParentProject.Name}"; model.Step1.PartId = loadModel.Id; } else { model.Step1.ProjectId = loadModel.Id; model.Step1.ProjectName = model.Step1.Project.Name; } model.Step3.IsRevenueGenerating = model.Step1.Project.IsRevenueGenerating; model.Step1.StatusIsEditable = loadModel.StatusIsEditable; model.CurrentStep = "Step1"; model.Step1.TemplateId = GetDefaultTemplate("CreateScenarioDefaultTemplate"); return PartialView("_createScenario", model); } [HttpPost] [ValidateAjax] [ValidateAntiForgeryToken] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult SubmitCreateScenarioStep1(CreateScenarioModel.GeneralInfoModel model) { model.TrimStringProperties(); try { if (ModelState.IsValid) { #region Populate fields not stored on client var projman = new ProjectManager(DbContext); var project = projman.Load(model.PartId ?? model.ProjectId); model.Project = project; model.ProjectDeadline = project.Deadline; var ecmanager = new ScenarioManager(DbContext); var ec = ecmanager.GetExpenditureCategories(model.TemplateId); model.ScenarioExpenditures = new ScenarioModel.ExpenditureItem[ec.Count]; var selectedIDs = (model.SelectedExpenditures ?? string.Empty).Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries); var checkedECs = ec.Where(expenditureItem => selectedIDs.Contains(expenditureItem.Id.ToString())).ToList(); var uncheckedECs = ec.Where(item => !selectedIDs.Contains(item.Id.ToString())).ToList(); int i = 0; foreach (var expenditureItem in uncheckedECs) { model.ScenarioExpenditures[i] = new ScenarioModel.ExpenditureItem { Id = expenditureItem.Id, Group = expenditureItem.Group, Name = expenditureItem.Name, Checked = false }; i++; } foreach (var expenditureItem in checkedECs) { model.ScenarioExpenditures[i] = new ScenarioModel.ExpenditureItem { Id = expenditureItem.Id, Group = expenditureItem.Group, Name = expenditureItem.Name, Checked = true }; i++; } var projTeamIds = model.Project.Team2Project.Select(x => x.TeamId).ToList(); //var missingTeams = teamallocations.Keys.Where(x => !projTeamIds.Contains(x)).ToList(); var missingTeams = model.Teams.Sliders.Where(x => !projTeamIds.Contains(x.EntityId)) .Select(x => x.EntityId).ToList(); DbContext.Teams.AsNoTracking().Where(t => missingTeams.Contains(t.Id)) .ToList().ForEach(x => model.Project.Team2Project.Add(new Team2Project { Team = x })); var detailsModel = LoadScenarioDetailsModel(model); model.LaborSplitPercentage = detailsModel.FinInfo.LaborSplitPercentage; model.EFXSplit = detailsModel.FinInfo.EFXSplit; model.Teams.Options = Utils.GetTeams(); #endregion return new PartialViewJsonResult(true, GetScenarioDetailsCalendarModel(model), "_generalStep", model, ControllerContext, ViewData, TempData); } } catch (BLLException blEx) // handle any system specific error { // display error message if required if (blEx.DisplayError) ModelState.AddModelError(string.Empty, blEx.Message); else // if display not requried then display modal form with general error message { LogException(blEx); ModelState.AddModelError(string.Empty, Messages.Scenario_Save_Error); } } catch (Exception exception) // handle any unexpected error { LogException(exception); ModelState.AddModelError(string.Empty, Messages.Scenario_Save_Error); } return new PartialViewJsonResult(false, null, "_generalStep", model, ControllerContext, ViewData, TempData); } [HttpPost] [ValidateAjax] [ValidateAntiForgeryToken] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult SubmitCreateScenarioStep3(CreateScenarioModel.FinInfoModel model) { try { // TODO: test for IsValid when model is null if (model == null) throw new ArgumentNullException(nameof(model)); model.TrimStringProperties(); return new PartialViewJsonResult(true, null, "_finStep", model, ControllerContext, ViewData, TempData); } catch (BLLException blEx) // handle any system specific error { // display error message if required if (blEx.DisplayError) ModelState.AddModelError(string.Empty, blEx.Message); else // if display not requried then display modal form with general error message { LogException(blEx); ModelState.AddModelError(string.Empty, Messages.Scenario_Save_Error); } } catch (Exception exception) // handle any unexpected error { LogException(exception); ModelState.AddModelError(string.Empty, Messages.Scenario_Save_Error); } return new FailedJsonResult(ModelState); } [HttpPost] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult GetECsByTemplateId(Guid? id, List selectedExpCats, List teams, bool overrideChecked) { IList expenditureCategories; using (var scenarioManager = new ScenarioManager(DbContext)) { expenditureCategories = scenarioManager.GetExpenditureCategories(id, teams); } if (selectedExpCats != null && selectedExpCats.Count > 0) { foreach (var expenditureItem in expenditureCategories) { if (overrideChecked) expenditureItem.Checked = selectedExpCats.Contains(expenditureItem.Id); else expenditureItem.Checked |= selectedExpCats.Contains(expenditureItem.Id); } } var options = expenditureCategories.GroupBy(x => x.Group).Select(x => new { text = x.Key, children = x.Select(s => new { id = s.Id, text = s.Name, }).ToList() }).ToList(); var selected = expenditureCategories.Where(x => x.Checked).Select(x => x.Id).ToList(); return Json(new { options, selected }); } [HttpGet] //[ValidateAntiForgeryToken] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult GetProjectPartsByProjectId(Guid? Id) { Guid userId = SecurityManager.GetUserPrincipal(); var pp = Utils.GetProjectParts(Id, userId).ToList(); return PartialView("_createScenarioProjectParts", pp); } /// /// Returns JSON UnitOfMeasure list with filters and sort for jQuery DataTables /// [HttpPost] [AreaSecurity(area = Areas.ScenarioTemplates, level = AccessLevel.Read)] public JsonResult Templates(JQueryDataTablesModel jQueryDataTablesModel) { int totalRecordCount; int searchRecordCount; var units = GetTemplates(startIndex: jQueryDataTablesModel.iDisplayStart, pageSize: jQueryDataTablesModel.iDisplayLength, sortedColumns: jQueryDataTablesModel.GetSortedColumns(), totalRecordCount: out totalRecordCount, searchRecordCount: out searchRecordCount, searchString: jQueryDataTablesModel.sSearch); return this.DataTablesJson(items: units, totalRecords: totalRecordCount, totalDisplayRecords: searchRecordCount, sEcho: jQueryDataTablesModel.sEcho); } [HttpPost] public JsonResult GetMasterScenarioCalendar(MasterScenarioFindModel findModel) { if (findModel == null || findModel.ProjectId == Guid.Empty) return null; List actuals; var actualsDetails = new List(); long actualsStartDateMs = 0, actualsEndDateMs = 0; var activeScenarios = DbContext.Scenarios.Where(x => x.Project.ParentProjectId == findModel.ProjectId && (x.Type == (int)ScenarioType.Portfolio && x.Status == (int)ScenarioStatus.Active || findModel.ShowActuals && x.Type == (int)ScenarioType.Actuals)) .ToList(); var startDate = (activeScenarios.Min(x => x.StartDate) ?? DateTime.UtcNow).Date; var endDate = (activeScenarios.Max(x => x.EndDate) ?? startDate.AddYears(1)).Date; var scenarios = activeScenarios.Select(x => x.Id).ToList(); var forecast = activeScenarios.Where(x => x.Type == (int)ScenarioType.Portfolio).Select(x => x.Id).ToList(); var projects = activeScenarios.Select(x => x.ParentId ?? Guid.Empty).Distinct().ToList(); var scenarioDetails = GetScenarioDetailsProxy(null, startDate, endDate, scenarios); var forecastDetails = scenarioDetails.Where(x => forecast.Contains(x.ScenarioId)).ToList(); if (findModel.ShowActuals) { actuals = activeScenarios.Where(x => x.Type == (int)ScenarioType.Actuals).Select(x => x.Id).ToList(); actualsDetails = scenarioDetails.Where(x => actuals.Contains(x.ScenarioId)).ToList(); actualsStartDateMs = (long)(activeScenarios.Where(x => x.Type == (int)ScenarioType.Actuals && x.StartDate.HasValue).Min(x => x.StartDate) ?? Constants.UnixEpochDate).Date.Subtract(Constants.UnixEpochDate).TotalMilliseconds; actualsEndDateMs = (long)(activeScenarios.Where(x => x.Type == (int)ScenarioType.Actuals && x.EndDate.HasValue).Max(x => x.EndDate) ?? Constants.UnixEpochDate).Date.Subtract(Constants.UnixEpochDate).TotalMilliseconds; } var calendar = GetCalendar(forecastDetails, actualsDetails, scenarios, projects, findModel.IsUOMHours, actualsStartDateMs, actualsEndDateMs, findModel.ShowActuals); var model = new MasterScenarioCalendarModel { StartDate = (long)startDate.Subtract(Constants.UnixEpochDate).TotalMilliseconds, EndDate = (long)endDate.Subtract(Constants.UnixEpochDate).TotalMilliseconds, Headers = calendar.Headers, ScenarioCalendar = calendar.Rows }; return Json(model); } // GET: /Scenarios/Details/5 [HttpGet] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult Details(Guid? id, string tab, string backUrl, string backName) { if (id == null || id == Guid.Empty) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); try { if (!SecurityManager.CheckScenarioPermission(id.Value, AccessLevel.Write)) return RedirectToAccessDenied(); var model = new ScenarioManager(DbContext).LoadScenarioDetailModel(id.Value); var state = new WorkFlowManager(DbContext).GetCurrentState(model.Id); model.WorkFlowScheme = ""; if (state != null) { model.WorkFlowState = state.state; model.WorkFlowScheme=new WorkFlowManager(DbContext).getScenarioSchemeName(model.Id); } if (model.Id == Guid.Empty) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); model.ActiveTab = tab; model.BackUrl = !string.IsNullOrEmpty(backUrl) ? backUrl : Url.Action("Index", "Project"); model.BackName = !string.IsNullOrEmpty(backName) ? backName : "list"; model.Projects = Utils.GetProjectsToScenarioCopy(DbContext, model.ParentId, User.Identity.GetUserName()); return View(model); } catch (BLLException blEx) { if (blEx.DisplayError) SetErrorScript(message: blEx.Message); else { LogException(blEx); SetErrorScript(); } } catch (Exception exception) { LogException(exception); SetErrorScript(); } return View(); } [HttpGet] public ActionResult GetScenarioAvailableExpCategories(Guid id) { return Json(GetScenarioCategories(id), JsonRequestBehavior.AllowGet); } [HttpPost] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult CopyTo(ScenarioCopyModel model) { if (Guid.Empty.Equals(model.TargetProjectId) || Guid.Empty.Equals(model.ScenarioId)) ModelState.AddModelError(string.Empty, Messages.Common_InvalidParameters); else { try { var newScenario = new ScenarioManager(DbContext).CopyTo(model.ScenarioId, model.TargetProjectId, model.TargetStatus, model.includeCostSavings); if (newScenario != null) return new SuccessContentJsonResult(newScenario.Id); } catch (BLLException blEx) // handle any system specific error { // display error message if required if (blEx.DisplayError) ModelState.AddModelError(string.Empty, blEx.Message); else // if display not requried then display modal form with general error message { LogException(blEx); ModelState.AddModelError(string.Empty, Messages.Scenario_Copy_Error); } } catch (Exception exception) // handle any unexpected error { LogException(exception); ModelState.AddModelError(string.Empty, Messages.Scenario_Copy_Error); } } return new FailedJsonResult(ModelState); } #region Notes [HttpPost] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult AddNote(NoteModel model) { //if (model.ScenarioId != Guid.Empty && ContentLocker.IsLock("Scenario", model.ScenarioId.ToString(), User.Identity.GetUserName())) // return new HttpStatusCodeResult(HttpStatusCode.BadRequest); model.TrimStringProperties(); if (ModelState.IsValid) { try { using (var noteManager = new NoteManager()) { model.NoteType = NoteType.Scenario.GetHashCode(); noteManager.Save(model); } return new HttpStatusCodeResult(HttpStatusCode.OK); } catch (BLLException blEx) // handle any system specific error { // display error message if required if (blEx.DisplayError) ModelState.AddModelError(string.Empty, blEx.Message); else // if display not requried then display modal form with general error message { LogException(blEx); } } catch (Exception exception) // handle any unexpected error { LogException(exception); } } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [HttpPost] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult EditNote(NoteModel model) { //if (model.ScenarioId != Guid.Empty && ContentLocker.IsLock("Scenario", model.ScenarioId.ToString(), User.Identity.GetUserName())) // return new HttpStatusCodeResult(HttpStatusCode.BadRequest); model.TrimStringProperties(); if (ModelState.IsValid) { try { using (var noteManager = new NoteManager()) { var note = noteManager.Load(model.Id, false); if (note == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); model.NoteType = NoteType.Scenario.GetHashCode(); noteManager.Save(model); } return new HttpStatusCodeResult(HttpStatusCode.OK); } catch (BLLException blEx) // handle any system specific error { // display error message if required if (blEx.DisplayError) ModelState.AddModelError(string.Empty, blEx.Message); else // if display not requried then display modal form with general error message { LogException(blEx); } } catch (Exception exception) // handle any unexpected error { LogException(exception); } } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [HttpPost] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult DeleteNote(Guid id) { if (id == Guid.Empty) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); try { using (var noteManager = new NoteManager()) { var note = noteManager.Load(id); if (note == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } noteManager.Delete(id); } return new HttpStatusCodeResult(HttpStatusCode.OK); } catch (BLLException blEx) // handle any system specific error { // display error message if required if (blEx.DisplayError) ModelState.AddModelError(string.Empty, blEx.Message); else // if display not requried then display modal form with general error message { LogException(blEx); } } catch (Exception exception) // handle any unexpected error { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } // GET: /User/Edit/5 [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult EditNote(Guid id) { if (id == Guid.Empty) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); Note note; using (var noteManager = new NoteManager()) { note = noteManager.Load(id); } if (note == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); return PartialView("_addNote", (NoteModel)note); } // GET: /User/Edit/5 [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult AddNote(Guid id) { if (id == Guid.Empty) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } return PartialView("_addNote", new NoteModel(id)); } [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Read)] public ActionResult LoadNotes(Guid scenarioId) { try { List notesModel; using (var noteManager = new NoteManager()) { notesModel = noteManager.GetNotes(null, scenarioId, NoteType.Scenario.GetHashCode(), false); } return PartialView("_notesList", notesModel); } catch (BLLException blEx) // handle any system specific error { // display error message if required if (blEx.DisplayError) ModelState.AddModelError(string.Empty, blEx.Message); else // if display not requried then display modal form with general error message { LogException(blEx); } } catch (Exception exception) // handle any unexpected error { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } #endregion #region Rates [HttpPost] public ActionResult GetRates(Guid scenarioId, bool? uomMode) { List rates; using (var expCatManager = new ExpenditureCategoryManager()) using (var uomManger = new UOMManager()) using (var rateManager = new RateManager()) using (var scenarioManager = new ScenarioManager()) { // temporary solution, action method will be removed var allExpCats = expCatManager.GetAsDictionary(); var allUoms = uomManger.GetAsDictionary(); if (!uomMode.HasValue) { var user = new UsersCache().Value.FirstOrDefault(x => x.Id == new Guid(HttpContext.User.Identity.GetID())); if (user != null) uomMode = !user.PreferredResourceAllocation; } var ratesDictionary = rateManager.GetRates(scenarioId); rates = scenarioManager.GetRatesModel(ratesDictionary); rates.ForEach(expCatData => { foreach (var rate in expCatData.rateValues) rate.rateValue = Utils.GetUOMMultiplier(allExpCats, allUoms, expCatData.expCatId, uomMode); }); } return Json(rates); } [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult EditRate(Guid? id, Guid? parentId, Guid expentureCategoryId) { RateModel model; if (id.HasValue && id != Guid.Empty) { using (var manager = new RateManager()) { model = (RateModel)manager.Load(id); } } else { Scenario scenario; using (var sManager = new ScenarioManager()) { scenario = sManager.Load(parentId); } model = new RateModel {Type = RateModel.RateType.Derived}; List lRates; using (var rateManager = new RateManager()) { lRates = rateManager.DataTable.Where(c => c.ExpenditureCategoryId == expentureCategoryId && c.Type == (int) RateModel.RateType.Derived && c.ParentId == parentId).ToList(); } if (lRates.Count == 0) { model.StartDate = scenario.StartDate.Value.Date; model.EndDate = scenario.EndDate.Value.Date; } else { var maxRateEndDate = lRates.Max(t => t.EndDate); if (maxRateEndDate < DateTime.MaxValue.AddYears(-4)) { model.StartDate = maxRateEndDate.AddDays(1); model.EndDate = scenario.EndDate.Value.Date <= model.StartDate ? model.StartDate.AddYears(1) : scenario.EndDate.Value.Date; } else { model.StartDate = maxRateEndDate; model.EndDate = model.StartDate.AddYears(1); } } } if ((!model.ParentId.HasValue || model.ParentId == Guid.Empty) && parentId.HasValue) model.ParentId = parentId; if (model.ExpenditureCategoryId == Guid.Empty) model.ExpenditureCategoryId = expentureCategoryId; return PartialView("_editRate", model); } [HttpPost] [ValidateAjax] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult EditRate(RateModel model) { if (model.Id != Guid.Empty && ContentLocker.IsLock("Rate", model.Id.ToString(), User.Identity.GetUserName())) { ModelState.AddModelError(string.Empty, Messages.Rate_UpdatedByAnotherUser); return new FailedJsonResult(ModelState); } try { using (var manager = new RateManager()) { var userid = Guid.Parse(HttpContext.User.Identity.GetID()); manager.Save(model, userid); } return new SuccessJsonResult(); } catch (BLLException blEx) // handle any system specific error { // display error message if required if (blEx.DisplayError) ModelState.AddModelError(string.Empty, blEx.Message); else // if display not requried then display modal form with general error message { LogException(blEx); ModelState.AddModelError(string.Empty, Messages.Rate_Save_Error); } } catch (Exception exception) { LogException(exception); ModelState.AddModelError(string.Empty, Messages.Rate_Save_Error); } if (model.Id != Guid.Empty) ContentLocker.RemoveLock("Rate", model.Id.ToString(), User.Identity.GetUserName()); return new FailedJsonResult(ModelState); } [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult DeleteRate(Guid? id) { RateModel model; if (id.HasValue && id != Guid.Empty) { using (var manager = new RateManager()) { model = (RateModel)manager.Load(id); } } else model = new RateModel(); return PartialView("_deleteRate", model); } [HttpPost] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult DeleteRate(RateModel model) { if (model == null || model.Id.Equals(Guid.Empty)) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); if (ContentLocker.IsLock("Rate", model.Id.ToString(), User.Identity.GetUserName())) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); try { using (var manager = new RateManager()) { var dbObj = manager.Load(model.Id, false); if (dbObj != null) { var userid = Guid.Parse(HttpContext.User.Identity.GetID()); manager.Delete(dbObj, userid); } } ContentLocker.RemoveLock("Rate", model.Id.ToString(), User.Identity.GetUserName()); return new HttpStatusCodeResult(HttpStatusCode.OK); } catch (Exception exception) { LogException(exception); } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [HttpPost] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult ProcessRates(RatesModel model) { try { model.TrimStringProperties(); switch (model.Mode) { case RatesModel.FormMode.ListRates: #region List Rates model.EditRateId = Guid.Empty; model.DeleteRateId = Guid.Empty; using (var ratesManager = new RateManager()) { if (!Guid.Empty.Equals(model.ExpenditureCategoryId)) { var gRates = ratesManager.GetRates(model.ExpenditureCategoryId, RateModel.RateType.Global); model.AddGlobalRange(gRates); } if (Guid.Empty != model.ScenarioId && !Guid.Empty.Equals(model.ExpenditureCategoryId)) { var lRates = ratesManager.GetRates(model.ExpenditureCategoryId, RateModel.RateType.Derived, model.ScenarioId); model.AddLocalRange(lRates.ToList()); } } #endregion break; case RatesModel.FormMode.DeriveRate: #region Derive rate from global using (var sManager = new ScenarioManager()) using (var ratesManager = new RateManager()) { var scenario = sManager.Load(model.ScenarioId); var scenarioStartDate = scenario.StartDate?.Date; var scenarioEndDate = scenario.EndDate?.Date; if (!Guid.Empty.Equals(model.ExpenditureCategoryId) && !Guid.Empty.Equals(model.ScenarioId)) { var rates = ratesManager.GetRates(model.ExpenditureCategoryId, RateModel.RateType.Global, null, scenarioStartDate, scenarioEndDate); if (rates.Count == 0) model.ErrorMessage = Messages.Scenario_Process_DeriveRate_Error; foreach (var rate in rates) { var derivedrates = ratesManager.GetRates(model.ExpenditureCategoryId, null, model.ScenarioId, derivedId: rate.Id); ratesManager.Save(new RateModel { Id = Guid.NewGuid(), Rate1 = rate.Rate1, StartDate = rate.StartDate < scenario.StartDate ? scenario.StartDate.Value.Date : rate.StartDate, EndDate = rate.EndDate > scenario.EndDate ? scenario.EndDate.Value.Date : rate.EndDate, ExpenditureCategoryId = rate.ExpenditureCategoryId, FreezeRate = rate.FreezeRate, ParentId = model.ScenarioId, Type = RateModel.RateType.Derived, DerivedObjectId = rate.Id }); ratesManager.DeleteRange(derivedrates); } } model.Mode = RatesModel.FormMode.ListRates; // reset action mode model.EditRateId = Guid.Empty; model.DeleteRateId = Guid.Empty; if (!Guid.Empty.Equals(model.ExpenditureCategoryId)) { var gRates = ratesManager.GetRates(model.ExpenditureCategoryId, RateModel.RateType.Global); model.AddGlobalRange(gRates); } if (Guid.Empty != model.ScenarioId && !Guid.Empty.Equals(model.ExpenditureCategoryId)) { var lRates = ratesManager.GetRates(model.ExpenditureCategoryId, RateModel.RateType.Derived, model.ScenarioId); model.AddLocalRange(lRates.ToList()); } } #endregion break; } return PartialView("_rates", model); } catch (Exception exception) { LogException(exception); } if (ModelState.IsValid && Guid.Empty != model.EditRateId) ContentLocker.RemoveLock("ExpenditureCategoryRate", model.EditRateId.ToString(), User.Identity.GetUserName()); return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [HttpPost] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult DeleteAllLocalRates(Guid? scenarioId, Guid? expenditureCategoryId) { if (!scenarioId.HasValue || scenarioId == Guid.Empty || !expenditureCategoryId.HasValue || expenditureCategoryId == Guid.Empty) return Json(new { Status = "Error", Msg = Messages.Rate_Parameter_scenarioIdOrExpenditureCatetoryId_CantBeEmpty }); if (ContentLocker.IsLock("Scenario", scenarioId.ToString(), User.Identity.GetUserName())) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); try { using (var rateManager = new RateManager()) { var ratesForDelete = rateManager.GetRates(expenditureCategoryId.Value, RateModel.RateType.Derived, scenarioId); rateManager.DeleteRange(ratesForDelete); } ContentLocker.RemoveLock("Scenario", scenarioId.ToString(), User.Identity.GetUserName()); return Json(new { Status = "Ok" }); } catch (Exception exception) { LogException(exception); return Json(new { Status = "Error", Msg = exception.Message }); } } #endregion [HttpGet] public JsonResult GetTeamCapacityScenarioId(Guid teamId) { using (var context = new EnVisageEntities()) { var data = context.Teams.Where(sd => sd.Id == teamId).Select(sd => sd.PlannedCapacityScenarioId).FirstOrDefault(); if (data == null) { var teamManager = new TeamManager(DbContext); var team = teamManager.Load(teamId, false); var scen = new Scenario { Name = team.Name.Trim() + " Planned Capacity", Type = (int) ScenarioType.TeamPlannedCapacity, Id = Guid.NewGuid() }; team.PlannedCapacityScenarioId = scen.Id; DbContext.Scenarios.Add(scen); DbContext.SaveChanges(); data = scen.Id; } return Json(data.ToString(), JsonRequestBehavior.AllowGet); } } [HttpGet] public void ToggleTemplateStatus() { string scenarioId = Request.QueryString["scenarioId"]; var guidId = new Guid(scenarioId); var context = new EnVisageEntities(); var scenario = (from c in context.Scenarios where c.Id == guidId select c).FirstOrDefault(); if (scenario != null && scenario.Status != (int)ScenarioStatus.Active) { scenario.Status = (int?)ScenarioStatus.Active; } else if (scenario != null) scenario.Status = (int)ScenarioStatus.Inactive; context.SaveChanges(); } // GET: /Scenario/Delete/5 [HttpGet] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult Delete(Guid? id, string backUrl = null) { if (id == null || id == Guid.Empty) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); // SA. ENV-755 if (!SecurityManager.CheckScenarioPermission(id.Value, AccessLevel.Write)) return RedirectToAccessDenied(); var model = new ScenarioModel(); try { var manager = new ScenarioManager(DbContext); model = (ScenarioModel)manager.Load(id); if (model == null) return HttpNotFound(); } catch (BLLException blEx) { if (blEx.DisplayError) SetErrorScript(message: blEx.Message); else { LogException(blEx); SetErrorScript(); } } catch (Exception exception) { LogException(exception); SetErrorScript(); } return View(model); } // POST: /Scenario/Delete/5 [HttpPost] [ValidateAntiForgeryToken] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult Delete(ScenarioModel model) { if (ContentLocker.IsLock("Scenario", model.Id.ToString(), User.Identity.GetUserName())) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); var userId = SecurityManager.GetUserPrincipal().ToString(); using (DbContextTransaction tran = DbContext.Database.BeginTransaction()) { var transactionId = DbContext.GetClientConnectionId(); try { var manager = new ScenarioManager(DbContext); manager.Delete(model, transactionId, userId); DbContext.SaveChanges(); tran.Commit(); Task.Run(() => AuditProxy.CommitHistoryChanges(transactionId, userId)); Task.Run(() => AuditProxy.CommitEventChanges(transactionId)); ContentLocker.RemoveLock("Scenario", model.Id.ToString(), User.Identity.GetUserName()); } catch (Exception ex) { tran.Rollback(); AuditProxy.ClearHistoryChanges(transactionId); AuditProxy.ClearEventChanges(transactionId); LogException(ex); ModelState.AddModelError(string.Empty, Messages.ScenarioDelete_Error); return View(model); } } if (Request["backUrl"] != null) { if (Url.IsLocalUrl(Request["backUrl"])) return Redirect(Request["backUrl"]); return RedirectToAction("Index", "Project"); } return RedirectToAction("Index", "Project"); } [HttpPost] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult CreateTemplate(ScenarioTemplateCreationModel model, Guid[] expCatId, string[] expCatName, string[] expCatGroup, bool[] templateExpCatChecked) { if (ContentLocker.IsLock("Scenario", model.Id.ToString(), User.Identity.GetUserName())) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); model.TrimStringProperties(); if (expCatId != null && expCatName != null && expCatGroup != null && templateExpCatChecked != null && expCatId.Length == expCatName.Length && expCatId.Length == expCatGroup.Length && expCatId.Length == templateExpCatChecked.Length) { model.Expenditures = new ScenarioModel.ExpenditureItem[expCatId.Length]; for (var i = 0; i < expCatId.Length; i++) { model.Expenditures[i] = new ScenarioModel.ExpenditureItem { Id = expCatId[i], Group = expCatGroup[i], Name = expCatName[i], Checked = templateExpCatChecked[i] }; } } var expCatIds = (from e in model.Expenditures where e.Checked select e.Id).ToArray(); try { // get scenario by id var manager = new ScenarioManager(DbContext); var scenario = manager.Load(model.Id); if (ModelState.IsValid) { var newTemplate = DbContext.Scenarios.Create(); newTemplate.Id = Guid.NewGuid(); newTemplate.Name = model.TemplateName; newTemplate.ParentId = null; newTemplate.TemplateId = null; newTemplate.Type = (int)ScenarioType.Template; newTemplate.ProjectedRevenue = 0; newTemplate.ExpectedGrossMargin = 0; newTemplate.CalculatedGrossMargin = 0; newTemplate.CGSplit = scenario.CGSplit; newTemplate.EFXSplit = scenario.EFXSplit; newTemplate.StartDate = null; newTemplate.EndDate = null; newTemplate.Duration = scenario.Duration; newTemplate.TDDirectCosts = 0; newTemplate.BUDirectCosts = 0; newTemplate.Shots = scenario.Shots; newTemplate.TDRevenueShot = 0; newTemplate.BURevenueShot = 0; newTemplate.FreezeRevenue = false; newTemplate.Color = null; newTemplate.Status = (int)ScenarioStatus.Active; newTemplate.UseLMMargin = scenario.UseLMMargin; newTemplate.ExpectedGrossMargin_LM = scenario.ExpectedGrossMargin_LM; newTemplate.CalculatedGrossMargin_LM = scenario.CalculatedGrossMargin_LM; newTemplate.TDDirectCosts_LM = 0; newTemplate.BUDirectCosts_LM = 0; newTemplate.BURevenueShot_LM = 0; newTemplate.ShotStartDate = null; newTemplate.GrowthScenario = scenario.GrowthScenario; newTemplate.Actuals_BUDirectCosts = 0; newTemplate.Actuals_BUDirectCosts_LM = 0; newTemplate.SystemAttributeObjectID = scenario.SystemAttributeObjectID; DbContext.Entry(newTemplate).State = EntityState.Added; if (!string.IsNullOrEmpty(model.TemplateGroupNames)) { var groups = model.TemplateGroupNames.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var savedGroups = DbContext.TemplateGroups.Where(x => groups.Contains(x.Name)).ToList(); foreach (var item in groups) { var group = savedGroups.FirstOrDefault(tg => tg.Name.Equals(item)); if (group == null) { group = new TemplateGroup { Id = Guid.NewGuid(), Name = item }; DbContext.TemplateGroups.Add(group); } DbContext.Template2TemplateGroup.Add(new Template2TemplateGroup { Id = Guid.NewGuid(), TemplateId = newTemplate.Id, TemplateGroupId = group.Id }); } } var sds = DbContext.ScenarioDetail.Where(s => s.ParentID == scenario.Id && expCatIds.Contains(s.ExpenditureCategoryId.Value)).ToList(); foreach (var scenarioDetails in sds) { DbContext.ScenarioDetail.Add(new ScenarioDetail { Id = Guid.NewGuid(), ParentID = newTemplate.Id, Cost = scenarioDetails.Cost, ExpenditureCategoryId = scenarioDetails.ExpenditureCategoryId, WeekEndingDate = null, Quantity = scenarioDetails.Quantity, WeekOrdinal = scenarioDetails.WeekOrdinal }); } DbContext.SaveChanges(); return Json(new { Status = "Ok" }); } } catch (BLLException blEx) { if (blEx.DisplayError) SetErrorScript(message: blEx.Message); else { LogException(blEx); SetErrorScript(); } } catch (Exception exception) { LogException(exception); SetErrorScript(); ModelState.AddModelError("errors", @"Error on adjust scenario."); } ContentLocker.RemoveLock("Scenario", model.Id.ToString(), User.Identity.GetUserName()); return PartialView("_createTemplateModal", model); } [HttpPost] public ActionResult RecalculateCalendar(ScenarioDetailSnapshotRecalculationModel recalculationModel) { if (recalculationModel == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); var scenarioManager = new ScenarioManager(DbContext); var ratesManager = new RateManager(DbContext); var projectManager = new ProjectManager(DbContext); //8.24.16 start tab DateTime? filterStartDate = null; DateTime? filterEndDate = null; if (recalculationModel.filterStartDate > 0 && recalculationModel.filterEndDate > 0) { filterStartDate = Utils.ConvertFromUnixDate(recalculationModel.filterStartDate); filterEndDate = Utils.ConvertFromUnixDate(recalculationModel.filterEndDate); } //8.24.16 end var model = new ScenarioDetailSnapshotModel() { Scenario = recalculationModel.Scenario, AvailableExpenditures = recalculationModel.AvailableExpenditures, TeamsInScenario = recalculationModel.TeamsInScenario, Rates = recalculationModel.Rates, NeedToRebind = recalculationModel.NeedToRebind, NeedToRecalculateScenarioDetails = recalculationModel.NeedToRecalculateScenarioDetails, LocalRatesLoaded = recalculationModel.LocalRatesLoaded, Calendar = new ScenarioCalendarModel { Expenditures = recalculationModel.Calendar != null ? recalculationModel.Calendar.Expenditures : new Dictionary() }, WorkFlowSchema = null }; #if DEBUG var watch1 = new System.Diagnostics.Stopwatch(); watch1.Start(); #endif if (model.NeedToRebind) { var periodStartDate = new DateTime(DateTime.Today.Year, 1, 1); var periodEndDate = new DateTime(DateTime.Today.Year, 12, 31); if (model.Scenario.Type != ScenarioType.LoanOut && model.Scenario.Type != ScenarioType.Training && model.Scenario.Type != ScenarioType.Vacation) { periodStartDate = Utils.ConvertFromUnixDate(model.Scenario.StartDate); periodEndDate = Utils.ConvertFromUnixDate(model.Scenario.EndDate); } // if scenario was inited with forecast data var forecast = model.Calendar.GetScenarioDetails(true); var scenarioDetailsPreLoaded = !(forecast == null || forecast.Count <= 0); var actuals = new List(); if (model.Scenario.ParentId.HasValue) { var project = projectManager.Load(model.Scenario.ParentId); if (project != null) { model.Scenario.ProjectDeadline = project.Deadline.HasValue ? Utils.ConvertToUnixDate(project.Deadline.Value) : (long?)null; model.Scenario.YellowIndicator = project.PerformanceYellowThreshold; if (!model.Scenario.YellowIndicator.HasValue) model.Scenario.YellowIndicator = project.Type.PerformanceYellowThreshold; model.Scenario.RedIndicator = project.PerformanceRedThreshold; if (!model.Scenario.RedIndicator.HasValue) model.Scenario.RedIndicator = project.Type.PerformanceRedThreshold; } if (!model.Scenario.GrowthScenario && !ScenarioType.Actuals.Equals(model.Scenario.Type)) { var actualScenario = scenarioManager.DataTable.AsNoTracking().FirstOrDefault(t => t.ParentId == model.Scenario.ParentId && t.Type == (int?)ScenarioType.Actuals); if (actualScenario != null) { var actualScenarioId = actualScenario.Id; model.Scenario.ActualStartDate = (long)(actualScenario.StartDate ?? Constants.UnixEpochDate).Date.Subtract(Constants.UnixEpochDate).TotalMilliseconds; model.Scenario.ActualEndDate = (long)(actualScenario.EndDate ?? Constants.UnixEpochDate).Date.Subtract(Constants.UnixEpochDate).TotalMilliseconds; if (actualScenario.StartDate.HasValue && actualScenario.StartDate.Value.Date < Constants.UnixEpochDate.AddMilliseconds(model.Scenario.StartDate)) periodStartDate = (actualScenario.StartDate ?? Constants.UnixEpochDate).Date; if (actualScenario.EndDate.HasValue && actualScenario.EndDate.Value.Date > Constants.UnixEpochDate.AddMilliseconds(model.Scenario.EndDate)) periodEndDate = (actualScenario.EndDate ?? Constants.UnixEpochDate).Date; if (!Guid.Empty.Equals(actualScenarioId)) { // get the last week even if scenario ends in the middle of the week. E.g. Scenario.EndDate=12/31 and week.EndDate=1/1 actuals = scenarioManager.GetScenarioProxyDetails(null, periodStartDate, periodEndDate.AddDays(6), new List { actualScenarioId }); } } } } if (model.Scenario.Id.HasValue && !Guid.Empty.Equals(model.Scenario.Id) && !scenarioDetailsPreLoaded) { // get the last week even if scenario ends in the middle of the week. E.g. Scenario.EndDate=12/31 and week.EndDate=1/1 forecast = scenarioManager.GetScenarioProxyDetails(null, periodStartDate, periodEndDate.AddDays(6), new List { model.Scenario.Id.Value }); model.AvailableExpenditures = GetScenarioCategories(model.Scenario.Id.Value); } if (!model.NeedToRecalculateScenarioDetails) { var oldExpenditureTeams = scenarioDetailsPreLoaded ? model.Calendar.GetTeams() : null; if (forecast != null && forecast.Count > 0) { // if scenario was inited with scenario details we do not need to refresh teams info on this step model.Calendar = BuildScenarioDetailsCalendarData(forecast, actuals, null); } else { // if there are no scenario details (e.g. BU scenario or incorrect data) we need to display data according to financial calendar var startDate = Utils.ConvertFromUnixDate(model.Scenario.StartDate); var endDate = Utils.ConvertFromUnixDate(model.Scenario.EndDate); var tempEndDate = endDate.AddDays(6); var fiscalCalendars = (new FiscalCalendarManager(DbContext)).GetFiscalCalendar(FiscalCalendarModel.FiscalYearType.Week, startDate, tempEndDate, true, false, false); model.Calendar = BuildScenarioDetailsCalendarData(forecast, actuals, fiscalCalendars); } if (model.Calendar?.Expenditures != null) { // if there is no preloaded data we should recalculate team information for scenario if (!scenarioDetailsPreLoaded) { //8.24.16 start tab //var teamsFromExpenditures = scenarioManager.FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, model.Scenario.ParentId ?? Guid.Empty, model.Calendar.Expenditures, HttpContext.User.Identity.GetID(), false); var teamsFromExpenditures = scenarioManager.FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, model.Scenario.ParentId ?? Guid.Empty, model.Calendar.Expenditures, HttpContext.User.Identity.GetID(), false, null, filterStartDate, filterEndDate); //8.24.16 end if (model.TeamsInScenario == null) model.TeamsInScenario = new List(); foreach (var teamInScenario in teamsFromExpenditures) { if (model.TeamsInScenario.Any(x => x.TeamId == teamInScenario.TeamId)) model.TeamsInScenario.RemoveAll(x => x.TeamId == teamInScenario.TeamId); model.TeamsInScenario.Add(teamInScenario); } } else { // otherwise we should fill expenditures with preloaded teams if (oldExpenditureTeams != null && oldExpenditureTeams.Count > 0) { foreach (var expCatId in model.Calendar.Expenditures.Keys) { if (oldExpenditureTeams.ContainsKey(expCatId)) model.Calendar.Expenditures[expCatId].Teams = oldExpenditureTeams[expCatId]; } } } } } var scenarioTeamIds = model.TeamsInScenario.Select(x => x.TeamId).ToList(); var id = model.Scenario.Id ?? model.Scenario.TemplateId; if (!model.LocalRatesLoaded) { var rates = ratesManager.GetRates(id); model.Rates = scenarioManager.GetRatesModel(rates); } else { var localRates = scenarioManager.GetRatesFromModel(model.Rates); var globalRates = DbContext.Rates.Where(x => x.Type == (short)RateModel.RateType.Global).ToList(); var rates = ratesManager.MergeRates(globalRates, localRates); model.Rates = scenarioManager.GetRatesModel(rates); } model.Expenditures2Add = GetAllCategories(scenarioTeamIds); } #if DEBUG watch1.Stop(); #endif #if DEBUG watch1 = new System.Diagnostics.Stopwatch(); watch1.Start(); #endif if (model.NeedToRecalculateScenarioDetails) { var rates = model.Rates.ToDictionary(rateModel => rateModel.expCatId, rateModel => rateModel.rateValues.ToDictionary( rateValue => Utils.ConvertFromUnixDate(rateValue.weekEndDate), rateValue => rateValue.rateValue)); var forecast = model.Calendar.GetScenarioDetails(true); var actuals = model.Calendar.GetScenarioDetails(false); var oldExpenditureTeams = model.Calendar.GetTeams(); var oldExpenditures = model.Calendar.Expenditures; var startDate = Utils.ConvertFromUnixDate(model.Scenario.StartDate); var endDate = Utils.ConvertFromUnixDate(model.Scenario.EndDate); var tempEndDate = endDate.AddDays(6); var fiscalCalendars = (new FiscalCalendarManager(DbContext)).GetFiscalCalendar(FiscalCalendarModel.FiscalYearType.Week, startDate, tempEndDate, true, false, false); bool isUpdate; int currentPeriods; var scenarioDetails = scenarioManager.PrepareScenarioDetails(model.Scenario, model.AvailableExpenditures, actuals, rates, forecast, fiscalCalendars, out isUpdate, out currentPeriods); forecast = scenarioDetails.Values.SelectMany(p => p.Select(entity => new VW_ScenarioAndProxyDetails { Id = entity.Id, ExpenditureCategoryId = entity.ExpenditureCategoryId, ExpenditureCategoryName = string.Empty, ExpCategoryWithCcName = entity.ExpenditureCategoryName, // SA. ENV-839 WeekEndingDate = entity.WeekEndingDate, WeekOrdinal = entity.WeekOrdinal, Quantity = entity.Quantity, Cost = entity.DetailCost, Type = entity.CategoryType.GetHashCode(), UseType = entity.UseType.GetHashCode(), CGEFX = entity.CG_EFX.ToString(), GLId = entity.GlId ?? Guid.Empty, UOMId = entity.UOMId ?? Guid.Empty, CreditId = entity.CreditId ?? Guid.Empty, SystemAttributeOne = entity.SysField1, SystemAttributeTwo = entity.SysField2, SortOrder = entity.SortOrder, AllowResourceAssignment = entity.ExpenditureCatagoryAllowsResource })).ToList(); model.Calendar = BuildScenarioDetailsCalendarData(forecast, actuals, fiscalCalendars); model.AvailableExpenditures = forecast.Where(x => x.ExpenditureCategoryId.HasValue) .Select(x => new ExpenditureModel { Id = x.ExpenditureCategoryId.Value, Name = x.ExpCategoryWithCcName // SA. ENV-839 }).Distinct().ToList(); if (model.Calendar?.Expenditures != null) { //8.24.16 start tab var teamInfoLoaded = false; //8.24.16 end #region Filling information about all teams for existence/new ECs var newExpCats = new Dictionary(); foreach (var expCat in model.Calendar.Expenditures) { if (!oldExpenditures.ContainsKey(expCat.Key)) newExpCats.Add(expCat.Key, expCat.Value); else { // all existence categories should be filled from snapshot data if (oldExpenditureTeams.ContainsKey(expCat.Key)) expCat.Value.Teams = scenarioManager.PrepareScenarioTeams(Guid.Parse(expCat.Key), model.Scenario, fiscalCalendars, oldExpenditureTeams[expCat.Key], isUpdate, currentPeriods); expCat.Value.Collapsed = oldExpenditures[expCat.Key].Collapsed; expCat.Value.CollapsedClass = oldExpenditures[expCat.Key].CollapsedClass; } } // need to recalculate team information only for new ECs if (newExpCats != null && newExpCats.Count > 0) { var teams = model.TeamsInScenario?.ToDictionary(x => x.TeamId, g => g.Allocation); //8.24.16 start tab //var newTeams = scenarioManager.FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, model.Scenario.ParentId ?? Guid.Empty, newExpCats, HttpContext.User.Identity.GetID(), model.Scenario.Id != Guid.Empty, teams); var newTeams = scenarioManager.FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, model.Scenario.ParentId ?? Guid.Empty, newExpCats, HttpContext.User.Identity.GetID(), model.Scenario.Id != Guid.Empty, teams, filterStartDate, filterEndDate); teamInfoLoaded = true; //8.24.16 end if (newTeams != null && newTeams.Count > 0) { model.TeamsInScenario.RemoveAll(x => newTeams.Any(s => s.TeamId == x.TeamId)); model.TeamsInScenario.AddRange(newTeams); } } #endregion #region Filling information about new only teams for all categories var teams2Add = model.TeamsInScenario?.Where(x => x.IsNew).ToDictionary(x => x.TeamId, g => g.Allocation); if (teams2Add != null && teams2Add.Count > 0) { //8.24.16 start tab //var newTeams = scenarioManager.FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, model.Scenario.ParentId ?? Guid.Empty, model.Calendar.Expenditures, HttpContext.User.Identity.GetID(), model.Scenario.Id != Guid.Empty, teams2Add); var newTeams = scenarioManager.FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, model.Scenario.ParentId ?? Guid.Empty, model.Calendar.Expenditures, HttpContext.User.Identity.GetID(), model.Scenario.Id != Guid.Empty, teams2Add, filterStartDate, filterEndDate); teamInfoLoaded = true; //8.24.16 end if (newTeams != null && newTeams.Count > 0) { model.TeamsInScenario.RemoveAll(x => newTeams.Any(s => s.TeamId == x.TeamId)); model.TeamsInScenario.AddRange(newTeams); } } //8.24.16 start tab if (filterStartDate.HasValue && filterEndDate.HasValue && !teamInfoLoaded) { var s = scenarioManager.Load(model.Scenario.Id, true); if (null != s) if (s.StartDate != filterStartDate.Value || s.EndDate != filterEndDate) { var teamsFromExpenditures = scenarioManager.FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, model.Scenario.ParentId ?? Guid.Empty, model.Calendar.Expenditures, HttpContext.User.Identity.GetID(), false, null, filterStartDate, filterEndDate); if (model.TeamsInScenario == null) model.TeamsInScenario = new List(); else foreach (var teamInScenario in teamsFromExpenditures) { if (model.TeamsInScenario.Any(x => x.TeamId == teamInScenario.TeamId)) model.TeamsInScenario.RemoveAll(x => x.TeamId == teamInScenario.TeamId); model.TeamsInScenario.Add(teamInScenario); } } } //8.24.16 end #endregion } // after recalculation we need to save all data foreach (var expCat in model.Calendar.Expenditures) foreach (var detail in expCat.Value.Details) detail.Value.Changed = true; model.NeedToRecalculateScenarioDetails = false; } #if DEBUG watch1.Stop(); #endif #if DEBUG watch1 = new System.Diagnostics.Stopwatch(); watch1.Start(); #endif if ((model.Scenario.ProjectedRevenue ?? 0) == 0 || (model.Scenario.GrossMargin ?? 0) == 0 || (model.Scenario.LMMargin ?? 0) == 0) { var project = (new ProjectManager(DbContext)).Load(model.Scenario.ParentId); var isRevenueGenerating = project != null && project.IsRevenueGenerating; var totalBtUpCosts = model.Calendar.Expenditures.Sum(t => t.Value.Details.Sum(dt => dt.Value.ForecastCost)) ?? 0; var totalBtUpCostsLM = model.Calendar.Expenditures.Where(t => t.Value.Type == (int)ExpenditureCategoryModel.CategoryTypes.Labor || t.Value.Type == (int)ExpenditureCategoryModel.CategoryTypes.Materials).Sum(t => t.Value.Details.Sum(dt => dt.Value.ForecastCost)) ?? 0; ScenarioManager.FeelEmptyMarginOrRevenue(model.Scenario, isRevenueGenerating, totalBtUpCosts, totalBtUpCostsLM); } try { if (model.Scenario.Id.HasValue) { var userid = SecurityManager.GetUserPrincipal(); WorkFlowManager wfman = new WorkFlowManager(null); model.WorkFlowSchema = wfman.getScenarioSchemeName(model.Scenario.Id.Value); model.WorkFlowActions = wfman.GetWorkFlowCommands(model.Scenario.Id.Value, userid); model.WorkFlowStates = wfman.GetWorkFlowStates(model.WorkFlowSchema); } } catch { } #if DEBUG watch1.Stop(); #endif return BigJson(model); } [HttpPost] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult SaveSnapshotChanges(ScenarioDetailsSnapshotSaveModel model) { if (ModelState.IsValid) { using (DbContextTransaction transaction = DbContext.Database.BeginTransaction()) { var transactionId = DbContext.GetClientConnectionId(); var userId = SecurityManager.GetUserPrincipal(); try { var teamsInProject = new List(); Guid scenarioId; using (var scenarioManager = new ScenarioManager(DbContext)) using (var projectManager = new ProjectManager(DbContext)) using (var noteMan = new NoteManager(DbContext)) using(var udfMan = new UDFManager(DbContext)) { if (model.Scenario.ParentId.HasValue) teamsInProject = projectManager.getTeamsAssingedToProject(model.Scenario.ParentId.Value); scenarioId = scenarioManager.Save(model, userId.ToString()); if (model.Notes == null) model.Notes = new List(); foreach (var n in model.Notes) { n.DomainId = scenarioId; noteMan.Save(n); } if (model.NotesToDelete == null) model.NotesToDelete = new List(); foreach (var n in model.NotesToDelete) { noteMan.Delete(n.Id); } #region UDFS if (model.Scenario.UserDefinedFields == null) model.Scenario.UserDefinedFields = new List(); foreach (var udf in model.Scenario.UserDefinedFields) { if (udf.Values != null) udfMan.saveValue(udf.Values, udf.Id, scenarioId, userId); } #endregion DbContext.SaveChanges(); } List updatedTeams = new List(); if (model.Calendar?.Expenditures != null) { foreach (var ec in model.Calendar.Expenditures) { var teams = ec.Value.Teams.Where(x => x.Value.Changed).Select(x => x.Value.Id).ToList().Distinct(); foreach (var t in teams) { if (updatedTeams.Contains(t)) continue; updatedTeams.Add(t); } } } transaction.Commit(); Task.Run(() => AuditProxy.CommitHistoryChanges(transactionId, userId.ToString())); Task.Run(() => AuditProxy.CommitEventChanges(transactionId)); // need to refresh project access permissions after teams was changed (new ProjectAccessCache()).Invalidate(); //do workflow actions/create workflow if it doesnt exist and we have a scheme id var wfMan = new WorkFlowManager(DbContext); if (scenarioId != Guid.Empty && wfMan.getScenarioSchemeName(scenarioId) != model.WorkFlowSchema && wfMan.isProcessExists(scenarioId)) { wfMan.RemoveFormWorkFlow(scenarioId); model.WorkFlowActions = new List(); } if (scenarioId != Guid.Empty && !string.IsNullOrEmpty(model.WorkFlowSchema) && model.Scenario.SaveAs == SaveAsScenario.Update) wfMan.DoWorkFlowAction(model.WorkFlowActions, scenarioId, userId, model.WorkFlowSchema, updatedTeams); else if (scenarioId != Guid.Empty && !string.IsNullOrEmpty(model.WorkFlowSchema) && model.Scenario.SaveAs == SaveAsScenario.New) wfMan.SetWorkFlowState(model.WorkFlowStates, scenarioId, userId, model.WorkFlowSchema); //send notification out for new teams var s = wfMan.GetCurrentState(scenarioId); string wfstate = null; if (!string.IsNullOrEmpty(s?.state)) wfstate = s.state; var who = new List(); var teamadds = model.TeamsInScenario.Where(x => !teamsInProject.Contains(x.TeamId)).Select(x => x.TeamId).ToList(); List addedTeams = new List(); foreach (var t in teamadds) { if (addedTeams.Contains(t)) continue; addedTeams.Add(t); var who2 = wfMan.GetContactTeamDetails(t, WorkFlowContactNotificationType.TeamScenarioAdd, wfstate); who.AddRange(who2); } if (who.Count > 0) { who = who.Distinct().ToList(); (new NotificationManager(this.DbContext)).CreateTeamAddNotification(who, scenarioId, 0, true, wfstate); } DbContext.SaveChanges(); // need to return scenario id to redirect user on saved scenario page return Json(new { ScenarioId = scenarioId }); } catch (BLLException blEx) // handle any system specific error { transaction.Rollback(); AuditProxy.ClearHistoryChanges(transactionId); AuditProxy.ClearEventChanges(transactionId); // display error message if required if (blEx.DisplayError) ModelState.AddModelError(string.Empty, blEx.Message); else // if display not requried then display modal form with general error message { LogException(blEx); SetErrorScript(); } } catch (Exception exception) // handle any unexpected error { transaction.Rollback(); AuditProxy.ClearHistoryChanges(transactionId); AuditProxy.ClearEventChanges(transactionId); LogException(exception); SetErrorScript(); } } } //if (!ModelState.IsValid) //{ // var errors = ModelState.Values.Where(x => x.Errors.Count > 0).ToList(); //} return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [HttpPost] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Read)] public JsonResult CalculateScenarioDuration(DateTime? startDate, DateTime? endDate) { if (!(startDate.HasValue && endDate.HasValue)) return Json(0); var tempEndDate = endDate.Value.AddDays(6); var duration = DbContext.FiscalCalendars .Count(t => t.Type == (int)FiscalCalendarModel.FiscalYearType.Week && t.EndDate >= startDate && t.EndDate <= tempEndDate && t.NonWorking == 0 && t.AdjustingPeriod == false); return Json(duration); } [HttpPost] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Read)] public ActionResult GetDataSources() { try { List teams; using (var scenarioManager = new ScenarioManager()) { teams = scenarioManager.GetTeamsWithExpenditures(); } var dataSources = new { teams, cgefx = Utils.GetCGEFX().ToList(), categoryTypes = Utils.CastEnumToSelectedList(), creditDepartments = Utils.GetCreditDepartments().ToList(), glAccounts = Utils.GetGLAccounts().ToList(), incomeTypes = Utils.GetIncomeTypes().ToList() }; return Json(dataSources); } catch (Exception ex) { LogException(ex); return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } } [HttpPost] [AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Read)] public ActionResult Compare(IEnumerable ids) { var manager = new ScenarioManager(DbContext); //TODO: Additional validation. All scenarios should has the same parent project e.t.c. if (ids.Count() < 2 || ids.Count() > 4) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest, "Scenario count must be between 2 and 4"); } var userId = User.Identity.GetID(); var user = DbContext.AspNetUsers.Find(userId); var overUnderCoeff = user.OverUnderCoefficient; var scenarioInfos = manager.GetScenariosCompareModel(ids, userId, overUnderCoeff); var optimalScenario = scenarioInfos.Single(x => x.IsOptimal); var optimalScenarioIndex = scenarioInfos.ToList().IndexOf(optimalScenario); var model = new Models.Scenarios.CompareModel { Scenarios = scenarioInfos, OptimalScenarioIndex = optimalScenarioIndex }; return PartialView("_compare", model); } [HttpGet] [Authorize(Users = "admin")] public ActionResult UpdateBUCosts() { return View(); } [HttpPost] [Authorize(Users = "admin")] public ActionResult UpdateBUCosts(string scenarios) { if (string.IsNullOrWhiteSpace(scenarios)) return JavaScript("alert('Please, type scenarios you want to recalculate BU costs for!');"); var scenarioManager = new ScenarioManager(DbContext); var failedScenarios = new List(); foreach (var scenarioId in scenarios.Split(new[] { "\r\n" }, StringSplitOptions.RemoveEmptyEntries)) { Guid scenarioIdGuid; if (!Guid.TryParse(scenarioId, out scenarioIdGuid) || scenarioIdGuid == Guid.Empty) continue; try { scenarioManager.SetBottomUpCosts(scenarioIdGuid); DbContext.SaveChanges(); } catch (Exception ex) { failedScenarios.Add(scenarioIdGuid); LogException(ex); } } return JavaScript(failedScenarios.Count > 0 ? $"failed({Newtonsoft.Json.JsonConvert.SerializeObject(failedScenarios)});" : "success();"); } [HttpPost] public ActionResult ToggleStatus(ToggleScenarioStatusModel model) { try { if (ModelState.IsValid) { var depManager = new ProjectDependencyManager(DbContext); var scenarioManager = new ScenarioManager(DbContext); var scenario = scenarioManager.Load(model.ScenarioId, false); var userId = SecurityManager.GetUserPrincipal().ToString(); using (DbContextTransaction tran = DbContext.Database.BeginTransaction()) { var transactionId = DbContext.GetClientConnectionId(); try { if (scenario != null && scenario.Status != (int)ScenarioStatus.Active) { var activeScenarios = (from c in DbContext.Scenarios where c.ParentId == scenario.ParentId && scenario.Type == c.Type && c.Status == (int?)ScenarioStatus.Active select c).ToList(); foreach (var scen in activeScenarios) { scenarioManager.LogScenarioStatusChange(scen, ScenarioStatus.Inactive, userId, transactionId); scen.Status = (int)ScenarioStatus.Inactive; } scenarioManager.LogScenarioStatusChange(scenario, ScenarioStatus.Active, userId, transactionId); scenario.Status = (int?)ScenarioStatus.Active; } else if (scenario != null) { scenarioManager.LogScenarioStatusChange(scenario, ScenarioStatus.Inactive, userId, transactionId); scenario.Status = (int)ScenarioStatus.Inactive; } if (model.Dependencies != null) depManager.ResolveConflicts(model.Dependencies); DbContext.SaveChanges(); tran.Commit(); Task.Run(() => AuditProxy.CommitHistoryChanges(transactionId, userId.ToString())); Task.Run(() => AuditProxy.CommitEventChanges(transactionId)); } catch (Exception e) { tran.Rollback(); AuditProxy.ClearHistoryChanges(transactionId); AuditProxy.ClearEventChanges(transactionId); throw e; } } return new SuccessJsonResult(); } else { return new FailedJsonResult(ModelState); } } catch (Exception ex) { LogException(ex); ModelState.AddModelError(string.Empty, Messages.Scenario_ToggleStatus_Error); return new FailedJsonResult(ModelState); } } #endregion #region Private Methods private Guid? GetDefaultTemplate(string step) { var tempContext = new EnVisageEntities(); Guid userid = Guid.Parse(HttpContext.User.Identity.GetID()); var id = tempContext.UserPreferences.FirstOrDefault(x => x.UserId == userid && x.Section == step && string.IsNullOrEmpty(x.Url)); if (id != null) return Guid.Parse(id.Data); var template = tempContext.Scenarios.FirstOrDefault(x => x.TemplateId == null && !x.IsBottomUp && x.Name == "Blank Template"); tempContext.UserPreferences.Add(new UserPreference { Id = Guid.NewGuid(), Data = template.Id.ToString(), Section = step, UserId = userid, Url = "" }); tempContext.SaveChanges(); return template.Id; } private ScenarioDetailsCalendarModel GetScenarioDetailsCalendarModel(CreateScenarioModel.GeneralInfoModel model) { var userId = SecurityManager.GetUserPrincipal(); var scenarioTeams = new Dictionary(); if (model.Teams?.Sliders != null) { scenarioTeams = model.Teams.Sliders.ToDictionary(x => x.EntityId, g => (short)g.AllocatePercentage); } var teamsInfo = new ScenarioManager(null).GetScenarioTeamsInfo(scenarioTeams, model.PartId ?? model.ProjectId, Guid.Empty, User.Identity.GetID()); var calendarModel = ScenarioUIManager.CreateScenarioDetailsCalendarModel(userId, model, ScenarioDetailsCalendarModel.ScenarioCalendarOpener.CreateScenarioWizard); if (teamsInfo != null) { calendarModel.TeamsInScenario = teamsInfo.Select(x => new TeamInScenarioModel() { TeamId = x.Id, Allocation = (short)x.Allocation, IsAccessible = x.IsAccessible, CanBeDeleted = x.CanBeDeleted }).ToList(); } return calendarModel; } private IEnumerable GetTemplates(int startIndex, int pageSize, IEnumerable sortedColumns, out int totalRecordCount, out int searchRecordCount, string searchString) { var query = from c in DbContext.Scenarios where c.Type == (int)ScenarioType.Template select new ScenarioListItemModel { Id = c.Id, Name = c.Name, StartDate = c.StartDate, EndDate = c.EndDate, Duration = c.Duration, CGSplit = c.CGSplit, EFXSplit = c.EFXSplit, ScenariosCount = c.ChildScenarios.Count, Status = c.Status, TemplateGroupNames = c.Template2TemplateGroup.Select(x => x.TemplateGroup.Name).OrderBy(x => x).ToList(), }; //filter if (!string.IsNullOrWhiteSpace(searchString)) { query = query.Where(c => c.Name.ToLower().Contains(searchString.ToLower())); } //sort foreach (var sortedColumn in sortedColumns) { switch (sortedColumn.PropertyName) { case "Id": query = sortedColumn.Direction == SortingDirection.Ascending ? query.OrderBy(c => c.Id) : query.OrderByDescending(c => c.Id); break; case "StartDate": query = sortedColumn.Direction == SortingDirection.Ascending ? query.OrderBy(c => c.StartDate) : query.OrderByDescending(c => c.StartDate); break; case "EndDate": query = sortedColumn.Direction == SortingDirection.Ascending ? query.OrderBy(c => c.EndDate) : query.OrderByDescending(c => c.EndDate); break; case "Duration": query = sortedColumn.Direction == SortingDirection.Ascending ? query.OrderBy(c => c.Duration) : query.OrderByDescending(c => c.Duration); break; case "CGSplit": query = sortedColumn.Direction == SortingDirection.Ascending ? query.OrderBy(c => c.CGSplit) : query.OrderByDescending(c => c.CGSplit); break; case "EFXSplit": query = sortedColumn.Direction == SortingDirection.Ascending ? query.OrderBy(c => c.EFXSplit) : query.OrderByDescending(c => c.EFXSplit); break; case "ScenariosCount": query = sortedColumn.Direction == SortingDirection.Ascending ? query.OrderBy(c => c.ScenariosCount) : query.OrderByDescending(c => c.ScenariosCount); break; default: query = sortedColumn.Direction == SortingDirection.Ascending ? query.OrderBy(c => c.Name) : query.OrderByDescending(c => c.Name); break; } } totalRecordCount = DbContext.Scenarios.Count(s => s.Type == (int?)ScenarioType.Template); searchRecordCount = query.Count(); return query.Skip(startIndex).Take(pageSize).ToList(); } private List GetScenarioDetailsProxy(ScenarioCalendarFilterModel model, DateTime startDate, DateTime endDate, IEnumerable scenarioList) { // get the last week even if scenario ends in the middle of the week. E.g. Scenario.EndDate=12/31 and week.EndDate=1/1 var details = (new ScenarioManager(DbContext)).GetScenarioProxyDetails(model, startDate, endDate.AddDays(6), scenarioList); return details.Select(x => (ScenarioDetailWithProxyItemModel)x).ToList(); } private CalendarModel GetCalendar(List forecastDetailsList, List actualsDetailsList, List scenarios, List projects, bool isUOMHours, long actualsStartDateMs = 0, long actualsEndDateMs = 0, bool showActuals = false, bool? showAvg = null) { var calendar = new CalendarModel(); if (forecastDetailsList == null) return calendar; var allExpCats = DbContext.ExpenditureCategory.Include(x => x.Expenditure).AsNoTracking().ToDictionary(x => x.Id); var allUoms = DbContext.UOMs.AsNoTracking().ToDictionary(x => x.Id); return GetCalendar(forecastDetailsList, actualsDetailsList, allExpCats, allUoms, scenarios, projects, isUOMHours, actualsStartDateMs, actualsEndDateMs, showActuals, showAvg); } private CalendarModel GetCalendar(List forecastDetailsList, List actualsDetailsList, Dictionary expCategories, Dictionary uoms, List scenarios, List projects, bool isUOMHours, long actualsStartDateMs = 0, long actualsEndDateMs = 0, bool showActuals = false, bool? showAvg = null) { var calendar = new CalendarModel(); if (expCategories == null || uoms == null) return calendar; if (forecastDetailsList == null) forecastDetailsList = new List(); if (actualsDetailsList == null) actualsDetailsList = new List(); if (!showAvg.HasValue) { var user = new UsersCache().Value.FirstOrDefault(x => x.Id == new Guid(HttpContext.User.Identity.GetID())); if (user != null) { showAvg = user.PreferredTotalsDisplaying; } if (!showAvg.HasValue) { showAvg = true; } } // SA. ENV-839 var expCatsEx = DbContext.VW_ExpenditureCategory.Where(x => expCategories.Keys.Contains(x.Id)).ToDictionary(y => y.Id); var weekEndingDates = showActuals && actualsDetailsList.Count > 0 ? forecastDetailsList.Union(actualsDetailsList) .Select(t => Constants.UnixEpochDate.AddMilliseconds(t.WeekEndingDateMs)) .Distinct() .OrderBy(o => o) .ToList() : forecastDetailsList.Select(t => Constants.UnixEpochDate.AddMilliseconds(t.WeekEndingDateMs)) .Distinct() .OrderBy(o => o) .ToList(); calendar.Headers = BuildHeaders(weekEndingDates); #region Forecast data var scenarioDetails = forecastDetailsList.GroupBy(x => x.ExpenditureCategoryId).ToDictionary(key => key.Key, grouping => grouping.ToList()); if (scenarioDetails.Count > 0) { var totalRow = new ScenarioDetailsModel.ScenarioCalendarRow { ExpCatId = Guid.Empty, ExpCatName = "Totals", GrandTotalCost = 0, GrandTotalQuantity = 0, CostValues = new decimal[calendar.Headers.Count], QuantityValues = new decimal[calendar.Headers.Count], UseType = ExpenditureCategoryModel.UseTypes.Calculated }; var resourcesByTeams = new PeopleResourcesManager(DbContext).LoadPeopleResourcesByProjects(projects); foreach (var teamResource in resourcesByTeams.OrderBy(r => r.LastName)) { if (calendar.AllResources.Any(x => x.Id == teamResource.Id)) continue; calendar.AllResources.Add(new ScenarioDetailsModel.ScenarioCalendarRowResource() { Id = teamResource.Id, Name = teamResource.FirstName + " " + teamResource.LastName, ExpedentureCategoryId = teamResource.ExpenditureCategoryId, IsActiveEmployee = teamResource.IsActiveEmployee }); } var allResIds = calendar.AllResources.Select(t => t.Id).Distinct().ToArray(); var allResNPTimes = new NonProjectTimeManager(DbContext).GetNonProjectTimesByResources(allResIds); var firstWeek = weekEndingDates.FirstOrDefault(); var lastWeek = weekEndingDates.LastOrDefault(); var allResHolidays = new FiscalCalendarManager(DbContext).GetHolidayAllocationsByResource(firstWeek, lastWeek, resourceIds: allResIds); var resourceAllocation = DbContext.PeopleResourceAllocations.Where(r => scenarios.Contains(r.ScenarioId)).ToList(); foreach (var row in scenarioDetails) { var expenditureCategory = expCatsEx[row.Key]; var expCatRow = new ScenarioDetailsModel.ScenarioCalendarRow { ExpCatId = row.Key, ExpCatName = expenditureCategory.ExpCategoryWithCcName, // SA. ENV-756. ENV-839 GrandTotalCost = 0, GrandTotalQuantity = 0, CostValues = new decimal[calendar.Headers.Count], QuantityValues = new decimal[calendar.Headers.Count], UseType = (ExpenditureCategoryModel.UseTypes)expenditureCategory.UseType, Resources = calendar.AllResources.Where(x => resourceAllocation.Where(ec => ec.ExpenditureCategoryId == row.Key). Select(r => r.PeopleResourceId).Contains(x.Id)). Select(x => new ScenarioDetailsModel.ScenarioCalendarRowResource() { Id = x.Id, Name = x.Name, QuantityValues = new decimal[calendar.Headers.Count], CostValues = new decimal[calendar.Headers.Count], CapacityQuantityValues = new decimal[calendar.Headers.Count], }).ToList(), RestQuantity = new decimal[calendar.Headers.Count], RestCost = new decimal[calendar.Headers.Count], ActualsCosts = new decimal[calendar.Headers.Count], ActualsQuantities = new decimal[calendar.Headers.Count] }; var uomMultiplier = Utils.GetUOMMultiplier(expCategories, uoms, row.Key, isUOMHours); var uomValue = uoms.FirstOrDefault(t => t.Key == expenditureCategory.UOMId); var monthCost = 0.0M; var monthQuantity = 0.0M; var resourceTotals = calendar.AllResources.Select(x => new Pairs { Id = x.Id, Quantity = 0.0M }).ToList(); var rowValues = row.Value.GroupBy(x => Constants.UnixEpochDate.AddMilliseconds(x.WeekEndingDateMs)).ToDictionary(x => x.Key, g => g.ToList()); foreach (var weekEnding in weekEndingDates) { var values = rowValues.ContainsKey(weekEnding) ? rowValues[weekEnding] : new List(); var colIndex = calendar.Headers.FindIndex(x => x.Milliseconds == weekEnding.Subtract(Constants.UnixEpochDate).TotalMilliseconds); if (colIndex < 0) continue; // need to skip all forecast data befor actuals end date if (showActuals && actualsStartDateMs > 0 && actualsEndDateMs > 0 && calendar.Headers[colIndex].Milliseconds <= actualsEndDateMs) { // skip actuals dates, because this cells will be populated later expCatRow.CostValues[colIndex] = 0; expCatRow.QuantityValues[colIndex] = 0; expCatRow.ForecastQtyTotalInActualsRange += values.Sum(x => x.Quantity) * uomMultiplier; } else { expCatRow.CostValues[colIndex] = values.Sum(x => x.Cost); expCatRow.QuantityValues[colIndex] = values.Sum(x => x.Quantity) * uomMultiplier; } //Get resources cost\quantity var currAllocation = resourceAllocation.Where(r => r.WeekEndingDate == weekEnding && r.ExpenditureCategoryId == row.Key).ToList(); expCatRow.Resources.ForEach(x => { // set resource weekly allocation x.QuantityValues[colIndex] = currAllocation.Where(ar => ar.PeopleResourceId == x.Id).Sum(ca => ca.Quantity) * uomMultiplier; // set resource weekly availability (UOM.Value - non-project time) if (uomValue.Value != null) { var npTime = allResNPTimes.Where(r => r.PeopleResourceId == x.Id && r.WeekEndingDate == weekEnding).Sum(r => r.HoursOff); var holidayKoeff = allResHolidays.ContainsKey(x.Id) && allResHolidays[x.Id].ContainsKey(weekEnding) ? allResHolidays[x.Id][weekEnding] : 1; x.CapacityQuantityValues[colIndex] = (uomValue.Value.UOMValue * holidayKoeff - npTime) * uomMultiplier; } }); expCatRow.RestQuantity[colIndex] = expCatRow.QuantityValues[colIndex] - expCatRow.Resources.Select(x => x.QuantityValues[colIndex]).Sum(); totalRow.CostValues[colIndex] += expCatRow.CostValues[colIndex]; totalRow.QuantityValues[colIndex] += expCatRow.QuantityValues[colIndex]; expCatRow.Resources.ForEach(x => resourceTotals.FirstOrDefault(r => r.Id == x.Id).Quantity += x.QuantityValues[colIndex]); monthQuantity += expCatRow.QuantityValues[colIndex]; monthCost += expCatRow.CostValues[colIndex]; if (colIndex < calendar.Headers.Count - 1) colIndex++; if (colIndex >= calendar.Headers.Count - 1 || calendar.Headers[colIndex].IsMonth) { expCatRow.CostValues[colIndex] = monthCost; if (showAvg.Value && !isUOMHours) { expCatRow.QuantityValues[colIndex] = Math.Round(monthQuantity / calendar.Headers[colIndex].Weeks.Count, 3); } else { expCatRow.QuantityValues[colIndex] = monthQuantity; } if (showAvg.Value && !isUOMHours) { //var avgCost =Math.Round(monthCost/ calendar.Headers[colIndex].Weeks.Count(), 3); var avgQuantity = Math.Round(monthQuantity / calendar.Headers[colIndex].Weeks.Count, 3); //expCatRow.GrandTotalCost += avgCost; expCatRow.GrandTotalQuantity += avgQuantity; expCatRow.Resources.ForEach(x => x.QuantityValues[colIndex] = Math.Round(resourceTotals.FirstOrDefault(r => r.Id == x.Id).Quantity / calendar.Headers[colIndex].Weeks.Count(), 3)); //totalRow.CostValues[colIndex] += avgCost; totalRow.QuantityValues[colIndex] += avgQuantity; } else { expCatRow.GrandTotalQuantity += monthQuantity; expCatRow.Resources.ForEach(x => x.QuantityValues[colIndex] = resourceTotals.FirstOrDefault(r => r.Id == x.Id).Quantity); totalRow.QuantityValues[colIndex] += monthQuantity; } expCatRow.Resources.ForEach(x => x.GrandTotalQuantity += (x.QuantityValues[colIndex])); expCatRow.GrandTotalCost += monthCost; totalRow.CostValues[colIndex] += monthCost; monthCost = 0.0M; monthQuantity = 0.0M; resourceTotals.ForEach(x => x.Quantity = 0.0M); } } calendar.Rows.Add(expCatRow); totalRow.GrandTotalCost += expCatRow.GrandTotalCost; totalRow.GrandTotalQuantity += expCatRow.GrandTotalQuantity; } if (showAvg.Value && !isUOMHours) { var monthsCount = calendar.Headers.Count(x => x.IsMonth); calendar.Rows.ForEach( x => x.GrandTotalQuantity = Math.Round(x.GrandTotalQuantity / monthsCount, 3)); totalRow.GrandTotalQuantity = 0; calendar.Rows.ForEach(x => totalRow.GrandTotalQuantity += x.GrandTotalQuantity); calendar.Rows.ForEach(x => x.Resources.ForEach(r => r.GrandTotalQuantity = Math.Round(r.GrandTotalQuantity / monthsCount, 3))); } calendar.Rows = calendar.Rows.OrderBy(x => x.ExpCatName).ToList(); calendar.Rows.Insert(0, totalRow); } #endregion #region Load Actuals if (showActuals && actualsDetailsList != null && actualsDetailsList.Count > 0) { var weekCells = calendar.Headers.Count(x => !x.IsMonth); var totalRow = calendar.Rows.FirstOrDefault(t => t.ExpCatId == Guid.Empty); totalRow.ActualsCosts = new decimal[calendar.Headers.Count]; totalRow.ActualsQuantities = new decimal[calendar.Headers.Count]; var actualsDetails = actualsDetailsList.GroupBy(x => x.ExpenditureCategoryId).ToDictionary(x => x.Key); foreach (var row in actualsDetails) { var expenditureCategory = expCatsEx[row.Key]; var expCatRow = calendar.Rows.FirstOrDefault(t => t.ExpCatId == row.Key); if (expCatRow == null) { expCatRow = new ScenarioDetailsModel.ScenarioCalendarRow { ExpCatId = row.Key, ExpCatName = expenditureCategory.ExpCategoryWithCcName, // SA. ENV-756. ENV-839 GrandTotalCost = 0, GrandTotalQuantity = 0, CostValues = new decimal[calendar.Headers.Count], QuantityValues = new decimal[calendar.Headers.Count], UseType = (ExpenditureCategoryModel.UseTypes)expenditureCategory.UseType, Resources = new List(), RestQuantity = new decimal[calendar.Headers.Count], RestCost = new decimal[calendar.Headers.Count], ActualsCosts = new decimal[calendar.Headers.Count], ActualsQuantities = new decimal[calendar.Headers.Count] }; calendar.Rows.Add(expCatRow); } var uomMultiplier = Utils.GetUOMMultiplier(expCategories, uoms, row.Key, isUOMHours); var rowValues = row.Value.GroupBy(x => Constants.UnixEpochDate.AddMilliseconds(x.WeekEndingDateMs)).ToDictionary(x => x.Key, g => g.ToList()); foreach (var weekEnding in weekEndingDates) { var values = rowValues.ContainsKey(weekEnding) ? rowValues[weekEnding] : new List(); var colIndex = -1; var monthIndex = -1; var msEnd = weekEnding.Subtract(Constants.UnixEpochDate).TotalMilliseconds; var msStart = msEnd - (6 * 24 * 60 * 60 * 1000); ScenarioDetailsModel.Header header = null; for (var i = 0; i < calendar.Headers.Count; i++) { if (!calendar.Headers[i].IsMonth && calendar.Headers[i].Milliseconds > msStart && calendar.Headers[i].Milliseconds <= msEnd && colIndex < 0) { header = calendar.Headers[i]; colIndex = i; } if (colIndex >= 0 && calendar.Headers[i].IsMonth && monthIndex < 0) { monthIndex = i; break; } } if (header == null) continue; var oldCost = expCatRow.ActualsCosts[colIndex]; var oldQty = expCatRow.ActualsQuantities[colIndex]; expCatRow.ActualsCosts[colIndex] = values.Sum(x => x.Cost); expCatRow.ActualsQuantities[colIndex] = values.Sum(x => x.Quantity) * uomMultiplier; var monthQuantityDelta = expCatRow.ActualsQuantities[colIndex] - oldQty; var monthCostDelta = expCatRow.ActualsCosts[colIndex] - oldCost; totalRow.ActualsCosts[colIndex] += monthCostDelta; totalRow.ActualsQuantities[colIndex] += monthQuantityDelta; if (monthIndex > 0) { expCatRow.ActualsCosts[monthIndex] += monthCostDelta; expCatRow.ActualsQuantities[monthIndex] += monthQuantityDelta; totalRow.ActualsCosts[monthIndex] += monthCostDelta; totalRow.ActualsQuantities[monthIndex] += monthQuantityDelta; } } var catTotalActualsQty = 0.0M; for (var i = 0; i < calendar.Headers.Count; i++) { if (!calendar.Headers[i].IsMonth && calendar.Headers[i].Milliseconds >= actualsStartDateMs && calendar.Headers[i].Milliseconds <= actualsEndDateMs) { catTotalActualsQty += expCatRow.ActualsQuantities[i]; } } expCatRow.ForecastCompletedPercent = expCatRow.ForecastQtyTotalInActualsRange == 0 ? 0 : Math.Abs(catTotalActualsQty / expCatRow.ForecastQtyTotalInActualsRange - 1.0M); #region replace forecast data with actuals var lastMonthRecalculated = false; for (var i = 0; i < calendar.Headers.Count; i++) { if (!calendar.Headers[i].IsMonth) { // modify weekly data // need to replace with actuals data all forecast befor actuals end date if (showActuals && actualsStartDateMs > 0 && actualsEndDateMs > 0 && calendar.Headers[i].Milliseconds <= actualsEndDateMs) { // repalce forecast data with actuals if week is in actuals range var costDelta = expCatRow.ActualsCosts[i] - expCatRow.CostValues[i]; var qtyDelta = expCatRow.ActualsQuantities[i] - expCatRow.QuantityValues[i]; expCatRow.QuantityValues[i] += qtyDelta; expCatRow.CostValues[i] += costDelta; totalRow.QuantityValues[i] += qtyDelta; totalRow.CostValues[i] += costDelta; } else if (lastMonthRecalculated) { // break loop if all weeks within actuals date range have been updated // and month on the right border of the range has been recalculated break; } // indicate that new month has been started so we will need to recalculate it lastMonthRecalculated = false; } else { var monthlyQuantity = 0.0M; var monthlyCost = 0.0M; // recalculate month var qtyDelta = 0.0M; foreach (var t in calendar.Headers[i].Weeks) { monthlyQuantity += expCatRow.QuantityValues[t]; monthlyCost += expCatRow.CostValues[t]; } if (showAvg.Value && !isUOMHours) qtyDelta = Math.Round(monthlyQuantity / calendar.Headers[i].Weeks.Count, 3) - expCatRow.QuantityValues[i]; else qtyDelta = monthlyQuantity - expCatRow.QuantityValues[i]; var costDelta = monthlyCost - expCatRow.CostValues[i]; expCatRow.QuantityValues[i] += qtyDelta; expCatRow.CostValues[i] += costDelta; expCatRow.GrandTotalCost += costDelta; expCatRow.GrandTotalQuantity += qtyDelta; totalRow.CostValues[i] += costDelta; totalRow.QuantityValues[i] += qtyDelta; totalRow.GrandTotalCost += costDelta; totalRow.GrandTotalQuantity += qtyDelta; // indicate that month has been recalculated lastMonthRecalculated = true; } } if (showAvg.Value && !isUOMHours) { expCatRow.GrandTotalQuantity = 0; for (int i = 0; i < calendar.Headers.Count; i++) { if (calendar.Headers[i].IsMonth) continue; expCatRow.GrandTotalQuantity += expCatRow.QuantityValues[i]; } expCatRow.GrandTotalQuantity = Math.Round(expCatRow.GrandTotalQuantity / weekCells); } #endregion } if (showAvg.Value && !isUOMHours) { totalRow.GrandTotalQuantity = 0; for (int i = 0; i < calendar.Headers.Count; i++) { if (calendar.Headers[i].IsMonth) continue; totalRow.GrandTotalQuantity += totalRow.QuantityValues[i]; } totalRow.GrandTotalQuantity = Math.Round(totalRow.GrandTotalQuantity / weekCells); } } #endregion return calendar; } private List BuildHeaders(List gridHeaders) { var headers = new List((int)(gridHeaders.Count * 1.25)); var prevMonth = string.Empty; var monthIndex = -1; ScenarioDetailsModel.Header monthColumn = null; foreach (var gridHeader in gridHeaders) { // get week start date as previous week end date + 1 day //var weekStartDate = prevWeek.AddDays(1); // if there is a gap between weeks (e.g. there is a non-working week that was not represented in scenario details records) // then we should subtract 6 days from current week end date //if (gridHeader.AddDays(-6) > weekStartDate) // weekStartDate = gridHeader.AddDays(-6); // get month name as month of the week start date var gridHeaderTitle = gridHeader.ToString("MMMM yyyy"); if (!prevMonth.Equals(gridHeaderTitle)) { if (monthColumn != null) { headers.Add(monthColumn); } monthColumn = new ScenarioDetailsModel.Header() { Show = true, Milliseconds = (long)gridHeader.Subtract(Constants.UnixEpochDate).TotalMilliseconds, MonthIndex = ++monthIndex, IsMonth = true, Title = gridHeaderTitle, Weeks = new List() }; } var weekHeader = new ScenarioDetailsModel.Header { IsMonth = false, Milliseconds = (long)gridHeader.Subtract(Constants.UnixEpochDate).TotalMilliseconds, MonthIndex = monthColumn.MonthIndex, Title = gridHeader.ToShortDateString() }; headers.Add(weekHeader); monthColumn.Weeks.Add(headers.Count - 1); monthColumn.Milliseconds = weekHeader.Milliseconds; prevMonth = gridHeaderTitle; } if (monthColumn != null) { headers.Add(monthColumn); } return headers; } private Dictionary> BuildHeadersEx(List gridHeaders) { var headers = new Dictionary> { {ScenarioDetailsModel.HeaderPeriod.Month, new List()}, {ScenarioDetailsModel.HeaderPeriod.Week, new List()} }; List monthHeaders = headers[ScenarioDetailsModel.HeaderPeriod.Month]; List weekHeaders = headers[ScenarioDetailsModel.HeaderPeriod.Week]; var prevMonth = string.Empty; var monthIndex = -1; ScenarioDetailsModel.MonthHeader lastMonthColumn = null; ScenarioDetailsModel.WeekHeader totalsHeader = null; foreach (var gridHeader in gridHeaders) { // get week start date as previous week end date + 1 day //var weekStartDate = prevWeek.AddDays(1); // if there is a gap between weeks (e.g. there is a non-working week that was not represented in scenario details records) // then we should subtract 6 days from current week end date //if (gridHeader.AddDays(-6) > weekStartDate) // weekStartDate = gridHeader.AddDays(-6); // get month name as month of the week start date var gridHeaderTitle = gridHeader.ToString("MMMM yyyy"); if (!prevMonth.Equals(gridHeaderTitle)) { if (totalsHeader != null) { weekHeaders.Add(totalsHeader); lastMonthColumn.TotalsHeader = weekHeaders.Count - 1; } // Create month new column lastMonthColumn = new ScenarioDetailsModel.MonthHeader { Milliseconds = (long)gridHeader.Subtract(Constants.UnixEpochDate).TotalMilliseconds, Title = gridHeaderTitle, IsCollapsed = true, }; monthHeaders.Add(lastMonthColumn); monthIndex = monthHeaders.Count - 1; // Create month totals column for the prev month totalsHeader = new ScenarioDetailsModel.WeekHeader { DataType = ScenarioDetailsModel.WeekHeaderType.Totals, Milliseconds = lastMonthColumn.Milliseconds, MonthHeader = monthIndex, }; } var weekHeader = new ScenarioDetailsModel.WeekHeader { Milliseconds = (long)gridHeader.Subtract(Constants.UnixEpochDate).TotalMilliseconds, Title = gridHeader.ToShortDateString(), MonthHeader = monthIndex, }; weekHeaders.Add(weekHeader); lastMonthColumn.WeekHeaders.Add(weekHeaders.Count - 1); // lastMonthColumn.Milliseconds = weekHeader.Milliseconds; prevMonth = gridHeaderTitle; } if (totalsHeader != null) { weekHeaders.Add(totalsHeader); lastMonthColumn.TotalsHeader = weekHeaders.Count - 1; } return headers; } private Dictionary> BuildHeadersExForScenario(List weekEndings, long forecastDataStartDateMs, long forecastDataEndDateMs, long actualsViewDataStartDateMs, long actualsViewDataEndDateMs, long? actualsDataStartDateMs, long? actualsDataEndDateMs) { if (weekEndings == null || weekEndings.Count <= 0) return new Dictionary>(); var headers = BuildHeadersEx(weekEndings); // SA. ENV-667. Begin // Get week headers, that must be visible in grid forecast mode var weekHeadersForecastVisible = (from ScenarioDetailsModel.HeaderBase item in headers[ScenarioDetailsModel.HeaderPeriod.Week] let weekHeader = item as ScenarioDetailsModel.WeekHeader where weekHeader != null && weekHeader.DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal && weekHeader.Milliseconds >= forecastDataStartDateMs && weekHeader.Milliseconds <= forecastDataEndDateMs select item as ScenarioDetailsModel.WeekHeader).ToList(); // Get week headers, that must be visible in grid actuals mode var weekHeadersActualsVisible = (from ScenarioDetailsModel.HeaderBase item in headers[ScenarioDetailsModel.HeaderPeriod.Week] let header = item as ScenarioDetailsModel.WeekHeader where header != null && header.DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal && header.Milliseconds >= actualsViewDataStartDateMs && header.Milliseconds <= actualsViewDataEndDateMs select item as ScenarioDetailsModel.WeekHeader).ToList(); // Get week headers, that must be editable in grid actuals mode (default = are editable) var weekHeadersActualsEditable = (from ScenarioDetailsModel.HeaderBase item in headers[ScenarioDetailsModel.HeaderPeriod.Week] let weekHeader = item as ScenarioDetailsModel.WeekHeader where weekHeader != null && weekHeader.DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal && weekHeader.Milliseconds >= actualsDataStartDateMs && weekHeader.Milliseconds <= actualsDataEndDateMs select item as ScenarioDetailsModel.WeekHeader).ToList(); // Set week headers visibility for forecast and actuals modes. Set non editable columns weekHeadersForecastVisible.ForEach(x => x.Visible[ScenarioDetailsModel.CalendarDataViewModes.Forecast] = true); weekHeadersActualsVisible.ForEach(x => x.Visible[ScenarioDetailsModel.CalendarDataViewModes.Actuals] = true); weekHeadersActualsEditable.ForEach(x => x.Editable[ScenarioDetailsModel.CalendarDataViewModes.Actuals] = false); // Set visibility for month and totals headers for (int monthIndex = 0; monthIndex < headers[ScenarioDetailsModel.HeaderPeriod.Month].Count; monthIndex++) { ScenarioDetailsModel.MonthHeader monthItem = headers[ScenarioDetailsModel.HeaderPeriod.Month][monthIndex] as ScenarioDetailsModel.MonthHeader; int forcastVisibleWeeksCount = headers[ScenarioDetailsModel.HeaderPeriod.Week].Count(x => { var weekHeader = x as ScenarioDetailsModel.WeekHeader; return weekHeader != null && weekHeader.MonthHeader == monthIndex && weekHeader.DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal && x.Visible[ScenarioDetailsModel.CalendarDataViewModes.Forecast]; }); int actualsVisibleWeeksCount = headers[ScenarioDetailsModel.HeaderPeriod.Week].Count(x => { var weekHeader = x as ScenarioDetailsModel.WeekHeader; return weekHeader != null && weekHeader.MonthHeader == monthIndex && weekHeader.DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal && x.Visible[ScenarioDetailsModel.CalendarDataViewModes.Actuals]; }); int actualsNonEditableWeeksCount = headers[ScenarioDetailsModel.HeaderPeriod.Week].Count(x => { var weekHeader = x as ScenarioDetailsModel.WeekHeader; return weekHeader != null && weekHeader.MonthHeader == monthIndex && weekHeader.DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal && !x.Editable[ScenarioDetailsModel.CalendarDataViewModes.Actuals]; }); if (monthItem != null) { monthItem.ColspanValues[ScenarioDetailsModel.CalendarDataViewModes.Forecast] = forcastVisibleWeeksCount; monthItem.ColspanValues[ScenarioDetailsModel.CalendarDataViewModes.Actuals] = actualsVisibleWeeksCount; monthItem.Visible[ScenarioDetailsModel.CalendarDataViewModes.Forecast] = forcastVisibleWeeksCount > 0; monthItem.Visible[ScenarioDetailsModel.CalendarDataViewModes.Actuals] = actualsVisibleWeeksCount > 0; monthItem.Editable[ScenarioDetailsModel.CalendarDataViewModes.Actuals] = actualsNonEditableWeeksCount < 1; headers[ScenarioDetailsModel.HeaderPeriod.Week][monthItem.TotalsHeader] .Visible[ScenarioDetailsModel.CalendarDataViewModes.Forecast] = forcastVisibleWeeksCount > 0; headers[ScenarioDetailsModel.HeaderPeriod.Week][monthItem.TotalsHeader] .Visible[ScenarioDetailsModel.CalendarDataViewModes.Actuals] = actualsVisibleWeeksCount > 0; headers[ScenarioDetailsModel.HeaderPeriod.Week][monthItem.TotalsHeader] .Editable[ScenarioDetailsModel.CalendarDataViewModes.Actuals] = actualsNonEditableWeeksCount < 1; } } // for (int monthIndex = 0; monthIndex < headers[... return headers; } private ScenarioDetailModel LoadScenarioDetailsModel(CreateScenarioModel.GeneralInfoModel sm) { var parentId = sm.PartId ?? sm.ProjectId; if (null == parentId || Guid.Empty.Equals(parentId)) throw new BLLException("We should have a project or part to create a scenario"); var model = new ScenarioDetailModel { ParentId = parentId, Name = sm.ScenarioName, GrowthScenario = sm.GrowthScenario, TemplateId = sm.TemplateId ?? Guid.Empty, StartDate = sm.StartDate, EndDate = sm.EndDate, Id = sm.ScenarioId, IsBottomUp = sm.IsBottomUp, ProjectHasDependencies = sm.ProjectHasDependencies, StartDateConstraint = sm.StartDateConstraint, EndDateConstraint = sm.EndDateConstraint, ScenarioExpenditures = sm.ScenarioExpenditures?.Where(t => t.Checked).ToList(), }; var udfMan = new UDFManager(new EnVisageEntities()); model.UserDefinedFields = udfMan.LoadCollection(model.Id, UserDefinedFieldDomain.Scenario); if (!Guid.Empty.Equals(model.ParentId)) { var actualScenario = DbContext.Scenarios.FirstOrDefault(x => x.ParentId == model.ParentId && x.Type == (int)ScenarioType.Actuals); if (actualScenario != null) { var actualSplitInfo = (new ScenarioManager(DbContext)).GetLaborMaterialsSplit(actualScenario.Id); if (actualScenario.StartDate.HasValue) model.ActualsStartDate = Utils.ConvertToUnixDate(actualScenario.StartDate.Value.Date); if (actualScenario.EndDate.HasValue) model.ActualsEndDate = Utils.ConvertToUnixDate(actualScenario.EndDate.Value.Date); model.FinInfo.ActualLabor = actualSplitInfo.Labor; model.FinInfo.ActualMaterials = actualSplitInfo.Materials; model.FinInfo.ActualsTotal = actualSplitInfo.Total; model.HasActuals = actualScenario.EndDate.HasValue; model.FinInfo.ActualLaborMaterialsSplit = actualSplitInfo.LaborAndMaterialSplit; } var forecastSplitInfo = (new ScenarioManager(DbContext)).GetLaborMaterialsSplit(model.TemplateId); model.FinInfo.LaborMaterialsSplit = forecastSplitInfo.LaborAndMaterialSplit; #region Override Actuals fields if there are no actuals data bool actualDataExists = false; if (actualScenario != null) // Actual data scenario exists actualDataExists = actualScenario.EndDate.HasValue; model.FinInfo.DateForStartOfChanges = model.StartDate.HasValue && model.StartDate.Value > DateTime.Today ? model.StartDate.Value.Date : DateTime.Today; if (!actualDataExists) { // Show forecats values as actuals, if actuals not found model.FinInfo.CalculatedGrossMarginActuals = model.FinInfo.CalculatedGrossMargin; model.FinInfo.CalculatedGrossMarginLMActuals = model.FinInfo.CalculatedGrossMarginLM; } else { // date for start of changes should be bigger than actuals end date if (actualScenario.EndDate >= model.FinInfo.DateForStartOfChanges) model.FinInfo.DateForStartOfChanges = actualScenario.EndDate.Value.AddDays(1); } } #endregion return model; } private List GetScenarioCategories(Guid id) { return DbContext.VW_ExpCategoriesInScenario.AsNoTracking() .Where(t => t.ScenarioID == id) .OrderBy(t => t.ExpCategoryWithCcName) .Select(t => new ExpenditureModel { Id = t.Id, Name = t.ExpCategoryWithCcName // SA. ENV-839 }).ToList(); } /// /// Returns ExpCategories list. Every category marked, if it is related to one of the given (scenario) teams /// /// Scenario teams /// /// SA. ENV-840 private List GetAllCategories(List teams) { // Get all categories List categories = DbContext.VW_ExpenditureCategory.AsNoTracking() .OrderBy(t => t.Name) .Select(t => new ScenarioExpenditureModel { Id = t.Id, Name = t.ExpCategoryWithCcName // SA. ENV-839 }).ToList(); if (teams != null && teams.Count > 0) { // Get categories, related to given teams List scenarioCategories = DbContext.VW_TeamResource.AsNoTracking() .Where(pr => teams.Contains(pr.TeamId)) .Select(pr => pr.ExpenditureCategoryId).Distinct().ToList(); // Mark related to teams categories categories.Where(x => scenarioCategories.Contains(x.Id)).ToList().ForEach(x => x.IsRelatedToScenario = 1); } return categories; } private ScenarioCalendarModel BuildScenarioDetailsCalendarData(List forecastDetails, List actualsDetails, List fiscalCalendar) { if (forecastDetails == null) forecastDetails = new List(); if (actualsDetails == null) actualsDetails = new List(); if (fiscalCalendar == null) fiscalCalendar = new List(); var forecastWeekEndings = forecastDetails.Where(x => x.WeekEndingDate.HasValue).Select(x => x.WeekEndingDate.Value).ToList(); // if there are no scenario details (e.g. BU scenario or incorrect data) we need to display data according to financial calendar if (forecastWeekEndings.Count <= 0) forecastWeekEndings = fiscalCalendar.Select(x => x.EndDate).ToList(); var actualsWeekEndings = actualsDetails.Where(x => x.WeekEndingDate.HasValue).Select(x => x.WeekEndingDate.Value).ToList(); var weekEndings = forecastWeekEndings.Union(actualsWeekEndings).OrderBy(x => x).ToList(); // SA. ENV-667. Get the bound dates for forecast and actuals periods for display. Begin var forecastDataExists = forecastWeekEndings.Count > 0; var actualsIsExists = actualsWeekEndings.Count > 0; long forecastDataStartDateMs = 0; long forecastDataEndDateMs = 0; long actualsViewDataStartDateMs = 0; long actualsViewDataEndDateMs = 0; long? actualsDataStartDateMs = null; long? actualsDataEndDateMs = null; if (forecastDataExists) { forecastDataStartDateMs = (long)forecastWeekEndings.Min().Subtract(Constants.UnixEpochDate).TotalMilliseconds; forecastDataEndDateMs = (long)forecastWeekEndings.Max().Subtract(Constants.UnixEpochDate).TotalMilliseconds; actualsViewDataStartDateMs = (long)weekEndings.Min().Subtract(Constants.UnixEpochDate).TotalMilliseconds; actualsViewDataEndDateMs = (long)weekEndings.Max().Subtract(Constants.UnixEpochDate).TotalMilliseconds; if (actualsIsExists) { actualsDataStartDateMs = actualsViewDataStartDateMs; actualsDataEndDateMs = (long)actualsWeekEndings.Max().Subtract(Constants.UnixEpochDate).TotalMilliseconds; } //SA. ENV-667. End } // if (forecastDataExists) var uoms = DbContext.UOMs.ToDictionary(x => x.Id, g => g.UOMValue); return new ScenarioCalendarModel() { Expenditures = (new ScenarioManager(DbContext)).BuildExpendituresForScenario(weekEndings, forecastDetails, actualsDetails, uoms), Headers = BuildHeadersExForScenario(weekEndings, forecastDataStartDateMs, forecastDataEndDateMs, actualsViewDataStartDateMs, actualsViewDataEndDateMs, actualsDataStartDateMs, actualsDataEndDateMs) }; // SA. ENV-667. End } #endregion #region Models private class Pairs { public Guid Id { get; set; } public decimal Quantity { get; set; } } #endregion } }