using System.Data.Entity; using System.Data.Entity.Infrastructure; using System.Net; using EnVisage.App_Start; using EnVisage.Code.BLL; using EnVisage.Code.Cache; using EnVisage.Code.HtmlHelpers; using EnVisage.Models; using System; using System.Collections.Generic; using System.Linq; using System.Web.Mvc; using jQuery.DataTables.Mvc; using EnVisage.Code; using Microsoft.AspNet.Identity; using Newtonsoft.Json; using System.Linq.Expressions; using System.IO; namespace EnVisage.Controllers { [Authorize] public class ScenariosController : BaseController { // GET: /Scenarios/Templates [HttpGet] [AreaSecurityAttribute(area = Areas.ScenarioTemplates, level = AccessLevel.Read)] public ActionResult Templates() { if (!SecurityManager.CheckSecurityObjectPermission(Areas.ScenarioTemplates, AccessLevel.Read)) return Redirect("/"); return View(); } [HttpGet] [AreaSecurityAttribute(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 }; model.ScenarioId = Guid.Empty; model.Step1.TemplateId = Guid.Empty; 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; // SA. ENV-773. Begin model.Step1.Teams = new SlidersGroupModel { GroupId = Guid.NewGuid(), Options = Utils.GetTeams(false) }; int projectTeamsCount = model.Step1.Project.Team2Project.Count(); int slidersTotalSumm = 100; double remainingValue = slidersTotalSumm; List projectTeamModels = (from Team2Project teamRec in model.Step1.Project.Team2Project select new SliderModel() { Id = teamRec.Id, EntityId = teamRec.TeamId, Name = teamRec.Team.Name, ParentId = model.Step1.Teams.GroupId, AllocatePercentage = Math.Round((double)slidersTotalSumm / projectTeamsCount, 0) }).ToList(); for (int index = 0; index < projectTeamModels.Count; index++) { if ((projectTeamModels.Count - index) != 1) remainingValue -= Convert.ToInt32(projectTeamModels[index].AllocatePercentage); else // The last slider projectTeamModels[index].AllocatePercentage = remainingValue; } 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; // SA. ENV-773. End if (model.Step1.Project.ParentProjectId.HasValue) { model.Step1.ProjectId = model.Step1.Project.ParentProjectId.Value; model.Step1.PartId = loadModel.Id; } else { model.Step1.ProjectId = loadModel.Id; } model.Step3.IsRevenueGenerating = model.Step1.Project.IsRevenueGenerating; model.Step1.StatusIsEditable = loadModel.StatusIsEditable; model.CurrentStep = "Step1"; return PartialView("_createScenario", model); } [HttpPost] [ValidateAntiForgeryToken] [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult SubmitCreateScenarioStep1(CreateScenarioModel.GeneralInfoModel model, string[] expCatGroups) { model.TrimStringProperties(); try { //var teamallocations = // Newtonsoft.Json.JsonConvert.DeserializeObject>(model.TeamAllocations); #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()]; List selectedIDs; if (null == expCatGroups) selectedIDs = new List(); else selectedIDs = expCatGroups.ToList(); 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.LaborSplitPercentage; model.EFXSplit = detailsModel.EFXSplit; model.Teams.Options = Utils.GetTeams(false); #endregion if (ModelState.IsValid) { if (checkedECs.Count == 0) { ModelState.AddModelError("ScenarioExpenditures", string.Format(Constants.ERROR_TEMPLATE_REQUIRED, "Expenditures")); } else { return PartialView("_generalStep", model); } } } 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); //SetErrorScript(); ModelState.AddModelError(string.Empty, "Cannot save scenario. Try again later."); } } catch (Exception exception) // handle any unexpected error { LogException(exception); //SetErrorScript(); ModelState.AddModelError(string.Empty, "Cannot save scenario. Try again later."); } HttpContext.Response.StatusCode = 500; HttpContext.Response.Clear(); return PartialView("_generalStep", model); } [HttpPost] [ValidateAntiForgeryToken] [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult SubmitCreateScenarioStep3(CreateScenarioModel.FinInfoModel model) { model.TrimStringProperties(); try { //var costSavingItems = new List(); //if (model.CostSavings != null && !string.IsNullOrWhiteSpace(model.CostSavings.CostSavingItems)) // costSavingItems = JsonConvert.DeserializeObject>(model.CostSavings.CostSavingItems); if (ModelState.IsValid) { return PartialView("_finStep", model); } } 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); //SetErrorScript(); ModelState.AddModelError(string.Empty, "Cannot save scenario. Try again later."); } } catch (Exception exception) // handle any unexpected error { LogException(exception); //SetErrorScript(); ModelState.AddModelError(string.Empty, "Cannot save scenario. Try again later."); } HttpContext.Response.StatusCode = 500; HttpContext.Response.Clear(); return PartialView("_finStep", model); } [HttpPost] [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult GetECsByTemplateId(Guid? id, List selectedExpCats, List teams, bool overrideChecked) { var manager = new ScenarioManager(DbContext); var ec = manager.GetExpenditureCategories(id, teams); var checkedItems = selectedExpCats != null && selectedExpCats.Count > 0 ? selectedExpCats : new List(); foreach (var expenditureItem in ec) { if (overrideChecked) { expenditureItem.Checked = checkedItems.Contains(expenditureItem.Id); } else { expenditureItem.Checked |= checkedItems.Contains(expenditureItem.Id); } } return PartialView("_createScenarioExpenditures", ec); } [HttpGet] //[ValidateAntiForgeryToken] [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult GetProjectPartsByProjectId(Guid? Id) { Guid userId = SecurityManager.GetUserPrincipal(); var pp = Utils.GetProjectParts(Id, userId); return PartialView("_createScenarioProjectParts", pp); } [HttpPost] [AreaSecurityAttribute(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; if (!Guid.Empty.Equals(model.ExpenditureCategoryId)) { var gRates = (from c in DbContext.Rates where c.ExpenditureCategoryId == model.ExpenditureCategoryId && c.Type == (int)RateModel.RateType.Global select c); model.AddGlobalRange(gRates.ToList()); } if (Guid.Empty != model.ScenarioId && !Guid.Empty.Equals(model.ExpenditureCategoryId)) { var lRates = (from c in DbContext.Rates where c.ExpenditureCategoryId == model.ExpenditureCategoryId && c.Type == (int)RateModel.RateType.Derived && c.ParentId == model.ScenarioId select c); model.AddLocalRange(lRates.ToList()); } #endregion break; case RatesModel.FormMode.EditRate: #region Load Edit Rate Form if (model.EditRateId.HasValue && !Guid.Empty.Equals(model.EditRateId)) { var rate = DbContext.Rates.AsNoTracking().FirstOrDefault(t => t.Id == model.EditRateId); if (rate != null) { model.EditRate = new RateModel() { Id = rate.Id, DerivedObjectId = rate.DerivedId, EndDate = rate.EndDate, ExpenditureCategoryId = rate.ExpenditureCategoryId, FreezeRate = rate.FreezeRate, ParentId = rate.ParentId, Rate1 = rate.Rate1, StartDate = rate.StartDate, Type = (RateModel.RateType)rate.Type }; } } else { model.EditRate = new RateModel() { DerivedObjectId = Guid.Empty, EndDate = new DateTime(9999, 12, 31), ExpenditureCategoryId = model.ExpenditureCategoryId, FreezeRate = 0, ParentId = model.ScenarioId, Rate1 = 0, StartDate = new DateTime(1753, 1, 1), Type = RateModel.RateType.Derived }; } #endregion break; case RatesModel.FormMode.ConfirmEditRate: #region Save Rate if (ModelState.IsValid) { if (ContentLocker.IsLock("ExpenditureCategoryRate", model.EditRateId.ToString(), User.Identity.Name)) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); var manager = new RateManager(DbContext); manager.Save(model.EditRate); DbContext.SaveChanges(); manager.Dispose(); ContentLocker.RemoveLock("ExpenditureCategoryRate", model.EditRateId.ToString(), User.Identity.Name); model.Mode = RatesModel.FormMode.ListRates; // reset action mode model.EditRateId = Guid.Empty; model.DeleteRateId = Guid.Empty; if (!Guid.Empty.Equals(model.ExpenditureCategoryId)) { var gRates = (from c in DbContext.Rates where c.ExpenditureCategoryId == model.ExpenditureCategoryId && c.Type == (int)RateModel.RateType.Global select c); model.AddGlobalRange(gRates.ToList()); } if (Guid.Empty != model.ScenarioId && !Guid.Empty.Equals(model.ExpenditureCategoryId)) { var lRates = (from c in DbContext.Rates where c.ExpenditureCategoryId == model.ExpenditureCategoryId && c.Type == (int)RateModel.RateType.Derived && c.ParentId == model.ScenarioId select c); model.AddLocalRange(lRates.ToList()); } } else { model.Mode = RatesModel.FormMode.EditRate; HttpContext.Response.StatusCode = 500; HttpContext.Response.Clear(); } #endregion break; case RatesModel.FormMode.DeriveRate: #region Derive rate from global var sManager = new ScenarioManager(DbContext); var scenario = sManager.Load(model.ScenarioId); if (!Guid.Empty.Equals(model.ExpenditureCategoryId) && !Guid.Empty.Equals(model.ScenarioId)) { var rates = (from c in DbContext.Rates where c.ExpenditureCategoryId == model.ExpenditureCategoryId && c.Type == (int)RateModel.RateType.Global && c.StartDate <= scenario.EndDate && c.EndDate >= scenario.StartDate select c).ToList(); foreach (var rate in rates) { var derivedrates = (from c in DbContext.Rates where c.ExpenditureCategoryId == model.ExpenditureCategoryId && c.ParentId == model.ScenarioId && c.DerivedId == rate.Id select c).ToList(); DbContext.Rates.RemoveRange(derivedrates); var newrate = new Rate { Id = Guid.NewGuid(), Rate1 = rate.Rate1, StartDate = (rate.StartDate < scenario.StartDate) ? scenario.StartDate.Value : rate.StartDate, EndDate = (rate.EndDate > scenario.EndDate) ? scenario.EndDate.Value : rate.EndDate, ExpenditureCategoryId = rate.ExpenditureCategoryId, FreezeRate = rate.FreezeRate, ParentId = model.ScenarioId, Type = (short)RateModel.RateType.Derived, DerivedId = rate.Id }; DbContext.Rates.Add(newrate); DbContext.SaveChanges(); } } model.Mode = RatesModel.FormMode.ListRates; // reset action mode model.EditRateId = Guid.Empty; model.DeleteRateId = Guid.Empty; if (!Guid.Empty.Equals(model.ExpenditureCategoryId)) { var gRates = (from c in DbContext.Rates where c.ExpenditureCategoryId == model.ExpenditureCategoryId && c.Type == (int)RateModel.RateType.Global select c); model.AddGlobalRange(gRates.ToList()); } if (Guid.Empty != model.ScenarioId && !Guid.Empty.Equals(model.ExpenditureCategoryId)) { var lRates = (from c in DbContext.Rates where c.ExpenditureCategoryId == model.ExpenditureCategoryId && c.Type == (int)RateModel.RateType.Derived && c.ParentId == model.ScenarioId select c); model.AddLocalRange(lRates.ToList()); } #endregion break; case RatesModel.FormMode.ConfirmDeleteRate: #region delete rate if (!Guid.Empty.Equals(model.ExpenditureCategoryId) && !Guid.Empty.Equals(model.ScenarioId)) { var deleterates = (from c in DbContext.Rates where c.ExpenditureCategoryId == model.ExpenditureCategoryId && c.ParentId == model.ScenarioId select c); if (model.DeleteRateId != null && !Guid.Empty.Equals(model.DeleteRateId)) deleterates = deleterates.Where(t => t.Id == model.DeleteRateId); DbContext.Rates.RemoveRange(deleterates); DbContext.SaveChanges(); } model.DeleteRateId = Guid.Empty; model.EditRateId = Guid.Empty; model.Mode = RatesModel.FormMode.ListRates; // reset action mode if (!Guid.Empty.Equals(model.ExpenditureCategoryId)) { var gRates = (from c in DbContext.Rates where c.ExpenditureCategoryId == model.ExpenditureCategoryId && c.Type == (int)RateModel.RateType.Global select c); model.AddGlobalRange(gRates.ToList()); } if (Guid.Empty != model.ScenarioId && !Guid.Empty.Equals(model.ExpenditureCategoryId)) { var lRates = (from c in DbContext.Rates where c.ExpenditureCategoryId == model.ExpenditureCategoryId && c.Type == (int)RateModel.RateType.Derived && c.ParentId == model.ScenarioId select c); model.AddLocalRange(lRates.ToList()); } break; #endregion } return PartialView("_rates", model); } catch (BLLException blEx) { if (blEx.DisplayError) SetErrorScript(message: blEx.Message); else { LogException(blEx); SetErrorScript(); } model.Mode = RatesModel.FormMode.EditRate; } catch (Exception exception) { LogException(exception); SetErrorScript(); model.Mode = RatesModel.FormMode.EditRate; } if (ModelState.IsValid && (RatesModel.FormMode.ConfirmEditRate == model.Mode || RatesModel.FormMode.ConfirmDeleteRate == model.Mode) && Guid.Empty != model.EditRateId) ContentLocker.RemoveLock("ExpenditureCategoryRate", model.EditRateId.ToString(), User.Identity.Name); HttpContext.Response.StatusCode = 500; HttpContext.Response.Clear(); return PartialView("_rates", model); } /// /// Returns JSON UnitOfMeasure list with filters and sort for jQuery DataTables /// [HttpPost] [AreaSecurityAttribute(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); } 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": if (sortedColumn.Direction == SortingDirection.Ascending) query = query.OrderBy(c => c.Id); else query = query.OrderByDescending(c => c.Id); break; case "StartDate": if (sortedColumn.Direction == SortingDirection.Ascending) query = query.OrderBy(c => c.StartDate); else query = query.OrderByDescending(c => c.StartDate); break; case "EndDate": if (sortedColumn.Direction == SortingDirection.Ascending) query = query.OrderBy(c => c.EndDate); else query = query.OrderByDescending(c => c.EndDate); break; case "Duration": if (sortedColumn.Direction == SortingDirection.Ascending) query = query.OrderBy(c => c.Duration); else query = query.OrderByDescending(c => c.Duration); break; case "CGSplit": if (sortedColumn.Direction == SortingDirection.Ascending) query = query.OrderBy(c => c.CGSplit); else query = query.OrderByDescending(c => c.CGSplit); break; case "EFXSplit": if (sortedColumn.Direction == SortingDirection.Ascending) query = query.OrderBy(c => c.EFXSplit); else query = query.OrderByDescending(c => c.EFXSplit); break; case "ScenariosCount": if (sortedColumn.Direction == SortingDirection.Ascending) query = query.OrderBy(c => c.ScenariosCount); else query = query.OrderByDescending(c => c.ScenariosCount); break; default: if (sortedColumn.Direction == SortingDirection.Ascending) query = query.OrderBy(c => c.Name); else query = 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(); } // GET: /Scenarios/ [HttpGet] public ActionResult LoadExpenditures(Guid? id) { if (id == null || id == Guid.Empty) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); try { var manager = new ScenarioManager(DbContext); var model = manager.GetExpenditureCategories(id.Value); return PartialView("_expenditures", model); } catch (BLLException blEx) { if (blEx.DisplayError) SetErrorScript(message: blEx.Message); else { LogException(blEx); SetErrorScript(); } } catch (Exception exception) { LogException(exception); SetErrorScript(); } return PartialView("_expenditures", new List()); } [HttpPost] public ActionResult LoadJsonScenarioCalendar(ScenarioDetailsModel model) { if (model == null || model.ScenarioId == Guid.Empty) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); var scenario = DbContext.Scenarios.AsNoTracking().FirstOrDefault(t => t.Id == model.ScenarioId); if (scenario == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); model.StartDate = scenario.StartDate ?? DateTime.Today; model.EndDate = scenario.EndDate ?? DateTime.Today.AddYears(1); model.GrowthScenario = scenario.GrowthScenario; model.ScenarioId = scenario.Id; model.ParentId = scenario.ParentId ?? Guid.Empty; model.ScenarioType = (ScenarioType?)scenario.Type; if (scenario.Project != null) { model.YellowIndicator = scenario.Project.PerformanceYellowThreshold; if (!model.YellowIndicator.HasValue) model.YellowIndicator = scenario.Project.Type.PerformanceYellowThreshold; model.RedIndicator = scenario.Project.PerformanceRedThreshold; if (!model.RedIndicator.HasValue) model.RedIndicator = scenario.Project.Type.PerformanceRedThreshold; } return Json(GetScenarioCalendar(model), JsonRequestBehavior.AllowGet); } private class Pairs { public Guid Id { get; set; } public decimal Quantity { get; set; } } [HttpPost] public JsonResult GetMasterScenarioCalendar(MasterScenarioFindModel findModel) { if (findModel == null || findModel.ProjectId == Guid.Empty) return null; var actuals = new List(); 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); 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).Subtract(Constants.UnixEpochDate).TotalMilliseconds; actualsEndDateMs = (long)(activeScenarios.Where(x => x.Type == (int)ScenarioType.Actuals && x.EndDate.HasValue).Max(x => x.EndDate) ?? Constants.UnixEpochDate).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); } private ScenarioDetailsModel GetScenarioCalendar(ScenarioDetailsModel model) { DateTime periodStartDate; DateTime periodEndDate; long actualsStartDate = 0; long actualsEndDate = 0; Guid actualScenarioId = Guid.Empty; var allExpCatsDict = DbContext.ExpenditureCategory.Include(x => x.Expenditure).AsNoTracking().ToDictionary(x => x.Id); var allUomsDict = DbContext.UOMs.AsNoTracking().ToDictionary(x => x.Id); var user = new UsersCache().Value.FirstOrDefault(x => x.Id == new Guid(HttpContext.User.Identity.GetID())); if (!model.IsUOMHours.HasValue && user != null) { model.IsUOMHours = !user.PreferredResourceAllocation; } if (!model.PreferredTotalsDisplaying.HasValue && user != null) { model.PreferredTotalsDisplaying = user.PreferredTotalsDisplaying; } switch (model.ScenarioType) { case ScenarioType.LoanOut: case ScenarioType.Training: case ScenarioType.Vacation: periodStartDate = new DateTime(DateTime.Today.Year, 1, 1); periodEndDate = new DateTime(DateTime.Today.Year, 12, 31); break; default: periodStartDate = model.StartDate; periodEndDate = model.EndDate; break; } if (!model.GrowthScenario && !ScenarioType.Actuals.Equals(model.ScenarioType)) { var actualScenario = DbContext.Scenarios.FirstOrDefault( t => t.ParentId == model.ParentId && t.Type == (int?)ScenarioType.Actuals); if (actualScenario != null) { actualScenarioId = actualScenario.Id; actualsStartDate = (long)(actualScenario.StartDate ?? Constants.UnixEpochDate).Subtract(Constants.UnixEpochDate).TotalMilliseconds; actualsEndDate = (long)(actualScenario.EndDate ?? Constants.UnixEpochDate).Subtract(Constants.UnixEpochDate).TotalMilliseconds; if (actualScenario.StartDate < model.StartDate) periodStartDate = actualScenario.StartDate ?? Constants.UnixEpochDate; if (actualScenario.EndDate > model.EndDate) periodEndDate = actualScenario.EndDate ?? Constants.UnixEpochDate; } } #region Load scenario Data var filter = new ScenarioCalendarFilterModel() { CreditDepartment = model.CreditDepartment, GLAccount = model.GLAccount, LaborMaterials = model.LaborMaterials.HasValue ? model.LaborMaterials.Value.ToString() : null, CategoryType = model.CategoryType.HasValue ? model.CategoryType.Value.ToString() : null, IncomeType = model.IncomeType, SelectedExpCats = model.SelectedExpCats }; List scenarioDetailsList = GetScenarioDetailsProxy(filter, periodStartDate, periodEndDate, new List { model.ScenarioId }); List actualsDetailsList = null; if (model.ShowActuals && !Guid.Empty.Equals(actualScenarioId)) actualsDetailsList = GetScenarioDetailsProxy(filter, periodStartDate, periodEndDate, new List { actualScenarioId }); #endregion var calendar = GetCalendar(scenarioDetailsList, actualsDetailsList, new List { model.ScenarioId }, new List() { model.ParentId }, model.IsUOMHours ?? false, actualsStartDate, actualsEndDate, model.ShowActuals, model.PreferredTotalsDisplaying); model.Headers = calendar.Headers; model.ScenarioCalendar = calendar.Rows; model.AllResources = calendar.AllResources; model.ActualsStartDateMs = actualsStartDate; model.ActualsEndDateMs = actualsEndDate; return model; } 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 = new List(); if (showActuals && actualsDetailsList.Count > 0) weekEndingDates = forecastDetailsList.Union(actualsDetailsList).Select(t => Constants.UnixEpochDate.AddMilliseconds(t.WeekEndingDate)).Distinct().OrderBy(o => o).ToList(); else weekEndingDates = forecastDetailsList.Select(t => Constants.UnixEpochDate.AddMilliseconds(t.WeekEndingDate)).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, ScenarioDetailIds = new Guid[calendar.Headers.Count], CostValues = new decimal[calendar.Headers.Count], QuantityValues = new decimal[calendar.Headers.Count], UseType = ExpenditureCategoryModel.UseTypes.Calculated }; var resourcesByTeams = DbContext.Team2Project.Where(x => projects.Contains(x.ProjectId)). SelectMany(x => x.Team.PeopleResources).OrderBy(r => r.LastName); foreach (var teamResource in resourcesByTeams) { 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 allResourceVacations = DbContext.PeopleResourceVacations.Where(t => allResIds.Contains(t.PeopleResourceId)).Select(t => new { t.PeopleResourceId, t.HoursOff, t.WeekEndingDate }).ToArray(); var allResourceTrainings = DbContext.PeopleResourceTrainings.Where(t => allResIds.Contains(t.PeopleResourceId)).Select(t => new { t.PeopleResourceId, t.HoursOff, t.WeekEndingDate }).ToArray(); 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, ScenarioDetailIds = new Guid[calendar.Headers.Count], 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.WeekEndingDate)).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 { // needs only for single scenario calendar if (values.Count == 1) expCatRow.ScenarioDetailIds[colIndex] = values.First().Id; 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 ?? 0) * uomMultiplier; // set resource weekly availability (UOM.Value - vacations - loan offs - trainings) if (uomValue.Value != null) { var vacationsSum = allResourceVacations.Where(t => t.PeopleResourceId == x.Id && t.WeekEndingDate <= weekEnding && t.WeekEndingDate >= weekEnding.AddDays(-6)) .Sum(s => s.HoursOff); var trainingsSum = allResourceTrainings.Where(t => t.PeopleResourceId == x.Id && t.WeekEndingDate <= weekEnding && t.WeekEndingDate >= weekEnding.AddDays(-6)) .Sum(s => s.HoursOff); x.CapacityQuantityValues[colIndex] = (uomValue.Value.UOMValue - vacationsSum - trainingsSum) * 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.ScenarioDetailIds[colIndex] = Guid.Empty; 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; } totalRow.ScenarioDetailIds[colIndex] = Guid.Empty; 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.Where(x => !x.IsMonth).Count(); 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, ScenarioDetailIds = new Guid[calendar.Headers.Count], 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.WeekEndingDate)).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>(); headers.Add(ScenarioDetailsModel.HeaderPeriod.Month, new List()); headers.Add(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] where ((item as ScenarioDetailsModel.WeekHeader).DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal) && ((item as ScenarioDetailsModel.WeekHeader).Milliseconds >= forecastDataStartDateMs) && ((item as ScenarioDetailsModel.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] where ((item as ScenarioDetailsModel.WeekHeader).DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal) && ((item as ScenarioDetailsModel.WeekHeader).Milliseconds >= actualsViewDataStartDateMs) && ((item as ScenarioDetailsModel.WeekHeader).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] where ((item as ScenarioDetailsModel.WeekHeader).DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal) && ((item as ScenarioDetailsModel.WeekHeader).Milliseconds >= actualsDataStartDateMs) && ((item as ScenarioDetailsModel.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] .Where(x => (x as ScenarioDetailsModel.WeekHeader).MonthHeader == monthIndex && (x as ScenarioDetailsModel.WeekHeader).DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal && x.Visible[ScenarioDetailsModel.CalendarDataViewModes.Forecast]).Count(); int actualsVisibleWeeksCount = headers[ScenarioDetailsModel.HeaderPeriod.Week] .Where(x => (x as ScenarioDetailsModel.WeekHeader).MonthHeader == monthIndex && (x as ScenarioDetailsModel.WeekHeader).DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal && x.Visible[ScenarioDetailsModel.CalendarDataViewModes.Actuals]).Count(); int actualsNonEditableWeeksCount = headers[ScenarioDetailsModel.HeaderPeriod.Week] .Where(x => (x as ScenarioDetailsModel.WeekHeader).MonthHeader == monthIndex && (x as ScenarioDetailsModel.WeekHeader).DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal && !x.Editable[ScenarioDetailsModel.CalendarDataViewModes.Actuals]).Count(); 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; } /// /// Fill then ScenarioDetailModel model inner object collections, needed to render the view /// /// Scenario id /// Manager /// SA. ENV-707 private ScenarioDetailModel LoadScenarioDetailsModel(Guid id, ScenarioManager manager) { var model = (ScenarioDetailModel)manager.Load(id) ?? new ScenarioDetailModel(); if (model.CostSaving != null) model.CostSaving.CostSavingDescription = model.CostSavingsDescription; if (model.CostSaving != null) model.CostSaving.ScenarioStartDate = model.StartDate; if (model.Id == Guid.Empty || !model.Status.HasValue) return model; var actualScenario = DbContext.Scenarios.FirstOrDefault(x => x.ParentId == model.ParentId && x.Type == (int)ScenarioType.Actuals); if (actualScenario != null) { var actualSplitInfo = Utils.GetLaborMaterialsSplit(actualScenario.Id); if (actualScenario.StartDate.HasValue) model.ActualsStartDate = (long)actualScenario.StartDate.Value.Subtract(Constants.UnixEpochDate).TotalMilliseconds; if (actualScenario.EndDate.HasValue) model.ActualsEndDate = (long)actualScenario.EndDate.Value.Subtract(Constants.UnixEpochDate).TotalMilliseconds; model.ActualLabor = actualSplitInfo.Labor; model.ActualMaterials = actualSplitInfo.Materials; model.ActualsTotal = actualSplitInfo.Total; model.HasActuals = actualScenario.EndDate.HasValue; model.ActualLaborMaterialsSplit = actualSplitInfo.LaborAndMaterialSplit; } var forecastSplitInfo = Utils.GetLaborMaterialsSplit(model.Id); model.Expenditures = Utils.GetScenarioExpenditures(model.Id); model.ScenarioExpenditures = manager.GetExpenditureCategories(model.Id); model.LaborMaterialsSplit = forecastSplitInfo.LaborAndMaterialSplit; #region Override Actuals fields if there are no actuals data // SA. ENV-698. Show Forecast value as Actual Bottom-Up Direct Costs, if Project has no actuals. Begin bool actualDataExists = false; if (actualScenario != null) // Actual data scenario exists actualDataExists = actualScenario.EndDate.HasValue; model.DateForStartOfChanges = model.StartDate.HasValue && model.StartDate.Value > DateTime.Today ? model.StartDate.Value.Date : DateTime.Today; if (!actualDataExists) { // Show nulls and forecast values as actuals, if actuals not found model.BUDirectCostsActuals = model.BUDirectCosts; model.BUDirectCostsLMActuals = model.BUDirectCostsLM; model.BUCostsShotsActuals = model.BUCostsShots; model.BUCostsShotsLMActuals = model.BUCostsShotsLM; model.CalculatedGrossMarginActuals = null; model.CalculatedGrossMarginLMActuals = null; model.ActualRevenueAfterCost = null; model.ActualLabor = null; model.ActualLaborMaterialsSplit = null; model.ActualMaterials = null; } else { // date for start of changes should be bigger than actuals end date if (actualScenario.EndDate >= model.DateForStartOfChanges) model.DateForStartOfChanges = actualScenario.EndDate.Value.AddDays(1); } // SA. ENV-698. End #endregion return model; } 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"); ScenarioDetailModel model = null; model = new ScenarioDetailModel { ParentId = parentId, Name = sm.ScenarioName, GrowthScenario = sm.GrowthScenario, //UseLMMargin = sm.Step3.UseLMMargin, //GrossMargin = sm.Step3.Margin != null ? (int) sm.Step3.Margin : 0, TemplateId = sm.TemplateId ?? Guid.Empty, StartDate = sm.StartDate, EndDate = sm.EndDate, //IsRevenueGenerating = sm.Project.IsRevenueGenerating, Id = sm.ScenarioId, ScenarioExpenditures = null != sm.ScenarioExpenditures ? sm.ScenarioExpenditures.Where(t => t.Checked).ToList() : null, }; 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 = Utils.GetLaborMaterialsSplit(actualScenario.Id); if (actualScenario.StartDate.HasValue) model.ActualsStartDate = (long)actualScenario.StartDate.Value.Subtract(Constants.UnixEpochDate).TotalMilliseconds; if (actualScenario.EndDate.HasValue) model.ActualsEndDate = (long)actualScenario.EndDate.Value.Subtract(Constants.UnixEpochDate).TotalMilliseconds; model.ActualLabor = actualSplitInfo.Labor; model.ActualMaterials = actualSplitInfo.Materials; model.ActualsTotal = actualSplitInfo.Total; model.HasActuals = actualScenario.EndDate.HasValue; model.ActualLaborMaterialsSplit = actualSplitInfo.LaborAndMaterialSplit; } var forecastSplitInfo = Utils.GetLaborMaterialsSplit(model.TemplateId); model.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.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.BUDirectCostsActuals = model.BUDirectCosts; model.BUDirectCostsLMActuals = model.BUDirectCostsLM; model.BUCostsShotsActuals = model.BUCostsShots; model.BUCostsShotsLMActuals = model.BUCostsShotsLM; model.CalculatedGrossMarginActuals = model.CalculatedGrossMargin; model.CalculatedGrossMarginLMActuals = model.CalculatedGrossMarginLM; } else { // date for start of changes should be bigger than actuals end date if (actualScenario.EndDate >= model.DateForStartOfChanges) model.DateForStartOfChanges = actualScenario.EndDate.Value.AddDays(1); } } #endregion return model; } // GET: /Scenarios/Details/5 [HttpGet] [AreaSecurityAttribute(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); ScenarioDetailModel model = null; var manager = new ScenarioManager(DbContext); try { model = LoadScenarioDetailsModel(id.Value, manager); if (model.Id == Guid.Empty) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } model.ActiveTab = tab; // SA. Param names where restored. 'ref' changed to 'backUrl' and 'back' changed to 'backName' ENV-707. Begin model.BackUrl = !string.IsNullOrEmpty(backUrl) ? backUrl : Url.Action("Index", "Project"); model.BackName = !string.IsNullOrEmpty(backName) ? backName : "list"; // SA. ENV-707. End } catch (BLLException blEx) { if (blEx.DisplayError) SetErrorScript(message: blEx.Message); else { LogException(blEx); SetErrorScript(); } } catch (Exception exception) { LogException(exception); SetErrorScript(); } return View(model); } [HttpGet] public ActionResult GetScenarioAvailableExpCategories(Guid id) { return Json(GetScenarioCategories(id), JsonRequestBehavior.AllowGet); } 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 categoties = 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 = (from PeopleResource pr in DbContext.PeopleResources.AsNoTracking() where pr.TeamId.HasValue && teams.Contains(pr.TeamId.Value) select pr.ExpenditureCategoryId).ToList(); // Mark related to teams categories categoties.Where(x => scenarioCategories.Contains(x.Id)).ToList().ForEach(x => x.IsRelatedToScenario = 1); } return categoties; } [HttpPost] public ActionResult GetRates(Guid scenarioId, bool? uomMode) { // temporary solution, action method will be removed var allExpCats = DbContext.ExpenditureCategory.AsNoTracking().ToDictionary(x => x.Id); var allUoms = DbContext.UOMs.AsNoTracking().ToDictionary(x => x.Id); 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 rates = (new ScenarioManager(DbContext)).GetRates(scenarioId); foreach (var expCatData in rates) { foreach (var rate in expCatData.rateValues) rate.rateValue = Utils.GetUOMMultiplier(allExpCats, allUoms, expCatData.expCatId, uomMode); } return Json(rates); } [HttpPost] [AreaSecurityAttribute(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, "Invalid parameters"); else { try { var newScenario = new ScenarioManager(DbContext).CopyTo(model.ScenarioId, model.TargetProjectId, model.TargetStatus, model.includeCostSavings); return Json(newScenario); } 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); SetErrorScript(); } } catch (Exception exception) // handle any unexpected error { LogException(exception); SetErrorScript(); } } HttpContext.Response.StatusCode = 500; HttpContext.Response.Clear(); return PartialView("_copyToModal", model); } [HttpPost] [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult SaveChanges(SaveScenarioDetailsChangesModel model) { if (model.ScenarioId != Guid.Empty && ContentLocker.IsLock("Scenario", model.ScenarioId.ToString(), User.Identity.Name)) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); var context = new EnVisageEntities(); model.TrimStringProperties(); var allExpCats = DbContext.ExpenditureCategory.AsNoTracking().ToDictionary(x => x.Id); var allUoms = DbContext.UOMs.AsNoTracking().ToDictionary(x => x.Id); if (!model.ScenarioFilters.IsUOMHours.HasValue) { var user = new UsersCache().Value.FirstOrDefault(x => x.Id == new Guid(HttpContext.User.Identity.GetID())); if (user != null) model.ScenarioFilters.IsUOMHours = !user.PreferredResourceAllocation; } if (ModelState.IsValid) { try { var manager = new ScenarioManager(context); var scenario = manager.Load(model.ScenarioId, false); var scenarioStartDateMsSince1970 = scenario.StartDate.HasValue ? (long)scenario.StartDate.Value.Subtract(Constants.UnixEpochDate).TotalMilliseconds : 0; foreach (var changedExpCat in model.ChangedExpCats) { var uomMultiplier = Utils.GetUOMMultiplier(allExpCats, allUoms, changedExpCat.Id, model.ScenarioFilters.IsUOMHours); foreach (var changedColumn in changedExpCat.Values) { if (changedColumn.Milliseconds >= scenarioStartDateMsSince1970) { var scenarioDetailsItem = (from c in context.ScenarioDetail where c.Id == changedColumn.Id select c).FirstOrDefault();//manager.LoadScenarioDetail(changedColumn.Id, false); if (scenarioDetailsItem == null) continue; if (scenarioDetailsItem.Id == Guid.Empty) { throw new NullReferenceException(string.Format("Scenario Details {0} mising", changedColumn.Id)); } scenarioDetailsItem.Quantity = changedColumn.Quantity / uomMultiplier; scenarioDetailsItem.Cost = changedColumn.Cost; } } if (changedExpCat.Resources != null) { var resourceIds = changedExpCat.Resources.Select(x => x.Id).ToList(); var resourceAllocations = context.PeopleResourceAllocations.Where(x => resourceIds.Contains(x.PeopleResourceId) && x.ScenarioId == model.ScenarioId).ToList(); foreach (var resource in changedExpCat.Resources) { var resourceId = resource.Id; if (resource.IsRemoved) { var recourcesToDelete = context.PeopleResourceAllocations.Where(x => x.PeopleResourceId == resourceId && x.ScenarioId == model.ScenarioId).ToList(); recourcesToDelete.ForEach(x => context.PeopleResourceAllocations.Remove(x)); recourcesToDelete.ForEach(x => context.Entry(x).State = EntityState.Deleted); } else { foreach (var changedResource in resource.Values) { var date = Constants.UnixEpochDate.AddSeconds(changedResource.Milliseconds / 1000); var allocatedResource = (from c in resourceAllocations where c.WeekEndingDate == date && c.ScenarioId == model.ScenarioId && c.PeopleResourceId == resourceId && c.ExpenditureCategoryId == changedExpCat.Id select c).FirstOrDefault(); if (changedResource.Quantity <= 0) { if (allocatedResource != null) context.Entry(allocatedResource).State = EntityState.Deleted; continue; } if (allocatedResource == null) { allocatedResource = context.PeopleResourceAllocations.Create(); allocatedResource.Id = Guid.NewGuid(); allocatedResource.ExpenditureCategoryId = changedExpCat.Id; allocatedResource.PeopleResourceId = resourceId; allocatedResource.ScenarioId = model.ScenarioId; allocatedResource.WeekEndingDate = date; context.Entry(allocatedResource).State = EntityState.Added; } else { context.Entry(allocatedResource).State = EntityState.Modified; } allocatedResource.Quantity = changedResource.Quantity / uomMultiplier; } } } } } //TODO: review that we really need 2 transactions here. Looks like it was here since bottom-up costs recalculation was in stored procedure. context.SaveChanges(); manager.SetBottomUpCosts(scenario); context.SaveChanges(); model.ScenarioFilters.StartDate = scenario.StartDate ?? DateTime.Today; model.ScenarioFilters.EndDate = scenario.EndDate ?? DateTime.Today.AddYears(1); model.ScenarioFilters.GrowthScenario = scenario.GrowthScenario; model.ScenarioFilters.ParentId = scenario.ParentId ?? Guid.Empty; model.ScenarioFilters.ScenarioType = (ScenarioType?)scenario.Type; //var detailsGrid = GetScenarioCalendar(model.ScenarioFilters); 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); SetErrorScript(); } } catch (Exception exception) // handle any unexpected error { LogException(exception); SetErrorScript(); } } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [HttpPost] [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult AddNote(NoteModel model) { //if (model.ScenarioId != Guid.Empty && ContentLocker.IsLock("Scenario", model.ScenarioId.ToString(), User.Identity.Name)) // return new HttpStatusCodeResult(HttpStatusCode.BadRequest); model.TrimStringProperties(); if (ModelState.IsValid) { try { model.Id = Guid.NewGuid(); var newnote = new Note(); model.CopyTo(newnote); newnote.UserId = new Guid(User.Identity.GetID()); DbContext.Notes.Add(newnote); DbContext.SaveChanges(); 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] [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult EditNote(NoteModel model) { //if (model.ScenarioId != Guid.Empty && ContentLocker.IsLock("Scenario", model.ScenarioId.ToString(), User.Identity.Name)) // return new HttpStatusCodeResult(HttpStatusCode.BadRequest); model.TrimStringProperties(); if (ModelState.IsValid) { try { var note = (from c in DbContext.Notes where c.Id == model.Id select c).FirstOrDefault(); if (note == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); note.Title = model.Title; note.NoteDetail = model.Details; DbContext.SaveChanges(); 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] [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult DeleteNote(Guid id) { if (id == Guid.Empty) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); var note = (from c in DbContext.Notes where c.Id == id select c).FirstOrDefault(); if (note == null) { return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } else { try { DbContext.Notes.Remove(note); DbContext.SaveChanges(); 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 [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult EditNote(Guid id) { if (id == Guid.Empty) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); var note = DbContext.Notes.AsNoTracking().FirstOrDefault(x => x.Id == id); if (note == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); else return PartialView("_addNote", (NoteModel)note); } // GET: /User/Edit/5 [AreaSecurityAttribute(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 { var notesModel = new List(); var notes = DbContext.Notes.Where(x => x.ParentId == scenarioId).OrderBy(x => x.DateAdded).ToList(); foreach (var note in notes) notesModel.Add((NoteModel)note); 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); } [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult EditRate(Guid? id, Guid? parentId, Guid expentureCategoryId) { RateModel model = null; if (id.HasValue && id != Guid.Empty) { var manager = new RateManager(DbContext); model = (RateModel)manager.Load(id); } else { var sManager = new ScenarioManager(DbContext); var scenario = sManager.Load(parentId); model = new RateModel(); model.Type = RateModel.RateType.Derived; var lRates = (from c in DbContext.Rates where c.ExpenditureCategoryId == expentureCategoryId && c.Type == (int)RateModel.RateType.Derived && c.ParentId == parentId select c).ToList(); if ((from d in lRates select d.EndDate).Max() < DateTime.MaxValue.AddYears(-4)) { model.StartDate = (lRates.Count == 0) ? scenario.StartDate.Value : ((from d in lRates select d.EndDate).Max()).AddDays(1); model.EndDate = (scenario.EndDate.Value <= model.StartDate) ? model.StartDate.AddDays(1) : scenario.EndDate.Value; } else { model.StartDate = (from d in lRates select d.EndDate).Max(); model.EndDate = model.StartDate.AddDays(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] [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult EditRate(RateModel model) { if (ContentLocker.IsLock("Rate", model.Id.ToString(), User.Identity.Name)) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); if (ModelState.IsValid) { try { var manager = new RateManager(DbContext); manager.Save(model); DbContext.SaveChanges(); manager.Dispose(); return JavaScript("window.location.search += '&tab=rateTable';"); } catch (Exception exception) { LogException(exception); SetErrorScript(); } } ContentLocker.RemoveLock("Rate", model.Id.ToString(), User.Identity.Name); return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult DeleteRate(Guid? id) { RateModel model = null; if (id.HasValue && id != Guid.Empty) { var manager = new RateManager(DbContext); model = (RateModel)manager.Load(id); } else model = new RateModel(); return PartialView("_deleteRate", model); } [HttpPost] [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult DeleteRate(RateModel model) { // SA. ENV-1011. Removed model validation, because model has no enough data to be valid. // For Reate deletion we need Id only if ((model == null) || model.Id.Equals(Guid.Empty)) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); if (ContentLocker.IsLock("Rate", model.Id.ToString(), User.Identity.Name)) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); var dbObj = DbContext.Rates.FirstOrDefault(x => x.Id == model.Id); DbContext.Rates.Remove(dbObj); DbContext.SaveChanges(); ContentLocker.RemoveLock("Rate", model.Id.ToString(), User.Identity.Name); return ProcessRates(new RatesModel() { ExpenditureCategoryId = model.ExpenditureCategoryId, ScenarioId = model.ParentId.Value, Mode = RatesModel.FormMode.ListRates }); } [HttpGet] public JsonResult GetTeamCapacityScenarioId(Guid teamId) { using (var context = new EnVisageEntities()) { var data = (from sd in context.Teams where sd.Id == teamId select sd.PlannedCapacityScenarioId).FirstOrDefault(); if (data == null) { var teamManager = new TeamManager(DbContext); var team = teamManager.Load(teamId, false); var scen = new Scenario(); scen.Name = team.Name.Trim() + " Planned Capacity"; scen.Type = (int)ScenarioType.TeamPlannedCapacity; scen.Id = Guid.NewGuid(); team.PlannedCapacityScenarioId = scen.Id; DbContext.Scenarios.Add(scen); DbContext.SaveChanges(); data = scen.Id; } return Json((data == null) ? string.Empty : 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] [AreaSecurityAttribute(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 new HttpStatusCodeResult(HttpStatusCode.Unauthorized); 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] [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult Delete(ScenarioModel model) { if (ContentLocker.IsLock("Scenario", model.Id.ToString(), User.Identity.Name)) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); var manager = new ScenarioManager(DbContext); var dbObj = manager.Load(model.Id, false); if (dbObj == null) return HttpNotFound(); (DbContext as IObjectContextAdapter).ObjectContext.ExecuteStoreCommand(string.Format("exec sp_DeleteScenario '{0}'", dbObj.Id)); DbContext.SaveChanges(); ContentLocker.RemoveLock("Scenario", dbObj.Id.ToString(), User.Identity.Name); if (Request["backUrl"] != null) { return Redirect(Request["backUrl"]); } else { return RedirectToAction("Index", "Project"); } } [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult AdjustMargin(Guid? id) { var manager = new ScenarioManager(DbContext); var scenario = (ScenarioModel)manager.Load(id, false) ?? new ScenarioModel(); var project = DbContext.Projects.FirstOrDefault(x => x.Id == scenario.ParentId); var model = new UpdateScenarioModel() { Id = scenario.Id, EndDate = scenario.EndDate, Expenditures = scenario.Expenditures, GrossMargin = scenario.GrossMargin, LMMargin = scenario.LMMargin, TDDirectCosts = scenario.TDDirectCosts, ProjectedRevenue = scenario.ProjectedRevenue, StartDate = scenario.StartDate, UseLMMargin = scenario.UseLMMargin, AllowAdjustment = scenario.AllowAdjustment, PriorWeekCutOff = scenario.PriorWeekCutOff ?? scenario.StartDate, BUDirectCosts = scenario.BUDirectCosts }; if (project != null) model.IsRevenueGenerating = project.IsRevenueGenerating; else model.IsRevenueGenerating = false; model.Expenditures = manager.GetExpenditureCategories(model.Id); return PartialView("_adjustMargin", model); } [HttpPost] [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult AdjustMargin(UpdateScenarioModel model, Guid[] expCatId, string[] expCatName, string[] expCatGroup, bool[] expCatChecked) { if (ContentLocker.IsLock("Scenario", model.Id.ToString(), User.Identity.Name)) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); model.TrimStringProperties(); if (expCatId != null && expCatName != null && expCatGroup != null && expCatChecked != null && expCatId.Length == expCatName.Length && expCatId.Length == expCatGroup.Length && expCatId.Length == expCatChecked.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 = expCatChecked[i] }; } } try { // get scenario by id var manager = new ScenarioManager(DbContext); var scenario = (ScenarioModel)manager.Load(model.Id); var project = DbContext.Projects.FirstOrDefault(x => x.Id == scenario.ParentId); if (project != null) model.IsRevenueGenerating = project.IsRevenueGenerating; else model.IsRevenueGenerating = false; if (ModelState.IsValid) { // update scenario values scenario.StartDate = model.StartDate; scenario.EndDate = model.EndDate; // SA. ENV-698. GrossMargin and LMMargin adjustment was fixed. Begin if (model.UseLMMargin) // User selected the LM Margin adjustment only scenario.LMMargin = model.LMMargin; else // User selected the Gross Margin adjustment only scenario.GrossMargin = model.GrossMargin; // The original code lines: // scenario.GrossMargin = model.UseLMMargin ? null : model.GrossMargin;//!model.UseLMMargin ? model.GrossMargin / 100 : null; // scenario.LMMargin = model.UseLMMargin ? model.LMMargin : null; // SA. ENV-698. End scenario.TDDirectCosts = model.TDDirectCosts; scenario.ProjectedRevenue = model.ProjectedRevenue; scenario.PriorWeekCutOff = model.PriorWeekCutOff; scenario.AllowAdjustment = model.AllowAdjustment; scenario.UseLMMargin = model.UseLMMargin; scenario.Expenditures = model.Expenditures; scenario.FreezeResource = model.AllowAdjustment; manager.Save(scenario); ContentLocker.RemoveLock("Scenario", model.Id.ToString(), User.Identity.Name); 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.Name); return PartialView("_adjustMargin", model); } [HttpPost] [AreaSecurityAttribute(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.Name)) 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 == true 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 char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); var savedGroups = DbContext.TemplateGroups.Where(x => groups.Contains(x.Name)).ToList(); var currentTemplategroups = new List(); foreach (var item in groups) { currentTemplategroups.Add(item); 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(); ContentLocker.RemoveLock("Scenario", model.Id.ToString(), User.Identity.Name); 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.Name); return PartialView("_createTemplateModal", model); } [HttpPost] [AreaSecurityAttribute(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 = "Parameter scenarioId or expenditureCategoryId can not be null or empty." }); if (ContentLocker.IsLock("Scenario", scenarioId.ToString(), User.Identity.Name)) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); try { var ratesForDelete = DbContext.Rates .Where(x => x.ParentId == scenarioId && x.ExpenditureCategoryId == expenditureCategoryId && x.Type == (short)RateModel.RateType.Derived); DbContext.Rates.RemoveRange(ratesForDelete); DbContext.SaveChanges(); ContentLocker.RemoveLock("Scenario", scenarioId.ToString(), User.Identity.Name); return Json(new { Status = "Ok" }); } catch (Exception exception) { LogException(exception); return Json(new { Status = "Error", Msg = exception.Message }); } } [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 model = new ScenarioDetailSnapshotModel() { Scenario = recalculationModel.Scenario, CalendarFilter = recalculationModel.CalendarFilter, AvailableExpenditures = recalculationModel.AvailableExpenditures, TeamsInScenario = recalculationModel.TeamsInScenario, Rates = recalculationModel.Rates, NeedToRebind = recalculationModel.NeedToRebind, NeedToRecalculateScenarioDetails = recalculationModel.NeedToRecalculateScenarioDetails, NeedToRefreshAllTeams = recalculationModel.NeedToRefreshAllTeams, LocalRatesLoaded = recalculationModel.LocalRatesLoaded, Calendar = new ScenarioCalendarModel() { Expenditures = recalculationModel.Calendar != null ? recalculationModel.Calendar.Expenditures : new Dictionary() } }; if (model.NeedToRebind) { var actualScenarioId = Guid.Empty; var periodStartDate = new DateTime(DateTime.Today.Year, 1, 1); var periodEndDate = new DateTime(DateTime.Today.Year, 12, 31); // 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 = DbContext.Projects.FirstOrDefault(x => x.Id == 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 = DbContext.Scenarios.FirstOrDefault(t => t.ParentId == model.Scenario.ParentId && t.Type == (int?)ScenarioType.Actuals); if (actualScenario != null) { actualScenarioId = actualScenario.Id; model.Scenario.ActualStartDate = (long)(actualScenario.StartDate ?? Constants.UnixEpochDate).Subtract(Constants.UnixEpochDate).TotalMilliseconds; model.Scenario.ActualEndDate = (long)(actualScenario.EndDate ?? Constants.UnixEpochDate).Subtract(Constants.UnixEpochDate).TotalMilliseconds; if (actualScenario.StartDate < Constants.UnixEpochDate.AddMilliseconds(model.Scenario.StartDate)) periodStartDate = actualScenario.StartDate ?? Constants.UnixEpochDate; if (actualScenario.EndDate > Constants.UnixEpochDate.AddMilliseconds(model.Scenario.EndDate)) periodEndDate = actualScenario.EndDate ?? Constants.UnixEpochDate; 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.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 (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 scenario was inited with scenario details we do not need to refresh teams info on this step model.Calendar = BuildScenarioDetailsCalendarData(forecast, actuals); if (model.Calendar != null && model.Calendar.Expenditures != null) { // if there is no preloaded data we should recalculate team information for scenario if (!scenarioDetailsPreLoaded) { model.TeamsInScenario = scenarioManager.FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, model.Calendar.Expenditures, HttpContext.User.Identity.GetID()); } 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]; } } } } } // SA. ENV-840 List scenarioTeamIds = model.TeamsInScenario.Select(x => x.TeamId).ToList(); model.CategoryTypes = Utils.CastEnumToSelectedList(); model.CGEFX = Utils.GetCGEFX().ToList(); model.IncomeTypes = Utils.GetIncomeTypes().ToList(); model.GLAccounts = Utils.GetGLAccounts().ToList(); model.CreditDepartments = Utils.GetCreditDepartments().ToList(); model.Teams = Utils.GetTeams(User.Identity.GetID()).ToList(); var id = model.Scenario.Id ?? model.Scenario.TemplateId; if (!model.LocalRatesLoaded) { model.Rates = scenarioManager.GetRates(id); } 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 (model.NeedToRecalculateScenarioDetails) { var rates = new Dictionary>(); foreach (var rateModel in model.Rates) { var rateValues = new Dictionary(); foreach (var rateValue in rateModel.rateValues) rateValues.Add(Utils.ConvertFromUnixDate(rateValue.weekEndDate), rateValue.rateValue); rates.Add(rateModel.expCatId, rateValues); } 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); var isUpdate = false; var currentPeriods = 0; var scenarioDetails = scenarioManager.PrepareScenarioDetails(model.Scenario, model.AvailableExpenditures, actuals, rates, forecast, fiscalCalendars, out isUpdate, out currentPeriods); forecast = new List(); foreach (var ec in scenarioDetails) { foreach (var entity in ec.Value) { forecast.Add(new VW_ScenarioAndProxyDetails() { Id = entity.Id, ExpenditureCategoryId = entity.ExpenditureCategoryId, ExpenditureCategoryName = string.Empty, ExpCategoryWithCcName = entity.ExpenditureCategoryName, // SA. ENV-839 WeekEndingDate = entity.WeekEndingDate, LastUpdate = entity.LastUpdate, 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 }); } } model.Calendar = BuildScenarioDetailsCalendarData(forecast, actuals); 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 != null && model.Calendar.Expenditures != null) { #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 == null ? null : model.TeamsInScenario.ToDictionary(x => x.TeamId, g => g.Allocation); // SA. ENV-1001. Removed ProjectId param from the method invoke below. Original code: //FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, model.Scenario.ParentId.Value, newExpCats, teams); scenarioManager.FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, newExpCats, HttpContext.User.Identity.GetID(), teams); } #endregion #region Filling information about new only teams for all categories var teams2Add = model.TeamsInScenario != null ? model.TeamsInScenario.Where(x => x.IsNew).ToDictionary(x => x.TeamId, g => g.Allocation) : null; if (teams2Add != null && teams2Add.Count > 0) { model.TeamsInScenario.ForEach(x => x.IsNew = false); // SA. ENV-1001. Removed ProjectId param from the method invoke below. Original code: //FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, model.Scenario.ParentId.Value, model.Calendar.Expenditures, teams2Add); scenarioManager.FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, model.Calendar.Expenditures, HttpContext.User.Identity.GetID(), teams2Add); } #endregion #region Recalculation all teams for all categories if (model.NeedToRefreshAllTeams) { model.TeamsInScenario = scenarioManager.FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, model.Calendar.Expenditures, HttpContext.User.Identity.GetID()); } #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; } return Json(model); } private ScenarioCalendarModel BuildScenarioDetailsCalendarData(List forecastDetails, List actualsDetails) { if (forecastDetails == null) forecastDetails = new List(); if (actualsDetails == null) actualsDetails = new List(); var forecastWeekEndings = forecastDetails.Where(x => x.WeekEndingDate.HasValue).Select(x => x.WeekEndingDate.Value).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 != null) && (forecastWeekEndings.Count > 0); var actualsIsExists = (actualsWeekEndings != null && 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) return new ScenarioCalendarModel() { Expenditures = (new ScenarioManager(DbContext)).BuildExpendituresForScenario(weekEndings, forecastDetails, actualsDetails), Headers = BuildHeadersExForScenario(weekEndings, forecastDataStartDateMs, forecastDataEndDateMs, actualsViewDataStartDateMs, actualsViewDataEndDateMs, actualsDataStartDateMs, actualsDataEndDateMs) }; // SA. ENV-667. End } [HttpPost] [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult SaveSnapshotChanges(ScenarioDetailsSnapshotSaveModel model) { if (ModelState.IsValid) { try { var scenarioManager = new ScenarioManager(DbContext); var scenarioId = scenarioManager.Save(model); DbContext.SaveChanges(); // need to refresh project access permissions after teams was changed (new ProjectAccessCache()).Invalidate(); // 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 { // 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 { LogException(exception); SetErrorScript(); } } return new HttpStatusCodeResult(HttpStatusCode.InternalServerError); } [HttpPost] [AreaSecurityAttribute(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.Where(t => t.Type == (int)FiscalCalendarModel.FiscalYearType.Week && t.EndDate >= startDate && t.EndDate <= tempEndDate && t.NonWorking == 0 && t.AdjustingPeriod == false) .Count(); return Json(duration); } } }