Files
parr_api/PARR.TemplateDistributor/TemplateDistributor.cs

439 lines
24 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 Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.BLL.Services.Interfaces;
using PARR.Constants;
using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces;
using PARR.DAL.TransformServices;
using System.Reflection.Metadata.Ecma335;
namespace PARR.TemplateDistributor
{
internal class TemplateDistributor : ITemplateDistributor
{
private readonly ILogger<TemplateDistributor> logger;
private readonly ITemplateService templateService;
private readonly IApplicationsInWorkService applicationsInWorkService;
private readonly ICalendarService calendarService;
private readonly IEsppScheduleTransformService esppScheduleTransformService;
private readonly IWeekendDayService weekendDayService;
private readonly INextRunModifierService nextRunModifierService;
public TemplateDistributor(
ILogger<TemplateDistributor> logger,
ITemplateService templateService,
IApplicationsInWorkService applicationsInWorkService,
ICalendarService calendarService,
IEsppScheduleTransformService esppScheduleTransformService,
IWeekendDayService weekendDayService,
INextRunModifierService nextRunModifierService
)
{
this.logger = logger;
this.templateService = templateService;
this.applicationsInWorkService = applicationsInWorkService;
this.calendarService = calendarService;
this.esppScheduleTransformService = esppScheduleTransformService;
this.weekendDayService = weekendDayService;
this.nextRunModifierService = nextRunModifierService;
}
public async Task UpdateScheduleAsync(Guid applicationInWorkId)//TODO Migratin to Job
{
var appInWork = await applicationsInWorkService.GetAsync(applicationInWorkId);
var templates = await templateService.Get()
.Include(t => t.Unit)//TODO Migratin to Job
//.ThenInclude(h => h!.WorkGroup)
.Where(t => t.JobId == applicationInWorkId && t.Unit!.BaseFields!.WorkGroup != null)//TODO Migratin to Job
.ToListAsync();
//Группируем шаблоны по рабочим группам
var workGroupsWithTemplates = templates.GroupBy(t => t.Unit!.BaseFields!.WorkGroup);
/*foreach (var workGroupWithTemplates in workGroupsWithTemplates)
{
var wgId = (Guid)workGroupWithTemplates.Key!;
var values = workGroupWithTemplates.ToList();
//Обновляем сразу время при необходимости, так как дальше будем работать только с датой
var timesList = values.Select(t => t.NextRun.TimeOfDay).Distinct().ToList();
if (timesList.Count() > 1 || (timesList.Count() == 1 && timesList[0] != appInWork!.ReferenceDate.TimeOfDay))
{
values.ForEach(t =>
{
if (t.NextRun.TimeOfDay != appInWork!.ReferenceDate.TimeOfDay)
t.NextRun = new DateTimeOffset(t.NextRun.Year, t.NextRun.Month, t.NextRun.Day,
appInWork!.ReferenceDate.Hour, appInWork!.ReferenceDate.Minute, appInWork!.ReferenceDate.Second,
new TimeSpan(0, 0, 0));
});
if (!await templateService.CommitAsync())
{
logger.LogError($"Ошибка записи изменений в БД при актуализации времени NextRun шаблонов РР({applicationInWorkId})");
return;
}
}
var distrTemplates = await DistributeTemplateAsync(new List<Template>(), applicationInWorkId, wgId);
if (distrTemplates.Any())
if (!await templateService.CommitAsync())
logger.LogError($"Ошибка записи изменений в БД при перераспределении NextRun шаблонов РР({applicationInWorkId})");
}*/
// TODO Migratin to Job
}
public async Task<List<Template>> DistributeTemplateAsync(List<Template> templates, Guid applicationInWorkId, Guid workGroupId)
{
// var appInWork = await applicationsInWorkService.Get()
// .Include(aiw => aiw.EsppSchValues)
// .ThenInclude(esv => esv.EsppSchTypeValue)
// .ThenInclude(etv => etv!.DistributionPeriod)
// .FirstAsync(t => t.Id == applicationInWorkId);
// var refDate = appInWork.ReferenceDate;
// //Проверяем наличие распределения РР на период
// if (appInWork.IsAutoDistributionEnabled)
// {
// var existingTemplates = await templateService.Get()
// .Include(t => t.Unit)
// .Where(t => t.JobId == applicationInWorkId
// //&& t.Host!.WorkGroupId == workGroupId//TODO Migratin to Job
// )
// .ToListAsync();
// //Удалим из входных шаблонов уже существующие в БД (по идее, такого никогда не должно случиться)
// var duplicates = templates.Select(t => t.Id).Where(t => existingTemplates.Any(et => et.Id == t)).ToList();
// if (duplicates.Count > 0)
// {
// duplicates.ForEach(t =>
// {
// logger.LogWarning($"Для распределения AiW({applicationInWorkId}),workGroupId({workGroupId}) передан существующий в базе данных шаблон {t}");
// });
// templates.RemoveAll(t => duplicates.Any(dt => dt == t.Id));
// }
// existingTemplates.AddRange(templates);
// //Если распределенная РР получаем начало
// var period = appInWork.EsppSchValues.First()!.EsppSchTypeValue!.DistributionPeriod;
// var periodType = ParseDistributionPeriodType(period!.Type);
// var workDays = await GetWorkDaysAsync(appInWork);
// var result = new List<Template>();
// //распределяем РР отдельно активированные
// var activatedTemplates = existingTemplates.Where(t => t.IsActiveTemplate && t.IsActiveSchedule).ToList();
// if (activatedTemplates.Count > 0)
// {
// Distribute(ref activatedTemplates, refDate, period, periodType, workDays);
// // result.AddRange(activatedTemplates);
// }
// //распределяем РР отдельно деактивированные
// var deactivatedTemplates = existingTemplates.Where(t =>
// !t.IsActiveTemplate || !t.IsActiveSchedule || (
// !t.IsActiveTemplate && !t.IsActiveSchedule
// )).ToList();
// if (deactivatedTemplates.Count > 0)
// {
// Distribute(ref deactivatedTemplates, refDate, period, periodType, workDays);
// // result.AddRange(deactivatedTemplates);
// }
// if (templates.Any())
// {
// // это была генерация шаблонов, не нужны все шаблоны, нужны только те которые передал для получения NextRun
// result.AddRange(templates);
// }
// else
// {
// // это было распределение шаблонов, добавляем активированные и деактивированные
// result.AddRange(activatedTemplates);
// result.AddRange(deactivatedTemplates);
// }
// return result;
// #region comments
// //var d = new Dictionary<DateOnly, List<Template>>();
// //foreach (var wd in workDays)
// //{
// // var wrokDateTimeOffset = new DateTimeOffset(
// // wd.Year, wd.Month, wd.Day,
// // refDate.Hour, refDate.Minute, refDate.Second,
// // new TimeSpan(0, 0, 0));
// // if (wrokDateTimeOffset < DateTimeOffset.UtcNow)
// // wrokDateTimeOffset = esppScheduleTransformService.GetNextDateForDistributionRun(wrokDateTimeOffset, periodType, period.Duration);
// // var assignedTemplates = existingTemplates.Where(t => t.NextRun == wrokDateTimeOffset).ToList();
// // d.Add(wd, assignedTemplates);
// //}
// //var templatesPerStep = (double)templates.Count() / workDays.Count();//
// //var currentTemplateStep = (int)Math.Ceiling(templatesPerStep);
// //var delta = templatesPerStep - currentTemplateStep;
// ////templates.First().NextRun = currentDay;
// //var templateDistributed = 0;
// ////Отталкиваясь от количества
// //while (templateDistributed < templates.Count())
// //{
// // var wdCounts = d.Select(wd => wd.Value).OrderBy(count => count).ToArray();
// // var templateCountDelta = d.Max(t => t.Value.Count()) - d.Min(t => t.Value.Count());
// // var addingTemplatesCount = wdCounts.Length > 1 ?
// // (templateCountDelta == 0) ? (int)Math.Ceiling(templatesPerStep) : templateCountDelta :
// // (int)Math.Ceiling(templatesPerStep);
// // var workDaysWithMinTemplates = d.Where(wd => wd.Value == wdCounts[0]).ToArray();
// // if (workDaysWithMinTemplates.Length * addingTemplatesCount < templates.Count())
// // {
// // foreach (var wd in workDaysWithMinTemplates)
// // {
// // //Считаем DateTimeOffset, потому что у нас только дата пока
// // var currentDay = new DateTimeOffset(
// // wd.Key.Year, wd.Key.Month, wd.Key.Day,
// // refDate.Hour, refDate.Minute, refDate.Second,
// // new TimeSpan(0, 0, 0));
// // templates.Skip(templateDistributed).Take(addingTemplatesCount).ToList().ForEach(t =>
// // {
// // t.NextRun = currentDay;
// // d[wd.Key].Add(t);
// // });
// // templateDistributed += addingTemplatesCount;
// // }
// // }
// // else
// // {
// // while (templateDistributed < templates.Count())
// // {
// // var wd = d.Where(wd => wd.Value == d.Min(t => t.Value)).First();
// // //Считаем DateTimeOffset, потому что у нас только дата пока
// // var currentDay = new DateTimeOffset(
// // wd.Key.Year, wd.Key.Month, wd.Key.Day,
// // refDate.Hour, refDate.Minute, refDate.Second,
// // new TimeSpan(0, 0, 0));
// // templates.Skip(templateDistributed).Take(addingTemplatesCount).ToList().ForEach(t =>
// // {
// // t.NextRun = currentDay;
// // d[wd.Key].Add(t);
// // });
// // templateDistributed += addingTemplatesCount;
// // }
// // }
// //}
// #endregion
// }
// else
// // templates.ForEach(t => t.NextRun = appInWork!.ReferenceDate);
// // это не автораспределение, по ReferenceDate получаем ближайший рабочий день
// templates.ForEach(async t => t.NextRun = await nextRunModifierService.GetWorkDayAsync(appInWork!.ReferenceDate));
// return templates;
return new List<Template>();
}
private void Distribute(ref List<Template> templates, DateTimeOffset refDate, DistributionPeriod period, DistributionPeriodTypeEnum periodType, List<DateOnly> workDays)
{
//Готовим план распределения
var distrPlan = GetDistributionPlan(workDays, templates.Count);
//Проверяем шаблоны уже распределённые, чтобы не перемещать лишний раз
var templateToDistrib = GetTemplatesToDistribute(ref distrPlan, templates);
string? loggerWorkGroup = templates.FirstOrDefault()?.Unit?.BaseFields?.WorkGroup != null ? templates.FirstOrDefault()?.Unit.BaseFields.WorkGroup : templates.FirstOrDefault()?.Unit?.BaseFields?.WorkGroup?.ToString();
logger.LogInformation($"{GetType().Name}(AppInWId:{templates.Select(t => t.JobId).First()}, " +
$"WorkGroup:{loggerWorkGroup}) " +
$"запланировал изменение даты следующего срабатывания у {templateToDistrib.Count()} существующих шаблона(ов), {templates.Count() - templateToDistrib.Count()} остались без изменений");
if (!templateToDistrib.Any())
return;
var distributedTemplates = 0;
//Распределяем шаблоны в соответствии с планом непосредственно
foreach (var workDay in distrPlan)
{
var templatesCountForCurDay = workDay.Value;
if (templatesCountForCurDay < 1)
continue;
templateToDistrib.Skip(distributedTemplates).Take(templatesCountForCurDay).ToList().ForEach(t =>
{
var nextRun = new DateTimeOffset(
workDay.Key.Year, workDay.Key.Month, workDay.Key.Day,
refDate.Hour, refDate.Minute, refDate.Second,
new TimeSpan(0, 0, 0));
if (nextRun < DateTimeOffset.UtcNow)
nextRun = esppScheduleTransformService.GetNextDateForDistributionRun(nextRun, periodType, period.Duration);
t.NextRun = nextRun;
});
distributedTemplates += templatesCountForCurDay;
distrPlan[workDay.Key] -= templatesCountForCurDay;
}
}
/// <summary>
/// Получить список рабочих дней из календаря и перенести прошедшие даты на следующий период по РР
/// </summary>
/// <param name="appInWork"></param>
/// <returns></returns>
private async Task<List<DateOnly>> GetWorkDaysAsync(ApplicationsInWork appInWork)
{
//var period = appInWork.EsppSchValues.FirstOrDefault()?.EsppSchTypeValue?.DistributionPeriod;
//var periodType = ParseDistributionPeriodType(period!.Type);
////старт период, берем сейчас, будем получать рабочие дни с вычетом выходных на весь период сразу.
//var startPeriod = DateOnly.FromDateTime(DateTimeOffset.UtcNow.Date);
//// старт период считаем относительно refDate
////var startPeriod = await esppScheduleTransformService.GetStartPeriodForDateAsync(appInWork.Id, appInWork.ReferenceDate, appInWork.ReferenceDate, periodType, period.Duration);
//var workDays = calendarService.GetWorkDatesForPeriod(startPeriod, TimeOnly.FromDateTime(appInWork.ReferenceDate.DateTime), periodType, period.Duration, weekendDayService.GetWeekends);
//#region старая логика
////var resultWorkDays = new List<DateOnly>();
////workDays.ForEach(wd =>
////{
//// var wrokDateTimeOffset = new DateTimeOffset(
//// wd.Year, wd.Month, wd.Day,
//// appInWork.ReferenceDate.Hour, appInWork.ReferenceDate.Minute, appInWork.ReferenceDate.Second,
//// new TimeSpan(0, 0, 0));
//// if (wrokDateTimeOffset < DateTimeOffset.UtcNow)
//// {
//// wrokDateTimeOffset = esppScheduleTransformService.GetNextDateForDistributionRun(wrokDateTimeOffset, periodType, period.Duration);
//// //wd = DateOnly.FromDateTime(wrokDateTimeOffset.DateTime);
//// resultWorkDays.Add(DateOnly.FromDateTime(wrokDateTimeOffset.DateTime));
//// }
//// else
//// {
//// resultWorkDays.Add(wd);
//// }
////});
//////----- убираем даты, которые могли выйти за конец периода (из-за сдвига NextRun по рабочим дням)
////var endPeriod = calendarService.GetEndPeriodDate(DateOnly.FromDateTime(DateTimeOffset.UtcNow.Date), periodType, period.Duration);
////resultWorkDays = resultWorkDays.Where(t => t <= endPeriod).Distinct().OrderBy(t => t).ToList();
//////-----
////return resultWorkDays;
//#endregion
//return workDays;
return new List<DateOnly>();
}
/// <summary>
/// Проверить соответствие распределения существующих шаблонов нововму плану.
/// На выходе получаем шаблоны, которым требуется изменить дату следующего запуска.
/// </summary>
/// <param name="distributionPlan"></param>
/// <param name="templates"></param>
/// <returns></returns>
private List<Template> GetTemplatesToDistribute(ref Dictionary<DateOnly, int> distributionPlan, List<Template> templates)
{
var result = new List<Template>();
foreach (var wd in distributionPlan)
{
var templatesInWd = templates.Where(t => DateOnly.FromDateTime(t.NextRun.DateTime) == wd.Key).ToList();
if (templatesInWd.Any() && templatesInWd.Count > wd.Value)
{
result.AddRange(templatesInWd.Skip(wd.Value).Take(templatesInWd.Count - wd.Value).ToList());
distributionPlan[wd.Key] -= wd.Value;
}
else
distributionPlan[wd.Key] -= templatesInWd.Count;
}
//Определим шаблоны за границами рабочих дней. Их тоже необходимо перераспределить
var workDays = distributionPlan.Keys.ToList();
var templateWithNextRunOut = templates.Where(t => workDays.All(w => w != DateOnly.FromDateTime(t.NextRun.DateTime))).ToList();
if (templateWithNextRunOut.Any())
result.AddRange(templateWithNextRunOut);
if (result.Any())
//Обнулим nextRun
result.ForEach(t => t.NextRun = DateTimeOffset.MinValue);
return result;
}
private DistributionPeriodTypeEnum ParseDistributionPeriodType(string value)
{
var result = (DistributionPeriodTypeEnum)Enum.Parse(typeof(DistributionPeriodTypeEnum), value);
return result;
}
private Dictionary<DateOnly, int> GetDistributionPlan(List<DateOnly> workDays, int templateCount)
{
var result = new Dictionary<DateOnly, int>();
//Заполняем будующий план датами из полученных на входе данных
foreach (var wd in workDays)
{
var workDate = new DateOnly(wd.Year, wd.Month, wd.Day);
result.Add(workDate, 0);
}
var templateDistributedCount = 0;//количество распределённых на текущий момент шаблонов
//Если количество шаблонов больше количества рабочих дней, то сразу распределяем их равным количеством по всем рабочим дням
if (templateCount >= workDays.Count)
{
//количеством шаблонов на один рабочий день
var mainPartTemplateCountToDistribute = templateCount / workDays.Count;
//перебираем рабочие дня и записываем количество шаблонов
foreach (var item in result)
{
result[item.Key] = mainPartTemplateCountToDistribute;
templateDistributedCount += mainPartTemplateCountToDistribute;
}
}
var daysBetweenTemplates = (double)workDays.Count / (templateCount % workDays.Count);//количество дней между шаблонами, если распределить их равномерно
var currentStepBetweenTemplates = (double)Math.Floor(daysBetweenTemplates);//инициализация шага перехода между датами запуска двух шаблонов
var balance = (double)daysBetweenTemplates - Math.Floor(currentStepBetweenTemplates);//остаток между датами, потому что мы берём целые дни, его нужно учесть в следующей итирации
var currentDayIndex = 0;//инициализация индекса даты словаря рабочих дней
while (templateDistributedCount < templateCount)
{
//лишняя проверка так как шагом ранее мы распределям шаблоны равным количеством и currentStepBetweenTemplates не может быть меньше одного дня
//if (currentStepBetweenTemplates > 0)
//{
result[result.ElementAt(currentDayIndex).Key] += 1;
templateDistributedCount++;
currentDayIndex += (int)Math.Floor(currentStepBetweenTemplates);
//}
currentStepBetweenTemplates = daysBetweenTemplates + balance;
balance = daysBetweenTemplates - currentStepBetweenTemplates;
}
return result;
}
}
}