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

3009 lines
149 KiB
C#

using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Net;
using EnVisage.App_Start;
using EnVisage.Code.BLL;
using EnVisage.Code.Cache;
using EnVisage.Code.HtmlHelpers;
using EnVisage.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web.Mvc;
using jQuery.DataTables.Mvc;
using EnVisage.Code;
using Microsoft.AspNet.Identity;
using Newtonsoft.Json;
using System.Linq.Expressions;
using System.IO;
namespace EnVisage.Controllers
{
[Authorize]
public class ScenariosController : BaseController
{
// GET: /Scenarios/Templates
[HttpGet]
[AreaSecurityAttribute(area = Areas.ScenarioTemplates, level = AccessLevel.Read)]
public ActionResult Templates()
{
if (!SecurityManager.CheckSecurityObjectPermission(Areas.ScenarioTemplates, AccessLevel.Read))
return Redirect("/");
return View();
}
[HttpGet]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult LoadScenario(ScenarioLoadModel loadModel)
{
if (loadModel == null)
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
CreateScenarioModel model = new CreateScenarioModel()
{
SaveCallback = loadModel.SaveCallback,
CanSaveDraft = loadModel.CanSaveDraft
};
model.ScenarioId = Guid.Empty;
model.Step1.TemplateId = Guid.Empty;
model.Step1.Project = DbContext.Projects.AsNoTracking().FirstOrDefault(p => p.Id == loadModel.Id);
if (null == model.Step1.Project)
return new HttpNotFoundResult();
model.Step1.ProjectDeadline = model.Step1.Project.Deadline;
model.Step1.StartDate = loadModel.StartDate;
// SA. ENV-773. Begin
model.Step1.Teams = new SlidersGroupModel
{
GroupId = Guid.NewGuid(),
Options = Utils.GetTeams(false)
};
int projectTeamsCount = model.Step1.Project.Team2Project.Count();
int slidersTotalSumm = 100;
double remainingValue = slidersTotalSumm;
List<SliderModel> projectTeamModels =
(from Team2Project teamRec in model.Step1.Project.Team2Project
select new SliderModel()
{
Id = teamRec.Id,
EntityId = teamRec.TeamId,
Name = teamRec.Team.Name,
ParentId = model.Step1.Teams.GroupId,
AllocatePercentage = Math.Round((double)slidersTotalSumm / projectTeamsCount, 0)
}).ToList();
for (int index = 0; index < projectTeamModels.Count; index++)
{
if ((projectTeamModels.Count - index) != 1)
remainingValue -= Convert.ToInt32(projectTeamModels[index].AllocatePercentage);
else
// The last slider
projectTeamModels[index].AllocatePercentage = remainingValue;
}
if (loadModel.TeamId.HasValue && !projectTeamModels.Exists(x => x.EntityId == loadModel.TeamId.Value))
{
projectTeamModels.Add(new SliderModel()
{
Id = loadModel.TeamId.Value,
EntityId = loadModel.TeamId.Value,
Name = loadModel.TeamName,
ParentId = model.Step1.Teams.GroupId,
AllocatePercentage = 0
});
}
model.Step1.Teams.Sliders = projectTeamModels;
// SA. ENV-773. End
if (model.Step1.Project.ParentProjectId.HasValue)
{
model.Step1.ProjectId = model.Step1.Project.ParentProjectId.Value;
model.Step1.PartId = loadModel.Id;
}
else
{
model.Step1.ProjectId = loadModel.Id;
}
model.Step3.IsRevenueGenerating = model.Step1.Project.IsRevenueGenerating;
model.Step1.StatusIsEditable = loadModel.StatusIsEditable;
model.CurrentStep = "Step1";
return PartialView("_createScenario", model);
}
[HttpPost]
[ValidateAntiForgeryToken]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult SubmitCreateScenarioStep1(CreateScenarioModel.GeneralInfoModel model, string[] expCatGroups)
{
model.TrimStringProperties();
try
{
//var teamallocations =
// Newtonsoft.Json.JsonConvert.DeserializeObject<Dictionary<Guid, short>>(model.TeamAllocations);
#region Populate fields not stored on client
var projman = new ProjectManager(DbContext);
var project = projman.Load(model.PartId ?? model.ProjectId);
model.Project = project;
model.ProjectDeadline = project.Deadline;
var ecmanager = new ScenarioManager(DbContext);
var ec = ecmanager.GetExpenditureCategories(model.TemplateId);
model.ScenarioExpenditures = new ScenarioModel.ExpenditureItem[ec.Count()];
List<string> selectedIDs;
if (null == expCatGroups)
selectedIDs = new List<string>();
else
selectedIDs = expCatGroups.ToList();
var checkedECs = ec.Where(expenditureItem => selectedIDs.Contains(expenditureItem.Id.ToString())).ToList();
var uncheckedECs = ec.Where(item => !selectedIDs.Contains(item.Id.ToString())).ToList();
int i = 0;
foreach (var expenditureItem in uncheckedECs)
{
model.ScenarioExpenditures[i] = new ScenarioModel.ExpenditureItem
{
Id = expenditureItem.Id,
Group = expenditureItem.Group,
Name = expenditureItem.Name,
Checked = false
};
i++;
}
foreach (var expenditureItem in checkedECs)
{
model.ScenarioExpenditures[i] = new ScenarioModel.ExpenditureItem
{
Id = expenditureItem.Id,
Group = expenditureItem.Group,
Name = expenditureItem.Name,
Checked = true
};
i++;
}
var projTeamIds = model.Project.Team2Project.Select(x => x.TeamId).ToList();
//var missingTeams = teamallocations.Keys.Where(x => !projTeamIds.Contains(x)).ToList();
var missingTeams = model.Teams.Sliders.Where(x => !projTeamIds.Contains(x.EntityId))
.Select(x => x.EntityId).ToList();
DbContext.Teams.AsNoTracking().Where(t => missingTeams.Contains(t.Id))
.ToList().ForEach(x => model.Project.Team2Project.Add(new Team2Project
{
Team = x
}));
var detailsModel = LoadScenarioDetailsModel(model);
model.LaborSplitPercentage = detailsModel.LaborSplitPercentage;
model.EFXSplit = detailsModel.EFXSplit;
model.Teams.Options = Utils.GetTeams(false);
#endregion
if (ModelState.IsValid)
{
if (checkedECs.Count == 0)
{
ModelState.AddModelError("ScenarioExpenditures",
string.Format(Constants.ERROR_TEMPLATE_REQUIRED, "Expenditures"));
}
else
{
return PartialView("_generalStep", model);
}
}
}
catch (BLLException blEx) // handle any system specific error
{
// display error message if required
if (blEx.DisplayError)
ModelState.AddModelError(string.Empty, blEx.Message);
else // if display not requried then display modal form with general error message
{
LogException(blEx);
//SetErrorScript();
ModelState.AddModelError(string.Empty, "Cannot save scenario. Try again later.");
}
}
catch (Exception exception) // handle any unexpected error
{
LogException(exception);
//SetErrorScript();
ModelState.AddModelError(string.Empty, "Cannot save scenario. Try again later.");
}
HttpContext.Response.StatusCode = 500;
HttpContext.Response.Clear();
return PartialView("_generalStep", model);
}
[HttpPost]
[ValidateAntiForgeryToken]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult SubmitCreateScenarioStep3(CreateScenarioModel.FinInfoModel model)
{
model.TrimStringProperties();
try
{
//var costSavingItems = new List<ScenarioCostSavingModel>();
//if (model.CostSavings != null && !string.IsNullOrWhiteSpace(model.CostSavings.CostSavingItems))
// costSavingItems = JsonConvert.DeserializeObject<List<ScenarioCostSavingModel>>(model.CostSavings.CostSavingItems);
if (ModelState.IsValid)
{
return PartialView("_finStep", model);
}
}
catch (BLLException blEx) // handle any system specific error
{
// display error message if required
if (blEx.DisplayError)
ModelState.AddModelError(string.Empty, blEx.Message);
else // if display not requried then display modal form with general error message
{
LogException(blEx);
//SetErrorScript();
ModelState.AddModelError(string.Empty, "Cannot save scenario. Try again later.");
}
}
catch (Exception exception) // handle any unexpected error
{
LogException(exception);
//SetErrorScript();
ModelState.AddModelError(string.Empty, "Cannot save scenario. Try again later.");
}
HttpContext.Response.StatusCode = 500;
HttpContext.Response.Clear();
return PartialView("_finStep", model);
}
[HttpPost]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult GetECsByTemplateId(Guid? id, List<Guid> selectedExpCats, List<Guid> teams, bool overrideChecked)
{
var manager = new ScenarioManager(DbContext);
var ec = manager.GetExpenditureCategories(id, teams);
var checkedItems = selectedExpCats != null && selectedExpCats.Count > 0 ? selectedExpCats : new List<Guid>();
foreach (var expenditureItem in ec)
{
if (overrideChecked)
{
expenditureItem.Checked = checkedItems.Contains(expenditureItem.Id);
}
else
{
expenditureItem.Checked |= checkedItems.Contains(expenditureItem.Id);
}
}
return PartialView("_createScenarioExpenditures", ec);
}
[HttpGet]
//[ValidateAntiForgeryToken]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult GetProjectPartsByProjectId(Guid? Id)
{
Guid userId = SecurityManager.GetUserPrincipal();
var pp = Utils.GetProjectParts(Id, userId);
return PartialView("_createScenarioProjectParts", pp);
}
[HttpPost]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult ProcessRates(RatesModel model)
{
try
{
model.TrimStringProperties();
switch (model.Mode)
{
case RatesModel.FormMode.ListRates:
#region List Rates
model.EditRateId = Guid.Empty;
model.DeleteRateId = Guid.Empty;
if (!Guid.Empty.Equals(model.ExpenditureCategoryId))
{
var gRates = (from c in DbContext.Rates
where
c.ExpenditureCategoryId == model.ExpenditureCategoryId &&
c.Type == (int)RateModel.RateType.Global
select c);
model.AddGlobalRange(gRates.ToList());
}
if (Guid.Empty != model.ScenarioId && !Guid.Empty.Equals(model.ExpenditureCategoryId))
{
var lRates = (from c in DbContext.Rates
where
c.ExpenditureCategoryId == model.ExpenditureCategoryId &&
c.Type == (int)RateModel.RateType.Derived &&
c.ParentId == model.ScenarioId
select c);
model.AddLocalRange(lRates.ToList());
}
#endregion
break;
case RatesModel.FormMode.EditRate:
#region Load Edit Rate Form
if (model.EditRateId.HasValue && !Guid.Empty.Equals(model.EditRateId))
{
var rate = DbContext.Rates.AsNoTracking().FirstOrDefault(t => t.Id == model.EditRateId);
if (rate != null)
{
model.EditRate = new RateModel()
{
Id = rate.Id,
DerivedObjectId = rate.DerivedId,
EndDate = rate.EndDate,
ExpenditureCategoryId = rate.ExpenditureCategoryId,
FreezeRate = rate.FreezeRate,
ParentId = rate.ParentId,
Rate1 = rate.Rate1,
StartDate = rate.StartDate,
Type = (RateModel.RateType)rate.Type
};
}
}
else
{
model.EditRate = new RateModel()
{
DerivedObjectId = Guid.Empty,
EndDate = new DateTime(9999, 12, 31),
ExpenditureCategoryId = model.ExpenditureCategoryId,
FreezeRate = 0,
ParentId = model.ScenarioId,
Rate1 = 0,
StartDate = new DateTime(1753, 1, 1),
Type = RateModel.RateType.Derived
};
}
#endregion
break;
case RatesModel.FormMode.ConfirmEditRate:
#region Save Rate
if (ModelState.IsValid)
{
if (ContentLocker.IsLock("ExpenditureCategoryRate", model.EditRateId.ToString(),
User.Identity.Name))
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
var manager = new RateManager(DbContext);
manager.Save(model.EditRate);
DbContext.SaveChanges();
manager.Dispose();
ContentLocker.RemoveLock("ExpenditureCategoryRate", model.EditRateId.ToString(),
User.Identity.Name);
model.Mode = RatesModel.FormMode.ListRates; // reset action mode
model.EditRateId = Guid.Empty;
model.DeleteRateId = Guid.Empty;
if (!Guid.Empty.Equals(model.ExpenditureCategoryId))
{
var gRates = (from c in DbContext.Rates
where
c.ExpenditureCategoryId == model.ExpenditureCategoryId &&
c.Type == (int)RateModel.RateType.Global
select c);
model.AddGlobalRange(gRates.ToList());
}
if (Guid.Empty != model.ScenarioId && !Guid.Empty.Equals(model.ExpenditureCategoryId))
{
var lRates = (from c in DbContext.Rates
where
c.ExpenditureCategoryId == model.ExpenditureCategoryId &&
c.Type == (int)RateModel.RateType.Derived &&
c.ParentId == model.ScenarioId
select c);
model.AddLocalRange(lRates.ToList());
}
}
else
{
model.Mode = RatesModel.FormMode.EditRate;
HttpContext.Response.StatusCode = 500;
HttpContext.Response.Clear();
}
#endregion
break;
case RatesModel.FormMode.DeriveRate:
#region Derive rate from global
var sManager = new ScenarioManager(DbContext);
var scenario = sManager.Load(model.ScenarioId);
if (!Guid.Empty.Equals(model.ExpenditureCategoryId) && !Guid.Empty.Equals(model.ScenarioId))
{
var rates = (from c in DbContext.Rates
where
c.ExpenditureCategoryId == model.ExpenditureCategoryId &&
c.Type == (int)RateModel.RateType.Global &&
c.StartDate <= scenario.EndDate &&
c.EndDate >= scenario.StartDate
select c).ToList();
foreach (var rate in rates)
{
var derivedrates = (from c in DbContext.Rates
where
c.ExpenditureCategoryId == model.ExpenditureCategoryId &&
c.ParentId == model.ScenarioId && c.DerivedId == rate.Id
select c).ToList();
DbContext.Rates.RemoveRange(derivedrates);
var newrate = new Rate
{
Id = Guid.NewGuid(),
Rate1 = rate.Rate1,
StartDate = (rate.StartDate < scenario.StartDate) ? scenario.StartDate.Value : rate.StartDate,
EndDate = (rate.EndDate > scenario.EndDate) ? scenario.EndDate.Value : rate.EndDate,
ExpenditureCategoryId = rate.ExpenditureCategoryId,
FreezeRate = rate.FreezeRate,
ParentId = model.ScenarioId,
Type = (short)RateModel.RateType.Derived,
DerivedId = rate.Id
};
DbContext.Rates.Add(newrate);
DbContext.SaveChanges();
}
}
model.Mode = RatesModel.FormMode.ListRates; // reset action mode
model.EditRateId = Guid.Empty;
model.DeleteRateId = Guid.Empty;
if (!Guid.Empty.Equals(model.ExpenditureCategoryId))
{
var gRates = (from c in DbContext.Rates
where
c.ExpenditureCategoryId == model.ExpenditureCategoryId &&
c.Type == (int)RateModel.RateType.Global
select c);
model.AddGlobalRange(gRates.ToList());
}
if (Guid.Empty != model.ScenarioId && !Guid.Empty.Equals(model.ExpenditureCategoryId))
{
var lRates = (from c in DbContext.Rates
where
c.ExpenditureCategoryId == model.ExpenditureCategoryId &&
c.Type == (int)RateModel.RateType.Derived &&
c.ParentId == model.ScenarioId
select c);
model.AddLocalRange(lRates.ToList());
}
#endregion
break;
case RatesModel.FormMode.ConfirmDeleteRate:
#region delete rate
if (!Guid.Empty.Equals(model.ExpenditureCategoryId) && !Guid.Empty.Equals(model.ScenarioId))
{
var deleterates = (from c in DbContext.Rates
where
c.ExpenditureCategoryId == model.ExpenditureCategoryId &&
c.ParentId == model.ScenarioId
select c);
if (model.DeleteRateId != null && !Guid.Empty.Equals(model.DeleteRateId))
deleterates = deleterates.Where(t => t.Id == model.DeleteRateId);
DbContext.Rates.RemoveRange(deleterates);
DbContext.SaveChanges();
}
model.DeleteRateId = Guid.Empty;
model.EditRateId = Guid.Empty;
model.Mode = RatesModel.FormMode.ListRates; // reset action mode
if (!Guid.Empty.Equals(model.ExpenditureCategoryId))
{
var gRates = (from c in DbContext.Rates
where
c.ExpenditureCategoryId == model.ExpenditureCategoryId &&
c.Type == (int)RateModel.RateType.Global
select c);
model.AddGlobalRange(gRates.ToList());
}
if (Guid.Empty != model.ScenarioId && !Guid.Empty.Equals(model.ExpenditureCategoryId))
{
var lRates = (from c in DbContext.Rates
where
c.ExpenditureCategoryId == model.ExpenditureCategoryId &&
c.Type == (int)RateModel.RateType.Derived &&
c.ParentId == model.ScenarioId
select c);
model.AddLocalRange(lRates.ToList());
}
break;
#endregion
}
return PartialView("_rates", model);
}
catch (BLLException blEx)
{
if (blEx.DisplayError)
SetErrorScript(message: blEx.Message);
else
{
LogException(blEx);
SetErrorScript();
}
model.Mode = RatesModel.FormMode.EditRate;
}
catch (Exception exception)
{
LogException(exception);
SetErrorScript();
model.Mode = RatesModel.FormMode.EditRate;
}
if (ModelState.IsValid && (RatesModel.FormMode.ConfirmEditRate == model.Mode || RatesModel.FormMode.ConfirmDeleteRate == model.Mode) && Guid.Empty != model.EditRateId)
ContentLocker.RemoveLock("ExpenditureCategoryRate", model.EditRateId.ToString(), User.Identity.Name);
HttpContext.Response.StatusCode = 500;
HttpContext.Response.Clear();
return PartialView("_rates", model);
}
/// <summary>
/// Returns JSON UnitOfMeasure list with filters and sort for jQuery DataTables
/// </summary>
[HttpPost]
[AreaSecurityAttribute(area = Areas.ScenarioTemplates, level = AccessLevel.Read)]
public JsonResult Templates(JQueryDataTablesModel jQueryDataTablesModel)
{
int totalRecordCount;
int searchRecordCount;
var units = GetTemplates(startIndex: jQueryDataTablesModel.iDisplayStart,
pageSize: jQueryDataTablesModel.iDisplayLength, sortedColumns: jQueryDataTablesModel.GetSortedColumns(),
totalRecordCount: out totalRecordCount, searchRecordCount: out searchRecordCount, searchString: jQueryDataTablesModel.sSearch);
return this.DataTablesJson(items: units,
totalRecords: totalRecordCount,
totalDisplayRecords: searchRecordCount,
sEcho: jQueryDataTablesModel.sEcho);
}
private IEnumerable<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":
if (sortedColumn.Direction == SortingDirection.Ascending)
query = query.OrderBy(c => c.Id);
else
query = query.OrderByDescending(c => c.Id);
break;
case "StartDate":
if (sortedColumn.Direction == SortingDirection.Ascending)
query = query.OrderBy(c => c.StartDate);
else
query = query.OrderByDescending(c => c.StartDate);
break;
case "EndDate":
if (sortedColumn.Direction == SortingDirection.Ascending)
query = query.OrderBy(c => c.EndDate);
else
query = query.OrderByDescending(c => c.EndDate);
break;
case "Duration":
if (sortedColumn.Direction == SortingDirection.Ascending)
query = query.OrderBy(c => c.Duration);
else
query = query.OrderByDescending(c => c.Duration);
break;
case "CGSplit":
if (sortedColumn.Direction == SortingDirection.Ascending)
query = query.OrderBy(c => c.CGSplit);
else
query = query.OrderByDescending(c => c.CGSplit);
break;
case "EFXSplit":
if (sortedColumn.Direction == SortingDirection.Ascending)
query = query.OrderBy(c => c.EFXSplit);
else
query = query.OrderByDescending(c => c.EFXSplit);
break;
case "ScenariosCount":
if (sortedColumn.Direction == SortingDirection.Ascending)
query = query.OrderBy(c => c.ScenariosCount);
else
query = query.OrderByDescending(c => c.ScenariosCount);
break;
default:
if (sortedColumn.Direction == SortingDirection.Ascending)
query = query.OrderBy(c => c.Name);
else
query = query.OrderByDescending(c => c.Name);
break;
}
}
totalRecordCount = DbContext.Scenarios.Count(s => s.Type == (int?)ScenarioType.Template);
searchRecordCount = query.Count();
return query.Skip(startIndex).Take(pageSize).ToList();
}
// GET: /Scenarios/
[HttpGet]
public ActionResult LoadExpenditures(Guid? id)
{
if (id == null || id == Guid.Empty)
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
try
{
var manager = new ScenarioManager(DbContext);
var model = manager.GetExpenditureCategories(id.Value);
return PartialView("_expenditures", model);
}
catch (BLLException blEx)
{
if (blEx.DisplayError)
SetErrorScript(message: blEx.Message);
else
{
LogException(blEx);
SetErrorScript();
}
}
catch (Exception exception)
{
LogException(exception);
SetErrorScript();
}
return PartialView("_expenditures", new List<ScenarioModel.ExpenditureItem>());
}
[HttpPost]
public ActionResult LoadJsonScenarioCalendar(ScenarioDetailsModel model)
{
if (model == null || model.ScenarioId == Guid.Empty)
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
var scenario = DbContext.Scenarios.AsNoTracking().FirstOrDefault(t => t.Id == model.ScenarioId);
if (scenario == null)
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
model.StartDate = scenario.StartDate ?? DateTime.Today;
model.EndDate = scenario.EndDate ?? DateTime.Today.AddYears(1);
model.GrowthScenario = scenario.GrowthScenario;
model.ScenarioId = scenario.Id;
model.ParentId = scenario.ParentId ?? Guid.Empty;
model.ScenarioType = (ScenarioType?)scenario.Type;
if (scenario.Project != null)
{
model.YellowIndicator = scenario.Project.PerformanceYellowThreshold;
if (!model.YellowIndicator.HasValue)
model.YellowIndicator = scenario.Project.Type.PerformanceYellowThreshold;
model.RedIndicator = scenario.Project.PerformanceRedThreshold;
if (!model.RedIndicator.HasValue)
model.RedIndicator = scenario.Project.Type.PerformanceRedThreshold;
}
return Json(GetScenarioCalendar(model), JsonRequestBehavior.AllowGet);
}
private class Pairs
{
public Guid Id { get; set; }
public decimal Quantity { get; set; }
}
[HttpPost]
public JsonResult GetMasterScenarioCalendar(MasterScenarioFindModel findModel)
{
if (findModel == null || findModel.ProjectId == Guid.Empty)
return null;
var actuals = new List<Guid>();
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);
var scenarios = activeScenarios.Select(x => x.Id).ToList();
var forecast = activeScenarios.Where(x => x.Type == (int)ScenarioType.Portfolio).Select(x => x.Id).ToList();
var projects = activeScenarios.Select(x => x.ParentId ?? Guid.Empty).Distinct().ToList();
var scenarioDetails = GetScenarioDetailsProxy(null, startDate, endDate, scenarios);
var forecastDetails = scenarioDetails.Where(x => forecast.Contains(x.ScenarioId)).ToList();
if (findModel.ShowActuals)
{
actuals = activeScenarios.Where(x => x.Type == (int)ScenarioType.Actuals).Select(x => x.Id).ToList();
actualsDetails = scenarioDetails.Where(x => actuals.Contains(x.ScenarioId)).ToList();
actualsStartDateMs = (long)(activeScenarios.Where(x => x.Type == (int)ScenarioType.Actuals && x.StartDate.HasValue).Min(x => x.StartDate) ?? Constants.UnixEpochDate).Subtract(Constants.UnixEpochDate).TotalMilliseconds;
actualsEndDateMs = (long)(activeScenarios.Where(x => x.Type == (int)ScenarioType.Actuals && x.EndDate.HasValue).Max(x => x.EndDate) ?? Constants.UnixEpochDate).Subtract(Constants.UnixEpochDate).TotalMilliseconds;
}
var calendar = GetCalendar(forecastDetails, actualsDetails, scenarios, projects, findModel.IsUOMHours, actualsStartDateMs, actualsEndDateMs, findModel.ShowActuals);
var model = new MasterScenarioCalendarModel()
{
StartDate = (long)startDate.Subtract(Constants.UnixEpochDate).TotalMilliseconds,
EndDate = (long)endDate.Subtract(Constants.UnixEpochDate).TotalMilliseconds,
Headers = calendar.Headers,
ScenarioCalendar = calendar.Rows
};
return Json(model);
}
private ScenarioDetailsModel GetScenarioCalendar(ScenarioDetailsModel model)
{
DateTime periodStartDate;
DateTime periodEndDate;
long actualsStartDate = 0;
long actualsEndDate = 0;
Guid actualScenarioId = Guid.Empty;
var allExpCatsDict = DbContext.ExpenditureCategory.Include(x => x.Expenditure).AsNoTracking().ToDictionary(x => x.Id);
var allUomsDict = DbContext.UOMs.AsNoTracking().ToDictionary(x => x.Id);
var user = new UsersCache().Value.FirstOrDefault(x => x.Id == new Guid(HttpContext.User.Identity.GetID()));
if (!model.IsUOMHours.HasValue && user != null)
{
model.IsUOMHours = !user.PreferredResourceAllocation;
}
if (!model.PreferredTotalsDisplaying.HasValue && user != null)
{
model.PreferredTotalsDisplaying = user.PreferredTotalsDisplaying;
}
switch (model.ScenarioType)
{
case ScenarioType.LoanOut:
case ScenarioType.Training:
case ScenarioType.Vacation:
periodStartDate = new DateTime(DateTime.Today.Year, 1, 1);
periodEndDate = new DateTime(DateTime.Today.Year, 12, 31);
break;
default:
periodStartDate = model.StartDate;
periodEndDate = model.EndDate;
break;
}
if (!model.GrowthScenario && !ScenarioType.Actuals.Equals(model.ScenarioType))
{
var actualScenario = DbContext.Scenarios.FirstOrDefault(
t => t.ParentId == model.ParentId && t.Type == (int?)ScenarioType.Actuals);
if (actualScenario != null)
{
actualScenarioId = actualScenario.Id;
actualsStartDate = (long)(actualScenario.StartDate ?? Constants.UnixEpochDate).Subtract(Constants.UnixEpochDate).TotalMilliseconds;
actualsEndDate = (long)(actualScenario.EndDate ?? Constants.UnixEpochDate).Subtract(Constants.UnixEpochDate).TotalMilliseconds;
if (actualScenario.StartDate < model.StartDate)
periodStartDate = actualScenario.StartDate ?? Constants.UnixEpochDate;
if (actualScenario.EndDate > model.EndDate)
periodEndDate = actualScenario.EndDate ?? Constants.UnixEpochDate;
}
}
#region Load scenario Data
var filter = new ScenarioCalendarFilterModel()
{
CreditDepartment = model.CreditDepartment,
GLAccount = model.GLAccount,
LaborMaterials = model.LaborMaterials.HasValue ? model.LaborMaterials.Value.ToString() : null,
CategoryType = model.CategoryType.HasValue ? model.CategoryType.Value.ToString() : null,
IncomeType = model.IncomeType,
SelectedExpCats = model.SelectedExpCats
};
List<ScenarioDetailWithProxyItemModel> scenarioDetailsList = GetScenarioDetailsProxy(filter, periodStartDate, periodEndDate, new List<Guid> { model.ScenarioId });
List<ScenarioDetailWithProxyItemModel> actualsDetailsList = null;
if (model.ShowActuals && !Guid.Empty.Equals(actualScenarioId))
actualsDetailsList = GetScenarioDetailsProxy(filter, periodStartDate, periodEndDate, new List<Guid> { actualScenarioId });
#endregion
var calendar = GetCalendar(scenarioDetailsList, actualsDetailsList, new List<Guid> { model.ScenarioId }, new List<Guid>() { model.ParentId }, model.IsUOMHours ?? false, actualsStartDate, actualsEndDate, model.ShowActuals, model.PreferredTotalsDisplaying);
model.Headers = calendar.Headers;
model.ScenarioCalendar = calendar.Rows;
model.AllResources = calendar.AllResources;
model.ActualsStartDateMs = actualsStartDate;
model.ActualsEndDateMs = actualsEndDate;
return model;
}
private List<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 = new List<DateTime>();
if (showActuals && actualsDetailsList.Count > 0)
weekEndingDates = forecastDetailsList.Union(actualsDetailsList).Select(t => Constants.UnixEpochDate.AddMilliseconds(t.WeekEndingDate)).Distinct().OrderBy(o => o).ToList();
else
weekEndingDates = forecastDetailsList.Select(t => Constants.UnixEpochDate.AddMilliseconds(t.WeekEndingDate)).Distinct().OrderBy(o => o).ToList();
calendar.Headers = BuildHeaders(weekEndingDates);
#region Forecast data
var scenarioDetails = forecastDetailsList.GroupBy(x => x.ExpenditureCategoryId).ToDictionary(key => key.Key, grouping => grouping.ToList());
if (scenarioDetails.Count > 0)
{
var totalRow = new ScenarioDetailsModel.ScenarioCalendarRow
{
ExpCatId = Guid.Empty,
ExpCatName = "Totals",
GrandTotalCost = 0,
GrandTotalQuantity = 0,
ScenarioDetailIds = new Guid[calendar.Headers.Count],
CostValues = new decimal[calendar.Headers.Count],
QuantityValues = new decimal[calendar.Headers.Count],
UseType = ExpenditureCategoryModel.UseTypes.Calculated
};
var resourcesByTeams = DbContext.Team2Project.Where(x => projects.Contains(x.ProjectId)).
SelectMany(x => x.Team.PeopleResources).OrderBy(r => r.LastName);
foreach (var teamResource in resourcesByTeams)
{
if (calendar.AllResources.Any(x => x.Id == teamResource.Id))
continue;
calendar.AllResources.Add(new ScenarioDetailsModel.ScenarioCalendarRowResource()
{
Id = teamResource.Id,
Name = teamResource.FirstName + " " + teamResource.LastName,
ExpedentureCategoryId = teamResource.ExpenditureCategoryId,
IsActiveEmployee = teamResource.IsActiveEmployee
});
}
var allResIds = calendar.AllResources.Select(t => t.Id).Distinct().ToArray();
var allResourceVacations = DbContext.PeopleResourceVacations.Where(t => allResIds.Contains(t.PeopleResourceId)).Select(t => new
{
t.PeopleResourceId,
t.HoursOff,
t.WeekEndingDate
}).ToArray();
var allResourceTrainings = DbContext.PeopleResourceTrainings.Where(t => allResIds.Contains(t.PeopleResourceId)).Select(t => new
{
t.PeopleResourceId,
t.HoursOff,
t.WeekEndingDate
}).ToArray();
var resourceAllocation = DbContext.PeopleResourceAllocations.Where(r => scenarios.Contains(r.ScenarioId)).ToList();
foreach (var row in scenarioDetails)
{
var expenditureCategory = expCatsEx[row.Key];
var expCatRow = new ScenarioDetailsModel.ScenarioCalendarRow
{
ExpCatId = row.Key,
ExpCatName = expenditureCategory.ExpCategoryWithCcName, // SA. ENV-756. ENV-839
GrandTotalCost = 0,
GrandTotalQuantity = 0,
ScenarioDetailIds = new Guid[calendar.Headers.Count],
CostValues = new decimal[calendar.Headers.Count],
QuantityValues = new decimal[calendar.Headers.Count],
UseType = (ExpenditureCategoryModel.UseTypes)expenditureCategory.UseType,
Resources = calendar.AllResources.Where(x => resourceAllocation.Where(ec => ec.ExpenditureCategoryId == row.Key).
Select(r => r.PeopleResourceId).Contains(x.Id)).
Select(x => new ScenarioDetailsModel.ScenarioCalendarRowResource()
{
Id = x.Id,
Name = x.Name,
QuantityValues = new decimal[calendar.Headers.Count],
CostValues = new decimal[calendar.Headers.Count],
CapacityQuantityValues = new decimal[calendar.Headers.Count],
}).ToList(),
RestQuantity = new decimal[calendar.Headers.Count],
RestCost = new decimal[calendar.Headers.Count],
ActualsCosts = new decimal[calendar.Headers.Count],
ActualsQuantities = new decimal[calendar.Headers.Count]
};
var uomMultiplier = Utils.GetUOMMultiplier(expCategories, uoms, row.Key, isUOMHours);
var uomValue = uoms.FirstOrDefault(t => t.Key == expenditureCategory.UOMId);
var monthCost = 0.0M;
var monthQuantity = 0.0M;
var resourceTotals = calendar.AllResources.Select(x => new Pairs() { Id = x.Id, Quantity = 0.0M }).ToList();
var rowValues = row.Value.GroupBy(x => Constants.UnixEpochDate.AddMilliseconds(x.WeekEndingDate)).ToDictionary(x => x.Key, g => g.ToList());
foreach (var weekEnding in weekEndingDates)
{
var values = rowValues.ContainsKey(weekEnding) ? rowValues[weekEnding] : new List<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
{
// needs only for single scenario calendar
if (values.Count == 1)
expCatRow.ScenarioDetailIds[colIndex] = values.First().Id;
expCatRow.CostValues[colIndex] = values.Sum(x => x.Cost);
expCatRow.QuantityValues[colIndex] = values.Sum(x => x.Quantity) * uomMultiplier;
}
//Get resources cost\quantity
var currAllocation = resourceAllocation.Where(r => r.WeekEndingDate == weekEnding && r.ExpenditureCategoryId == row.Key).ToList();
expCatRow.Resources.ForEach(x =>
{
// set resource weekly allocation
x.QuantityValues[colIndex] = currAllocation.Where(ar => ar.PeopleResourceId == x.Id).Sum(ca => ca.Quantity ?? 0) * uomMultiplier;
// set resource weekly availability (UOM.Value - vacations - loan offs - trainings)
if (uomValue.Value != null)
{
var vacationsSum = allResourceVacations.Where(t =>
t.PeopleResourceId == x.Id &&
t.WeekEndingDate <= weekEnding && t.WeekEndingDate >= weekEnding.AddDays(-6))
.Sum(s => s.HoursOff);
var trainingsSum = allResourceTrainings.Where(t =>
t.PeopleResourceId == x.Id &&
t.WeekEndingDate <= weekEnding && t.WeekEndingDate >= weekEnding.AddDays(-6))
.Sum(s => s.HoursOff);
x.CapacityQuantityValues[colIndex] = (uomValue.Value.UOMValue - vacationsSum - trainingsSum) * uomMultiplier;
}
});
expCatRow.RestQuantity[colIndex] = expCatRow.QuantityValues[colIndex] - expCatRow.Resources.Select(x => x.QuantityValues[colIndex]).Sum();
totalRow.CostValues[colIndex] += expCatRow.CostValues[colIndex];
totalRow.QuantityValues[colIndex] += expCatRow.QuantityValues[colIndex];
expCatRow.Resources.ForEach(x => resourceTotals.FirstOrDefault(r => r.Id == x.Id).Quantity += x.QuantityValues[colIndex]);
monthQuantity += expCatRow.QuantityValues[colIndex];
monthCost += expCatRow.CostValues[colIndex];
if (colIndex < calendar.Headers.Count - 1)
colIndex++;
if (colIndex >= calendar.Headers.Count - 1 || calendar.Headers[colIndex].IsMonth)
{
expCatRow.ScenarioDetailIds[colIndex] = Guid.Empty;
expCatRow.CostValues[colIndex] = monthCost;
if (showAvg.Value && !isUOMHours)
{
expCatRow.QuantityValues[colIndex] = Math.Round(monthQuantity / calendar.Headers[colIndex].Weeks.Count(), 3);
}
else
{
expCatRow.QuantityValues[colIndex] = monthQuantity;
}
totalRow.ScenarioDetailIds[colIndex] = Guid.Empty;
if (showAvg.Value && !isUOMHours)
{
//var avgCost =Math.Round(monthCost/ calendar.Headers[colIndex].Weeks.Count(), 3);
var avgQuantity = Math.Round(monthQuantity / calendar.Headers[colIndex].Weeks.Count(), 3);
//expCatRow.GrandTotalCost += avgCost;
expCatRow.GrandTotalQuantity += avgQuantity;
expCatRow.Resources.ForEach(x => x.QuantityValues[colIndex] = Math.Round(resourceTotals.FirstOrDefault(r => r.Id == x.Id).Quantity / calendar.Headers[colIndex].Weeks.Count(), 3));
//totalRow.CostValues[colIndex] += avgCost;
totalRow.QuantityValues[colIndex] += avgQuantity;
}
else
{
expCatRow.GrandTotalQuantity += monthQuantity;
expCatRow.Resources.ForEach(x => x.QuantityValues[colIndex] = resourceTotals.FirstOrDefault(r => r.Id == x.Id).Quantity);
totalRow.QuantityValues[colIndex] += monthQuantity;
}
expCatRow.Resources.ForEach(x => x.GrandTotalQuantity += (x.QuantityValues[colIndex]));
expCatRow.GrandTotalCost += monthCost;
totalRow.CostValues[colIndex] += monthCost;
monthCost = 0.0M;
monthQuantity = 0.0M;
resourceTotals.ForEach(x => x.Quantity = 0.0M);
}
}
calendar.Rows.Add(expCatRow);
totalRow.GrandTotalCost += expCatRow.GrandTotalCost;
totalRow.GrandTotalQuantity += expCatRow.GrandTotalQuantity;
}
if (showAvg.Value && !isUOMHours)
{
var monthsCount = calendar.Headers.Count(x => x.IsMonth);
calendar.Rows.ForEach(
x => x.GrandTotalQuantity = Math.Round(x.GrandTotalQuantity / monthsCount, 3));
totalRow.GrandTotalQuantity = 0;
calendar.Rows.ForEach(x =>
totalRow.GrandTotalQuantity += x.GrandTotalQuantity);
calendar.Rows.ForEach(x =>
x.Resources.ForEach(r => r.GrandTotalQuantity = Math.Round(r.GrandTotalQuantity / monthsCount, 3)));
}
calendar.Rows = calendar.Rows.OrderBy(x => x.ExpCatName).ToList();
calendar.Rows.Insert(0, totalRow);
}
#endregion
#region Load Actuals
if (showActuals && actualsDetailsList != null && actualsDetailsList.Count > 0)
{
var weekCells = calendar.Headers.Where(x => !x.IsMonth).Count();
var totalRow = calendar.Rows.FirstOrDefault(t => t.ExpCatId == Guid.Empty);
totalRow.ActualsCosts = new decimal[calendar.Headers.Count];
totalRow.ActualsQuantities = new decimal[calendar.Headers.Count];
var actualsDetails = actualsDetailsList.GroupBy(x => x.ExpenditureCategoryId).ToDictionary(x => x.Key);
foreach (var row in actualsDetails)
{
var expenditureCategory = expCatsEx[row.Key];
var expCatRow = calendar.Rows.FirstOrDefault(t => t.ExpCatId == row.Key);
if (expCatRow == null)
{
expCatRow = new ScenarioDetailsModel.ScenarioCalendarRow
{
ExpCatId = row.Key,
ExpCatName = expenditureCategory.ExpCategoryWithCcName, // SA. ENV-756. ENV-839
GrandTotalCost = 0,
GrandTotalQuantity = 0,
ScenarioDetailIds = new Guid[calendar.Headers.Count],
CostValues = new decimal[calendar.Headers.Count],
QuantityValues = new decimal[calendar.Headers.Count],
UseType = (ExpenditureCategoryModel.UseTypes)expenditureCategory.UseType,
Resources = new List<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.WeekEndingDate)).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>>();
headers.Add(ScenarioDetailsModel.HeaderPeriod.Month, new List<ScenarioDetailsModel.HeaderBase>());
headers.Add(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]
where ((item as ScenarioDetailsModel.WeekHeader).DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal) &&
((item as ScenarioDetailsModel.WeekHeader).Milliseconds >= forecastDataStartDateMs) &&
((item as ScenarioDetailsModel.WeekHeader).Milliseconds <= forecastDataEndDateMs)
select item as ScenarioDetailsModel.WeekHeader).ToList();
// Get week headers, that must be visible in grid actuals mode
var weekHeadersActualsVisible =
(from ScenarioDetailsModel.HeaderBase item in headers[ScenarioDetailsModel.HeaderPeriod.Week]
where ((item as ScenarioDetailsModel.WeekHeader).DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal) &&
((item as ScenarioDetailsModel.WeekHeader).Milliseconds >= actualsViewDataStartDateMs) &&
((item as ScenarioDetailsModel.WeekHeader).Milliseconds <= actualsViewDataEndDateMs)
select item as ScenarioDetailsModel.WeekHeader).ToList();
// Get week headers, that must be editable in grid actuals mode (default = are editable)
var weekHeadersActualsEditable =
(from ScenarioDetailsModel.HeaderBase item in headers[ScenarioDetailsModel.HeaderPeriod.Week]
where ((item as ScenarioDetailsModel.WeekHeader).DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal) &&
((item as ScenarioDetailsModel.WeekHeader).Milliseconds >= actualsDataStartDateMs) &&
((item as ScenarioDetailsModel.WeekHeader).Milliseconds <= actualsDataEndDateMs)
select item as ScenarioDetailsModel.WeekHeader).ToList();
// Set week headers visibility for forecast and actuals modes. Set non editable columns
weekHeadersForecastVisible.ForEach(x => x.Visible[ScenarioDetailsModel.CalendarDataViewModes.Forecast] = true);
weekHeadersActualsVisible.ForEach(x => x.Visible[ScenarioDetailsModel.CalendarDataViewModes.Actuals] = true);
weekHeadersActualsEditable.ForEach(x => x.Editable[ScenarioDetailsModel.CalendarDataViewModes.Actuals] = false);
// Set visibility for month and totals headers
for (int monthIndex = 0; monthIndex < headers[ScenarioDetailsModel.HeaderPeriod.Month].Count; monthIndex++)
{
ScenarioDetailsModel.MonthHeader monthItem =
headers[ScenarioDetailsModel.HeaderPeriod.Month][monthIndex] as ScenarioDetailsModel.MonthHeader;
int forcastVisibleWeeksCount =
headers[ScenarioDetailsModel.HeaderPeriod.Week]
.Where(x => (x as ScenarioDetailsModel.WeekHeader).MonthHeader == monthIndex &&
(x as ScenarioDetailsModel.WeekHeader).DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal &&
x.Visible[ScenarioDetailsModel.CalendarDataViewModes.Forecast]).Count();
int actualsVisibleWeeksCount =
headers[ScenarioDetailsModel.HeaderPeriod.Week]
.Where(x => (x as ScenarioDetailsModel.WeekHeader).MonthHeader == monthIndex &&
(x as ScenarioDetailsModel.WeekHeader).DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal &&
x.Visible[ScenarioDetailsModel.CalendarDataViewModes.Actuals]).Count();
int actualsNonEditableWeeksCount =
headers[ScenarioDetailsModel.HeaderPeriod.Week]
.Where(x => (x as ScenarioDetailsModel.WeekHeader).MonthHeader == monthIndex &&
(x as ScenarioDetailsModel.WeekHeader).DataType == ScenarioDetailsModel.WeekHeaderType.Ordinal &&
!x.Editable[ScenarioDetailsModel.CalendarDataViewModes.Actuals]).Count();
monthItem.ColspanValues[ScenarioDetailsModel.CalendarDataViewModes.Forecast] = forcastVisibleWeeksCount;
monthItem.ColspanValues[ScenarioDetailsModel.CalendarDataViewModes.Actuals] = actualsVisibleWeeksCount;
monthItem.Visible[ScenarioDetailsModel.CalendarDataViewModes.Forecast] = (forcastVisibleWeeksCount > 0);
monthItem.Visible[ScenarioDetailsModel.CalendarDataViewModes.Actuals] = (actualsVisibleWeeksCount > 0);
monthItem.Editable[ScenarioDetailsModel.CalendarDataViewModes.Actuals] = (actualsNonEditableWeeksCount < 1);
headers[ScenarioDetailsModel.HeaderPeriod.Week][monthItem.TotalsHeader]
.Visible[ScenarioDetailsModel.CalendarDataViewModes.Forecast] = (forcastVisibleWeeksCount > 0);
headers[ScenarioDetailsModel.HeaderPeriod.Week][monthItem.TotalsHeader]
.Visible[ScenarioDetailsModel.CalendarDataViewModes.Actuals] = (actualsVisibleWeeksCount > 0);
headers[ScenarioDetailsModel.HeaderPeriod.Week][monthItem.TotalsHeader]
.Editable[ScenarioDetailsModel.CalendarDataViewModes.Actuals] = (actualsNonEditableWeeksCount < 1);
} // for (int monthIndex = 0; monthIndex < headers[...
return headers;
}
/// <summary>
/// Fill then ScenarioDetailModel model inner object collections, needed to render the view
/// </summary>
/// <param name="id">Scenario id</param>
/// <param name="manager">Manager</param>
/// <remarks>SA. ENV-707</remarks>
private ScenarioDetailModel LoadScenarioDetailsModel(Guid id, ScenarioManager manager)
{
var model = (ScenarioDetailModel)manager.Load(id) ?? new ScenarioDetailModel();
if (model.CostSaving != null) model.CostSaving.CostSavingDescription = model.CostSavingsDescription;
if (model.CostSaving != null) model.CostSaving.ScenarioStartDate = model.StartDate;
if (model.Id == Guid.Empty || !model.Status.HasValue)
return model;
var actualScenario = DbContext.Scenarios.FirstOrDefault(x => x.ParentId == model.ParentId && x.Type == (int)ScenarioType.Actuals);
if (actualScenario != null)
{
var actualSplitInfo = Utils.GetLaborMaterialsSplit(actualScenario.Id);
if (actualScenario.StartDate.HasValue)
model.ActualsStartDate = (long)actualScenario.StartDate.Value.Subtract(Constants.UnixEpochDate).TotalMilliseconds;
if (actualScenario.EndDate.HasValue)
model.ActualsEndDate = (long)actualScenario.EndDate.Value.Subtract(Constants.UnixEpochDate).TotalMilliseconds;
model.ActualLabor = actualSplitInfo.Labor;
model.ActualMaterials = actualSplitInfo.Materials;
model.ActualsTotal = actualSplitInfo.Total;
model.HasActuals = actualScenario.EndDate.HasValue;
model.ActualLaborMaterialsSplit = actualSplitInfo.LaborAndMaterialSplit;
}
var forecastSplitInfo = Utils.GetLaborMaterialsSplit(model.Id);
model.Expenditures = Utils.GetScenarioExpenditures(model.Id);
model.ScenarioExpenditures = manager.GetExpenditureCategories(model.Id);
model.LaborMaterialsSplit = forecastSplitInfo.LaborAndMaterialSplit;
#region Override Actuals fields if there are no actuals data
// SA. ENV-698. Show Forecast value as Actual Bottom-Up Direct Costs, if Project has no actuals. Begin
bool actualDataExists = false;
if (actualScenario != null)
// Actual data scenario exists
actualDataExists = actualScenario.EndDate.HasValue;
model.DateForStartOfChanges = model.StartDate.HasValue && model.StartDate.Value > DateTime.Today ? model.StartDate.Value.Date : DateTime.Today;
if (!actualDataExists)
{
// Show nulls and forecast values as actuals, if actuals not found
model.BUDirectCostsActuals = model.BUDirectCosts;
model.BUDirectCostsLMActuals = model.BUDirectCostsLM;
model.BUCostsShotsActuals = model.BUCostsShots;
model.BUCostsShotsLMActuals = model.BUCostsShotsLM;
model.CalculatedGrossMarginActuals = null;
model.CalculatedGrossMarginLMActuals = null;
model.ActualRevenueAfterCost = null;
model.ActualLabor = null;
model.ActualLaborMaterialsSplit = null;
model.ActualMaterials = null;
}
else
{
// date for start of changes should be bigger than actuals end date
if (actualScenario.EndDate >= model.DateForStartOfChanges)
model.DateForStartOfChanges = actualScenario.EndDate.Value.AddDays(1);
}
// SA. ENV-698. End
#endregion
return model;
}
private ScenarioDetailModel LoadScenarioDetailsModel(CreateScenarioModel.GeneralInfoModel sm)
{
var parentId = sm.PartId ?? sm.ProjectId;
if (null == parentId || Guid.Empty.Equals(parentId))
throw new BLLException("We should have a project or part to create a scenario");
ScenarioDetailModel model = null;
model = new ScenarioDetailModel
{
ParentId = parentId,
Name = sm.ScenarioName,
GrowthScenario = sm.GrowthScenario,
//UseLMMargin = sm.Step3.UseLMMargin,
//GrossMargin = sm.Step3.Margin != null ? (int) sm.Step3.Margin : 0,
TemplateId = sm.TemplateId ?? Guid.Empty,
StartDate = sm.StartDate,
EndDate = sm.EndDate,
//IsRevenueGenerating = sm.Project.IsRevenueGenerating,
Id = sm.ScenarioId,
ScenarioExpenditures = null != sm.ScenarioExpenditures ? sm.ScenarioExpenditures.Where(t => t.Checked).ToList() : null,
};
if (!Guid.Empty.Equals(model.ParentId))
{
var actualScenario = DbContext.Scenarios.FirstOrDefault(x => x.ParentId == model.ParentId && x.Type == (int)ScenarioType.Actuals);
if (actualScenario != null)
{
var actualSplitInfo = Utils.GetLaborMaterialsSplit(actualScenario.Id);
if (actualScenario.StartDate.HasValue)
model.ActualsStartDate = (long)actualScenario.StartDate.Value.Subtract(Constants.UnixEpochDate).TotalMilliseconds;
if (actualScenario.EndDate.HasValue)
model.ActualsEndDate = (long)actualScenario.EndDate.Value.Subtract(Constants.UnixEpochDate).TotalMilliseconds;
model.ActualLabor = actualSplitInfo.Labor;
model.ActualMaterials = actualSplitInfo.Materials;
model.ActualsTotal = actualSplitInfo.Total;
model.HasActuals = actualScenario.EndDate.HasValue;
model.ActualLaborMaterialsSplit = actualSplitInfo.LaborAndMaterialSplit;
}
var forecastSplitInfo = Utils.GetLaborMaterialsSplit(model.TemplateId);
model.LaborMaterialsSplit = forecastSplitInfo.LaborAndMaterialSplit;
#region Override Actuals fields if there are no actuals data
bool actualDataExists = false;
if (actualScenario != null)
// Actual data scenario exists
actualDataExists = actualScenario.EndDate.HasValue;
model.DateForStartOfChanges = model.StartDate.HasValue && model.StartDate.Value > DateTime.Today ? model.StartDate.Value.Date : DateTime.Today;
if (!actualDataExists)
{
// Show forecats values as actuals, if actuals not found
model.BUDirectCostsActuals = model.BUDirectCosts;
model.BUDirectCostsLMActuals = model.BUDirectCostsLM;
model.BUCostsShotsActuals = model.BUCostsShots;
model.BUCostsShotsLMActuals = model.BUCostsShotsLM;
model.CalculatedGrossMarginActuals = model.CalculatedGrossMargin;
model.CalculatedGrossMarginLMActuals = model.CalculatedGrossMarginLM;
}
else
{
// date for start of changes should be bigger than actuals end date
if (actualScenario.EndDate >= model.DateForStartOfChanges)
model.DateForStartOfChanges = actualScenario.EndDate.Value.AddDays(1);
}
}
#endregion
return model;
}
// GET: /Scenarios/Details/5
[HttpGet]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult Details(Guid? id, string tab, string backUrl, string backName)
{
if (id == null || id == Guid.Empty)
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
ScenarioDetailModel model = null;
var manager = new ScenarioManager(DbContext);
try
{
model = LoadScenarioDetailsModel(id.Value, manager);
if (model.Id == Guid.Empty)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
model.ActiveTab = tab;
// SA. Param names where restored. 'ref' changed to 'backUrl' and 'back' changed to 'backName' ENV-707. Begin
model.BackUrl = !string.IsNullOrEmpty(backUrl) ? backUrl : Url.Action("Index", "Project");
model.BackName = !string.IsNullOrEmpty(backName) ? backName : "list";
// SA. ENV-707. End
}
catch (BLLException blEx)
{
if (blEx.DisplayError)
SetErrorScript(message: blEx.Message);
else
{
LogException(blEx);
SetErrorScript();
}
}
catch (Exception exception)
{
LogException(exception);
SetErrorScript();
}
return View(model);
}
[HttpGet]
public ActionResult GetScenarioAvailableExpCategories(Guid id)
{
return Json(GetScenarioCategories(id), JsonRequestBehavior.AllowGet);
}
private List<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> categoties =
DbContext.VW_ExpenditureCategory.AsNoTracking()
.OrderBy(t => t.Name)
.Select(t => new ScenarioExpenditureModel
{
Id = t.Id,
Name = t.ExpCategoryWithCcName // SA. ENV-839
}).ToList();
if ((teams != null) && (teams.Count > 0))
{
// Get categories, related to given teams
List<Guid> scenarioCategories =
(from PeopleResource pr in DbContext.PeopleResources.AsNoTracking()
where pr.TeamId.HasValue && teams.Contains(pr.TeamId.Value)
select pr.ExpenditureCategoryId).ToList();
// Mark related to teams categories
categoties.Where(x => scenarioCategories.Contains(x.Id)).ToList().ForEach(x => x.IsRelatedToScenario = 1);
}
return categoties;
}
[HttpPost]
public ActionResult GetRates(Guid scenarioId, bool? uomMode)
{
// temporary solution, action method will be removed
var allExpCats = DbContext.ExpenditureCategory.AsNoTracking().ToDictionary(x => x.Id);
var allUoms = DbContext.UOMs.AsNoTracking().ToDictionary(x => x.Id);
if (!uomMode.HasValue)
{
var user = new UsersCache().Value.FirstOrDefault(x => x.Id == new Guid(HttpContext.User.Identity.GetID()));
if (user != null)
uomMode = !user.PreferredResourceAllocation;
}
var rates = (new ScenarioManager(DbContext)).GetRates(scenarioId);
foreach (var expCatData in rates)
{
foreach (var rate in expCatData.rateValues)
rate.rateValue = Utils.GetUOMMultiplier(allExpCats, allUoms, expCatData.expCatId, uomMode);
}
return Json(rates);
}
[HttpPost]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult CopyTo(ScenarioCopyModel model)
{
if (Guid.Empty.Equals(model.TargetProjectId) || Guid.Empty.Equals(model.ScenarioId))
ModelState.AddModelError(string.Empty, "Invalid parameters");
else
{
try
{
var newScenario = new ScenarioManager(DbContext).CopyTo(model.ScenarioId, model.TargetProjectId, model.TargetStatus, model.includeCostSavings);
return Json(newScenario);
}
catch (BLLException blEx) // handle any system specific error
{
// display error message if required
if (blEx.DisplayError)
ModelState.AddModelError(string.Empty, blEx.Message);
else // if display not requried then display modal form with general error message
{
LogException(blEx);
SetErrorScript();
}
}
catch (Exception exception) // handle any unexpected error
{
LogException(exception);
SetErrorScript();
}
}
HttpContext.Response.StatusCode = 500;
HttpContext.Response.Clear();
return PartialView("_copyToModal", model);
}
[HttpPost]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult SaveChanges(SaveScenarioDetailsChangesModel model)
{
if (model.ScenarioId != Guid.Empty && ContentLocker.IsLock("Scenario", model.ScenarioId.ToString(), User.Identity.Name))
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
var context = new EnVisageEntities();
model.TrimStringProperties();
var allExpCats = DbContext.ExpenditureCategory.AsNoTracking().ToDictionary(x => x.Id);
var allUoms = DbContext.UOMs.AsNoTracking().ToDictionary(x => x.Id);
if (!model.ScenarioFilters.IsUOMHours.HasValue)
{
var user = new UsersCache().Value.FirstOrDefault(x => x.Id == new Guid(HttpContext.User.Identity.GetID()));
if (user != null)
model.ScenarioFilters.IsUOMHours = !user.PreferredResourceAllocation;
}
if (ModelState.IsValid)
{
try
{
var manager = new ScenarioManager(context);
var scenario = manager.Load(model.ScenarioId, false);
var scenarioStartDateMsSince1970 = scenario.StartDate.HasValue ?
(long)scenario.StartDate.Value.Subtract(Constants.UnixEpochDate).TotalMilliseconds : 0;
foreach (var changedExpCat in model.ChangedExpCats)
{
var uomMultiplier = Utils.GetUOMMultiplier(allExpCats, allUoms, changedExpCat.Id, model.ScenarioFilters.IsUOMHours);
foreach (var changedColumn in changedExpCat.Values)
{
if (changedColumn.Milliseconds >= scenarioStartDateMsSince1970)
{
var scenarioDetailsItem = (from c in context.ScenarioDetail where c.Id == changedColumn.Id select c).FirstOrDefault();//manager.LoadScenarioDetail(changedColumn.Id, false);
if (scenarioDetailsItem == null) continue;
if (scenarioDetailsItem.Id == Guid.Empty)
{
throw new NullReferenceException(string.Format("Scenario Details {0} mising", changedColumn.Id));
}
scenarioDetailsItem.Quantity = changedColumn.Quantity / uomMultiplier;
scenarioDetailsItem.Cost = changedColumn.Cost;
}
}
if (changedExpCat.Resources != null)
{
var resourceIds = changedExpCat.Resources.Select(x => x.Id).ToList();
var resourceAllocations = context.PeopleResourceAllocations.Where(x => resourceIds.Contains(x.PeopleResourceId) && x.ScenarioId == model.ScenarioId).ToList();
foreach (var resource in changedExpCat.Resources)
{
var resourceId = resource.Id;
if (resource.IsRemoved)
{
var recourcesToDelete = context.PeopleResourceAllocations.Where(x => x.PeopleResourceId == resourceId && x.ScenarioId == model.ScenarioId).ToList();
recourcesToDelete.ForEach(x => context.PeopleResourceAllocations.Remove(x));
recourcesToDelete.ForEach(x => context.Entry(x).State = EntityState.Deleted);
}
else
{
foreach (var changedResource in resource.Values)
{
var date = Constants.UnixEpochDate.AddSeconds(changedResource.Milliseconds / 1000);
var allocatedResource = (from c in resourceAllocations
where c.WeekEndingDate == date && c.ScenarioId == model.ScenarioId
&& c.PeopleResourceId == resourceId && c.ExpenditureCategoryId == changedExpCat.Id
select c).FirstOrDefault();
if (changedResource.Quantity <= 0)
{
if (allocatedResource != null)
context.Entry(allocatedResource).State = EntityState.Deleted;
continue;
}
if (allocatedResource == null)
{
allocatedResource = context.PeopleResourceAllocations.Create();
allocatedResource.Id = Guid.NewGuid();
allocatedResource.ExpenditureCategoryId = changedExpCat.Id;
allocatedResource.PeopleResourceId = resourceId;
allocatedResource.ScenarioId = model.ScenarioId;
allocatedResource.WeekEndingDate = date;
context.Entry(allocatedResource).State = EntityState.Added;
}
else
{
context.Entry(allocatedResource).State = EntityState.Modified;
}
allocatedResource.Quantity = changedResource.Quantity / uomMultiplier;
}
}
}
}
}
//TODO: review that we really need 2 transactions here. Looks like it was here since bottom-up costs recalculation was in stored procedure.
context.SaveChanges();
manager.SetBottomUpCosts(scenario);
context.SaveChanges();
model.ScenarioFilters.StartDate = scenario.StartDate ?? DateTime.Today;
model.ScenarioFilters.EndDate = scenario.EndDate ?? DateTime.Today.AddYears(1);
model.ScenarioFilters.GrowthScenario = scenario.GrowthScenario;
model.ScenarioFilters.ParentId = scenario.ParentId ?? Guid.Empty;
model.ScenarioFilters.ScenarioType = (ScenarioType?)scenario.Type;
//var detailsGrid = GetScenarioCalendar(model.ScenarioFilters);
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
catch (BLLException blEx) // handle any system specific error
{
// display error message if required
if (blEx.DisplayError)
ModelState.AddModelError(string.Empty, blEx.Message);
else // if display not requried then display modal form with general error message
{
LogException(blEx);
SetErrorScript();
}
}
catch (Exception exception) // handle any unexpected error
{
LogException(exception);
SetErrorScript();
}
}
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError);
}
[HttpPost]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult AddNote(NoteModel model)
{
//if (model.ScenarioId != Guid.Empty && ContentLocker.IsLock("Scenario", model.ScenarioId.ToString(), User.Identity.Name))
// return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
model.TrimStringProperties();
if (ModelState.IsValid)
{
try
{
model.Id = Guid.NewGuid();
var newnote = new Note();
model.CopyTo(newnote);
newnote.UserId = new Guid(User.Identity.GetID());
DbContext.Notes.Add(newnote);
DbContext.SaveChanges();
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
catch (BLLException blEx) // handle any system specific error
{
// display error message if required
if (blEx.DisplayError)
ModelState.AddModelError(string.Empty, blEx.Message);
else // if display not requried then display modal form with general error message
{
LogException(blEx);
}
}
catch (Exception exception) // handle any unexpected error
{
LogException(exception);
}
}
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError);
}
[HttpPost]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult EditNote(NoteModel model)
{
//if (model.ScenarioId != Guid.Empty && ContentLocker.IsLock("Scenario", model.ScenarioId.ToString(), User.Identity.Name))
// return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
model.TrimStringProperties();
if (ModelState.IsValid)
{
try
{
var note = (from c in DbContext.Notes where c.Id == model.Id select c).FirstOrDefault();
if (note == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
note.Title = model.Title;
note.NoteDetail = model.Details;
DbContext.SaveChanges();
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
catch (BLLException blEx) // handle any system specific error
{
// display error message if required
if (blEx.DisplayError)
ModelState.AddModelError(string.Empty, blEx.Message);
else // if display not requried then display modal form with general error message
{
LogException(blEx);
}
}
catch (Exception exception) // handle any unexpected error
{
LogException(exception);
}
}
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError);
}
[HttpPost]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult DeleteNote(Guid id)
{
if (id == Guid.Empty)
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
var note = (from c in DbContext.Notes where c.Id == id select c).FirstOrDefault();
if (note == null)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
else
{
try
{
DbContext.Notes.Remove(note);
DbContext.SaveChanges();
return new HttpStatusCodeResult(HttpStatusCode.OK);
}
catch (BLLException blEx) // handle any system specific error
{
// display error message if required
if (blEx.DisplayError)
ModelState.AddModelError(string.Empty, blEx.Message);
else // if display not requried then display modal form with general error message
{
LogException(blEx);
}
}
catch (Exception exception) // handle any unexpected error
{
LogException(exception);
}
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError);
}
}
// GET: /User/Edit/5
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult EditNote(Guid id)
{
if (id == Guid.Empty)
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
var note = DbContext.Notes.AsNoTracking().FirstOrDefault(x => x.Id == id);
if (note == null)
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
else
return PartialView("_addNote", (NoteModel)note);
}
// GET: /User/Edit/5
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult AddNote(Guid id)
{
if (id == Guid.Empty)
{
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
}
return PartialView("_addNote", new NoteModel(id));
}
[AreaSecurity(area = Areas.Scenarios, level = AccessLevel.Read)]
public ActionResult LoadNotes(Guid scenarioId)
{
try
{
var notesModel = new List<NoteModel>();
var notes = DbContext.Notes.Where(x => x.ParentId == scenarioId).OrderBy(x => x.DateAdded).ToList();
foreach (var note in notes)
notesModel.Add((NoteModel)note);
return PartialView("_notesList", notesModel);
}
catch (BLLException blEx) // handle any system specific error
{
// display error message if required
if (blEx.DisplayError)
ModelState.AddModelError(string.Empty, blEx.Message);
else // if display not requried then display modal form with general error message
{
LogException(blEx);
}
}
catch (Exception exception) // handle any unexpected error
{
LogException(exception);
}
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError);
}
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult EditRate(Guid? id, Guid? parentId, Guid expentureCategoryId)
{
RateModel model = null;
if (id.HasValue && id != Guid.Empty)
{
var manager = new RateManager(DbContext);
model = (RateModel)manager.Load(id);
}
else
{
var sManager = new ScenarioManager(DbContext);
var scenario = sManager.Load(parentId);
model = new RateModel();
model.Type = RateModel.RateType.Derived;
var lRates = (from c in DbContext.Rates
where
c.ExpenditureCategoryId == expentureCategoryId &&
c.Type == (int)RateModel.RateType.Derived &&
c.ParentId == parentId
select c).ToList();
if ((from d in lRates select d.EndDate).Max() < DateTime.MaxValue.AddYears(-4))
{
model.StartDate = (lRates.Count == 0) ? scenario.StartDate.Value : ((from d in lRates select d.EndDate).Max()).AddDays(1);
model.EndDate = (scenario.EndDate.Value <= model.StartDate) ? model.StartDate.AddDays(1) : scenario.EndDate.Value;
}
else
{
model.StartDate = (from d in lRates select d.EndDate).Max();
model.EndDate = model.StartDate.AddDays(1);
}
}
if ((!model.ParentId.HasValue || model.ParentId == Guid.Empty) && parentId.HasValue)
model.ParentId = parentId;
if (model.ExpenditureCategoryId == Guid.Empty)
model.ExpenditureCategoryId = expentureCategoryId;
return PartialView("_editRate", model);
}
[HttpPost]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult EditRate(RateModel model)
{
if (ContentLocker.IsLock("Rate", model.Id.ToString(), User.Identity.Name))
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
if (ModelState.IsValid)
{
try
{
var manager = new RateManager(DbContext);
manager.Save(model);
DbContext.SaveChanges();
manager.Dispose();
return JavaScript("window.location.search += '&tab=rateTable';");
}
catch (Exception exception)
{
LogException(exception);
SetErrorScript();
}
}
ContentLocker.RemoveLock("Rate", model.Id.ToString(), User.Identity.Name);
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError);
}
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult DeleteRate(Guid? id)
{
RateModel model = null;
if (id.HasValue && id != Guid.Empty)
{
var manager = new RateManager(DbContext);
model = (RateModel)manager.Load(id);
}
else
model = new RateModel();
return PartialView("_deleteRate", model);
}
[HttpPost]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult DeleteRate(RateModel model)
{
// SA. ENV-1011. Removed model validation, because model has no enough data to be valid.
// For Reate deletion we need Id only
if ((model == null) || model.Id.Equals(Guid.Empty))
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
if (ContentLocker.IsLock("Rate", model.Id.ToString(), User.Identity.Name))
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
var dbObj = DbContext.Rates.FirstOrDefault(x => x.Id == model.Id);
DbContext.Rates.Remove(dbObj);
DbContext.SaveChanges();
ContentLocker.RemoveLock("Rate", model.Id.ToString(), User.Identity.Name);
return ProcessRates(new RatesModel()
{
ExpenditureCategoryId = model.ExpenditureCategoryId,
ScenarioId = model.ParentId.Value,
Mode = RatesModel.FormMode.ListRates
});
}
[HttpGet]
public JsonResult GetTeamCapacityScenarioId(Guid teamId)
{
using (var context = new EnVisageEntities())
{
var data = (from sd in context.Teams
where sd.Id == teamId
select sd.PlannedCapacityScenarioId).FirstOrDefault();
if (data == null)
{
var teamManager = new TeamManager(DbContext);
var team = teamManager.Load(teamId, false);
var scen = new Scenario();
scen.Name = team.Name.Trim() + " Planned Capacity";
scen.Type = (int)ScenarioType.TeamPlannedCapacity;
scen.Id = Guid.NewGuid();
team.PlannedCapacityScenarioId = scen.Id;
DbContext.Scenarios.Add(scen);
DbContext.SaveChanges();
data = scen.Id;
}
return Json((data == null) ? string.Empty : data.ToString(), JsonRequestBehavior.AllowGet);
}
}
[HttpGet]
public void ToggleTemplateStatus()
{
string scenarioId = Request.QueryString["scenarioId"];
var guidId = new Guid(scenarioId);
var context = new EnVisageEntities();
var scenario = (from c in context.Scenarios where c.Id == guidId select c).FirstOrDefault();
if (scenario != null && scenario.Status != (int)ScenarioStatus.Active)
{
scenario.Status = (int?)ScenarioStatus.Active;
}
else if (scenario != null)
scenario.Status = (int)ScenarioStatus.Inactive;
context.SaveChanges();
}
// GET: /Scenario/Delete/5
[HttpGet]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult Delete(Guid? id, string backUrl = null)
{
if (id == null || id == Guid.Empty)
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
// SA. ENV-755
if (!SecurityManager.CheckScenarioPermission(id.Value, AccessLevel.Write))
return new HttpStatusCodeResult(HttpStatusCode.Unauthorized);
var model = new ScenarioModel();
try
{
var manager = new ScenarioManager(DbContext);
model = (ScenarioModel)manager.Load(id);
if (model == null)
return HttpNotFound();
}
catch (BLLException blEx)
{
if (blEx.DisplayError)
SetErrorScript(message: blEx.Message);
else
{
LogException(blEx);
SetErrorScript();
}
}
catch (Exception exception)
{
LogException(exception);
SetErrorScript();
}
return View(model);
}
// POST: /Scenario/Delete/5
[HttpPost]
[ValidateAntiForgeryToken]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult Delete(ScenarioModel model)
{
if (ContentLocker.IsLock("Scenario", model.Id.ToString(), User.Identity.Name))
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
var manager = new ScenarioManager(DbContext);
var dbObj = manager.Load(model.Id, false);
if (dbObj == null)
return HttpNotFound();
(DbContext as IObjectContextAdapter).ObjectContext.ExecuteStoreCommand(string.Format("exec sp_DeleteScenario '{0}'", dbObj.Id));
DbContext.SaveChanges();
ContentLocker.RemoveLock("Scenario", dbObj.Id.ToString(), User.Identity.Name);
if (Request["backUrl"] != null)
{
return Redirect(Request["backUrl"]);
}
else
{
return RedirectToAction("Index", "Project");
}
}
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult AdjustMargin(Guid? id)
{
var manager = new ScenarioManager(DbContext);
var scenario = (ScenarioModel)manager.Load(id, false) ?? new ScenarioModel();
var project = DbContext.Projects.FirstOrDefault(x => x.Id == scenario.ParentId);
var model = new UpdateScenarioModel()
{
Id = scenario.Id,
EndDate = scenario.EndDate,
Expenditures = scenario.Expenditures,
GrossMargin = scenario.GrossMargin,
LMMargin = scenario.LMMargin,
TDDirectCosts = scenario.TDDirectCosts,
ProjectedRevenue = scenario.ProjectedRevenue,
StartDate = scenario.StartDate,
UseLMMargin = scenario.UseLMMargin,
AllowAdjustment = scenario.AllowAdjustment,
PriorWeekCutOff = scenario.PriorWeekCutOff ?? scenario.StartDate,
BUDirectCosts = scenario.BUDirectCosts
};
if (project != null)
model.IsRevenueGenerating = project.IsRevenueGenerating;
else
model.IsRevenueGenerating = false;
model.Expenditures = manager.GetExpenditureCategories(model.Id);
return PartialView("_adjustMargin", model);
}
[HttpPost]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult AdjustMargin(UpdateScenarioModel model, Guid[] expCatId, string[] expCatName, string[] expCatGroup, bool[] expCatChecked)
{
if (ContentLocker.IsLock("Scenario", model.Id.ToString(), User.Identity.Name))
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
model.TrimStringProperties();
if (expCatId != null && expCatName != null && expCatGroup != null && expCatChecked != null &&
expCatId.Length == expCatName.Length && expCatId.Length == expCatGroup.Length && expCatId.Length == expCatChecked.Length)
{
model.Expenditures = new ScenarioModel.ExpenditureItem[expCatId.Length];
for (var i = 0; i < expCatId.Length; i++)
{
model.Expenditures[i] = new ScenarioModel.ExpenditureItem
{
Id = expCatId[i],
Group = expCatGroup[i],
Name = expCatName[i],
Checked = expCatChecked[i]
};
}
}
try
{
// get scenario by id
var manager = new ScenarioManager(DbContext);
var scenario = (ScenarioModel)manager.Load(model.Id);
var project = DbContext.Projects.FirstOrDefault(x => x.Id == scenario.ParentId);
if (project != null)
model.IsRevenueGenerating = project.IsRevenueGenerating;
else
model.IsRevenueGenerating = false;
if (ModelState.IsValid)
{
// update scenario values
scenario.StartDate = model.StartDate;
scenario.EndDate = model.EndDate;
// SA. ENV-698. GrossMargin and LMMargin adjustment was fixed. Begin
if (model.UseLMMargin)
// User selected the LM Margin adjustment only
scenario.LMMargin = model.LMMargin;
else
// User selected the Gross Margin adjustment only
scenario.GrossMargin = model.GrossMargin;
// The original code lines:
// scenario.GrossMargin = model.UseLMMargin ? null : model.GrossMargin;//!model.UseLMMargin ? model.GrossMargin / 100 : null;
// scenario.LMMargin = model.UseLMMargin ? model.LMMargin : null;
// SA. ENV-698. End
scenario.TDDirectCosts = model.TDDirectCosts;
scenario.ProjectedRevenue = model.ProjectedRevenue;
scenario.PriorWeekCutOff = model.PriorWeekCutOff;
scenario.AllowAdjustment = model.AllowAdjustment;
scenario.UseLMMargin = model.UseLMMargin;
scenario.Expenditures = model.Expenditures;
scenario.FreezeResource = model.AllowAdjustment;
manager.Save(scenario);
ContentLocker.RemoveLock("Scenario", model.Id.ToString(), User.Identity.Name);
return Json(new { Status = "Ok" });
}
}
catch (BLLException blEx)
{
if (blEx.DisplayError)
SetErrorScript(message: blEx.Message);
else
{
LogException(blEx);
SetErrorScript();
}
}
catch (Exception exception)
{
LogException(exception);
SetErrorScript();
ModelState.AddModelError("errors", "Error on adjust scenario.");
}
ContentLocker.RemoveLock("Scenario", model.Id.ToString(), User.Identity.Name);
return PartialView("_adjustMargin", model);
}
[HttpPost]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult CreateTemplate(ScenarioTemplateCreationModel model, Guid[] expCatId, string[] expCatName, string[] expCatGroup, bool[] templateExpCatChecked)
{
if (ContentLocker.IsLock("Scenario", model.Id.ToString(), User.Identity.Name))
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
model.TrimStringProperties();
if (expCatId != null && expCatName != null && expCatGroup != null && templateExpCatChecked != null &&
expCatId.Length == expCatName.Length && expCatId.Length == expCatGroup.Length && expCatId.Length == templateExpCatChecked.Length)
{
model.Expenditures = new ScenarioModel.ExpenditureItem[expCatId.Length];
for (var i = 0; i < expCatId.Length; i++)
{
model.Expenditures[i] = new ScenarioModel.ExpenditureItem
{
Id = expCatId[i],
Group = expCatGroup[i],
Name = expCatName[i],
Checked = templateExpCatChecked[i]
};
}
}
var expCatIds = (from e in model.Expenditures where e.Checked == true select e.Id).ToArray();
try
{
// get scenario by id
var manager = new ScenarioManager(DbContext);
var scenario = manager.Load(model.Id);
if (ModelState.IsValid)
{
var newTemplate = DbContext.Scenarios.Create();
newTemplate.Id = Guid.NewGuid();
newTemplate.Name = model.TemplateName;
newTemplate.ParentId = null;
newTemplate.TemplateId = null;
newTemplate.Type = (int)ScenarioType.Template;
newTemplate.ProjectedRevenue = 0;
newTemplate.ExpectedGrossMargin = 0;
newTemplate.CalculatedGrossMargin = 0;
newTemplate.CGSplit = scenario.CGSplit;
newTemplate.EFXSplit = scenario.EFXSplit;
newTemplate.StartDate = null;
newTemplate.EndDate = null;
newTemplate.Duration = scenario.Duration;
newTemplate.TDDirectCosts = 0;
newTemplate.BUDirectCosts = 0;
newTemplate.Shots = scenario.Shots;
newTemplate.TDRevenueShot = 0;
newTemplate.BURevenueShot = 0;
newTemplate.FreezeRevenue = false;
newTemplate.Color = null;
newTemplate.Status = (int)ScenarioStatus.Active;
newTemplate.UseLMMargin = scenario.UseLMMargin;
newTemplate.ExpectedGrossMargin_LM = scenario.ExpectedGrossMargin_LM;
newTemplate.CalculatedGrossMargin_LM = scenario.CalculatedGrossMargin_LM;
newTemplate.TDDirectCosts_LM = 0;
newTemplate.BUDirectCosts_LM = 0;
newTemplate.BURevenueShot_LM = 0;
newTemplate.ShotStartDate = null;
newTemplate.GrowthScenario = scenario.GrowthScenario;
newTemplate.Actuals_BUDirectCosts = 0;
newTemplate.Actuals_BUDirectCosts_LM = 0;
newTemplate.SystemAttributeObjectID = scenario.SystemAttributeObjectID;
DbContext.Entry(newTemplate).State = EntityState.Added;
if (!string.IsNullOrEmpty(model.TemplateGroupNames))
{
var groups = model.TemplateGroupNames.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
var savedGroups = DbContext.TemplateGroups.Where(x => groups.Contains(x.Name)).ToList();
var currentTemplategroups = new List<string>();
foreach (var item in groups)
{
currentTemplategroups.Add(item);
var group = savedGroups.FirstOrDefault(tg => tg.Name.Equals(item));
if (group == null)
{
group = new TemplateGroup()
{
Id = Guid.NewGuid(),
Name = item
};
DbContext.TemplateGroups.Add(group);
}
DbContext.Template2TemplateGroup.Add(new Template2TemplateGroup()
{
Id = Guid.NewGuid(),
TemplateId = newTemplate.Id,
TemplateGroupId = group.Id
});
}
}
var sds = DbContext.ScenarioDetail.Where(s => s.ParentID == scenario.Id && expCatIds.Contains(s.ExpenditureCategoryId.Value)).ToList();
foreach (var scenarioDetails in sds)
{
DbContext.ScenarioDetail.Add(new ScenarioDetail()
{
Id = Guid.NewGuid(),
ParentID = newTemplate.Id,
Cost = scenarioDetails.Cost,
ExpenditureCategoryId = scenarioDetails.ExpenditureCategoryId,
WeekEndingDate = null,
Quantity = scenarioDetails.Quantity,
WeekOrdinal = scenarioDetails.WeekOrdinal
});
}
DbContext.SaveChanges();
ContentLocker.RemoveLock("Scenario", model.Id.ToString(), User.Identity.Name);
return Json(new { Status = "Ok" });
}
}
catch (BLLException blEx)
{
if (blEx.DisplayError)
SetErrorScript(message: blEx.Message);
else
{
LogException(blEx);
SetErrorScript();
}
}
catch (Exception exception)
{
LogException(exception);
SetErrorScript();
ModelState.AddModelError("errors", "Error on adjust scenario.");
}
ContentLocker.RemoveLock("Scenario", model.Id.ToString(), User.Identity.Name);
return PartialView("_createTemplateModal", model);
}
[HttpPost]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult DeleteAllLocalRates(Guid? scenarioId, Guid? expenditureCategoryId)
{
if (!scenarioId.HasValue || scenarioId == Guid.Empty || !expenditureCategoryId.HasValue || expenditureCategoryId == Guid.Empty)
return Json(new { Status = "Error", Msg = "Parameter scenarioId or expenditureCategoryId can not be null or empty." });
if (ContentLocker.IsLock("Scenario", scenarioId.ToString(), User.Identity.Name))
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
try
{
var ratesForDelete = DbContext.Rates
.Where(x => x.ParentId == scenarioId &&
x.ExpenditureCategoryId == expenditureCategoryId &&
x.Type == (short)RateModel.RateType.Derived);
DbContext.Rates.RemoveRange(ratesForDelete);
DbContext.SaveChanges();
ContentLocker.RemoveLock("Scenario", scenarioId.ToString(), User.Identity.Name);
return Json(new { Status = "Ok" });
}
catch (Exception exception)
{
LogException(exception);
return Json(new { Status = "Error", Msg = exception.Message });
}
}
[HttpPost]
public ActionResult RecalculateCalendar(ScenarioDetailSnapshotRecalculationModel recalculationModel)
{
if (recalculationModel == null)
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
var scenarioManager = (new ScenarioManager(DbContext));
var ratesManager = (new RateManager(DbContext));
var model = new ScenarioDetailSnapshotModel()
{
Scenario = recalculationModel.Scenario,
CalendarFilter = recalculationModel.CalendarFilter,
AvailableExpenditures = recalculationModel.AvailableExpenditures,
TeamsInScenario = recalculationModel.TeamsInScenario,
Rates = recalculationModel.Rates,
NeedToRebind = recalculationModel.NeedToRebind,
NeedToRecalculateScenarioDetails = recalculationModel.NeedToRecalculateScenarioDetails,
NeedToRefreshAllTeams = recalculationModel.NeedToRefreshAllTeams,
LocalRatesLoaded = recalculationModel.LocalRatesLoaded,
Calendar = new ScenarioCalendarModel()
{
Expenditures = recalculationModel.Calendar != null ? recalculationModel.Calendar.Expenditures : new Dictionary<string, ExpenditureDetail>()
}
};
if (model.NeedToRebind)
{
var actualScenarioId = Guid.Empty;
var periodStartDate = new DateTime(DateTime.Today.Year, 1, 1);
var periodEndDate = new DateTime(DateTime.Today.Year, 12, 31);
// if scenario was inited with forecast data
var forecast = model.Calendar.GetScenarioDetails(true);
var scenarioDetailsPreLoaded = !(forecast == null || forecast.Count <= 0);
var actuals = new List<VW_ScenarioAndProxyDetails>();
if (model.Scenario.ParentId.HasValue)
{
var project = DbContext.Projects.FirstOrDefault(x => x.Id == model.Scenario.ParentId);
if (project != null)
{
model.Scenario.ProjectDeadline = project.Deadline.HasValue ? Utils.ConvertToUnixDate(project.Deadline.Value) : (long?)null;
model.Scenario.YellowIndicator = project.PerformanceYellowThreshold;
if (!model.Scenario.YellowIndicator.HasValue)
model.Scenario.YellowIndicator = project.Type.PerformanceYellowThreshold;
model.Scenario.RedIndicator = project.PerformanceRedThreshold;
if (!model.Scenario.RedIndicator.HasValue)
model.Scenario.RedIndicator = project.Type.PerformanceRedThreshold;
}
if (!model.Scenario.GrowthScenario && !ScenarioType.Actuals.Equals(model.Scenario.Type))
{
var actualScenario = DbContext.Scenarios.FirstOrDefault(t => t.ParentId == model.Scenario.ParentId && t.Type == (int?)ScenarioType.Actuals);
if (actualScenario != null)
{
actualScenarioId = actualScenario.Id;
model.Scenario.ActualStartDate = (long)(actualScenario.StartDate ?? Constants.UnixEpochDate).Subtract(Constants.UnixEpochDate).TotalMilliseconds;
model.Scenario.ActualEndDate = (long)(actualScenario.EndDate ?? Constants.UnixEpochDate).Subtract(Constants.UnixEpochDate).TotalMilliseconds;
if (actualScenario.StartDate < Constants.UnixEpochDate.AddMilliseconds(model.Scenario.StartDate))
periodStartDate = actualScenario.StartDate ?? Constants.UnixEpochDate;
if (actualScenario.EndDate > Constants.UnixEpochDate.AddMilliseconds(model.Scenario.EndDate))
periodEndDate = actualScenario.EndDate ?? Constants.UnixEpochDate;
if (!Guid.Empty.Equals(actualScenarioId))
{
// get the last week even if scenario ends in the middle of the week. E.g. Scenario.EndDate=12/31 and week.EndDate=1/1
actuals = scenarioManager.GetScenarioProxyDetails(null, periodStartDate, periodEndDate.AddDays(6), new List<Guid> { actualScenarioId });
}
}
}
}
if (model.Scenario.Type != ScenarioType.LoanOut &&
model.Scenario.Type != ScenarioType.Training &&
model.Scenario.Type != ScenarioType.Vacation)
{
periodStartDate = Utils.ConvertFromUnixDate(model.Scenario.StartDate);
periodEndDate = Utils.ConvertFromUnixDate(model.Scenario.EndDate);
}
if (model.Scenario.Id.HasValue && !Guid.Empty.Equals(model.Scenario.Id) && !scenarioDetailsPreLoaded)
{
// get the last week even if scenario ends in the middle of the week. E.g. Scenario.EndDate=12/31 and week.EndDate=1/1
forecast = scenarioManager.GetScenarioProxyDetails(null, periodStartDate, periodEndDate.AddDays(6),
new List<Guid> { model.Scenario.Id.Value });
model.AvailableExpenditures = GetScenarioCategories(model.Scenario.Id.Value);
}
if (!model.NeedToRecalculateScenarioDetails)
{
var oldExpenditureTeams = scenarioDetailsPreLoaded ? model.Calendar.GetTeams() : null;
// if scenario was inited with scenario details we do not need to refresh teams info on this step
model.Calendar = BuildScenarioDetailsCalendarData(forecast, actuals);
if (model.Calendar != null && model.Calendar.Expenditures != null)
{
// if there is no preloaded data we should recalculate team information for scenario
if (!scenarioDetailsPreLoaded)
{
model.TeamsInScenario = scenarioManager.FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, model.Calendar.Expenditures, HttpContext.User.Identity.GetID());
}
else
{
// otherwise we should fill expenditures with preloaded teams
if (oldExpenditureTeams != null && oldExpenditureTeams.Count > 0)
{
foreach (var expCatId in model.Calendar.Expenditures.Keys)
{
if (oldExpenditureTeams.ContainsKey(expCatId))
model.Calendar.Expenditures[expCatId].Teams = oldExpenditureTeams[expCatId];
}
}
}
}
}
// SA. ENV-840
List<Guid> scenarioTeamIds = model.TeamsInScenario.Select(x => x.TeamId).ToList();
model.CategoryTypes = Utils.CastEnumToSelectedList<ExpenditureCategoryModel.CategoryTypes>();
model.CGEFX = Utils.GetCGEFX().ToList();
model.IncomeTypes = Utils.GetIncomeTypes().ToList();
model.GLAccounts = Utils.GetGLAccounts().ToList();
model.CreditDepartments = Utils.GetCreditDepartments().ToList();
model.Teams = Utils.GetTeams(User.Identity.GetID()).ToList();
var id = model.Scenario.Id ?? model.Scenario.TemplateId;
if (!model.LocalRatesLoaded)
{
model.Rates = scenarioManager.GetRates(id);
}
else
{
var localRates = scenarioManager.GetRatesFromModel(model.Rates);
var globalRates = DbContext.Rates.Where(x => x.Type == (short)RateModel.RateType.Global).ToList();
var rates = ratesManager.MergeRates(globalRates, localRates);
model.Rates = scenarioManager.GetRatesModel(rates);
}
model.Expenditures2Add = GetAllCategories(scenarioTeamIds);
}
if (model.NeedToRecalculateScenarioDetails)
{
var rates = new Dictionary<Guid, Dictionary<DateTime, decimal>>();
foreach (var rateModel in model.Rates)
{
var rateValues = new Dictionary<DateTime, decimal>();
foreach (var rateValue in rateModel.rateValues)
rateValues.Add(Utils.ConvertFromUnixDate(rateValue.weekEndDate), rateValue.rateValue);
rates.Add(rateModel.expCatId, rateValues);
}
var forecast = model.Calendar.GetScenarioDetails(true);
var actuals = model.Calendar.GetScenarioDetails(false);
var oldExpenditureTeams = model.Calendar.GetTeams();
var oldExpenditures = model.Calendar.Expenditures;
var startDate = Utils.ConvertFromUnixDate(model.Scenario.StartDate);
var endDate = Utils.ConvertFromUnixDate(model.Scenario.EndDate);
var tempEndDate = endDate.AddDays(6);
var fiscalCalendars = (new FiscalCalendarManager(DbContext)).GetFiscalCalendar(FiscalCalendarModel.FiscalYearType.Week, startDate, tempEndDate, true, false);
var isUpdate = false;
var currentPeriods = 0;
var scenarioDetails = scenarioManager.PrepareScenarioDetails(model.Scenario, model.AvailableExpenditures, actuals, rates, forecast, fiscalCalendars, out isUpdate, out currentPeriods);
forecast = new List<VW_ScenarioAndProxyDetails>();
foreach (var ec in scenarioDetails)
{
foreach (var entity in ec.Value)
{
forecast.Add(new VW_ScenarioAndProxyDetails()
{
Id = entity.Id,
ExpenditureCategoryId = entity.ExpenditureCategoryId,
ExpenditureCategoryName = string.Empty,
ExpCategoryWithCcName = entity.ExpenditureCategoryName, // SA. ENV-839
WeekEndingDate = entity.WeekEndingDate,
LastUpdate = entity.LastUpdate,
WeekOrdinal = entity.WeekOrdinal,
Quantity = entity.Quantity,
Cost = entity.DetailCost,
Type = entity.CategoryType.GetHashCode(),
UseType = entity.UseType.GetHashCode(),
CGEFX = entity.CG_EFX.ToString(),
GLId = entity.GlId ?? Guid.Empty,
UOMId = entity.UOMId ?? Guid.Empty,
CreditId = entity.CreditId ?? Guid.Empty,
SystemAttributeOne = entity.SysField1,
SystemAttributeTwo = entity.SysField2,
SortOrder = entity.SortOrder
});
}
}
model.Calendar = BuildScenarioDetailsCalendarData(forecast, actuals);
model.AvailableExpenditures = forecast.Where(x => x.ExpenditureCategoryId.HasValue)
.Select(x => new ExpenditureModel()
{
Id = x.ExpenditureCategoryId.Value,
Name = x.ExpCategoryWithCcName // SA. ENV-839
}).Distinct().ToList();
if (model.Calendar != null && model.Calendar.Expenditures != null)
{
#region Filling information about all teams for existence/new ECs
var newExpCats = new Dictionary<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 == null ? null : model.TeamsInScenario.ToDictionary(x => x.TeamId, g => g.Allocation);
// SA. ENV-1001. Removed ProjectId param from the method invoke below. Original code:
//FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, model.Scenario.ParentId.Value, newExpCats, teams);
scenarioManager.FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, newExpCats, HttpContext.User.Identity.GetID(), teams);
}
#endregion
#region Filling information about new only teams for all categories
var teams2Add = model.TeamsInScenario != null ? model.TeamsInScenario.Where(x => x.IsNew).ToDictionary(x => x.TeamId, g => g.Allocation) : null;
if (teams2Add != null && teams2Add.Count > 0)
{
model.TeamsInScenario.ForEach(x => x.IsNew = false);
// SA. ENV-1001. Removed ProjectId param from the method invoke below. Original code:
//FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, model.Scenario.ParentId.Value, model.Calendar.Expenditures, teams2Add);
scenarioManager.FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, model.Calendar.Expenditures, HttpContext.User.Identity.GetID(), teams2Add);
}
#endregion
#region Recalculation all teams for all categories
if (model.NeedToRefreshAllTeams)
{
model.TeamsInScenario = scenarioManager.FillTeamsDetails(model.Scenario.Id ?? model.Scenario.TemplateId, model.Calendar.Expenditures, HttpContext.User.Identity.GetID());
}
#endregion
}
// after recalculation we need to save all data
foreach (var expCat in model.Calendar.Expenditures)
foreach (var detail in expCat.Value.Details)
detail.Value.Changed = true;
model.NeedToRecalculateScenarioDetails = false;
}
return Json(model);
}
private ScenarioCalendarModel BuildScenarioDetailsCalendarData(List<VW_ScenarioAndProxyDetails> forecastDetails, List<VW_ScenarioAndProxyDetails> actualsDetails)
{
if (forecastDetails == null)
forecastDetails = new List<VW_ScenarioAndProxyDetails>();
if (actualsDetails == null)
actualsDetails = new List<VW_ScenarioAndProxyDetails>();
var forecastWeekEndings = forecastDetails.Where(x => x.WeekEndingDate.HasValue).Select(x => x.WeekEndingDate.Value).ToList();
var actualsWeekEndings = actualsDetails.Where(x => x.WeekEndingDate.HasValue).Select(x => x.WeekEndingDate.Value).ToList();
var weekEndings = forecastWeekEndings.Union(actualsWeekEndings).OrderBy(x => x).ToList();
// SA. ENV-667. Get the bound dates for forecast and actuals periods for display. Begin
var forecastDataExists = (forecastWeekEndings != null) && (forecastWeekEndings.Count > 0);
var actualsIsExists = (actualsWeekEndings != null && actualsWeekEndings.Count > 0);
long forecastDataStartDateMs = 0;
long forecastDataEndDateMs = 0;
long actualsViewDataStartDateMs = 0;
long actualsViewDataEndDateMs = 0;
long? actualsDataStartDateMs = null;
long? actualsDataEndDateMs = null;
if (forecastDataExists)
{
forecastDataStartDateMs = (long)forecastWeekEndings.Min().Subtract(Constants.UnixEpochDate).TotalMilliseconds;
forecastDataEndDateMs = (long)forecastWeekEndings.Max().Subtract(Constants.UnixEpochDate).TotalMilliseconds;
actualsViewDataStartDateMs = (long)weekEndings.Min().Subtract(Constants.UnixEpochDate).TotalMilliseconds;
actualsViewDataEndDateMs = (long)weekEndings.Max().Subtract(Constants.UnixEpochDate).TotalMilliseconds;
if (actualsIsExists)
{
actualsDataStartDateMs = actualsViewDataStartDateMs;
actualsDataEndDateMs = (long)actualsWeekEndings.Max().Subtract(Constants.UnixEpochDate).TotalMilliseconds;
}
//SA. ENV-667. End
} // if (forecastDataExists)
return new ScenarioCalendarModel()
{
Expenditures = (new ScenarioManager(DbContext)).BuildExpendituresForScenario(weekEndings, forecastDetails, actualsDetails),
Headers = BuildHeadersExForScenario(weekEndings, forecastDataStartDateMs, forecastDataEndDateMs, actualsViewDataStartDateMs, actualsViewDataEndDateMs, actualsDataStartDateMs, actualsDataEndDateMs)
};
// SA. ENV-667. End
}
[HttpPost]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)]
public ActionResult SaveSnapshotChanges(ScenarioDetailsSnapshotSaveModel model)
{
if (ModelState.IsValid)
{
try
{
var scenarioManager = new ScenarioManager(DbContext);
var scenarioId = scenarioManager.Save(model);
DbContext.SaveChanges();
// need to refresh project access permissions after teams was changed
(new ProjectAccessCache()).Invalidate();
// need to return scenario id to redirect user on saved scenario page
return Json(new
{
ScenarioId = scenarioId
});
}
catch (BLLException blEx) // handle any system specific error
{
// display error message if required
if (blEx.DisplayError)
ModelState.AddModelError(string.Empty, blEx.Message);
else // if display not requried then display modal form with general error message
{
LogException(blEx);
SetErrorScript();
}
}
catch (Exception exception) // handle any unexpected error
{
LogException(exception);
SetErrorScript();
}
}
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError);
}
[HttpPost]
[AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Read)]
public JsonResult CalculateScenarioDuration(DateTime? startDate, DateTime? endDate)
{
if (!(startDate.HasValue && endDate.HasValue))
return Json(0);
var tempEndDate = endDate.Value.AddDays(6);
var duration = DbContext.FiscalCalendars.Where(t => t.Type == (int)FiscalCalendarModel.FiscalYearType.Week &&
t.EndDate >= startDate && t.EndDate <= tempEndDate && t.NonWorking == 0 && t.AdjustingPeriod == false)
.Count();
return Json(duration);
}
}
}