using EnVisage.Code; using EnVisage.Code.ForecastDashboard; using EnVisage.Models; using Kendo.Mvc; using Kendo.Mvc.UI; using System; using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Linq.Dynamic; using System.Web.Mvc; namespace EnVisage.Controllers { public class ForecastGridController : BaseController { #region Actions [HttpPost] public JsonResult GetProjectsSummary(ForecastDashboardFilterModel forecastFilter) { var userId = SecurityManager.GetUserPrincipal(); var manager = new ForecastDashboardManager(DbContext); int totalRecordCount; int searchRecordCount; var scenarios = manager.GetScenarios(userId, out totalRecordCount, out searchRecordCount, "", forecastFilter); var resourceAvailability = manager.GetResourceAvailability(forecastFilter, userId); var numberOfProjects = scenarios.Count(); var projectWeeks = scenarios.Sum(x => x.Duration); var totalRevenue = scenarios.Sum(x => x.ProjectedRevenue); var totalCostSavings = scenarios.Sum(x => x.CostSavings); var totalBUCost = scenarios.Sum(s => (s.UseLMMargin ?? 0) == 1 ? s.BUDirectCosts_LM : s.BUDirectCosts); var totalResourceAvailability = Math.Round(resourceAvailability.Sum(q => q.ResourceAvailability.Value) / resourceAvailability.Sum(q => q.AvailableHours.Value) * 100); var totalNetImpact = scenarios.Sum(x => x.ProjectNetImpact); totalResourceAvailability = double.IsNaN(totalResourceAvailability) ? 0 : totalResourceAvailability; var summary = new { numberOfProjects, projectWeeks, totalRevenue, totalCostSavings, totalBUCost, totalNetImpact, totalResourceAvailability }; return Json(summary); } [HttpPost] public JsonResult GetScenarios([DataSourceRequest]Kendo.Mvc.UI.DataSourceRequest request, ForecastDashboardFilterModel forecastFilter) { int totalRecordCount; int searchRecordCount; var manager = new ForecastDashboardManager(DbContext); var userId = SecurityManager.GetUserPrincipal(); var scenarios = manager.GetScenarios(userId, out totalRecordCount, out searchRecordCount, "", forecastFilter); var distinct = scenarios.GroupBy(x => x.Id) .Select(x => x.FirstOrDefault()) .ToList(); var gridData = GetGridData(request, scenarios, distinct); return Json(gridData, JsonRequestBehavior.AllowGet); } [HttpPost] public JsonResult GetResourceAvailability([DataSourceRequest]Kendo.Mvc.UI.DataSourceRequest request, ForecastDashboardFilterModel forecastFilter) { var manager = new ForecastDashboardManager(DbContext); var userId = SecurityManager.GetUserPrincipal(); var data = manager.GetResourceAvailability(forecastFilter, userId); var gridData = GetGridData(request, data, data); return Json(gridData, JsonRequestBehavior.AllowGet); } [HttpPost] public ActionResult ExportResourceAvailability(string contentType, string base64, string fileName) { var fileContents = Convert.FromBase64String(base64); return File(fileContents, contentType, fileName); } #endregion #region Private Methods private object GetGridData([DataSourceRequest]Kendo.Mvc.UI.DataSourceRequest request, IEnumerable collection, IEnumerable distinctCollection) { var ordering = string.Empty; if (request.Groups != null && request.Groups.Any()) { ordering = string.Join(",", request.Groups.Select(GetOrderDescriptor)); } if (request.Sorts != null && request.Sorts.Any()) { var sort = string.Join(",", request.Sorts.Select(GetOrderDescriptor)); ordering = string.IsNullOrEmpty(ordering) ? sort : string.Join(",", ordering, sort); } if (!string.IsNullOrEmpty(ordering)) { collection = collection.OrderBy(ordering); } var pageItems = collection.Skip((request.Page - 1) * request.PageSize).Take(request.PageSize); var aggregatesTotal = GetAggregates(distinctCollection.GroupBy(x => 1), request.Aggregates); //GroupBy(x => 1) - fake grouping to enable dynamic aggregation var aggregates = aggregatesTotal.Select("Aggregates").AsQueryable().FirstOrDefault(); var gridData = new { Total = collection.Count(), Aggregates = aggregates, Groups = GetGroups2(collection, pageItems, request.Groups, request.Aggregates), Data = request.Groups == null || !request.Groups.Any() ? pageItems : null }; return gridData; } private string GetOrderDescriptor(SortDescriptor sort) { return GetOrderDescriptor(sort.Member, sort.SortDirection); } private string GetOrderDescriptor(GroupDescriptor group) { return GetOrderDescriptor(group.Member, group.SortDirection); } private string GetOrderDescriptor(string field, ListSortDirection? dir) { switch (dir) { case ListSortDirection.Ascending: return field; case ListSortDirection.Descending: return field + " descending"; } return null; } private IEnumerable GetAggregates(IEnumerable groups, IEnumerable aggregates) { Func formatAggrFunc = aggrFunc => { var aggrFuncName = aggrFunc.AggregateMethodName.ToLower(); return $"{TranslateAggregator(aggrFunc)} as {aggrFuncName}"; }; Func>, string> formatMemberAggregates = (kvp) => { var memberAggregates = kvp.Value.Select(x => formatAggrFunc(x)); var memberAggregatesStr = string.Join("," + Environment.NewLine, memberAggregates); return $"new ({memberAggregatesStr}) as {kvp.Key}"; }; var groupedAggregates = aggregates.GroupBy(x => x.Member).ToDictionary(x => x.Key, x => x.SelectMany(y => y.Aggregates)); var aggrExpressions = groupedAggregates.Select(formatMemberAggregates); var aggrExpressionsStr = string.Join("," + Environment.NewLine, aggrExpressions); var aggrSelector = $"new ({aggrExpressionsStr}) as Aggregates"; var selector = @"new (" + aggrSelector + ", it.Key)"; var aggreagates = groups.Select(selector); return aggreagates; } private string TranslateAggregator(AggregateFunction aggrFunction) { var aggrMethod = aggrFunction.AggregateMethodName; var member = aggrFunction.SourceField; switch (aggrMethod) { case "Count": return "it.Count()"; case "Sum": return $"it.Sum(it.{member})"; case "Min": return $"it.Min(it.{member})"; case "Max": return $"it.Max(it.{member})"; case "Average": return $"it.Average(it.{member})"; } throw new InvalidOperationException($"Invalid aggregate method '{aggrMethod}'"); } private IEnumerable GetGroups2(IEnumerable items, IEnumerable pageItems, IEnumerable groupInfos, IEnumerable aggregates, string fieldName = null, string fieldValue = null) { if (groupInfos == null || !groupInfos.Any()) { yield break; } var groupInfo = groupInfos.First(); var isLast = groupInfos.Count() == 1; var groups = items.GroupBy(groupInfo.Member, "it"); var aggrResult = GetAggregates(groups, aggregates); var pagedGroups = pageItems.GroupBy(groupInfo.Member, "it"); var query = from IGrouping pagedGroup in pagedGroups join IGrouping group1 in groups on pagedGroup.Key equals group1.Key join dynamic aggr in aggrResult on pagedGroup.Key equals aggr.Key select new { aggregates = aggr.Aggregates, field = groupInfo.Member, value = pagedGroup.Key, items = isLast ? (fieldName != null && fieldValue != null ? pageItems.Where(fieldName + " = \"" + fieldValue + "\"" +" && " + groupInfo.Member + " = \"" + pagedGroup.Key + "\"") : pagedGroup) : GetGroups2(group1, pageItems, groupInfos.Skip(1), aggregates, groupInfo.Member, pagedGroup.Key), hasSubgroups = !isLast }; var orderedItems = groupInfo.SortDirection == ListSortDirection.Descending ? query.OrderByDescending(x => x.value) : query.OrderBy(x => x.value); foreach (var item in orderedItems) { yield return item; } } #endregion } }