using AutoMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using PARR.API.Contracts.V1; using PARR.API.Contracts.V1.Requests.BaseRequests; using PARR.API.Contracts.V1.Responses; using PARR.API.Contracts.V1.Responses.Base; using PARR.API.Contracts.V1.Responses.Statistics; using PARR.API.Controllers.V1.Base; using PARR.Constants; using PARR.DAL.DomainServices.Shortcodes; using PARR.DAL.Models; using PARR.DAL.Models.Job; using PARR.DAL.NextRunServices; using PARR.DAL.Services.Interfaces; using PARR.DAL.Services.Interfaces.Job; namespace PARR.API.Controllers.V1.Statistics { /// /// Статистика распределения шаблонов /// [Authorize(Roles = ParrRoles.Administrator.Role)] public class StatTemplateDistributionController : BaseApiController { private readonly ITemplateService templateService; private readonly IJobGroupService jobGroupService; private readonly IMapper mapper; private readonly IShortcodesService shortcodesService; private readonly INextRunService nextRunService; private readonly ILogger logger; public StatTemplateDistributionController( ITemplateService templateService, IJobGroupService jobGroupService, IMapper mapper, IShortcodesService shortcodesService, INextRunService nextRunService, ILogger logger ) { this.templateService = templateService; this.jobGroupService = jobGroupService; this.mapper = mapper; this.shortcodesService = shortcodesService; this.nextRunService = nextRunService; this.logger = logger; } /// /// Статистика распределения шаблонов по jobGroupId, только для Used /// /// /// [HttpGet(ApiRoutes.StatTemplateDistribution.Get)] public async Task Get([FromRoute] Guid jobGroupId, [FromQuery] TimeZoneOffsetClient timeZoneQuery) { // eadc5498-dba6-4f10-9b4b-a1653e3c3e61 //TODO: похоже что timeZoneQuery лишняя var jobGroup = await jobGroupService.Get() .Include(t => t.DistributionConfig) .ThenInclude(t => t.DistributionPeriod) .AsNoTracking() .FirstOrDefaultAsync(t => t.Id == jobGroupId); if (jobGroup == null) return BadRequest(new Response(false, new List { new ErrorModel { Message = "Не найдена группа работ" } })); if (!jobGroup.IsAutoDistributionEnabled || jobGroup.DistributionConfig == null) return BadRequest(new Response(false, new List { new ErrorModel { Message = "Для группы работ не включено автораспределение" } })); var items = await templateService.Get() .Where(t => t.Job!.GroupId == jobGroupId && t.StatusTypeId == TemplateStatusTypeEnum.Used) .Select(t => new { WorkGroup = t.Job!.WorkGroupMask, Template = t }) .AsNoTracking() .ToListAsync(); if (!items.Any()) return Ok(new Response(new StatTemplateDistributorResponse { JobGroup = mapper.Map(jobGroup), ItemsList = null }, true)); var (dateStart, dateEnd) = GetStartEndPeriod(jobGroup, items.Max(t => t.Template.NextRun), timeZoneQuery.TimeZoneOffsetHours); // получаем список рабочих дней // получет все дни, даже с выходными, чтоб видеть реальную картину var workDays = await nextRunService.GetWorkDaysAsync(dateStart, dateEnd, false, jobGroup.ReferenceDate); // получить список выходных дней (показываем только если IsExcludeWeekends = true) var weekends = jobGroup.DistributionConfig.IsExcludeWeekends ? await nextRunService.GetWeekendsAsync(dateStart, dateEnd, jobGroup.ReferenceDate) : new HashSet(); var responseItemsList = new List(); if (jobGroup.DistributionConfig.IsGroupingByWorkGroup) { // тут группируем по рабочим группам // Получаем список пар (WorkGroupName, Template) var templatesWithWorkGroupNames = new List<(string WorkGroupName, Template Template)>(); // получаем названия рабочих групп foreach (var item in items) { var workGroupName = await shortcodesService.ApplyShortcodesAsync(item.WorkGroup, item.Template); templatesWithWorkGroupNames.Add((workGroupName, item.Template)); } // Группируем по WorkGroupName var grouped = templatesWithWorkGroupNames .GroupBy(x => x.WorkGroupName) .ToDictionary(g => g.Key, g => g.Select(x => x.Template).ToList()); foreach (var groupedItem in grouped) { var statResult = GetResponseByWorkGroup(groupedItem.Key, groupedItem.Value, workDays, weekends, timeZoneQuery.TimeZoneOffsetHours, jobGroup.ReferenceDate); responseItemsList.Add(statResult); } } else { // не нужно группировать по рабочим группам // сразу формируем response responseItemsList.Add(GetResponseByWorkGroup(null, items.Select(t => t.Template).ToList(), workDays, weekends, timeZoneQuery.TimeZoneOffsetHours, jobGroup.ReferenceDate)); } var response = new StatTemplateDistributorResponse { JobGroup = mapper.Map(jobGroup), ItemsList = responseItemsList.OrderBy(t => t.WorkGroupName).ToList() }; return Ok(new Response(response, true)); } /// /// Получить дату начала и конца периода /// /// /// /// /// private (DateOnly dateStart, DateOnly dateEnd) GetStartEndPeriod(JobGroup jobGroup, DateTimeOffset maxNextRunTemplate, int timeZoneOffsetHours) { var durationDays = nextRunService.GetDurationDays(jobGroup.DistributionConfig!); var dateStart = DateOnly.FromDateTime(DateTime.UtcNow.AddHours(timeZoneOffsetHours)); // последний день, не может быть меньше чем durationDays var dateEnd = dateStart.AddDays(durationDays); // последний день, не может быть меньше чем refDate+durationDays var sumRefDuration = DateOnly.FromDateTime(jobGroup.ReferenceDate.AddHours(timeZoneOffsetHours).AddDays(durationDays).Date); if (dateEnd < sumRefDuration) dateEnd = sumRefDuration; // с DateEnd вообще какая-то шурпатня :( // как наглядно понять ок не ок распределяется, если на каком-то графике dateEnd может ухеать за период распределения // последний день, не может быть меньше чем дата последнего шаблона maxNextRunTemplate var lastTempalteDate = DateOnly.FromDateTime(maxNextRunTemplate.AddHours(timeZoneOffsetHours).Date); if (dateEnd < lastTempalteDate) dateEnd = lastTempalteDate; return (dateStart, dateEnd); } /// /// Заполнить респонс /// /// /// /// /// /// private StatDistributorItemResponse GetResponseByWorkGroup(string? workGroupName, List