2890 lines
108 KiB
C#
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
|
|
}
|
|
} |