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
///
/// Direct GET to the page - returns main view
///
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
});
}
///
/// Direct POST request to the page - returns main part of dashboard table - active scenarios
///
[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(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);
}
///
/// Adds Scenario to the given Scenario Group
///
[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() });
}
}
///
/// Removes Scenario from the given Scenario Group
///
[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();
filter.Views = new List();
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);
}
///
/// Returns data for main chart
///
[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();
filter.Views = new List();
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 result = new Dictionary();
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);
}
}
///
/// Returns data for pie chart
///
[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);
}
///
/// Returns data for Top-5 projects / project types chart
///
[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 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();
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("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 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;
}
///
/// gets grouped items with "per group" aggregates.
/// Currently calculates wrong aggreates with multiple grouping. Works fine with grouping by one column
///
private IEnumerable GetGroups2(IEnumerable items, IEnumerable pageItems, IEnumerable 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 pagedGroup in pagedGroups
join IGrouping 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";
}
///
/// Adds Scenario to the given Scenario Group
///
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();
}
}
///
/// Removes Scenario from the given Scenario Group
///
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
}
}