2026-01-22 11:46:05 +10:00
using Microsoft.EntityFrameworkCore ;
2025-12-22 17:03:41 +10:00
using Microsoft.Extensions.Logging ;
2026-01-21 12:19:27 +10:00
using PARR.Constants ;
using PARR.DAL.DomainServices.Shortcodes ;
using PARR.DAL.Models ;
using PARR.DAL.Models.Job ;
using PARR.DAL.NextRunServices.Models ;
2026-01-20 15:04:36 +10:00
using PARR.DAL.NextRunServices.Subservices ;
2025-12-22 17:03:41 +10:00
using PARR.DAL.Services.Interfaces ;
using PARR.DAL.Services.Interfaces.Job ;
namespace PARR.DAL.NextRunServices
{
internal class NextRunService : INextRunService
{
private readonly ILogger < NextRunService > logger ;
private readonly ITemplateService templateService ;
private readonly IJobGroupService jobGroupService ;
private readonly IEsppScheduleTransformService esppScheduleTransformService ;
2026-01-20 15:04:36 +10:00
private readonly ITemplateDistributor templateDistributor ;
2026-01-21 12:19:27 +10:00
private readonly IShortcodesService shortcodesService ;
2025-12-22 17:03:41 +10:00
public NextRunService (
ILogger < NextRunService > logger ,
ITemplateService templateService ,
IJobGroupService jobGroupService ,
2026-01-20 15:04:36 +10:00
IEsppScheduleTransformService esppScheduleTransformService ,
2026-01-21 12:19:27 +10:00
ITemplateDistributor templateDistributor ,
IShortcodesService shortcodesService
2025-12-22 17:03:41 +10:00
)
{
this . logger = logger ;
this . templateService = templateService ;
this . jobGroupService = jobGroupService ;
this . esppScheduleTransformService = esppScheduleTransformService ;
2026-01-20 15:04:36 +10:00
this . templateDistributor = templateDistributor ;
2026-01-21 12:19:27 +10:00
this . shortcodesService = shortcodesService ;
2025-12-22 17:03:41 +10:00
}
2026-01-21 12:19:27 +10:00
public async Task < List < TemplateNextRunResultDto > ? > GetNextRunForJobGroupWithAutoDistributionAsync ( Guid jobGroupId )
2025-12-22 17:03:41 +10:00
{
2026-01-21 12:19:27 +10:00
logger . LogInformation ( "Начинаю распределять шаблоны для группы работ {gobGroupId}" , jobGroupId ) ;
var jobGroup = await jobGroupService . Get ( )
. Include ( t = > t . DistributionConfig )
. ThenInclude ( t = > t . DistributionPeriod )
. AsNoTracking ( )
. FirstOrDefaultAsync ( t = > t . Id = = jobGroupId ) ;
if ( jobGroup = = null )
{
logger . LogError ( "Н е найдена группа работа с id: {id}" , jobGroupId ) ;
return null ;
}
if ( ! jobGroup . IsAutoDistributionEnabled | | jobGroup . DistributionConfig = = null )
{
logger . LogError ( "Группа работ id: {id} не подходит для автораспределения, у нее или отсутствуют настройки или не включено автораспределение. IsAutoDistributionEnabled: {IsAutoDistributionEnabled}. Есть конфиг: {DistributionConfig}" , jobGroupId , jobGroup . IsAutoDistributionEnabled , jobGroup . DistributionConfig ! = null ) ;
return null ;
}
logger . LogInformation ( "Параметры распределения. groupId: {groupId}, name: {groupName}, referenceDate: {referenceDate}, " +
"distributionPeriodName: {distributionPeriodName}, distributionPeriodDuration: {distributionPeriodDuration}, " +
"distributionPeriodType: {distributionPeriodType}, IsExcludeWeekends: {IsExcludeWeekends}, IsGroupingByWorkGroup: {IsGroupingByWorkGroup}" ,
jobGroupId , jobGroup . GroupName , jobGroup . ReferenceDate , jobGroup . DistributionConfig . DistributionPeriod . Name , jobGroup . DistributionConfig . DistributionPeriod . Duration , jobGroup . DistributionConfig . DistributionPeriod . Type ,
jobGroup . DistributionConfig . IsExcludeWeekends , jobGroup . DistributionConfig . IsGroupingByWorkGroup
) ;
var duration = GetDurationDays ( jobGroup . DistributionConfig ) ;
var dateStart = GetDateStart ( jobGroup . ReferenceDate ) ;
2026-01-26 14:29:47 +10:00
//получаем шаблоны только в статусе Used
2026-01-21 12:19:27 +10:00
var allTemplates = await templateService . Get ( )
. Include ( t = > t . Job )
2026-01-26 14:29:47 +10:00
. Where ( t = > t . Job ! . GroupId = = jobGroupId & & t . StatusTypeId = = TemplateStatusTypeEnum . Used )
2026-01-21 12:19:27 +10:00
. AsNoTracking ( )
. ToListAsync ( ) ;
if ( ! allTemplates . Any ( ) )
{
2026-01-26 14:29:47 +10:00
logger . LogInformation ( "В группе c ИД {jobGroupId} отсутствуют шаблоны в статусе Used" , jobGroupId ) ;
2026-01-21 12:19:27 +10:00
return null ;
}
2026-01-26 14:29:47 +10:00
logger . LogDebug ( "В с е г о шаблонов для распределения в статусе Used: {count} шт." , allTemplates . Count ) ;
2026-01-21 12:19:27 +10:00
var result = new List < TemplateNextRunResultDto > ( ) ;
if ( jobGroup . DistributionConfig . IsGroupingByWorkGroup )
{
// Нужно группировать по РГ
// список шаблонов с полученной из шорткода РГ
var templatesWithWorkGroup = new List < TemplateWithWorkGroupDto > ( ) ;
foreach ( var template in allTemplates )
{
var workGroupName = await shortcodesService . ApplyShortcodesAsync ( template . Job ! . WorkGroupMask , template ) ;
templatesWithWorkGroup . Add ( new TemplateWithWorkGroupDto ( template . Id , template . NextRun , workGroupName ) ) ;
}
var grouping = templatesWithWorkGroup . GroupBy ( t = > t . WorkGroup ) ;
//распределяем
foreach ( var workGroupTemplates in grouping )
{
logger . LogInformation ( "Начинаю распределять шаблоны для рабочей группы: {workGroup}. В с е г о шаблонов: {countTemplates} шт." , workGroupTemplates . Key , workGroupTemplates . Count ( ) ) ;
var templatesForDistribute = workGroupTemplates . Select ( t = > new TemplateNextRunDto ( t . Id , t . NextRun ) ) . ToList ( ) ;
var distributedResult = await templateDistributor . DistributeTemplatesAsync ( dateStart , duration , jobGroup . ReferenceDate , templatesForDistribute , jobGroup . DistributionConfig . IsExcludeWeekends ) ;
result . AddRange ( distributedResult ) ;
}
}
else
{
// Н е нужна группировка по РГ
// сразу распределяем все шаблоны
var templatesForDistribute = allTemplates . Select ( t = > new TemplateNextRunDto ( t . Id , t . NextRun ) ) . ToList ( ) ;
result = await templateDistributor . DistributeTemplatesAsync ( dateStart , duration , jobGroup . ReferenceDate , templatesForDistribute , jobGroup . DistributionConfig . IsExcludeWeekends ) ;
}
logger . LogInformation ( "Завершено распределение шаблонов для группы {jobGroupId}. В с е г о распределено шаблонов: {count} шт." , jobGroupId , result . Count ) ;
return result ;
2025-12-22 17:03:41 +10:00
}
2026-01-23 09:28:14 +10:00
public async Task < DateTimeOffset > GetNextRunForJobGroupWithEsppSchedulleAsync ( Guid jobGroupId )
{
var jobGroup = await jobGroupService . Get ( ) . AsNoTracking ( ) . FirstOrDefaultAsync ( t = > t . Id = = jobGroupId ) ;
if ( jobGroup = = null )
{
logger . LogError ( "Н е найдена группа работ с Id: {jobGroupId}." , jobGroupId ) ;
throw new ArgumentNullException ( nameof ( jobGroupId ) , $"Н е найдена группа работ с Id: {jobGroupId}" ) ;
}
// Берем из обычного расписания ЕСПП
return await esppScheduleTransformService . GetNextDateAsync ( jobGroupId , jobGroup . ReferenceDate ) ;
}
2026-01-21 15:36:54 +10:00
public async Task < DateTimeOffset > GetNextRunForNewTemplateAsync ( Guid jobGroupId )
2025-12-22 17:03:41 +10:00
{
2026-01-21 15:36:54 +10:00
// определяю это автораспределение или нет, вызываю соответствующий рассчет
var jobGroup = await jobGroupService . Get ( )
. Include ( t = > t . DistributionConfig )
. ThenInclude ( t = > t . DistributionPeriod )
. AsNoTracking ( )
. FirstOrDefaultAsync ( t = > t . Id = = jobGroupId ) ;
2025-12-22 17:03:41 +10:00
if ( jobGroup = = null )
{
logger . LogError ( "Н е найдена группа работ с Id: {jobGroupId}." , jobGroupId ) ;
throw new ArgumentNullException ( nameof ( jobGroupId ) , $"Н е найдена группа работ с Id: {jobGroupId}" ) ;
}
2026-01-21 15:36:54 +10:00
if ( jobGroup . IsAutoDistributionEnabled )
{
// тут автораспределение
if ( jobGroup . DistributionConfig = = null )
{
logger . LogError ( "Для группы работ {jobGroupId}, указано автораспределение, но отсутствует конфиг в таблице {GroupDistributionConfigs}" , jobGroupId , nameof ( JobGroupDistributionConfig ) ) ;
throw new ArgumentNullException ( nameof ( JobGroupDistributionConfig ) , $"Для jobGroupId: {jobGroupId} отсутствует конфигурация автораспределения в таблице {nameof(JobGroupDistributionConfig)}" ) ;
}
#region В ы н е с т и в о д т е л ь н ы й м е т о д , п о ч т и в с е п о в т о р я е т с я
var dateStart = GetDateStart ( jobGroup . ReferenceDate ) ;
var duration = GetDurationDays ( jobGroup . DistributionConfig ) ;
var result = await templateDistributor . GetValidNextRunForTemplateAsync ( dateStart ,
duration ,
jobGroup . ReferenceDate ,
new TemplateNextRunDto ( Guid . Empty , null ) ,
//TODO: тут пока не получаем список шаблонов, но позже, когда будем строить каждый раз план, нужно будет сюда передавать список связанных шаблонов
new List < TemplateNextRunDto > ( ) ,
jobGroup . DistributionConfig . IsExcludeWeekends ,
isNew : true
) ;
#endregion
logger . LogDebug ( "Получил nextRun {nextRun} по jobGroupId {jobGroupId} для нового шаблона, тип расписания: автораспределение" , result . NextRun , jobGroupId ) ;
return result . NextRun ;
}
else
{
// тут расписание ЕСПП
var nextRun = await esppScheduleTransformService . GetNextDateAsync ( jobGroup . Id , jobGroup . ReferenceDate ) ;
logger . LogDebug ( "Получил nextRun {nextRun} по jobGroupId {jobGroupId}, тип расписания ЕСПП" , nextRun , jobGroupId ) ;
return nextRun ;
}
2025-12-22 17:03:41 +10:00
}
2026-01-21 15:36:54 +10:00
public async Task < DateTimeOffset > GetNextRunForTemplateAsync ( Guid templateId , bool isNew )
2025-12-22 17:03:41 +10:00
{
var template = await templateService . Get ( )
2026-01-21 15:36:54 +10:00
. Include ( t = > t . Job )
. ThenInclude ( t = > t . Group ) . ThenInclude ( t = > t . DistributionConfig ) . ThenInclude ( t = > t . DistributionPeriod )
2025-12-22 17:03:41 +10:00
. AsNoTracking ( )
. FirstOrDefaultAsync ( t = > t . Id = = templateId ) ;
if ( template = = null )
{
2026-01-21 15:36:54 +10:00
logger . LogError ( "Н е найден шаблон с Id: {templateId}." , templateId ) ;
throw new ArgumentNullException ( nameof ( templateId ) , $"Н е найден шаблон с Id: {templateId}" ) ;
2025-12-22 17:03:41 +10:00
}
if ( template . Job ! . Group ! . IsAutoDistributionEnabled = = true )
{
// включено автораспределение
2026-01-21 15:36:54 +10:00
if ( template . Job ! . Group . DistributionConfig = = null )
{
logger . LogError ( "Для шаблона {templateId}, jobGroupId {jobGroupId}, указано автораспределение, но отсутствует конфиг в таблице {GroupDistributionConfigs}" , template . Id , template . Job . GroupId , nameof ( JobGroupDistributionConfig ) ) ;
throw new ArgumentNullException ( nameof ( JobGroupDistributionConfig ) , $"Для jobGroupId: {template.Job.GroupId} отсутствует конфигурация автораспределения в таблице {nameof(JobGroupDistributionConfig)}" ) ;
}
#region В ы н е с т и в о д т е л ь н ы й м е т о д , п о ч т и в с е п о в т о р я е т с я
var dateStart = GetDateStart ( template . Job ! . Group . ReferenceDate ) ;
var duration = GetDurationDays ( template . Job ! . Group . DistributionConfig ) ;
var result = await templateDistributor . GetValidNextRunForTemplateAsync ( dateStart ,
duration ,
template . Job . Group . ReferenceDate ,
new TemplateNextRunDto ( template . Id , template . NextRun ) ,
//TODO: тут пока не получаем список шаблонов, но позже, когда будем строить каждый раз план, нужно будет сюда передавать список связанных шаблонов
new List < TemplateNextRunDto > ( ) ,
template . Job . Group . DistributionConfig . IsExcludeWeekends ,
isNew
) ;
#endregion
logger . LogDebug ( "Получил nextRun {nextRun} по templateId {templateId}, isNew: {isNew}, тип расписания: автораспределение" , result . NextRun , templateId , isNew ) ;
return result . NextRun ;
2025-12-22 17:03:41 +10:00
}
else
{
// считаем как ЕСПП
2026-01-21 15:36:54 +10:00
var nextRun = await esppScheduleTransformService . GetNextDateAsync ( template . Job . Group . Id , template . Job . Group . ReferenceDate ) ;
logger . LogDebug ( "Получил nextRun {nextRun} по templateId {templateId}, isNew: {isNew}, тип расписания: ЕСПП" , nextRun , templateId , isNew ) ;
return nextRun ;
2025-12-22 17:03:41 +10:00
}
}
2026-01-21 12:19:27 +10:00
2026-01-21 15:36:54 +10:00
2026-01-21 12:19:27 +10:00
/// <summary>
/// Получить продолжительность в днях
/// </summary>
/// <param name="config"></param>
/// <returns></returns>
2026-01-26 14:29:47 +10:00
public int GetDurationDays ( JobGroupDistributionConfig config )
2026-01-21 12:19:27 +10:00
{
var period = config . DistributionPeriod ;
var periodType = period ! . Type ;
int . TryParse ( period . Duration , out var duration ) ;
var periodDays = duration ;
if ( periodType = = DistributionPeriodTypeEnum . Day . ToString ( ) )
periodDays = duration ;
if ( periodType = = DistributionPeriodTypeEnum . Month . ToString ( ) )
// в месяце 30 дней, duration*30
periodDays = duration * 30 ;
if ( periodType = = DistributionPeriodTypeEnum . Year . ToString ( ) )
// в году 365 дней, duration*365
periodDays = duration * 365 ;
logger . LogDebug ( "Период распределения: {name}, duration: {duration}, type: {type}. Итого в днях: {days}" , period . Name , period . Duration , period . Type , periodDays ) ;
if ( periodDays = = 0 )
logger . LogWarning ( "Период распределения 0 дней. Неверный конфиг распределения в таблице {table}. {name}, duration: {duration}, type: {type}" , nameof ( DistributionPeriod ) , period . Name , period . Duration , period . Type ) ;
return periodDays ;
}
/// <summary>
/// Получить дату начала распределения
/// </summary>
/// <param name="referenceDate"></param>
/// <returns></returns>
private DateOnly GetDateStart ( DateTimeOffset referenceDate )
{
var today = DateTime . UtcNow ;
if ( today < referenceDate )
return DateOnly . FromDateTime ( referenceDate . Date ) ;
else
return DateOnly . FromDateTime ( today . Date ) ;
}
2026-01-26 14:29:47 +10:00
2026-01-28 14:40:40 +10:00
public async Task < List < DateOnly > > GetWorkDaysAsync ( DateOnly start , DateOnly end , bool excludeWeekends , DateTimeOffset referenceDate )
2026-01-26 14:29:47 +10:00
{
2026-01-28 14:40:40 +10:00
//return await templateDistributor.GetWorkDaysAsync(start, end, excludeWeekends);
2026-01-28 16:19:19 +10:00
return await templateDistributor . GetWorkDaysV2Async ( start , end , excludeWeekends , referenceDate ) ;
2026-01-28 14:40:40 +10:00
}
public async Task < HashSet < DateOnly > > GetWeekendsAsync ( DateOnly startDate , DateOnly endDate , DateTimeOffset referenceDate )
{
2026-01-28 16:19:19 +10:00
return await templateDistributor . GetWeekendsV2Async ( startDate , endDate , referenceDate ) ;
2026-01-26 14:29:47 +10:00
}
2025-12-22 17:03:41 +10:00
}
}