1757 lines
77 KiB
C#
1757 lines
77 KiB
C#
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
|
|
|
|
/// <summary>
|
|
/// Identifier ProjectTypeId for 'Other' data element
|
|
/// </summary>
|
|
protected readonly Guid C_PROJECTTYPES_OTHER_TYPE_ID = new Guid("19BCFF07-2A1C-464C-AAEB-347576E381B2");
|
|
/// <summary>
|
|
/// Identifier GoalId for 'Other' data element
|
|
/// </summary>
|
|
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<Guid> TypeId { get; set; }
|
|
public List<PieData> 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> Data { get; set; }
|
|
public IEnumerable<DateTime> FiscalCalendarWeekendings { get; set; }
|
|
public long[] FiscalCalendarChartPoints { get; set; }
|
|
public List<Guid> 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<ForecastDashboardChartCapacityModel> { row }, weekEndingMs);
|
|
}
|
|
|
|
private long GetOptimuseWeeklyHoursData(List<ForecastDashboardChartCapacityModel> 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<ForecastDashboardChartCapacityModel> { row }, weekEndingMs);
|
|
}
|
|
|
|
private long GetOptimuseWeeklyResourcesData(List<ForecastDashboardChartCapacityModel> rows, long weekEndingMs)
|
|
{
|
|
if (rows == null || rows.Count <= 0)
|
|
return 0;
|
|
|
|
return rows.Sum(x => GetOptimuseWeeklyData(x.Resources, weekEndingMs));
|
|
}
|
|
|
|
private long GetOptimuseWeeklyData(List<long[]> 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<DateTime, ForecastDashboardWeeklyCapacityModel> GetWeeklyTeamCapacity(List<Guid> teamIds, Dictionary<Guid, ExpenditureCategory> expCats, Dictionary<Guid, UOM> uoms, bool isPlanned, List<DateTime> fiscalCalendarWeekendings)
|
|
{
|
|
if (teamIds == null || teamIds.Count <= 0)
|
|
return new Dictionary<DateTime, ForecastDashboardWeeklyCapacityModel>();
|
|
|
|
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<DateTime, ForecastDashboardWeeklyCapacityModel> GetWeeklyCapacity(Guid[] sccenarioIds, Dictionary<Guid, ExpenditureCategory> expCats, Dictionary<Guid, UOM> uoms, List<DateTime> fiscalCalendarWeekendings)
|
|
{
|
|
var weeklyCapacity = new Dictionary<DateTime, ForecastDashboardWeeklyCapacityModel>();
|
|
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<DateTime, ForecastDashboardWeeklyCapacityModel> GetWeeklyPlannedCapacityAdjusted(List<Guid> scenarios, Dictionary<Guid, ExpenditureCategory> expCats,
|
|
Dictionary<Guid, UOM> uoms, List<DateTime> fiscalCalendarWeekendings)
|
|
{
|
|
Dictionary<DateTime, ForecastDashboardWeeklyCapacityModel> weeklyCapacity =
|
|
new Dictionary<DateTime, ForecastDashboardWeeklyCapacityModel>();
|
|
|
|
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<ScenarioDetailWithProxyItemModel>());
|
|
|
|
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<DateTime, ForecastDashboardWeeklyCapacityModel> GetWeeklyActualCapacityAdjusted(List<Guid> scenarios, Dictionary<Guid, ExpenditureCategory> expCats,
|
|
Dictionary<Guid, UOM> uoms, List<DateTime> fiscalCalendarWeekendings)
|
|
{
|
|
// Get direct actual capacity (with no adjustments)
|
|
Dictionary<DateTime, ForecastDashboardWeeklyCapacityModel> 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<ProjectWithChildrenView> projects, bool isLaborMode, DateTime? startDate, DateTime? endDate,
|
|
IEnumerable<Guid> projectTypesFilter, IEnumerable<Guid> strategicGoalsFilter)
|
|
{
|
|
var projectIds = projects.Select(q => q.Id).ToArray();
|
|
var expCatIds = GetExpenditureCategories(isLaborMode);
|
|
var scTypes = new List<int> { (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<Guid>();
|
|
strategicGoalsFilter = strategicGoalsFilter ?? new List<Guid>();
|
|
|
|
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<Guid>() { 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<Guid> { 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<Guid>() { 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<Guid> { 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<object> BuildChartHeader4CapacityRow(ForecastDashboardChartCapacityModel row)
|
|
{
|
|
if (row == null)
|
|
return null;
|
|
|
|
var capacityRow = row.CapacityType == ForecastDashboardChartCapacityModel.ChartCapacityType.ActualCapacity ||
|
|
row.CapacityType == ForecastDashboardChartCapacityModel.ChartCapacityType.PlannedCapacity;
|
|
|
|
var headers = new List<object>
|
|
{
|
|
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<DonutChartDataItem> LimitChartDataAndGetOther(IEnumerable<DonutChartDataItem> data, bool byGoals, bool byCost, out IList<DonutChartDataItem> other)
|
|
{
|
|
other = new List<DonutChartDataItem>();
|
|
var filteredData = new List<DonutChartDataItem>();
|
|
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<DonutChartDataItem> 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<Guid> projectStatuses,
|
|
List<Guid> projectTypes, ForecastDashboardMode mode, List<Guid> teams, List<Guid> views,
|
|
List<Guid> companies, bool isLaborMode, List<Guid> filterGroups, List<Guid> strategicGoals,
|
|
List<Guid> tags, List<Guid> clients)
|
|
{
|
|
List<DateTime> fiscalCalendarWeekendings = new List<DateTime>();
|
|
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,
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
/// <param name="firstWeekEnding"></param>
|
|
/// <param name="lastWeekEnding"></param>
|
|
/// <param name="projects"></param>
|
|
/// <param name="projectStatuses"></param>
|
|
/// <param name="projectTypes"></param>
|
|
/// <param name="clients"></param>
|
|
/// <param name="scenarioType"></param>
|
|
/// <param name="isMainDashboardMode"></param>
|
|
/// <param name="isLaborMode"></param>
|
|
/// <param name="filterGroups"></param>
|
|
/// <param name="teams"></param>
|
|
/// <returns></returns>
|
|
private IQueryable<Data> GetWeeksData(DateTime firstWeekEnding, DateTime lastWeekEnding, IQueryable<ProjectWithChildrenView> projects, List<Guid> projectStatuses, List<Guid> projectTypes, List<Guid> clients, int scenarioType, bool isMainDashboardMode, bool isLaborMode, List<Guid> filterGroups, IEnumerable<Guid> 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<int>();
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns current, next and previous fiscal periods of given type for the datepoint
|
|
/// </summary>
|
|
/// <param name="datePoint"></param>
|
|
/// <param name="periodType"></param>
|
|
/// <returns>Returns periods, if exist (-1 = prev, 0 = current, 1 = next)</returns>
|
|
private Dictionary<int, Guid> GetClosedToDateFiscalPeriods(DateTime datePoint,
|
|
FiscalCalendarModel.FiscalYearType periodType)
|
|
{
|
|
Dictionary<int, Guid> result = new Dictionary<int, Guid>();
|
|
DateTime viewRangeStartDate = datePoint.AddYears(-2);
|
|
DateTime viewRangeEndDate = datePoint.AddYears(2);
|
|
|
|
List<FiscalCalendar> 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<string, StackGraphDataBlock> GetPerformanceGraphData(Guid userId, DateTime datePoint, ForecastDashboardPerformanceGraphFilterModel filter,
|
|
Func<PerformanceDataRequest, IEnumerable<BarData>> getBarDataFunc)
|
|
{
|
|
var result = new Dictionary<string, StackGraphDataBlock>();
|
|
|
|
// 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<int, Guid> fiscalPeriods = GetClosedToDateFiscalPeriods(datePoint, FiscalCalendarModel.FiscalYearType.Quarter);
|
|
|
|
Dictionary<int, string> resultKeys = new Dictionary<int, string> { { -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<BarData> 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<BarData> 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<ForecastDashboardProjectPerformanceModel> GetProjectPerformanceData(IQueryable<VW_ProjectPerformance> 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);
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// Fill absent weekends and normalize arrays
|
|
/// </summary>
|
|
private List<long[]> FillMissingWeeends(List<long[]> list, IEnumerable<long> 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<Guid> 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<Data> GetBubbleChartData(DateTime startDate, DateTime endDate, Guid userId, string type, List<Guid> projectStatuses,
|
|
List<Guid> projectTypes, ForecastDashboardMode mode, List<Guid> teams, List<Guid> views,
|
|
List<Guid> companies, bool isLaborMode, List<Guid> filterGroups, List<Guid> strategicGoals,
|
|
List<Guid> tags, List<Guid> clients)
|
|
{
|
|
var statisticData = GetStatisticData(startDate, endDate, userId, type, projectStatuses, projectTypes, mode, teams, views,
|
|
companies, isLaborMode, filterGroups, strategicGoals, tags, clients);
|
|
|
|
return statisticData.Data;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns top 5 projects performance data by cost
|
|
/// </summary>
|
|
/// <param name="mode">Page display mode (general dashboard, team or view dashboard)</param>
|
|
/// <param name="userId"></param>
|
|
/// <param name="datePoint"></param>
|
|
/// <param name="filterItemId">Team or View id to filter projects</param>
|
|
/// <returns></returns>
|
|
public Dictionary<string, StackGraphDataBlock> 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<Guid> projectStatuses,
|
|
List<Guid> projectTypes, List<Guid> clients, ForecastDashboardMode mode, List<Guid> teams, List<Guid> views, List<Guid> companies, bool isLaborMode, List<Guid> filterGroups, List<Guid> strategicGoals, List<Guid> 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<string, ForecastDashboardWeeklyCapacityModel>();
|
|
|
|
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<DateTime, List<NptWeeklyModel>>();
|
|
//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<NptWeeklyModel>());
|
|
// 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<NptWeeklyModel>();
|
|
//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<string, ForecastDashboardWeeklyCapacityModel>();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns top 5 projects performance data by cost
|
|
/// </summary>
|
|
/// <param name="mode">Page display mode (general dashboard, team or view dashboard)</param>
|
|
/// <param name="userId"></param>
|
|
/// <param name="datePoint"></param>
|
|
/// <param name="filterItemId">Team or View id to filter projects</param>
|
|
/// <returns></returns>
|
|
public Dictionary<string, StackGraphDataBlock>
|
|
GetPerformanceGraphDataByProjectClass(ForecastDashboardMode mode, Guid userId, DateTime datePoint, ForecastDashboardPerformanceGraphFilterModel filter)
|
|
{
|
|
return GetPerformanceGraphData(userId, datePoint, filter, GetBarDataByProjectType);
|
|
}
|
|
|
|
public IQueryable<Data> 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<DonutChartDataItem> otherGoals;
|
|
IList<DonutChartDataItem> 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;
|
|
//}
|
|
|
|
|
|
|
|
|
|
|
|
///// <summary>
|
|
/////
|
|
///// </summary>
|
|
///// <param name="startDate">Start of reporting period.</param>
|
|
///// <param name="endDate">End of reporting period.</param>
|
|
///// <param name="projects">A queryable collection of projects to filter by.</param>
|
|
///// <param name="isLaborMode">Indicates whether to load data for labor or for materials expenditure categories.</param>
|
|
///// <returns>A list of data for each classification.</returns>
|
|
//private List<PieData> GetPieChartData(DateTime startDate, DateTime endDate, IQueryable<Project> projects, bool isLaborMode, List<Guid> 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> { (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<Guid>() { 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<PieData> GetPieChartGoalsData(DateTime startDate, DateTime endDate, IQueryable<Project> projects, bool isLaborMode, List<Guid> filterGroups, List<Guid> 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> { (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<Guid>() { 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>() { 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;
|
|
//}
|
|
|
|
///// <summary>
|
|
/////
|
|
///// </summary>
|
|
///// <param name="startDate">Start of reporting period.</param>
|
|
///// <param name="endDate">End of reporting period.</param>
|
|
///// <param name="projects">A queryable collection of projects to filter by.</param>
|
|
///// <param name="isLaborMode">Indicates whether to load data for labor or for materials expenditure categories.</param>
|
|
///// <returns>A list of data for each classification.</returns>
|
|
//private List<PieData> GetPieChartData(IQueryable<Project> 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> { (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<Guid>() { 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<PieData> GetPieChartGoalsData(IQueryable<Project> 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> { (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<Guid>() { 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<List<object>>
|
|
{
|
|
public void AddValue(long tickValue, decimal dataValue)
|
|
{
|
|
List<object> point = new List<object> {tickValue, dataValue};
|
|
this.Add(point);
|
|
}
|
|
}
|
|
|
|
public class StackGraphSerie
|
|
{
|
|
public StackGraphSerieData data;
|
|
|
|
public StackGraphSerie()
|
|
{
|
|
data = new StackGraphSerieData();
|
|
}
|
|
}
|
|
|
|
public class StackGraphBarOptions : List<List<object>>
|
|
{
|
|
public void AddValue(long tickValue, string barTitle)
|
|
{
|
|
List<object> option = new List<object> {tickValue, barTitle};
|
|
this.Add(option);
|
|
}
|
|
}
|
|
|
|
public class StackGraphDataBlock
|
|
{
|
|
public List<StackGraphSerie> series;
|
|
public List<StackGraphSerie> quantitySeries;
|
|
public StackGraphBarOptions barOptions;
|
|
public StackGraphBarOptions barOptionsQuantity;
|
|
|
|
public StackGraphDataBlock()
|
|
{
|
|
series = new List<StackGraphSerie>();
|
|
quantitySeries = new List<StackGraphSerie>();
|
|
}
|
|
}
|
|
|
|
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<Guid>();
|
|
ProjectTypes = new List<Guid>();
|
|
ProjectFilter = new List<Guid>();
|
|
Views = new List<Guid>();
|
|
Teams = new List<Guid>();
|
|
Companies = new List<Guid>();
|
|
Clients = new List<Guid>();
|
|
StrategicGoals = new List<Guid>();
|
|
Tags = new List<Guid>();
|
|
}
|
|
|
|
public Guid CurrentPeriodId { get; set; }
|
|
public IEnumerable<Guid> FilterGroups { get; set; }
|
|
public IEnumerable<Guid> ProjectTypes { get; set; }
|
|
public IEnumerable<Guid> ProjectStatuses { get; set; }
|
|
public IEnumerable<Guid> ProjectFilter { get; set; }
|
|
public IEnumerable<Guid> Teams { get; set; }
|
|
public IEnumerable<Guid> Views { get; set; }
|
|
public IEnumerable<Guid> Companies { get; set; }
|
|
public IEnumerable<Guid> StrategicGoals { get; internal set; }
|
|
public IEnumerable<Guid> Tags { get; set; }
|
|
public IEnumerable<Guid> 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
|
|
}
|