fix(templateMatcher): Исправление ошибок в именах шаблонов и рефакторинг синхронизации

- Устранена ошибка, когда при переиспользовании шаблона для шорткодов передавался ЭК "КОМПЛЕКСЫ-[ЗО]" вместо целевого ЭК шаблона.
- Проведен небольшой рефакторинг общих процессов синхронизации различных типов групп работ.
This commit is contained in:
Mikhail Kuznetsov
2026-05-20 17:59:06 +10:00
parent c0148088c1
commit 513e5f869a
26 changed files with 995 additions and 638 deletions

View File

@@ -1,8 +1,12 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using PARR.Core.Repositories.Interfaces;
using PARR.Domain.Common.Rabbit.Messages.TemplateMatching;
using PARR.Domain.Entities;
using PARR.Domain.Entities.Base.History;
using PARR.Domain.Entities.Job;
using PARR.Domain.Entities.Unit;
using PARR.Domain.Enums;
using PARR.TemplateMatcher.Services.Interfaces;
namespace PARR.TemplateMatcher.Services.Implementations;
@@ -10,71 +14,105 @@ namespace PARR.TemplateMatcher.Services.Implementations;
internal class TemplateReuser : ITemplateReuser
{
private readonly ILogger<TemplateReuser> logger;
private readonly ITemplateRepository templateService;
private readonly ITemplateRepository templateRepository;
private readonly ITemplateNameNormalizer nameNormalizer;
private readonly ITemplateMqPublisher mqPublisher;
public TemplateReuser(
ILogger<TemplateReuser> logger,
ITemplateRepository templateService)
ITemplateRepository templateRepository,
ITemplateNameNormalizer nameNormalizer,
ITemplateMqPublisher mqPublisher)
{
this.logger = logger;
this.templateService = templateService;
this.templateRepository = templateRepository;
this.nameNormalizer = nameNormalizer;
this.mqPublisher = mqPublisher;
}
public async Task<Template?> TryReuseOneUnusedTemplateAsync(
Guid jobId,
Guid unitId,
public async Task<bool> TryReuseAsync(
Job targetJob,
Guid targetUnitId,
Unit? targetUnit,
int? index,
List<UnitInTemplateMessage> unitsInTemplate,
bool isActiveTemplate,
bool isActiveSchedule,
HistoryInitiator initiator,
int maxAttempts = 3)
CancellationToken ct = default)
{
const int maxAttempts = 3;
for (int attempt = 1; attempt <= maxAttempts; attempt++)
{
ct.ThrowIfCancellationRequested();
try
{
// Атомарно резервируем один шаблон через DAL
var templateId = await templateService.ReserveUnusedTemplateAsync(unitId, initiator);
// 1. Атомарный захват шаблона
var templateId = await templateRepository.ReserveUnusedTemplateAsync(targetUnitId, initiator);
if (templateId == null)
{
logger.LogDebug("Нет доступных Unused-шаблонов для переиспользования (попытка {Attempt}).", attempt);
return null;
logger.LogDebug("Нет доступных Unused-шаблонов (попытка {Attempt}).", attempt);
return false; // Шаблоны закончились
}
// Загружаем зарезервированный шаблон
var template = await templateService.Get()
// Загружаем захваченный шаблон
var template = await templateRepository.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)
.FirstOrDefaultAsync(t => t.Id == templateId);
.FirstOrDefaultAsync(t => t.Id == templateId, ct);
if (template == null)
{
logger.LogWarning("Зарезервированный шаблон {TemplateId} не найден при загрузке.", templateId);
continue;
logger.LogWarning("Зарезервированный шаблон {TemplateId} не найден.", templateId);
continue; // Попробовать еще раз
}
logger.LogInformation(
"Успешно захвачен шаблон {TemplateId} (старый Job {OldJobId}) для нового Job {NewJobId}, Unit {UnitId} (попытка {Attempt}).",
template.Id, template.JobId, jobId, unitId, attempt);
logger.LogInformation("Шаблон {TemplateId} захвачен для переиспользования.", templateId);
return template;
// 2. Нормализация имени
var templateForName = new Template
{
Id = template.Id,
Name = template.Name,
JobId = targetJob.Id,
UnitId = targetUnitId,
Index = index,
Job = targetJob,
Unit = targetUnit,
UnitsInTemplate = unitsInTemplate.Select(m => new UnitsInTemplate { UnitId = m.UnitId, UnitFieldValueId = m.UnitFieldValueId }).ToList()
};
var expectedName = await nameNormalizer.GetNormalizedTemplateNameAsync(templateForName);
// 3. Отправка команды в MQ
var message = new TemplateUpdaterMessage
{
TemplateId = template.Id,
JobId = targetJob.Id,
UnitId = targetUnitId,
Name = expectedName,
IsActiveTemplate = isActiveTemplate,
IsActiveSchedule = isActiveSchedule,
StatusTypeId = TemplateStatusTypeEnum.Used,
Initiator = initiator,
IsNew = true,
Index = index ?? template.Index,
UnitsInTemplate = unitsInTemplate
};
await mqPublisher.PublishUpdateAsync(message, ct);
return true; // Успех
}
catch (Exception ex)
{
logger.LogError(ex, "Ошибка при попытке захвата шаблона (попытка {Attempt}).", attempt);
if (attempt == maxAttempts)
throw;
// Небольшая задержка перед повтором
await Task.Delay(Random.Shared.Next(10, 50));
logger.LogError(ex, "Ошибка при переиспользовании (попытка {Attempt}).", attempt);
if (attempt == maxAttempts) return false; // После 3 попыток сдаемся
await Task.Delay(Random.Shared.Next(10, 50), ct);
}
}
return null;
return false;
}
}