diff --git a/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs b/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs index 315ef4b0..d0bebfb4 100644 --- a/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs +++ b/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs @@ -93,6 +93,8 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer .Include(jg => jg.Jobs) .ThenInclude(j => j.UnitFilters) .ThenInclude(uf => uf.RelationshipFilters) + .Include(jg => jg.Jobs) + .ThenInclude(jg => jg.Tnk) .FirstOrDefaultAsync(jg => jg.Id == jobGroupId); if (jobGroup == null || jobGroup.Jobs == null || !jobGroup.Jobs.Any()) @@ -177,7 +179,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer return; } - // --- НОВАЯ ЛОГИКА: Дополнительная фильтрация по "РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК" --- + // --- Дополнительная фильтрация по "РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК" --- var unitIdsWithValidWorkGroupFieldSet = expectedUnitsWithGroupingField .Where(u => unitIdsWithValidGroupingFieldSet.Contains(u.Id) && // Убедимся, что юнит уже прошёл фильтр по GroupingFieldId u.UnitValues.Any(uv => @@ -274,7 +276,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer logger.LogDebug("После применения RelationshipFilters осталось {Count} связей UnitInUnit.", filteredUnitInUnitLinks.Count); - // --- НОВАЯ ЛОГИКА: Сгруппировать юниты из unitIdsWithValidGroupingFieldSet по связанному юниту --- + // --- Сгруппировать юниты из unitIdsWithValidGroupingFieldSet по связанному юниту --- var groupedRelationships = new Dictionary>(); foreach (var link in filteredUnitInUnitLinks) { @@ -301,7 +303,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer logger.LogDebug("Сформировано {Count} групп по связанным юнитам до разрешения конфликтов.", groupedRelationships.Count); - // --- НОВАЯ ЛОГИКА: Разрешение конфликта - один юнит из unitIdsWithValidGroupingFieldSet только в одном списке значений --- + // --- Разрешение конфликта - один юнит из unitIdsWithValidGroupingFieldSet только в одном списке значений --- var unitToKeys = new Dictionary>(); // Карта: юнит из списка -> список ключей, где он встречается foreach (var kvp in groupedRelationships) @@ -392,30 +394,10 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer logger.LogDebug("Обработка подгруппы {Index} связанного юнита {RegionalUnitId}, размер {Size}.", i, relationshipUnitId, subGroupSize); - // 8. Найти подходящий Job для подгруппы (логика без изменений) - Job? targetJob = jobsInGroup - .Where(j => j.MaxValueRelationships.HasValue && j.MaxValueRelationships.Value == subGroupSize) - .FirstOrDefault(); + // Выбираем targetJob для подгруппы + Job? targetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob); - 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; // maxJob уже проверен на null ранее - logger.LogDebug("Для подгруппы {Index} связанного юнита {RegionalUnitId} не найден подходящий Job, используем maxJob {MaxJobId}.", i, relationshipUnitId, maxJob.Id); - } - else - { - logger.LogDebug("Для подгруппы {Index} связанного юнита {RegionalUnitId} выбран Job {TargetJobId} с MaxValueRelationships {MaxValue}.", i, relationshipUnitId, targetJob.Id, targetJob.MaxValueRelationships); - } - - // 9. Загрузить существующие шаблоны для targetJob, связанные с regionalUnitId + // 9. Загрузить существующие шаблоны для targetJob, связанные с relationshipUnitId var existingTemplatesForRelationship = await templateService.Get() .AsNoTracking() .Include(t => t.UnitsInTemplate) @@ -432,12 +414,53 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer if (existingUnitIds.SetEquals(newUnitIds)) { - logger.LogDebug("Шаблон {TemplateId} (Job {JobId}, Regional {RegionalId}, Index {Index}) актуален.", existingTemplateForSubGroup.Id, targetJob.Id, relationshipUnitId, i); + logger.LogDebug("Шаблон {TemplateId} (Job {JobId}, Regional {RegionalId}, Index {Index}) актуален по юнитам.", existingTemplateForSubGroup.Id, targetJob.Id, relationshipUnitId, i); + + // ✅ Проверить, изменилось ли имя шаблона (например, из-за %МАКС:...% или %ТНК-КРАТКО%) + var expectedName = await GetNormalizedTemplateNameAsync(targetJob, relationshipUnitId, i, subGroup); + if (!string.Equals(existingTemplateForSubGroup.Name, expectedName, StringComparison.OrdinalIgnoreCase)) + { + logger.LogDebug("Шаблон {TemplateId} требует обновления имени: старое = '{OldName}', новое = '{NewName}'", existingTemplateForSubGroup.Id, existingTemplateForSubGroup.Name, expectedName); + + var nextRun = await GetNextRunAsync(targetJob, existingTemplateForSubGroup.NextRun); + + var updateRequest = new TemplateUpdaterMq + { + TemplateId = existingTemplateForSubGroup.Id, + JobId = targetJob.Id, + UnitId = relationshipUnitId, + Name = expectedName, + IsActiveTemplate = existingTemplateForSubGroup.IsActiveTemplate, + IsActiveSchedule = existingTemplateForSubGroup.IsActiveSchedule, + LastRun = existingTemplateForSubGroup.LastRun, + NextRun = nextRun, + Index = i, + StatusTypeId = TemplateStatusTypeEnum.Used, + Initiator = initiator, + UnitsInTemplate = subGroup + }; + + await SendTemplateUpdateMessage(updateRequest); + } + else + { + logger.LogDebug("Шаблон {TemplateId} полностью актуален (юниты и имя).", existingTemplateForSubGroup.Id); + } } else { logger.LogDebug("Шаблон {TemplateId} (Job {JobId}, Regional {RegionalId}, Index {Index}) требует обновления юнитов.", existingTemplateForSubGroup.Id, targetJob.Id, relationshipUnitId, i); - await UpdateTemplateUnitsAsync(existingTemplateForSubGroup, subGroup, targetJob, initiator); + + // Пересчитать targetJob для нового размера subGroup + var newTargetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob); + + // Если Job изменился — нужно обновить шаблон с новым JobId + if (newTargetJob.Id != existingTemplateForSubGroup.JobId) + { + logger.LogDebug("Job для шаблона {TemplateId} изменился: старый {OldJobId}, новый {NewJobId}.", existingTemplateForSubGroup.Id, existingTemplateForSubGroup.JobId, newTargetJob.Id); + } + + await UpdateTemplateUnitsAsync(existingTemplateForSubGroup, subGroup, newTargetJob, initiator); } } else @@ -495,19 +518,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer var subGroup = childUnitGroups[i]; var subGroupSize = subGroup.Count; - Job? targetJobForExpectedKey = jobsInGroup - .Where(j => j.MaxValueRelationships.HasValue && j.MaxValueRelationships.Value == subGroupSize) - .FirstOrDefault(); - - if (targetJobForExpectedKey == null) - { - targetJobForExpectedKey = jobsInGroup - .Where(j => j.MaxValueRelationships.HasValue && j.MaxValueRelationships.Value >= subGroupSize) - .OrderBy(j => j.MaxValueRelationships!.Value) - .FirstOrDefault(); - } - - if (targetJobForExpectedKey == null) targetJobForExpectedKey = maxJob; // maxJob уже проверен на null + Job? targetJobForExpectedKey = SelectTargetJob(jobsInGroup, subGroupSize, maxJob); expectedTemplateKeys.Add((targetJobForExpectedKey.Id, regionalUnitId, i)); } @@ -542,6 +553,41 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer } // --- Вспомогательные методы --- + + /// + /// Выбирает Job, соответствующий размеру подгруппы + /// + /// Список Job'ов в группе + /// Размер подгруппы + /// Job с максимальным MaxValueRelationships + /// Найденный Job или maxJob, если не найден подходящий + private Job SelectTargetJob(List jobsInGroup, int subGroupSize, Job maxJob) + { + Job? 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; // maxJob уже проверен на null ранее + logger.LogDebug("Для подгруппы размером {Size} не найден подходящий Job, используем maxJob {MaxJobId}.", subGroupSize, maxJob.Id); + } + else + { + logger.LogDebug("Для подгруппы размером {Size} выбран Job {TargetJobId} с MaxValueRelationships {MaxValue}.", subGroupSize, targetJob.Id, targetJob.MaxValueRelationships); + } + + return targetJob; + } + private async Task UpdateTemplateUnitsAsync(Template template, List newUnitIds, Job targetJob, HistoryInitiator initiator) { template.StatusTypeId = TemplateStatusTypeEnum.Updating; @@ -659,7 +705,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer return templateName + "_" + DateTimeOffset.UtcNow.ToUnixTimeSeconds(); } - // Изменённая сигнатура: добавлен templateUnitIds private async Task GetNormalizedTemplateNameAsync(Job targetJob, Guid unitId, int? index = null, List? templateUnitIds = null) { // Подготовка объекта TemplateForShortcodes для передачи в ShortcodesService diff --git a/PARR.TemplateUpdater/Services/TemplateUpdaterService.cs b/PARR.TemplateUpdater/Services/TemplateUpdaterService.cs index 967ab9d3..d8296c6c 100644 --- a/PARR.TemplateUpdater/Services/TemplateUpdaterService.cs +++ b/PARR.TemplateUpdater/Services/TemplateUpdaterService.cs @@ -180,7 +180,10 @@ namespace PARR.TemplateUpdater.Services return false; } - var existTemplateName = await templateService.Get().AsNoTracking().AnyAsync(t => t.Id != query.TemplateId && t.Name.ToUpper() == query.Name.Trim().ToUpper()); + var existTemplateName = await templateService.Get().AsNoTracking().AnyAsync(t => + t.Id != query.TemplateId + && t.Name.ToUpper() == query.Name.Trim().ToUpper() + && t.Index == query.Index); if (existTemplateName) { logger.LogError($"Сообщение не валидно. Имя шаблона не уникально: {query.Name}");