fix(templateMatcher, templateUpdater): исправлена валидация и добавлена проверка имени шаблона

This commit is contained in:
Mikhail Kuznetsov
2025-12-24 14:12:20 +10:00
parent 65f4c1445a
commit ee8e2343a5
2 changed files with 91 additions and 43 deletions

View File

@@ -93,6 +93,8 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
.Include(jg => jg.Jobs) .Include(jg => jg.Jobs)
.ThenInclude(j => j.UnitFilters) .ThenInclude(j => j.UnitFilters)
.ThenInclude(uf => uf.RelationshipFilters) .ThenInclude(uf => uf.RelationshipFilters)
.Include(jg => jg.Jobs)
.ThenInclude(jg => jg.Tnk)
.FirstOrDefaultAsync(jg => jg.Id == jobGroupId); .FirstOrDefaultAsync(jg => jg.Id == jobGroupId);
if (jobGroup == null || jobGroup.Jobs == null || !jobGroup.Jobs.Any()) if (jobGroup == null || jobGroup.Jobs == null || !jobGroup.Jobs.Any())
@@ -177,7 +179,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
return; return;
} }
// --- НОВАЯ ЛОГИКА: Дополнительная фильтрация по "РАБОЧАЯ_ГР_ОТВ_ЗАК" --- // --- Дополнительная фильтрация по "РАБОЧАЯ_ГР_ОТВ_ЗАК" ---
var unitIdsWithValidWorkGroupFieldSet = expectedUnitsWithGroupingField var unitIdsWithValidWorkGroupFieldSet = expectedUnitsWithGroupingField
.Where(u => unitIdsWithValidGroupingFieldSet.Contains(u.Id) && // Убедимся, что юнит уже прошёл фильтр по GroupingFieldId .Where(u => unitIdsWithValidGroupingFieldSet.Contains(u.Id) && // Убедимся, что юнит уже прошёл фильтр по GroupingFieldId
u.UnitValues.Any(uv => u.UnitValues.Any(uv =>
@@ -274,7 +276,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
logger.LogDebug("После применения RelationshipFilters осталось {Count} связей UnitInUnit.", filteredUnitInUnitLinks.Count); logger.LogDebug("После применения RelationshipFilters осталось {Count} связей UnitInUnit.", filteredUnitInUnitLinks.Count);
// --- НОВАЯ ЛОГИКА: Сгруппировать юниты из unitIdsWithValidGroupingFieldSet по связанному юниту --- // --- Сгруппировать юниты из unitIdsWithValidGroupingFieldSet по связанному юниту ---
var groupedRelationships = new Dictionary<Guid, List<Guid>>(); var groupedRelationships = new Dictionary<Guid, List<Guid>>();
foreach (var link in filteredUnitInUnitLinks) foreach (var link in filteredUnitInUnitLinks)
{ {
@@ -301,7 +303,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
logger.LogDebug("Сформировано {Count} групп по связанным юнитам до разрешения конфликтов.", groupedRelationships.Count); logger.LogDebug("Сформировано {Count} групп по связанным юнитам до разрешения конфликтов.", groupedRelationships.Count);
// --- НОВАЯ ЛОГИКА: Разрешение конфликта - один юнит из unitIdsWithValidGroupingFieldSet только в одном списке значений --- // --- Разрешение конфликта - один юнит из unitIdsWithValidGroupingFieldSet только в одном списке значений ---
var unitToKeys = new Dictionary<Guid, List<Guid>>(); // Карта: юнит из списка -> список ключей, где он встречается var unitToKeys = new Dictionary<Guid, List<Guid>>(); // Карта: юнит из списка -> список ключей, где он встречается
foreach (var kvp in groupedRelationships) foreach (var kvp in groupedRelationships)
@@ -392,30 +394,10 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
logger.LogDebug("Обработка подгруппы {Index} связанного юнита {RegionalUnitId}, размер {Size}.", i, relationshipUnitId, subGroupSize); logger.LogDebug("Обработка подгруппы {Index} связанного юнита {RegionalUnitId}, размер {Size}.", i, relationshipUnitId, subGroupSize);
// 8. Найти подходящий Job для подгруппы (логика без изменений) // Выбираем targetJob для подгруппы
Job? targetJob = jobsInGroup Job? targetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
.Where(j => j.MaxValueRelationships.HasValue && j.MaxValueRelationships.Value == subGroupSize)
.FirstOrDefault();
if (targetJob == null) // 9. Загрузить существующие шаблоны для targetJob, связанные с relationshipUnitId
{
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
var existingTemplatesForRelationship = await templateService.Get() var existingTemplatesForRelationship = await templateService.Get()
.AsNoTracking() .AsNoTracking()
.Include(t => t.UnitsInTemplate) .Include(t => t.UnitsInTemplate)
@@ -432,12 +414,53 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
if (existingUnitIds.SetEquals(newUnitIds)) 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 else
{ {
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);
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 else
@@ -495,19 +518,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
var subGroup = childUnitGroups[i]; var subGroup = childUnitGroups[i];
var subGroupSize = subGroup.Count; var subGroupSize = subGroup.Count;
Job? targetJobForExpectedKey = jobsInGroup Job? targetJobForExpectedKey = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
.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
expectedTemplateKeys.Add((targetJobForExpectedKey.Id, regionalUnitId, i)); expectedTemplateKeys.Add((targetJobForExpectedKey.Id, regionalUnitId, i));
} }
@@ -542,6 +553,41 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
} }
// --- Вспомогательные методы --- // --- Вспомогательные методы ---
/// <summary>
/// Выбирает Job, соответствующий размеру подгруппы
/// </summary>
/// <param name="jobsInGroup">Список Job'ов в группе</param>
/// <param name="subGroupSize">Размер подгруппы</param>
/// <param name="maxJob">Job с максимальным MaxValueRelationships</param>
/// <returns>Найденный Job или maxJob, если не найден подходящий</returns>
private Job SelectTargetJob(List<Job> 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<Guid> newUnitIds, Job targetJob, HistoryInitiator initiator) private async Task UpdateTemplateUnitsAsync(Template template, List<Guid> newUnitIds, Job targetJob, HistoryInitiator initiator)
{ {
template.StatusTypeId = TemplateStatusTypeEnum.Updating; template.StatusTypeId = TemplateStatusTypeEnum.Updating;
@@ -659,7 +705,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
return templateName + "_" + DateTimeOffset.UtcNow.ToUnixTimeSeconds(); return templateName + "_" + DateTimeOffset.UtcNow.ToUnixTimeSeconds();
} }
// Изменённая сигнатура: добавлен templateUnitIds
private async Task<string> GetNormalizedTemplateNameAsync(Job targetJob, Guid unitId, int? index = null, List<Guid>? templateUnitIds = null) private async Task<string> GetNormalizedTemplateNameAsync(Job targetJob, Guid unitId, int? index = null, List<Guid>? templateUnitIds = null)
{ {
// Подготовка объекта TemplateForShortcodes для передачи в ShortcodesService // Подготовка объекта TemplateForShortcodes для передачи в ShortcodesService

View File

@@ -180,7 +180,10 @@ namespace PARR.TemplateUpdater.Services
return false; 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) if (existTemplateName)
{ {
logger.LogError($"Сообщение не валидно. Имя шаблона не уникально: {query.Name}"); logger.LogError($"Сообщение не валидно. Имя шаблона не уникально: {query.Name}");