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.JobEntities; using PARR.Domain.Entities.Unit; using PARR.Domain.Enums; using PARR.TemplateMatcher.Services.Interfaces; namespace PARR.TemplateMatcher.Services.Implementations; internal class TemplateReuser : ITemplateReuser { private readonly ILogger logger; private readonly ITemplateRepository templateRepository; private readonly ITemplateNameNormalizer nameNormalizer; private readonly ITemplateMqPublisher mqPublisher; public TemplateReuser( ILogger logger, ITemplateRepository templateRepository, ITemplateNameNormalizer nameNormalizer, ITemplateMqPublisher mqPublisher) { this.logger = logger; this.templateRepository = templateRepository; this.nameNormalizer = nameNormalizer; this.mqPublisher = mqPublisher; } public async Task TryReuseAsync( Job targetJob, Guid targetUnitId, Unit? targetUnit, int? index, List unitsInTemplate, bool isActiveTemplate, bool isActiveSchedule, HistoryInitiator initiator, CancellationToken ct = default) { const int maxAttempts = 3; for (int attempt = 1; attempt <= maxAttempts; attempt++) { ct.ThrowIfCancellationRequested(); try { // 1. Атомарный захват шаблона var templateId = await templateRepository.ReserveUnusedTemplateAsync(targetUnitId, initiator); if (templateId == null) { logger.LogDebug("Нет доступных Unused-шаблонов (попытка {Attempt}).", attempt); return false; // Шаблоны закончились } // Загружаем захваченный шаблон var template = await templateRepository.Get() .AsNoTracking() .Include(t => t.Unit) .FirstOrDefaultAsync(t => t.Id == templateId, ct); if (template == null) { logger.LogWarning("Зарезервированный шаблон {TemplateId} не найден.", templateId); continue; // Попробовать еще раз } logger.LogInformation("Шаблон {TemplateId} захвачен для переиспользования.", templateId); // 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) return false; // После 3 попыток сдаемся await Task.Delay(Random.Shared.Next(10, 50), ct); } } return false; } }