feat(core): WorkloadService - реализация логики формирования отчета
This commit is contained in:
@@ -82,6 +82,7 @@ namespace PARR.Core
|
|||||||
#region Workload
|
#region Workload
|
||||||
|
|
||||||
services.AddScoped<WorkloadCacheService>();
|
services.AddScoped<WorkloadCacheService>();
|
||||||
|
services.AddScoped<IWorkloadService, WorkloadService>();
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using NodaTime;
|
||||||
using PARR.Core.Common.Interfaces;
|
using PARR.Core.Common.Interfaces;
|
||||||
using PARR.Core.Repositories.Interfaces;
|
using PARR.Core.Repositories.Interfaces;
|
||||||
using PARR.Core.Services.Shortcodes;
|
using PARR.Core.Services.Shortcodes;
|
||||||
using PARR.Core.Services.Workload.Models;
|
using PARR.Core.Services.Workload.Models;
|
||||||
using PARR.Domain.Cache.Models;
|
using PARR.Domain.Cache.Models;
|
||||||
|
using PARR.Domain.DTOs.Workload;
|
||||||
using PARR.Domain.Entities;
|
using PARR.Domain.Entities;
|
||||||
|
using PARR.Domain.Enums;
|
||||||
|
|
||||||
namespace PARR.Core.Services.Workload.Implementations
|
namespace PARR.Core.Services.Workload.Implementations
|
||||||
{
|
{
|
||||||
@@ -33,7 +36,7 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//----------- Настройки, вынести -----------
|
//todo:----------- Настройки, вынести -----------
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ключ КЭШ шаблонов
|
/// Ключ КЭШ шаблонов
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -56,7 +59,6 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ключ КЭШ, отчет о загруженности, одна ЗО
|
/// Ключ КЭШ, отчет о загруженности, одна ЗО
|
||||||
/// Формировать ключ, основа + Hash ЗО
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly string[] cacheWorkloadResponseAreaItemKey = { "workload", "response area", "item" };
|
private readonly string[] cacheWorkloadResponseAreaItemKey = { "workload", "response area", "item" };
|
||||||
|
|
||||||
@@ -67,7 +69,6 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ключ КЭШ, отчет о загруженности, одна РГ
|
/// Ключ КЭШ, отчет о загруженности, одна РГ
|
||||||
/// Формировать ключ, основа + Hash РГ
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly string[] cacheWorkloadWorkGroupItemKey = { "workload", "work group", "item" };
|
private readonly string[] cacheWorkloadWorkGroupItemKey = { "workload", "work group", "item" };
|
||||||
|
|
||||||
@@ -161,19 +162,22 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Получить кэш РГ и ЗО всех шаблонов, получить недостающие поля из БД.
|
/// Получить кэш РГ и ЗО всех шаблонов, получить недостающие поля из БД.
|
||||||
/// Сравнит кол-во записей в БД и в КЭШ, если не хватает до 1000 шаблонов, доформирует кэш,
|
/// Сравнит кол-во записей в БД и в КЭШ, если не хватает до 1000 шаблонов, доформирует кэш,
|
||||||
/// если больше 1000, вернет пустой список, так как считается кэш неактуальным.
|
/// если больше 1000, вернет null, так как считается кэш неактуальным.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <param name="dateStart">Дата начала</param>
|
||||||
public async Task<Dictionary<Guid, TemplateReportData>> GetTemplateReportDataAsync()
|
/// <param name="durationDays">Продолжительность дней</param>
|
||||||
|
/// <param name="offset">Смещение часового пояса</param>
|
||||||
|
/// <returns>null - если нет кэша или он не актуальный. Иначе, список шаблонов.</returns>
|
||||||
|
public async Task<List<TemplateReportData>?> GetTemplateReportDataAsync(DateOnly dateStart, int durationDays, TimeSpan offset)
|
||||||
{
|
{
|
||||||
logger.LogDebug("Получаю данные для формирования отчета.");
|
logger.LogDebug("Получаю данные для формирования отчета. DateStart {DateStart}, DurationDays {DurationDays}, Offset {Offset}", dateStart, durationDays, offset);
|
||||||
|
|
||||||
// Получим кэш целиком
|
// Получим кэш целиком
|
||||||
var cacheData = await redisCacheService.GetAllHashFieldsAsync<TemplateCache>(redisCacheService.GetKey(cacheTemplateKey));
|
var cacheData = await redisCacheService.GetAllHashFieldsAsync<TemplateCache>(redisCacheService.GetKey(cacheTemplateKey));
|
||||||
if (cacheData.Count == 0)
|
if (cacheData.Count == 0)
|
||||||
{
|
{
|
||||||
logger.LogDebug("Записей в кэш 0. Конец.");
|
logger.LogDebug("Записей в кэш 0. Конец.");
|
||||||
return new Dictionary<Guid, TemplateReportData>();
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сравним кол-во записей в БД и в КЭШ, вдруг там разница больше допустимой
|
// Сравним кол-во записей в БД и в КЭШ, вдруг там разница больше допустимой
|
||||||
@@ -182,16 +186,24 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
{
|
{
|
||||||
logger.LogInformation("Разница в количестве шаблонов в БД ({TemplateCount}) и в КЭШ ({CacheCount}) больше допустимой {MaxCacheDiff}. Считаем КЭШ неактуальным. Требуется повторное формирование КЭШа.",
|
logger.LogInformation("Разница в количестве шаблонов в БД ({TemplateCount}) и в КЭШ ({CacheCount}) больше допустимой {MaxCacheDiff}. Считаем КЭШ неактуальным. Требуется повторное формирование КЭШа.",
|
||||||
templatesCount, cacheData.Count, maxCacheDiff);
|
templatesCount, cacheData.Count, maxCacheDiff);
|
||||||
return new Dictionary<Guid, TemplateReportData>();
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Получаем из БД только нужные данные для отчета
|
// Получаем из БД только нужные данные для отчета, попадающие в интервал
|
||||||
|
var dtStart = new DateTimeOffset(dateStart.Year, dateStart.Month, dateStart.Day, 0, 0, 0, offset);
|
||||||
|
var dtEnd = dtStart.AddDays(durationDays);
|
||||||
|
|
||||||
|
logger.LogDebug("Фильтруем шаблоны в БД по интервалу: Start={DtStart:yyyy-MM-dd HH:mm:ss zzz}, End={DtEnd:yyyy-MM-dd HH:mm:ss zzz}", dtStart, dtEnd);
|
||||||
|
|
||||||
var templates = await templateRepository.Get()
|
var templates = await templateRepository.Get()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
|
.Where(t => dtStart <= t.NextRun && t.NextRun < dtEnd)
|
||||||
.Select(t => new { Id = t.Id, IsActiveTemplate = t.IsActiveTemplate, IsActiveSchedule = t.IsActiveSchedule, Name = t.Name, NextRun = t.NextRun })
|
.Select(t => new { Id = t.Id, IsActiveTemplate = t.IsActiveTemplate, IsActiveSchedule = t.IsActiveSchedule, Name = t.Name, NextRun = t.NextRun })
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
var reportData = new Dictionary<Guid, TemplateReportData>();
|
logger.LogDebug("Загружено шаблонов из БД {Count}", templates.Count);
|
||||||
|
|
||||||
|
var reportData = new List<TemplateReportData>();
|
||||||
|
|
||||||
foreach (var template in templates)
|
foreach (var template in templates)
|
||||||
{
|
{
|
||||||
@@ -220,7 +232,7 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
template.IsActiveTemplate,
|
template.IsActiveTemplate,
|
||||||
template.IsActiveSchedule,
|
template.IsActiveSchedule,
|
||||||
DateTimeOffset.UtcNow);
|
DateTimeOffset.UtcNow);
|
||||||
reportData.Add(template.Id, data);
|
reportData.Add(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogDebug("Сформировано итоговых данных для отчета, строк: {Count}", reportData.Count);
|
logger.LogDebug("Сформировано итоговых данных для отчета, строк: {Count}", reportData.Count);
|
||||||
@@ -279,5 +291,93 @@ namespace PARR.Core.Services.Workload.Implementations
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получить отчет Workload из кэша
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reportType"></param>
|
||||||
|
/// <param name="dateStart"></param>
|
||||||
|
/// <param name="durationDays"></param>
|
||||||
|
/// <param name="offset"></param>
|
||||||
|
/// <param name="filterParam">Наименование ЗО или РГ или null</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task<WorkloadReport?> GetWorkloadReportAsync(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam)
|
||||||
|
{
|
||||||
|
var key = GetReportCacheKey(reportType, dateStart, durationDays, offset, filterParam);
|
||||||
|
|
||||||
|
return await redisCacheService.GetCachedDataAsync<WorkloadReport>(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Записать отчет Workload в кэш
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="report"></param>
|
||||||
|
/// <param name="reportType"></param>
|
||||||
|
/// <param name="dateStart"></param>
|
||||||
|
/// <param name="durationDays"></param>
|
||||||
|
/// <param name="offset"></param>
|
||||||
|
/// <param name="filterParam">Наименование ЗО или РГ или null</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
public async Task SetWorkloadReport(WorkloadReport report, WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam)
|
||||||
|
{
|
||||||
|
var key = GetReportCacheKey(reportType, dateStart, durationDays, offset, filterParam);
|
||||||
|
await redisCacheService.SetCachedDataAsync(key, report, cacheWorkloadTtl);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Формирует ключ для отчета Workload
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="reportType"></param>
|
||||||
|
/// <param name="dateStart"></param>
|
||||||
|
/// <param name="durationDays"></param>
|
||||||
|
/// <param name="offset"></param>
|
||||||
|
/// <param name="filterParam"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private string GetReportCacheKey(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam = null)
|
||||||
|
{
|
||||||
|
// формируе ключ, вида: основа + дата начала + продолжительность + смещение
|
||||||
|
// Если есть параметр (filterParam), добавляется параметр в виде Hash
|
||||||
|
|
||||||
|
var keyList = new List<string>();
|
||||||
|
|
||||||
|
switch (reportType)
|
||||||
|
{
|
||||||
|
case WorkloadReportType.ResponseAreaAll:
|
||||||
|
keyList.AddRange(cacheWorkloadResponseAreaAllKey);
|
||||||
|
break;
|
||||||
|
case WorkloadReportType.ResponseAreaItem:
|
||||||
|
keyList.AddRange(cacheWorkloadResponseAreaItemKey);
|
||||||
|
break;
|
||||||
|
case WorkloadReportType.WorkGroupAll:
|
||||||
|
keyList.AddRange(cacheWorkloadWorkGroupAllKey);
|
||||||
|
break;
|
||||||
|
case WorkloadReportType.WorkGroupItem:
|
||||||
|
keyList.AddRange(cacheWorkloadWorkGroupItemKey);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
logger.LogError("Недопустимое значение тип отчета '{ReportType}'", reportType);
|
||||||
|
throw new ArgumentException("Недопустимое значение тип отчета", nameof(reportType));
|
||||||
|
}
|
||||||
|
|
||||||
|
keyList.AddRange(new string[] { dateStart.ToString(), durationDays.ToString(), offset.ToString() });
|
||||||
|
|
||||||
|
var keyStr = string.Empty;
|
||||||
|
|
||||||
|
if (filterParam == null)
|
||||||
|
keyStr = redisCacheService.GetKey(keyList.ToArray());
|
||||||
|
else
|
||||||
|
keyStr = redisCacheService.GetKey(keyList.ToArray(), new string[] { filterParam });
|
||||||
|
|
||||||
|
logger.LogDebug("Сформирован ключ для кэш '{Key}'", keyStr);
|
||||||
|
|
||||||
|
return keyStr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
197
PARR.Core/Services/Workload/Implementations/WorkloadService.cs
Normal file
197
PARR.Core/Services/Workload/Implementations/WorkloadService.cs
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using PARR.Core.Services.NextRunServices;
|
||||||
|
using PARR.Core.Services.Workload.Interfaces;
|
||||||
|
using PARR.Core.Services.Workload.Models;
|
||||||
|
using PARR.Domain.DTOs.Workload;
|
||||||
|
using PARR.Domain.Enums;
|
||||||
|
|
||||||
|
namespace PARR.Core.Services.Workload.Implementations
|
||||||
|
{
|
||||||
|
internal class WorkloadService : IWorkloadService
|
||||||
|
{
|
||||||
|
private readonly ILogger<WorkloadService> logger;
|
||||||
|
private readonly WorkloadCacheService workloadCacheService;
|
||||||
|
private readonly INextRunService nextRunService;
|
||||||
|
|
||||||
|
public WorkloadService(
|
||||||
|
ILogger<WorkloadService> logger,
|
||||||
|
WorkloadCacheService workloadCacheService,
|
||||||
|
INextRunService nextRunService
|
||||||
|
)
|
||||||
|
{
|
||||||
|
this.logger = logger;
|
||||||
|
this.workloadCacheService = workloadCacheService;
|
||||||
|
this.nextRunService = nextRunService;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<WorkloadReport?> GetWorkloadReport(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam)
|
||||||
|
{
|
||||||
|
logger.LogDebug("Начинаю формировать отчет о загруженности по параметрам: reportType '{ReportType}', dateStart {DateStart}, durationDays {DurationDays}, offset {Offset}",
|
||||||
|
reportType.ToString(), dateStart, durationDays, offset);
|
||||||
|
|
||||||
|
if ((reportType == WorkloadReportType.WorkGroupItem || reportType == WorkloadReportType.ResponseAreaItem) && string.IsNullOrEmpty(filterParam))
|
||||||
|
{
|
||||||
|
logger.LogError("Для типа отчета {ReportType} не передан параметр.", reportType.ToString());
|
||||||
|
throw new ArgumentNullException(nameof(filterParam), $"Для отчета типа {reportType.ToString()} параметр является обязательным.");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Если есть отчет в КЭШ, вернуть
|
||||||
|
var cacheReport = await workloadCacheService.GetWorkloadReportAsync(reportType, dateStart, durationDays, offset, filterParam);
|
||||||
|
if (cacheReport != null)
|
||||||
|
{
|
||||||
|
logger.LogDebug("Вернул отчет из кэша.");
|
||||||
|
return cacheReport;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если нет основного кэша, вернуть null, пусть пользователь нажимает кнопку сформировать отчет
|
||||||
|
var templates = await workloadCacheService.GetTemplateReportDataAsync(dateStart, durationDays, offset);
|
||||||
|
if (templates == null)
|
||||||
|
{
|
||||||
|
logger.LogDebug("Не могу рассчитать отчет Workload, так как нет основного кэш или он не актуальный.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Есть основной кэш, построить отчет и сохранить в кэш отчета
|
||||||
|
logger.LogDebug("Основной кэш есть, начинаю строить отчет.");
|
||||||
|
|
||||||
|
// Список дней
|
||||||
|
var reportDays = await GetReportDays(dateStart, durationDays);
|
||||||
|
|
||||||
|
var displayName = string.Empty;
|
||||||
|
|
||||||
|
// Формируем отчет в зависимости от типа
|
||||||
|
var statistics = new List<WorkloadStatisticItem>();
|
||||||
|
switch (reportType)
|
||||||
|
{
|
||||||
|
case WorkloadReportType.ResponseAreaAll:
|
||||||
|
displayName = "По зонам ответственности";
|
||||||
|
foreach (var group in templates.GroupBy(t => t.ResponseArea))
|
||||||
|
statistics.Add(BuildStatisticItem(group.Key, group, reportDays, offset));
|
||||||
|
break;
|
||||||
|
case WorkloadReportType.ResponseAreaItem:
|
||||||
|
displayName = "По зоне ответственности";
|
||||||
|
var filteredRA = templates.Where(t => t.ResponseArea.Equals(filterParam, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||||
|
statistics.Add(BuildStatisticItem(filterParam!, filteredRA, reportDays, offset));
|
||||||
|
break;
|
||||||
|
case WorkloadReportType.WorkGroupAll:
|
||||||
|
displayName = "По рабочим группам";
|
||||||
|
foreach (var group in templates.GroupBy(t => t.WorkGroup))
|
||||||
|
statistics.Add(BuildStatisticItem(group.Key, group, reportDays, offset));
|
||||||
|
break;
|
||||||
|
case WorkloadReportType.WorkGroupItem:
|
||||||
|
displayName = "По рабочей группе";
|
||||||
|
var filteredWG = templates.Where(t => t.WorkGroup.Equals(filterParam, StringComparison.OrdinalIgnoreCase)).ToList();
|
||||||
|
statistics.Add(BuildStatisticItem(filterParam!, filteredWG, reportDays, offset));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var report = new WorkloadReport
|
||||||
|
{
|
||||||
|
Type = reportType,
|
||||||
|
DisplayName = displayName,
|
||||||
|
SubfilterName = filterParam,
|
||||||
|
ReportCacheCreatedAt = DateTimeOffset.UtcNow,
|
||||||
|
TemplateCacheCreatedAt = templates.FirstOrDefault()?.CacheDate ?? DateTimeOffset.MinValue,
|
||||||
|
Days = reportDays,
|
||||||
|
Statistics = statistics
|
||||||
|
};
|
||||||
|
|
||||||
|
// Сохраним в кэш
|
||||||
|
await workloadCacheService.SetWorkloadReport(report, reportType, dateStart, durationDays, offset, filterParam);
|
||||||
|
logger.LogDebug("Отчет построен и сохранен в кэш");
|
||||||
|
|
||||||
|
return report;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Получить список дней для отчета, с отметкой выходной/рабочий
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dateStart"></param>
|
||||||
|
/// <param name="durationDays"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private async Task<List<WorkloadDayItem>> GetReportDays(DateOnly dateStart, int durationDays)
|
||||||
|
{
|
||||||
|
var dateEnd = dateStart.AddDays(durationDays);
|
||||||
|
var days = await nextRunService.GetWorkDaysAsync(dateStart, dateEnd, false);
|
||||||
|
var weekends = await nextRunService.GetWeekendsAsync(dateStart, dateEnd);
|
||||||
|
|
||||||
|
return days.Select(t => new WorkloadDayItem { Date = t, IsWeekend = weekends.Contains(t) })
|
||||||
|
.OrderBy(t => t.Date)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Составить статистику по одному объекту за период
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="templates"></param>
|
||||||
|
/// <param name="reportDays"></param>
|
||||||
|
/// <param name="offset"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private List<WorkloadDailyItem> GetDailyMetrics(List<TemplateReportData> templates, List<WorkloadDayItem> reportDays, TimeSpan offset)
|
||||||
|
{
|
||||||
|
var result = new List<WorkloadDailyItem>();
|
||||||
|
|
||||||
|
foreach (var dayItem in reportDays)
|
||||||
|
{
|
||||||
|
var dateStart = new DateTimeOffset(dayItem.Date.Year, dayItem.Date.Month, dayItem.Date.Day, 0, 0, 0, offset);
|
||||||
|
var dateEnd = dateStart.AddDays(1);
|
||||||
|
|
||||||
|
var filteredItems = templates.Where(t => dateStart <= t.NextRun && t.NextRun < dateEnd);
|
||||||
|
|
||||||
|
var dailyItem = new WorkloadDailyItem
|
||||||
|
{
|
||||||
|
Date = dayItem.Date,
|
||||||
|
IsWeekend = dayItem.IsWeekend,
|
||||||
|
Total = filteredItems.Count(),
|
||||||
|
Activated = filteredItems.Count(t => t.IsActiveTemplate && t.IsActiveSchedule),
|
||||||
|
Deactivated = filteredItems.Count(t => !t.IsActiveTemplate || !t.IsActiveSchedule)
|
||||||
|
};
|
||||||
|
|
||||||
|
result.Add(dailyItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.OrderBy(t => t.Date).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Заполнить Summary
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="dailyMetrics"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private WorkloadSummary GetSummary(List<WorkloadDailyItem> dailyMetrics, int allDays)
|
||||||
|
{
|
||||||
|
// Локальная функция для расчета одной метрики
|
||||||
|
static WorkloadSummaryItem Calc(List<WorkloadDailyItem> dailyMetrics, int allDays, Func<WorkloadDailyItem, int> selector) => new()
|
||||||
|
{
|
||||||
|
Count = dailyMetrics.Sum(selector),
|
||||||
|
Average = allDays > 0 ? Math.Round((double)dailyMetrics.Sum(selector) / allDays, 2) : 0
|
||||||
|
};
|
||||||
|
|
||||||
|
return new WorkloadSummary
|
||||||
|
{
|
||||||
|
Total = Calc(dailyMetrics, allDays, t => t.Total),
|
||||||
|
Activated = Calc(dailyMetrics, allDays, t => t.Activated),
|
||||||
|
Deactivated = Calc(dailyMetrics, allDays, t => t.Deactivated)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Формирует статистику по одной группе
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="title"></param>
|
||||||
|
/// <param name="groupTemplates"></param>
|
||||||
|
/// <param name="reportDays"></param>
|
||||||
|
/// <param name="offset"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
private WorkloadStatisticItem BuildStatisticItem(string title, IEnumerable<TemplateReportData> groupTemplates, List<WorkloadDayItem> reportDays, TimeSpan offset)
|
||||||
|
{
|
||||||
|
var dailyMetrics = GetDailyMetrics(groupTemplates.ToList(), reportDays, offset);
|
||||||
|
var summary = GetSummary(dailyMetrics, reportDays.Count);
|
||||||
|
|
||||||
|
return new WorkloadStatisticItem { DailyMetrics = dailyMetrics, Summary = summary, Title = title };
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
namespace PARR.Core.Services.Workload.Interfaces
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Сервис формирования отчетности о загруженности
|
||||||
|
/// </summary>
|
||||||
|
public interface IWorkloadService
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
93
PARR.Domain/DTOs/Workload/WorkloadReport.cs
Normal file
93
PARR.Domain/DTOs/Workload/WorkloadReport.cs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
using PARR.Domain.Enums;
|
||||||
|
|
||||||
|
namespace PARR.Domain.DTOs.Workload
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Отчет о загруженности
|
||||||
|
/// </summary>
|
||||||
|
public record WorkloadReport
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Тип отчета (все ЗО, все РГ, одна ЗО, одна РГ)
|
||||||
|
/// </summary>
|
||||||
|
public WorkloadReportType Type { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Название отчета (По всем ЗО, по одной ЗО)
|
||||||
|
/// </summary>
|
||||||
|
public required string DisplayName { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Подзаголовок, наименование конкретной ЗО/РГ
|
||||||
|
/// </summary>
|
||||||
|
public string? SubfilterName { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Дата формирования отчета
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset ReportCacheCreatedAt { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Дата формирования кэша ЗО и РГ
|
||||||
|
/// </summary>
|
||||||
|
public DateTimeOffset TemplateCacheCreatedAt { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Список дней
|
||||||
|
/// </summary>
|
||||||
|
public required List<WorkloadDayItem> Days { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Нагруженность
|
||||||
|
/// </summary>
|
||||||
|
public required List<WorkloadStatisticItem> Statistics { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public record WorkloadSummary
|
||||||
|
{
|
||||||
|
public required WorkloadSummaryItem Total { get; init; }
|
||||||
|
|
||||||
|
public required WorkloadSummaryItem Activated { get; init; }
|
||||||
|
|
||||||
|
public required WorkloadSummaryItem Deactivated { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record WorkloadSummaryItem
|
||||||
|
{
|
||||||
|
public int Count { get; init; }
|
||||||
|
|
||||||
|
public double Average { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record WorkloadDayItem
|
||||||
|
{
|
||||||
|
public DateOnly Date { get; init; }
|
||||||
|
|
||||||
|
public bool IsWeekend { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record WorkloadStatisticItem
|
||||||
|
{
|
||||||
|
public required string Title { get; init; }
|
||||||
|
|
||||||
|
public required WorkloadSummary Summary { get; init; }
|
||||||
|
|
||||||
|
public required List<WorkloadDailyItem> DailyMetrics { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public record WorkloadDailyItem
|
||||||
|
{
|
||||||
|
public DateOnly Date { get; init; }
|
||||||
|
|
||||||
|
public bool IsWeekend { get; init; }
|
||||||
|
|
||||||
|
public int Total { get; init; }
|
||||||
|
|
||||||
|
public int Activated { get; init; }
|
||||||
|
|
||||||
|
public int Deactivated { get; init; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
31
PARR.Domain/Enums/WorkloadReportType.cs
Normal file
31
PARR.Domain/Enums/WorkloadReportType.cs
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace PARR.Domain.Enums
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Тип отчета о загруженности
|
||||||
|
/// </summary>
|
||||||
|
[JsonConverter(typeof(JsonStringEnumConverter))]
|
||||||
|
public enum WorkloadReportType
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Отчет по всем ЗО
|
||||||
|
/// </summary>
|
||||||
|
ResponseAreaAll = 0,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Отчет по одной ЗО
|
||||||
|
/// </summary>
|
||||||
|
ResponseAreaItem = 1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Отчет по всем РГ
|
||||||
|
/// </summary>
|
||||||
|
WorkGroupAll = 2,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Отчет по одной РГ
|
||||||
|
/// </summary>
|
||||||
|
WorkGroupItem = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user