@@ -5,13 +5,11 @@ using PARR.BLL.Services.Interfaces;
using PARR.Common.Domain ;
using PARR.Constants ;
using PARR.DAL.Cache.Models ;
using PARR.DAL.DomainServices.Interfaces ;
using PARR.DAL.DomainServices.Shortcodes ;
using PARR.DAL.DomainServices.UnitFilterService ;
using PARR.DAL.Models ;
using PARR.DAL.Models.Job ;
using PARR.DAL.Models.Unit ;
using PARR.DAL.NextRunServices ;
using PARR.DAL.Services.Interfaces ;
using PARR.DAL.Services.Interfaces.Job ;
using PARR.DAL.Services.Interfaces.Unit ;
@@ -28,7 +26,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
private readonly ILogger < GroupedTemplateSynchronizer > logger ;
private readonly IUnitFilterService unitFilterService ;
private readonly IUnitInUnitService unitInUnitService ;
private readonly IUnitInValueService unitInValueService ;
private readonly IUnitService unitService ;
private readonly MqSettings mqSettings ;
@@ -42,12 +39,13 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
private readonly ITemplateNameNormalizer templateNameNormalizer ;
private readonly ITemplateUpdaterMqSender templateUpdaterMqSender ;
private readonly IMatchingStatusService matchingStatusService ;
private readonly IShortcodesService shortcodesService ;
//private readonly INextRunService nextRunService;
public GroupedTemplateSynchronizer (
ILogger < GroupedTemplateSynchronizer > logger ,
IUnitFilterService unitFilterService ,
IUnitInUnitService unitInUnitService ,
IUnitInValueService unitInValueService ,
IUnitService unitService ,
MqSettings mqSettings ,
@@ -60,13 +58,13 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
ITemplateDeactivator templateDeactivator ,
ITemplateNameNormalizer templateNameNormalizer ,
ITemplateUpdaterMqSender templateUpdaterMqSender ,
IMatchingStatusService matchingStatusService
IMatchingStatusService matchingStatusService ,
IShortcodesService shortcodesService
//INextRunService nextRunService
)
{
this . logger = logger ;
this . unitFilterService = unitFilterService ;
this . unitInUnitService = unitInUnitService ;
this . unitInValueService = unitInValueService ;
this . unitService = unitService ;
this . mqSettings = mqSettings ;
@@ -80,6 +78,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
this . templateNameNormalizer = templateNameNormalizer ;
this . templateUpdaterMqSender = templateUpdaterMqSender ;
this . matchingStatusService = matchingStatusService ;
this . shortcodesService = shortcodesService ;
//this.nextRunService = nextRunService;
}
@@ -87,7 +86,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
{
logger . LogWarning ( "GroupedTemplateSynchronizer: SyncTemplatesForJob вызван для JobId {JobId}. Это не поддерживаемая операция." , jobId ) ;
}
public async Task SyncTemplatesForJobGroupAsync ( Guid jobGroupId , HistoryInitiator initiator )
{
logger . LogDebug ( "Начало синхронизации шаблонов для JobGroup {JobGroupId}" , jobGroupId ) ;
@@ -119,12 +117,14 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
// 1. Получить JobGroup и связанные Job'ы
var jobGroup = await jobGroupService . Get ( )
. AsNoTracking ( )
. AsSingleQuery ( )
. Include ( jg = > jg . GroupType )
. Include ( jg = > jg . Jobs )
. ThenInclude ( j = > j . AutoControl )
. Include ( jg = > jg . Jobs )
. ThenInclude ( j = > j . UnitFilters )
. ThenInclude ( uf = > uf . RelationshipFilters )
. ThenInclude ( rf = > rf . UnitField ) // Подгрузим поля для фильтрации
. Include ( jg = > jg . Jobs )
. ThenInclude ( jg = > jg . Tnk )
. FirstOrDefaultAsync ( jg = > jg . Id = = jobGroupId ) ;
@@ -138,20 +138,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
var jobsInGroup = jobGroup . Jobs . ToList ( ) ;
// --- Получение FieldId и разрешённых значений для "Р А БО ЧА Я_ГР _О Т В _З А _ЭК " ---
var workGroupField = await unitFieldService . GetByAihitNameAsync ( "Р А БО ЧА Я_ГР _О Т В _З А _ЭК " ) ;
if ( workGroupField = = null )
{
logger . LogError ( "Поле 'Р А БО ЧА Я_ГР _О Т В _З А _ЭК ' не найдено в справочнике полей. Синхронизация прервана." ) ;
await UpdateMatchingStatusAsync ( jobGroupId , "Ошибка: поле 'Р А БО ЧА Я_ГР _О Т В _З А _ЭК ' не найдено" ) ;
return ;
}
var workGroupFieldId = workGroupField . Id ;
var relationshipGroupValueIds = regionalEkPtkGroupService . Get ( )
. Select ( g = > g . FieldValueId )
. ToList ( ) ;
logger . LogDebug ( "Найдено {Count} значений из UnitRegionalEkPtkGroup для проверки поля 'Р А БО ЧА Я_ГР _О Т В _З А _ЭК '." , relationshipGroupValueIds . Count ) ;
// 2. Найти Job с максимальным MaxValueRelationships
var maxJob = jobsInGroup
. Where ( j = > j . MaxValueRelationships . HasValue )
@@ -165,26 +151,25 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
return ;
}
if ( maxJob . UnitFilters = = null )
{
logger . LogWarning ( "Job {JobId} не содержит UnitFilters." , maxJob . Id ) ;
}
logger . LogDebug ( "Используется Job {JobId} с максимальным MaxValueRelationships ({MaxValue}) для фильтрации." , maxJob . Id , maxJob . MaxValueRelationships ) ;
// 3. Использовать фильтры maxJob для получения отфильтрованных юнитов
var filteredUnits = await unitFilterService . GetUnitsByJobFilterAsync ( maxJob . Id ) ;
if ( filteredUnits = = null | | ! filteredUnits . Any ( ) )
// 3. Использовать unitFilterService для получения отфильтрованных юнитов с их связями
// Это включает в себя все фильтры: UnitFilter, FieldFilter, RelationshipFilter, UmbrellaFilter
logger . LogDebug ( "Получение отфильтрованных юнитов с их связями через UnitFilterService для Job {JobId}." , maxJob . Id ) ;
var unitFilterResults = await unitFilterService . GetUnitsByJobFilterAsync ( maxJob . Id ) ;
if ( unitFilterResults = = null | | ! unitFilterResults . Any ( ) )
{
logger . LogInformation ( "Для JobGroup {JobGroupId} фильтры не дали Unit'ов." , jobGroupId ) ;
await UpdateMatchingStatusAsync ( jobGroupId , "Фильтры не дали Unit'ов" ) ;
logger . LogInformation ( "Для JobGroup {JobGroupId} фильтры не дали Unit'ов с подходящими связями ." , jobGroupId ) ;
await UpdateMatchingStatusAsync ( jobGroupId , "Фильтры не дали Unit'ов с подходящими связями " ) ;
return ;
}
// Извлекаем ID юнитов для последующих операций
var expectedUnitIds = filteredUnits . Select ( u = > u . Id ) . ToList ( ) ;
logger . LogDebug ( "Получено {Count} юнитов с подходящими связями через UnitFilterService." , unitFilterResults . Count ( ) ) ;
// 4. Отфильтровать expectedUnitIds по GroupingUnitFieldId
// --- ФИЛЬТРАЦИЯ unitFilterResults (dto.Id) ---
// 4. Фильтрация unitFilterResults по GroupingUnitFieldId (проверяем dto.Id)
if ( ! jobGroup . GroupingUnitFieldId . HasValue )
{
logger . LogError ( "JobGroup {JobGroupId} не имеет GroupingUnitFieldId, необходимого для группировки." , jobGroupId ) ;
@@ -193,249 +178,166 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
}
var groupingFieldId = jobGroup . GroupingUnitFieldId . Value ;
logger . LogDebug ( "Фильтрация юнитов (UnitFilterResultDto.Id) по GroupingUnitFieldId (FieldId={FieldId})." , groupingFieldId ) ;
// --- ОПТИМИЗАЦИЯ: Загрузка значений поля GroupingUnitFieldId отдельно ---
logger . LogDebug ( "Загружаем значения поля GroupingUnitFieldId (FieldId={FieldId}) для {Count} юнитов." , groupingFieldId , expectedUnitIds . Count ) ;
// Загрузим значения поля GroupingUnitFieldId для всех Id из unitFilterResults
var allUnitFilterResultIds = unitFilterResults . Select ( dto = > dto . Id ) . ToList ( ) ;
var groupingUnitValues = await unitInValueService . GetByUnitIdsAndFieldIdsAsync ( allUnitFilterResultIds , new HashSet < Guid > { groupingFieldId } ) ;
var groupingUnitValues = await unitInValueService . GetByUnitIdsAndFieldIdsAsync ( expectedUnitIds , new HashSet < Guid > { g roupingFieldId } ) ;
var unitIdsWithValidGroupingFieldSet = groupingUnitValues
// Найдем Id юнитов, у которых есть значение в G roupingUnit FieldId
var validUnitFilterResultIds = groupingUnitValues
. Where ( uv = > uv . Value ! = null & & ! string . IsNullOrEmpty ( uv . Value . Value ) )
. Select ( uv = > uv . UnitId )
. ToHashSet ( ) ;
// Оставляем только те UnitFilterResultDto, чей Id проходит фильтр
var filteredUnitFilterResultsByGrouping = unitFilterResults
. Where ( dto = > validUnitFilterResultIds . Contains ( dto . Id ) )
. ToList ( ) ;
logger . LogDebug ( "После фильтрации по GroupingUnitFieldId осталось {Count} юнитов." , unitIdsWithValidGroupingFieldSet . Count ) ;
logger . LogDebug ( "После фильтрации по GroupingUnitFieldId осталось {Count} UnitFilterResultDto." , filteredUnitFilterResultsByGrouping . Count ) ;
if ( ! unitIdsWithValidGroupingFieldSet . Any ( ) )
if ( ! filteredUnitFilterResultsByGrouping . Any ( ) )
{
logger . LogInformation ( "После фильтрации по GroupingUnitFieldId в JobGroup {JobGroupId} не осталось юнитов." , jobGroupId ) ;
await UpdateMatchingStatusAsync ( jobGroupId , "Нет юнитов после фильтрации по GroupingUnitFieldId" ) ;
return ;
}
// --- Аналогично для фильтрации по " Р А БО ЧА Я_ГР _О Т В _З А _ЭК " ---
// var workGroupFieldId = workGroup Field. Id; // УЖЕ ОПРЕДЕЛЕНО Р А Н Е Е (строка 157)
// var relationshipGroupValueIds = regionalEkPtkGroupService.Get() // УДАЛИТЬ - УЖЕ ОПРЕДЕЛЕНО (строка 161)
// 5. Фильтрация unitFilterResults по Р А БО ЧА Я_ГР _О Т В _З А _ЭК (проверяем dto.Id)
var workGroupFieldId = await Get FieldIdByAihitNameAsync ( "Р А БО ЧА Я_ГР _О Т В _З А _ЭК " ) ;
logger . LogDebug ( "Загружаем значения поля 'Р А БО ЧА Я_ГР _О Т В _З А _ЭК ' (FieldId={FieldId}) для {Count} юнитов ." , workGroupFieldId , unitIdsWithValidGroupingFieldSet . Count );
logger . LogDebug ( "Фильтрация юнитов (UnitFilterResultDto.Id) по полю 'Р А БО ЧА Я_ГР _О Т В _З А _ЭК ' (FieldId={FieldId})." , workGroupFieldId ) ;
var workGroupValues = await unitInValueService . GetByUnitIdsAndFieldIdsAsync ( unitIdsWithValidGroupingFieldSet , new HashSet < Guid > { workGroupFieldId } ) ;
// Загрузим значения поля Р А БО ЧА Я_ГР _О Т В _З А _ЭК для Id из filteredUnitFilterResultsByGrouping
var allFilteredUnitFilterResultIds = filteredUnitFilterResultsByGrouping . Select ( dto = > dto . Id ) . ToList ( ) ;
var workGroupValues = await unitInValueService . GetByUnitIdsAndFieldIdsAsync ( allFilteredUnitFilterResultIds , new HashSet < Guid > { workGroupFieldId } ) ;
var unitIdsWithValidWorkGroupFieldSet = workGroupValues
. Where ( uv = > uv . Value ! = null & & relationshipGroupValueIds . Contains ( uv . Value . Id ) )
// Получим разрешенные значения из regionalEkPtkGroupService
var allowedValueIds = regionalEkPtkGroupService . Get ( )
. Select ( g = > g . FieldValueId )
. ToHashSet ( ) ;
logger . LogDebug ( "Найдено {Count} разрешенных значений для поля 'Р А БО ЧА Я_ГР _О Т В _З А _ЭК '." , allowedValueIds . Count ) ;
// Найдем Id юнитов, у которых значение в Р А БО ЧА Я_ГР _О Т В _З А _ЭК разрешено
var validUnitFilterResultIdsForWorkGroup = workGroupValues
. Where ( uv = > uv . Value ! = null & & allowedValueIds . Contains ( uv . Value . Id ) )
. Select ( uv = > uv . UnitId )
. ToHashSet ( ) ;
// Оставляем только те UnitFilterResultDto, чей Id проходит фильтр
var finalFilteredUnitFilterResults = filteredUnitFilterResultsByGrouping
. Where ( dto = > validUnitFilterResultIdsForWorkGroup . Contains ( dto . Id ) )
. ToList ( ) ;
logger . LogDebug ( "После фильтрации по 'Р А БО ЧА Я_ГР _О Т В _З А _ЭК ' осталось {Count} юнитов." , unitIdsWithValidWorkGroupFieldSet . Count ) ;
logger . LogDebug ( "После фильтрации по 'Р А БО ЧА Я_ГР _О Т В _З А _ЭК ' осталось {Count} UnitFilterResultDto." , finalFilteredUnitFilterResults . Count ) ;
if ( ! unitIdsWithValidWorkGroupFieldSet . Any ( ) )
if ( ! finalFilteredUnitFilterResults . Any ( ) )
{
logger . LogInformation ( "После фильтрации по 'Р А БО ЧА Я_ГР _О Т В _З А _ЭК ' в JobGroup {JobGroupId} не осталось юнитов." , jobGroupId ) ;
await UpdateMatchingStatusAsync ( jobGroupId , "Нет юнитов после фильтрации по 'Р А БО ЧА Я_ГР _О Т В _З А _ЭК '" ) ;
return ;
}
// --- ИСПОЛЬЗУЕМ ПОСЛЕДНИЙ РЕЗУЛЬТАТ ДАЛЬШЕ ---
var finalUnitIds = unitIdsWithValidWorkGroupFieldSet ; // Более понятное имя
// 5. Получить RelationshipFilters из maxJob
var relationshipFilters = maxJob . UnitFilters ? . SelectMany ( uf = > uf . RelationshipFilters ) . ToList ( ) ? ? new List < JobRelationshipFilter > ( ) ;
logger . LogDebug ( "Получение связей UnitInUnit для юнитов, прошедших фильтрацию..." ) ;
var potentialUnitInUnitLinks = await unitInUnitService . Get ( )
. AsNoTracking ( )
. Where ( link = > finalUnitIds . Contains ( link . ParentUnitId ) | | finalUnitIds . Contains ( link . ChildUnitId ) )
. ToListAsync ( ) ;
logger . LogDebug ( "Найдено {Count} потенциальных связей UnitInUnit." , potentialUnitInUnitLinks . Count ) ;
var allParentIds = potentialUnitInUnitLinks . Select ( l = > l . ParentUnitId ) . ToHashSet ( ) ;
var allChildIds = potentialUnitInUnitLinks . Select ( l = > l . ChildUnitId ) . ToHashSet ( ) ;
// --- Оптимизация: Загрузка всех значений за ОДИН запрос ---
var allRelevantUnitIds = allParentIds . Concat ( allChildIds ) . ToHashSet ( ) ;
var allUnitValues = await unitInValueService . Get ( )
. AsNoTracking ( )
. Include ( uv = > uv . Field )
. Include ( uv = > uv . Value )
. Where ( uv = > allRelevantUnitIds . Contains ( uv . UnitId ) )
. ToListAsync ( ) ;
// --- Создание карт значений из одного списка ---
var parentValuesMap = allUnitValues
. Where ( uv = > allParentIds . Contains ( uv . UnitId ) )
. GroupBy ( uv = > uv . UnitId )
. ToDictionary ( g = > g . Key , g = > g . ToList ( ) ) ;
var childValuesMap = allUnitValues
. Where ( uv = > allChildIds . Contains ( uv . UnitId ) )
. GroupBy ( uv = > uv . UnitId )
. ToDictionary ( g = > g . Key , g = > g . ToList ( ) ) ;
logger . LogDebug ( "Применение {Count} RelationshipFilters к найденным связям." , relationshipFilters . Count ) ;
var filteredUnitInUnitLinks = new List < UnitInUnit > ( ) ;
foreach ( var link in potentialUnitInUnitLinks )
// --- ПОСТРОЕНИЕ ОБРАТНОГО ОТОБРАЖЕНИЯ (после фильтрации) ---
logger . LogDebug ( "Построение обратного отображения: связанные юниты -> юниты, связанные с ними (после фильтрации)." ) ;
var reverseMapping = new Dictionary < Guid , List < Guid > > ( ) ;
foreach ( var dto in finalFilteredUnitFilterResults )
{
bool linkMatchesAllFilters = true ;
foreach ( var rf in relationshipFilters )
List < Guid > relatedUnitIds ;
if ( maxJob . IsParentRelationships = = true )
{
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 )
// dto.Id - это ParentUnitId, связанные - ChildUnitIds (expectedUnitIds) -> dto.Id идет в Units InTemplate
// relatedUnitIds - это ChildUnitIds, которые станут UnitId шаблона
relatedUnitIds = dto . Children . Select ( c = > c . UnitId ) . ToList ( ) ; // <-- Исправлено
}
else
{
linkMatchesAllFilters = false ;
break ;
// dto.Id - это ChildUnitId, связанные - ParentUnitIds (expectedUnitIds) -> dto.Id идет в UnitsInTemplate
// relatedUnitIds - это ParentUnitIds, которые станут UnitId шаблона
relatedUnitIds = dto . Parents . Select ( p = > p . UnitId ) . ToList ( ) ;
}
// dto.Id - это юнит, который прошел фильтры, он будет в UnitsInTemplate
var unitInTemplateId = dto . Id ;
foreach ( var relatedUnitId in relatedUnitIds )
{
// relatedUnitId уже прошел все фильтры, т.к. dto.Id (е г о связанный юнит) прошел фильтры
if ( ! reverseMapping . ContainsKey ( relatedUnitId ) )
{
reverseMapping [ relatedUnitId ] = new List < Guid > ( ) ;
}
reverseMapping [ relatedUnitId ] . Add ( unitInTemplateId ) ;
}
}
if ( linkMatchesAllFilters )
logger . LogDebug ( "Построено {Count} записей в обратном отображении." , reverseMapping . Count ) ;
if ( ! reverseMapping . Any ( ) )
{
filteredUnitInUnitLinks . Add ( link ) ;
}
logger . LogInformation ( "После построения обратного отображения в JobGroup {JobGroupId} не осталось юнитов." , jobGroupId ) ;
await UpdateMatchingStatusAsync ( jobGroupId , "Нет юнитов после построения обратного отображения" ) ;
return ;
}
logger . LogDebug ( "После применения RelationshipFilters осталось {Count} связей UnitInUnit." , filteredUnitInUnitLinks . Count ) ;
// 6. Внутренняя группировка по О Т В Е Т С Т В Е Н Н ЫЙ_З А _ЭК / Р А БО ЧА Я_ГР _О Т В _З А _ЭК (для юнитов в Units InTemplate)
logger . LogDebug ( "Внутренняя группировка по полю (IsGroupByResponsible={IsGroupByResponsible})." , jobGroup . IsGroupByResponsible ) ;
// --- Сгруппировать юниты ---
var groupedRelationships = new Dictionary < Guid , List < Guid > > ( ) ;
foreach ( var link in filteredUnitInUnitLinks )
{
var parentUnitId = link . ParentUnitId ;
var childUnitId = link . ChildUnitId ;
var groupingFieldIdForInnerGrouping = jobGroup . IsGroupByResponsible = = true
? await GetFieldIdByAihitNameAsync ( "О Т В Е Т С Т В Е Н Н ЫЙ_З А _ЭК " )
: await GetFieldIdByAihitNameAsync ( "Р А БО ЧА Я_ГР _О Т В _З А _ЭК " ) ;
if ( finalUnitIds . Contains ( parentUnitId ) )
{
if ( ! groupedRelationships . ContainsKey ( childUnitId ) )
{
groupedRelationships [ childUnitId ] = new List < Guid > ( ) ;
}
groupedRelationships [ childUnitId ] . Add ( parentUnitId ) ;
}
else if ( finalUnitIds . Contains ( childUnitId ) )
{
if ( ! groupedRelationships . ContainsKey ( parentUnitId ) )
{
groupedRelationships [ parentUnitId ] = new List < Guid > ( ) ;
}
groupedRelationships [ parentUnitId ] . Add ( childUnitId ) ;
}
}
// Загрузим значения поля для *всех* юнитов, которые могут быть в UnitsInTemplate
// Это все юниты из всех списков в reverseMapping.Values
var allUnitsInTemplate = reverseMapping . Values . SelectMany ( list = > list ) . Distinct ( ) . ToList ( ) ;
var innerGroupingValues = await unitInValueService . GetByUnitIdsAndFieldIdsAsync ( allUnitsInTemplate , new HashSet < Guid > { groupingFieldIdForInnerGrouping } ) ;
logger . LogDebug ( "Сформировано {Count} групп по связанным юнитам до разрешения конфликтов." , groupedRelationships . Count ) ;
// Создадим маппинг UnitId (из UnitsInTemplate) -> значение поля для внутренней группировки
var unitInTemplateToInnerGroupingValueMap = innerGroupingValues
. Where ( uv = > uv . Value ! = null & & ! string . IsNullOrEmpty ( uv . Value . Value ) )
. ToDictionary ( uv = > uv . UnitId , uv = > uv . Value . Value ) ;
// --- Разрешение конфликта с детерминированной сортировкой ---
var unitToKeys = new Dictionary < Guid , List < Guid > > ( ) ;
foreach ( var kvp in groupedRelationships )
// 7. Основной цикл обработки: итерируемся по potentialUnitIds (UnitId шаблонов)
foreach ( var kvpOuter in reverseMapping )
{
var key = kvp . Key ;
var units = kvp . Value ;
foreach ( var unitId in units )
{
if ( ! unitToKeys . ContainsKey ( unitId ) )
{
unitToKeys [ unitId ] = new List < Guid > ( ) ;
}
unitToKeys [ unitId ] . Add ( key ) ;
}
}
var potentialUnitId = kvpOuter . Key ;
var unitsInTemplateForThisPotentialUnitId = kvpOuter . Value ;
// Получаем имена всех конфликтующих ключей
var conflictKeys = unitToKeys
. Where ( kvp = > kvp . Value . Count > 1 )
. SelectMany ( kvp = > kvp . Value )
. Distinct ( )
logger . LogDebug ( "Обработка потенциального шаблона для UnitId {PotentialUnitId} с {Count} юнитами в UnitsInTemplate до внутренней группировки." , potentialUnitId , unitsInTemplateForThisPotentialUnitId . Count ) ;
// --- ВНУТРЕННЯЯ ГРУППИРОВКА ---
// Сгруппируем *юниты из UnitsInTemplate* для *этого* potentialUnitId по значению поля
var innerGroupedUnitsInTemplate = unitsInTemplateForThisPotentialUnitId
. GroupBy ( unitId = > unitInTemplateToInnerGroupingValueMap . GetValueOrDefault ( unitId , "Нет данных" ) ) // Используем маппинг
. ToList ( ) ;
var unitNamesMap = await unitServic e. Ge t( )
. Where ( u = > conflictKeys . Contains ( u . Id ) )
. ToDictionaryAsync ( u = > u . Id , u = > u . Name ? ? string . Empty ) ;
logger . LogDebug ( "Для UnitId {PotentialUnitId}: сформировано {Count} внутренних групп UnitsInTemplate." , potentialUnitId , innerGroupedUnitsInTemplat e . Coun t) ;
foreach ( var conflictedUnitEntry in unitToKeys . Where ( kvp = > kvp . Value . Count > 1 ) )
// 8. Цикл по внутренним группам UnitsInTemplate
foreach ( var innerGroup in innerGroupedUnitsInTemplate )
{
var unitId = conflictedUnitEntry . Key ;
var keysForUnit = conflictedUnitEntry . Value ;
var groupingValueName = innerGroup . Key ; // Значение поля
var unitsInTemplateInInnerGroup = innerGroup . ToList ( ) ; // Список юнитов (UnitId), связанных с potentialUnitId и имеющих одно и то же значение поля
// Сортируем по: 1) кол-во юнитов (убывание), 2) имя ключа (возрастание)
var sortedKeys = keysForUnit
. Select ( key = > (
key ,
count : groupedRelationships [ key ] . Count ,
name : unitNamesMap . GetValueOrDefault ( key , "" )
) )
. OrderByDescending ( x = > x . count )
. ThenBy ( x = > x . name )
. ToList ( ) ;
var bestKey = sortedKeys . First ( ) . key ;
// Удаляем юнит из В С Е Х групп, кроме лучшей
foreach ( var key in keysForUnit )
{
if ( key ! = bestKey & & groupedRelationships . ContainsKey ( key ) )
{
groupedRelationships [ key ] . Remove ( unitId ) ;
}
}
}
// Удаляем пустые группы
var emptyKeys = groupedRelationships
. Where ( kvp = > ! kvp . Value . Any ( ) )
. Select ( kvp = > kvp . Key )
. ToList ( ) ;
foreach ( var key in emptyKeys )
{
groupedRelationships . Remove ( key ) ;
}
logger . LogDebug ( "Сформировано {Count} групп по связанным юнитам после разрешения конфликтов." , groupedRelationships . Count ) ;
// === Основной цикл обработки ===
foreach ( var kvp in groupedRelationships )
{
var relationshipUnitId = kvp . Key ;
var childUnitIds = kvp . Value ;
if ( childUnitIds . Count = = 0 ) continue ;
logger . LogDebug ( "Обработка связанного юнита {RelationshipUnitId} с {Count} юнитами из списка." , relationshipUnitId , childUnitIds . Count ) ;
var childUnitNameMap = await unitService . Get ( )
. AsNoTracking ( )
. Where ( u = > childUnitIds . Contains ( u . Id ) )
. ToDictionaryAsync ( u = > u . Id , u = > u . Name ) ;
var sortedChildUnitIds = childUnitIds
. OrderBy ( id = > childUnitNameMap . GetValueOrDefault ( id , id . ToString ( ) ) )
. ToList ( ) ;
logger . LogDebug ( "Обработка внутренней группы '{GroupingValue}' для UnitId {PotentialUnitId} с {Count} юнитами." , groupingValueName , potentialUnitId , unitsInTemplateInInnerGroup . Count ) ;
// Разбиваем юниты из *этой* внутренней группы на подгруппы по maxJob.MaxValueRelationships
int maxValueForSplitting = maxJob . MaxValueRelationships ! . Value ;
var childUnitGroups = sortedChildUnitIds
var unitsInTemplateSubGroups = unitsInTemplateInInnerGroup
. Select ( ( id , index ) = > new { id , groupIndex = index / maxValueForSplitting } )
. GroupBy ( x = > x . groupIndex )
. Select ( g = > g . Select ( x = > x . id ) . ToList ( ) )
. ToList ( ) ;
logger . LogDebug ( "Связанный юнит {RelationshipUnitId}: разбит на {GroupCount} подгрупп." , relationshipUnitId , childUnit Groups . Count ) ;
logger . LogDebug ( "Внутренняя группа '{GroupingValue}' для UnitId {PotentialUnitId}: разбит на {GroupCount} подгрупп UnitsInTemplate." , groupingValueName , potentialUnitId , unitsInTemplateSub Groups . Count ) ;
// Индекс начинается с 1
for ( int i = 1 ; i < = childUnit Groups. Count ; i + + )
// 9. Цикл по подгруппам UnitsInTemplate для создания/обновления шаблонов
for ( int i = 1 ; i < = unitsInTemplateSub Groups. Count ; i + + ) // Индекс начинается с 1
{
var subGroup = childUnit Groups[ i - 1 ] ; // корректируем индекс для доступа к коллекции
var subGroupSize = s ubGroup. Count ;
logger . LogDebug ( "Обработка подгруппы {Index} связанного юнита {RelationshipUnitId}, размер {Size}." , i , relationship UnitId , subGroupSize ) ;
var unitsInTemplateSubGroup = unitsInTemplateSub Groups[ i - 1 ] ; // корректируем индекс для доступа к коллекции
var subGroupSize = unitsInTemplateS ubGroup. Count ;
logger . LogDebug ( "Обработка подгруппы {Index} внутренней группы '{GroupingValue}' для UnitId {PotentialUnitId}, размер UnitsInTemplate {Size}." , i , groupingValueName , potential UnitId , subGroupSize ) ;
Job ? targetJob = SelectTargetJob ( jobsInGroup , subGroupSize , maxJob ) ;
@@ -449,7 +351,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
. ThenInclude ( t = > t ! . GroupType )
. Include ( t = > t . UnitsInTemplate )
. ThenInclude ( uit = > uit . Unit )
. Where ( t = > t . JobId = = targetJob . Id & & t . UnitId = = relationship UnitId & & t . Index = = i & & t . StatusTypeId = = TemplateStatusTypeEnum . Used )
. Where ( t = > t . JobId = = targetJob . Id & & t . UnitId = = potential UnitId & & t . Index = = i & & t . StatusTypeId = = TemplateStatusTypeEnum . Used )
. ToListAsync ( ) ;
var existingTemplateForSubGroup = existingTemplatesForRelationship . FirstOrDefault ( ) ;
@@ -458,7 +360,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
{
// === 1. Получаем текущие и новые ID юнитов ===
var currentUnitIds = existingTemplateForSubGroup . UnitsInTemplate . Select ( uit = > uit . UnitId ) . ToList ( ) ;
var proposedUnitIds = s ubGroup. ToList ( ) ;
var proposedUnitIds = unitsInTemplateS ubGroup. ToList ( ) ;
// === 2. Сравниваем детерминированно с сортировкой по имени ===
var allUnitIdsForSort = currentUnitIds . Concat ( proposedUnitIds ) . Distinct ( ) . ToList ( ) ;
@@ -486,17 +388,14 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
if ( ! string . Equals ( existingTemplateForSubGroup . Name , expectedName , StringComparison . OrdinalIgnoreCase ) )
{
logger . LogDebug ( "Шаблон {TemplateId} требует обновления имени." , existingTemplateForSubGroup . Id ) ;
//var nextRun = await nextRunService.GetNextRunForTemplateAsync(existingTemplateForSubGroup.Id, false);
var updateRequest = new TemplateUpdaterMq
{
TemplateId = existingTemplateForSubGroup . Id ,
JobId = targetJob . Id ,
UnitId = relationship UnitId,
UnitId = potential UnitId,
Name = expectedName ,
IsActiveTemplate = existingTemplateForSubGroup . IsActiveTemplate ,
IsActiveSchedule = existingTemplateForSubGroup . IsActiveSchedule ,
//LastRun = existingTemplateForSubGroup.LastRun,
//NextRun = nextRun,
IsNew = false ,
Index = i ,
StatusTypeId = TemplateStatusTypeEnum . Used ,
@@ -513,7 +412,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
else
{
logger . LogDebug ( "Шаблон {TemplateId} требует обновления юнитов или их порядка (после сортировки)." , existingTemplateForSubGroup . Id ) ;
var newTargetJob = SelectTargetJob ( jobsInGroup , subGroupSize , maxJob ) ;
var newTargetJob = SelectTargetJob ( jobsInGroup , unitsInTemplateSubGroup . Count , maxJob ) ; // Размер - из подмножества
if ( newTargetJob . Id ! = existingTemplateForSubGroup . JobId )
{
logger . LogDebug ( "Job для шаблона {TemplateId} изменился." , existingTemplateForSubGroup . Id ) ;
@@ -523,83 +422,91 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
}
else
{
var reusableTemplate = await templateReuser . TryReuseOneUnusedTemplateAsync ( targetJob . Id , relationship UnitId, initiator ) ;
var reusableTemplate = await templateReuser . TryReuseOneUnusedTemplateAsync ( targetJob . Id , potential UnitId, initiator ) ;
if ( reusableTemplate ! = null )
{
logger . LogInformation ( "Переиспользован шаблон {TemplateId} для Job {JobId}, связанного юнита {RelationshipId}, Index {Index}." , reusableTemplate . Id , targetJob . Id , relationship UnitId, i ) ;
logger . LogInformation ( "Переиспользован шаблон {TemplateId} для Job {JobId}, связанного юнита {RelationshipId}, Index {Index}." , reusableTemplate . Id , targetJob . Id , potential UnitId, i ) ;
var tempTemplateForName = new Template
{
Id = reusableTemplate . Id ,
Name = reusableTemplate . Name ,
JobId = targetJob . Id ,
UnitId = relationship UnitId,
UnitId = potential UnitId,
Index = i ,
Job = targetJob ,
Unit = reusableTemplate . Unit ,
UnitsInTemplate = s ubGroup. Select ( id = > new UnitsInTemplate { UnitId = id } ) . ToList ( )
UnitsInTemplate = unitsInTemplateS ubGroup. Select ( id = > new UnitsInTemplate { UnitId = id } ) . ToList ( )
} ;
var expectedName = await templateNameNormalizer . GetNormalizedTemplateNameAsync ( tempTemplateForName ) ;
//var nextRun = await nextRunService.GetNextRunForTemplateAsync(reusableTemplate.Id, true);
var updateRequest = new TemplateUpdaterMq
{
TemplateId = reusableTemplate . Id ,
JobId = targetJob . Id ,
UnitId = relationship UnitId,
UnitId = potential UnitId,
Name = expectedName ,
IsActiveTemplate = targetJob . AutoControl ? . InitUsedTemplateState ? ? DefaultUsedTemplateState ,
IsActiveSchedule = targetJob . AutoControl ? . InitUsedScheduleState ? ? DefaultUsedScheduleState ,
StatusTypeId = TemplateStatusTypeEnum . Used ,
Initiator = initiator ,
//NextRun = nextRun,
IsNew = true ,
Index = i ,
UnitsInTemplate = s ubGroup
UnitsInTemplate = unitsInTemplateS ubGroup
} ;
await templateUpdaterMqSender . SendTemplateUpdateMessageAsync ( updateRequest ) ;
}
else
{
logger . LogDebug ( "Создание нового шаблона для Job {JobId}, связанного юнита {RelationshipUnit Id}, Index {Index}, с {Count} юнитами." , targetJob . Id , relationship UnitId, i , s ubGroup. Count ) ;
await CreateGroupedTemplateAsync ( targetJob . Id , relationshipUnitId , s ubGroup, i , initiator ) ;
logger . LogDebug ( "Создание нового шаблона для Job {JobId}, связанного юнита {RelationshipId}, Index {Index}, с {Count} юнитами." , targetJob . Id , potential UnitId, i , unitsInTemplateS ubGroup. Count ) ;
await CreateGroupedTemplateAsync ( targetJob . Id , potentialUnitId , unitsInTemplateS ubGroup, i , initiator ) ;
}
}
}
}
}
// === Деактивация ===
// Собираем ожидаемые ключи шаблонов на основе внутренне сгруппированных результатов
var expectedTemplateKeys = new HashSet < ( Guid JobId , Guid UnitId , int Index ) > ( ) ;
foreach ( var kvp in groupedRelationships )
foreach ( var kvpOuter in reverseMapping )
{
var relationship UnitId = kvp . Key ;
var child UnitIds = kvp . Value ;
var childUnitNameMapForDeactivate = await unitService . Get ( )
. AsNoTracking ( )
. Where ( u = > childUnitIds . Contains ( u . Id ) )
. ToDictionaryAsync ( u = > u. Id , u = > u . Name ) ;
var sortedChildUnitIdsForDeactivate = childUnitIds
. OrderBy ( id = > childUnitNameMapForDeactivate . GetValueOrDefault ( id , id . ToString ( ) ) )
var potential UnitId = kvpOuter . Key ;
var unitsInTemplateForThisPotential UnitId = kvpOuter . Value ;
// Сгруппируем *юниты из UnitsInTemplate* для *этого* potentialUnitId по значению поля
var innerGroupedUnitsInTemplate = unitsInTemplateForThisPotentialUnitId
. GroupBy ( unitId = > unitInTemplateToInnerGroupingValueMap . GetValueOrDefault ( unitId , "Нет данных" ) )
. ToList ( ) ;
foreach ( var innerGroup in innerGroupedUnitsInTemplate )
{
var groupingValueName = innerGroup . Key ;
var unitsInTemplateInInnerGroup = innerGroup . ToList ( ) ;
// Разбиваем юниты из *этой* внутренней группы на подгруппы по maxJob.MaxValueRelationships
int maxValueForSplitting = maxJob . MaxValueRelationships ! . Value ;
var childUnitGroups = sortedChildUnitIdsForDeactivate
var unitsInTemplateSubGroups = unitsInTemplateInInnerGroup
. Select ( ( id , index ) = > new { id , groupIndex = index / maxValueForSplitting } )
. GroupBy ( x = > x . groupIndex )
. Select ( g = > g . Select ( x = > x . id ) . ToList ( ) )
. ToList ( ) ;
// Индекс начинается с 1
for ( int i = 1 ; i < = childUnitGroups . Count ; i + + )
// Для каждой подгруппы UnitsInTemplate
for ( int i = 1 ; i < = unitsInTemplateSubGroups . Count ; i + + ) // Индекс начинается с 1
{
var subGroup = childUnit Groups[ i - 1 ] ; // корректируем индекс для доступа к коллекции
var subGroupSize = s ubGroup. Count ;
var unitsInTemplateSubGroup = unitsInTemplateSub Groups[ i - 1 ] ; // корректируем индекс для доступа к коллекции
var subGroupSize = unitsInTemplateS ubGroup. Count ;
Job ? targetJobForExpectedKey = SelectTargetJob ( jobsInGroup , subGroupSize , maxJob ) ;
expectedTemplateKeys . Add ( ( targetJobForExpectedKey . Id , relationshipUnitId , i ) ) ;
expectedTemplateKeys . Add ( ( targetJobForExpectedKey . Id , potentialUnitId , i ) ) ; // potentialUnitId - это UnitId шаблона
}
}
}
// Получаем В С Е шаблоны для JobGroup (не только для текущих relationship UnitIds)
// Получаем В С Е шаблоны для JobGroup (не только для текущих potential UnitIds)
var allJobIdsInGroup = jobsInGroup . Select ( j = > j . Id ) . ToHashSet ( ) ;
var allExistingTemplatesInGroup = await templateService . Get ( )
. AsNoTracking ( )
@@ -724,8 +631,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
HistoryInitiator = initiator
} ;
//var msg = JsonSerializer.Serialize(mqRequest);
//var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg });
var result = await mqService . SendAsync ( mqSettings . TemplateGenerator , new List < object > { mqRequest } ) ;
if ( ! result . IsSuccess )
@@ -749,4 +654,17 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
TimeSpan . FromMinutes ( 30 )
) ;
}
#region В с п о м о г а т е л ь н ы е м е т о д ы
private async Task < Guid > GetFieldIdByAihitNameAsync ( string fieldName )
{
var field = await unitFieldService . GetByAihitNameAsync ( fieldName ) ;
if ( field = = null )
{
logger . LogError ( "Поле '{FieldName}' не найдено в справочнике полей." , fieldName ) ;
throw new InvalidOperationException ( $"Поле '{fieldName}' не найдено в справочнике полей." ) ;
}
return field . Id ;
}
#endregion
}