@@ -1,7 +1,5 @@
using Microsoft.EntityFrameworkCore ;
using Microsoft.EntityFrameworkCore.Diagnostics ;
using Microsoft.Extensions.Logging ;
using NodaTime ;
using PARR.Constants ;
using PARR.DAL.Contracts ;
using PARR.DAL.DomainServices.Shortcodes ;
@@ -47,14 +45,15 @@ namespace PARR.DAL.NextRunServices
this . settingsFromDb = settingsFromDb ;
}
public async Task < List < TemplateNextRunResultDto > ? > GetNextRunForJobGroupWithAutoDistributionAsync ( Guid jobGroupId )
public async Task < List < TemplateNextRunResultDto > ? > GetNextRunForJobGroupWithAutoDistributionAsync ( Guid jobGroupId , TemplateStatusTypeEnum ? templateStatusType )
{
logger . LogInformation ( "Начинаю распределять шаблоны для группы работ {gobGroupId}" , jobGroupId ) ;
logger . LogInformation ( "Начинаю распределять шаблоны для группы работ {gobGroupId}, templateStatusType: {templateStatusType}" , jobGroupId , templateStatusType ) ;
var jobGroup = await jobGroupService . Get ( )
. AsNoTracking ( )
. Include ( t = > t . DistributionConfig )
. ThenInclude ( t = > t . DistributionPeriod )
. AsNoTracking ( )
. FirstOrDefaultAsync ( t = > t . Id = = jobGroupId ) ;
if ( jobGroup = = null )
@@ -76,13 +75,20 @@ namespace PARR.DAL.NextRunServices
jobGroup . DistributionConfig . IsExcludeWeekends , jobGroup . DistributionConfig . IsGroupingByWorkGroup , jobGroup . IsResponseAreaTimezone ) ;
//получаем шаблоны только в статусе Used
var allT emplates = await templateService . Get ( )
. AsNoTracking ( )
. Include ( t = > t . Job )
. Where ( t = >
t . Job ! . GroupId = = jobGroupId
& & t . StatusTypeId = = TemplateStatusTypeEnum. Used
) . ToListAsync ( ) ;
var t emplateQuery = templateService . Get ( )
. AsNoTracking ( )
. Include ( t = > t . Job )
. Where ( t = >
t . Job ! . GroupId = = jobGroupId
//&& t. StatusTypeId == TemplateStatusTypeEnum. Used
) ;
if ( templateStatusType . HasValue )
templateQuery = templateQuery . Where ( t = > t . StatusTypeId = = templateStatusType ) ;
var allTemplates = await templateQuery . ToListAsync ( ) ;
if ( ! allTemplates . Any ( ) )
{
logger . LogInformation ( "В группе c ИД {jobGroupId} отсутствуют шаблоны в статусе Used" , jobGroupId ) ;
@@ -106,39 +112,150 @@ namespace PARR.DAL.NextRunServices
if ( isGroupingByWorkGroup )
{
// группировать по РГ
logger . LogDebug ( "Нужно группировать по РГ" ) ;
if ( isResponseAreaTimeZone )
// получаем для каждого шаблона РГ и сразу группируем по РГ
var templatesByWorkGroup = new Dictionary < string? , List < Template > > ( ) ;
foreach ( var template in allTemplates )
{
// использовать часовой пояс РГ
var workGroupName = await shortcodesService . ApplyShortcodesAsync ( template . Job ! . WorkGroupMask , template ) ;
// Если даже workGroupName == null, ну и ладно, сгруппируем по null, и распределим шаблоны в рамках этого null
if ( workGroupName = = null )
logger . LogWarning ( "Для шаблона {id}, с маской РГ '{workGroupMask}', не смог с помощью шорткода определить рабочую группу, шорткод сервис вернул РГ: '{workGroupName}'" ,
template . Id , template . Job ! . WorkGroupMask , workGroupName ) ;
// Добавляем шаблон в группу
if ( ! templatesByWorkGroup . TryGetValue ( workGroupName , out var templatesList ) )
{
templatesList = new List < Template > ( ) ;
templatesByWorkGroup [ workGroupName ] = templatesList ;
}
templatesList . Add ( template ) ;
}
else
logger . LogDebug ( "Групп для распределения: {count}" , templatesByWorkGroup . Count ) ;
var dateStart = GetDateStart ( ) ;
// Будем распределять каждую РГ отдельно
foreach ( var ( workGroupName , templates ) in templatesByWorkGroup )
{
// не нужно использовать часовой пояс РГ (используем часовой пояс У З ЕСПП)
logger . LogDebug ( "Готовлюсь распределать шаблоны {templateCount} шт. в РГ '{workGroup}'" , templates . Count , workGroupName ) ;
if ( isResponseAreaTimeZone )
{
// использовать часовой пояс РГ
logger . LogDebug ( "Использовать часовой пояс РГ" ) ;
// считаем, что у всех шаблонов сгруппированных по РГ, одна З О
// получаем З О для первого шаблона
var firstTemplate = templates . First ( ) ;
var responseArea = await shortcodesService . ApplyShortcodesAsync ( firstTemplate . Job ! . ResponseAreaMask , firstTemplate ) ;
if ( responseArea = = null )
logger . LogWarning ( "Для шаблона {id}, с маской З О '{responseAreaMask}', не смог с помощью шорткода определить З О , шорткод сервис вернул З О : '{responseArea}'" ,
firstTemplate . Id , firstTemplate . Job ! . ResponseAreaMask , responseArea ) ;
var offset = GetOffsetForDistributionByResponseArea ( responseArea ) ;
var templatesForDistribute = templates . Select ( t = > new TemplateNextRunDto ( t . Id , t . NextRun ) ) . ToList ( ) ;
var referenceDate = GetReferenceDate ( jobGroup , responseArea ) ;
logger . LogInformation ( "Буду распределать шаблоны {count} шт, сгруппированные по РГ '{workGroup}', использую часовой пояс З О . dateStart: {dateStart}, offset: {offset}, referenceDate: {referenceDate}" ,
templatesForDistribute . Count , workGroupName , dateStart , offset , referenceDate ) ;
var distributionResult = await templateDistributor . DistributeTemplatesAsync ( dateStart , durationDays , referenceDate , offset , templatesForDistribute , jobGroup . DistributionConfig . IsExcludeWeekends ) ;
logger . LogDebug ( "Закончил распределение для РГ '{workGroupName}', referenceDate: {referenceDate}, часового пояса З О {responseArea}, utcOffset: {offset}, распределил шаблонов: {count} шт." ,
workGroupName , referenceDate , responseArea , offset , distributionResult . Count ) ;
distributedTemplates . AddRange ( distributionResult ) ;
}
else
{
// не нужно использовать часовой пояс РГ (используем часовой пояс У З ЕСПП)
logger . LogDebug ( "Н е нужно использовать часовой пояс РГ (используем часовой пояс У З ЕСПП)" ) ;
var offset = GetOffsetForDistributionByResponseArea ( ) ;
var templatesForDistribute = templates . Select ( t = > new TemplateNextRunDto ( t . Id , t . NextRun ) ) . ToList ( ) ;
var referenceDate = GetReferenceDate ( jobGroup , null ) ;
logger . LogInformation ( "Буду распределать шаблоны {count} шт, сгруппированные по РГ '{workGroup}', Н Е НУЖНО использовать часовой пояс З О . dateStart: {dateStart}, offset: {offset}, referenceDate: {referenceDate}" ,
templatesForDistribute . Count , workGroupName , dateStart , offset , referenceDate ) ;
var distributionResult = await templateDistributor . DistributeTemplatesAsync ( dateStart , durationDays , referenceDate , offset , templatesForDistribute , jobGroup . DistributionConfig . IsExcludeWeekends ) ;
logger . LogDebug ( "Закончил распределение для РГ '{workGroup}', referenceDate: {referenceDate}, utcOffset: {offset}, распределил шаблонов: {count} шт." , workGroupName , referenceDate , offset , distributionResult . Count ) ;
distributedTemplates . AddRange ( distributionResult ) ;
}
}
}
else
{
//не нужно группировать по РГ
logger . LogDebug ( "Н е нужно группировать по РГ" ) ;
if ( isResponseAreaTimeZone )
{
// использовать ЧАСОВОЙ ПОЯС РГ (нужно сгруппировать шаблоны по часовым поясам, затем отдельно распределить каждую группу)
// использовать ЧАСОВОЙ ПОЯС З О (нужно сгруппировать шаблоны по часовым поясам, затем отдельно распределить каждую группу)
logger . LogDebug ( "Использовать ЧАСОВОЙ ПОЯС З О (нужно сгруппировать шаблоны по часовым поясам, затем отдельно распределить каждую группу)" ) ;
// получаем для каждого шаблона З О и сразу группируем по З О
var templatesByResponseArea = new Dictionary < string? , List < Template > > ( ) ;
foreach ( var template in allTemplates )
{
var responseArea = await shortcodesService . ApplyShortcodesAsync ( template . Job ! . ResponseAreaMask , template ) ;
if ( responseArea = = null )
logger . LogWarning ( "Для шаблона {id}, с маской З О '{responseAreaMask}', не смог с помощью шорткода определить З О , шорткод сервис вернул З О : '{responseArea}'" ,
template . Id , template . Job ! . ResponseAreaMask , responseArea ) ;
// Добавляем шаблон в группу
if ( ! templatesByResponseArea . TryGetValue ( responseArea , out var templateList ) )
{
templateList = new List < Template > ( ) ;
templatesByResponseArea [ responseArea ] = templateList ;
}
templateList . Add ( template ) ;
}
logger . LogDebug ( "Групп для распределения: {count}" , templatesByResponseArea . Count ) ;
var dateStart = GetDateStart ( ) ;
// для каждой З О получить offset и распределить шаблоны
foreach ( var ( responseArea , templates ) in templatesByResponseArea )
{
var offset = GetOffsetForDistributionByResponseArea ( responseArea ) ;
var templatesForDistribute = templates . Select ( t = > new TemplateNextRunDto ( t . Id , t . NextRun ) ) . ToList ( ) ;
var referenceDate = GetReferenceDate ( jobGroup , responseArea ) ;
logger . LogInformation ( "Буду распределять шаблоны {count} шт, их Н Е НУЖНО группировать по РГ, НУЖНО использовать часовой пояс З О : {responseArea}, utcOffset: {offset}. dateStart: {dateStart}, referenceDate: {referenceDate}" ,
templatesForDistribute . Count , responseArea , offset , dateStart , referenceDate ) ;
var distributionResult = await templateDistributor . DistributeTemplatesAsync ( dateStart , durationDays , referenceDate , offset , templatesForDistribute , jobGroup . DistributionConfig . IsExcludeWeekends ) ;
logger . LogDebug ( "Закончил распределение для часового пояса З О {responseArea}, utcOffset: {offset}, referenceDate: {referenceDate}, распределил шаблонов: {count} шт." , responseArea , offset , referenceDate , distributionResult . Count ) ;
distributedTemplates . AddRange ( distributionResult ) ;
}
}
else
{
// не нужно использовать часовой пояс РГ
// не нужно использовать часовой пояс З О
logger . LogDebug ( "Н е нужно использовать часовой пояс З О . Распределю все шаблоны без группировки." ) ;
var offset = GetOffsetForDistribution ( ) ;
var offset = GetOffsetForDistributionByResponseArea ( ) ;
var templatesForDistribute = allTemplates . Select ( t = > new TemplateNextRunDto ( t . Id , t . NextRun ) ) . ToList ( ) ;
var dateStart = GetDateStart ( offset ) ;
var referenceDate = GetReferenceDate ( jobGroup , null ) ;
var dateStart = GetDateStart ( ) ;
logger . LogInformation ( "Буду распределать шаблоны {count} шт, их Н Е НУЖНО группировать по РГ, Н Е НУЖНО использовать часовой пояс РГ . dateStart в целевом часовом поясе : {dateStart}, offset: {offset}" ,
templatesForDistribute . Count , dateStart , offset ) ;
logger . LogInformation ( "Буду распределать шаблоны {count} шт, их Н Е НУЖНО группировать по РГ, Н Е НУЖНО использовать часовой пояс З О . dateStart: {dateStart}, offset: {offset}, referenceDate: {referenceDate} " ,
templatesForDistribute . Count , dateStart , offset , referenceDate );
distributedTemplates = await templateDistributor . DistributeTemplatesAsync ( dateStart , durationDays , jobGroup . R eferenceDate, offset , templatesForDistribute , jobGroup . DistributionConfig . IsExcludeWeekends ) ;
distributedTemplates = await templateDistributor . DistributeTemplatesAsync ( dateStart , durationDays , r eferenceDate, offset , templatesForDistribute , jobGroup . DistributionConfig . IsExcludeWeekends ) ;
}
}
logger . LogInformation ( "Закончил распределение шаблонов {count} шт. для jobGroupId: {jobGroupId}. Изменено: {changedCount}, без изменений: {oldCount}" ,
distributedTemplates . Count , jobGroupId , distributedTemplates . Count ( t = > t . NextRun ! = t . NextRunOld ) , distributedTemplates . Count ( t = > t . NextRun = = t . NextRunOld ) ) ;
return distributedTemplates ;
}
@@ -186,9 +303,9 @@ namespace PARR.DAL.NextRunServices
/// Получить часовую зону для распределения
/// </summary>
/// <returns></returns>
private TimeSpan GetOffsetForDistribution ( string? responseArea = null )
private TimeSpan GetOffsetForDistributionByResponseArea ( string? responseArea = null )
{
// или часова зона робота, или часовая зона РГ
// или часовая зона робота, или часовая зона РГ
if ( responseArea ! = null )
{
// возвращаем часовой пояс З О
@@ -222,19 +339,81 @@ namespace PARR.DAL.NextRunServices
/// <summary>
/// Получить дату начала распределения в целевом часовом поясе
/// Получить дату начала распределения
/// </summary>
/// <param name="offset"></param>
/// <returns></returns>
private DateOnly GetDateStart ( TimeSpan offset )
private DateOnly GetDateStart ( )
{
var dateStart = DateOnly. FromDateTime( DateTimeOffset. UtcNow. ToOffset( offset) . Date ) ;
logger. LogDebug( "Дата начала распределения (сегодня) в целевом часовом поясе {dateStart}, offset: {offset}", dateStart, offset) ;
//var dateStart = DateOnly. FromDateTime( DateTimeOffset. UtcNow. ToOffset( offset).Date) ;
// logger. LogDebug( "Дата начала распределения (сегодня) в целевом часовом поясе {dateStart}, offset: {offset}", dateStart, offset) ;
var dateStart = DateOnly . FromDateTime ( DateTime . UtcNow . Date ) ;
logger . LogDebug ( "Дата начала распределения (сегодня): {dateStart}" , dateStart ) ;
return dateStart ;
}
/// <summary>
/// Получить referenceDate для расчета с учетом часового пояса З О или без учета
/// </summary>
/// <param name="jobGroup"></param>
/// <param name="responseArea">Н е обзяательный параметр, нужен обязательно при расчете с учетом часового пояса З О </param>
/// <returns></returns>
private DateTimeOffset GetReferenceDate ( JobGroup jobGroup , string? responseArea )
{
// для того чтобы посчитать nextRun в часовом поясе З О , нужно пересчитать refDate чтобы понять в какое время хотел выполнять заказчик
logger . LogDebug ( "Исходный referenceDate UTC: {referenceDate}" , jobGroup . ReferenceDate ) ;
if ( jobGroup . IsResponseAreaTimezone )
{
// нужно вернуть с учетом часового пояса клиента сохранившего refDate (используется для расчета с учетом часового пояса З О )
var clientOffset = jobGroup . UserTimeZoneOffset ;
if ( ! clientOffset . HasValue )
{
logger . LogError ( "При расчете referenceDate для З О '{responseArea}', UserTimeZoneOffset=null. Вернул referenceDate {referenceDate} без смещения З О " ,
responseArea , jobGroup . ReferenceDate ) ;
return jobGroup . ReferenceDate ;
}
if ( responseArea = = null )
{
logger . LogError ( "При расчете referenceDate для З О '{responseArea}', responseArea=null. Вернул referenceDate {referenceDate} без смещения З О " ,
responseArea , jobGroup . ReferenceDate ) ;
return jobGroup . ReferenceDate ;
}
var responseAreaOffset = scheduleResponseAreaTimeOffsetService . GetByResponseAreaOrDefault ( responseArea ) . UtcTimeOffset ;
// = реф + офКлиента(который нажал в ГУИ сохранить)-офЗО
var resultOffset = clientOffset . Value - responseAreaOffset ;
var refDateWithResponseAreaOffset = jobGroup . ReferenceDate . Add ( resultOffset ) ;
logger . LogDebug ( "Клиент ожидает, что будет выполняться задача в каждой З О в {time}. referenceDate+clientOffset, {referenceDate}, {clientOffset}" ,
TimeOnly . FromDateTime ( jobGroup . ReferenceDate . Add ( clientOffset . Value ) . DateTime ) , jobGroup . ReferenceDate , clientOffset ) ;
logger . LogDebug ( "Итоговый referenceDate UTC: {resultRefDate}, для использования часовой зоны З О : '{responseArea}', добавлено времени {resultOffset} к исходному {refDate}" ,
refDateWithResponseAreaOffset , responseArea , resultOffset , jobGroup . ReferenceDate ) ;
logger . LogDebug ( "Если итоговый referenceDate UTC {refDateWithResponseAreaOffset} перевести в часовой пояс З О {responseAreaOffset}, то дата в часовом поясе З О будет {refDateTargetTz}" ,
refDateWithResponseAreaOffset , responseAreaOffset , refDateWithResponseAreaOffset . Add ( responseAreaOffset ) ) ;
return refDateWithResponseAreaOffset ;
}
else
{
// если Н Е НУЖНО в часовом поясе З О , то просто возвращаем refDate
logger . LogDebug ( "Итоговый referenceDate UTC: {refDate}, не нужно использовать часовой пояс З О " , jobGroup . ReferenceDate ) ;
return jobGroup . ReferenceDate ;
}
}
}
}