feat(api, domain, core): StatWorkloadTemplate - дополнительная фильтрация по РГ и ЗО.

This commit is contained in:
Mikhail Trubnikov
2026-06-05 10:28:15 +10:00
parent 1baa8f2296
commit 8cb7a2d599
7 changed files with 126 additions and 17 deletions

View File

@@ -0,0 +1,17 @@
using PARR.Domain.Enums.Workload;
namespace PARR.API.Contracts.V1.Requests.Queries
{
public record StatWorkloadTemplateReqportQuery
{
/// <summary>
/// Тип дополнительно фильтра для работы (ЗО, РГ)
/// </summary>
public WorkloadJobSubfliterType? JobSubfilterType { get; init; }
/// <summary>
/// Значение дополнительного фильтра для работы
/// </summary>
public string? JobSubfilterValue { get; init; }
}
}

View File

@@ -72,6 +72,8 @@ namespace PARR.API.Contracts.V1.Responses.Statistics
{
public required string Title { get; init; }
public Guid? ObjId { get; init; }
public required WorkloadSummaryResponse Summary { get; init; }
public required List<WorkloadDailyItemResponse> DailyMetrics { get; init; }

View File

@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using PARR.API.Contracts.V1;
using PARR.API.Contracts.V1.Requests.BaseRequests;
using PARR.API.Contracts.V1.Requests.Queries;
using PARR.API.Contracts.V1.Responses.Base;
using PARR.API.Contracts.V1.Responses.Statistics;
using PARR.API.Controllers.V1.Base;
@@ -45,7 +46,14 @@ namespace PARR.API.Controllers.V1.Statistics
/// <returns></returns>
/// <exception cref="ArgumentException"></exception>
[HttpGet(ApiRoutes.StatWorkloadTemplate.GetWorkloadTemplateReport)]
public async Task<IActionResult> GetWorkloadTemplateReport([FromRoute] WorkloadTemplateReportType reportType, [FromRoute] string filter, [FromRoute] WorkloadTemplateReportState state, [FromRoute] DateOnly date, [FromQuery] TimeZoneOffsetClient offset)
public async Task<IActionResult> GetWorkloadTemplateReport(
[FromRoute] WorkloadTemplateReportType reportType,
[FromRoute] string filter,
[FromRoute] WorkloadTemplateReportState state,
[FromRoute] DateOnly date,
[FromQuery] TimeZoneOffsetClient offset,
[FromQuery] StatWorkloadTemplateReqportQuery query
)
{
if (string.IsNullOrWhiteSpace(filter))
{
@@ -53,7 +61,13 @@ namespace PARR.API.Controllers.V1.Statistics
throw new ArgumentException(nameof(filter), "Не передано значение фильтра.");
}
var report = await workloadService.GetTemplateReportAsync(reportType, filter, state, date, offset.TimeZoneOffset);
if (query.JobSubfilterType.HasValue && string.IsNullOrWhiteSpace(query.JobSubfilterValue))
{
logger.LogWarning("Не передано значение дополнительного фильтра для работы");
throw new ArgumentException(nameof(filter), "Не передано значение дополнительного фильтра для работы.");
}
var report = await workloadService.GetTemplateReportAsync(reportType, filter, state, date, offset.TimeZoneOffset, query.JobSubfilterType, query.JobSubfilterValue);
var response = mapper.Map<List<StatWorkloadTemplateReportResponse>>(report);

View File

@@ -9,6 +9,7 @@ using PARR.Core.Services.TaskServices.Helpers;
using PARR.Core.Services.Workload.Interfaces;
using PARR.Core.Services.Workload.Models;
using PARR.Domain.DTOs.Workload;
using PARR.Domain.Entities;
using PARR.Domain.Enums;
using PARR.Domain.Enums.Workload;
@@ -55,7 +56,6 @@ namespace PARR.Core.Services.Workload.Implementations
throw new ArgumentNullException(nameof(filterParam), $"Для отчета типа {reportType.ToString()} параметр является обязательным.");
}
// Если есть отчет в КЭШ, вернуть
var cacheReport = await workloadCacheService.GetWorkloadReportAsync(reportType, dateStart, durationDays, offset, filterParam);
if (cacheReport != null)
@@ -87,7 +87,7 @@ namespace PARR.Core.Services.Workload.Implementations
case WorkloadReportType.ResponseAreaAll:
displayName = "По зонам ответственности";
foreach (var group in templates.GroupBy(t => t.ResponseArea, StringComparer.OrdinalIgnoreCase))
statistics.Add(BuildStatisticItem(group.Key, group, reportDays, offset));
statistics.Add(BuildStatisticItem(group.Key, null, group, reportDays, offset));
break;
case WorkloadReportType.ResponseAreaWorkGroups:
displayName = "По зоне ответственности";
@@ -95,13 +95,13 @@ namespace PARR.Core.Services.Workload.Implementations
var filteredRA = templates.Where(t => string.Equals(t.ResponseArea, filterParam, StringComparison.OrdinalIgnoreCase)).ToList();
// Группируем работы по РГ
foreach (var group in filteredRA.GroupBy(t => t.WorkGroup, StringComparer.OrdinalIgnoreCase))
statistics.Add(BuildStatisticItem(group.Key, group, reportDays, offset));
statistics.Add(BuildStatisticItem(group.Key, null, group, reportDays, offset));
//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));
statistics.Add(BuildStatisticItem(group.Key, null, group, reportDays, offset));
break;
case WorkloadReportType.WorkGroupJob:
displayName = "По рабочей группе";
@@ -118,7 +118,7 @@ namespace PARR.Core.Services.Workload.Implementations
foreach (var group in filteredWG.GroupBy(t => t.JobId))
{
var jobName = jobs.GetValueOrDefault(group.Key) ?? "Работа";
statistics.Add(BuildStatisticItem(jobName, group, reportDays, offset));
statistics.Add(BuildStatisticItem(jobName, group.Key, group, reportDays, offset));
}
break;
}
@@ -175,7 +175,15 @@ namespace PARR.Core.Services.Workload.Implementations
}
public async Task<List<WorkloadTemplateReport>> GetTemplateReportAsync(WorkloadTemplateReportType reportType, string filter, WorkloadTemplateReportState state, DateOnly date, TimeSpan offset)
public async Task<List<WorkloadTemplateReport>> GetTemplateReportAsync(
WorkloadTemplateReportType reportType,
string filter,
WorkloadTemplateReportState state,
DateOnly date,
TimeSpan offset,
WorkloadJobSubfliterType? jobSubfilterType,
string? jobSubfilterValue
)
{
if (string.IsNullOrWhiteSpace(filter))
{
@@ -218,19 +226,16 @@ namespace PARR.Core.Services.Workload.Implementations
break;
}
// Если тип - работы
// Если тип - работы, получим из бд сразу нужные работы
if (reportType == WorkloadTemplateReportType.Job)
{
if (!Guid.TryParse(trimmedFilter, out Guid jobId))
throw new ArgumentException("Значение фильтра не является Guid");
return await query.Where(t => t.JobId == jobId)
.OrderBy(t => t.Name)
.Select(t => new WorkloadTemplateReport(t.Id, t.Name, t.NextRun, t.IsActiveTemplate, t.IsActiveSchedule))
.ToListAsync();
query = query.Where(t => t.JobId == jobId);
}
// Тип "не работы", загружаем шаблоны
// Загружаем шаблоны
var dbTemplates = await query.OrderBy(t => t.Name)
.Select(t => new WorkloadTemplateReport(t.Id, t.Name, t.NextRun, t.IsActiveTemplate, t.IsActiveSchedule))
.ToListAsync();
@@ -238,6 +243,13 @@ namespace PARR.Core.Services.Workload.Implementations
if (dbTemplates.Count == 0)
return new List<WorkloadTemplateReport>();
// Если тип - работы, и нет дополнительных фильтров по ЗО или РГ, не полезем в кэш, вернем сразу
if (reportType == WorkloadTemplateReportType.Job && (!jobSubfilterType.HasValue || string.IsNullOrWhiteSpace(jobSubfilterValue)))
{
// Тут кэш не нужен, вернем сразу из бд (нет фильтров по РГ и ЗО, нужен список всех работ на дату)
return FilterWorkloadJobTemplateReport(dbTemplates, null);
}
// Получаем из кэша шаблоны (если их в кэше нет, догружаем в кэш)
var cacheData = await workloadCacheService.GetTemplateReportDataByIdsAsync(dbTemplates.Select(t => t.TemplateId).ToHashSet());
@@ -250,6 +262,30 @@ namespace PARR.Core.Services.Workload.Implementations
// Фильтр по типу
switch (reportType)
{
case WorkloadTemplateReportType.Job:
// Смотрим есть ли дополнительные фильтры
if (jobSubfilterType.HasValue && !string.IsNullOrWhiteSpace(jobSubfilterValue))
{
var cacheTemplatesJobs = new HashSet<Guid>();
// Фильтруем из кэша
var trimmedJobSubfilterValue = jobSubfilterValue.Trim();
switch (jobSubfilterType)
{
case WorkloadJobSubfliterType.ResponseArea:
cacheTemplatesJobs = cacheData.Where(t => string.Equals(t.Data.ResponseArea, trimmedJobSubfilterValue, StringComparison.OrdinalIgnoreCase))
.Select(t => t.Data.TemplateId).ToHashSet();
break;
case WorkloadJobSubfliterType.WorkGroup:
cacheTemplatesJobs = cacheData.Where(t => string.Equals(t.Data.WorkGroup, trimmedJobSubfilterValue, StringComparison.OrdinalIgnoreCase))
.Select(t => t.Data.TemplateId).ToHashSet();
break;
default:
throw new ArgumentException("Неверный тип фильтра", nameof(jobSubfilterType));
}
return FilterWorkloadJobTemplateReport(dbTemplates, cacheTemplatesJobs);
}
return FilterWorkloadJobTemplateReport(dbTemplates, null);
case WorkloadTemplateReportType.WorkGroup:
var cacheTemplatesWg = cacheData.Where(t => string.Equals(t.Data.WorkGroup, trimmedFilter, StringComparison.OrdinalIgnoreCase))
.Select(t => t.Data.TemplateId).ToHashSet();
@@ -266,6 +302,16 @@ namespace PARR.Core.Services.Workload.Implementations
}
private List<WorkloadTemplateReport> FilterWorkloadJobTemplateReport(List<WorkloadTemplateReport> dbTemplates, HashSet<Guid>? filteredCacheTemplates)
{
if (filteredCacheTemplates == null)
return dbTemplates;
// Отсекаем шаблоны которых нет в кэше
return dbTemplates.Where(t => filteredCacheTemplates.Contains(t.TemplateId)).ToList();
}
/// <summary>
/// Получить список дней для отчета, с отметкой выходной/рабочий
/// </summary>
@@ -347,12 +393,12 @@ namespace PARR.Core.Services.Workload.Implementations
/// <param name="reportDays"></param>
/// <param name="offset"></param>
/// <returns></returns>
private WorkloadStatisticItem BuildStatisticItem(string title, IEnumerable<TemplateReportData> groupTemplates, List<WorkloadDayItem> reportDays, TimeSpan offset)
private WorkloadStatisticItem BuildStatisticItem(string title, Guid? objId, 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 };
return new WorkloadStatisticItem { DailyMetrics = dailyMetrics, Summary = summary, Title = title, ObjId = objId };
}
}
}

