feat(templateMatcher): Убрана избыточная проверка связанных юнитов, теперь это выполняется в UnitFilter. Добавлена группировка по рабочей группе или ответственному за ЭК
This commit is contained in:
@@ -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,413 +178,335 @@ 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> { groupingFieldId });
|
||||
|
||||
var unitIdsWithValidGroupingFieldSet = groupingUnitValues
|
||||
// Найдем Id юнитов, у которых есть значение в GroupingUnitFieldId
|
||||
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 = workGroupField.Id; // УЖЕ ОПРЕДЕЛЕНО РАНЕЕ (строка 157)
|
||||
// var relationshipGroupValueIds = regionalEkPtkGroupService.Get() // УДАЛИТЬ - УЖЕ ОПРЕДЕЛЕНО (строка 161)
|
||||
// 5. Фильтрация unitFilterResults по РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК (проверяем dto.Id)
|
||||
var workGroupFieldId = await GetFieldIdByAihitNameAsync("РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК");
|
||||
|
||||
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)
|
||||
{
|
||||
linkMatchesAllFilters = false;
|
||||
break;
|
||||
}
|
||||
// dto.Id - это ParentUnitId, связанные - ChildUnitIds (expectedUnitIds) -> dto.Id идет в UnitsInTemplate
|
||||
// relatedUnitIds - это ChildUnitIds, которые станут UnitId шаблона
|
||||
relatedUnitIds = dto.Children.Select(c => c.UnitId).ToList(); // <-- Исправлено
|
||||
}
|
||||
else
|
||||
{
|
||||
// dto.Id - это ChildUnitId, связанные - ParentUnitIds (expectedUnitIds) -> dto.Id идет в UnitsInTemplate
|
||||
// relatedUnitIds - это ParentUnitIds, которые станут UnitId шаблона
|
||||
relatedUnitIds = dto.Parents.Select(p => p.UnitId).ToList();
|
||||
}
|
||||
|
||||
if (linkMatchesAllFilters)
|
||||
// dto.Id - это юнит, который прошел фильтры, он будет в UnitsInTemplate
|
||||
var unitInTemplateId = dto.Id;
|
||||
|
||||
foreach (var relatedUnitId in relatedUnitIds)
|
||||
{
|
||||
filteredUnitInUnitLinks.Add(link);
|
||||
// relatedUnitId уже прошел все фильтры, т.к. dto.Id (его связанный юнит) прошел фильтры
|
||||
if (!reverseMapping.ContainsKey(relatedUnitId))
|
||||
{
|
||||
reverseMapping[relatedUnitId] = new List<Guid>();
|
||||
}
|
||||
reverseMapping[relatedUnitId].Add(unitInTemplateId);
|
||||
}
|
||||
}
|
||||
|
||||
logger.LogDebug("После применения RelationshipFilters осталось {Count} связей UnitInUnit.", filteredUnitInUnitLinks.Count);
|
||||
logger.LogDebug("Построено {Count} записей в обратном отображении.", reverseMapping.Count);
|
||||
|
||||
// --- Сгруппировать юниты ---
|
||||
var groupedRelationships = new Dictionary<Guid, List<Guid>>();
|
||||
foreach (var link in filteredUnitInUnitLinks)
|
||||
if (!reverseMapping.Any())
|
||||
{
|
||||
var parentUnitId = link.ParentUnitId;
|
||||
var childUnitId = link.ChildUnitId;
|
||||
|
||||
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);
|
||||
}
|
||||
logger.LogInformation("После построения обратного отображения в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId);
|
||||
await UpdateMatchingStatusAsync(jobGroupId, "Нет юнитов после построения обратного отображения");
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogDebug("Сформировано {Count} групп по связанным юнитам до разрешения конфликтов.", groupedRelationships.Count);
|
||||
// 6. Внутренняя группировка по ОТВЕТСТВЕННЫЙ_ЗА_ЭК / РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК (для юнитов в UnitsInTemplate)
|
||||
logger.LogDebug("Внутренняя группировка по полю (IsGroupByResponsible={IsGroupByResponsible}).", jobGroup.IsGroupByResponsible);
|
||||
|
||||
// --- Разрешение конфликта с детерминированной сортировкой ---
|
||||
var unitToKeys = new Dictionary<Guid, List<Guid>>();
|
||||
foreach (var kvp in groupedRelationships)
|
||||
var groupingFieldIdForInnerGrouping = jobGroup.IsGroupByResponsible == true
|
||||
? await GetFieldIdByAihitNameAsync("ОТВЕТСТВЕННЫЙ_ЗА_ЭК")
|
||||
: await GetFieldIdByAihitNameAsync("РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК");
|
||||
|
||||
// Загрузим значения поля для *всех* юнитов, которые могут быть в UnitsInTemplate
|
||||
// Это все юниты из всех списков в reverseMapping.Values
|
||||
var allUnitsInTemplate = reverseMapping.Values.SelectMany(list => list).Distinct().ToList();
|
||||
var innerGroupingValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(allUnitsInTemplate, new HashSet<Guid> { groupingFieldIdForInnerGrouping });
|
||||
|
||||
// Создадим маппинг UnitId (из UnitsInTemplate) -> значение поля для внутренней группировки
|
||||
var unitInTemplateToInnerGroupingValueMap = innerGroupingValues
|
||||
.Where(uv => uv.Value != null && !string.IsNullOrEmpty(uv.Value.Value))
|
||||
.ToDictionary(uv => uv.UnitId, uv => uv.Value.Value);
|
||||
|
||||
// 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()
|
||||
.ToList();
|
||||
logger.LogDebug("Обработка потенциального шаблона для UnitId {PotentialUnitId} с {Count} юнитами в UnitsInTemplate до внутренней группировки.", potentialUnitId, unitsInTemplateForThisPotentialUnitId.Count);
|
||||
|
||||
var unitNamesMap = await unitService.Get()
|
||||
.Where(u => conflictKeys.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, u => u.Name ?? string.Empty);
|
||||
|
||||
foreach (var conflictedUnitEntry in unitToKeys.Where(kvp => kvp.Value.Count > 1))
|
||||
{
|
||||
var unitId = conflictedUnitEntry.Key;
|
||||
var keysForUnit = conflictedUnitEntry.Value;
|
||||
|
||||
// Сортируем по: 1) кол-во юнитов (убывание), 2) имя ключа (возрастание)
|
||||
var sortedKeys = keysForUnit
|
||||
.Select(key => (
|
||||
key,
|
||||
count: groupedRelationships[key].Count,
|
||||
name: unitNamesMap.GetValueOrDefault(key, "")
|
||||
))
|
||||
.OrderByDescending(x => x.count)
|
||||
.ThenBy(x => x.name)
|
||||
// --- ВНУТРЕННЯЯ ГРУППИРОВКА ---
|
||||
// Сгруппируем *юниты из UnitsInTemplate* для *этого* potentialUnitId по значению поля
|
||||
var innerGroupedUnitsInTemplate = unitsInTemplateForThisPotentialUnitId
|
||||
.GroupBy(unitId => unitInTemplateToInnerGroupingValueMap.GetValueOrDefault(unitId, "Нет данных")) // Используем маппинг
|
||||
.ToList();
|
||||
|
||||
var bestKey = sortedKeys.First().key;
|
||||
logger.LogDebug("Для UnitId {PotentialUnitId}: сформировано {Count} внутренних групп UnitsInTemplate.", potentialUnitId, innerGroupedUnitsInTemplate.Count);
|
||||
|
||||
// Удаляем юнит из ВСЕХ групп, кроме лучшей
|
||||
foreach (var key in keysForUnit)
|
||||
// 8. Цикл по внутренним группам UnitsInTemplate
|
||||
foreach (var innerGroup in innerGroupedUnitsInTemplate)
|
||||
{
|
||||
if (key != bestKey && groupedRelationships.ContainsKey(key))
|
||||
var groupingValueName = innerGroup.Key; // Значение поля
|
||||
var unitsInTemplateInInnerGroup = innerGroup.ToList(); // Список юнитов (UnitId), связанных с potentialUnitId и имеющих одно и то же значение поля
|
||||
|
||||
logger.LogDebug("Обработка внутренней группы '{GroupingValue}' для UnitId {PotentialUnitId} с {Count} юнитами.", groupingValueName, potentialUnitId, unitsInTemplateInInnerGroup.Count);
|
||||
|
||||
// Разбиваем юниты из *этой* внутренней группы на подгруппы по maxJob.MaxValueRelationships
|
||||
int maxValueForSplitting = maxJob.MaxValueRelationships!.Value;
|
||||
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("Внутренняя группа '{GroupingValue}' для UnitId {PotentialUnitId}: разбит на {GroupCount} подгрупп UnitsInTemplate.", groupingValueName, potentialUnitId, unitsInTemplateSubGroups.Count);
|
||||
|
||||
// 9. Цикл по подгруппам UnitsInTemplate для создания/обновления шаблонов
|
||||
for (int i = 1; i <= unitsInTemplateSubGroups.Count; i++) // Индекс начинается с 1
|
||||
{
|
||||
groupedRelationships[key].Remove(unitId);
|
||||
}
|
||||
}
|
||||
}
|
||||
var unitsInTemplateSubGroup = unitsInTemplateSubGroups[i - 1]; // корректируем индекс для доступа к коллекции
|
||||
var subGroupSize = unitsInTemplateSubGroup.Count;
|
||||
logger.LogDebug("Обработка подгруппы {Index} внутренней группы '{GroupingValue}' для UnitId {PotentialUnitId}, размер UnitsInTemplate {Size}.", i, groupingValueName, potentialUnitId, subGroupSize);
|
||||
|
||||
// Удаляем пустые группы
|
||||
var emptyKeys = groupedRelationships
|
||||
.Where(kvp => !kvp.Value.Any())
|
||||
.Select(kvp => kvp.Key)
|
||||
.ToList();
|
||||
Job? targetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
||||
|
||||
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();
|
||||
|
||||
int maxValueForSplitting = maxJob.MaxValueRelationships!.Value;
|
||||
var childUnitGroups = sortedChildUnitIds
|
||||
.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, childUnitGroups.Count);
|
||||
|
||||
// Индекс начинается с 1
|
||||
for (int i = 1; i <= childUnitGroups.Count; i++)
|
||||
{
|
||||
var subGroup = childUnitGroups[i - 1]; // корректируем индекс для доступа к коллекции
|
||||
var subGroupSize = subGroup.Count;
|
||||
logger.LogDebug("Обработка подгруппы {Index} связанного юнита {RelationshipUnitId}, размер {Size}.", i, relationshipUnitId, subGroupSize);
|
||||
|
||||
Job? targetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
||||
|
||||
var existingTemplatesForRelationship = await templateService.Get()
|
||||
.AsNoTracking()
|
||||
.Include(t => t.Unit)
|
||||
.Include(t => t.Job)
|
||||
.ThenInclude(t => t!.Tnk)
|
||||
.Include(t => t.Job)
|
||||
.ThenInclude(t => t!.Group)
|
||||
.ThenInclude(t => t!.GroupType)
|
||||
.Include(t => t.UnitsInTemplate)
|
||||
.ThenInclude(uit => uit.Unit)
|
||||
.Where(t => t.JobId == targetJob.Id && t.UnitId == relationshipUnitId && t.Index == i && t.StatusTypeId == TemplateStatusTypeEnum.Used)
|
||||
.ToListAsync();
|
||||
|
||||
var existingTemplateForSubGroup = existingTemplatesForRelationship.FirstOrDefault();
|
||||
|
||||
if (existingTemplateForSubGroup != null)
|
||||
{
|
||||
// === 1. Получаем текущие и новые ID юнитов ===
|
||||
var currentUnitIds = existingTemplateForSubGroup.UnitsInTemplate.Select(uit => uit.UnitId).ToList();
|
||||
var proposedUnitIds = subGroup.ToList();
|
||||
|
||||
// === 2. Сравниваем детерминированно с сортировкой по имени ===
|
||||
var allUnitIdsForSort = currentUnitIds.Concat(proposedUnitIds).Distinct().ToList();
|
||||
var unitNamesForSort = await unitService.Get()
|
||||
var existingTemplatesForRelationship = await templateService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => allUnitIdsForSort.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, u => u.Name ?? u.Id.ToString());
|
||||
.Include(t => t.Unit)
|
||||
.Include(t => t.Job)
|
||||
.ThenInclude(t => t!.Tnk)
|
||||
.Include(t => t.Job)
|
||||
.ThenInclude(t => t!.Group)
|
||||
.ThenInclude(t => t!.GroupType)
|
||||
.Include(t => t.UnitsInTemplate)
|
||||
.ThenInclude(uit => uit.Unit)
|
||||
.Where(t => t.JobId == targetJob.Id && t.UnitId == potentialUnitId && t.Index == i && t.StatusTypeId == TemplateStatusTypeEnum.Used)
|
||||
.ToListAsync();
|
||||
|
||||
var sortedCurrentUnitIds = currentUnitIds
|
||||
.OrderBy(id => unitNamesForSort.GetValueOrDefault(id, id.ToString()))
|
||||
.ToList();
|
||||
var existingTemplateForSubGroup = existingTemplatesForRelationship.FirstOrDefault();
|
||||
|
||||
var sortedProposedUnitIds = proposedUnitIds
|
||||
.OrderBy(id => unitNamesForSort.GetValueOrDefault(id, id.ToString()))
|
||||
.ToList();
|
||||
|
||||
bool unitsAreEqual = sortedCurrentUnitIds.SequenceEqual(sortedProposedUnitIds);
|
||||
|
||||
if (unitsAreEqual)
|
||||
if (existingTemplateForSubGroup != null)
|
||||
{
|
||||
logger.LogDebug("Шаблон {TemplateId} актуален по юнитам и их порядку (после сортировки).", existingTemplateForSubGroup.Id);
|
||||
existingTemplateForSubGroup.UnitsInTemplate = sortedProposedUnitIds.Select(id => new UnitsInTemplate { UnitId = id }).ToList();
|
||||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(existingTemplateForSubGroup);
|
||||
// === 1. Получаем текущие и новые ID юнитов ===
|
||||
var currentUnitIds = existingTemplateForSubGroup.UnitsInTemplate.Select(uit => uit.UnitId).ToList();
|
||||
var proposedUnitIds = unitsInTemplateSubGroup.ToList();
|
||||
|
||||
if (!string.Equals(existingTemplateForSubGroup.Name, expectedName, StringComparison.OrdinalIgnoreCase))
|
||||
// === 2. Сравниваем детерминированно с сортировкой по имени ===
|
||||
var allUnitIdsForSort = currentUnitIds.Concat(proposedUnitIds).Distinct().ToList();
|
||||
var unitNamesForSort = await unitService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => allUnitIdsForSort.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, u => u.Name ?? u.Id.ToString());
|
||||
|
||||
var sortedCurrentUnitIds = currentUnitIds
|
||||
.OrderBy(id => unitNamesForSort.GetValueOrDefault(id, id.ToString()))
|
||||
.ToList();
|
||||
|
||||
var sortedProposedUnitIds = proposedUnitIds
|
||||
.OrderBy(id => unitNamesForSort.GetValueOrDefault(id, id.ToString()))
|
||||
.ToList();
|
||||
|
||||
bool unitsAreEqual = sortedCurrentUnitIds.SequenceEqual(sortedProposedUnitIds);
|
||||
|
||||
if (unitsAreEqual)
|
||||
{
|
||||
logger.LogDebug("Шаблон {TemplateId} требует обновления имени.", existingTemplateForSubGroup.Id);
|
||||
//var nextRun = await nextRunService.GetNextRunForTemplateAsync(existingTemplateForSubGroup.Id, false);
|
||||
logger.LogDebug("Шаблон {TemplateId} актуален по юнитам и их порядку (после сортировки).", existingTemplateForSubGroup.Id);
|
||||
existingTemplateForSubGroup.UnitsInTemplate = sortedProposedUnitIds.Select(id => new UnitsInTemplate { UnitId = id }).ToList();
|
||||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(existingTemplateForSubGroup);
|
||||
|
||||
if (!string.Equals(existingTemplateForSubGroup.Name, expectedName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
logger.LogDebug("Шаблон {TemplateId} требует обновления имени.", existingTemplateForSubGroup.Id);
|
||||
var updateRequest = new TemplateUpdaterMq
|
||||
{
|
||||
TemplateId = existingTemplateForSubGroup.Id,
|
||||
JobId = targetJob.Id,
|
||||
UnitId = potentialUnitId,
|
||||
Name = expectedName,
|
||||
IsActiveTemplate = existingTemplateForSubGroup.IsActiveTemplate,
|
||||
IsActiveSchedule = existingTemplateForSubGroup.IsActiveSchedule,
|
||||
IsNew = false,
|
||||
Index = i,
|
||||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||||
Initiator = initiator,
|
||||
UnitsInTemplate = sortedProposedUnitIds
|
||||
};
|
||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogDebug("Шаблон {TemplateId} полностью актуален.", existingTemplateForSubGroup.Id);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogDebug("Шаблон {TemplateId} требует обновления юнитов или их порядка (после сортировки).", existingTemplateForSubGroup.Id);
|
||||
var newTargetJob = SelectTargetJob(jobsInGroup, unitsInTemplateSubGroup.Count, maxJob); // Размер - из подмножества
|
||||
if (newTargetJob.Id != existingTemplateForSubGroup.JobId)
|
||||
{
|
||||
logger.LogDebug("Job для шаблона {TemplateId} изменился.", existingTemplateForSubGroup.Id);
|
||||
}
|
||||
await UpdateTemplateUnitsAsync(existingTemplateForSubGroup, sortedProposedUnitIds, newTargetJob, initiator, i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(targetJob.Id, potentialUnitId, initiator);
|
||||
if (reusableTemplate != null)
|
||||
{
|
||||
logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, связанного юнита {RelationshipId}, Index {Index}.", reusableTemplate.Id, targetJob.Id, potentialUnitId, i);
|
||||
|
||||
var tempTemplateForName = new Template
|
||||
{
|
||||
Id = reusableTemplate.Id,
|
||||
Name = reusableTemplate.Name,
|
||||
JobId = targetJob.Id,
|
||||
UnitId = potentialUnitId,
|
||||
Index = i,
|
||||
Job = targetJob,
|
||||
Unit = reusableTemplate.Unit,
|
||||
UnitsInTemplate = unitsInTemplateSubGroup.Select(id => new UnitsInTemplate { UnitId = id }).ToList()
|
||||
};
|
||||
|
||||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName);
|
||||
|
||||
var updateRequest = new TemplateUpdaterMq
|
||||
{
|
||||
TemplateId = existingTemplateForSubGroup.Id,
|
||||
TemplateId = reusableTemplate.Id,
|
||||
JobId = targetJob.Id,
|
||||
UnitId = relationshipUnitId,
|
||||
UnitId = potentialUnitId,
|
||||
Name = expectedName,
|
||||
IsActiveTemplate = existingTemplateForSubGroup.IsActiveTemplate,
|
||||
IsActiveSchedule = existingTemplateForSubGroup.IsActiveSchedule,
|
||||
//LastRun = existingTemplateForSubGroup.LastRun,
|
||||
//NextRun = nextRun,
|
||||
IsNew = false,
|
||||
Index = i,
|
||||
IsActiveTemplate = targetJob.AutoControl?.InitUsedTemplateState ?? DefaultUsedTemplateState,
|
||||
IsActiveSchedule = targetJob.AutoControl?.InitUsedScheduleState ?? DefaultUsedScheduleState,
|
||||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||||
Initiator = initiator,
|
||||
UnitsInTemplate = sortedProposedUnitIds
|
||||
IsNew = true,
|
||||
Index = i,
|
||||
UnitsInTemplate = unitsInTemplateSubGroup
|
||||
};
|
||||
|
||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogDebug("Шаблон {TemplateId} полностью актуален.", existingTemplateForSubGroup.Id);
|
||||
logger.LogDebug("Создание нового шаблона для Job {JobId}, связанного юнита {RelationshipId}, Index {Index}, с {Count} юнитами.", targetJob.Id, potentialUnitId, i, unitsInTemplateSubGroup.Count);
|
||||
await CreateGroupedTemplateAsync(targetJob.Id, potentialUnitId, unitsInTemplateSubGroup, i, initiator);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogDebug("Шаблон {TemplateId} требует обновления юнитов или их порядка (после сортировки).", existingTemplateForSubGroup.Id);
|
||||
var newTargetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
||||
if (newTargetJob.Id != existingTemplateForSubGroup.JobId)
|
||||
{
|
||||
logger.LogDebug("Job для шаблона {TemplateId} изменился.", existingTemplateForSubGroup.Id);
|
||||
}
|
||||
await UpdateTemplateUnitsAsync(existingTemplateForSubGroup, sortedProposedUnitIds, newTargetJob, initiator, i);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(targetJob.Id, relationshipUnitId, initiator);
|
||||
if (reusableTemplate != null)
|
||||
{
|
||||
logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, связанного юнита {RelationshipId}, Index {Index}.", reusableTemplate.Id, targetJob.Id, relationshipUnitId, i);
|
||||
|
||||
var tempTemplateForName = new Template
|
||||
{
|
||||
Id = reusableTemplate.Id,
|
||||
Name = reusableTemplate.Name,
|
||||
JobId = targetJob.Id,
|
||||
UnitId = relationshipUnitId,
|
||||
Index = i,
|
||||
Job = targetJob,
|
||||
Unit = reusableTemplate.Unit,
|
||||
UnitsInTemplate = subGroup.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 = relationshipUnitId,
|
||||
Name = expectedName,
|
||||
IsActiveTemplate = targetJob.AutoControl?.InitUsedTemplateState ?? DefaultUsedTemplateState,
|
||||
IsActiveSchedule = targetJob.AutoControl?.InitUsedScheduleState ?? DefaultUsedScheduleState,
|
||||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||||
Initiator = initiator,
|
||||
//NextRun = nextRun,
|
||||
IsNew = true,
|
||||
Index = i,
|
||||
UnitsInTemplate = subGroup
|
||||
};
|
||||
|
||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogDebug("Создание нового шаблона для Job {JobId}, связанного юнита {RelationshipUnitId}, Index {Index}, с {Count} юнитами.", targetJob.Id, relationshipUnitId, i, subGroup.Count);
|
||||
await CreateGroupedTemplateAsync(targetJob.Id, relationshipUnitId, subGroup, i, initiator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// === Деактивация ===
|
||||
// Собираем ожидаемые ключи шаблонов на основе внутренне сгруппированных результатов
|
||||
var expectedTemplateKeys = new HashSet<(Guid JobId, Guid UnitId, int Index)>();
|
||||
foreach (var kvp in groupedRelationships)
|
||||
foreach (var kvpOuter in reverseMapping)
|
||||
{
|
||||
var relationshipUnitId = kvp.Key;
|
||||
var childUnitIds = 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 potentialUnitId = kvpOuter.Key;
|
||||
var unitsInTemplateForThisPotentialUnitId = kvpOuter.Value;
|
||||
|
||||
// Сгруппируем *юниты из UnitsInTemplate* для *этого* potentialUnitId по значению поля
|
||||
var innerGroupedUnitsInTemplate = unitsInTemplateForThisPotentialUnitId
|
||||
.GroupBy(unitId => unitInTemplateToInnerGroupingValueMap.GetValueOrDefault(unitId, "Нет данных"))
|
||||
.ToList();
|
||||
int maxValueForSplitting = maxJob.MaxValueRelationships!.Value;
|
||||
var childUnitGroups = sortedChildUnitIdsForDeactivate
|
||||
.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++)
|
||||
|
||||
foreach (var innerGroup in innerGroupedUnitsInTemplate)
|
||||
{
|
||||
var subGroup = childUnitGroups[i - 1]; // корректируем индекс для доступа к коллекции
|
||||
var subGroupSize = subGroup.Count;
|
||||
Job? targetJobForExpectedKey = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
||||
expectedTemplateKeys.Add((targetJobForExpectedKey.Id, relationshipUnitId, i));
|
||||
var groupingValueName = innerGroup.Key;
|
||||
var unitsInTemplateInInnerGroup = innerGroup.ToList();
|
||||
|
||||
// Разбиваем юниты из *этой* внутренней группы на подгруппы по maxJob.MaxValueRelationships
|
||||
int maxValueForSplitting = maxJob.MaxValueRelationships!.Value;
|
||||
var unitsInTemplateSubGroups = unitsInTemplateInInnerGroup
|
||||
.Select((id, index) => new { id, groupIndex = index / maxValueForSplitting })
|
||||
.GroupBy(x => x.groupIndex)
|
||||
.Select(g => g.Select(x => x.id).ToList())
|
||||
.ToList();
|
||||
|
||||
// Для каждой подгруппы UnitsInTemplate
|
||||
for (int i = 1; i <= unitsInTemplateSubGroups.Count; i++) // Индекс начинается с 1
|
||||
{
|
||||
var unitsInTemplateSubGroup = unitsInTemplateSubGroups[i - 1]; // корректируем индекс для доступа к коллекции
|
||||
var subGroupSize = unitsInTemplateSubGroup.Count;
|
||||
Job? targetJobForExpectedKey = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
||||
|
||||
expectedTemplateKeys.Add((targetJobForExpectedKey.Id, potentialUnitId, i)); // potentialUnitId - это UnitId шаблона
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Получаем ВСЕ шаблоны для JobGroup (не только для текущих relationshipUnitIds)
|
||||
// Получаем ВСЕ шаблоны для JobGroup (не только для текущих potentialUnitIds)
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user