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)
.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<Guid, List<Guid>>();
foreach (var link in filteredUnitInUnitLinks)
{
@@ -301,7 +303,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
logger.LogDebug("Сформировано {Count} групп по связанным юнитам до разрешения конфликтов.", groupedRelationships.Count);
// --- НОВАЯ ЛОГИКА: Разрешение конфликта - один юнит из unitIdsWithValidGroupingFieldSet только в одном списке значений ---
// --- Разрешение конфликта - один юнит из unitIdsWithValidGroupingFieldSet только в одном списке значений ---
var unitToKeys = new Dictionary<Guid, List<Guid>>(); // Карта: юнит из списка -> список ключей, где он встречается
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
}
// --- Вспомогательные методы ---
/// <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)
{
template.StatusTypeId = TemplateStatusTypeEnum.Updating;
@@ -659,7 +705,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
return templateName + "_" + DateTimeOffset.UtcNow.ToUnixTimeSeconds();
}
// Изменённая сигнатура: добавлен templateUnitIds
private async Task<string> GetNormalizedTemplateNameAsync(Job targetJob, Guid unitId, int? index = null, List<Guid>? templateUnitIds = null)
{
// Подготовка объекта TemplateForShortcodes для передачи в ShortcodesService

View File

@@ -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}");