Files
parr_api/PARR.API/Controllers/V1/Statistics/StatTemplateDistributionController.cs

236 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
{
/// <summary>
/// Статистика распределения шаблонов
/// </summary>
[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<StatTemplateDistributionController> logger;
public StatTemplateDistributionController(
ITemplateService templateService,
IJobGroupService jobGroupService,
IMapper mapper,
IShortcodesService shortcodesService,
INextRunService nextRunService,
ILogger<StatTemplateDistributionController> logger
)
{
this.templateService = templateService;
this.jobGroupService = jobGroupService;
this.mapper = mapper;
this.shortcodesService = shortcodesService;
this.nextRunService = nextRunService;
this.logger = logger;
}
/// <summary>
/// Статистика распределения шаблонов по jobGroupId, только для Used
/// </summary>
/// <param name="jobGroupId"></param>
/// <returns></returns>
[HttpGet(ApiRoutes.StatTemplateDistribution.Get)]
public async Task<IActionResult> 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<ErrorModel> { new ErrorModel { Message = "Не найдена группа работ" } }));
if (!jobGroup.IsAutoDistributionEnabled || jobGroup.DistributionConfig == null)
return BadRequest(new Response(false, new List<ErrorModel> { 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<StatTemplateDistributorResponse>(new StatTemplateDistributorResponse
{
JobGroup = mapper.Map<JobGroupWithDistributionConfigResponse>(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<DateOnly>();
var responseItemsList = new List<StatDistributorItemResponse>();
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<JobGroupWithDistributionConfigResponse>(jobGroup),
ItemsList = responseItemsList.OrderBy(t => t.WorkGroupName).ToList()
};
return Ok(new Response<StatTemplateDistributorResponse>(response, true));
}
/// <summary>
/// Получить дату начала и конца периода
/// </summary>
/// <param name="jobGroup"></param>
/// <param name="maxNextRunTemplate"></param>
/// <param name="timeZoneOffsetHours"></param>
/// <returns></returns>
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);
}
/// <summary>
/// Заполнить респонс
/// </summary>
/// <param name="workGroupName"></param>
/// <param name="templates"></param>
/// <param name="workDays"></param>
/// <param name="timeZoneOffsetHours"></param>
/// <returns></returns>
private StatDistributorItemResponse GetResponseByWorkGroup(string? workGroupName, List<Template> templates, List<DateOnly> workDays, HashSet<DateOnly> weekends, int timeZoneOffsetHours, DateTimeOffset referenceDate)
{
var result = new StatDistributorItemResponse
{
WorkGroupName = workGroupName,
Statistics = workDays.OrderBy(t => t).Select(t => new StatTemplateDistributorDateStat
{
AllCount = 0,
Date = new DateTimeOffset(t.Year, t.Month, t.Day, referenceDate.Hour, referenceDate.Minute, referenceDate.Second, referenceDate.Offset),
IsWorkDay = !weekends.Contains(t),
IsActivatedCount = 0,
IsDeactivatedCount = 0,
Templates = new List<StatTempleteDistribItem>()
}).ToList()
};
//foreach (var templatesFromDate in templates.GroupBy(t => DateOnly.FromDateTime(t.NextRun.Date)))
// смещаем по часовой зоне и группируем по дате
foreach (var templatesFromDate in templates.GroupBy(t => DateOnly.FromDateTime(t.NextRun.AddHours(timeZoneOffsetHours).Date)))
{
var statisticsForDate = result.Statistics.FirstOrDefault(t => DateOnly.FromDateTime(t.Date.AddHours(timeZoneOffsetHours).Date) == templatesFromDate.Key);
if (statisticsForDate == null)
{
logger.LogWarning("В списке рабочих дней, нет дня для шаблонов {count} шт. выполняющихся в {date}", templatesFromDate.Count(), templatesFromDate.Key);
continue;
}
statisticsForDate.AllCount = templatesFromDate.Count();
statisticsForDate.IsDeactivatedCount = templatesFromDate.Count(t => !t.IsActiveTemplate || !t.IsActiveSchedule);
statisticsForDate.IsActivatedCount = templatesFromDate.Count(t => t.IsActiveTemplate && t.IsActiveSchedule);
statisticsForDate.Templates = templatesFromDate.Select(t => new StatTempleteDistribItem
{
Name = t.Name,
IsActiveSchedular = t.IsActiveSchedule,
IsActiveTemplate = t.IsActiveTemplate,
NextRun = t.NextRun
})
.OrderBy(t => t.Name)
.ToList();
}
return result;
}
}
}