View File

@@ -33,8 +33,10 @@ namespace PARR.Core.Services.Workload.Interfaces
/// <param name="state"></param>
/// <param name="date"></param>
/// <param name="offset"></param>
/// <param name="jobSubfilterType">Дополнительный фильтр при отчете по шаблонам, тип</param>
/// <param name="jobSubfilterValue">Дополнительный фильтр при отчете по шаблонам, значение</param>
/// <returns></returns>
Task<List<WorkloadTemplateReport>> GetTemplateReportAsync(WorkloadTemplateReportType reportType, string filter, WorkloadTemplateReportState state, DateOnly date, TimeSpan offset);
Task<List<WorkloadTemplateReport>> GetTemplateReportAsync(WorkloadTemplateReportType reportType, string filter, WorkloadTemplateReportState state, DateOnly date, TimeSpan offset, WorkloadJobSubfliterType? jobSubfilterType, string? jobSubfilterValue);
}
}

View File

@@ -73,6 +73,12 @@ namespace PARR.Domain.DTOs.Workload
{
public required string Title { get; init; }
/// <summary>
/// ИД строки, если есть.
/// Например JobId
/// </summary>
public Guid? ObjId { get; init; }
public required WorkloadSummary Summary { get; init; }
public required List<WorkloadDailyItem> DailyMetrics { get; init; }

View File

@@ -0,0 +1,22 @@
using System.Text.Json.Serialization;
namespace PARR.Domain.Enums.Workload
{
/// <summary>
/// Дополнительный фильтр для работ.
/// Отчет Workload, шаблоны
/// </summary>
[JsonConverter(typeof(JsonStringEnumConverter))]
public enum WorkloadJobSubfliterType
{
/// <summary>
/// ЗО
/// </summary>
ResponseArea = 0,
/// <summary>
/// РГ
/// </summary>
WorkGroup=1
}
}