using EnVisage.Code.BLL; using EnVisage.Models; using System; using System.Collections.Generic; using System.Linq; using System.Web; namespace EnVisage.Code.Charts { public class DashboardCharthManager { private class Data { public decimal? cost { get; set; } public decimal? quantity { get; set; } public DateTime? weekend { get; set; } public string projectStatus { 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 class PieData { public List TypeId { get; set; } public string Label { get; set; } public decimal Value { get; set; } public string PresetColor { get; set; } } EnVisageEntities db = new EnVisageEntities(); /// /// /// /// /// /// /// /// /// /// /// /// /// Indicates whether UOM (unit of measure) is hours or resources. /// public Dictionary> GetData(DateTime StartDate, DateTime EndDate, Guid userId, string type, List projectStatuses, List projectTypes, ForecastDashboardMode mode, List teams, List views, bool? isUOMHours, bool isLaborMode, List filterGroups, List strategicGoals) { var result = new Dictionary>(); var projects = GetFilteredProjectList(userId, teams, views); 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)); } int typeInt = 0; if (!string.IsNullOrWhiteSpace(type) && type != "null") typeInt = (int)Enum.Parse(typeof(ScenarioType), type); Dictionary expCats = new Dictionary(); if (isLaborMode) expCats = (from e in db.ExpenditureCategory where e.Type == (int)ExpenditureCategoryModel.CategoryTypes.Labor select e).ToDictionary(e => e.Id); else expCats = (from e in db.ExpenditureCategory where e.Type != (int)ExpenditureCategoryModel.CategoryTypes.Labor select e).ToDictionary(e => e.Id); List expCatGuids = expCats.Keys.ToList(); var uoms = db.UOMs.AsNoTracking().ToDictionary(u => u.Id); IQueryable data = null; long[] fiscalCalendarChartPoints = null; List fiscalCalendarWeekendings = new List(); DateTime firstWeekEnding = DateTime.MinValue; DateTime lastWeekEnding = DateTime.MinValue; if (EndDate.Subtract(StartDate).TotalDays <= 400) { fiscalCalendarChartPoints = (from c in db.FiscalCalendars where c.Type == (int)FiscalCalendarModel.FiscalYearType.Week && c.EndDate >= StartDate && c.EndDate <= EndDate && c.AdjustingPeriod == false && c.NonWorking == 0 orderby c.StartDate select c.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]); } fiscalCalendarWeekendings.AddRange(fiscalCalendarChartPoints.Select(i => Constants.UnixEpochDate.AddMilliseconds(i))); data = GetWeeksData(firstWeekEnding, lastWeekEnding, projects, projectStatuses, projectTypes, typeInt, mode != ForecastDashboardMode.MainDashboard, isLaborMode, filterGroups); } else { //ENV-848. As our StartDate's month END will be the very first X point on the graph, it looks like graph is starting with next month, so let's gather date for month before the start date StartDate = StartDate.AddMonths(-1); 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]); } fiscalCalendarWeekendings.AddRange(fiscalCalendarChartPoints.Select(i => Constants.UnixEpochDate.AddMilliseconds(i))); data = GetMonthData(projects, projectStatuses, projectTypes, typeInt, mode != ForecastDashboardMode.MainDashboard, isLaborMode, filterGroups, fiscalCalendarWeekendings); } var projectIds = projects.Select(t => t.Id).ToArray(); var allVacations = (from vacWeek in db.PeopleResourceVacations join vac in db.Vacations on vacWeek.VacationId equals vac.Id join p in db.PeopleResources on vac.PeopleResourceId equals p.Id join t in db.Team2Project on p.TeamId equals t.TeamId where (teams.Count == 0 || (p.TeamId.HasValue && teams.Contains(p.TeamId.Value))) && projectIds.Contains(t.ProjectId) && expCatGuids.Contains(p.ExpenditureCategoryId) select new { vacWeek.Id, vacWeek.WeekEndingDate, vacWeek.HoursOff, Rate = db.Rates.FirstOrDefault(r => r.ExpenditureCategoryId == p.ExpenditureCategoryId && r.StartDate <= vacWeek.WeekEndingDate && r.EndDate >= vacWeek.WeekEndingDate) }).Distinct().ToArray(); var allTrainings = (from traningWeek in db.PeopleResourceTrainings join training in db.Trainings on traningWeek.TrainingId equals training.Id join p in db.PeopleResources on traningWeek.PeopleResourceId equals p.Id join t in db.Team2Project on p.TeamId equals t.TeamId where (teams.Count == 0 || (p.TeamId.HasValue && teams.Contains(p.TeamId.Value))) && projectIds.Contains(t.ProjectId) && expCatGuids.Contains(p.ExpenditureCategoryId) select new { traningWeek.Id, traningWeek.WeekEndingDate, traningWeek.HoursOff, Rate = db.Rates.FirstOrDefault(r => r.ExpenditureCategoryId == p.ExpenditureCategoryId && r.StartDate <= traningWeek.WeekEndingDate && r.EndDate >= traningWeek.WeekEndingDate) }).Distinct().ToArray(); //init result dictionary if (mode == ForecastDashboardMode.MainDashboard) { result.Add("plannedCapacity", new List()); result.Add("plannedCapacityQ", new List()); } result.Add("training", new List()); result.Add("trainingQ", new List()); result.Add("vacation", new List()); result.Add("vacationQ", new List()); result.Add("totalPlannedCapacity", new List()); result.Add("totalPlannedCapacityQ", new List()); result.Add("resourceCapacity", new List()); result.Add("resourceCapacityQ", new List()); //capacity and project status data remains uninitialized as we do not know what capacity scenarios and project statuses we have List dataList = data.ToList(); long wkending = dataList.Count > 0 ? (long)dataList[0].weekend.Value.Subtract(Constants.UnixEpochDate).TotalMilliseconds : 0; Dictionary tmpProjectStatus = new Dictionary(); Dictionary tmpCapacity = new Dictionary(); Dictionary totalActualCapacity = new Dictionary(); //if (mode == ForecastDashboardMode.MainDashboard) // totalActualCapacity = GetWeeklyTeamCapacity(teams, expCats, uoms, isUOMHours, false, fiscalCalendarWeekendings); //else if (mode == ForecastDashboardMode.TeamForecast) // totalActualCapacity = GetWeeklyTeamCapacity(teams, expCats, uoms, isUOMHours, false, fiscalCalendarWeekendings); //else if (mode == ForecastDashboardMode.ViewForecast) // totalActualCapacity = GetWeeklyViewCapacity(views, expCats, uoms, isUOMHours, false, fiscalCalendarWeekendings); Dictionary totalPlannedCapacity = new Dictionary(); //if (mode == ForecastDashboardMode.MainDashboard) // totalPlannedCapacity = GetWeeklyTeamCapacity(teams, expCats, uoms, isUOMHours, true, fiscalCalendarWeekendings); //else if (mode == ForecastDashboardMode.TeamForecast) // totalPlannedCapacity = GetWeeklyTeamCapacity(teams, expCats, uoms, isUOMHours, true, fiscalCalendarWeekendings); //else if (mode == ForecastDashboardMode.ViewForecast) // totalPlannedCapacity = GetWeeklyViewCapacity(views, expCats, uoms, isUOMHours, true, fiscalCalendarWeekendings); Guid[] plannedScenarios = new Guid[0]; Guid[] actualScenarios = new Guid[0]; if (mode == ForecastDashboardMode.MainDashboard || mode == ForecastDashboardMode.TeamForecast) { plannedScenarios = (from s in db.Teams where teams.Contains(s.Id) && s.PlannedCapacityScenarioId != null select s.PlannedCapacityScenarioId.Value).ToArray(); actualScenarios = (from s in db.Teams where teams.Contains(s.Id) && s.ActualCapacityScenarioId != null select s.ActualCapacityScenarioId.Value).ToArray(); } else if (mode == ForecastDashboardMode.ViewForecast) { plannedScenarios = (from s in db.Teams join tv in db.Team2View on s.Id equals tv.TeamId where views.Contains(tv.ViewId) && s.PlannedCapacityScenarioId != null select s.PlannedCapacityScenarioId.Value).ToArray(); actualScenarios = (from s in db.Teams join tv in db.Team2View on s.Id equals tv.TeamId where views.Contains(tv.ViewId) && s.ActualCapacityScenarioId != null select s.ActualCapacityScenarioId.Value).ToArray(); } Guid[] expCatIds = expCats.Keys.ToArray(); //var sdsP = (from sd in db.ScenarioDetail where plannedScenarios.Contains(sd.ParentID.Value) && fiscalCalendarWeekendings.Contains(sd.WeekEndingDate.Value) && expCatIds.Contains(sd.ExpenditureCategoryId.Value) select sd).ToDictionary( x=> x.WeekEndingDate, x => Sum(x.Cost)); //var sdsA = (from sd in db.ScenarioDetail where actualScenarios.Contains(sd.ParentID.Value) && fiscalCalendarWeekendings.Contains(sd.WeekEndingDate.Value) && expCatIds.Contains(sd.ExpenditureCategoryId.Value) select sd).ToDictionary(x => x.WeekEndingDate, x => x); Dictionary>> plannedCapacityDetails = (from sdPlan in db.ScenarioDetail where plannedScenarios.Contains(sdPlan.ParentID.Value) && fiscalCalendarWeekendings.Contains(sdPlan.WeekEndingDate.Value) && expCatIds.Contains(sdPlan.ExpenditureCategoryId.Value) select sdPlan).GroupBy(sd => sd.WeekEndingDate).ToDictionary(k1 => k1.Key, g1 => g1.GroupBy(sd2 => sd2.ExpenditureCategoryId).ToDictionary(k2 => k2.Key, g2 => g2.ToList())); Dictionary>> actualCapacityDetails = (from sdAct in db.ScenarioDetail where actualScenarios.Contains(sdAct.ParentID.Value) && fiscalCalendarWeekendings.Contains(sdAct.WeekEndingDate.Value) && expCatIds.Contains(sdAct.ExpenditureCategoryId.Value) select sdAct).GroupBy(sd => sd.WeekEndingDate).ToDictionary(k1 => k1.Key, g1 => g1.GroupBy(sd2 => sd2.ExpenditureCategoryId).ToDictionary(k2 => k2.Key, g2 => g2.ToList())); for (int i = 0; i < dataList.Count; i++) { var week = dataList[i].weekend.Value; if (!totalPlannedCapacity.ContainsKey(week)) { var quantity = 0M; var cost = 0M; if (plannedCapacityDetails.ContainsKey(week)) { var sdP = plannedCapacityDetails[week];// (from s in sdsP where s.WeekEndingDate == week select s).ToList(); foreach (var s in sdP) { quantity += s.Value.Sum(x => x.Quantity.Value) * Utils.GetUOMMultiplier(expCats, uoms, s.Key ?? Guid.Empty, isUOMHours); cost += s.Value.Sum(x => x.Cost.Value); } } totalPlannedCapacity.Add(week, new PeopleResourceModel.CapacityValues() { Quantity = quantity, Cost = cost, WeekEnding = week }); } if (!totalActualCapacity.ContainsKey(week)) { var quantity = 0M; var cost = 0M; if (actualCapacityDetails.ContainsKey(week)) { var sdA = actualCapacityDetails[week];//(from s in sdsA where s.WeekEndingDate == week select s).ToList(); foreach (var s in sdA) { quantity += s.Value.Sum(x => x.Quantity.Value) * Utils.GetUOMMultiplier(expCats, uoms, s.Key ?? Guid.Empty, isUOMHours); cost += s.Value.Sum(x => x.Cost.Value); } } totalActualCapacity.Add(week, new PeopleResourceModel.CapacityValues() { Quantity = quantity, Cost = cost, WeekEnding = week }); } Data sd = dataList[i]; //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, 0.0M); tmpProjectStatus.Add(sd.projectStatus + "Q", 0.0M); } tmpProjectStatus[sd.projectStatus] += sd.cost ?? 0; tmpProjectStatus[sd.projectStatus + "Q"] += (sd.quantity ?? 0) * Utils.GetUOMMultiplier(expCats, uoms, sd.ExpCatId ?? Guid.Empty, isUOMHours); } else if ((int)ScenarioType.Capacity == sd.type.Value) { if (!tmpCapacity.ContainsKey(sd.Name)) { tmpCapacity.Add(sd.Name, 0.0M); tmpCapacity.Add(sd.Name + "Q", 0.0M); } tmpCapacity[sd.Name] += sd.cost ?? 0; tmpCapacity[sd.Name + "Q"] += (sd.quantity ?? 0) * Utils.GetUOMMultiplier(expCats, uoms, sd.ExpCatId ?? Guid.Empty, isUOMHours); } else { throw new InvalidOperationException("Incorrect scenario type found"); } if (i == dataList.Count - 1 || (i < dataList.Count - 1 && (long)dataList[i+1].weekend.Value.Subtract(Constants.UnixEpochDate).TotalMilliseconds != wkending)) { //new week, push result data and reinit temps if (0 != wkending) { if (mode == ForecastDashboardMode.MainDashboard) { foreach (string cn in tmpCapacity.Keys) { if (!result.ContainsKey(cn)) { result.Add(cn, new List()); } result[cn].Add(new long[] { wkending, (long)(Math.Round(tmpCapacity[cn])) }); } } //TRAININGS var ed = Constants.UnixEpochDate.AddMilliseconds(wkending); var startd = ed.AddDays(-6); decimal tmpTrainingQ = allTrainings.Where(t => t.WeekEndingDate <= ed && t.WeekEndingDate >= startd).Sum(t => t.HoursOff); result["trainingQ"].Add(new long[] { wkending, (long)(tmpTrainingQ * Utils.GetUOMMultiplier(expCats, uoms, sd.ExpCatId ?? Guid.Empty, isUOMHours)) }); decimal tmpTraining = allTrainings.Where(t => t.WeekEndingDate <= ed && t.WeekEndingDate >= startd).Sum(t => t.HoursOff * (t.Rate != null ? t.Rate.Rate1 : 0)); result["training"].Add(new long[] { wkending, (long)tmpTraining }); //VACATIONS ed = Constants.UnixEpochDate.AddMilliseconds(wkending); startd = ed.AddDays(-6); decimal tmpVacationQ = allVacations.Where(t => t.WeekEndingDate <= ed && t.WeekEndingDate >= startd).Sum(t => t.HoursOff); result["vacationQ"].Add(new long[] { wkending, (long)(tmpVacationQ * Utils.GetUOMMultiplier(expCats, uoms, sd.ExpCatId ?? Guid.Empty, isUOMHours)) }); decimal tmpVacation = allVacations.Where(t => t.WeekEndingDate <= ed && t.WeekEndingDate >= startd).Sum(t => t.HoursOff * (t.Rate != null ? t.Rate.Rate1 : 0)); result["vacation"].Add(new long[] { wkending, (long)tmpVacation }); foreach (string sn in tmpProjectStatus.Keys) { if (!result.ContainsKey(sn)) { result.Add(sn, new List()); } result[sn].Add(new long[] { wkending, (long)(Math.Round(tmpProjectStatus[sn])) }); } } if (i < dataList.Count - 1) wkending = (long)dataList[i + 1].weekend.Value.Subtract(Constants.UnixEpochDate).TotalMilliseconds; tmpProjectStatus = new Dictionary(); tmpCapacity = new Dictionary(); } } result["resourceCapacity"].AddRange(totalActualCapacity.Select(x => new long[] { (long)x.Key.Subtract(Constants.UnixEpochDate).TotalMilliseconds, (long)(Math.Round(x.Value.Cost)) })); result["resourceCapacityQ"].AddRange(totalActualCapacity.Select(x => new long[] { (long)x.Key.Subtract(Constants.UnixEpochDate).TotalMilliseconds, (long)(Math.Round(x.Value.Quantity)) })); result["totalPlannedCapacity"].AddRange(totalPlannedCapacity.Select(x => new long[] { (long)x.Key.Subtract(Constants.UnixEpochDate).TotalMilliseconds, (long)(Math.Round(x.Value.Cost)) })); result["totalPlannedCapacityQ"].AddRange(totalPlannedCapacity.Select(x => new long[] { (long)x.Key.Subtract(Constants.UnixEpochDate).TotalMilliseconds, (long)(Math.Round(x.Value.Quantity)) })); //Fill missing weekends for all items in dictionary foreach (string key in result.Keys) { List lst = FillMissingWeeends(result[key], fiscalCalendarChartPoints); result[key].Clear(); result[key].AddRange(lst); } return result; } /// /// Get projects, filtered by user access permissions and given filter view or team (depends on page mode) /// /// Forecast page display mode /// User id /// Id of view or team to filter projects (depends on page mode) /// Returning list of teams, discovered during filtration (e.g. for the filtering by given view Id) /// Available for given user and team (view teams) projects /// SA. ENV-608 private IQueryable GetFilteredProjectList(Guid userId, List teams, List views) { var filteredTeams = (new TeamManager(db)).GetTeamsByUser(userId.ToString(), teams, views); teams.AddRange(filteredTeams.Where(x => !teams.Any(t => t == x.Id)).Select(x => x.Id)); // Get list of project, that are available to given user, filtered for given team or view IQueryable projects = (from c in db.Projects.AsNoTracking() join o in db.VW_ProjectAccessByUser on c.Id equals o.Id where o.UserId.Equals(userId) && c.Team2Project.Any(x => teams.Contains(x.TeamId)) select c); return projects; } /// /// Returns current, next and previous fiscal periods of given type for the datepoint /// /// /// /// Returns periods, if exist (-1 = prev, 0 = current, 1 = next) /// SA. ENV-608 private Dictionary GetClosedToDateFiscalPeriods(DateTime datePoint, FiscalCalendarModel.FiscalYearType periodType) { Dictionary result = new Dictionary(); DateTime viewRangeStartDate = datePoint.AddYears(-2); DateTime viewRangeEndDate = datePoint.AddYears(2); List periods = (from FiscalCalendar period in db.FiscalCalendars.AsNoTracking() orderby period.StartDate where (period.Type == (int)periodType) && (period.StartDate >= viewRangeStartDate) && (period.EndDate <= viewRangeEndDate) select period).ToList(); int periodCount = periods.Count; FiscalCalendar periodItem; for (int index = 0; index < periodCount; index++) { 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; } /// /// Returns top 5 projects performance data by cost /// /// Page display mode (general dashboard, team or view dashboard) /// /// /// Team or View id to filter projects /// /// SA. ENV-608 public Dictionary GetPerformanceGraphDataByProjects(ForecastDashboardMode mode, Guid userId, DateTime datePoint, List filterGroups, List projectTypes, List teams, List views) { var result = new Dictionary(); StackGraphDataBlock singleGraphDataBlock; StackGraphSerie forecastDataSerie; StackGraphSerie variationsPosDataSerie; StackGraphSerie variationsNegDataSerie; StackGraphSerie actualsDataSerie; StackGraphBarOptions barTitles; // Get list of project, that are available to given user, filtered for given team or view IQueryable projects = GetFilteredProjectList(userId, teams, views); List 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(); resultKeys.Add(-1, "PrevPeriod"); resultKeys.Add(0, "CurrentPeriod"); resultKeys.Add(1, "NextPeriod"); for (int index = -1; index <= 1; index++) { if (fiscalPeriods.ContainsKey(index)) { Guid currentPeriodId = fiscalPeriods[index]; var query = db.VW_ProjectPerformance.AsNoTracking().OrderByDescending(row => row.ProjectWeight).Where(row => currentPeriodId.Equals(row.FiscalPeriodId) && projectFilter.Contains(row.ProjectId) && row.BUDirectCosts.HasValue); if (filterGroups != null && filterGroups.Count > 0) query = query.Where(row => row.ScenarioGroupId.HasValue && filterGroups.Contains(row.ScenarioGroupId.Value)); if (projectTypes != null && projectTypes.Count > 0) query = query.Where(row => projectTypes.Contains(row.ProjectTypeId)); var projectRows = (from VW_ProjectPerformance row in query select row).Take(5); int rowIndex = 1; singleGraphDataBlock = new StackGraphDataBlock(); forecastDataSerie = new StackGraphSerie(); variationsPosDataSerie = new StackGraphSerie(); variationsNegDataSerie = new StackGraphSerie(); barTitles = new StackGraphBarOptions(); actualsDataSerie = new StackGraphSerie(); foreach (VW_ProjectPerformance row in projectRows) { long graphPoint = rowIndex; if (row.ActualsCostVariation.HasValue) { if (row.ActualsCostVariation.Value > 0) { // Actual costs lager Forecast costs forecastDataSerie.data.AddValue(graphPoint, row.BUDirectCosts.Value); variationsPosDataSerie.data.AddValue(graphPoint, row.ActualsCostVariation.Value); variationsNegDataSerie.data.AddValue(graphPoint, 0); } else { // Actual costs less Forecast costs forecastDataSerie.data.AddValue(graphPoint, row.BUDirectCosts.Value + row.ActualsCostVariation.Value); // ActualsCostVariation is negative value variationsPosDataSerie.data.AddValue(graphPoint, 0); variationsNegDataSerie.data.AddValue(graphPoint, -row.ActualsCostVariation.Value); } } else { // No actauls forecastDataSerie.data.AddValue(graphPoint, row.BUDirectCosts.Value); variationsPosDataSerie.data.AddValue(graphPoint, 0); variationsNegDataSerie.data.AddValue(graphPoint, 0); } // Store actuals data to virtual serie if (row.ActualsTotalCost.HasValue) actualsDataSerie.data.AddValue(graphPoint, row.ActualsTotalCost.Value); else actualsDataSerie.data.AddValue(graphPoint, 0); // Store project name barTitles.AddValue(graphPoint, row.ProjectName); rowIndex++; } singleGraphDataBlock.series.Add(forecastDataSerie); singleGraphDataBlock.series.Add(variationsPosDataSerie); singleGraphDataBlock.series.Add(variationsNegDataSerie); singleGraphDataBlock.series.Add(actualsDataSerie); singleGraphDataBlock.barOptions = barTitles; string dataKey = resultKeys[index]; result.Add(dataKey, singleGraphDataBlock); } } 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 /// /// SA. ENV-608 public Dictionary GetPerformanceGraphDataByProjectClass(ForecastDashboardMode mode, Guid userId, DateTime datePoint, List filterGroups, List projectTypes, List teams, List views) { var result = new Dictionary(); StackGraphDataBlock singleGraphDataBlock; StackGraphSerie forecastDataSerie; StackGraphSerie variationsPosDataSerie; StackGraphSerie variationsNegDataSerie; StackGraphSerie actualsDataSerie; StackGraphBarOptions barTitles; // Get list of project, that are available to given user, filtered for given team or view IQueryable projects = GetFilteredProjectList(userId, teams, views); List 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(); resultKeys.Add(-1, "PrevPeriod"); resultKeys.Add(0, "CurrentPeriod"); resultKeys.Add(1, "NextPeriod"); for (int index = -1; index <= 1; index++) { if (fiscalPeriods.ContainsKey(index)) { Guid currentPeriodId = fiscalPeriods[index]; var query = db.VW_ProjectPerformance.AsNoTracking().Where(x => currentPeriodId.Equals(x.FiscalPeriodId) && projectFilter.Contains(x.ProjectId) && x.BUDirectCosts.HasValue); if (filterGroups != null && filterGroups.Count > 0) query = query.Where(x => x.ScenarioGroupId.HasValue && filterGroups.Contains(x.ScenarioGroupId.Value)); if (projectTypes != null && projectTypes.Count > 0) query = query.Where(row => projectTypes.Contains(row.ProjectTypeId)); var projectClassRows = (from VW_ProjectPerformance row in query group row by new { row.ProjectTypeName } into grp select new { grp.Key.ProjectTypeName, BUDirectCosts = grp.Sum(x => x.BUDirectCosts), ActualsCostVariation = grp.Sum(x => x.ActualsCostVariation.HasValue ? x.ActualsCostVariation : 0), ActualsTotalCosts = grp.Sum(x => x.ActualsTotalCost.HasValue ? x.ActualsTotalCost : 0), ProjectsCount = grp.Count() }).OrderByDescending(x => x.BUDirectCosts).Take(5).ToList(); int rowIndex = 1; singleGraphDataBlock = new StackGraphDataBlock(); forecastDataSerie = new StackGraphSerie(); variationsPosDataSerie = new StackGraphSerie(); variationsNegDataSerie = new StackGraphSerie(); barTitles = new StackGraphBarOptions(); actualsDataSerie = new StackGraphSerie(); foreach (var row in projectClassRows) { long graphPoint = rowIndex; if (row.ActualsCostVariation.HasValue) { if (row.ActualsCostVariation.Value > 0) { // Actual costs lager Forecast costs forecastDataSerie.data.AddValue(graphPoint, row.BUDirectCosts.Value); variationsPosDataSerie.data.AddValue(graphPoint, row.ActualsCostVariation.Value); variationsNegDataSerie.data.AddValue(graphPoint, 0); } else { // Actual costs less Forecast costs forecastDataSerie.data.AddValue(graphPoint, row.BUDirectCosts.Value + row.ActualsCostVariation.Value); // ActualsCostVariation is negative value variationsPosDataSerie.data.AddValue(graphPoint, 0); variationsNegDataSerie.data.AddValue(graphPoint, -row.ActualsCostVariation.Value); } } else { // No actauls forecastDataSerie.data.AddValue(graphPoint, row.BUDirectCosts.Value); variationsPosDataSerie.data.AddValue(graphPoint, 0); variationsNegDataSerie.data.AddValue(graphPoint, 0); } // Store actuals data to virtual serie if (row.ActualsTotalCosts.HasValue) actualsDataSerie.data.AddValue(graphPoint, row.ActualsTotalCosts.Value); else actualsDataSerie.data.AddValue(graphPoint, 0); // Store project name barTitles.AddValue(graphPoint, row.ProjectTypeName); rowIndex++; } singleGraphDataBlock.series.Add(forecastDataSerie); singleGraphDataBlock.series.Add(variationsPosDataSerie); singleGraphDataBlock.series.Add(variationsNegDataSerie); singleGraphDataBlock.series.Add(actualsDataSerie); singleGraphDataBlock.barOptions = barTitles; string dataKey = resultKeys[index]; result.Add(dataKey, singleGraphDataBlock); } } return result; } /// /// 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 long[] { x, 0 })); list.RemoveAll(x => !weekEnds.Contains(x[0])); } return list.OrderBy(x => x[0]).ToList(); } /// /// /// /// /// /// /// /// /// /// /// private IQueryable GetWeeksData(DateTime firstWeekEnding, DateTime lastWeekEnding, IQueryable projects, List projectStatuses, List projectTypes, int scenarioType, bool noOldCapacities, bool isLaborMode, List filterGroups) { 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)); Guid[] projectIds = (from p in projects select p.Id).ToArray(); Guid[] expCatIds = null; if (isLaborMode) expCatIds = (from e in db.ExpenditureCategory where e.Type == (int)ExpenditureCategoryModel.CategoryTypes.Labor select e.Id).ToArray(); else expCatIds = (from e in db.ExpenditureCategory where e.Type != (int)ExpenditureCategoryModel.CategoryTypes.Labor select e.Id).ToArray(); List scTypes = new List(); if (!noOldCapacities) scTypes.AddRange(new int[] { (int)ScenarioType.Capacity, (int)ScenarioType.Vacation, (int)ScenarioType.Training }); if (scenarioType != 0) scTypes.Add(scenarioType); else scTypes.Add((int)ScenarioType.Portfolio); IQueryable data = null; if (filterGroups != null && filterGroups.Count > 0) { data = (from sd in db.ScenarioDetail.AsNoTracking() join s in db.Scenarios on sd.ParentID equals s.Id join p in db.Projects on s.ParentId equals p.Id where sd.WeekEndingDate >= firstWeekEnding && sd.WeekEndingDate <= lastWeekEnding && projectIds.Contains(s.ParentId.Value) && s.Status == (int?)ScenarioStatus.Active && scTypes.Contains(s.Type) && expCatIds.Contains(sd.ExpenditureCategoryId.Value) && s.Scenario2Group.Any(g => filterGroups.Contains(g.GroupId)) orderby sd.WeekEndingDate select new Data { cost = sd.Cost, quantity = sd.Quantity, weekend = sd.WeekEndingDate, projectStatus = projects.FirstOrDefault(x => x.Id == s.ParentId).Status.Name, type = s.Type, Name = s.Name, ScenarioBUCost = (s.UseLMMargin ?? 0) == 1 ? s.BUDirectCosts_LM : s.BUDirectCosts, ScenarioDuration = s.Duration ?? 0, ExpCatId = sd.ExpenditureCategoryId, ProjectId = s.ParentId.Value, ProjectType = p.TypeId }); } else { data = (from sd in db.ScenarioDetail.AsNoTracking() join s in db.Scenarios on sd.ParentID equals s.Id join p in db.Projects on s.ParentId equals p.Id where sd.WeekEndingDate >= firstWeekEnding && sd.WeekEndingDate <= lastWeekEnding && projectIds.Contains(s.ParentId.Value) && s.Status == (int?)ScenarioStatus.Active && scTypes.Contains(s.Type) && expCatIds.Contains(sd.ExpenditureCategoryId.Value) orderby sd.WeekEndingDate select new Data { cost = sd.Cost, quantity = sd.Quantity, weekend = sd.WeekEndingDate, projectStatus = projects.FirstOrDefault(x => x.Id == s.ParentId).Status.Name, type = s.Type, Name = s.Name, ScenarioBUCost = (s.UseLMMargin ?? 0) == 1 ? s.BUDirectCosts_LM : s.BUDirectCosts, ScenarioDuration = s.Duration ?? 0, ExpCatId = sd.ExpenditureCategoryId, ProjectType = p.TypeId }); } return data; } /// /// /// /// /// /// /// /// /// /// /// A value of resource capacity multiplier. E.g. if user wants to see data in hours, then /// equals to 1. If user wants to see data in resources then equals to 1/40 = 0.025. /// private IQueryable GetMonthData(IQueryable projects, List projectStatuses, List projectTypes, int scenarioType, bool noOldCapacities, bool isLaborMode, List filterGroups, List fiscalCalendarWeekendings) { 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)); Guid[] projectIds = (from p in projects select p.Id).ToArray(); Guid[] expCatIds = null; if (isLaborMode) expCatIds = (from e in db.ExpenditureCategory where e.Type == (int)ExpenditureCategoryModel.CategoryTypes.Labor select e.Id).ToArray(); else expCatIds = (from e in db.ExpenditureCategory where e.Type != (int)ExpenditureCategoryModel.CategoryTypes.Labor select e.Id).ToArray(); List scTypes = new List(); if (!noOldCapacities) scTypes.AddRange(new int[] { (int)ScenarioType.Capacity, (int)ScenarioType.Vacation, (int)ScenarioType.Training }); if (scenarioType != 0) scTypes.Add(scenarioType); else scTypes.Add((int)ScenarioType.Portfolio); IQueryable data = null; if (filterGroups != null && filterGroups.Count > 0) { data = (from sd in db.ScenarioDetail.AsNoTracking() join s in db.Scenarios on sd.ParentID equals s.Id join p in db.Projects on s.ParentId equals p.Id where fiscalCalendarWeekendings.Contains(sd.WeekEndingDate.Value) && projectIds.Contains(s.ParentId.Value) && s.Status == (int?)ScenarioStatus.Active && scTypes.Contains(s.Type) && expCatIds.Contains(sd.ExpenditureCategoryId.Value) && s.Scenario2Group.Any(g => filterGroups.Contains(g.GroupId)) orderby sd.WeekEndingDate select new Data { cost = sd.Cost, quantity = sd.Quantity, weekend = sd.WeekEndingDate, projectStatus = projects.FirstOrDefault(x => x.Id == s.ParentId).Status.Name, type = s.Type, Name = s.Name, ScenarioBUCost = (s.UseLMMargin ?? 0) == 1 ? s.BUDirectCosts_LM : s.BUDirectCosts, ScenarioDuration = s.Duration ?? 0, ExpCatId = sd.ExpenditureCategoryId, ProjectId = s.ParentId.Value, ProjectType = p.TypeId }); } else { data = (from sd in db.ScenarioDetail.AsNoTracking() join s in db.Scenarios on sd.ParentID equals s.Id join p in db.Projects on s.ParentId equals p.Id where fiscalCalendarWeekendings.Contains(sd.WeekEndingDate.Value) && projectIds.Contains(s.ParentId.Value) && s.Status == (int?)ScenarioStatus.Active && scTypes.Contains(s.Type) && expCatIds.Contains(sd.ExpenditureCategoryId.Value) orderby sd.WeekEndingDate select new Data { cost = sd.Cost, quantity = sd.Quantity, weekend = sd.WeekEndingDate, projectStatus = projects.FirstOrDefault(x => x.Id == s.ParentId).Status.Name, type = s.Type, Name = s.Name, ScenarioBUCost = (s.UseLMMargin ?? 0) == 1 ? s.BUDirectCosts_LM : s.BUDirectCosts, ScenarioDuration = s.Duration ?? 0, ExpCatId = sd.ExpenditureCategoryId, ProjectId = s.ParentId.Value, ProjectType = p.TypeId }); } return data; } public List GetPieData(DateTime startDate, DateTime endDate, Guid userId, ForecastDashboardMode mode, List teams, List views, bool isLaborMode, List filterGroups, List projectTypes, List projectStatuses, List strategicGoals, string presetColor, bool chartModeCost) { var query = db.Projects.AsNoTracking().Where(x => !x.HasChildren); if (projectStatuses != null && projectStatuses.Count > 0) query = query.Where(x => projectStatuses.Contains(x.StatusId)); if (projectTypes != null && projectTypes.Count > 0) query = query.Where(x => projectTypes.Contains(x.TypeId)); if (strategicGoals != null && strategicGoals.Count > 0) { var sgs = db.StrategicGoal2Project.Where(x => strategicGoals.Contains(x.StrategicGoalId)).Select(x => x.ProjectId).ToList(); query = query.Where(x => sgs.Contains(x.Id)); } var filteredTeams = (new TeamManager(db)).GetTeamsByUser(userId.ToString(), teams, views); teams.AddRange(filteredTeams.Where(x => !teams.Any(t => t == x.Id)).Select(x=> x.Id)); IQueryable projects = (from c in query join o in db.VW_ProjectAccessByUser on c.Id equals o.Id where o.UserId.Equals(userId) && c.Team2Project.Any(x => teams.Contains(x.TeamId)) select c); var data = GetPieChartData(startDate, endDate.AddDays(7), projects, isLaborMode, filterGroups, chartModeCost).OrderByDescending(x => x.Value); var retData = new List(); var other = new List(); var totalTime = data.Sum(x=>x.Value); var total = 1.0M; if (data.Count() == 1) data.First().PresetColor = presetColor; if (projectTypes.Count == 0 && totalTime > 0) { foreach (var item in data) { var percentage = item.Value / totalTime; if (total > .15M) { retData.Add(item); total -= percentage; } else{ other.Add(item); } } //var perPrecent = data.Max(x => x.Time) / 100; //retData = data.Where(x => x.Time >= perPrecent * 80).Take(2).ToList(); //var existedTypeIds = retData.SelectMany(ex => ex.TypeId).ToList(); //var other = data.Where(x => !existedTypeIds.Contains(x.TypeId.FirstOrDefault())); if (other.Count() > 2) { retData.Add(new PieData() { Label = "Other", TypeId = other.SelectMany(x => x.TypeId).ToList(), Value = other.Sum(x => x.Value) }); }else{ retData = data.ToList(); } } else { retData = data.ToList(); } return retData; } /// /// /// /// 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 Dictionary GetWeeklyViewCapacity(List views, Dictionary expCats, Dictionary uoms, bool? isUOMMode, bool isPlanned, List fiscalCalendarWeekendings) { if (views == null || views.Count <= 0) return new Dictionary(); var context = new EnVisageEntities(); Guid[] scenarios; if (isPlanned) scenarios = (from s in context.Teams join tv in context.Team2View on s.Id equals tv.TeamId where views.Contains(tv.ViewId) && s.PlannedCapacityScenarioId != null select s.PlannedCapacityScenarioId.Value).ToArray(); else scenarios = (from s in context.Teams join tv in context.Team2View on s.Id equals tv.TeamId where views.Contains(tv.ViewId) && s.ActualCapacityScenarioId != null select s.ActualCapacityScenarioId.Value).ToArray(); return GetWeeklyCapacity(scenarios, expCats, uoms, isUOMMode, fiscalCalendarWeekendings); } private Dictionary GetWeeklyTeamCapacity(List TeamIds, Dictionary expCats, Dictionary uoms, bool? isUOMMode, bool isPlanned, List fiscalCalendarWeekendings) { if (TeamIds == null || TeamIds.Count <= 0) return new Dictionary(); var context = new EnVisageEntities(); Guid[] scenarios; if (isPlanned) scenarios = (from s in context.Teams where TeamIds.Contains(s.Id) && s.PlannedCapacityScenarioId != null select s.PlannedCapacityScenarioId.Value).ToArray(); else scenarios = (from s in context.Teams where TeamIds.Contains(s.Id) && s.ActualCapacityScenarioId != null select s.ActualCapacityScenarioId.Value).ToArray(); return GetWeeklyCapacity(scenarios, expCats, uoms, isUOMMode, fiscalCalendarWeekendings); } private Dictionary GetWeeklyCapacity(Guid[] SccenarioIds, Dictionary expCats, Dictionary uoms, bool? isUOMMode, List fiscalCalendarWeekendings) { var dict = new Dictionary(); var context = new EnVisageEntities(); Guid[] expCatIds = expCats.Keys.ToArray(); var sds = (from sd in context.ScenarioDetail where SccenarioIds.Contains(sd.ParentID.Value) && fiscalCalendarWeekendings.Contains(sd.WeekEndingDate.Value) && expCatIds.Contains(sd.ExpenditureCategoryId.Value) select sd).ToList(); // fill dictionary with weekly capacity for each week foreach (var week in fiscalCalendarWeekendings) { if (!dict.ContainsKey(week)) { var quantity = 0M; var cost = 0M; var sd = (from s in sds where s.WeekEndingDate == week select s).ToList(); foreach (var s in sd) { quantity += s.Quantity.Value * Utils.GetUOMMultiplier(expCats, uoms, s.ExpenditureCategoryId ?? Guid.Empty, isUOMMode); cost += s.Cost.Value; } dict.Add(week, new PeopleResourceModel.CapacityValues() { Quantity = quantity, Cost = cost, WeekEnding = week }); } } return dict; } } #region SA. ENV-608. Dashboard performance graph public class StackGraphSerieData : List> { public void AddValue(long tickValue, decimal dataValue) { List point = new List(); point.Add(tickValue); point.Add(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(); option.Add(tickValue); option.Add(barTitle); this.Add(option); } } public class StackGraphDataBlock { public List series; public StackGraphBarOptions barOptions; public StackGraphDataBlock() { series = new List(); } } #endregion }