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

275 lines
14 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;
using PARR.DAL.Services.Interfaces.Schedule;
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 INextRunServiceV2 nextRunService;
private readonly ILogger<StatTemplateDistributionController> logger;
private readonly IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService;
public StatTemplateDistributionController(
ITemplateService templateService,
IJobGroupService jobGroupService,
IMapper mapper,
IShortcodesService shortcodesService,
INextRunServiceV2 nextRunService,
ILogger<StatTemplateDistributionController> logger,
IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService
)
{
this.templateService = templateService;
this.jobGroupService = jobGroupService;
this.mapper = mapper;
this.shortcodesService = shortcodesService;
this.nextRunService = nextRunService;
this.logger = logger;
this.scheduleResponseAreaTimeOffsetService = scheduleResponseAreaTimeOffsetService;
}
/// <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
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
{
WorkGroupMask = t.Job!.WorkGroupMask,
ResponseAreaMask = t.Job!.ResponseAreaMask,
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.Min(t => t.Template.NextRun), items.Max(t => t.Template.NextRun), timeZoneQuery.TimeZoneOffset);
// получаем список рабочих дней
// получет все дни, даже с выходными, чтоб видеть реальную картину
var allDays = await nextRunService.GetWorkDaysAsync(dateStart, dateEnd, false);
// получить список выходных дней (показываем только если IsExcludeWeekends = true)
var weekends = jobGroup.DistributionConfig.IsExcludeWeekends
? await nextRunService.GetWeekendsAsync(dateStart, dateEnd)
: new HashSet<DateOnly>();
var responseItemsList = new List<StatDistributorItemResponse>();
if (jobGroup.DistributionConfig.IsGroupingByWorkGroup)
{
// тут группируем по рабочим группам
logger.LogDebug("Группируем по рабочим группам");
// Получаем список пар (WorkGroupName, Template)
var templatesWithWorkGroupNames = new List<(string WorkGroupName, Template Template)>();
// получаем названия рабочих групп
foreach (var item in items)
{
var workGroupName = await shortcodesService.ApplyShortcodesAsync(item.WorkGroupMask, 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.ToList(), allDays, weekends, timeZoneQuery.TimeZoneOffset);
responseItemsList.Add(statResult);
}
}
else if (jobGroup.IsResponseAreaTimezone && !jobGroup.DistributionConfig.IsGroupingByWorkGroup)
{
// грппируем только по ЗО
logger.LogDebug("Группируем по ЗО");
// Будет группироваться по EsppValue, по MSK, MSK+1...
// получаем названия ЗО, из ЗО часовой пояс, формируем список шаблонов с часовым поясом ЗО
var templatesWithTimeZone = new List<(Template Template, string MskTimeZone)>();
foreach (var item in items)
{
var responseArea = await shortcodesService.ApplyShortcodesAsync(item.ResponseAreaMask, item.Template);
var mskTimeZone = scheduleResponseAreaTimeOffsetService.GetByResponseAreaOrDefault(responseArea);
templatesWithTimeZone.Add((item.Template, mskTimeZone.EsppValue));
}
// Группируем по часовому поясу
var grouped = templatesWithTimeZone
.GroupBy(x => x.MskTimeZone)
.ToDictionary(g => g.Key, g => g.Select(x => x.Template).ToList());
foreach (var groupedItem in grouped)
{
var statResult = GetResponseByWorkGroup(groupedItem.Key, groupedItem.Value.ToList(), allDays, weekends, timeZoneQuery.TimeZoneOffset);
responseItemsList.Add(statResult);
}
}
else
{
// не нужно группировать
logger.LogDebug("Без группировки");
// сразу формируем response
responseItemsList.Add(GetResponseByWorkGroup(null, items.Select(t => t.Template).ToList(), allDays, weekends, timeZoneQuery.TimeZoneOffset));
}
var response = new StatTemplateDistributorResponse
{
JobGroup = mapper.Map<JobGroupWithDistributionConfigResponse>(jobGroup),
ItemsList = responseItemsList.OrderBy(t => t.GroupFieldName).ToList()
};
return Ok(new Response<StatTemplateDistributorResponse>(response, true));
}
/// <summary>
/// Получить дату начала и конца периода
/// </summary>
/// <param name="jobGroup"></param>
/// <param name="minNextRunTemplate"></param>
/// <param name="maxNextRunTemplate"></param>
/// <param name="clientOffset"></param>
/// <returns></returns>
private (DateOnly dateStart, DateOnly dateEnd) GetStartEndPeriod(JobGroup jobGroup, DateTimeOffset minNextRunTemplate, DateTimeOffset maxNextRunTemplate, TimeSpan clientOffset)
{
var durationDays = nextRunService.GetDurationDays(jobGroup.DistributionConfig!);
var dateStart = DateOnly.FromDateTime(DateTime.UtcNow.Add(clientOffset));
// последний день, не может быть меньше чем durationDays
var dateEnd = dateStart.AddDays(durationDays);
// последний день, не может быть меньше чем refDate+durationDays
var sumRefDuration = DateOnly.FromDateTime(jobGroup.ReferenceDate.Add(clientOffset).AddDays(durationDays).Date);
if (dateEnd < sumRefDuration)
dateEnd = sumRefDuration;
// последний день, не может быть меньше чем дата последнего шаблона maxNextRunTemplate
var lastTemplateDate = DateOnly.FromDateTime(maxNextRunTemplate.Add(clientOffset).Date);
if (dateEnd < lastTemplateDate)
dateEnd = lastTemplateDate;
// Дата начала, не может быть меньше чем minNextRunTemplate
var minNextRunDate = DateOnly.FromDateTime(minNextRunTemplate.Add(clientOffset).Date);
if (dateStart < minNextRunDate)
dateStart = minNextRunDate;
return (dateStart, dateEnd);
}
/// <summary>
/// Заполнить респонс
/// </summary>
/// <param name="groupFieldName"></param>
/// <param name="templateList"></param>
/// <param name="allDays"></param>
/// <param name="weekends"></param>
/// <param name="clientOffset"></param>
/// <returns></returns>
private StatDistributorItemResponse GetResponseByWorkGroup(string? groupFieldName, List<Template> templateList, List<DateOnly> allDays, HashSet<DateOnly> weekends, TimeSpan clientOffset)
{
var result = new StatDistributorItemResponse
{
GroupFieldName = groupFieldName,
Statistics = allDays.OrderBy(t => t).Select(t => new StatTemplateDistributorDateStat
{
AllCount = 0,
// вычтем clientOffset, чтоб когда на клиенте он прибавился обратно, дата стала верной для клиента
Date = new DateTimeOffset(t.Year, t.Month, t.Day, 0, 0, 0, TimeSpan.Zero).Add(-clientOffset),
IsWorkDay = !weekends.Contains(t),
IsActivatedCount = 0,
IsDeactivatedCount = 0,
Templates = new List<StatTemplateDistribItem>()
}).ToList()
};
// смещаем по часовой зоне клиента и группируем по дате
foreach (var templatesFromDate in templateList.GroupBy(t => DateOnly.FromDateTime(t.NextRun.Add(clientOffset).Date)))
{
var statisticsForDate = result.Statistics.FirstOrDefault(t => DateOnly.FromDateTime(t.Date.Add(clientOffset).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 StatTemplateDistribItem
{
Id = t.Id,
Name = t.Name,
IsActiveSchedular = t.IsActiveSchedule,
IsActiveTemplate = t.IsActiveTemplate,
NextRun = t.NextRun
})
.OrderBy(t => t.Name)
.ToList();
}
return result;
}
}
}