EnVisageOnline/Main/Source/EnVisage/Controllers/ScenariosController.cs

2890 lines
108 KiB
C#

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