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 required string Title { get; init; }
public Guid? ObjId { get; init; }
public required WorkloadSummaryResponse Summary { get; init; } public required WorkloadSummaryResponse Summary { get; init; }
public required List<WorkloadDailyItemResponse> DailyMetrics { get; init; } public required List<WorkloadDailyItemResponse> DailyMetrics { get; init; }

View File

@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using PARR.API.Contracts.V1; using PARR.API.Contracts.V1;
using PARR.API.Contracts.V1.Requests.BaseRequests; 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.Base;
using PARR.API.Contracts.V1.Responses.Statistics; using PARR.API.Contracts.V1.Responses.Statistics;
using PARR.API.Controllers.V1.Base; using PARR.API.Controllers.V1.Base;
@@ -45,7 +46,14 @@ namespace PARR.API.Controllers.V1.Statistics
/// <returns></returns> /// <returns></returns>
/// <exception cref="ArgumentException"></exception> /// <exception cref="ArgumentException"></exception>
[HttpGet(ApiRoutes.StatWorkloadTemplate.GetWorkloadTemplateReport)] [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)) if (string.IsNullOrWhiteSpace(filter))
{ {
@@ -53,7 +61,13 @@ namespace PARR.API.Controllers.V1.Statistics
throw new ArgumentException(nameof(filter), "Не передано значение фильтра."); 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); 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.Interfaces;
using PARR.Core.Services.Workload.Models; using PARR.Core.Services.Workload.Models;
using PARR.Domain.DTOs.Workload; using PARR.Domain.DTOs.Workload;
using PARR.Domain.Entities;
using PARR.Domain.Enums; using PARR.Domain.Enums;
using PARR.Domain.Enums.Workload; using PARR.Domain.Enums.Workload;
@@ -55,7 +56,6 @@ namespace PARR.Core.Services.Workload.Implementations
throw new ArgumentNullException(nameof(filterParam), $"Для отчета типа {reportType.ToString()} параметр является обязательным."); throw new ArgumentNullException(nameof(filterParam), $"Для отчета типа {reportType.ToString()} параметр является обязательным.");
} }
// Если есть отчет в КЭШ, вернуть // Если есть отчет в КЭШ, вернуть
var cacheReport = await workloadCacheService.GetWorkloadReportAsync(reportType, dateStart, durationDays, offset, filterParam); var cacheReport = await workloadCacheService.GetWorkloadReportAsync(reportType, dateStart, durationDays, offset, filterParam);
if (cacheReport != null) if (cacheReport != null)
@@ -87,7 +87,7 @@ namespace PARR.Core.Services.Workload.Implementations
case WorkloadReportType.ResponseAreaAll: case WorkloadReportType.ResponseAreaAll:
displayName = "По зонам ответственности"; displayName = "По зонам ответственности";
foreach (var group in templates.GroupBy(t => t.ResponseArea, StringComparer.OrdinalIgnoreCase)) 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; break;
case WorkloadReportType.ResponseAreaWorkGroups: case WorkloadReportType.ResponseAreaWorkGroups:
displayName = "По зоне ответственности"; displayName = "По зоне ответственности";
@@ -95,13 +95,13 @@ namespace PARR.Core.Services.Workload.Implementations
var filteredRA = templates.Where(t => string.Equals(t.ResponseArea, filterParam, StringComparison.OrdinalIgnoreCase)).ToList(); var filteredRA = templates.Where(t => string.Equals(t.ResponseArea, filterParam, StringComparison.OrdinalIgnoreCase)).ToList();
// Группируем работы по РГ // Группируем работы по РГ
foreach (var group in filteredRA.GroupBy(t => t.WorkGroup, StringComparer.OrdinalIgnoreCase)) 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)); //statistics.Add(BuildStatisticItem(filterParam!, filteredRA, reportDays, offset));
break; break;
case WorkloadReportType.WorkGroupAll: case WorkloadReportType.WorkGroupAll:
displayName = "По рабочим группам"; displayName = "По рабочим группам";
foreach (var group in templates.GroupBy(t => t.WorkGroup)) 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; break;
case WorkloadReportType.WorkGroupJob: case WorkloadReportType.WorkGroupJob:
displayName = "По рабочей группе"; displayName = "По рабочей группе";
@@ -118,7 +118,7 @@ namespace PARR.Core.Services.Workload.Implementations
foreach (var group in filteredWG.GroupBy(t => t.JobId)) foreach (var group in filteredWG.GroupBy(t => t.JobId))
{ {
var jobName = jobs.GetValueOrDefault(group.Key) ?? "Работа"; var jobName = jobs.GetValueOrDefault(group.Key) ?? "Работа";
statistics.Add(BuildStatisticItem(jobName, group, reportDays, offset)); statistics.Add(BuildStatisticItem(jobName, group.Key, group, reportDays, offset));
} }
break; 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)) if (string.IsNullOrWhiteSpace(filter))
{ {
@@ -218,19 +226,16 @@ namespace PARR.Core.Services.Workload.Implementations
break; break;
} }
// Если тип - работы // Если тип - работы, получим из бд сразу нужные работы
if (reportType == WorkloadTemplateReportType.Job) if (reportType == WorkloadTemplateReportType.Job)
{ {
if (!Guid.TryParse(trimmedFilter, out Guid jobId)) if (!Guid.TryParse(trimmedFilter, out Guid jobId))
throw new ArgumentException("Значение фильтра не является Guid"); throw new ArgumentException("Значение фильтра не является Guid");
return await query.Where(t => t.JobId == jobId) query = 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();
} }
// Тип "не работы", загружаем шаблоны // Загружаем шаблоны
var dbTemplates = await query.OrderBy(t => t.Name) var dbTemplates = await query.OrderBy(t => t.Name)
.Select(t => new WorkloadTemplateReport(t.Id, t.Name, t.NextRun, t.IsActiveTemplate, t.IsActiveSchedule)) .Select(t => new WorkloadTemplateReport(t.Id, t.Name, t.NextRun, t.IsActiveTemplate, t.IsActiveSchedule))
.ToListAsync(); .ToListAsync();
@@ -238,6 +243,13 @@ namespace PARR.Core.Services.Workload.Implementations
if (dbTemplates.Count == 0) if (dbTemplates.Count == 0)
return new List<WorkloadTemplateReport>(); 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()); var cacheData = await workloadCacheService.GetTemplateReportDataByIdsAsync(dbTemplates.Select(t => t.TemplateId).ToHashSet());
@@ -250,6 +262,30 @@ namespace PARR.Core.Services.Workload.Implementations
// Фильтр по типу // Фильтр по типу
switch (reportType) 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: case WorkloadTemplateReportType.WorkGroup:
var cacheTemplatesWg = cacheData.Where(t => string.Equals(t.Data.WorkGroup, trimmedFilter, StringComparison.OrdinalIgnoreCase)) var cacheTemplatesWg = cacheData.Where(t => string.Equals(t.Data.WorkGroup, trimmedFilter, StringComparison.OrdinalIgnoreCase))
.Select(t => t.Data.TemplateId).ToHashSet(); .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>
/// Получить список дней для отчета, с отметкой выходной/рабочий /// Получить список дней для отчета, с отметкой выходной/рабочий
/// </summary> /// </summary>
@@ -347,12 +393,12 @@ namespace PARR.Core.Services.Workload.Implementations
/// <param name="reportDays"></param> /// <param name="reportDays"></param>
/// <param name="offset"></param> /// <param name="offset"></param>
/// <returns></returns> /// <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 dailyMetrics = GetDailyMetrics(groupTemplates.ToList(), reportDays, offset);
var summary = GetSummary(dailyMetrics, reportDays.Count); 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="state"></param>
/// <param name="date"></param> /// <param name="date"></param>
/// <param name="offset"></param> /// <param name="offset"></param>
/// <param name="jobSubfilterType">Дополнительный фильтр при отчете по шаблонам, тип</param>
/// <param name="jobSubfilterValue">Дополнительный фильтр при отчете по шаблонам, значение</param>
/// <returns></returns> /// <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; } public required string Title { get; init; }
/// <summary>
/// ИД строки, если есть.
/// Например JobId
/// </summary>
public Guid? ObjId { get; init; }
public required WorkloadSummary Summary { get; init; } public required WorkloadSummary Summary { get; init; }
public required List<WorkloadDailyItem> DailyMetrics { 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
}
}