using EnVisage.Code.BLL; using EnVisage.Models; using EnVisage.Properties; using System; using System.Collections.Generic; using System.Linq; namespace EnVisage.Code.Charts { public class DashboardChartManager: IDisposable { #region Properties /// /// Identifier ProjectTypeId for 'Other' data element /// protected readonly Guid C_PROJECTTYPES_OTHER_TYPE_ID = new Guid("19BCFF07-2A1C-464C-AAEB-347576E381B2"); /// /// Identifier GoalId for 'Other' data element /// protected readonly Guid C_GOALS_OTHER_GOAL_ID = new Guid("CC8114A0-94DC-4641-908C-4143C2568098"); readonly EnVisageEntities _db; #endregion #region Models public class Data { public decimal? cost { get; set; } public decimal? quantity { get; set; } public DateTime? weekend { get; set; } public string projectStatus { get; set; } public string projectStatusColor { get; set; } public int? type { get; set; } public string Name { get; set; } public Guid? ExpCatId { get; set; } public Guid ProjectId { get; set; } public Guid ProjectType { get; set; } public decimal? ScenarioBUCost { get; set; } public int ScenarioDuration { get; set; } public decimal ProjectPriority { get; internal set; } public string ProjectName { get; internal set; } public DateTime? StardDate { get; internal set; } public DateTime? EndDate { get; internal set; } public string ProjectColor { get; internal set; } public decimal? PerformanceRedThreshold { get; internal set; } public decimal? PerformanceYellowThreshold { get; internal set; } public string ParentProjectColor { get; internal set; } public decimal? VariationPercent { get; internal set; } public Guid? ParentProjectId { get; internal set; } public string ParentProjectName { get; internal set; } } public class PieData { public List TypeId { get; set; } public List PieDatas { get; set; } public string Label { get; set; } public decimal Value { get; set; } public string PresetColor { get; set; } } internal struct NptWeeklyModel { public DateTime WeekEndingDate { get; set; } public decimal HoursOff { get; set; } public Guid ExpenditureCategoryId { get; set; } public decimal Cost { get; set; } } public class StatisticData { public IQueryable Data { get; set; } public IEnumerable FiscalCalendarWeekendings { get; set; } public long[] FiscalCalendarChartPoints { get; set; } public List FilteredTeams { get; set; } } #endregion #region Constructors public DashboardChartManager() { _db = new EnVisageEntities(); } public void Dispose() { _db?.Dispose(); } #endregion #region Private Methods #region Optimuse Private Methods private long GetOptimuseWeeklyHoursData(ForecastDashboardChartCapacityModel row, long weekEndingMs) { return row == null ? 0 : GetOptimuseWeeklyHoursData(new List { row }, weekEndingMs); } private long GetOptimuseWeeklyHoursData(List rows, long weekEndingMs) { if (rows == null || rows.Count <= 0) return 0; return rows.Sum(x => GetOptimuseWeeklyData(x.Hours, weekEndingMs)); } private long GetOptimuseWeeklyResourcesData(ForecastDashboardChartCapacityModel row, long weekEndingMs) { return row == null ? 0 : GetOptimuseWeeklyResourcesData(new List { row }, weekEndingMs); } private long GetOptimuseWeeklyResourcesData(List rows, long weekEndingMs) { if (rows == null || rows.Count <= 0) return 0; return rows.Sum(x => GetOptimuseWeeklyData(x.Resources, weekEndingMs)); } private long GetOptimuseWeeklyData(List rowData, long weekEndingMs) { if (rowData == null || rowData.Count <= 0) return 0; var weeklyData = rowData.FirstOrDefault(x => x != null && x.Length == 2 && x[0] == weekEndingMs); if (weeklyData != null) return weeklyData[1]; return 0; } #endregion private Dictionary GetWeeklyTeamCapacity(List teamIds, Dictionary expCats, Dictionary uoms, bool isPlanned, List fiscalCalendarWeekendings) { if (teamIds == null || teamIds.Count <= 0) return new Dictionary(); var context = new EnVisageEntities(); var scenarios = isPlanned ? context.Teams.AsNoTracking() .Where(s => teamIds.Contains(s.Id) && s.PlannedCapacityScenarioId != null) .Select(s => s.PlannedCapacityScenarioId.Value).ToArray() : context.Teams.AsNoTracking() .Where(s => teamIds.Contains(s.Id) && s.ActualCapacityScenarioId != null) .Select(s => s.ActualCapacityScenarioId.Value).ToArray(); var scenarioIds = scenarios.ToList(); var result = isPlanned ? GetWeeklyPlannedCapacityAdjusted(scenarioIds, expCats, uoms, fiscalCalendarWeekendings) : GetWeeklyActualCapacityAdjusted(scenarioIds, expCats, uoms, fiscalCalendarWeekendings); return result; } private Dictionary GetWeeklyCapacity(Guid[] sccenarioIds, Dictionary expCats, Dictionary uoms, List fiscalCalendarWeekendings) { var weeklyCapacity = new Dictionary(); var context = new EnVisageEntities(); var expCatIds = expCats.Keys.ToArray(); var sds = (from sd in context.ScenarioDetail.AsNoTracking() where sccenarioIds.Contains(sd.ParentID.Value) && fiscalCalendarWeekendings.Contains(sd.WeekEndingDate.Value) && expCatIds.Contains(sd.ExpenditureCategoryId.Value) && sd.ExpenditureCategoryId.HasValue select sd).ToArray(); // fill dictionary with weekly capacity for each week foreach (var week in fiscalCalendarWeekendings) { if (!weeklyCapacity.ContainsKey(week)) { var weekCapacityModel = new ForecastDashboardWeeklyCapacityModel(); var weeklyDetails = sds.Where(s => s.WeekEndingDate == week).ToList(); foreach (var item in weeklyDetails) { weekCapacityModel.Hours += item.Quantity ?? 0; weekCapacityModel.Resources += (item.Quantity ?? 0) * Utils.GetUOMMultiplier(expCats, uoms, item.ExpenditureCategoryId.Value, false); weekCapacityModel.Cost += item.Cost ?? 0; } weeklyCapacity.Add(week, weekCapacityModel); } } return weeklyCapacity; } private Dictionary GetWeeklyPlannedCapacityAdjusted(List scenarios, Dictionary expCats, Dictionary uoms, List fiscalCalendarWeekendings) { Dictionary weeklyCapacity = new Dictionary(); if (fiscalCalendarWeekendings == null) throw new ArgumentNullException(nameof(fiscalCalendarWeekendings)); if (fiscalCalendarWeekendings.Count < 1) return weeklyCapacity; ScenarioManager scMngr = new ScenarioManager(_db); DateTime startDateWe = fiscalCalendarWeekendings.Min(); DateTime endDateWe = fiscalCalendarWeekendings.Max(); var expCatIds = expCats.Keys.ToList(); var planCapacityAdjusted = scMngr.GetPlanningCapacityAdjusted(scenarios.ToList(), expCatIds, startDateWe, endDateWe); var capacityPrepared = fiscalCalendarWeekendings.ToDictionary(k => k, v => new List()); planCapacityAdjusted.Values.ToList().ForEach(x => x.Values.ToList() .ForEach(z => z.RemoveAll(model => !model.WeekEndingDate.HasValue || !fiscalCalendarWeekendings.Contains(model.WeekEndingDate.Value)))); planCapacityAdjusted.Values.ToList().ForEach(x => x.Values.ToList() .ForEach(z => z.ForEach(model => capacityPrepared[model.WeekEndingDate.Value].Add(model)))); weeklyCapacity = fiscalCalendarWeekendings.Select(we => new { Weekending = we, Data = new ForecastDashboardWeeklyCapacityModel() { Hours = capacityPrepared.ContainsKey(we) && capacityPrepared[we] != null && (capacityPrepared[we].Count > 0) ? capacityPrepared[we].Select(model => model.Quantity).Sum() : 0, Cost = capacityPrepared.ContainsKey(we) && capacityPrepared[we] != null && (capacityPrepared[we].Count > 0) ? capacityPrepared[we].Select(model => model.Cost).Sum() : 0, Resources = capacityPrepared.ContainsKey(we) && capacityPrepared[we] != null && (capacityPrepared[we].Count > 0) ? capacityPrepared[we].Select(model => model.Quantity * Utils.GetUOMMultiplier(expCats, uoms, model.ExpenditureCategoryId, false)).Sum() : 0, } }).ToDictionary(k => k.Weekending, v => v.Data); return weeklyCapacity; } private Dictionary GetWeeklyActualCapacityAdjusted(List scenarios, Dictionary expCats, Dictionary uoms, List fiscalCalendarWeekendings) { // Get direct actual capacity (with no adjustments) Dictionary result = GetWeeklyCapacity(scenarios.ToArray(), expCats, uoms, fiscalCalendarWeekendings); // Get holiday adjustments var expCatIds = expCats.Keys.ToList(); var adjRecordsByWeekendings = _db.VW_ActualCapacityAdjustmentByExpCats.AsNoTracking() .Where(x => scenarios.Contains(x.ActualCapacityScenarioId) && expCatIds.Contains(x.ExpenditureCategoryId) && fiscalCalendarWeekendings.Contains(x.WeekEndingDate)) .GroupBy(x => x.WeekEndingDate) .ToDictionary(k => k.Key, grp => new ScenarioDetailWithProxyItemModel() { WeekEndingDate = grp.Key, ExpenditureCategoryId = grp.Any() ? grp.FirstOrDefault().ExpenditureCategoryId : Guid.Empty, Quantity = grp.Where(z => z.ResourcesTotalOffHrs.HasValue).Select(z => z.ResourcesTotalOffHrs.Value).Sum(), Cost = grp.Where(z => z.ResourcesTotalOffCost.HasValue).Select(z => z.ResourcesTotalOffCost.Value).Sum(), }); // Applying holiday corrections foreach (DateTime we in result.Keys) { if (adjRecordsByWeekendings.ContainsKey(we)) { var capacityItem = result[we]; var adjItem = adjRecordsByWeekendings[we]; result[we].Hours = capacityItem.Hours >= adjItem.Quantity ? (capacityItem.Hours - adjItem.Quantity) : 0; result[we].Cost = capacityItem.Cost >= adjItem.Cost ? (capacityItem.Cost - adjItem.Cost) : 0; result[we].Resources = capacityItem.Hours >= adjItem.Quantity ? capacityItem.Resources - adjItem.Quantity * Utils.GetUOMMultiplier(expCats, uoms, adjItem.ExpenditureCategoryId, false) : 0; } } return result; } private DonutChartData GetPieChartData( IQueryable projects, bool isLaborMode, DateTime? startDate, DateTime? endDate, IEnumerable projectTypesFilter, IEnumerable strategicGoalsFilter) { var projectIds = projects.Select(q => q.Id).ToArray(); var expCatIds = GetExpenditureCategories(isLaborMode); var scTypes = new List { (int)ScenarioType.Portfolio }; var scenarios = _db.Scenarios.AsNoTracking().Where(x => x.Status == (int?)ScenarioStatus.Active && scTypes.Contains(x.Type) && x.ParentId.HasValue); var scenDetailParentIds = from sd in _db.ScenarioDetail.AsNoTracking() join s in scenarios on sd.ParentID.Value equals s.Id where expCatIds.Contains(sd.ExpenditureCategoryId.Value) select sd.ParentID; var scenariosList = scenarios.Where(q => scenDetailParentIds.Contains(q.Id)); projectTypesFilter = projectTypesFilter ?? new List(); strategicGoalsFilter = strategicGoalsFilter ?? new List(); var query = (from s in scenariosList //join sd in db.ScenarioDetail on s.Id equals sd.ParentID join p in _db.Projects on s.ParentId equals p.Id join sg2project in _db.StrategicGoal2Project on p.Id equals sg2project.ProjectId into sg2project_joined from sg2project in sg2project_joined.DefaultIfEmpty() join sg in _db.StrategicGoals.AsNoTracking() on sg2project.StrategicGoalId equals sg.Id into sg_joined from sg in sg_joined.DefaultIfEmpty() join projectType in _db.Types on p.TypeId equals projectType.Id where projectIds.Contains(s.ParentId.Value) //&& expCatIds.Contains(sd.ExpenditureCategoryId.Value) && ((!startDate.HasValue || s.EndDate >= startDate) && (!endDate.HasValue || s.StartDate <= endDate)) select new { ScenarioId = s.Id, s.Duration, Cost = (s.UseLMMargin ?? 0) == 1 ? s.BUDirectCosts_LM : s.BUDirectCosts, TypeId = projectType.Id, TypeName = projectType.Name, StrategicGoalId = sg != null ? sg.Id : Guid.Empty, StrategicGoalName = sg != null ? sg.Name : "No Goal" }).Distinct(); var projectTypes = query .GroupBy(x => new { x.TypeId, x.TypeName }) .Select(group => new DonutChartDataItem { ProjectTypeId = group.Key.TypeId, TypeId = new List() { group.Key.TypeId }, Cost = group.Sum(x => x.Cost), Duration = group.Sum(x => x.Duration), Label = group.Key.TypeName, ChildItems = group.GroupBy(pt => new { pt.StrategicGoalId, pt.StrategicGoalName }).Select(pt => new { Cost = pt.Sum(x => x.Cost), Duration = pt.Sum(x => x.Duration), Label = pt.Key.StrategicGoalName, TypeId = new List { pt.Key.StrategicGoalId }, GroupType = "Goal" }), GroupType = "ProjectType" }); if (projectTypesFilter.Any()) { projectTypes = projectTypes.Where(x => x.ProjectTypeId.HasValue && projectTypesFilter.Contains(x.ProjectTypeId.Value)); } var goals = query .GroupBy(x => new { x.StrategicGoalId, x.StrategicGoalName }) .Select(group => new DonutChartDataItem { GoalId = group.Key.StrategicGoalId, TypeId = new List() { group.Key.StrategicGoalId }, Cost = group.Sum(x => x.Cost), Duration = group.Sum(x => x.Duration), Label = group.Key.StrategicGoalName, ChildItems = group.GroupBy(pt => new { pt.TypeId, pt.TypeName }).Select(pt => new { Cost = pt.Sum(x => x.Cost), Duration = pt.Sum(x => x.Duration), Label = pt.Key.TypeName, TypeId = new List { pt.Key.TypeId }, GroupType = "ProjectType" }), GroupType = "Goal" }); if (strategicGoalsFilter.Any()) { goals = goals.Where(x => x.GoalId.HasValue && strategicGoalsFilter.Contains(x.GoalId.Value)); } var result = new DonutChartData { Goals = goals.ToList(), ProjectTypes = projectTypes.ToList(), TotalCost = query.GroupBy(customer => customer.ScenarioId).Select(group => group.FirstOrDefault()).Sum(x => x.Cost), //TODO: check sum in projectTypes TotalDuration = query.GroupBy(customer => customer.ScenarioId).Select(group => group.FirstOrDefault()).Sum(x => x.Duration), //TODO: check sum in projectTypes }; return result; } private string GetLineColor(ForecastDashboardChartCapacityModel row) { switch (row.CapacityType) { case ForecastDashboardChartCapacityModel.ChartCapacityType.Project: return string.IsNullOrEmpty(row.Color) ? null : row.Color; //case ForecastDashboardChartCapacityModel.ChartCapacityType.NonProjectTime: // break; case ForecastDashboardChartCapacityModel.ChartCapacityType.ActualCapacity: return Settings.Default.ActualCapacityColor; case ForecastDashboardChartCapacityModel.ChartCapacityType.PlannedCapacity: return Settings.Default.PlannedCapacityColor; } return null; } private List BuildChartHeader4CapacityRow(ForecastDashboardChartCapacityModel row) { if (row == null) return null; var capacityRow = row.CapacityType == ForecastDashboardChartCapacityModel.ChartCapacityType.ActualCapacity || row.CapacityType == ForecastDashboardChartCapacityModel.ChartCapacityType.PlannedCapacity; var headers = new List { new { label = row.Name, data = row.Costs, type = "Cost", stack = !capacityRow, lines = new {show = true, fill = !capacityRow}, color = GetLineColor(row) }, new { label = row.Name, data = row.Hours, type = "Hours", stack = !capacityRow, lines = new {show = true, fill = !capacityRow}, color = GetLineColor(row) }, new { label = row.Name, data = row.Resources, type = "Resources", stack = !capacityRow, lines = new {show = true, fill = !capacityRow}, color = GetLineColor(row) } }; return headers; } private IEnumerable LimitChartDataAndGetOther(IEnumerable data, bool byGoals, bool byCost, out IList other) { other = new List(); var filteredData = new List(); var totalValue = byCost ? data.Sum(x => x.Cost ?? 0) : data.Sum(x => x.Duration ?? 0); var totalPercentage = 1.0M; if (totalValue > 0) { var orderedData = byCost ? data.OrderByDescending(x => x.Cost) : data.OrderByDescending(x => x.Duration); foreach (var item in orderedData) { if (item.ChildItems != null) { var children = item.ChildItems.Select(x => new DonutChartDataItem { Cost = x.Cost, Duration = x.Duration, Label = x.Label, TypeId = x.TypeId, GroupType = x.GroupType, }); IList otherChildren; item.ChildItems = LimitChartDataAndGetOther(children, byGoals, byCost, out otherChildren); item.OtherChildItems = otherChildren; } var value = byCost ? (item.Cost ?? 0) : item.Duration; var percentage = (value ?? 0) / totalValue; if (totalPercentage > .3M) { filteredData.Add(item); totalPercentage -= percentage; } else { other.Add(item); } } if (other.Count > 2) { var otherItem = new DonutChartDataItem { Label = "Other", TypeId = other.SelectMany(x => x.TypeId).ToList(), Cost = other.Sum(x => x.Cost), Duration = other.Sum(x => x.Duration), PieDatas = other.ToList(), GroupType = other.First().GroupType, // Set fixed identifiers for Other data element ProjectTypeId = !byGoals ? C_PROJECTTYPES_OTHER_TYPE_ID : (Guid?)null, GoalId = byGoals ? C_GOALS_OTHER_GOAL_ID : (Guid?)null, ChildItems = other.Where(x => x.ChildItems != null) .SelectMany(x => x.ChildItems) .GroupBy(x => x.Label) .Select(x => new { Cost = x.Sum(c => (decimal)c.Cost), Duration = x.Sum(c => (decimal)c.Duration), x.First().Label, x.First().GroupType, x.First().TypeId }), }; filteredData.Add(otherItem); } else { other.Clear(); filteredData = orderedData.ToList(); } } else { filteredData = data.ToList(); } return filteredData; } private long[] GetFiscalCalendarChartPoints_Weekly(DateTime startDate, DateTime endDate, ref DateTime firstWeekEnding, ref DateTime lastWeekEnding) { var weekEndings = FiscalCalendarManager.GetWeekendingsByRange(startDate, endDate, _db); var fiscalCalendarChartPoints = weekEndings.Select(Utils.ConvertToUnixDate).ToArray(); if (weekEndings.Count > 0) { firstWeekEnding = weekEndings[0]; lastWeekEnding = weekEndings[weekEndings.Count - 1]; } return fiscalCalendarChartPoints; } private StatisticData GetStatisticData(DateTime startDate, DateTime endDate, Guid userId, string type, List projectStatuses, List projectTypes, ForecastDashboardMode mode, List teams, List views, List companies, bool isLaborMode, List filterGroups, List strategicGoals, List tags, List clients) { List fiscalCalendarWeekendings = new List(); DateTime firstWeekEnding = DateTime.MinValue; DateTime lastWeekEnding = DateTime.MinValue; int typeInt = 0; if (!string.IsNullOrWhiteSpace(type) && type != "null") typeInt = (int)Enum.Parse(typeof(ScenarioType), type); var teamManager = new TeamManager(_db); var projectManager = new ProjectManager(_db); #region convert incoming params to teams var filteredTeams = teamManager.GetTeamsByUserFiltered(userId.ToString(), teams, views, companies).Select(t => t.TeamId).ToList(); #endregion var projects = projectManager.GetProjectsWithChildrenByTeams(filteredTeams, tags).AsQueryable(); if (strategicGoals != null && strategicGoals.Count > 0) { var sgs = _db.StrategicGoal2Project.Where(x => strategicGoals.Contains(x.StrategicGoalId)).Select(x => x.ProjectId).ToList(); projects = projects.Where(x => sgs.Contains(x.Id)); } var fiscalCalendarChartPoints = GetFiscalCalendarChartPoints_Weekly(startDate, endDate, ref firstWeekEnding, ref lastWeekEnding); fiscalCalendarWeekendings.AddRange(fiscalCalendarChartPoints.Select(i => Constants.UnixEpochDate.AddMilliseconds(i))); var isMainDashboardMode = mode == ForecastDashboardMode.MainDashboard; var data = GetWeeksData(firstWeekEnding, lastWeekEnding, projects, projectStatuses, projectTypes, clients, typeInt, isMainDashboardMode, isLaborMode, filterGroups, filteredTeams); return new StatisticData { Data = data, FiscalCalendarWeekendings = fiscalCalendarWeekendings, FiscalCalendarChartPoints = fiscalCalendarChartPoints, FilteredTeams = filteredTeams, }; } /// /// /// /// /// /// /// /// /// /// /// /// /// /// /// private IQueryable GetWeeksData(DateTime firstWeekEnding, DateTime lastWeekEnding, IQueryable projects, List projectStatuses, List projectTypes, List clients, int scenarioType, bool isMainDashboardMode, bool isLaborMode, List filterGroups, IEnumerable teams) { if (projectStatuses != null && projectStatuses.Count > 0) projects = projects.Where(x => projectStatuses.Contains(x.StatusId)); if (projectTypes != null && projectTypes.Count > 0) projects = projects.Where(x => projectTypes.Contains(x.TypeId)); if (clients != null && clients.Count > 0) projects = projects.Where(x => x.ClientId.HasValue && clients.Contains(x.ClientId.Value)); var projectIds = projects.Select(p => p.Id).ToArray(); var expCatIds = isLaborMode ? _db.ExpenditureCategory.Where(e => e.Type == (int)ExpenditureCategoryModel.CategoryTypes.Labor).Select(e => e.Id).ToArray() : _db.ExpenditureCategory.Where(e => e.Type != (int)ExpenditureCategoryModel.CategoryTypes.Labor).Select(e => e.Id).ToArray(); var scTypes = new List(); if (isMainDashboardMode) { scTypes.AddRange(new[] { (int)ScenarioType.Capacity, (int)ScenarioType.Vacation, (int)ScenarioType.Training }); } if (scenarioType != 0) scTypes.Add(scenarioType); else scTypes.Add((int)ScenarioType.Portfolio); var groupsFilter = filterGroups?.ToArray() ?? new Guid[0]; var data = from ta in _db.TeamAllocations.AsNoTracking() join sd in _db.ScenarioDetail.AsNoTracking() on new { ta.ScenarioId, ta.ExpenditureCategoryId, ta.WeekEndingDate } equals new { ScenarioId = (Guid)sd.ParentID, ExpenditureCategoryId = (Guid)sd.ExpenditureCategoryId, WeekEndingDate = (DateTime)sd.WeekEndingDate } join s in _db.Scenarios.AsNoTracking() on ta.ScenarioId equals s.Id join p in _db.Projects.AsNoTracking() on s.ParentId equals p.Id join sp in _db.VW_ScenarioPerformance on s.Id equals sp.ForecastScenarioId into sp_joined from sp in sp_joined.DefaultIfEmpty() where ta.WeekEndingDate >= firstWeekEnding && ta.WeekEndingDate <= lastWeekEnding && projectIds.Contains(p.Id) && projectIds.Contains(s.ParentId.Value) && s.Status == (int?)ScenarioStatus.Active && teams.Contains(ta.TeamId) && scTypes.Contains(s.Type) && expCatIds.Contains(ta.ExpenditureCategoryId) && (!groupsFilter.Any() || s.Scenario2Group.Any(g => groupsFilter.Contains(g.GroupId))) orderby ta.WeekEndingDate select new Data { cost = sd.Quantity > 0 ? (ta.Quantity / sd.Quantity) * sd.Cost : 0, quantity = ta.Quantity, weekend = ta.WeekEndingDate, projectStatus = p.Status.Name, projectStatusColor = p.Status.Color, type = s.Type, Name = s.Name, ScenarioBUCost = (s.UseLMMargin ?? 0) == 1 ? s.BUDirectCosts_LM : s.BUDirectCosts, ScenarioDuration = s.Duration ?? 0, StardDate = s.StartDate, EndDate = s.EndDate, ExpCatId = ta.ExpenditureCategoryId, ProjectId = p.Id, ProjectType = p.TypeId, ProjectPriority = p.Priority, ProjectName = p.Name, ProjectColor = p.Color, ParentProjectId = p.ParentProject != null ? p.ParentProject.Id : default(Guid?), ParentProjectName = p.ParentProject != null ? p.ParentProject.Name : null, ParentProjectColor = p.ParentProject != null ? p.ParentProject.Color : null, PerformanceRedThreshold = p.PerformanceRedThreshold ?? p.Type.PerformanceRedThreshold, PerformanceYellowThreshold = p.PerformanceYellowThreshold ?? p.Type.PerformanceYellowThreshold, VariationPercent = sp.VariationPercent }; return data; } /// /// Returns current, next and previous fiscal periods of given type for the datepoint /// /// /// /// Returns periods, if exist (-1 = prev, 0 = current, 1 = next) private Dictionary GetClosedToDateFiscalPeriods(DateTime datePoint, FiscalCalendarModel.FiscalYearType periodType) { Dictionary result = new Dictionary(); DateTime viewRangeStartDate = datePoint.AddYears(-2); DateTime viewRangeEndDate = datePoint.AddYears(2); List periods = _db.FiscalCalendars.AsNoTracking() .OrderBy(period => period.StartDate) .Where(period => period.Type == (int)periodType && period.StartDate >= viewRangeStartDate && period.EndDate <= viewRangeEndDate) .ToList(); int periodCount = periods.Count; for (int index = 0; index < periodCount; index++) { var periodItem = periods[index]; if (periodItem.StartDate <= datePoint && periodItem.EndDate >= datePoint) { result.Add(0, periodItem.Id); if (index > 0) result.Add(-1, periods[index - 1].Id); if (index + 1 < periodCount) result.Add(1, periods[index + 1].Id); break; } } return result; } private Dictionary GetPerformanceGraphData(Guid userId, DateTime datePoint, ForecastDashboardPerformanceGraphFilterModel filter, Func> getBarDataFunc) { var result = new Dictionary(); // Get list of project, that are available to given user, filtered for given team or view #region convert incoming params to teams var teamManager = new TeamManager(_db); var filteredTeams = teamManager.GetTeamsByUserFiltered(userId.ToString(), filter.Teams, filter.Views, filter.Companies).Select(t => t.TeamId); #endregion var projectManager = new ProjectManager(_db); var projects = projectManager.GetProjectsWithChildrenByTeams(filteredTeams, filter.Tags).AsQueryable(); var projectFilter = projects.Select(x => x.Id).ToList(); // Get current, prev and following fiscal periods Dictionary fiscalPeriods = GetClosedToDateFiscalPeriods(datePoint, FiscalCalendarModel.FiscalYearType.Quarter); Dictionary resultKeys = new Dictionary { { -1, "PrevPeriod" }, { 0, "CurrentPeriod" }, { 1, "NextPeriod" } }; for (int index = -1; index <= 1; index++) { if (!fiscalPeriods.ContainsKey(index)) { continue; } var request = new PerformanceDataRequest { CurrentPeriodId = fiscalPeriods[index], FilterGroups = filter.FilterGroups, ProjectFilter = projectFilter, ProjectTypes = filter.ProjectTypes, ProjectStatuses = filter.ProjectStatuses, Clients = filter.Clients, Companies = filter.Companies, Teams = filter.Teams, Views = filter.Views, StrategicGoals = filter.StrategicGoals, Tags = filter.Tags }; var projectClassRows = getBarDataFunc(request); int rowIndex = 1; var singleGraphDataBlock = new StackGraphDataBlock(); var chartSeriesCost = new ChartSeries(); var chartSeriesQuantity = new ChartSeries(); var projectClassRowsList = projectClassRows.ToList(); foreach (var row in projectClassRowsList.OrderByDescending(r => r.BUDirectCosts)) { AddValuesToChartSeries(rowIndex, chartSeriesCost, row.BarTitle, row.ActualsCostVariation, row.BUDirectCosts, row.ActualsTotalCost); rowIndex++; } rowIndex = 1; foreach (var row in projectClassRowsList.OrderByDescending(r => r.ForecastTotalQuantity)) { AddValuesToChartSeries(rowIndex, chartSeriesQuantity, row.BarTitle, row.ActualsQuantityVariation, row.ForecastTotalQuantity, row.ActualsTotalQuantity); rowIndex++; } singleGraphDataBlock.series.Add(chartSeriesCost.forecastDataSerie); singleGraphDataBlock.series.Add(chartSeriesCost.variationsPosDataSerie); singleGraphDataBlock.series.Add(chartSeriesCost.variationsNegDataSerie); singleGraphDataBlock.series.Add(chartSeriesCost.variationsEqDataSerie); singleGraphDataBlock.series.Add(chartSeriesCost.actualsDataSerie); singleGraphDataBlock.quantitySeries.Add(chartSeriesQuantity.forecastDataSerie); singleGraphDataBlock.quantitySeries.Add(chartSeriesQuantity.variationsPosDataSerie); singleGraphDataBlock.quantitySeries.Add(chartSeriesQuantity.variationsNegDataSerie); singleGraphDataBlock.quantitySeries.Add(chartSeriesQuantity.variationsEqDataSerie); singleGraphDataBlock.quantitySeries.Add(chartSeriesQuantity.actualsDataSerie); singleGraphDataBlock.barOptions = chartSeriesCost.barTitles; singleGraphDataBlock.barOptionsQuantity = chartSeriesQuantity.barTitles; string dataKey = resultKeys[index]; result.Add(dataKey, singleGraphDataBlock); } return result; } private IEnumerable GetBarDataByProject(PerformanceDataRequest request) { var table = _db.VW_ProjectPerformance.AsNoTracking().OrderByDescending(x => x.ProjectWeight); var query = GetProjectPerformanceData(table, request).ToList(); var result = query.Select(x => new BarData { ActualsCostVariation = x.ActualsCostVariation, ActualsTotalCost = x.ActualsTotalCost ?? 0, BarTitle = x.ProjectName, BUDirectCosts = x.BUDirectCosts ?? 0, ActualsTotalQuantity = x.ActualsTotalQuantity, ActualsQuantityVariation = x.ActualsQuantityVariation, ForecastTotalQuantity = x.ForecastTotalQuantity ?? 0 }).Take(5); return result; } private IEnumerable GetBarDataByProjectType(PerformanceDataRequest request) { var table = _db.VW_ProjectPerformance.AsNoTracking(); var query = GetProjectPerformanceData(table, request).ToList(); var result = (from row in query group row by new { row.ProjectTypeId, row.ProjectTypeName } into grp select new BarData { BarTitle = grp.Key.ProjectTypeName, BUDirectCosts = grp.Sum(x => x.BUDirectCosts.Value), ActualsCostVariation = grp.Any(x => x.ActualsCostVariation.HasValue) ? grp.Sum(x => x.ActualsCostVariation) : null, ActualsTotalCost = grp.Any(x => x.ActualsTotalCost.HasValue) ? grp.Sum(x => x.ActualsTotalCost) : null, ActualsTotalQuantity = grp.Any(x => x.ActualsTotalQuantity.HasValue) ? grp.Sum(x => x.ActualsTotalQuantity) : null, ActualsQuantityVariation = grp.Any(x => x.ActualsQuantityVariation.HasValue) ? grp.Sum(x => x.ActualsQuantityVariation) : null, ForecastTotalQuantity = grp.Sum(x => x.ForecastTotalQuantity) ?? 0, }).OrderByDescending(x => x.BUDirectCosts).Take(5); return result; } private IQueryable GetProjectPerformanceData(IQueryable table, PerformanceDataRequest request) { var query = ( from row in table where row.FiscalPeriodId == request.CurrentPeriodId && request.ProjectFilter.Contains(row.ProjectId) && row.BUDirectCosts.HasValue && (!request.FilterGroups.Any() || (row.ScenarioGroupId.HasValue && request.FilterGroups.Contains(row.ScenarioGroupId.Value))) && (!request.ProjectTypes.Any() || request.ProjectTypes.Contains(row.ProjectTypeId)) && (!request.ProjectStatuses.Any() || request.ProjectStatuses.Contains(row.StatusId)) && (!request.Clients.Any() || (row.ClientId.HasValue && request.Clients.Contains(row.ClientId.Value))) && (!request.StrategicGoals.Any() || (row.StrategicGoalId.HasValue && request.StrategicGoals.Contains(row.StrategicGoalId.Value))) && (!request.Tags.Any() || (row.TagID.HasValue && request.Tags.Contains(row.TagID.Value))) select row ).Select(x => new ForecastDashboardProjectPerformanceModel() { ForecastScenarioId = x.ForecastScenarioId, ActualsScenarioId = x.ActualsScenarioId, ForecastScenarioType = x.ForecastScenarioType, ForecastTotalCost = x.ForecastTotalCost, ActualsTotalCost = x.ActualsTotalCost, VariationPercent = x.VariationPercent, ProjectId = x.ProjectId, ProjectName = x.ProjectName, Status = x.Status, BUDirectCosts = x.BUDirectCosts, ScenarioEndDate = x.ScenarioEndDate, ProjectTypeId = x.ProjectTypeId, ProjectTypeName = x.ProjectTypeName, ProjectPriority = x.ProjectPriority, ActualsCostForecasted = x.ActualsCostForecasted, ActualsCostVariation = x.ActualsCostVariation, ProjectWeight = x.ProjectWeight, ForecastTotalQuantity = x.ForecastTotalQuantity, ActualsTotalQuantity = x.ActualsTotalQuantity, VariationQuantityPercent = x.VariationQuantityPercent, ActualsQuantityVariation = x.ActualsQuantityVariation, ScenarioStartDate = x.ScenarioStartDate, }).Distinct(); return query; } private void AddValuesToChartSeries(long graphPoint, ChartSeries chartSeries, string barTitle, decimal? actualsVariation, decimal forecastValue, decimal? actualsTotal) { if (actualsVariation.HasValue) { if (actualsVariation.Value > 0) { // Actual costs lager Forecast costs chartSeries.forecastDataSerie.data.AddValue(graphPoint, forecastValue); chartSeries.variationsPosDataSerie.data.AddValue(graphPoint, forecastValue + actualsVariation.Value); chartSeries.variationsNegDataSerie.data.AddValue(graphPoint, 0); chartSeries.variationsEqDataSerie.data.AddValue(graphPoint, 0); } else if (actualsVariation.Value == 0) { chartSeries.forecastDataSerie.data.AddValue(graphPoint, forecastValue); chartSeries.variationsPosDataSerie.data.AddValue(graphPoint, 0); chartSeries.variationsNegDataSerie.data.AddValue(graphPoint, 0); chartSeries.variationsEqDataSerie.data.AddValue(graphPoint, forecastValue + actualsVariation.Value); } else { // Actual costs less Forecast costs chartSeries.forecastDataSerie.data.AddValue(graphPoint, forecastValue); // ActualsCostVariation is negative value chartSeries.variationsPosDataSerie.data.AddValue(graphPoint, 0); chartSeries.variationsNegDataSerie.data.AddValue(graphPoint, forecastValue + actualsVariation.Value); chartSeries.variationsEqDataSerie.data.AddValue(graphPoint, 0); } } else { // No actauls chartSeries.forecastDataSerie.data.AddValue(graphPoint, forecastValue); chartSeries.variationsPosDataSerie.data.AddValue(graphPoint, 0); chartSeries.variationsNegDataSerie.data.AddValue(graphPoint, 0); chartSeries.variationsEqDataSerie.data.AddValue(graphPoint, 0); } // Store actuals data to virtual serie chartSeries.actualsDataSerie.data.AddValue(graphPoint, actualsTotal.HasValue ? Math.Abs(actualsVariation ?? 0m) : 0); // Store project name chartSeries.barTitles.AddValue(graphPoint, barTitle); } /// /// Fill absent weekends and normalize arrays /// private List FillMissingWeeends(List list, IEnumerable weekEnds) { if (list.Count < weekEnds.Count()) { var result = weekEnds.Except(list.Select(x => x[0])); list.AddRange(result.Select(x => new[] { x, 0 })); list.RemoveAll(x => !weekEnds.Contains(x[0])); } return list.OrderBy(x => x[0]).ToList(); } #endregion #region Public Methods public IQueryable GetExpenditureCategories(bool isLaborMode) { var result = from e in _db.ExpenditureCategory where isLaborMode && e.Type == (int)ExpenditureCategoryModel.CategoryTypes.Labor || !isLaborMode && e.Type != (int)ExpenditureCategoryModel.CategoryTypes.Labor select e.Id; return result; } public OptimuseModel GetOptimuseData(ForecastDashboardChartModel data) { if (data?.ChartData == null || data.ChartData.Count <= 0) throw new ArgumentNullException(nameof(data)); if (data.ChartData == null || data.ChartData.Count <= 0) throw new ArgumentNullException("chart data"); if (data.WeekEndings == null) throw new ArgumentNullException("week endings"); var result = new OptimuseModel(); var plannedCapacityRow = data.ChartData.Values.FirstOrDefault(x => x.CapacityType == ForecastDashboardChartCapacityModel.ChartCapacityType.PlannedCapacity); var actualCapacityRow = data.ChartData.Values.FirstOrDefault(x => x.CapacityType == ForecastDashboardChartCapacityModel.ChartCapacityType.ActualCapacity); var capacityAllocationRows = data.ChartData.Values .Where(x => x.CapacityType != ForecastDashboardChartCapacityModel.ChartCapacityType.ActualCapacity && x.CapacityType != ForecastDashboardChartCapacityModel.ChartCapacityType.PlannedCapacity) .ToList(); var systemSettingsManager = new SystemSettingsManager(_db); var optiMUSEThreshold = systemSettingsManager.OptiMUSEThreshold(); foreach (var weekEnding in data.WeekEndings) { var weekEndingMs = Utils.ConvertToUnixDate(weekEnding); var plannedCapacityResourcesValue = GetOptimuseWeeklyResourcesData(plannedCapacityRow, weekEndingMs); var actualCapacityResourcesValue = GetOptimuseWeeklyResourcesData(actualCapacityRow, weekEndingMs); var allocatedCapacityResourcesValue = GetOptimuseWeeklyResourcesData(capacityAllocationRows, weekEndingMs); var plannedCapacityHoursValue = GetOptimuseWeeklyHoursData(plannedCapacityRow, weekEndingMs); var actualCapacityHoursValue = GetOptimuseWeeklyHoursData(actualCapacityRow, weekEndingMs); var allocatedCapacityHoursValue = GetOptimuseWeeklyHoursData(capacityAllocationRows, weekEndingMs); var plannedCapacityResourcesResult = allocatedCapacityResourcesValue - plannedCapacityResourcesValue; var actualCapacityResourcesResult = allocatedCapacityResourcesValue - actualCapacityResourcesValue; var plannedCapacityHoursResult = allocatedCapacityHoursValue - plannedCapacityHoursValue; var actualCapacityHoursResult = allocatedCapacityHoursValue - actualCapacityHoursValue; var optiMUSEThresholdCef = optiMUSEThreshold / 100m; var plannedOptiMUSEThreshold = plannedCapacityHoursValue * optiMUSEThresholdCef; var actualOptiMUSEThreshold = actualCapacityHoursValue * optiMUSEThresholdCef; result.WeekEndings.Add(weekEndingMs); if (plannedCapacityHoursResult - plannedOptiMUSEThreshold > 0) { result.PlannedCapacity.OverAllocatedSummary.AddItem(weekEndingMs.ToString(), plannedCapacityHoursResult, plannedCapacityResourcesResult); } else if (plannedCapacityHoursResult + plannedOptiMUSEThreshold < 0) { result.PlannedCapacity.UnderAllocatedSummary.AddItem(weekEndingMs.ToString(), plannedCapacityHoursResult, plannedCapacityResourcesResult); } else { result.PlannedCapacity.IdealAllocatedSummary.AddItem(weekEndingMs.ToString(), plannedCapacityHoursResult, plannedCapacityResourcesResult); } if (actualCapacityHoursResult - actualOptiMUSEThreshold > 0) { result.ActualCapacity.OverAllocatedSummary.AddItem(weekEndingMs.ToString(), actualCapacityHoursResult, actualCapacityResourcesResult); } else if (actualCapacityHoursResult + actualOptiMUSEThreshold < 0) { result.ActualCapacity.UnderAllocatedSummary.AddItem(weekEndingMs.ToString(), actualCapacityHoursResult, actualCapacityResourcesResult); } else { result.ActualCapacity.IdealAllocatedSummary.AddItem(weekEndingMs.ToString(), actualCapacityHoursResult, actualCapacityResourcesResult); } result.PlannedCapacity.TotalHoursCapacity += plannedCapacityHoursValue; result.PlannedCapacity.TotalResourcesCapacity += plannedCapacityResourcesValue; result.ActualCapacity.TotalHoursCapacity += actualCapacityHoursValue; result.ActualCapacity.TotalResourcesCapacity += actualCapacityResourcesValue; } return result; } public ProjectsByStatusChartModel GetProjectsByStatusGraphModel(ForecastDashboardChartModel data) { var result = new ProjectsByStatusChartModel(); if (data == null) return result; result.PeriodStartDate = Utils.ConvertToUnixDate(data.PeriodStartDate); result.PeriodEndDate = Utils.ConvertToUnixDate(data.PeriodEndDate); // Get project statuses var context = new EnVisageEntities(); var statuses = context.Status.AsNoTracking() .Where(x => !x.IsSystem) .OrderByDescending(x => x.Probability100) .Select(x => x.Name).ToList(); foreach (var statusName in statuses) { if (!data.ChartData.ContainsKey(statusName)) continue; var statusHeaders = BuildChartHeader4CapacityRow(data.ChartData[statusName]); if (statusHeaders == null || statusHeaders.Count <= 0) continue; result.Headers.AddRange(statusHeaders); } var capacityRows = data.ChartData .Where(x => x.Value.CapacityType != ForecastDashboardChartCapacityModel.ChartCapacityType.Project) .Select(x => x.Value) .OrderBy(x => x.CapacityType) .ToArray(); foreach (var row in capacityRows) { var capacityHeaders = BuildChartHeader4CapacityRow(row); if (capacityHeaders == null || capacityHeaders.Count <= 0) continue; result.Headers.AddRange(capacityHeaders); } return result; } public IQueryable GetBubbleChartData(DateTime startDate, DateTime endDate, Guid userId, string type, List projectStatuses, List projectTypes, ForecastDashboardMode mode, List teams, List views, List companies, bool isLaborMode, List filterGroups, List strategicGoals, List tags, List clients) { var statisticData = GetStatisticData(startDate, endDate, userId, type, projectStatuses, projectTypes, mode, teams, views, companies, isLaborMode, filterGroups, strategicGoals, tags, clients); return statisticData.Data; } /// /// Returns top 5 projects performance data by cost /// /// Page display mode (general dashboard, team or view dashboard) /// /// /// Team or View id to filter projects /// public Dictionary GetPerformanceGraphDataByProjects(ForecastDashboardMode mode, Guid userId, DateTime datePoint, ForecastDashboardPerformanceGraphFilterModel filter) { return GetPerformanceGraphData(userId, datePoint, filter, GetBarDataByProject); } public ForecastDashboardChartModel GetData(DateTime startDate, DateTime endDate, Guid userId, string type, List projectStatuses, List projectTypes, List clients, ForecastDashboardMode mode, List teams, List views, List companies, bool isLaborMode, List filterGroups, List strategicGoals, List tags) { var statistic = GetStatisticData(startDate, endDate, userId, type, projectStatuses, projectTypes, mode, teams, views, companies, isLaborMode, filterGroups, strategicGoals, tags, clients); var data = statistic.Data; var fiscalCalendarChartPoints = statistic.FiscalCalendarChartPoints; var result = new ForecastDashboardChartModel { WeekEndings = statistic.FiscalCalendarWeekendings.OrderBy(x => x).ToList() }; if (result.WeekEndings != null && result.WeekEndings.Any()) { var firstWeek = new FiscalCalendarManager(_db).GetFirstWeek(result.WeekEndings[0]); if (firstWeek != null) { result.PeriodStartDate = firstWeek.StartDate; result.PeriodEndDate = result.WeekEndings[result.WeekEndings.Count - 1]; } } var expCats = isLaborMode ? _db.ExpenditureCategory.AsNoTracking().Where(e => e.Type == (int)ExpenditureCategoryModel.CategoryTypes.Labor).ToDictionary(e => e.Id) : _db.ExpenditureCategory.AsNoTracking().Where(e => e.Type != (int)ExpenditureCategoryModel.CategoryTypes.Labor).ToDictionary(e => e.Id); var uoms = _db.UOMs.AsNoTracking().ToDictionary(u => u.Id); //init result dictionary var nptData = new ForecastDashboardChartCapacityModel { Name = "Non-Project Time", CapacityType = ForecastDashboardChartCapacityModel.ChartCapacityType.NonProjectTime }; result.ChartData.Add("actualCapacity", new ForecastDashboardChartCapacityModel { Name = "Actual Capacity", CapacityType = ForecastDashboardChartCapacityModel.ChartCapacityType.ActualCapacity }); result.ChartData.Add("plannedCapacity", new ForecastDashboardChartCapacityModel { Name = "Planned Capacity", CapacityType = ForecastDashboardChartCapacityModel.ChartCapacityType.PlannedCapacity }); //capacity and project status data remains uninitialized as we do not know what capacity scenarios and project statuses we have var dataList = data.GroupBy(g => g.weekend).ToDictionary(k => k.Key, d => d.ToList()); var tmpProjectStatus = new Dictionary(); var weeklyPlannedCapacity = GetWeeklyTeamCapacity(statistic.FilteredTeams, expCats, uoms, true, result.WeekEndings); var weeklyActualCapacity = GetWeeklyTeamCapacity(statistic.FilteredTeams, expCats, uoms, false, result.WeekEndings); //var nptManager = new NonProjectTimeManager(_db); //var npTimes = nptManager.GetNonProjectTimes4Teams(statistic.FilteredTeams, startDate, endDate); //var allNonProjTime = new Dictionary>(); //foreach (var resourceNpt in npTimes.SelectMany(r => r.Value.Values)) //{ // foreach (var alloc in resourceNpt.Allocations) // { // var dt = Utils.ConvertFromUnixDate(Convert.ToInt64(alloc.Key)); // if (!allNonProjTime.ContainsKey(dt)) // allNonProjTime.Add(dt, new List()); // var items = allNonProjTime[dt]; // var nptItem = new NptWeeklyModel // { // ExpenditureCategoryId = resourceNpt.ExpenditureCategoryId, // WeekEndingDate = dt, // HoursOff = alloc.Value, // Cost = 0 // }; // if (resourceNpt.Costs.Count > 0 && resourceNpt.Costs.ContainsKey(alloc.Key)) // nptItem.Cost = resourceNpt.Costs[alloc.Key]; // items.Add(nptItem); // } //} foreach (var weekEnding in result.WeekEndings) { if (dataList.ContainsKey(weekEnding)) { foreach (var sd in dataList[weekEnding]) { //fill data into tmps if (!sd.type.HasValue) continue; if ((int)ScenarioType.Portfolio == sd.type.Value || (int)ScenarioType.Scheduling == sd.type.Value) { //the dataset is already filtered by correct scenario type so we're just summarizing the numbers here if (!tmpProjectStatus.ContainsKey(sd.projectStatus)) tmpProjectStatus.Add(sd.projectStatus, new ForecastDashboardWeeklyCapacityModel()); tmpProjectStatus[sd.projectStatus].ProjectStatusColor = string.IsNullOrEmpty(sd.projectStatusColor) ? sd.projectStatusColor : sd.projectStatusColor.IndexOf('#') == 0 ? sd.projectStatusColor : '#' + sd.projectStatusColor; tmpProjectStatus[sd.projectStatus].Cost += (sd.cost ?? 0); tmpProjectStatus[sd.projectStatus].Hours += (sd.quantity ?? 0); tmpProjectStatus[sd.projectStatus].Resources += (sd.quantity ?? 0) * Utils.GetUOMMultiplier(expCats, uoms, sd.ExpCatId ?? Guid.Empty, false); } else { throw new InvalidOperationException("Incorrect scenario type found"); } } } var wkending = (long)weekEnding.Subtract(Constants.UnixEpochDate).TotalMilliseconds; //NON-PROJECT TIME //var npTimeWeekly = allNonProjTime.ContainsKey(weekEnding) ? allNonProjTime[weekEnding] : new List(); //if (npTimeWeekly.Any()) //{ // var npTimeHours = npTimeWeekly.Sum(t => t.HoursOff); // var npTimeResources = npTimeWeekly.Sum(t => t.HoursOff * Utils.GetUOMMultiplier(expCats, uoms, t.ExpenditureCategoryId, false)); // var npTimeCost = npTimeWeekly.Sum(t => t.Cost); // nptData.Hours.Add(new long[] { wkending, (long)Math.Round(npTimeHours) }); // nptData.Resources.Add(new long[] { wkending, (long)Math.Round(npTimeResources) }); // nptData.Costs.Add(new long[] { wkending, (long)Math.Round(npTimeCost) }); //} foreach (string sn in tmpProjectStatus.Keys) { if (!result.ChartData.ContainsKey(sn)) result.ChartData.Add(sn, new ForecastDashboardChartCapacityModel() { CapacityType = ForecastDashboardChartCapacityModel.ChartCapacityType.Project, Name = sn }); result.ChartData[sn].Color = tmpProjectStatus[sn].ProjectStatusColor; result.ChartData[sn].Hours.Add(new[] { wkending, (long)(Math.Round(tmpProjectStatus[sn].Hours)) }); result.ChartData[sn].Resources.Add(new[] { wkending, (long)(Math.Round(tmpProjectStatus[sn].Resources)) }); result.ChartData[sn].Costs.Add(new[] { wkending, (long)(Math.Round(tmpProjectStatus[sn].Cost)) }); } tmpProjectStatus = new Dictionary(); } if (nptData.Hours.Count > 0) result.ChartData.Add("npTime", nptData); result.ChartData["actualCapacity"].Costs.AddRange(weeklyActualCapacity.Select(x => new[] { (long)x.Key.Subtract(Constants.UnixEpochDate).TotalMilliseconds, (long)(Math.Round(x.Value.Cost)) })); result.ChartData["actualCapacity"].Hours.AddRange(weeklyActualCapacity.Select(x => new[] { (long)x.Key.Subtract(Constants.UnixEpochDate).TotalMilliseconds, (long)(Math.Round(x.Value.Hours)) })); result.ChartData["actualCapacity"].Resources.AddRange(weeklyActualCapacity.Select(x => new[] { (long)x.Key.Subtract(Constants.UnixEpochDate).TotalMilliseconds, (long)(Math.Round(x.Value.Resources)) })); result.ChartData["plannedCapacity"].Costs.AddRange(weeklyPlannedCapacity.Select(x => new[] { (long)x.Key.Subtract(Constants.UnixEpochDate).TotalMilliseconds, (long)(Math.Round(x.Value.Cost)) })); result.ChartData["plannedCapacity"].Hours.AddRange(weeklyPlannedCapacity.Select(x => new[] { (long)x.Key.Subtract(Constants.UnixEpochDate).TotalMilliseconds, (long)(Math.Round(x.Value.Hours)) })); result.ChartData["plannedCapacity"].Resources.AddRange(weeklyPlannedCapacity.Select(x => new[] { (long)x.Key.Subtract(Constants.UnixEpochDate).TotalMilliseconds, (long)(Math.Round(x.Value.Resources)) })); //Fill missing weekends for all items in dictionary foreach (string key in result.ChartData.Keys) { result.ChartData[key].Hours = FillMissingWeeends(result.ChartData[key].Hours, fiscalCalendarChartPoints); result.ChartData[key].Resources = FillMissingWeeends(result.ChartData[key].Resources, fiscalCalendarChartPoints); result.ChartData[key].Costs = FillMissingWeeends(result.ChartData[key].Costs, fiscalCalendarChartPoints); } return result; } /// /// Returns top 5 projects performance data by cost /// /// Page display mode (general dashboard, team or view dashboard) /// /// /// Team or View id to filter projects /// public Dictionary GetPerformanceGraphDataByProjectClass(ForecastDashboardMode mode, Guid userId, DateTime datePoint, ForecastDashboardPerformanceGraphFilterModel filter) { return GetPerformanceGraphData(userId, datePoint, filter, GetBarDataByProjectType); } public IQueryable GetWeeksDataByResourceAllocation(Scenario scenario) { var data = from resAllocation in _db.PeopleResourceAllocations.AsNoTracking() join sd in _db.ScenarioDetail.AsNoTracking() on new { resAllocation.ScenarioId, resAllocation.ExpenditureCategoryId, resAllocation.WeekEndingDate } equals new { ScenarioId = (Guid)sd.ParentID, ExpenditureCategoryId = (Guid)sd.ExpenditureCategoryId, WeekEndingDate = (DateTime)sd.WeekEndingDate } join p in _db.Projects.AsNoTracking() on scenario.ParentId equals p.Id where resAllocation.ScenarioId == scenario.Id orderby resAllocation.WeekEndingDate select new Data { cost = sd.Cost, quantity = resAllocation.Quantity, weekend = resAllocation.WeekEndingDate, projectStatus = scenario.Project.Status.Name, projectStatusColor = scenario.Project.Status.Color, type = scenario.Type, Name = scenario.Name, ScenarioBUCost = (scenario.UseLMMargin ?? 0) == 1 ? scenario.BUDirectCosts_LM : scenario.BUDirectCosts, ScenarioDuration = scenario.Duration ?? 0, StardDate = scenario.StartDate, EndDate = scenario.EndDate, ExpCatId = resAllocation.ExpenditureCategoryId, }; return data; } public DoubleDonutChartData GetPieData(Guid userId, ForecastDashboardNewPieChartFilterModel filter) { #region convert incoming params to teams var teamManager = new TeamManager(_db); var filteredTeams = teamManager.GetTeamsByUserFiltered(userId.ToString(), filter.Teams, filter.Views, filter.Companies).Select(t => t.TeamId); #endregion var projectManager = new ProjectManager(_db); var projects = projectManager.GetProjectsWithChildrenByTeams(filteredTeams, filter.Tags, includedItems: ProjectManager.LoadProjectItems.Goals).AsQueryable(); if (filter.ProjectTypes != null && filter.ProjectTypes.Count > 0) projects = projects.Where(i => filter.ProjectTypes.Contains(i.TypeId)); if (filter.ProjectStatuses != null && filter.ProjectStatuses.Count > 0) projects = projects.Where(i => filter.ProjectStatuses.Contains(i.StatusId)); if (filter.Clients != null && filter.Clients.Count > 0) projects = projects.Where(i => i.ClientId.HasValue && filter.Clients.Contains(i.ClientId.Value)); if (filter.StrategicGoals != null && filter.StrategicGoals.Count > 0) projects = projects.Where(x => x.StrategicGoals != null && x.StrategicGoals.Any(s => filter.StrategicGoals.Contains(s))); var donutData = GetPieChartData(projects, filter.IsLaborMode, filter.StartDate, filter.EndDate, filter.ProjectTypes, filter.StrategicGoals); IList otherGoals; IList otherProjectTypes; var dataByCost = new DonutChartData { Goals = LimitChartDataAndGetOther(donutData.Goals, true, true, out otherGoals), ProjectTypes = LimitChartDataAndGetOther(donutData.ProjectTypes, false, true, out otherProjectTypes), OtherGoals = otherGoals, OtherProjectTypes = otherProjectTypes }; var dataByDuration = new DonutChartData { Goals = LimitChartDataAndGetOther(donutData.Goals, true, false, out otherGoals), ProjectTypes = LimitChartDataAndGetOther(donutData.ProjectTypes, false, false, out otherProjectTypes), OtherGoals = otherGoals, OtherProjectTypes = otherProjectTypes }; var result = new DoubleDonutChartData { CostData = dataByCost, DurationData = dataByDuration, TotalCost = donutData.TotalCost ?? 0, TotalDuration = donutData.TotalDuration ?? 0, }; return result; } #endregion //private long[] GetFiscalCalendarChartPoints_Monthly(DateTime startDate, DateTime endDate, ref DateTime firstWeekEnding, ref DateTime lastWeekEnding) //{ // var fiscalCalendarChartPoints = // ( // from f0 in db.FiscalCalendars // from f1 in db.FiscalCalendars // where f0.Type == (int)FiscalCalendarModel.FiscalYearType.Week && f1.Type == (int)FiscalCalendarModel.FiscalYearType.Month && // f1.YearInt == f0.YearInt && f0.StartDate <= f1.EndDate && f0.EndDate >= f1.EndDate && // f0.EndDate >= startDate && f0.EndDate <= endDate && f0.AdjustingPeriod == false && f0.NonWorking == 0 // select f0.EndDate // ) // .ToArray() // .Select(t => (long)t.Subtract(Constants.UnixEpochDate).TotalMilliseconds).ToArray(); // if (fiscalCalendarChartPoints.Length > 0) // { // firstWeekEnding = Constants.UnixEpochDate.AddMilliseconds(fiscalCalendarChartPoints[0]); // lastWeekEnding = Constants.UnixEpochDate.AddMilliseconds(fiscalCalendarChartPoints[fiscalCalendarChartPoints.Length - 1]); // } // return fiscalCalendarChartPoints; //} ///// ///// ///// ///// Start of reporting period. ///// End of reporting period. ///// A queryable collection of projects to filter by. ///// Indicates whether to load data for labor or for materials expenditure categories. ///// A list of data for each classification. //private List GetPieChartData(DateTime startDate, DateTime endDate, IQueryable projects, bool isLaborMode, List filterGroups, bool chartModeCost) //{ // var projectIds = (from p in projects select p.Id).ToArray(); // var expCatIds = isLaborMode // ? (from e in _db.ExpenditureCategory // where e.Type == (int)ExpenditureCategoryModel.CategoryTypes.Labor // select e.Id).ToArray() // : (from e in _db.ExpenditureCategory // where e.Type != (int)ExpenditureCategoryModel.CategoryTypes.Labor // select e.Id).ToArray(); // var scTypes = new List { (int)ScenarioType.Portfolio, (int)ScenarioType.Scheduling }; // var types = _db.Types.AsNoTracking().ToDictionary(key => key.Id, elem => elem.Name); // var query = _db.Scenarios.AsNoTracking().Where(x => x.Status == (int?)ScenarioStatus.Active); // if (filterGroups != null && filterGroups.Count > 0) // query = query.Where(x => filterGroups.Contains(x.Scenario2Group.FirstOrDefault().GroupId)); // var data = (from sd in _db.ScenarioDetail // join s in query on sd.ParentID equals s.Id // join p in _db.Projects on s.ParentId equals p.Id // //where !(s.StartDate <= StartDate || s.EndDate >= EndDate) // where sd.WeekEndingDate >= startDate && sd.WeekEndingDate <= endDate // && projectIds.Contains(s.ParentId.Value) // && scTypes.Contains(s.Type) && expCatIds.Contains(sd.ExpenditureCategoryId.Value) // select new // { // s.Id, // s.UseLMMargin, // s.BUDirectCosts_LM, // s.BUDirectCosts, // s.Duration, // p.TypeId, // }).Distinct().ToArray().GroupBy(group => group.TypeId).Select(grouping => new PieData // { // TypeId = new List() { grouping.Key }, // Value = (chartModeCost) ? grouping.Sum(x => (x.UseLMMargin ?? 0) == 1 ? x.BUDirectCosts_LM ?? 0 : x.BUDirectCosts ?? 0) : grouping.Sum(x => x.Duration ?? 0), // Label = types.ContainsKey(grouping.Key) ? types[grouping.Key] : string.Empty // }).ToList(); // return data; //} //private List GetPieChartGoalsData(DateTime startDate, DateTime endDate, IQueryable projects, bool isLaborMode, List filterGroups, List strategicGoals, bool chartModeCost) //{ // var projectIds = (from p in projects select p.Id).ToArray(); // var expCatIds = isLaborMode // ? (from e in _db.ExpenditureCategory // where e.Type == (int)ExpenditureCategoryModel.CategoryTypes.Labor // select e.Id).ToArray() // : (from e in _db.ExpenditureCategory // where e.Type != (int)ExpenditureCategoryModel.CategoryTypes.Labor // select e.Id).ToArray(); // var scTypes = new List { (int)ScenarioType.Portfolio, (int)ScenarioType.Scheduling }; // var goals = _db.StrategicGoals.AsNoTracking().ToDictionary(key => key.Id, elem => elem.Name); // var query = _db.Scenarios.AsNoTracking().Where(x => x.Status == (int?)ScenarioStatus.Active && scTypes.Contains(x.Type)); // if (filterGroups != null && filterGroups.Count > 0) // query = query.Where(x => filterGroups.Contains(x.Scenario2Group.FirstOrDefault().GroupId)); // var data = (from sd in _db.ScenarioDetail // join s in query on sd.ParentID equals s.Id // join p in _db.Projects on s.ParentId equals p.Id // join sg in _db.StrategicGoal2Project on p.Id equals sg.ProjectId // where sd.WeekEndingDate >= startDate && sd.WeekEndingDate <= endDate // && projectIds.Contains(s.ParentId.Value) // && expCatIds.Contains(sd.ExpenditureCategoryId.Value) // select new // { // s.Id, // s.UseLMMargin, // s.BUDirectCosts_LM, // s.BUDirectCosts, // s.Duration, // p.TypeId, // sg.StrategicGoalId // }).Distinct().ToArray().GroupBy(group => group.StrategicGoalId).Select(grouping => new PieData // { // TypeId = new List() { grouping.Key }, // Value = (chartModeCost) ? grouping.Sum(x => (x.UseLMMargin ?? 0) == 1 ? x.BUDirectCosts_LM ?? 0 : x.BUDirectCosts ?? 0) : grouping.Sum(x => x.Duration ?? 0), // Label = goals.ContainsKey(grouping.Key) ? goals[grouping.Key] : string.Empty // }).ToList(); // var projectsWithGoals = _db.StrategicGoal2Project.Select(x => x.ProjectId).Distinct().ToArray(); // if (!strategicGoals.Any()) // data.AddRange((from sd in _db.ScenarioDetail // join s in query on sd.ParentID equals s.Id // join p in _db.Projects on s.ParentId equals p.Id // where !projectsWithGoals.Contains(p.Id) && sd.WeekEndingDate >= startDate && sd.WeekEndingDate <= endDate // && projectIds.Contains(s.ParentId.Value) // && expCatIds.Contains(sd.ExpenditureCategoryId.Value) // select new // { // s.Id, // s.UseLMMargin, // s.BUDirectCosts_LM, // s.BUDirectCosts, // s.Duration, // p.TypeId // }).Distinct().ToArray().GroupBy(group => 0).Select(grouping => new PieData // { // TypeId = new List() { Guid.Empty }, // Value = (chartModeCost) ? grouping.Sum(x => (x.UseLMMargin ?? 0) == 1 ? x.BUDirectCosts_LM ?? 0 : x.BUDirectCosts ?? 0) : grouping.Sum(x => x.Duration ?? 0), // Label = "No Goal" // }).ToList()); // else data = data.Where(x => strategicGoals.Contains(x.TypeId[0])).ToList(); // return data; //} ///// ///// ///// ///// Start of reporting period. ///// End of reporting period. ///// A queryable collection of projects to filter by. ///// Indicates whether to load data for labor or for materials expenditure categories. ///// A list of data for each classification. //private List GetPieChartData(IQueryable projects, bool isLaborMode, bool chartModeCost) //{ // var projectIds = (from p in projects select p.Id).ToArray(); // var expCatIds = isLaborMode // ? (from e in _db.ExpenditureCategory // where e.Type == (int)ExpenditureCategoryModel.CategoryTypes.Labor // select e.Id).ToArray() // : (from e in _db.ExpenditureCategory // where e.Type != (int)ExpenditureCategoryModel.CategoryTypes.Labor // select e.Id).ToArray(); // var scTypes = new List { (int)ScenarioType.Portfolio, (int)ScenarioType.Scheduling }; // var types = _db.Types.AsNoTracking().ToDictionary(key => key.Id, elem => elem.Name); // var query = _db.Scenarios.AsNoTracking().Where(x => x.Status == (int?)ScenarioStatus.Active); // var data = (from sd in _db.ScenarioDetail // join s in query on sd.ParentID equals s.Id // join p in _db.Projects on s.ParentId equals p.Id // where projectIds.Contains(s.ParentId.Value) // && scTypes.Contains(s.Type) && expCatIds.Contains(sd.ExpenditureCategoryId.Value) // select new // { // s.Id, // s.UseLMMargin, // s.BUDirectCosts_LM, // s.BUDirectCosts, // s.Duration, // p.TypeId, // }).Distinct().ToArray().GroupBy(group => group.TypeId).Select(grouping => new PieData // { // TypeId = new List() { grouping.Key }, // Value = (chartModeCost) ? grouping.Sum(x => (x.UseLMMargin ?? 0) == 1 ? x.BUDirectCosts_LM ?? 0 : x.BUDirectCosts ?? 0) : grouping.Sum(x => x.Duration ?? 0), // Label = types.ContainsKey(grouping.Key) ? types[grouping.Key] : string.Empty // }).ToList(); // return data; //} //private List GetPieChartGoalsData(IQueryable projects, bool isLaborMode, bool chartModeCost) //{ // var projectIds = (from p in projects select p.Id).ToArray(); // var expCatIds = isLaborMode // ? (from e in _db.ExpenditureCategory // where e.Type == (int)ExpenditureCategoryModel.CategoryTypes.Labor // select e.Id).ToArray() // : (from e in _db.ExpenditureCategory // where e.Type != (int)ExpenditureCategoryModel.CategoryTypes.Labor // select e.Id).ToArray(); // var scTypes = new List { (int)ScenarioType.Portfolio, (int)ScenarioType.Scheduling }; // var scenarios = _db.Scenarios.AsNoTracking().Where(x => x.Status == (int?)ScenarioStatus.Active && scTypes.Contains(x.Type)); // var data = // from sd in _db.ScenarioDetail.AsNoTracking() // join s in scenarios on sd.ParentID equals s.Id // join p in _db.Projects on s.ParentId equals p.Id // join sg2project in _db.StrategicGoal2Project on p.Id equals sg2project.ProjectId into sg2project_joined // from sg2project in sg2project_joined.DefaultIfEmpty() // join sg in _db.StrategicGoals.AsNoTracking() on sg2project.StrategicGoalId equals sg.Id into sg_joined // from sg in sg_joined.DefaultIfEmpty() // where // projectIds.Contains(s.ParentId.Value) // && expCatIds.Contains(sd.ExpenditureCategoryId.Value) // select new // { // s.Id, // s.UseLMMargin, // s.BUDirectCosts_LM, // s.BUDirectCosts, // s.Duration, // p.TypeId, // StrategicGoalId = sg != null ? sg.Id : Guid.Empty, // StrategicGoalName = sg != null ? sg.Name : "No Goal" // }; // var result = data // .Distinct() // .GroupBy(x => new { x.StrategicGoalId, x.StrategicGoalName }) // .Select(group => new PieData // { // TypeId = new List() { group.Key.StrategicGoalId }, // Value = (chartModeCost) ? group.Sum(x => (x.UseLMMargin ?? 0) == 1 ? x.BUDirectCosts_LM ?? 0 : x.BUDirectCosts ?? 0) : group.Sum(x => x.Duration ?? 0), // Label = group.Key.StrategicGoalName // }); // return result.ToList(); //} } #region Dashboard performance graph public class StackGraphSerieData : List> { public void AddValue(long tickValue, decimal dataValue) { List point = new List {tickValue, dataValue}; this.Add(point); } } public class StackGraphSerie { public StackGraphSerieData data; public StackGraphSerie() { data = new StackGraphSerieData(); } } public class StackGraphBarOptions : List> { public void AddValue(long tickValue, string barTitle) { List option = new List {tickValue, barTitle}; this.Add(option); } } public class StackGraphDataBlock { public List series; public List quantitySeries; public StackGraphBarOptions barOptions; public StackGraphBarOptions barOptionsQuantity; public StackGraphDataBlock() { series = new List(); quantitySeries = new List(); } } class ChartSeries { public StackGraphSerie forecastDataSerie { get; private set; } public StackGraphSerie variationsPosDataSerie { get; private set; } public StackGraphSerie variationsNegDataSerie { get; private set; } public StackGraphSerie variationsEqDataSerie { get; private set; } public StackGraphSerie actualsDataSerie { get; private set; } public StackGraphBarOptions barTitles { get; private set; } public ChartSeries() { forecastDataSerie = new StackGraphSerie(); variationsPosDataSerie = new StackGraphSerie(); variationsNegDataSerie = new StackGraphSerie(); variationsEqDataSerie = new StackGraphSerie(); actualsDataSerie = new StackGraphSerie(); barTitles = new StackGraphBarOptions(); } } class PerformanceDataRequest { public PerformanceDataRequest() { FilterGroups = new List(); ProjectTypes = new List(); ProjectFilter = new List(); Views = new List(); Teams = new List(); Companies = new List(); Clients = new List(); StrategicGoals = new List(); Tags = new List(); } public Guid CurrentPeriodId { get; set; } public IEnumerable FilterGroups { get; set; } public IEnumerable ProjectTypes { get; set; } public IEnumerable ProjectStatuses { get; set; } public IEnumerable ProjectFilter { get; set; } public IEnumerable Teams { get; set; } public IEnumerable Views { get; set; } public IEnumerable Companies { get; set; } public IEnumerable StrategicGoals { get; internal set; } public IEnumerable Tags { get; set; } public IEnumerable Clients { get; set; } } class BarData { public string BarTitle { get; set; } public decimal? ActualsCostVariation { get; set; } public decimal BUDirectCosts { get; set; } public decimal? ActualsTotalCost { get; set; } public decimal? ActualsTotalQuantity { get; internal set; } public decimal? ActualsQuantityVariation { get; internal set; } public decimal ForecastTotalQuantity { get; internal set; } } #endregion }