578 lines
21 KiB
C#
578 lines
21 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Linq.Dynamic;
|
|
using System.Web.Mvc;
|
|
using EnVisage.Models;
|
|
using EnVisage.Code;
|
|
using EnVisage.Code.Charts;
|
|
using System.Collections;
|
|
using EnVisage.Code.ForecastDashboard;
|
|
using System.Net;
|
|
using EnVisage.Code.BLL;
|
|
|
|
namespace EnVisage.Controllers
|
|
{
|
|
[Authorize]
|
|
public class ForecastDashboardController : BaseController
|
|
{
|
|
#region Actions
|
|
|
|
/// <summary>
|
|
/// Direct GET to the page - returns main view
|
|
/// </summary>
|
|
public ActionResult Index(string menuId, string pageModel, string additionalFilters)
|
|
{
|
|
return PartialView("_forecastDashboard",
|
|
new ForecastDashboardOptionsModel
|
|
{
|
|
MenuId = menuId,
|
|
// Added to trace the source page, where forecast widget to be shown
|
|
SourcePageModel = !String.IsNullOrEmpty(pageModel) ? pageModel : String.Empty,
|
|
AdditionalFilterParams = additionalFilters
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Direct POST request to the page - returns main part of dashboard table - active scenarios
|
|
/// </summary>
|
|
[HttpPost]
|
|
public JsonResult GetTable(DataSourceRequest request, ForecastDashboardFilterModel filter)
|
|
{
|
|
int totalRecordCount;
|
|
int searchRecordCount;
|
|
|
|
//var sortedColumns = request.Sort != null ? request.Sort.Select(x => new SortedColumn(x.Field, x.Dir)).ToList() : null;
|
|
//var sortedColumnsCollection = sortedColumns != null ? new ReadOnlyCollection<SortedColumn>(sortedColumns) : null;
|
|
string ordering = string.Empty;
|
|
var manager = new ForecastDashboardManager(DbContext);
|
|
var userId = SecurityManager.GetUserPrincipal();
|
|
|
|
var scenarios = manager.GetScenarios(userId, out totalRecordCount, out searchRecordCount, "", filter);
|
|
|
|
if (request.Group != null)
|
|
{
|
|
ordering = string.Join(",", request.Group.Select(GetOrderDescriptor));
|
|
}
|
|
if (request.Sort != null)
|
|
{
|
|
var sort = string.Join(",", request.Sort.Select(GetOrderDescriptor));
|
|
ordering = string.IsNullOrEmpty(ordering) ? sort : string.Join(",", ordering, sort);
|
|
}
|
|
if (!string.IsNullOrEmpty(ordering))
|
|
{
|
|
scenarios = scenarios.OrderBy(ordering);
|
|
}
|
|
var distinctScenarios = scenarios.GroupBy(x => x.Id)
|
|
.Select(x => x.FirstOrDefault())
|
|
.ToList();
|
|
var pageItems = scenarios.Skip((request.Page - 1) * request.PageSize).Take(request.PageSize);
|
|
var gridData = new
|
|
{
|
|
Total = scenarios.Count(),
|
|
Aggregates = GetAggregates(distinctScenarios),
|
|
Groups = GetGroups2(scenarios, pageItems, request.Group),
|
|
Data = request.Group == null || !request.Group.Any() ? pageItems : null
|
|
};
|
|
|
|
return Json(gridData, JsonRequestBehavior.AllowGet);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds Scenario to the given Scenario Group
|
|
/// </summary>
|
|
[HttpPost]
|
|
public JsonResult CopyScenarioToGroup(Guid? scenarioId, Guid? groupId)
|
|
{
|
|
try
|
|
{
|
|
if (!scenarioId.HasValue || !groupId.HasValue)
|
|
throw new Exception("ScenarioId and GroupId can't be null.");
|
|
if (scenarioId == Guid.Empty || groupId == Guid.Empty)
|
|
throw new Exception("ScenarioId and GroupId can't be empty.");
|
|
|
|
CopyToGroup(scenarioId.Value, groupId.Value);
|
|
return Json(new { Status = "Ok" });
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Json(new { Status = "Error", ErrorMsg = ex.Message, ErrorDetails = ex.ToString() });
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes Scenario from the given Scenario Group
|
|
/// </summary>
|
|
[HttpPost]
|
|
public JsonResult ExtractFromGroup(Guid? scenarioId, Guid? groupId)
|
|
{
|
|
try
|
|
{
|
|
if (!scenarioId.HasValue || !groupId.HasValue)
|
|
throw new Exception("ScenarioId and GroupId can't be null.");
|
|
if (scenarioId == Guid.Empty || groupId == Guid.Empty)
|
|
throw new Exception("ScenarioId and GroupId can't be empty.");
|
|
|
|
DeleteFromGroup(scenarioId.Value, groupId.Value);
|
|
return Json(new { Status = "Ok" });
|
|
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
return Json(new { Status = "Error", ErrorMsg = ex.Message, ErrorDetails = ex.ToString() });
|
|
}
|
|
}
|
|
|
|
[HttpPost]
|
|
public ActionResult GetBubbleChartData(ForecastDashboardChartFilterModel filter)
|
|
{
|
|
if (!filter.StartDate.HasValue)
|
|
filter.StartDate = new DateTime(DateTime.Today.Year, 1, 1);
|
|
|
|
if (!filter.EndDate.HasValue)
|
|
filter.EndDate = new DateTime(DateTime.Today.Year, 12, 31);
|
|
|
|
var mode = ForecastDashboardMode.MainDashboard;
|
|
|
|
if (filter.AdditionalParams != null && filter.AdditionalParams.ContainsKey("mode"))
|
|
{
|
|
filter.Teams = new List<Guid>();
|
|
filter.Views = new List<Guid>();
|
|
|
|
if (filter.AdditionalParams["mode"] == "team" && filter.AdditionalParams.ContainsKey("teamId"))
|
|
{
|
|
mode = ForecastDashboardMode.TeamForecast;
|
|
var teamId = Guid.Empty;
|
|
if (Guid.TryParse((string)filter.AdditionalParams["teamId"], out teamId))
|
|
filter.Teams.Add(teamId);
|
|
}
|
|
if (filter.AdditionalParams["mode"] == "view" && filter.AdditionalParams.ContainsKey("viewId"))
|
|
{
|
|
mode = ForecastDashboardMode.ViewForecast;
|
|
var viewId = Guid.Empty;
|
|
if (Guid.TryParse((string)filter.AdditionalParams["viewId"], out viewId))
|
|
filter.Views.Add(viewId);
|
|
}
|
|
}
|
|
string defaultColor;
|
|
DashboardChartManager.Data[] statisticData;
|
|
using (var manager = new DashboardChartManager())
|
|
{
|
|
var userId = SecurityManager.GetUserPrincipal();
|
|
defaultColor = Utils.GetDefaultProjectColor();
|
|
|
|
statisticData = manager.GetBubbleChartData(filter.StartDate.Value, filter.EndDate.Value, userId, filter.Type, filter.ProjectStatuses, filter.ProjectTypes, mode, filter.Teams, filter.Views, filter.Companies, filter.IsLaborMode, filter.FilterGroups, filter.StrategicGoals, filter.Tags, filter.Clients)
|
|
.ToArray();
|
|
}
|
|
|
|
var chartData =
|
|
from item in statisticData
|
|
group item by new
|
|
{
|
|
item.ProjectId,
|
|
item.ProjectPriority,
|
|
item.ProjectName,
|
|
item.StardDate,
|
|
item.EndDate,
|
|
item.ProjectColor,
|
|
item.ParentProjectColor,
|
|
item.PerformanceRedThreshold,
|
|
item.PerformanceYellowThreshold,
|
|
item.ParentProjectId,
|
|
item.ParentProjectName,
|
|
item.VariationPercent
|
|
} into g
|
|
let dataItem = g.Key
|
|
let middleDate = Utils.GetMiddleDate(dataItem.StardDate.Value, dataItem.EndDate.Value)
|
|
select new
|
|
{
|
|
middleDate = middleDate.ToString("MM/dd/yyyy"),
|
|
middleUnixDate = Utils.ConvertToUnixDate(middleDate),
|
|
projectId = dataItem.ParentProjectId ?? dataItem.ProjectId,
|
|
projectName = dataItem.ProjectName + (dataItem.ParentProjectId.HasValue ? ": " + dataItem.ParentProjectName : string.Empty),
|
|
projectPriority = dataItem.ProjectPriority,
|
|
projectColor = Utils.GetProjectColor(dataItem.ProjectColor, dataItem.ParentProjectColor, defaultColor),
|
|
performanceColor = GetColor(dataItem.PerformanceYellowThreshold, dataItem.PerformanceRedThreshold, dataItem.VariationPercent),
|
|
quantity = Math.Round((decimal)g.Sum(x => x.quantity)),
|
|
cost = g.Sum(x => x.cost)
|
|
};
|
|
|
|
long minDate = -1;
|
|
long maxDate = -1;
|
|
|
|
var chartDataArr = chartData.ToArray();
|
|
if (chartDataArr.Any())
|
|
{
|
|
minDate = chartDataArr.Min(x => x.middleUnixDate);
|
|
maxDate = chartDataArr.Max(x => x.middleUnixDate);
|
|
if (minDate == maxDate)
|
|
{
|
|
long longDay = 1000 * 60 * 60 * 24;
|
|
minDate -= (longDay * 182);
|
|
maxDate += (longDay * 183);
|
|
}
|
|
|
|
for (var i = 0; i < chartDataArr.Length; i++)
|
|
{
|
|
var middleDate1 = chartDataArr[i].middleDate;
|
|
var counter = i + 1;
|
|
var offset = 1;
|
|
for (var j = counter; j < chartDataArr.Length; j++)
|
|
{
|
|
var middleDate2 = chartDataArr[j].middleDate;
|
|
if (middleDate1 == middleDate2)
|
|
{
|
|
chartDataArr[j] = new
|
|
{
|
|
middleDate = Convert.ToDateTime(chartDataArr[j].middleDate).AddDays(offset).ToString("MM/dd/yyyy"),
|
|
middleUnixDate = chartDataArr[j].middleUnixDate,
|
|
projectId = chartDataArr[j].projectId,
|
|
projectName = chartDataArr[j].projectName,
|
|
projectPriority = chartDataArr[j].projectPriority,
|
|
projectColor = chartDataArr[j].projectColor,
|
|
performanceColor = chartDataArr[j].performanceColor,
|
|
quantity = chartDataArr[j].quantity,
|
|
cost = chartDataArr[j].cost
|
|
};
|
|
offset++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
var result = new
|
|
{
|
|
chartData = chartDataArr.OrderByDescending(q => q.quantity),
|
|
minimumDate = minDate,
|
|
maximumDate = maxDate
|
|
};
|
|
|
|
return Json(result);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns data for main chart
|
|
/// </summary>
|
|
[HttpPost]
|
|
public ActionResult GetGraphData(ForecastDashboardChartFilterModel filter)
|
|
{
|
|
if (filter == null)
|
|
return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
|
|
|
|
try
|
|
{
|
|
if (!filter.StartDate.HasValue)
|
|
filter.StartDate = new DateTime(DateTime.Today.Year, 1, 1);
|
|
|
|
if (!filter.EndDate.HasValue)
|
|
filter.EndDate = new DateTime(DateTime.Today.Year, 12, 31);
|
|
|
|
var mode = ForecastDashboardMode.MainDashboard;
|
|
|
|
if (filter.AdditionalParams != null && filter.AdditionalParams.ContainsKey("mode"))
|
|
{
|
|
filter.Teams = new List<Guid>();
|
|
filter.Views = new List<Guid>();
|
|
|
|
if (filter.AdditionalParams["mode"] == "team" && filter.AdditionalParams.ContainsKey("teamId"))
|
|
{
|
|
mode = ForecastDashboardMode.TeamForecast;
|
|
Guid teamId;
|
|
if (Guid.TryParse(filter.AdditionalParams["teamId"], out teamId))
|
|
filter.Teams.Add(teamId);
|
|
}
|
|
if (filter.AdditionalParams["mode"] == "view" && filter.AdditionalParams.ContainsKey("viewId"))
|
|
{
|
|
mode = ForecastDashboardMode.ViewForecast;
|
|
Guid viewId;
|
|
if (Guid.TryParse(filter.AdditionalParams["viewId"], out viewId))
|
|
filter.Views.Add(viewId);
|
|
}
|
|
}
|
|
|
|
Dictionary<string, object> result = new Dictionary<string, object>();
|
|
using (var dashboardChartManager = new DashboardChartManager())
|
|
{
|
|
|
|
var data = dashboardChartManager.GetData(filter.StartDate.Value, filter.EndDate.Value, Guid.Parse(User.Identity.GetID()), filter.Type,
|
|
filter.ProjectStatuses, filter.ProjectTypes, filter.Clients, mode, filter.Teams, filter.Views,
|
|
filter.Companies, filter.IsLaborMode, filter.FilterGroups, filter.StrategicGoals, filter.Tags);
|
|
|
|
if ((filter.ChartType & DashboardChartType.ProjectsByStatus) == DashboardChartType.ProjectsByStatus)
|
|
result.Add(DashboardChartType.ProjectsByStatus.ToString(), dashboardChartManager.GetProjectsByStatusGraphModel(data));
|
|
|
|
if ((filter.ChartType & DashboardChartType.Optimuse) == DashboardChartType.Optimuse)
|
|
result.Add(DashboardChartType.Optimuse.ToString(), dashboardChartManager.GetOptimuseData(data));
|
|
}
|
|
|
|
return Json(result);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
LogException(ex);
|
|
|
|
return new HttpStatusCodeResult(HttpStatusCode.InternalServerError);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns data for pie chart
|
|
/// </summary>
|
|
[HttpPost]
|
|
public JsonResult GetClassPieDataForNewDashboard(ForecastDashboardNewPieChartFilterModel filter)
|
|
{
|
|
var userId = Guid.Parse(User.Identity.GetID());
|
|
DoubleDonutChartData data;
|
|
using (var manager = new DashboardChartManager())
|
|
{
|
|
data = manager.GetPieData(userId, filter);
|
|
}
|
|
return Json(data);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns data for Top-5 projects / project types chart
|
|
/// </summary>
|
|
[HttpPost]
|
|
public JsonResult GetPerformanceGraphData(ForecastDashboardPerformanceGraphFilterModel filter)
|
|
{
|
|
// Take data for specified time point (by default, client sends current date in his own time zone)
|
|
var userId = Guid.Parse(User.Identity.GetID());
|
|
var mode = ForecastDashboardMode.MainDashboard;
|
|
var timePoint = filter.TimePoint > 0 ? Utils.ConvertFromUnixDate(filter.TimePoint).Date : DateTime.UtcNow.Date;
|
|
|
|
Dictionary<string, StackGraphDataBlock> graphData;
|
|
using (var mngr = new DashboardChartManager())
|
|
{
|
|
switch (filter.GraphMode)
|
|
{
|
|
case 1:
|
|
// Top 5 cost projects performance
|
|
graphData = mngr.GetPerformanceGraphDataByProjects(mode, userId, timePoint, filter);
|
|
break;
|
|
|
|
case 2:
|
|
// Top 5 project types performance
|
|
graphData = mngr.GetPerformanceGraphDataByProjectClass(mode, userId, timePoint, filter);
|
|
break;
|
|
|
|
default:
|
|
// Invalid data type requested
|
|
graphData = new Dictionary<string, StackGraphDataBlock>();
|
|
break;
|
|
}
|
|
}
|
|
|
|
return Json(graphData);
|
|
}
|
|
|
|
[HttpGet]
|
|
public ActionResult CheckActiveScenario(Guid projectId)
|
|
{
|
|
var projectManager = new ProjectManager(DbContext);
|
|
var project = projectManager.GetProjectByUser(Guid.Parse(User.Identity.GetID()), projectId);
|
|
if (project == null)
|
|
return HttpNotFound();
|
|
|
|
return Json(new
|
|
{
|
|
activeScenarioId = project.ActiveScenarioId
|
|
}, JsonRequestBehavior.AllowGet);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private Methods
|
|
|
|
private string GetOrderDescriptor(Sort sort)
|
|
{
|
|
return GetOrderDescriptor(sort.Field, sort.Dir);
|
|
}
|
|
|
|
private string GetOrderDescriptor(Group group)
|
|
{
|
|
return GetOrderDescriptor(group.Field, group.Dir);
|
|
}
|
|
|
|
private string GetOrderDescriptor(string field, string dir)
|
|
{
|
|
switch (dir)
|
|
{
|
|
case "asc":
|
|
return field;
|
|
case "desc":
|
|
return field + " descending";
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
//private dynamic GetGroupItems(GroupResult groupResult)
|
|
//{
|
|
// if (groupResult == null || groupResult.Count == 0)
|
|
// {
|
|
// return null;
|
|
// }
|
|
// var hasSubgroups = groupResult.SubGroups != null && groupResult.SubGroups.Any();
|
|
// var groupItems = hasSubgroups ? groupResult.SubGroups.Select(GetGroupItems) : groupResult.Items;
|
|
|
|
// return new
|
|
// {
|
|
// field = groupResult.Field,
|
|
// value = groupResult.Key,
|
|
// items = groupItems,
|
|
// aggregates = GetAggregates(groupResult.Items.AsQueryable().Select<ForecastDashboardModel>("it")),
|
|
// hasSubgroups
|
|
// };
|
|
//}
|
|
|
|
//TODO: get aggregates from client (see GetGroups2) and translate to: new( new(it.Count()) as Id, new(it.Min(it.StartDate)) as StartDate ... )
|
|
private dynamic GetAggregates(IEnumerable<ForecastDashboardModel> items)
|
|
{
|
|
var result = new
|
|
{
|
|
Id = new { count = items.Count() },
|
|
StartDate = new { min = items.Min(x => x.StartDate) },
|
|
EndDate = new { max = items.Max(x => x.EndDate) },
|
|
TDDirectCosts = new { sum = items.Sum(x => x.TDDirectCosts) },
|
|
TDDirectCosts_LM = new { sum = items.Sum(x => x.TDDirectCosts_LM) },
|
|
BUDirectCosts = new { sum = items.Sum(x => x.BUDirectCosts) },
|
|
BUDirectCosts_LM = new { sum = items.Sum(x => x.BUDirectCosts_LM) },
|
|
CostSavings = new { sum = items.Sum(x => x.CostSavings) }
|
|
};
|
|
|
|
return result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// gets grouped items with "per group" aggregates.
|
|
/// Currently calculates wrong aggreates with multiple grouping. Works fine with grouping by one column
|
|
/// </summary>
|
|
private IEnumerable GetGroups2(IEnumerable items, IEnumerable pageItems, IEnumerable<Group> groupInfos)
|
|
{
|
|
if (groupInfos == null)
|
|
{
|
|
yield break;
|
|
}
|
|
using (var enumerator = groupInfos.GetEnumerator())
|
|
{
|
|
if (enumerator.MoveNext())
|
|
{
|
|
bool isLast;
|
|
do
|
|
{
|
|
var groupInfo = enumerator.Current;
|
|
isLast = !enumerator.MoveNext();
|
|
var groups = items.GroupBy(groupInfo.Field, "it");
|
|
var aggregates = GetAggregates(groups, groupInfo);
|
|
var pagedGroups = pageItems.GroupBy(groupInfo.Field, "it");
|
|
|
|
var tmp = from IGrouping<string, dynamic> pagedGroup in pagedGroups
|
|
join IGrouping<string, dynamic> group1 in groups on pagedGroup.Key equals group1.Key
|
|
join dynamic aggr in aggregates on pagedGroup.Key equals aggr.Key
|
|
select new
|
|
{
|
|
aggregates = aggr.Aggregates,
|
|
field = groupInfo.Field,
|
|
value = pagedGroup.Key,
|
|
items = isLast ?
|
|
pagedGroup :
|
|
GetGroups2(group1, pageItems, groupInfos.Skip(1)),
|
|
hasSubgroups = !isLast
|
|
};
|
|
|
|
foreach (var item in tmp)
|
|
{
|
|
yield return item;
|
|
}
|
|
|
|
} while (!isLast);
|
|
}
|
|
}
|
|
}
|
|
|
|
private IEnumerable GetAggregates(IEnumerable groups, Group groupInfo)
|
|
{
|
|
var aggrExpressions = groupInfo.Aggregates.Select(x =>
|
|
$"new ({TranslateAggregator(x)} as {x.Aggregate}) as {x.Field}");
|
|
var aggrExpressionsStr = string.Join(", ", aggrExpressions);
|
|
var aggrSelector = $"new ({aggrExpressionsStr}) as Aggregates";
|
|
var selector = @"new (" + aggrSelector + ", it.Key)";
|
|
var aggreagates = groups.Select(selector);
|
|
|
|
return aggreagates;
|
|
}
|
|
|
|
private string TranslateAggregator(Aggregator aggregator)
|
|
{
|
|
switch (aggregator.Aggregate)
|
|
{
|
|
case "count":
|
|
return "it.Count()";
|
|
case "sum":
|
|
return $"it.Sum(it.{aggregator.Field})";
|
|
case "min":
|
|
return $"it.Min(it.{aggregator.Field})";
|
|
case "max":
|
|
return $"it.Max(it.{aggregator.Field})";
|
|
case "avg":
|
|
return $"it.Average(it.{aggregator.Field})";
|
|
}
|
|
|
|
throw new InvalidOperationException($"Invalid aggregate '{aggregator.Aggregate}'");
|
|
}
|
|
|
|
private string GetColor(decimal? performanceYellowThreshold, decimal? performanceRedThreshold, decimal? variationPercent)
|
|
{
|
|
//TODO: default project color looks like green performance color. So, use some color #CCCCCC
|
|
|
|
|
|
|
|
if (variationPercent.HasValue && performanceYellowThreshold.HasValue && performanceRedThreshold.HasValue)
|
|
{
|
|
return variationPercent >= performanceRedThreshold.Value ?
|
|
"#FF7777" :
|
|
variationPercent < performanceRedThreshold.Value &&
|
|
variationPercent >= performanceYellowThreshold.Value ?
|
|
"#FFFF77" : "#77FF77";
|
|
}
|
|
|
|
return "#CCCCCC";
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds Scenario to the given Scenario Group
|
|
/// </summary>
|
|
private void CopyToGroup(Guid scenarioId, Guid groupId)
|
|
{
|
|
var scenarioIsExists = DbContext.Scenarios.Any(x => x.Id == scenarioId);
|
|
if (scenarioIsExists)
|
|
{
|
|
DbContext.Scenario2Group.Add(new Scenario2Group
|
|
{
|
|
Id = Guid.NewGuid(),
|
|
ScenarioId = scenarioId,
|
|
GroupId = groupId
|
|
});
|
|
|
|
DbContext.SaveChanges();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes Scenario from the given Scenario Group
|
|
/// </summary>
|
|
private void DeleteFromGroup(Guid scenarioId, Guid groupId)
|
|
{
|
|
var entity = DbContext.Scenario2Group.FirstOrDefault(x => x.ScenarioId == scenarioId && x.GroupId == groupId);
|
|
if (entity != null)
|
|
{
|
|
DbContext.Scenario2Group.Remove(entity);
|
|
DbContext.SaveChanges();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
} |