using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Data; using System.Data.Entity; using System.Linq; using System.Linq.Expressions; using System.Net; using System.Web; using System.Web.Mvc; using System.Web.Script.Serialization; using EnVisage; using EnVisage.Code; using EnVisage.Code.BLL; using EnVisage.Code.HtmlHelpers; using EnVisage.Models; using Microsoft.AspNet.Identity; using jQuery.DataTables.Mvc; using EnVisage.App_Start; using EnVisage.Code.Cache; namespace EnVisage.Controllers { [Authorize] public class CapacityManagementController : BaseController { private class Pairs { public Guid Id { get; set; } public decimal Quantity { get; set; } } /// /// Direct GET to the page - returns main view /// public ActionResult Index(string menuId, string additionalFilters, PagePreferencesList.PagePreferencesSource src) { return PartialView("_capacityManagement", new CapacityDetailsOptionsModel { MenuId = menuId, AdditionalFilterParams = additionalFilters, Source = src }); } /// /// GET: /Clients/ /// /// Empty view [HttpGet] [AreaSecurityAttribute(area = Areas.Clients, level = AccessLevel.Read)] public ActionResult Index() { var user = new UsersCache().Value.FirstOrDefault(x => x.Id == new Guid(HttpContext.User.Identity.GetUserId())); if (user != null) ViewBag.IsUOMHours = !user.PreferredResourceAllocation; return View(); } [HttpPost] public ActionResult GetCompanies() { return Json( DbContext.Companies.AsNoTracking().Select(c=> new SelectListItem() { Text = c.Name, Value=c.Id.ToString() }).ToList()); } [HttpPost] public ActionResult LoadJsonCalendar(CapacityDetailsModel model) { if (model == null || ((model.CompanyId == null || model.CompanyId == Guid.Empty) && (model.TeamId == null ||model.TeamId == Guid.Empty) && (model.ViewId == null || model.ViewId == Guid.Empty))) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); if (model.CompanyId != null && model.CompanyId != Guid.Empty) { var company = DbContext.Companies.AsNoTracking().FirstOrDefault(t => t.Id == model.CompanyId); if (company == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } if (model.TeamId != null && model.TeamId != Guid.Empty) { var team = DbContext.Teams.AsNoTracking().FirstOrDefault(t => t.Id == model.TeamId); if (team == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } if (model.ViewId != null && model.ViewId != Guid.Empty) { var team = DbContext.Views.AsNoTracking().FirstOrDefault(t => t.Id == model.ViewId); if (team == null) return new HttpStatusCodeResult(HttpStatusCode.BadRequest); } model.StartDate = DateTime.Today; model.EndDate = DateTime.Today.AddYears(1); return Json(GetCalendar(model), JsonRequestBehavior.AllowGet); } /// /// Returns a list of all principal GUIDs (user himself and his roles) to be used in direct requests to Security and ProjectAccess tables /// private Guid[] GetUserPrincipals() { var userId = User.Identity.GetUserId(); AspNetUser user = (from c in DbContext.AspNetUsers where c.Id == userId select c).FirstOrDefault(); var roleids = (from c in user.AspNetRoles select c.Id).ToList(); roleids.Add(userId); var result = new Guid[roleids.Count() + 1]; for (int i = 0; i < roleids.Count(); i++) result[i] = new Guid(roleids[i]); result[roleids.Count()] = new Guid(userId); return result; } private CapacityDetailsModel GetCalendar(CapacityDetailsModel model) { DateTime periodStartDate; DateTime periodEndDate; Guid actualScenarioId = Guid.Empty; periodStartDate = model.StartDate; periodEndDate = model.EndDate; var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); var calendarList = new List(); var totalsList = new List(); var principals = GetUserPrincipals(); var projectAccesses = new ProjectAccessCache().Value.Where(x => principals.Contains(x.PrincipalId)).Select(x => x.ProjectId); List projects = null; var teamIds = new List(); var teams = new List(); var groupByTeamMode = model.GroupByTeam ?? false; #region Collect common data if (model.CompanyId != null && !model.CompanyId.Equals(Guid.Empty)) { var companyIds = DbContext.Companies.Where(c => c.ParentCompanyId == model.CompanyId).Select(c=>c.Id).ToList(); companyIds.Add(model.CompanyId.Value); projects = DbContext.Projects.Where(p => p.CompanyId.HasValue && companyIds.Contains(p.CompanyId.Value) && projectAccesses.Contains(p.Id) && !p.HasChildren).AsNoTracking().ToList(); projects.ForEach(p => p.Team2Project.Where(t2p=>!teams.Select(t => t.Id).Contains(t2p.TeamId)).ToList().ForEach(t => teams.Add(t.Team))); teamIds.AddRange(teams.Select(t=>t.Id)); } else if(model.TeamId != null && !model.TeamId.Equals(Guid.Empty)){ groupByTeamMode = false; projects = DbContext.Team2Project.Where(p => p.TeamId == model.TeamId && projectAccesses.Contains(p.ProjectId) && !p.Project.HasChildren).Select(x => x.Project).AsNoTracking().ToList(); projects.ForEach(p => p.Team2Project.Where(t2p => model.TeamId == t2p.TeamId && !teams.Select(t => t.Id).Contains(t2p.TeamId)).ToList().ForEach(t => teams.Add(t.Team))); teamIds.AddRange(teams.Select(t => t.Id)); } else if (model.ViewId != null && !model.ViewId.Equals(Guid.Empty)) { var viewTeamIds = (from tv in DbContext.Team2View where tv.ViewId == model.ViewId select tv.TeamId).ToList(); teamIds.AddRange(viewTeamIds); projects = DbContext.Team2Project.Where(p => teamIds.Contains(p.TeamId) && projectAccesses.Contains(p.ProjectId) && !p.Project.HasChildren). Select(x => x.Project).AsNoTracking().Distinct().ToList(); projects.ForEach(p => p.Team2Project.Where(t2p => teamIds.Contains(t2p.TeamId) && !teams.Select(t => t.Id).Contains(t2p.TeamId)).ToList().ForEach(t => teams.Add(t.Team))); } if (!model.GroupByTeam.HasValue || !model.GroupByTeam.Value || teams.Count() == 0) { teams = new List() { new Team(){Id = Guid.Empty} }; } var projectIds = new List(); projects.ForEach(p => projectIds.Add(p.Id)); var scenarioIds = new List(); projects.ForEach(p=> p.Scenarios.Where(s => s.Status.HasValue && s.Status.Value == (int)ScenarioStatus.Active && s.Type == (int)ScenarioType.Portfolio) .ToList().ForEach(s => scenarioIds.Add(s.Id))); var resourceAllocation = DbContext.PeopleResourceAllocations.Where(r => scenarioIds.Contains(r.ScenarioId)).ToList(); model.AllResources = new List(); var resourcesByTeams = DbContext.Teams.AsNoTracking().Where(x => teamIds.Contains(x.Id)). Select(x => x.PeopleResources); foreach (var teamResource in resourcesByTeams) { model.AllResources.AddRange(teamResource.Where(tr=>!model.AllResources.Select(ar=>ar.Id).Contains(tr.Id)). Select(x => new CapacityDetailsModel.ScenarioCalendarRowResource() { Id = x.Id, Name = string.Format("{0} {1}", x.FirstName, x.LastName), ExpedentureCategoryId = x.ExpenditureCategoryId, TeamId = x.TeamId.Value, AssignedToTeam = teamIds.Contains(x.TeamId.Value), ProjectIds = projects.SelectMany(p => p.Team2Project).Where(p => p.TeamId == x.TeamId.Value).Select(p=>p.ProjectId).ToArray(), IsActiveEmployee = x.IsActiveEmployee })); } var allResIds = model.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 scenarioDetails = DbContext.VW_ScenarioAndProxyDetails.AsNoTracking() .Where(t => t.ParentID.HasValue && scenarioIds.Contains(t.ParentID.Value) && t.WeekEndingDate >= periodStartDate && t.WeekEndingDate <= periodEndDate).OrderBy(t => t.ExpenditureCategoryName).ThenBy(t => t.WeekEndingDate); Dictionary> teams2scenarios = DbContext.Team2Scenario.AsNoTracking().Where(t2s => scenarioIds.Contains(t2s.ScenarioId)).GroupBy(t2s => t2s.ScenarioId).ToDictionary(key => key.Key, grouping => grouping.ToList()); var scenarioDetailsInternal = scenarioDetails.Select(t => new { t.Id, t.ExpenditureCategoryId, t.WeekEndingDate, t.GLId, t.ExpenditureCategoryName, t.Quantity, t.Cost, t.ParentID, t.WeekOrdinal, t.UseType, t.UOMId }).ToArray().GroupBy( key => new { key.ExpenditureCategoryId, key.ExpenditureCategoryName, key.ParentID, key.GLId, key.UseType, key.UOMId }).ToDictionary(key => key.Key, grouping => grouping.ToList()); var itemsCount = 0; CapacityDetailsModel.CalendarRow vacationRow = null; CapacityDetailsModel.CalendarRow trainingRow = null; CapacityDetailsModel.CalendarRow capacityRow = null; CapacityDetailsModel.CalendarRow grandTotalRow = null; var headerDates = (from c in DbContext.FiscalCalendars where c.Type == 0 && c.StartDate >= model.StartDate && c.EndDate <= model.EndDate orderby c.StartDate select c.EndDate); BuildHeaders(model, headerDates.ToList()); itemsCount = model.Headers.Count; #region Add Total (Active Scenarios), Vacation, Training, Loan-outs, Capacity grandTotalRow = new CapacityDetailsModel.CalendarRow() { Name = "Total (Active Scenarios)", QuantityValues = new decimal[model.Headers.Count], IsParentCollapsed = false, IsTotals = true, RowType = CapacityDetailsModel.RowType.Total }; vacationRow = new CapacityDetailsModel.CalendarRow { Name = "Vacation", QuantityValues = new decimal[model.Headers.Count], IsParentCollapsed = false, IsTotals = true, RowType = CapacityDetailsModel.RowType.Vacation }; totalsList.Add(vacationRow); trainingRow= new CapacityDetailsModel.CalendarRow() { Name = "Training", QuantityValues = new decimal[model.Headers.Count], IsParentCollapsed = false, IsTotals = true, RowType = CapacityDetailsModel.RowType.Training }; totalsList.Add(trainingRow); //ENV-623 Hide loan-outs, as there is no way to input them or use the feature in the system, yet. //totalsList.Add(new CapacityDetailsModel.CalendarRow() //{ // Name = "Loan-outs", // QuantityValues = new decimal[model.Headers.Count], // IsParentCollapsed = false, // IsTotals = true, // RowType = CapacityDetailsModel.RowType.LoanOut //}); totalsList.Add(grandTotalRow); capacityRow = new CapacityDetailsModel.CalendarRow() { Name = "Capacity", QuantityValues = new decimal[model.Headers.Count], IsParentCollapsed = false, IsTotals = true, RowType = CapacityDetailsModel.RowType.Capacity }; totalsList.Add(capacityRow); #endregion if (!model.IsUOMHours.HasValue) { var user = new UsersCache().Value.FirstOrDefault(x => x.Id == new Guid(HttpContext.User.Identity.GetUserId())); if (user != null) model.IsUOMHours = !user.PreferredResourceAllocation; } var allExpCats = DbContext.ExpenditureCategory.AsNoTracking().ToList(); var allUoms = DbContext.UOMs.AsNoTracking().ToList(); model.AllResources.ForEach(res => { res.VacationQuantityValues = new decimal[itemsCount]; res.TrainingQuantityValues = new decimal[itemsCount]; }); #endregion foreach (var team in teams) { var groupByTeam = !Guid.Empty.Equals(team.Id); if (groupByTeam && groupByTeamMode) { //Add Team row calendarList.Add(new CapacityDetailsModel.CalendarRow() { Name = team.Name, RowType = CapacityDetailsModel.RowType.Team, }); } foreach (var project in projects.Where(p => !groupByTeam || p.Team2Project.Select(t2p => t2p.TeamId).Contains(team.Id)).OrderBy(p => p.Name).ThenBy(p => p.Id)) { var projectActiveScenarios = project.Scenarios.Where(s => s.Status.HasValue && s.Status.Value == (int)ScenarioStatus.Active && s.Type == (int)ScenarioType.Portfolio).OrderBy(p => p.Name).ThenBy(p => p.Id); if (!projectActiveScenarios.Any()) continue; #region Add Project row calendarList.Add(new CapacityDetailsModel.CalendarRow() { ProjectId = project.Id, Name = (project.ParentProject != null ? project.ParentProject.Name + ": " : "") + project.Name, Color = !string.IsNullOrEmpty(project.Color) ? project.Color.Contains('#') ? project.Color : "#" + project.Color : (project.ParentProject != null ? project.ParentProject.Color.Contains('#') ? project.ParentProject.Color : "#" + project.ParentProject.Color : null ), DetailIds = new Guid?[itemsCount], QuantityValues = new decimal[itemsCount], SpreadVal = new CapacityDetailsModel.Spread[itemsCount], IsParentCollapsed = false, ExpCatId = null, ScenarioId = null, RowType = CapacityDetailsModel.RowType.Project, ReadOnly = new bool[itemsCount], TeamId = team.Id }); #endregion foreach (var scenario in projectActiveScenarios) { var projectRow = calendarList.FirstOrDefault(x => x.ProjectId == project.Id && x.TeamId == team.Id && x.ExpCatId == null); #region Add Scenario to Project row if (projectRow != null && projectRow.ScenarioId == null) { projectRow.ScenarioId = scenario.Id; projectRow.Name1 = scenario.Name; var dates1 = scenarioDetailsInternal.Where(sd => sd.Key.ParentID == scenario.Id).SelectMany(x => x.Value).ToList(); var dates = dates1.Select(x => x.WeekEndingDate.Value).ToList(); if (dates.Count > 0) projectRow.StartDate = (long)dates.Min().Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds; if (dates.Count > 0) projectRow.EndDate = (long)dates.Max().Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds; } #endregion List project2ExpCats = null; if (groupByTeam) { project2ExpCats = model.AllResources.Where(r => team.Id.Equals(r.TeamId)).Select(r => r.ExpedentureCategoryId).ToList(); } else { project2ExpCats = model.AllResources.Where(r => teamIds.Contains(r.TeamId)).Select(r => r.ExpedentureCategoryId).ToList(); } //Filter all ExpCats by available resorces var expCats = scenarioDetailsInternal.Where(sd => sd.Key.ParentID == scenario.Id && project2ExpCats.Contains(sd.Key.ExpenditureCategoryId.Value)); decimal teamAssignmentMultiplier = 0; if (groupByTeam) { teamAssignmentMultiplier = GetProjectNeedMultiplier(scenario.Id, new List(){ team.Id}, teams2scenarios); } else { teamAssignmentMultiplier = GetProjectNeedMultiplier(scenario.Id, teamIds, teams2scenarios); } foreach (var expCat in expCats) { #region Add exp cat row for the top part of the calendar calendarList.Add(new CapacityDetailsModel.CalendarRow() { ProjectId = project.Id, ScenarioId = scenario.Id, TeamId = team.Id, ExpCatId = expCat.Key.ExpenditureCategoryId, Name = expCat.Key.ExpenditureCategoryName, DetailIds = new Guid?[itemsCount], QuantityValues = new decimal[itemsCount], SpreadVal = new CapacityDetailsModel.Spread[itemsCount], CollapsedClass = "fa-plus-square-2", RestQuantity = new decimal[itemsCount], ReadOnly = new bool[itemsCount], EmptyScenario = false, RowType = CapacityDetailsModel.RowType.ProjectExpenditureCategory, Resources = model.AllResources.Where(x => resourceAllocation.Where(ec => ec.ExpenditureCategoryId == expCat.Key.ExpenditureCategoryId && ec.ScenarioId == scenario.Id && (!groupByTeam || (groupByTeam && team.Id == x.TeamId))) .Select(r => r.PeopleResourceId).Contains(x.Id)) .Select(x => new CapacityDetailsModel.ScenarioCalendarRowResource() { Id = x.Id, Name = x.Name, QuantityValues = new decimal[itemsCount], CapacityQuantityValues = new decimal[itemsCount], VacationQuantityValues = new decimal[itemsCount], TrainingQuantityValues = new decimal[itemsCount], ReadOnly = !teamIds.Contains(x.TeamId), IsActiveEmployee = x.IsActiveEmployee, TeamId = x.TeamId }).ToList(), }); #endregion var expCatRow = calendarList.Last(); #region Add exp cat row for the bottom part of the calendar var totalsListExpCat = totalsList.FirstOrDefault(t=>t.ExpCatId == expCatRow.ExpCatId); if (totalsListExpCat == null) { totalsListExpCat = new CapacityDetailsModel.CalendarRow() { ExpCatId = expCat.Key.ExpenditureCategoryId, Name = expCat.Key.ExpenditureCategoryName, QuantityValues = new decimal[itemsCount], SpreadVal = new CapacityDetailsModel.Spread[itemsCount], CollapsedClass = "fa-plus-square", IsParentCollapsed = false, EmptyScenario = false, QuantityTotalResValue = new decimal[itemsCount], QuantityExpCatTotalValue = new decimal[itemsCount], RowType = CapacityDetailsModel.RowType.BottomCategory, Resources = new List(model.AllResources. Where(x => x.ExpedentureCategoryId == expCat.Key.ExpenditureCategoryId && teamIds.Contains(x.TeamId)).Select(x => new CapacityDetailsModel.ScenarioCalendarRowResource() { Id = x.Id, ExpedentureCategoryId = x.ExpedentureCategoryId, Name = x.Name, QuantityValues = new decimal[itemsCount], SpreadVal = new CapacityDetailsModel.Spread[itemsCount], QuantityTotalResValue = new decimal[itemsCount], VacationQuantityValues = new decimal[itemsCount], TrainingQuantityValues = new decimal[itemsCount], GrandTotalCost = 0M, GrandTotalQuantity = 0M, IsActiveEmployee = x.IsActiveEmployee, TeamId = x.TeamId })) }; totalsList.Add(totalsListExpCat); } #endregion projectRow.EmptyScenario = false; var uomMultiplier = Utils.GetUOMMultiplier(allExpCats, allUoms, expCat.Key.ExpenditureCategoryId ?? Guid.Empty, model.IsUOMHours); var expCatUOM = allUoms.FirstOrDefault(t => t.Id == expCat.Key.UOMId); var uomValue = expCatUOM == null ? 0 : expCatUOM.UOMValue*uomMultiplier; expCatRow.Resources.ForEach(r => r.Title = (expCatRow.Resources.Exists(r1 => r1.ReadOnly) ? "There are resources from other team assigned to the category so you cannot use Take all" : "Take all")); var monthQuantity = 0.0M; var monthVacation = 0.0M; var monthTraining = 0.0M; var monthResVal = 0.0M; var monthResTotalVal = 0.0M; var weeksCount = 0; var resourceTotals = model.AllResources.Select(x => x.Id).ToDictionary(key => key, pairs => 0.0M); var resourceTotalsExpCatTotals = model.AllResources.Select(x => new Pairs() { Id = x.Id, Quantity = 0.0M }).ToList(); var isMonthReadOnly = true; // iterate through the weeks/months collection (1 month item following 4/5 week items) for (int colIndex = 0; colIndex < itemsCount; colIndex++) { var isScenarioDate = (model.Headers[colIndex].Milliseconds >= projectRow.StartDate && model.Headers[colIndex].Milliseconds <= projectRow.EndDate); expCatRow.ReadOnly[colIndex] = !isScenarioDate; projectRow.ReadOnly[colIndex] = !isScenarioDate; isMonthReadOnly &= !isScenarioDate; // if item is a new week if (!model.Headers[colIndex].IsMonth) { if (isScenarioDate) { var val = expCat.Value.FirstOrDefault(x => x.WeekEndingDate.Value.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds == model.Headers[colIndex].Milliseconds); //[colIndex1]; if (val != null) { expCatRow.DetailIds[colIndex] = val.Id; // column.Id; expCatRow.QuantityValues[colIndex] = (val.Quantity ?? 0) * uomMultiplier * teamAssignmentMultiplier; // (column.Quantity ?? 0) * uomMultiplier; monthQuantity += expCatRow.QuantityValues[colIndex]; } } else { expCatRow.QuantityValues[colIndex] = 0; expCatRow.DetailIds[colIndex] = null; } weeksCount++; //Project/Scenario row update projectRow.QuantityValues[colIndex] += expCatRow.QuantityValues[colIndex]; grandTotalRow.QuantityValues[colIndex] += expCatRow.QuantityValues[colIndex]; //Get resources quantity var date = epoch.AddSeconds(model.Headers[colIndex].Milliseconds / 1000); //Get allocation for the date var currAllocation = resourceAllocation.Where(r => r.WeekEndingDate == date && r.ExpenditureCategoryId == expCatRow.ExpCatId && r.ScenarioId == expCatRow.ScenarioId).ToList(); expCatRow.Resources.ForEach(x => { var peopleResourceAllocation = currAllocation.FirstOrDefault(ar => ar.PeopleResourceId == x.Id); if (peopleResourceAllocation != null) x.QuantityValues[colIndex] = (peopleResourceAllocation.Quantity ?? 0) * uomMultiplier; else x.QuantityValues[colIndex] = 0; if (expCatUOM != null) { var vacationsSum = allResourceVacations.Where(t => t.PeopleResourceId == x.Id && t.WeekEndingDate <= date && t.WeekEndingDate >= date.AddDays(-6)) .Sum(s => s.HoursOff); vacationRow.QuantityValues[colIndex] += vacationsSum * uomMultiplier; monthVacation += vacationsSum * uomMultiplier; grandTotalRow.QuantityValues[colIndex] += vacationsSum * uomMultiplier; var trainingsSum = allResourceTrainings.Where(t => t.PeopleResourceId == x.Id && t.WeekEndingDate <= date && t.WeekEndingDate >= date.AddDays(-6)) .Sum(s => s.HoursOff); trainingRow.QuantityValues[colIndex] += trainingsSum * uomMultiplier; monthTraining += trainingsSum * uomMultiplier; grandTotalRow.QuantityValues[colIndex] += trainingsSum * uomMultiplier; x.CapacityQuantityValues[colIndex] = (expCatUOM.UOMValue - vacationsSum - trainingsSum) * uomMultiplier; } if (resourceTotals.ContainsKey(x.Id)) resourceTotals[x.Id] += x.QuantityValues[colIndex]; }); //Calculate not allocated quantity expCatRow.RestQuantity[colIndex] = expCatRow.QuantityValues[colIndex] - expCatRow.Resources.Select(x => x.QuantityValues[colIndex]).Sum() * uomMultiplier; //Add resources to the capacity EC foreach (var resCloneFrom in expCatRow.Resources) { var resCloneTo = totalsListExpCat.Resources.FirstOrDefault(r => resCloneFrom.Id == r.Id); if (resCloneTo != null) { resCloneTo.QuantityValues[colIndex] += resCloneFrom.QuantityValues[colIndex]; } } totalsListExpCat.QuantityExpCatTotalValue[colIndex] += expCatRow.QuantityValues[colIndex]; #region Set validation class var resTotal = expCatRow.Resources.Select(r => r.QuantityValues[colIndex]).Sum(); var compareRes = compareValues(resTotal, expCatRow.QuantityValues[colIndex]); expCatRow.SpreadVal[colIndex] = compareRes == 0 ? CapacityDetailsModel.Spread.Equal : compareRes > 0 ? CapacityDetailsModel.Spread.Over : compareRes < 0 ? CapacityDetailsModel.Spread.Less : CapacityDetailsModel.Spread.Notspecified; if (projectRow.SpreadVal[colIndex] != CapacityDetailsModel.Spread.Over) { if (projectRow.SpreadVal[colIndex] != CapacityDetailsModel.Spread.Less) { if (expCatRow.SpreadVal[colIndex] != CapacityDetailsModel.Spread.Notspecified) { projectRow.SpreadVal[colIndex] = expCatRow.SpreadVal[colIndex]; } } else { if (expCatRow.SpreadVal[colIndex] == CapacityDetailsModel.Spread.Over) { projectRow.SpreadVal[colIndex] = expCatRow.SpreadVal[colIndex]; } } } #endregion //EC capacity totalsListExpCat.QuantityResValue = uomValue; totalsListExpCat.QuantityValues[colIndex] = totalsListExpCat.Resources.Select(x => x.QuantityValues[colIndex]).Sum(); expCatRow.Resources.ForEach(x => resourceTotalsExpCatTotals.FirstOrDefault(r => r.Id == x.Id).Quantity += x.QuantityValues[colIndex]); totalsListExpCat.QuantityTotalResValue[colIndex] = totalsListExpCat.Resources.Where(r => r.IsActiveEmployee).Count() * uomValue; totalsListExpCat.Resources.ForEach(r => r.QuantityTotalResValue[colIndex] = (r.IsActiveEmployee ? uomValue : 0)); monthResTotalVal += totalsListExpCat.QuantityTotalResValue[colIndex]; monthResVal += uomValue; #region Set validation class compareRes = compareValues(totalsListExpCat.QuantityValues[colIndex], totalsListExpCat.QuantityTotalResValue[colIndex]); totalsListExpCat.SpreadVal[colIndex] = compareRes == 0 ? CapacityDetailsModel.Spread.Equal : compareRes > 0 ? CapacityDetailsModel.Spread.Over : compareRes < 0 ? CapacityDetailsModel.Spread.Less : CapacityDetailsModel.Spread.Notspecified; totalsListExpCat.Resources.ForEach(r => r.SpreadVal[colIndex] = r.QuantityValues[colIndex] == r.QuantityTotalResValue[colIndex] ? CapacityDetailsModel.Spread.Equal : r.QuantityValues[colIndex] > r.QuantityTotalResValue[colIndex] ? CapacityDetailsModel.Spread.Over : r.QuantityValues[colIndex] < r.QuantityTotalResValue[colIndex] ? CapacityDetailsModel.Spread.Less : CapacityDetailsModel.Spread.Notspecified); #endregion } else//if (model.Headers[colIndex].IsMonth) if item is a month then let's summarize data of it's weeks { expCatRow.DetailIds[colIndex] = Guid.Empty; expCatRow.QuantityValues[colIndex] = monthQuantity; expCatRow.ReadOnly[colIndex] = isMonthReadOnly; projectRow.ReadOnly[colIndex] = isMonthReadOnly; isMonthReadOnly = true; expCatRow.Resources.ForEach(x => { x.QuantityValues[colIndex] = resourceTotals.ContainsKey(x.Id) ? resourceTotals[x.Id] : 0; }); vacationRow.QuantityValues[colIndex] += monthVacation; trainingRow.QuantityValues[colIndex] += monthTraining; grandTotalRow.QuantityValues[colIndex] += monthVacation + monthTraining; totalsListExpCat.QuantityExpCatTotalValue[colIndex] += expCatRow.QuantityValues[colIndex]; //Project/Scenario row update projectRow.QuantityValues[colIndex] += expCatRow.QuantityValues[colIndex]; grandTotalRow.QuantityValues[colIndex] += expCatRow.QuantityValues[colIndex]; #region Set validation class var resTotal = expCatRow.Resources.Select(r => r.QuantityValues[colIndex]).Sum(); var compareRes = compareValues(resTotal, expCatRow.QuantityValues[colIndex]); expCatRow.SpreadVal[colIndex] = compareRes == 0 ? CapacityDetailsModel.Spread.Equal : compareRes > 0 ? CapacityDetailsModel.Spread.Over : compareRes < 0 ? CapacityDetailsModel.Spread.Less : CapacityDetailsModel.Spread.Notspecified; if (projectRow.SpreadVal[colIndex] != CapacityDetailsModel.Spread.Over) { if (projectRow.SpreadVal[colIndex] != CapacityDetailsModel.Spread.Less) { if (expCatRow.SpreadVal[colIndex] != CapacityDetailsModel.Spread.Notspecified) { projectRow.SpreadVal[colIndex] = expCatRow.SpreadVal[colIndex]; } } else { if (expCatRow.SpreadVal[colIndex] == CapacityDetailsModel.Spread.Over) { projectRow.SpreadVal[colIndex] = expCatRow.SpreadVal[colIndex]; } } } //expCatRow.Resources.ForEach(r => r.SpreadVal[colIndex] = expCatRow.SpreadVal[colIndex]); #endregion totalsListExpCat.QuantityTotalResValue[colIndex] = monthResTotalVal; // totalsListExpCat.Resources.Count* UoMVal; totalsListExpCat.Resources.ForEach(x => { x.QuantityTotalResValue[colIndex] = (x.IsActiveEmployee ? monthResVal : 0); x.QuantityValues[colIndex] += resourceTotalsExpCatTotals.FirstOrDefault(r => r.Id == x.Id).Quantity; }); totalsListExpCat.QuantityValues[colIndex] = totalsListExpCat.Resources.Select(x => x.QuantityValues[colIndex]).Sum(); totalsListExpCat.QuantityResValue = uomValue; #region Set validation class to total exp cat row compareRes = compareValues(totalsListExpCat.QuantityValues[colIndex], totalsListExpCat.QuantityTotalResValue[colIndex]); totalsListExpCat.SpreadVal[colIndex] = compareRes == 0 ? CapacityDetailsModel.Spread.Equal : compareRes > 0 ? CapacityDetailsModel.Spread.Over : compareRes < 0 ? CapacityDetailsModel.Spread.Less : CapacityDetailsModel.Spread.Notspecified; compareRes = compareValues(totalsListExpCat.QuantityValues[colIndex], totalsListExpCat.QuantityTotalResValue[colIndex]); totalsListExpCat.Resources.ForEach(r => r.SpreadVal[colIndex] = compareValues(r.QuantityValues[colIndex], r.QuantityTotalResValue[colIndex]) == 0 ? CapacityDetailsModel.Spread.Equal : compareValues(r.QuantityValues[colIndex], r.QuantityTotalResValue[colIndex]) > 0 ? CapacityDetailsModel.Spread.Over : compareValues(r.QuantityValues[colIndex], r.QuantityTotalResValue[colIndex]) > 0 ? CapacityDetailsModel.Spread.Less : CapacityDetailsModel.Spread.Notspecified); #endregion monthQuantity = 0.0M; monthVacation = 0.0M; monthTraining = 0.0M; monthResVal = 0.0M; monthResTotalVal = 0.0M; weeksCount = 0; var keys = resourceTotals.Keys.ToArray(); foreach (var key in keys) { resourceTotals[key] = 0.0M; } resourceTotalsExpCatTotals.ForEach(x => x.Quantity = 0.0M); } } } } } } model.Calendar = calendarList; if (totalsList.Count == 0) { var dates = (from c in DbContext.FiscalCalendars where c.Type == 0 && c.StartDate >= model.StartDate && c.EndDate <= model.EndDate orderby c.StartDate select c.EndDate); BuildHeaders(model, dates.ToList()); #region Add Total (Active Scenarios), Vacation, Training, Loan-outs, Capacity grandTotalRow = new CapacityDetailsModel.CalendarRow() { Name = "Total (Active Scenarios)", QuantityValues = new decimal[model.Headers.Count], IsParentCollapsed = false, IsTotals = true, RowType = CapacityDetailsModel.RowType.Total }; vacationRow = new CapacityDetailsModel.CalendarRow { Name = "Vacation", QuantityValues = new decimal[model.Headers.Count], IsParentCollapsed = false, IsTotals = true, RowType = CapacityDetailsModel.RowType.Vacation }; totalsList.Add(vacationRow); trainingRow= new CapacityDetailsModel.CalendarRow() { Name = "Training", QuantityValues = new decimal[model.Headers.Count], IsParentCollapsed = false, IsTotals = true, RowType = CapacityDetailsModel.RowType.Training }; totalsList.Add(trainingRow); totalsList.Add(new CapacityDetailsModel.CalendarRow() { Name = "Loan-outs", QuantityValues = new decimal[model.Headers.Count], IsParentCollapsed = false, IsTotals = true, RowType = CapacityDetailsModel.RowType.LoanOut }); totalsList.Add(grandTotalRow); totalsList.Add(new CapacityDetailsModel.CalendarRow() { Name = "Capacity", QuantityValues = new decimal[model.Headers.Count], IsParentCollapsed = false, IsTotals = true, RowType = CapacityDetailsModel.RowType.Capacity }); #endregion } totalsList.ForEach(ec => ec.EmptyScenario = (ec.Resources == null || !ec.Resources.Any())); #region Add expected EC var totalExpCats = DbContext.PeopleResources.Where(r => teamIds.Contains(r.TeamId.Value)).Select(r => r.ExpenditureCategory).Distinct().ToList(); var remainingExpCats = totalExpCats.Where(ec => !totalsList.Select(ec1 => ec1.ExpCatId).Contains(ec.Id)).ToList(); var d = remainingExpCats.Select(ec => new CapacityDetailsModel.CalendarRow() { ExpCatId = ec.Id, Name = ec.Expenditure.Name, QuantityValues = new decimal[model.Headers.Count], SpreadVal = new CapacityDetailsModel.Spread[model.Headers.Count], CollapsedClass = "fa-plus-square", IsParentCollapsed = false, EmptyScenario = false, QuantityTotalResValue = new decimal[model.Headers.Count], QuantityExpCatTotalValue = new decimal[model.Headers.Count], RowType = CapacityDetailsModel.RowType.BottomCategory, Resources = new List(model.AllResources. Where(x => x.ExpedentureCategoryId == ec.Id && teamIds.Contains(x.TeamId)).Select(x => new CapacityDetailsModel.ScenarioCalendarRowResource() { Id = x.Id, ExpedentureCategoryId = x.ExpedentureCategoryId, Name = x.Name, QuantityValues = new decimal[model.Headers.Count], SpreadVal = new CapacityDetailsModel.Spread[model.Headers.Count], QuantityTotalResValue = new decimal[model.Headers.Count], GrandTotalCost = 0M, GrandTotalQuantity = 0M, IsActiveEmployee = x.IsActiveEmployee, TeamId = x.TeamId })) }).ToList(); foreach (var ec in d) { var uomMultiplier = Utils.GetUOMMultiplier(remainingExpCats, allUoms, ec.ExpCatId.Value, model.IsUOMHours); var UoMVal = remainingExpCats.First(x => x.Id == ec.ExpCatId).UOM.UOMValue * uomMultiplier; ec.QuantityResValue = UoMVal; var monthQuantity = 0.0M; var monthQuantityRes = 0.0M; for(var colIndex = 0; colIndex < model.Headers.Count; colIndex++) { if (model.Headers[colIndex].IsMonth){ ec.QuantityTotalResValue[colIndex] = monthQuantity; ec.Resources.ForEach(r => r.QuantityTotalResValue[colIndex] = (r.IsActiveEmployee ? monthQuantityRes : 0)); monthQuantity = 0.0M; monthQuantityRes = 0.0M; }else{ monthQuantity += UoMVal * ec.Resources.Where(r=>r.IsActiveEmployee).Count(); monthQuantityRes += UoMVal; ec.QuantityTotalResValue[colIndex] = UoMVal * ec.Resources.Where(r=>r.IsActiveEmployee).Count(); ec.Resources.ForEach(r => r.QuantityTotalResValue[colIndex] = (r.IsActiveEmployee ? UoMVal : 0)); } //ec.SpreadVal[colIndex] = ec.QuantityValues[colIndex] == ec.QuantityTotalResValue[colIndex] ? CapacityDetailsModel.Spread.Equal : // ec.QuantityValues[colIndex] > ec.QuantityTotalResValue[colIndex] ? CapacityDetailsModel.Spread.Over : // ec.QuantityValues[colIndex] < r.QuantityTotalResValue[colIndex] ? CapacityDetailsModel.Spread.Less : CapacityDetailsModel.Spread.Notspecified); ec.Resources.ForEach(r => r.SpreadVal[colIndex] = compareValues(r.QuantityValues[colIndex] , r.QuantityTotalResValue[colIndex]) ==0 ? CapacityDetailsModel.Spread.Equal : compareValues(r.QuantityValues[colIndex] , r.QuantityTotalResValue[colIndex]) > 0 ? CapacityDetailsModel.Spread.Over : compareValues(r.QuantityValues[colIndex] , r.QuantityTotalResValue[colIndex]) < 0 ? CapacityDetailsModel.Spread.Less : CapacityDetailsModel.Spread.Notspecified); } } totalsList.AddRange(d); #endregion #region set capacity row values as sum of each expenditure category capacities foreach (var bottomExpCatRow in totalsList.Where(t => CapacityDetailsModel.RowType.BottomCategory.Equals(t.RowType))) { if (bottomExpCatRow.QuantityTotalResValue != null) for (var weekIndex = 0; weekIndex < bottomExpCatRow.QuantityTotalResValue.Length; weekIndex++) { capacityRow.QuantityValues[weekIndex] += bottomExpCatRow.QuantityTotalResValue[weekIndex]; } } #endregion model.Calendar.AddRange(totalsList); #region Fill Vacation, trainig and loan out weekly allocations for each resource decimal monthResVacation = 0.0M; decimal monthResTraining = 0.0M; foreach (var res in model.AllResources) { var uomMultiplier = Utils.GetUOMMultiplier(allExpCats, allUoms, res.ExpedentureCategoryId ?? Guid.Empty, model.IsUOMHours); for (int colIndex = 0; colIndex < itemsCount; colIndex++) //(var column1 in expCat.Value) { var date = epoch.AddSeconds(model.Headers[colIndex].Milliseconds/1000); // if item is a new week if (!model.Headers[colIndex].IsMonth) { var vacationQuantityValue = allResourceVacations.Where(t => t.PeopleResourceId == res.Id && t.WeekEndingDate <= date && t.WeekEndingDate >= date.AddDays(-6)) .Sum(s => s.HoursOff) * uomMultiplier; res.VacationQuantityValues[colIndex] = vacationQuantityValue; monthResVacation += vacationQuantityValue; var trainingQuantityValue = allResourceTrainings.Where(t => t.PeopleResourceId == res.Id && t.WeekEndingDate <= date && t.WeekEndingDate >= date.AddDays(-6)) .Sum(s => s.HoursOff) * uomMultiplier; res.TrainingQuantityValues[colIndex] = trainingQuantityValue; monthResTraining += trainingQuantityValue; } else { res.VacationQuantityValues[colIndex] = monthResVacation; monthResVacation = 0.0M; res.TrainingQuantityValues[colIndex] = monthResTraining; monthResTraining = 0.0M; } } } #endregion return model; //return null; } private int compareValues(decimal val1, decimal val2) { var val1_ = decimal.Round( val1, 3); var val2_ = decimal.Round(val2, 3); if ((-0.005m > (val1_ - val2_))) return -1; else if ((0.005m < (val1_ - val2_))) return 1; else return 0; } public void BuildHeaders(CapacityDetailsModel model, List gridHeaders) { model.Headers = new List((int)(gridHeaders.Count * 1.25)); var prevMonth = string.Empty; var prevYear = string.Empty; var monthIndex = -1; var yearIndex = -1; CapacityDetailsModel.Header monthColumn = null; CapacityDetailsModel.Header yearColumn = 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"); //("MMMM yyyy"); var gridHeaderYearTitle = gridHeader.ToString("yyyy"); if (!prevMonth.Equals(gridHeaderTitle)) { if (monthColumn != null) { model.Headers.Add(monthColumn); yearColumn.SpanCount++; // model.Headers.Count - 1; } monthColumn = new CapacityDetailsModel.Header() { Show = true, Milliseconds = (long)gridHeader.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds, MonthIndex = ++monthIndex, IsMonth = true, Title = gridHeaderTitle, Year = gridHeaderYearTitle, Weeks = new List() }; } if (!prevYear.Equals(gridHeaderYearTitle)) { if (yearColumn != null) { model.YearHeaders.Add(yearColumn); } yearColumn = new CapacityDetailsModel.Header() { Show = true, Milliseconds = (long)gridHeader.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds, YearIndex = ++yearIndex, IsMonth = false, Title = gridHeaderYearTitle, //Weeks = new List() }; } var weekHeader = new CapacityDetailsModel.Header() { IsMonth = false, Milliseconds = (long)gridHeader.Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds, MonthIndex = monthColumn.MonthIndex, Title = gridHeader.ToString("dd") //ToShortDateString() }; model.Headers.Add(weekHeader); monthColumn.Weeks.Add(model.Headers.Count - 1); monthColumn.Milliseconds = weekHeader.Milliseconds; yearColumn.Milliseconds = weekHeader.Milliseconds; prevMonth = gridHeaderTitle; prevYear = gridHeaderYearTitle; } if (monthColumn != null) { model.Headers.Add(monthColumn); yearColumn.SpanCount++; } if (yearColumn != null) { model.YearHeaders.Add(yearColumn); } } private decimal GetProjectNeedMultiplier(Guid scenarioId, List calendarTeamIds, Dictionary> teams2scenarios) { if (!teams2scenarios.ContainsKey(scenarioId)) return 1; decimal mul = 0.0M; foreach (Team2Scenario t2s in teams2scenarios[scenarioId]) { foreach (Guid teamId in calendarTeamIds) { if (t2s.TeamId == teamId) mul += (decimal)t2s.Allocation; } } return mul / 100; } //[HttpGet] //public ActionResult GetScenarioAvailableExpCategories(Guid id) //{ // var availableExpenditures = // DbContext.VW_ExpCategoriesInScenario.AsNoTracking() // //.Where(t => t.ScenarioID == id) // .OrderBy(t => t.Name) // .Select(t => new { t.Id, t.Name }) // .ToArray(); // return Json(availableExpenditures, JsonRequestBehavior.AllowGet); //} [HttpPost] [AreaSecurityAttribute(area = Areas.Scenarios, level = AccessLevel.Write)] public ActionResult SaveChanges(SaveCapacityDetailsChangesModel model) { //if (model.ScenarioId != Guid.Empty && ContentLocker.IsLock("Scenario", model.ScenarioId.ToString(), User.Identity.Name)) // return new HttpStatusCodeResult(HttpStatusCode.BadRequest); var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); var context = new EnVisageEntities(); model.TrimStringProperties(); var allExpCats = DbContext.ExpenditureCategory.AsNoTracking().ToList(); var allUoms = DbContext.UOMs.AsNoTracking().ToList(); if (!model.ScenarioFilters.IsUOMHours.HasValue) { var user = new UsersCache().Value.FirstOrDefault(x => x.Id == new Guid(HttpContext.User.Identity.GetUserId())); 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(new DateTime(1970, 1, 1)).TotalMilliseconds : // (long)new DateTime(1970, 1, 1).Subtract(new DateTime(1970, 1, 1)).TotalMilliseconds; 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 == changedExpCat.ScenarioId && x.ExpenditureCategoryId == changedExpCat.Id).ToList(); foreach (var resource in changedExpCat.Resources) { var resourceId = resource.Id; if (resource.IsRemoved) { var recourcesToDelete = context.PeopleResourceAllocations.Where(x => x.PeopleResourceId == resource.Id && x.ScenarioId == changedExpCat.ScenarioId && x.ExpenditureCategoryId == changedExpCat.Id).ToList(); recourcesToDelete.ForEach(x => context.PeopleResourceAllocations.Remove(x)); recourcesToDelete.ForEach(x => context.Entry(x).State = System.Data.Entity.EntityState.Deleted); } else { var allocateResourceIdsUpdated = new List(); foreach (var changedResource in resource.Values) { //if (changedResource.Id.HasValue) { var date = epoch.AddSeconds(changedResource.Milliseconds / 1000); var allocatedResource = (from c in resourceAllocations where c.WeekEndingDate == date && c.PeopleResourceId == resourceId select c).FirstOrDefault(); if (changedResource.Quantity <= 0) { if (allocatedResource != null) context.Entry(allocatedResource).State = System.Data.Entity.EntityState.Deleted; continue; } if (allocatedResource == null) { allocatedResource = context.PeopleResourceAllocations.Create(); allocatedResource.Id = Guid.NewGuid(); allocatedResource.ExpenditureCategoryId = changedExpCat.Id; allocatedResource.PeopleResourceId = resourceId; allocatedResource.ScenarioId = changedExpCat.ScenarioId; allocatedResource.WeekEndingDate = date; context.Entry(allocatedResource).State = System.Data.Entity.EntityState.Added; } else { context.Entry(allocatedResource).State = System.Data.Entity.EntityState.Modified; } allocatedResource.Quantity = changedResource.Quantity / uomMultiplier; } //var recourcesToDelete = context.PeopleResourceAllocations.Where(x => allocateResourceIdsUpdated.Contains(x.Id)).ToList(); //recourcesToDelete.ForEach(x => context.PeopleResourceAllocations.Remove(x)); //recourcesToDelete.ForEach(x => context.Entry(x).State = System.Data.Entity.EntityState.Deleted); } } } } } 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); } } }