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 ;
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-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 ,
IWeekendDayService weekendDayService
)
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-07-02 11:43:59 +10:00
}
2024-07-02 15:04:23 +10:00
public async Task UpdateScheduleAsync ( Guid applicationInWorkId )
{
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 ( )
. Include ( t = > t . Host )
2024-07-30 12:30:10 +10:00
. ThenInclude ( h = > h ! . WorkGroup )
2024-07-25 14:05:00 +10:00
. Where ( t = > t . ApplicationInWorkId = = applicationInWorkId & & t . Host ! . WorkGroupId ! = null )
2024-07-05 15:17:48 +10:00
. ToListAsync ( ) ;
2024-07-25 14:05:00 +10:00
//Группируем шаблоны по рабочим группам
2024-07-05 15:17:48 +10:00
var workGroupsWithTemplates = templates . GroupBy ( t = > t . Host ! . WorkGroupId ) ;
2024-07-11 11:32:14 +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})" ) ;
2024-07-11 17:23:52 +10:00
}
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
{
2024-07-05 15:17:48 +10:00
var appInWork = await applicationsInWorkService . Get ( )
. Include ( aiw = > aiw . EsppSchValues )
. ThenInclude ( esv = > esv . EsppSchTypeValue )
. ThenInclude ( etv = > etv ! . DistributionPeriod )
2024-07-25 14:05:00 +10:00
. FirstAsync ( t = > t . Id = = applicationInWorkId ) ;
2024-07-11 17:23:52 +10:00
2024-07-25 14:05:00 +10:00
var refDate = appInWork . ReferenceDate ;
2024-07-05 15:17:48 +10:00
//Проверяем наличие распределения Р Р на период
2024-07-25 14:05:00 +10:00
if ( appInWork . IsAutoDistributionEnabled )
2024-07-02 11:43:59 +10:00
{
2024-07-11 11:32:14 +10:00
var existingTemplates = await templateService . Get ( )
. Include ( t = > t . Host )
. Where ( t = > t . ApplicationInWorkId = = applicationInWorkId & & t . Host ! . WorkGroupId = = workGroupId )
. ToListAsync ( ) ;
2024-07-11 17:23:52 +10:00
//Удалим из входных шаблонов уже существующие в БД
2024-07-25 14:05:00 +10:00
//TODO: сделать селект повторяющихся шаблонов, написать их в варнинг и потом удалить
2024-07-11 17:23:52 +10:00
templates . RemoveAll ( t = > existingTemplates . Any ( et = > et . Id = = t . Id ) ) ;
2024-07-05 15:17:48 +10:00
//Если распределенная Р Р получаем начало
2024-07-25 14:05:00 +10:00
var period = appInWork . EsppSchValues . First ( ) ! . EsppSchTypeValue ! . DistributionPeriod ;
2024-07-11 11:32:14 +10:00
var periodType = ParseDistributionPeriodType ( period ! . Type ) ;
2024-07-02 11:43:59 +10:00
2024-07-11 11:32:14 +10:00
var workDays = await GetWorkDaysAsync ( appInWork ) ;
2024-07-05 15:17:48 +10:00
2024-07-11 11:32:14 +10:00
//Готовим план распределения
var distrPlan = GetDistributionPlan ( workDays , templates . Count + existingTemplates . Count ) ;
2024-07-05 15:17:48 +10:00
2024-07-11 11:32:14 +10:00
var templateToDistrib = new List < Template > ( ) ;
2024-07-05 15:17:48 +10:00
2024-07-11 11:32:14 +10:00
if ( existingTemplates . Count > 0 )
2024-08-09 16:36:37 +10:00
{
2024-07-11 12:02:50 +10:00
templateToDistrib = GetTemplatesToDistribute ( ref distrPlan , existingTemplates ) ;
2024-08-09 16:36:37 +10:00
logger . LogInformation ( $"{GetType().Name}(AppInWId:{applicationInWorkId}, WorkGroup:{existingTemplates.Select(t =>t.Host!.WorkGroup!.Name).First()}) запланировал изменение даты следующего срабатывания у {templateToDistrib.Count()} существующих шаблона(ов), {existingTemplates.Count() - templateToDistrib.Count()} остались без изменений" ) ;
}
2024-07-05 15:17:48 +10:00
2024-07-11 17:23:52 +10:00
templateToDistrib . AddRange ( templates ) ;
2024-07-11 14:42:55 +10:00
if ( ! templateToDistrib . Any ( ) )
return new List < Template > ( ) ;
2024-07-05 15:17:48 +10:00
2024-07-11 11:32:14 +10:00
var distributedTemplates = 0 ;
2024-07-05 15:17:48 +10:00
2024-07-11 11:32:14 +10:00
foreach ( var workDay in distrPlan )
2024-07-05 15:17:48 +10:00
{
2024-07-11 11:32:14 +10:00
var templatesCountForCurDay = workDay . Value ;
2024-07-11 17:23:52 +10:00
if ( templatesCountForCurDay < 1 )
continue ;
2024-07-11 11:32:14 +10:00
templateToDistrib . Skip ( distributedTemplates ) . Take ( templatesCountForCurDay ) . ToList ( ) . ForEach ( t = >
2024-07-05 15:17:48 +10:00
{
2024-07-11 11:32:14 +10:00
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 ;
2024-07-11 17:23:52 +10:00
distrPlan [ workDay . Key ] - = templatesCountForCurDay ;
2024-07-05 15:17:48 +10:00
}
2024-07-30 12:30:10 +10:00
return templateToDistrib ;
2024-07-11 11:32:14 +10:00
2024-07-25 14:05:00 +10:00
#region comments
2024-07-11 11:32:14 +10:00
//var d = new Dictionary<DateOnly, List<Template>>();
//foreach (var wd in workDays)
2024-07-05 15:17:48 +10:00
//{
2024-07-11 11:32:14 +10:00
// 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())
2024-07-05 15:17:48 +10:00
// {
2024-07-11 11:32:14 +10:00
// foreach (var wd in workDaysWithMinTemplates)
2024-07-05 15:17:48 +10:00
// {
2024-07-11 11:32:14 +10:00
// //Считаем 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;
// }
2024-07-05 15:17:48 +10:00
// }
// else
2024-07-11 11:32:14 +10:00
// {
// 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;
// }
// }
2024-07-05 15:17:48 +10:00
//}
2024-07-25 14:05:00 +10:00
#endregion
2024-07-05 15:17:48 +10:00
}
else
templates . ForEach ( t = > t . NextRun = appInWork ! . ReferenceDate ) ;
2024-07-02 11:43:59 +10:00
return templates ;
}
2024-07-05 15:17:48 +10:00
2024-07-25 14:05:00 +10:00
/// <summary>
/// Получить список рабочих дней из календаря и перенести прошедшие даты на следующий период по Р Р
/// </summary>
/// <param name="appInWork"></param>
/// <returns></returns>
public async Task < List < DateOnly > > GetWorkDaysAsync ( ApplicationsInWork appInWork )
2024-07-11 11:32:14 +10:00
{
var period = appInWork . EsppSchValues . FirstOrDefault ( ) ? . EsppSchTypeValue ? . DistributionPeriod ;
var periodType = ParseDistributionPeriodType ( period ! . Type ) ;
var startPeriod = await esppScheduleTransformService . GetStartPeriodForDateAsync ( appInWork . Id , appInWork . ReferenceDate , appInWork . ReferenceDate , periodType , period . Duration ) ;
var result = calendarService . GetWorkDatesForPeriod ( startPeriod , periodType , period . Duration , weekendDayService . GetWeekends ) ;
result . ForEach ( wd = >
{
var wrokDateTimeOffset = new DateTimeOffset (
wd . Year , wd . Month , wd . Day ,
2024-07-11 14:42:55 +10:00
appInWork . ReferenceDate . Hour , appInWork . ReferenceDate . Minute , appInWork . ReferenceDate . Second ,
2024-07-11 11:32:14 +10:00
new TimeSpan ( 0 , 0 , 0 ) ) ;
2024-07-25 14:05:00 +10:00
2024-07-11 11:32:14 +10:00
if ( wrokDateTimeOffset < DateTimeOffset . UtcNow )
{
wrokDateTimeOffset = esppScheduleTransformService . GetNextDateForDistributionRun ( wrokDateTimeOffset , periodType , period . Duration ) ;
wd = DateOnly . FromDateTime ( wrokDateTimeOffset . DateTime ) ;
}
} ) ;
return result ;
}
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
}