diff --git a/PARR.API/Contracts/V1/ApiRoutes.cs b/PARR.API/Contracts/V1/ApiRoutes.cs index 594ad678..3108d749 100644 --- a/PARR.API/Contracts/V1/ApiRoutes.cs +++ b/PARR.API/Contracts/V1/ApiRoutes.cs @@ -299,6 +299,10 @@ public static class Workload { public const string Build = BaseStat + "/workload/build"; + + public const string WorkGroup = BaseStat + "/workload/work-groups"; + + public const string ResponseArea = BaseStat + "/workload/response-areas"; } #endregion diff --git a/PARR.API/Contracts/V1/Requests/BaseRequests/TimeZoneOffsetClient.cs b/PARR.API/Contracts/V1/Requests/BaseRequests/TimeZoneOffsetClient.cs index cead2206..c3cfc1ee 100644 --- a/PARR.API/Contracts/V1/Requests/BaseRequests/TimeZoneOffsetClient.cs +++ b/PARR.API/Contracts/V1/Requests/BaseRequests/TimeZoneOffsetClient.cs @@ -7,10 +7,10 @@ namespace PARR.API.Contracts.V1.Requests.BaseRequests /// public class TimeZoneOffsetClient { - /// - /// Удалить TimeZoneOffsetHours и не использовать!!!!!! Использовать только в минутах TimeZoneOffsetMinutes - /// - public int TimeZoneOffsetHours { get; set; } = 0; + ///// + ///// Удалить TimeZoneOffsetHours и не использовать!!!!!! Использовать только в минутах TimeZoneOffsetMinutes + ///// + //public int TimeZoneOffsetHours { get; set; } = 0; /// /// Часовая зона клиента, в минутах diff --git a/PARR.API/Contracts/V1/Requests/Queries/WorkloadBaseQuery.cs b/PARR.API/Contracts/V1/Requests/Queries/WorkloadBaseQuery.cs index 30f7a355..33e01e8e 100644 --- a/PARR.API/Contracts/V1/Requests/Queries/WorkloadBaseQuery.cs +++ b/PARR.API/Contracts/V1/Requests/Queries/WorkloadBaseQuery.cs @@ -1,18 +1,13 @@ -using PARR.API.Contracts.V1.Requests.BaseRequests; - -namespace PARR.API.Contracts.V1.Requests.Queries +namespace PARR.API.Contracts.V1.Requests.Queries { /// /// Базовый запрос по загрузке по ЗО, РГ... /// - public class WorkloadBaseQuery: TimeZoneOffsetClient + public class WorkloadBaseQuery { - public bool? IsActiveTemplate { get; set; } - - public bool? IsActiveSchedule { get; set; } - - public bool? IsMonth { get; set; } - - //public int TimeZoneOffsetHours { get; set; } = 0; + /// + /// Период отчетности (по умолчанию 7 дней) + /// + public int DurationDays { get; set; } = 7; } } diff --git a/PARR.API/Contracts/V1/Responses/Statistics/StatResponseAreaWorkLoadResponse.cs b/PARR.API/Contracts/V1/Responses/Statistics/StatResponseAreaWorkLoadResponse.cs deleted file mode 100644 index 5df79513..00000000 --- a/PARR.API/Contracts/V1/Responses/Statistics/StatResponseAreaWorkLoadResponse.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace PARR.API.Contracts.V1.Responses.Statistics -{ - public class StatResponseAreaWorkLoadResponse : StatWorkLoadDataBaseResponse - { - //public required ResponseAreaResponse ResponseArea { get; set; } - } -} diff --git a/PARR.API/Contracts/V1/Responses/Statistics/StatWorkGroupWorkLoadResponse.cs b/PARR.API/Contracts/V1/Responses/Statistics/StatWorkGroupWorkLoadResponse.cs deleted file mode 100644 index 9cf20874..00000000 --- a/PARR.API/Contracts/V1/Responses/Statistics/StatWorkGroupWorkLoadResponse.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace PARR.API.Contracts.V1.Responses.Statistics -{ - public class StatWorkGroupWorkLoadResponse : StatWorkLoadDataBaseResponse - { - //public required WorkGroupResponse WorkGroup { get; set; } - } -} diff --git a/PARR.API/Contracts/V1/Responses/Statistics/StatWorkLoadDataBaseResponse.cs b/PARR.API/Contracts/V1/Responses/Statistics/StatWorkLoadDataBaseResponse.cs deleted file mode 100644 index 7644388f..00000000 --- a/PARR.API/Contracts/V1/Responses/Statistics/StatWorkLoadDataBaseResponse.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace PARR.API.Contracts.V1.Responses.Statistics -{ - public class StatWorkLoadDataBaseResponse - { - public List WorkLoad { get; set; } - - public int WorksCount => WorkLoad?.Sum(t => t.TemplateCount) ?? 0; - - public double AverageWorksCount - { - get - { - int divider = WorkLoad.Where(t => t.TemplateCount > 0).Count(); - if (divider > 0) - return Math.Round((double)WorksCount / divider, 2); - - return 0; - } - }//=> Math.Round((double)WorksCount / WorkLoad.Where(t => t.TemplateCount > 0).Count(), 2); - } - - public class StatWorkLoadDataResponse - { - //public DateTimeOffset Date { get; set; } - public DateOnly Date { get; set; } - public int TemplateCount { get; set; } - } -} diff --git a/PARR.API/Contracts/V1/Responses/Statistics/StatWorkWorkLoadResponse.cs b/PARR.API/Contracts/V1/Responses/Statistics/StatWorkWorkLoadResponse.cs deleted file mode 100644 index 9764fc66..00000000 --- a/PARR.API/Contracts/V1/Responses/Statistics/StatWorkWorkLoadResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace PARR.API.Contracts.V1.Responses.Statistics -{ - public class StatWorkWorkLoadResponse : StatWorkLoadDataBaseResponse - { - //public required ApplicationInWorkBaseResponse Job { get; set; } - - //public required WorkGroupResponse WorkGroup { get; set; } - } -} diff --git a/PARR.API/Contracts/V1/Responses/Statistics/StatWorkloadReportResponse.cs b/PARR.API/Contracts/V1/Responses/Statistics/StatWorkloadReportResponse.cs new file mode 100644 index 00000000..c1dc7b4b --- /dev/null +++ b/PARR.API/Contracts/V1/Responses/Statistics/StatWorkloadReportResponse.cs @@ -0,0 +1,90 @@ +using PARR.Domain.Enums; + +namespace PARR.API.Contracts.V1.Responses.Statistics +{ + /// + /// Response отчета о загруженности + /// + public record StatWorkloadReportResponse + { + /// + /// Тип отчета (все ЗО, все РГ, одна ЗО, одна РГ) + /// + public WorkloadReportType Type { get; init; } + + /// + /// Название отчета (По всем ЗО, по одной ЗО) + /// + public required string DisplayName { get; init; } + + /// + /// Подзаголовок, наименование конкретной ЗО/РГ + /// + public string? SubfilterName { get; init; } + + /// + /// Дата формирования отчета + /// + public DateTimeOffset ReportCacheCreatedAt { get; init; } + + /// + /// Дата формирования кэша ЗО и РГ + /// + public DateTimeOffset TemplateCacheCreatedAt { get; init; } + + /// + /// Список дней + /// + public required List Days { get; init; } + + /// + /// Нагруженность + /// + public required List Statistics { get; init; } + } + + public record WorkloadSummaryResponse + { + public required WorkloadSummaryItemResponse Total { get; init; } + + public required WorkloadSummaryItemResponse Activated { get; init; } + + public required WorkloadSummaryItemResponse Deactivated { get; init; } + } + + public record WorkloadSummaryItemResponse + { + public int Count { get; init; } + + public double Average { get; init; } + } + + public record WorkloadDayItemResponse + { + public DateOnly Date { get; init; } + + public bool IsWeekend { get; init; } + } + + public record WorkloadStatisticItemResponse + { + public required string Title { get; init; } + + public required WorkloadSummaryResponse Summary { get; init; } + + public required List DailyMetrics { get; init; } + } + + public record WorkloadDailyItemResponse + { + 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; } + } +} diff --git a/PARR.API/Controllers/V1/Statistics/StatWorkloadController.cs b/PARR.API/Controllers/V1/Statistics/StatWorkloadController.cs index d75a9cd1..0da2dea9 100644 --- a/PARR.API/Controllers/V1/Statistics/StatWorkloadController.cs +++ b/PARR.API/Controllers/V1/Statistics/StatWorkloadController.cs @@ -1,12 +1,17 @@ -using Microsoft.AspNetCore.Authorization; +using AutoMapper; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using NodaTime; 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; using PARR.API.Services.Interfaces; -using PARR.API.Settings; using PARR.Core.Services.TaskServices.Interfaces; using PARR.Core.Services.TaskServices.Providers; +using PARR.Core.Services.Workload.Interfaces; using PARR.Domain.Common.Roles; using PARR.Domain.Entities.Base.History; using PARR.Domain.Enums; @@ -21,26 +26,53 @@ namespace PARR.API.Controllers.V1.Statistics { private readonly ITaskManagementService taskManagementService; private readonly IClientService clientService; - //private readonly MqSettings mqSettings; private readonly ILogger logger; private readonly ITaskMqSettingsProvider taskMqSettingsProvider; + private readonly IWorkloadService workloadService; + private readonly IMapper mapper; public StatWorkloadController( ITaskManagementService taskManagementService, IClientService clientService, - //MqSettings mqSettings, ILogger logger, - ITaskMqSettingsProvider taskMqSettingsProvider + ITaskMqSettingsProvider taskMqSettingsProvider, + IWorkloadService workloadService, + IMapper mapper ) { this.taskManagementService = taskManagementService; this.clientService = clientService; - //this.mqSettings = mqSettings; this.logger = logger; this.taskMqSettingsProvider = taskMqSettingsProvider; + this.workloadService = workloadService; + this.mapper = mapper; } + /// + /// Отчет о загруженности по всем РГ + /// + /// + [HttpGet(ApiRoutes.Workload.WorkGroup)] + public async Task GetWorkGroupReport([FromQuery] TimeZoneOffsetClient offset, [FromQuery] WorkloadBaseQuery request) + { + return await GetReportAsync(WorkloadReportType.WorkGroupAll, request.DurationDays, offset.TimeZoneOffset, null); + } + + + /// + /// Отчет о загруженности по всем ЗО + /// + /// + [HttpGet(ApiRoutes.Workload.ResponseArea)] + public async Task GetResponseAreaReport([FromQuery] TimeZoneOffsetClient offset, [FromQuery] WorkloadBaseQuery request) + { + return await GetReportAsync(WorkloadReportType.ResponseAreaAll, request.DurationDays, offset.TimeZoneOffset, null); + } + + + + /// /// Сформировать отчетность /// @@ -79,5 +111,26 @@ namespace PARR.API.Controllers.V1.Statistics } + /// + /// Универсальный метод получения отчетности + /// + /// + private async Task GetReportAsync(WorkloadReportType reportType, int durationDays, TimeSpan offset, string? filter) + { + var utcNow = DateTimeOffset.UtcNow; + var localTime = utcNow.ToOffset(offset); + var dateStart = DateOnly.FromDateTime(localTime.Date); + + var report = await workloadService.GetWorkloadReport(reportType, dateStart, durationDays, offset, filter); + + if (report == null) + return BadRequest(new Response(false, new List { new ErrorModel { Message = "Необходимо сформировать отчетность." } })); + + var resonse = mapper.Map(report); + + return Ok(new Response(resonse, true)); + } + + } } diff --git a/PARR.API/MappingProfiles/DomainToResponseProfile.cs b/PARR.API/MappingProfiles/DomainToResponseProfile.cs index 42e4c730..e6847037 100644 --- a/PARR.API/MappingProfiles/DomainToResponseProfile.cs +++ b/PARR.API/MappingProfiles/DomainToResponseProfile.cs @@ -1,10 +1,12 @@ using AutoMapper; using PARR.API.Authentication.Models; using PARR.API.Contracts.V1.Responses; +using PARR.API.Contracts.V1.Responses.Statistics; using PARR.API.MappingProfiles.Resolvers; using PARR.Core.Repositories.Interfaces.Schedule; using PARR.Domain.DTOs.Matching; using PARR.Domain.DTOs.Shortcode; +using PARR.Domain.DTOs.Workload; using PARR.Domain.Entities; using PARR.Domain.Entities.Base.History; using PARR.Domain.Entities.Job; @@ -425,6 +427,24 @@ namespace PARR.API.MappingProfiles .ForMember(d => d.DetailsJobs, o => o.MapFrom(s => s.DetailsJobs != null ? s.DetailsJobs.Select(t => t.Data) : null)); #endregion + + + #region StatWorkloadReportResponse + + CreateMap(); + + CreateMap(); + + CreateMap(); + + CreateMap(); + + CreateMap(); + + CreateMap(); + + #endregion + } } diff --git a/PARR.Core/Services/Workload/Implementations/WorkloadCacheService.cs b/PARR.Core/Services/Workload/Implementations/WorkloadCacheService.cs index 44d8f582..e327e39f 100644 --- a/PARR.Core/Services/Workload/Implementations/WorkloadCacheService.cs +++ b/PARR.Core/Services/Workload/Implementations/WorkloadCacheService.cs @@ -110,7 +110,7 @@ namespace PARR.Core.Services.Workload.Implementations var cacheItem = new TemplateCache { - Data = new TemplateCacheDto { TemplateId = template.Id, ResponseArea = responseArea, WorkGroup = workGroup }, + Data = new TemplateCacheDto { TemplateId = template.Id, ResponseArea = responseArea, WorkGroup = workGroup, JobGroupId = template.Job!.GroupId, JobId = template.JobId }, Source = nameof(WorkloadCacheService), Timestamp = DateTimeOffset.UtcNow }; @@ -193,11 +193,15 @@ namespace PARR.Core.Services.Workload.Implementations var dtStart = new DateTimeOffset(dateStart.Year, dateStart.Month, dateStart.Day, 0, 0, 0, offset); var dtEnd = dtStart.AddDays(durationDays); + // Конвертируем в UTC для совместимости с PostgreSQL timestamptz + var dtStartUtc = dtStart.ToUniversalTime(); + var dtEndUtc = dtEnd.ToUniversalTime(); + 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() .AsNoTracking() - .Where(t => dtStart <= t.NextRun && t.NextRun < dtEnd) + .Where(t => dtStartUtc <= t.NextRun && t.NextRun < dtEndUtc) .Select(t => new { Id = t.Id, IsActiveTemplate = t.IsActiveTemplate, IsActiveSchedule = t.IsActiveSchedule, Name = t.Name, NextRun = t.NextRun }) .ToListAsync(); @@ -231,7 +235,7 @@ namespace PARR.Core.Services.Workload.Implementations template.NextRun, template.IsActiveTemplate, template.IsActiveSchedule, - DateTimeOffset.UtcNow); + cacheDate); reportData.Add(data); } @@ -266,7 +270,7 @@ namespace PARR.Core.Services.Workload.Implementations // Запишем результат в кэш var cacheItem = new TemplateCache { - Data = new TemplateCacheDto { TemplateId = template.Id, ResponseArea = responseArea, WorkGroup = workGroup }, + Data = new TemplateCacheDto { TemplateId = template.Id, ResponseArea = responseArea, WorkGroup = workGroup, JobId = template.JobId, JobGroupId = template.Job!.GroupId }, Source = nameof(WorkloadCacheService), Timestamp = DateTimeOffset.UtcNow }; diff --git a/PARR.Core/Services/Workload/Implementations/WorkloadService.cs b/PARR.Core/Services/Workload/Implementations/WorkloadService.cs index 61068db1..0471e332 100644 --- a/PARR.Core/Services/Workload/Implementations/WorkloadService.cs +++ b/PARR.Core/Services/Workload/Implementations/WorkloadService.cs @@ -72,6 +72,7 @@ namespace PARR.Core.Services.Workload.Implementations break; case WorkloadReportType.ResponseAreaItem: displayName = "По зоне ответственности"; + //todo: тут нужно вернуть другой отчет, либо группы работ, либо список работ var filteredRA = templates.Where(t => t.ResponseArea.Equals(filterParam, StringComparison.OrdinalIgnoreCase)).ToList(); statistics.Add(BuildStatisticItem(filterParam!, filteredRA, reportDays, offset)); break; @@ -82,6 +83,7 @@ namespace PARR.Core.Services.Workload.Implementations break; case WorkloadReportType.WorkGroupItem: displayName = "По рабочей группе"; + //todo: тут нужно вернуть другой отчет, либо группы работ, либо список работ var filteredWG = templates.Where(t => t.WorkGroup.Equals(filterParam, StringComparison.OrdinalIgnoreCase)).ToList(); statistics.Add(BuildStatisticItem(filterParam!, filteredWG, reportDays, offset)); break; @@ -95,7 +97,7 @@ namespace PARR.Core.Services.Workload.Implementations ReportCacheCreatedAt = DateTimeOffset.UtcNow, TemplateCacheCreatedAt = templates.FirstOrDefault()?.CacheDate ?? DateTimeOffset.MinValue, Days = reportDays, - Statistics = statistics + Statistics = statistics.OrderBy(t => t.Title).ToList() }; // Сохраним в кэш diff --git a/PARR.Core/Services/Workload/Interfaces/IWorkloadService.cs b/PARR.Core/Services/Workload/Interfaces/IWorkloadService.cs index f94c2fa9..aeb775dd 100644 --- a/PARR.Core/Services/Workload/Interfaces/IWorkloadService.cs +++ b/PARR.Core/Services/Workload/Interfaces/IWorkloadService.cs @@ -1,9 +1,22 @@ -namespace PARR.Core.Services.Workload.Interfaces +using PARR.Domain.DTOs.Workload; +using PARR.Domain.Enums; + +namespace PARR.Core.Services.Workload.Interfaces { /// /// Сервис формирования отчетности о загруженности /// public interface IWorkloadService { + /// + /// Получить отчет о загруженности + /// + /// + /// + /// + /// + /// + /// + Task GetWorkloadReport(WorkloadReportType reportType, DateOnly dateStart, int durationDays, TimeSpan offset, string? filterParam); } } diff --git a/PARR.Domain/Cache/Models/TemplateCache.cs b/PARR.Domain/Cache/Models/TemplateCache.cs index c6b916fd..a0e4ec00 100644 --- a/PARR.Domain/Cache/Models/TemplateCache.cs +++ b/PARR.Domain/Cache/Models/TemplateCache.cs @@ -18,5 +18,7 @@ namespace PARR.Domain.Cache.Models public Guid TemplateId { get; init; } public required string WorkGroup { get; init; } public required string ResponseArea { get; init; } + public Guid JobId { get; init; } + public Guid JobGroupId { get; init; } } }