2024-07-02 11:43:59 +10:00
using Microsoft.EntityFrameworkCore ;
2024-07-05 15:17:48 +10:00
using Microsoft.Extensions.Logging ;
using PARR.BLL.Services.Interfaces ;
using PARR.Constants ;
2024-07-02 11:43:59 +10:00
using PARR.DAL.Models ;
using PARR.DAL.Services.Interfaces ;
2024-07-05 15:17:48 +10:00
using PARR.DAL.TransformServices ;
2025-06-25 14:17:34 +10:00
using System.Reflection.Metadata.Ecma335 ;
2024-07-02 11:43:59 +10:00
namespace PARR.TemplateDistributor
{
internal class TemplateDistributor : ITemplateDistributor
{
2024-07-05 15:17:48 +10:00
private readonly ILogger < TemplateDistributor > logger ;
private readonly ITemplateService templateService ;
2024-07-02 11:43:59 +10:00
private readonly IApplicationsInWorkService applicationsInWorkService ;
2024-07-05 15:17:48 +10:00
private readonly ICalendarService calendarService ;
private readonly IEsppScheduleTransformService esppScheduleTransformService ;
private readonly IWeekendDayService weekendDayService ;
2024-09-19 13:51:44 +10:00
private readonly INextRunModifierService nextRunModifierService ;
2024-07-02 11:43:59 +10:00
2024-07-05 15:17:48 +10:00
public TemplateDistributor (
ILogger < TemplateDistributor > logger ,
ITemplateService templateService ,
IApplicationsInWorkService applicationsInWorkService ,
ICalendarService calendarService ,
IEsppScheduleTransformService esppScheduleTransformService ,
2024-09-19 13:51:44 +10:00
IWeekendDayService weekendDayService ,
INextRunModifierService nextRunModifierService
2024-07-05 15:17:48 +10:00
)
2024-07-02 11:43:59 +10:00
{
2024-07-05 15:17:48 +10:00
this . logger = logger ;
this . templateService = templateService ;
2024-07-02 11:43:59 +10:00
this . applicationsInWorkService = applicationsInWorkService ;
2024-07-05 15:17:48 +10:00
this . calendarService = calendarService ;
this . esppScheduleTransformService = esppScheduleTransformService ;
this . weekendDayService = weekendDayService ;
2024-09-19 13:51:44 +10:00
this . nextRunModifierService = nextRunModifierService ;
2024-07-02 11:43:59 +10:00
}
2025-06-25 14:17:34 +10:00
public async Task UpdateScheduleAsync ( Guid applicationInWorkId ) //TODO Migratin to Job
2024-07-02 15:04:23 +10:00
{
2024-07-11 17:23:52 +10:00
var appInWork = await applicationsInWorkService . GetAsync ( applicationInWorkId ) ;
2024-07-05 15:17:48 +10:00
var templates = await templateService . Get ( )
2025-06-25 14:17:34 +10:00
. 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
2024-07-05 15:17:48 +10:00
. ToListAsync ( ) ;
2024-07-25 14:05:00 +10:00
//Группируем шаблоны по рабочим группам
2025-06-25 14:17:34 +10:00
var workGroupsWithTemplates = templates . GroupBy ( t = > t . Unit ! . BaseFields ! . WorkGroup ) ;
2024-07-05 15:17:48 +10:00
2025-06-25 14:17:34 +10:00
/ * foreach ( var workGroupWithTemplates in workGroupsWithTemplates )
2024-07-05 15:17:48 +10:00
{
2024-07-25 14:05:00 +10:00
var wgId = ( Guid ) workGroupWithTemplates . Key ! ;
2024-07-11 11:32:14 +10:00
var values = workGroupWithTemplates . ToList ( ) ;
2024-07-25 14:05:00 +10:00
//Обновляем сразу время при необходимости, так как дальше будем работать только с датой
var timesList = values . Select ( t = > t . NextRun . TimeOfDay ) . Distinct ( ) . ToList ( ) ;
if ( timesList . Count ( ) > 1 | | ( timesList . Count ( ) = = 1 & & timesList [ 0 ] ! = appInWork ! . ReferenceDate . TimeOfDay ) )
2024-07-11 17:23:52 +10:00
{
2024-07-25 14:05:00 +10:00
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 ;
}
2024-07-11 17:23:52 +10:00
}
2024-07-25 14:05:00 +10:00
var distrTemplates = await DistributeTemplateAsync ( new List < Template > ( ) , applicationInWorkId , wgId ) ;
2024-07-05 15:17:48 +10:00
2024-07-11 17:23:52 +10:00
if ( distrTemplates . Any ( ) )
2024-07-25 14:05:00 +10:00
if ( ! await templateService . CommitAsync ( ) )
logger . LogError ( $"Ошибка записи изменений в БД при перераспределении NextRun шаблонов Р Р ({applicationInWorkId})" ) ;
2025-06-25 14:17:34 +10:00
} * /
// TODO Migratin to Job
2024-07-02 15:04:23 +10:00
}
2024-07-02 11:43:59 +10:00
2024-07-11 11:32:14 +10:00
public async Task < List < Template > > DistributeTemplateAsync ( List < Template > templates , Guid applicationInWorkId , Guid workGroupId )
2024-07-02 11:43:59 +10:00
{
2025-06-25 14:17:34 +10:00
// var appInWork = await applicationsInWorkService.Get()
// .Include(aiw => aiw.EsppSchValues)
// .ThenInclude(esv => esv.EsppSchTypeValue)
// .ThenInclude(etv => etv!.DistributionPeriod)
// .FirstAsync(t => t.Id == applicationInWorkId);
2024-09-13 09:37:21 +10:00
2025-06-25 14:17:34 +10:00
// var refDate = appInWork.ReferenceDate;
2024-07-05 15:17:48 +10:00
2025-06-25 14:17:34 +10:00
// //Проверяем наличие распределения Р Р на период
// 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));
2024-07-02 11:43:59 +10:00
2025-06-25 14:17:34 +10:00
// return templates;
return new List < Template > ( ) ;
2024-07-02 11:43:59 +10:00
}
2024-07-05 15:17:48 +10:00
2024-08-23 16:18:24 +10:00
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 ) ;
2025-06-25 14:17:34 +10:00
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()}, " +
2024-09-13 09:37:21 +10:00
$"WorkGroup:{loggerWorkGroup}) " +
$"запланировал изменение даты следующего срабатывания у {templateToDistrib.Count()} существующих шаблона(ов), {templates.Count() - templateToDistrib.Count()} остались без изменений" ) ;
2024-08-23 16:18:24 +10:00
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 ;
}
}
2024-07-25 14:05:00 +10:00
/// <summary>
/// Получить список рабочих дней из календаря и перенести прошедшие даты на следующий период по Р Р
/// </summary>
/// <param name="appInWork"></param>
/// <returns></returns>
2024-09-19 13:51:44 +10:00
private async Task < List < DateOnly > > GetWorkDaysAsync ( ApplicationsInWork appInWork )
2024-07-11 11:32:14 +10:00
{
2025-06-25 14:17:34 +10:00
//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 > ( ) ;
2024-07-11 11:32:14 +10:00
}
2024-07-25 14:05:00 +10:00
/// <summary>
/// Проверить соответствие распределения существующих шаблонов нововму плану.
/// Н а выходе получаем шаблоны, которым требуется изменить дату следующего запуска.
/// </summary>
/// <param name="distributionPlan"></param>
/// <param name="templates"></param>
/// <returns></returns>
2024-07-11 11:32:14 +10:00
private List < Template > GetTemplatesToDistribute ( ref Dictionary < DateOnly , int > distributionPlan , List < Template > templates )
{
var result = new List < Template > ( ) ;
foreach ( var wd in distributionPlan )
{
2024-07-11 17:23:52 +10:00
var templatesInWd = templates . Where ( t = > DateOnly . FromDateTime ( t . NextRun . DateTime ) = = wd . Key ) . ToList ( ) ;
2024-07-11 11:32:14 +10:00
2024-07-30 12:30:10 +10:00
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 ;
2024-07-11 11:32:14 +10:00
}
2024-07-30 12:30:10 +10:00
//Определим шаблоны за границами рабочих дней. Их тоже необходимо перераспределить
2024-07-11 17:23:52 +10:00
var workDays = distributionPlan . Keys . ToList ( ) ;
2024-07-30 12:30:10 +10:00
var templateWithNextRunOut = templates . Where ( t = > workDays . All ( w = > w ! = DateOnly . FromDateTime ( t . NextRun . DateTime ) ) ) . ToList ( ) ;
2024-08-09 16:36:37 +10:00
2024-07-30 12:30:10 +10:00
if ( templateWithNextRunOut . Any ( ) )
result . AddRange ( templateWithNextRunOut ) ;
if ( result . Any ( ) )
//Обнулим nextRun
result . ForEach ( t = > t . NextRun = DateTimeOffset . MinValue ) ;
2024-07-11 11:32:14 +10:00
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 )
{
2024-07-25 14:05:00 +10:00
var result = new Dictionary < DateOnly , int > ( ) ;
//Заполняем будующий план датами из полученных на входе данных
2024-07-11 11:32:14 +10:00
foreach ( var wd in workDays )
{
2024-07-25 14:05:00 +10:00
var workDate = new DateOnly ( wd . Year , wd . Month , wd . Day ) ;
result . Add ( workDate , 0 ) ;
2024-07-11 11:32:14 +10:00
}
2024-07-25 14:05:00 +10:00
var templateDistributedCount = 0 ; //количество распределённых на текущий момент шаблонов
2024-07-11 11:32:14 +10:00
2024-07-25 14:05:00 +10:00
//Если количество шаблонов больше количества рабочих дней, то сразу распределяем их равным количеством по всем рабочим дням
2024-07-30 12:30:10 +10:00
if ( templateCount > = workDays . Count )
2024-07-11 11:32:14 +10:00
{
2024-07-25 14:05:00 +10:00
//количеством шаблонов на один рабочий день
2024-07-11 11:32:14 +10:00
var mainPartTemplateCountToDistribute = templateCount / workDays . Count ;
2024-07-25 14:05:00 +10:00
//перебираем рабочие дня и записываем количество шаблонов
foreach ( var item in result )
2024-07-11 11:32:14 +10:00
{
2024-07-25 14:05:00 +10:00
result [ item . Key ] = mainPartTemplateCountToDistribute ;
2024-07-11 11:32:14 +10:00
2024-07-25 14:05:00 +10:00
templateDistributedCount + = mainPartTemplateCountToDistribute ;
2024-07-11 11:32:14 +10:00
}
}
2024-07-25 14:05:00 +10:00
var daysBetweenTemplates = ( double ) workDays . Count / ( templateCount % workDays . Count ) ; //количество дней между шаблонами, если распределить их равномерно
var currentStepBetweenTemplates = ( double ) Math . Floor ( daysBetweenTemplates ) ; //инициализация шага перехода между датами запуска двух шаблонов
var balance = ( double ) daysBetweenTemplates - Math . Floor ( currentStepBetweenTemplates ) ; //остаток между датами, потому что мы берём целые дни, е г о нужно учесть в следующей итирации
var currentDayIndex = 0 ; //инициализация индекса даты словаря рабочих дней
2024-07-12 14:51:01 +10:00
2024-07-25 14:05:00 +10:00
while ( templateDistributedCount < templateCount )
2024-07-11 11:32:14 +10:00
{
2024-07-25 14:05:00 +10:00
//лишняя проверка так как шагом ранее мы распределям шаблоны равным количеством и currentStepBetweenTemplates не может быть меньше одного дня
//if (currentStepBetweenTemplates > 0)
//{
result [ result . ElementAt ( currentDayIndex ) . Key ] + = 1 ;
templateDistributedCount + + ;
currentDayIndex + = ( int ) Math . Floor ( currentStepBetweenTemplates ) ;
//}
2024-07-12 14:51:01 +10:00
2024-07-25 14:05:00 +10:00
currentStepBetweenTemplates = daysBetweenTemplates + balance ;
balance = daysBetweenTemplates - currentStepBetweenTemplates ;
2024-07-11 11:32:14 +10:00
}
2024-07-25 14:05:00 +10:00
return result ;
2024-07-11 11:32:14 +10:00
}
2024-07-02 11:43:59 +10:00
}
2024-07-05 15:17:48 +10:00
2024-07-02 11:43:59 +10:00
}