@@ -4,11 +4,14 @@ using PARR.BLL.Domain.Mq;
using PARR.BLL.Services.Interfaces ;
using PARR.Common.Domain ;
using PARR.Constants ;
using PARR.DAL.Contracts ;
using PARR.DAL.DomainServices.Interfaces ;
using PARR.DAL.Models ;
using PARR.DAL.Models.Job ;
using PARR.DAL.Models.Unit ;
using PARR.DAL.Services.Interfaces ;
using PARR.DAL.Services.Interfaces.Job ;
using PARR.DAL.Services.Interfaces.Unit ;
using PARR.DAL.TransformServices ;
using PARR.TemplateMatcher.Settings ;
using System.Text.Json ;
@@ -25,29 +28,41 @@ namespace PARR.TemplateMatcher
private readonly ILogger < TemplateMatcher > logger ;
private readonly IUnitFilterService unitFilterService ;
private readonly IUnitInUnitService unitInUnitService ;
private readonly IUnitInValueService unitInValueService ; // Добавлено
private readonly IUnitService unitService ; // Добавлено
private readonly MqSettings mqSettings ;
private readonly IMqService mqService ;
private readonly ITemplateService templateService ;
private readonly IJobService jobService ;
private readonly IJobGroupService jobGroupService ;
private readonly IShortcodesService shortcodesService ;
private readonly IEsppScheduleTransformService esppScheduleTransformService ;
public TemplateMatcher (
ILogger < TemplateMatcher > logger ,
IUnitFilterService unitFilterService ,
IUnitInUnitService unitInUnitService ,
IUnitInValueService unitInValueService ,
IUnitService unitService ,
MqSettings mqSettings ,
IMqService mqService ,
ITemplateService templateService ,
IJobService jobService ,
IJobGroupService jobGroupService ,
IShortcodesService shortcodesService ,
IEsppScheduleTransformService esppScheduleTransformService )
{
this . logger = logger ;
this . unitFilterService = unitFilterService ;
this . unitInUnitService = unitInUnitService ;
this . unitInValueService = unitInValueService ;
this . unitService = unitService ;
this . mqSettings = mqSettings ;
this . mqService = mqService ;
this . templateService = templateService ;
this . jobService = jobService ;
this . jobGroupService = jobGroupService ;
this . shortcodesService = shortcodesService ;
this . esppScheduleTransformService = esppScheduleTransformService ;
}
@@ -66,12 +81,393 @@ namespace PARR.TemplateMatcher
return ;
}
// Проверяем, является ли Job "групповым"
bool isGroupJob = job . Group ! = null & & job . Group . GroupType ? . Code = = JobGroupTypesEnum . Group ;
if ( isGroupJob & & job . Group . GroupingUnitFieldId . HasValue )
{
logger . LogInformation ( "Job {JobId} является групповым. Используйте SyncTemplatesForJobGroup для синхронизации." , jobId ) ;
return ; // Ничего не делаем для группового Job
}
else
{
await SyncSimpleTemplatesAsync ( job , expectedUnitIds , initiator ) ;
}
logger . LogInformation ( "Синхронизация завершена для JobId {JobId}." , jobId ) ;
}
public async Task SyncTemplatesForJobGroup ( Guid jobGroupId , HistoryInitiator initiator )
{
logger . LogDebug ( "Начало синхронизации шаблонов для JobGroup {JobGroupId}" , jobGroupId ) ;
// 1. Получить JobGroup и связанные Job'ы
var jobGroup = await jobGroupService . Get ( )
. AsNoTracking ( ) // Добавлено
. Include ( jg = > jg . Jobs )
. ThenInclude ( j = > j . AutoControl )
. Include ( jg = > jg . Jobs )
. ThenInclude ( j = > j . UnitFilters )
. ThenInclude ( uf = > uf . RelationshipFilters )
. FirstOrDefaultAsync ( jg = > jg . Id = = jobGroupId ) ;
if ( jobGroup = = null | | jobGroup . Jobs = = null | | ! jobGroup . Jobs . Any ( ) )
{
logger . LogWarning ( "JobGroup {JobGroupId} не найден или не содержит Job'ов." , jobGroupId ) ;
return ;
}
var jobsInGroup = jobGroup . Jobs . ToList ( ) ;
// 2. Найти Job с максимальным MaxValueRelationships
var maxJob = jobsInGroup
. Where ( j = > j . MaxValueRelationships . HasValue )
. OrderByDescending ( j = > j . MaxValueRelationships )
. FirstOrDefault ( ) ;
if ( maxJob = = null )
{
logger . LogWarning ( "В JobGroup {JobGroupId} не найдено Job с установленным MaxValueRelationships." , jobGroupId ) ;
// Возможно, нужно обработать случай, когда MaxValueRelationships не установлено ни у одного Job.
// Пока просто выйдем.
return ;
}
// Проверяем, что UnitFilters и RelationshipFilters загружены
if ( maxJob . UnitFilters = = null )
{
logger . LogWarning ( "Job {JobId} не содержит UnitFilters." , maxJob . Id ) ;
// Продолжить с пустыми фильтрами или выйти?
// Пока продолжим с пустым списком.
}
logger . LogDebug ( "Используется Job {JobId} с максимальным MaxValueRelationships ({MaxValue}) для фильтрации." , maxJob . Id , maxJob . MaxValueRelationships ) ;
// 3. Использовать фильтры maxJob для получения expectedUnitIds
var expectedUnitIds = await unitFilterService . GetUnitsIdByJobFilterAsync ( maxJob . Id ) ;
if ( expectedUnitIds = = null | | ! expectedUnitIds . Any ( ) )
{
logger . LogInformation ( "Для JobGroup {JobGroupId} фильтры не дали Unit'ов." , jobGroupId ) ;
// Деактивировать все шаблоны для всех Job в группе?
// Пока просто выйдем.
return ;
}
// 4. Отфильтровать expectedUnitIds по GroupingUnitFieldId
if ( ! jobGroup . GroupingUnitFieldId . HasValue )
{
logger . LogError ( "JobGroup {JobGroupId} не имеет GroupingUnitFieldId, необходимого для группировки." , jobGroupId ) ;
return ;
}
var groupingFieldId = jobGroup . GroupingUnitFieldId . Value ;
// --- ИСПРАВЛЕНИЕ: Разбиваем запрос на части ---
// Загрузить UnitValues для отфильтрованных юнитов, чтобы проверить GroupingUnitFieldId
var filteredUnits = await unitService . Get ( )
. AsNoTracking ( ) // Добавлено
. AsSplitQuery ( ) // Добавлено
. Include ( t = > t . UnitValues ) // Добавлено
. ThenInclude ( t = > t . Value ) // Добавлено
. Where ( u = > expectedUnitIds . Contains ( u . Id ) )
. ToListAsync ( ) ; // Сначала загружаем Unit'ы
// Затем фильтруем их UnitValues и собираем UnitId
var unitIdsWithValidGroupingFieldSet = filteredUnits
. Where ( u = > u . UnitValues . Any ( uv = > uv . FieldId = = groupingFieldId & & uv . Value ! = null & & ! string . IsNullOrEmpty ( uv . Value . Value ) ) )
. Select ( u = > u . Id )
. ToHashSet ( ) ; // Создаем HashSet
logger . LogDebug ( "После фильтрации по GroupingUnitFieldId осталось {Count} юнитов." , unitIdsWithValidGroupingFieldSet . Count ) ;
if ( ! unitIdsWithValidGroupingFieldSet . Any ( ) )
{
logger . LogInformation ( "После фильтрации по GroupingUnitFieldId в JobGroup {JobGroupId} не осталось юнитов." , jobGroupId ) ;
return ;
}
// --- НОВАЯ ЛОГИКА: Применение RelationshipFilters ---
// Найти связи UnitInUnit для unitIdsWithValidGroupingFieldSet
var potentialUnitInUnitLinks = await unitInUnitService . Get ( )
. AsNoTracking ( ) // Добавлено
. Where ( link = > unitIdsWithValidGroupingFieldSet . Contains ( link . ChildUnitId ) )
. ToListAsync ( ) ;
logger . LogDebug ( "Найдено {Count} потенциальных связей UnitInUnit до применения RelationshipFilters." , potentialUnitInUnitLinks . Count ) ;
// Получить RelationshipFilters из maxJob
var relationshipFilters = maxJob . UnitFilters ? . SelectMany ( uf = > uf . RelationshipFilters ) . ToList ( ) ? ? new List < JobRelationshipFilter > ( ) ;
if ( relationshipFilters . Any ( ) )
{
// Загрузить UnitInValue для ParentUnitId и ChildUnitId из potentialUnitInUnitLinks
var allParentIds = potentialUnitInUnitLinks . Select ( l = > l . ParentUnitId ) . ToHashSet ( ) ;
var allChildIds = potentialUnitInUnitLinks . Select ( l = > l . ChildUnitId ) . ToHashSet ( ) ;
var parentUnitValues = await unitInValueService . Get ( )
. AsNoTracking ( ) // Добавлено
. Include ( uv = > uv . Field ) // Добавлено
. Include ( uv = > uv . Value ) // Добавлено
. Where ( uv = > allParentIds . Contains ( uv . UnitId ) )
. ToListAsync ( ) ;
var childUnitValues = await unitInValueService . Get ( )
. AsNoTracking ( ) // Добавлено
. Include ( uv = > uv . Field ) // Добавлено
. Include ( uv = > uv . Value ) // Добавлено
. Where ( uv = > allChildIds . Contains ( uv . UnitId ) )
. ToListAsync ( ) ;
// Сгруппировать значения по UnitId для быстрого доступа
var parentValuesMap = parentUnitValues
. GroupBy ( uv = > uv . UnitId )
. ToDictionary ( g = > g . Key , g = > g . ToList ( ) ) ;
var childValuesMap = childUnitValues
. GroupBy ( uv = > uv . UnitId )
. ToDictionary ( g = > g . Key , g = > g . ToList ( ) ) ;
// Применить фильтры к связям
var filteredUnitInUnitLinks = new List < UnitInUnit > ( ) ;
foreach ( var link in potentialUnitInUnitLinks )
{
bool linkMatchesAllFilters = true ;
foreach ( var rf in relationshipFilters )
{
var valuesToCheck = rf . IsParent ? parentValuesMap . GetValueOrDefault ( link . ParentUnitId , new List < UnitInValue > ( ) ) : childValuesMap . GetValueOrDefault ( link . ChildUnitId , new List < UnitInValue > ( ) ) ;
bool filterMatch = valuesToCheck . Any ( uv = >
uv . FieldId = = rf . FieldId & &
uv . Value ! = null & &
uv . Value . Value ! = null & &
uv . Value . Value . Contains ( rf . ValueMask ? ? "" , StringComparison . OrdinalIgnoreCase )
) ;
if ( rf . IsInverse )
filterMatch = ! filterMatch ;
if ( ! filterMatch )
{
linkMatchesAllFilters = false ;
break ; // Н е подходит под один из фильтров
}
}
if ( linkMatchesAllFilters )
{
filteredUnitInUnitLinks . Add ( link ) ;
}
}
potentialUnitInUnitLinks = filteredUnitInUnitLinks ; // Заменяем на отфильтрованные
}
logger . LogDebug ( "Осталось {Count} связей UnitInUnit после применения RelationshipFilters." , potentialUnitInUnitLinks . Count ) ;
// 6. Сгруппировать ChildUnitId по ParentUnitId (региональный ЭК) из ОТФИЛЬТРОВАННЫХ связей
var groupedByRegional = potentialUnitInUnitLinks
. GroupBy ( link = > link . ParentUnitId )
. ToDictionary ( g = > g . Key , g = > g . Select ( l = > l . ChildUnitId ) . ToList ( ) ) ;
logger . LogDebug ( "Сформировано {Count} групп по региональным юнитам." , groupedByRegional . Count ) ;
// 7. Разбить каждую группу и сопоставить с Job
// Для каждого регионального юнита и е г о дочерних юнитов:
foreach ( var kvp in groupedByRegional )
{
var regionalUnitId = kvp . Key ;
var childUnitIds = kvp . Value ;
logger . LogDebug ( "Обработка регионального юнита {RegionalUnitId} с {Count} дочерними юнитами." , regionalUnitId , childUnitIds . Count ) ;
// Применяем ограничение MaxValueRelationships maxJob
int maxValueForSplitting = maxJob . MaxValueRelationships . Value ; // Уже проверили, что не null
var childUnitGroups = childUnitIds
. Select ( ( id , index ) = > new { id , groupIndex = index / maxValueForSplitting } )
. GroupBy ( x = > x . groupIndex )
. Select ( g = > g . Select ( x = > x . id ) . ToList ( ) )
. ToList ( ) ;
logger . LogDebug ( "Региональный юнит {RegionalUnitId}: разбит на {GroupCount} подгрупп." , regionalUnitId , childUnitGroups . Count ) ;
// Для каждой подгруппы:
for ( int i = 0 ; i < childUnitGroups . Count ; i + + )
{
var subGroup = childUnitGroups [ i ] ;
var subGroupSize = subGroup . Count ;
logger . LogDebug ( "Обработка подгруппы {Index} регионального юнита {RegionalUnitId}, размер {Size}." , i , regionalUnitId , subGroupSize ) ;
// 8. Найти подходящий Job для подгруппы
// Попробовать найти Job с MaxValueRelationships, равным размеру подгруппы
var targetJob = jobsInGroup
. Where ( j = > j . MaxValueRelationships . HasValue & & j . MaxValueRelationships . Value = = subGroupSize )
. FirstOrDefault ( ) ;
if ( targetJob = = null )
{
// Найти Job с MaxValueRelationships >= размеру подгруппы, но минимально подходящее
targetJob = jobsInGroup
. Where ( j = > j . MaxValueRelationships . HasValue & & j . MaxValueRelationships . Value > = subGroupSize )
. OrderBy ( j = > j . MaxValueRelationships . Value )
. FirstOrDefault ( ) ;
}
if ( targetJob = = null )
{
// Если подходящий Job не найден, используем maxJob
targetJob = maxJob ;
logger . LogDebug ( "Для подгруппы {Index} регионального юнита {RegionalUnitId} не найден подходящий Job, используем maxJob {MaxJobId}." , i , regionalUnitId , maxJob . Id ) ;
}
else
{
logger . LogDebug ( "Для подгруппы {Index} регионального юнита {RegionalUnitId} выбран Job {TargetJobId} с MaxValueRelationships {MaxValue}." , i , regionalUnitId , targetJob . Id , targetJob . MaxValueRelationships ) ;
}
// 9. Загрузить существующие шаблоны для targetJob, связанные с regionalUnitId
var existingTemplatesForRegional = await templateService . Get ( )
. AsNoTracking ( ) // Добавлено
. Include ( t = > t . UnitsInTemplate )
. Where ( t = > t . JobId = = targetJob . Id & & t . UnitId = = regionalUnitId & & t . Index = = i )
. ToListAsync ( ) ;
Template existingTemplateForSubGroup = existingTemplatesForRegional . FirstOrDefault ( ) ;
if ( existingTemplateForSubGroup ! = null )
{
// Проверить, изменились ли юниты
var existingUnitIds = existingTemplateForSubGroup . UnitsInTemplate . Select ( uit = > uit . UnitId ) . ToHashSet ( ) ;
var newUnitIds = subGroup . ToHashSet ( ) ;
if ( existingUnitIds . SetEquals ( newUnitIds ) )
{
logger . LogDebug ( "Шаблон {TemplateId} (Job {JobId}, Regional {RegionalId}, Index {Index}) актуален." , existingTemplateForSubGroup . Id , targetJob . Id , regionalUnitId , i ) ;
// Возможно, нужно обновить имя или статус, если изменились фильтры или AutoControl
// Пока оставим как есть, если структура не изменилась.
}
else
{
logger . LogDebug ( "Шаблон {TemplateId} (Job {JobId}, Regional {RegionalId}, Index {Index}) требует обновления юнитов." , existingTemplateForSubGroup . Id , targetJob . Id , regionalUnitId , i ) ;
// Обновляем существующий шаблон
await UpdateTemplateUnitsAsync ( existingTemplateForSubGroup , subGroup , targetJob , initiator ) ;
}
}
else
{
// --- ИСПОЛЬЗУЕМ СТАНДАРТНЫЙ МЕТОД TryReuseOneUnusedTemplateAsync ---
var reusableTemplate = await TryReuseOneUnusedTemplateAsync ( targetJob . Id , regionalUnitId , initiator ) ; // передаём regionalUnitId как unitId для старого метода
if ( reusableTemplate ! = null ) // если захват успешен
{
logger . LogInformation ( "Переиспользован шаблон {TemplateId} для Job {JobId}, регионального юнита {RegionalId}, Index {Index}." , reusableTemplate . Id , targetJob . Id , regionalUnitId , i ) ;
// Подготовить сообщение для TemplateUpdater с новыми параметрами
var expectedName = await GetNormalizedTemplateNameAsync ( targetJob , regionalUnitId , i ) ;
var nextRun = await GetNextRunAsync ( targetJob ) ; // всегда пересчитываем для нового назначения
var updateRequest = new TemplateUpdaterMq
{
TemplateId = reusableTemplate . Id , // ID захваченного шаблона
JobId = targetJob . Id , // Новый JobId
UnitId = regionalUnitId , // Новый UnitId (региональный)
Name = expectedName ,
IsActiveTemplate = targetJob . AutoControl ? . InitUsedTemplateState ? ? DefaultUsedTemplateState ,
IsActiveSchedule = targetJob . AutoControl ? . InitUsedScheduleState ? ? DefaultUsedScheduleState ,
StatusTypeId = TemplateStatusTypeEnum . Used ,
Initiator = initiator ,
NextRun = nextRun ,
Index = i , // Новый Index
UnitsInTemplate = subGroup // Новые UnitsInTemplate
} ;
await SendTemplateUpdateMessage ( updateRequest ) ;
}
else
{
// Создать новый шаблон
logger . LogDebug ( "Создание нового шаблона для Job {JobId}, Regional {RegionalId}, Index {Index}, с {Count} юнитами." , targetJob . Id , regionalUnitId , i , subGroup . Count ) ;
await CreateGroupedTemplateAsync ( targetJob . Id , regionalUnitId , subGroup , i , initiator ) ;
}
}
}
}
// 10. Деактивировать шаблоны, которые больше не соответствуют ни одной подгруппе
// Это требует с б о р а всех ожидаемых (JobId, UnitId, Index) и сравнения с существующими.
// Соберем ожидаемые комбинации
var expectedTemplateKeys = new HashSet < ( Guid JobId , Guid UnitId , int Index ) > ( ) ;
foreach ( var kvp in groupedByRegional )
{
var regionalUnitId = kvp . Key ;
var childUnitIds = kvp . Value ;
int maxValueForSplitting = maxJob . MaxValueRelationships . Value ;
var childUnitGroups = childUnitIds
. Select ( ( id , index ) = > new { id , groupIndex = index / maxValueForSplitting } )
. GroupBy ( x = > x . groupIndex )
. Select ( g = > g . Select ( x = > x . id ) . ToList ( ) )
. ToList ( ) ;
for ( int i = 0 ; i < childUnitGroups . Count ; i + + )
{
var subGroup = childUnitGroups [ i ] ;
var subGroupSize = subGroup . Count ;
var targetJob = jobsInGroup
. Where ( j = > j . MaxValueRelationships . HasValue & & j . MaxValueRelationships . Value = = subGroupSize )
. FirstOrDefault ( ) ;
if ( targetJob = = null )
{
targetJob = jobsInGroup
. Where ( j = > j . MaxValueRelationships . HasValue & & j . MaxValueRelationships . Value > = subGroupSize )
. OrderBy ( j = > j . MaxValueRelationships . Value )
. FirstOrDefault ( ) ;
}
if ( targetJob = = null ) targetJob = maxJob ;
// Исправлено: используем конкретные типы для кортежа
expectedTemplateKeys . Add ( ( targetJob . Id , regionalUnitId , i ) ) ;
}
}
// Загрузить *все* шаблоны для всех Job в группе, связанные с региональными юнитами из групп
var allRegionalUnitIds = groupedByRegional . Keys . ToHashSet ( ) ;
var allJobIdsInGroup = jobsInGroup . Select ( j = > j . Id ) . ToHashSet ( ) ;
var allExistingTemplatesInGroup = await templateService . Get ( )
. AsNoTracking ( ) // Добавлено
. Include ( t = > t . UnitsInTemplate )
. Where ( t = > allJobIdsInGroup . Contains ( t . JobId ) & & allRegionalUnitIds . Contains ( t . UnitId ) )
. ToListAsync ( ) ;
foreach ( var existingTemplate in allExistingTemplatesInGroup )
{
// Исправлено: используем конкретные типы для ключа
var key = ( existingTemplate . JobId , existingTemplate . UnitId , existingTemplate . Index ? ? - 1 ) ;
if ( ! expectedTemplateKeys . Contains ( key ) )
{
logger . LogInformation ( "Деактивация лишнего шаблона {TemplateId} (Job {JobId}, Regional {UnitId}, Index {Index})." , existingTemplate . Id , existingTemplate . JobId , existingTemplate . UnitId , existingTemplate . Index ) ;
await DeactivateTemplateAsync ( existingTemplate , existingTemplate . JobId , initiator ) ;
}
}
logger . LogInformation ( "Синхронизация шаблонов завершена для JobGroup {JobGroupId}." , jobGroupId ) ;
}
private async Task SyncSimpleTemplatesAsync ( Job job , HashSet < Guid > expectedUnitIds , HistoryInitiator initiator )
{
logger . LogDebug ( "Синхронизация обычных шаблонов для JobId {JobId}" , job . Id ) ;
var existingTemplates = await templateService . Get ( )
. Where ( t = > t . JobId = = jobId )
. AsNoTracking ( ) // Добавлено
. Where ( t = > t . JobId = = job . Id )
. ToListAsync ( ) ;
logger . LogDebug ( "JobId {JobId}: {Expected} ожидаемых UnitId, {Existing} существующих шаблонов." ,
jobId , expectedUnitIds . Count , existingTemplates . Count ) ;
job. Id , expectedUnitIds . Count , existingTemplates . Count ) ;
// Обработка случая: фильтр вернул 0 UnitId → деактивировать В С Е шаблоны
if ( ! expectedUnitIds . Any ( ) )
@@ -79,19 +475,19 @@ namespace PARR.TemplateMatcher
if ( existingTemplates . Any ( ) )
{
logger . LogInformation ( "Для JobId {JobId} фильтры не дали Unit'ов — будет деактивировано {Count} шаблонов." ,
jobId , existingTemplates . Count ) ;
job. Id , existingTemplates . Count ) ;
foreach ( var template in existingTemplates )
{
await DeactivateTemplateAsync ( template , jobId , initiator ) ;
await DeactivateTemplateAsync ( template , job. Id , initiator ) ;
}
}
else
{
logger . LogInformation ( "Для JobId {JobId} нет Unit'ов по фильтрам и нет существующих шаблонов — синхронизация завершена." , jobId ) ;
logger . LogInformation ( "Для JobId {JobId} нет Unit'ов по фильтрам и нет существующих шаблонов — синхронизация завершена." , job. Id ) ;
}
logger . LogInformation ( "Синхронизация завершена для JobId {JobId} (фильтр пуст)." , jobId ) ;
logger . LogInformation ( "Синхронизация завершена для JobId {JobId} (фильтр пуст)." , job. Id ) ;
return ;
}
@@ -102,12 +498,13 @@ namespace PARR.TemplateMatcher
foreach ( var template in templatesToDeactivate )
{
await DeactivateTemplateAsync ( template , jobId , initiator ) ;
await DeactivateTemplateAsync ( template , job. Id , initiator ) ;
}
// Перечитываем шаблоны после деактивации
existingTemplates = await templateService . Get ( )
. Where ( t = > t . JobId = = jobId )
. AsNoTracking ( ) // Добавлено
. Where ( t = > t . JobId = = job . Id )
. ToListAsync ( ) ;
var unitToTemplate = existingTemplates . ToDictionary ( t = > t . UnitId , t = > t ) ;
@@ -121,7 +518,7 @@ namespace PARR.TemplateMatcher
foreach ( var unitId in unitIdsMissingTemplates )
{
var reused = await TryReuseOneUnusedTemplateAsync ( jobId , unitId , initiator ) ;
var reused = await TryReuseOneUnusedTemplateAsync ( job. Id , unitId , initiator ) ;
if ( reused ! = null )
{
logger . LogInformation ( "Переиспользован шаблон {TemplateId} для UnitId {UnitId}." , reused . Id , unitId ) ;
@@ -132,14 +529,15 @@ namespace PARR.TemplateMatcher
var updateRequest = new TemplateUpdaterMq
{
TemplateId = reused . Id ,
JobId = jobId ,
JobId = job. Id ,
UnitId = unitId ,
Name = expectedName ,
IsActiveTemplate = job . AutoControl ? . InitUsedTemplateState ? ? DefaultUsedTemplateState ,
IsActiveSchedule = job . AutoControl ? . InitUsedScheduleState ? ? DefaultUsedScheduleState ,
StatusTypeId = TemplateStatusTypeEnum . Used ,
Initiator = initiator ,
NextRun = nextRun
NextRun = nextRun ,
UnitsInTemplate = new List < Guid > ( )
} ;
await SendTemplateUpdateMessage ( updateRequest ) ;
@@ -165,17 +563,17 @@ namespace PARR.TemplateMatcher
// Создание новых шаблонов
foreach ( var unitId in unitIdsToCreateFresh )
{
await SendTemplateGeneratorMessageAsync ( jobId , unitId , initiator ) ;
await SendTemplateGeneratorMessageAsync ( job. Id , unitId , initiator ) ;
}
logger . LogInformation ( "Синхронизация завершена для JobId {JobId}." , jobId ) ;
}
public async Task UpdateTemplatesForJob ( Guid jobId , HistoryInitiator initiator )
{
logger . LogDebug ( "Начало обновления шаблонов для JobId {JobId}" , jobId ) ;
var existingTemplates = await templateService . Get ( )
. AsNoTracking ( ) // Добавлено
. Where ( t = > t . JobId = = jobId )
. ToListAsync ( ) ;
@@ -251,7 +649,8 @@ namespace PARR.TemplateMatcher
NextRun = nextRun ,
Index = template . Index ,
StatusTypeId = targetStatus ,
Initiator = initiator
Initiator = initiator ,
UnitsInTemplate = template . UnitsInTemplate . Select ( t = > t . UnitId ) . ToList ( )
} ;
await SendTemplateUpdateMessage ( updateRequest ) ;
@@ -261,6 +660,66 @@ namespace PARR.TemplateMatcher
}
private async Task UpdateTemplateUnitsAsync ( Template template , List < Guid > newUnitIds , Job job , HistoryInitiator initiator )
{
// Обновляем шаблон как "Updating"
template . StatusTypeId = TemplateStatusTypeEnum . Updating ;
template . DateModified = DateTimeOffset . UtcNow ;
if ( ! await templateService . CommitAsync ( initiator ) )
{
logger . LogError ( "Н е удалось перевести шаблон {TemplateId} в Updating для обновления юнитов." , template . Id ) ;
return ;
}
// Здесь нужно обновить UnitsInTemplate.
// Это может быть сделано через TemplateUpdaterMq, если он поддерживает передачу нового списка юнитов.
// Или напрямую в сервисе шаблонов, если логика обновления простая.
// Пока отправим сообщение в TemplateUpdater.
var expectedName = await GetNormalizedTemplateNameAsync ( job , template . UnitId ) ;
var nextRun = await GetNextRunAsync ( job , template . NextRun ) ;
var updateRequest = new TemplateUpdaterMq
{
TemplateId = template . Id ,
JobId = job . Id ,
UnitId = template . UnitId ,
Name = expectedName ,
IsActiveTemplate = template . IsActiveTemplate ,
IsActiveSchedule = template . IsActiveSchedule ,
LastRun = template . LastRun ,
NextRun = nextRun ,
Index = template . Index ,
StatusTypeId = TemplateStatusTypeEnum . Used , // Предполагаем, что используется
Initiator = initiator ,
UnitsInTemplate = newUnitIds // Передаем обновленный список юнитов
} ;
await SendTemplateUpdateMessage ( updateRequest ) ;
}
private async Task CreateGroupedTemplateAsync ( Guid jobId , Guid regionalUnitId , List < Guid > unitIds , int index , HistoryInitiator initiator )
{
logger . LogInformation ( "Создание нового группового шаблона для Job {JobId}, регионального юнита {RegionalUnitId}, Index {Index}, с {Count} юнитами." , jobId , regionalUnitId , index , unitIds . Count ) ;
// Предполагаем, что TemplateGeneratorMq может обрабатывать UnitsInTemplate и Index
var mqRequest = new TemplateGeneratorMq
{
JobId = jobId ,
UnitId = regionalUnitId , // UnitId шаблона
UnitsInTemplate = unitIds , // Юниты для UnitsInTemplate
Index = index , // Индекс шаблона
HistoryInitiator = initiator
} ;
var msg = JsonSerializer . Serialize ( mqRequest ) ;
var result = await mqService . SendAsync ( mqSettings . TemplateGenerator , new [ ] { msg } ) ;
if ( ! result . IsSuccess )
logger . LogError ( "Ошибка отправки команды создания группового шаблона для Job {JobId}, регионального юнита {RegionalUnitId}, Index {Index}." , jobId , regionalUnitId , index ) ;
}
private async Task < bool > DeactivateTemplateAsync (
Template template ,
Guid jobId ,
@@ -293,7 +752,8 @@ namespace PARR.TemplateMatcher
NextRun = template . NextRun ,
Index = template . Index ,
StatusTypeId = TemplateStatusTypeEnum . Unused ,
Initiator = initiator
Initiator = initiator ,
UnitsInTemplate = new List < Guid > ( )
} ;
await SendTemplateUpdateMessage ( updateRequest ) ;
@@ -339,7 +799,8 @@ namespace PARR.TemplateMatcher
NextRun = nextRun ,
Index = template . Index ,
StatusTypeId = TemplateStatusTypeEnum . Used ,
Initiator = initiator
Initiator = initiator ,
UnitsInTemplate = template . UnitsInTemplate . Select ( t = > t . UnitId ) . ToList ( )
} ;
await SendTemplateUpdateMessage ( updateRequest ) ;
@@ -352,11 +813,12 @@ namespace PARR.TemplateMatcher
HistoryInitiator initiator )
{
logger . LogInformation ( "Создание нового шаблона для UnitId {UnitId}." , unitId ) ;
var mqRequest = new TemplateGeneratorWorker Mq
var mqRequest = new TemplateGeneratorMq
{
JobId = jobId ,
UnitId = unitId ,
HistoryInitiator = initiator
HistoryInitiator = initiator ,
UnitsInTemplate = new List < Guid > ( )
} ;
var msg = JsonSerializer . Serialize ( mqRequest ) ;
@@ -387,9 +849,10 @@ namespace PARR.TemplateMatcher
}
}
// --- ИЗМЕНЕННЫЙ МЕТОД: Теперь используется как для простых, так и для групповых шаблонов ---
private async Task < Template ? > TryReuseOneUnusedTemplateAsync (
Guid jobId ,
Guid unitId ,
Guid jobId , // Используется для логики внутри метода (например, подготовка updateRequest в SyncSimpleTemplatesAsync)
Guid unitId , // Используется для логики внутри метода (например, подготовка updateRequest в SyncSimpleTemplatesAsync)
HistoryInitiator initiator ,
int maxAttempts = 3 )
{
@@ -398,6 +861,7 @@ namespace PARR.TemplateMatcher
try
{
var unusedCandidates = await templateService . Get ( )
. AsNoTracking ( ) // Добавлено
. Where ( t = > t . StatusTypeId = = TemplateStatusTypeEnum . Unused )
. OrderBy ( t = > t . DateModified ? ? t . DateCreated )
. Take ( UnusedCandidateBatchSize )
@@ -421,9 +885,9 @@ namespace PARR.TemplateMatcher
if ( await templateService . CommitAsync ( initiator ) )
{
logger . LogInformation ( "Успешно захвачен шаблон {TemplateId} для UnitId {UnitId} (попытка {Attempt})." ,
candidate . Id , unitId , attempt ) ;
return candidate ;
logger . LogInformation ( "Успешно захвачен шаблон {TemplateId} для переиспользования (попытка {Attempt})." ,
candidate . Id , attempt ) ;
return candidate ; // Возвращаем захваченный шаблон
}
// Откат при неудаче
@@ -462,7 +926,9 @@ namespace PARR.TemplateMatcher
private async Task < Job ? > GetJobWithGroupAndAutoControlAsync ( Guid jobId )
{
return await jobService . Get ( )
. AsNoTracking ( ) // Добавлено
. Include ( j = > j . Group )
. ThenInclude ( j = > j . GroupType )
. Include ( j = > j . AutoControl )
. FirstOrDefaultAsync ( j = > j . Id = = jobId ) ;
}
@@ -473,9 +939,9 @@ namespace PARR.TemplateMatcher
return units ? . ToHashSet ( ) ? ? new HashSet < Guid > ( ) ;
}
private async Task < string > GetNormalizedTemplateNameAsync ( Job job , Guid unitId )
private async Task < string > GetNormalizedTemplateNameAsync ( Job job , Guid unitId , int? index = null )
{
var rawName = await shortcodesService . ApplyShortcodesAsync ( job . TemplateNameMask , unitId , job . Id ) ;
var rawName = await shortcodesService . ApplyShortcodesAsync ( job . TemplateNameMask , unitId , job . Id , index );
return rawName . ToUpper ( ) ;
}