using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using PARR.BLL.Domain.Mq; using PARR.BLL.Services.Interfaces; using PARR.Common.Domain; using PARR.Constants; using PARR.DAL.DomainServices.Interfaces; using PARR.DAL.DomainServices.Shortcodes; using PARR.DAL.Models.Job; using PARR.DAL.Services.Interfaces; using PARR.DAL.Services.Interfaces.Job; using PARR.DAL.Services.Interfaces.Unit; using PARR.DAL.TransformServices; using PARR.TemplateMatcher.Services.Interfaces; using PARR.TemplateMatcher.Settings; using System.Text.Json; namespace PARR.TemplateMatcher.Services.Implementations; internal class SimpleTemplateSynchronizer : ITemplateSynchronizer { #if DEBUG private readonly Guid targetUnitId = Guid.Parse("358437ac-1eeb-4c00-840c-998326f657ac"); #endif private const bool DefaultUsedTemplateState = false; private const bool DefaultUsedScheduleState = false; private readonly ILogger logger; private readonly IUnitFilterService unitFilterService; private readonly MqSettings mqSettings; private readonly IMqService mqService; private readonly ITemplateService templateService; private readonly IJobService jobService; private readonly ITemplateReuser templateReuser; private readonly IEsppScheduleTransformService esppScheduleTransformService; private readonly ITemplateDeactivator templateDeactivator; private readonly ITemplateNameNormalizer templateNameNormalizer; private readonly ITemplateUpdaterMqSender templateUpdaterMqSender; public SimpleTemplateSynchronizer( ILogger logger, IUnitFilterService unitFilterService, IUnitInUnitService unitInUnitService, IUnitInValueService unitInValueService, IUnitService unitService, MqSettings mqSettings, IMqService mqService, ITemplateService templateService, IJobService jobService, ITemplateReuser templateReuser, IShortcodesService shortcodesService, IEsppScheduleTransformService esppScheduleTransformService, IUnitRegionalEkPtkGroupService regionalEkPtkGroupService, IUnitFieldService unitFieldService, ITemplateDeactivator templateDeactivator, ITemplateNameNormalizer templateNameNormalizer, ITemplateUpdaterMqSender templateUpdaterMqSender ) { this.logger = logger; this.unitFilterService = unitFilterService; this.mqSettings = mqSettings; this.mqService = mqService; this.templateService = templateService; this.jobService = jobService; this.templateReuser = templateReuser; this.esppScheduleTransformService = esppScheduleTransformService; this.templateDeactivator = templateDeactivator; this.templateNameNormalizer = templateNameNormalizer; this.templateUpdaterMqSender = templateUpdaterMqSender; } public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator) { logger.LogDebug("Начало синхронизации шаблонов для Job {JobId}", jobId); var job = await jobService.Get() .AsNoTracking() .Include(j => j.AutoControl) .Include(j => j.Tnk) .Include(j => j.Group) .ThenInclude(g => g!.GroupType) .Include(j => j.UnitFilters) .ThenInclude(uf => uf.RelationshipFilters) .FirstOrDefaultAsync(j => j.Id == jobId); if (job == null) { logger.LogWarning("Job {JobId} не найден.", jobId); return; } var unitIds = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId); if (unitIds == null || !unitIds.Any()) { logger.LogInformation("Для Job {JobId} фильтры не дали Unit'ов.", jobId); return; } #if DEBUG // ✅ Отладка: проверить, есть ли юнит в unitIds if (unitIds.Contains(targetUnitId)) { logger.LogDebug("Юнит {TargetUnitId} найден в unitIds.", targetUnitId); } else { logger.LogDebug("Юнит {TargetUnitId} НЕ найден в unitIds.", targetUnitId); return; // ❌ юнит отсеялся на этом этапе } #endif var existingTemplates = await templateService.Get() .AsNoTracking() .Include(t => t.UnitsInTemplate) .Where(t => t.JobId == jobId) .ToListAsync(); var existingUnitIds = existingTemplates .Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Used) .Select(t => t.UnitId) .ToHashSet(); var newUnitIds = unitIds.Except(existingUnitIds).ToList(); var unusedTemplates = existingTemplates .Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Used && !unitIds.Contains(t.UnitId)) .ToList(); foreach (var unitId in newUnitIds) { var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(jobId, unitId, initiator); if (reusableTemplate != null) { logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, UnitId {UnitId}.", reusableTemplate.Id, jobId, unitId); var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(job, unitId); var nextRun = await GetNextRunAsync(job); var updateRequest = new TemplateUpdaterMq { TemplateId = reusableTemplate.Id, JobId = jobId, UnitId = unitId, Name = expectedName, IsActiveTemplate = job.AutoControl?.InitUsedTemplateState ?? DefaultUsedTemplateState, IsActiveSchedule = job.AutoControl?.InitUsedScheduleState ?? DefaultUsedScheduleState, StatusTypeId = TemplateStatusTypeEnum.Used, Initiator = initiator, NextRun = nextRun, UnitsInTemplate = new List() // для простого шаблона }; await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); } else { logger.LogDebug("Создание нового шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId); await CreateSimpleTemplateAsync(jobId, unitId, initiator); } } foreach (var unusedTemplate in unusedTemplates) { logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, UnitId {UnitId}).", unusedTemplate.Id, jobId, unusedTemplate.UnitId); await templateDeactivator.DeactivateTemplateAsync(unusedTemplate, initiator); } logger.LogInformation("Синхронизация шаблонов завершена для Job {JobId}.", jobId); } public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator) { logger.LogWarning("SimpleTemplateSynchronizer: SyncTemplatesForJobGroup вызван для JobGroup {JobGroupId}. Это не поддерживаемая операция.", jobGroupId); } public async Task UpdateTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator) { logger.LogDebug("Обновление шаблонов для Job {JobId}", jobId); var job = await jobService.Get() .AsNoTracking() .Include(j => j.AutoControl) .Include(j => j.Tnk) .Include(j => j.Group) .ThenInclude(g => g!.GroupType) .Include(j => j.UnitFilters) .ThenInclude(uf => uf.RelationshipFilters) .FirstOrDefaultAsync(j => j.Id == jobId); if (job == null) { logger.LogWarning("Job {JobId} не найден.", jobId); return; } var unitIds = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId); if (unitIds == null || !unitIds.Any()) { logger.LogInformation("Для Job {JobId} фильтры не дали Unit'ов.", jobId); return; } #if DEBUG // ✅ Отладка: проверить, есть ли юнит в unitIds if (unitIds.Contains(targetUnitId)) { logger.LogDebug("Юнит {TargetUnitId} найден в unitIds.", targetUnitId); } else { logger.LogDebug("Юнит {TargetUnitId} НЕ найден в unitIds.", targetUnitId); return; // ❌ юнит отсеялся на этом этапе } #endif var existingTemplates = await templateService.Get() .AsNoTracking() .Include(t => t.UnitsInTemplate) .Where(t => t.JobId == jobId && t.StatusTypeId == TemplateStatusTypeEnum.Used) .ToListAsync(); foreach (var template in existingTemplates) { if (unitIds.Contains(template.UnitId)) { var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(job, template.UnitId); if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase)) { logger.LogDebug("Шаблон {TemplateId} требует обновления имени: старое = '{OldName}', новое = '{NewName}'", template.Id, template.Name, expectedName); var nextRun = await GetNextRunAsync(job, template.NextRun); var updateRequest = new TemplateUpdaterMq { TemplateId = template.Id, JobId = jobId, UnitId = template.UnitId, Name = expectedName, IsActiveTemplate = template.IsActiveTemplate, IsActiveSchedule = template.IsActiveSchedule, LastRun = template.LastRun, NextRun = nextRun, Index = template.Index, StatusTypeId = TemplateStatusTypeEnum.Used, Initiator = initiator, UnitsInTemplate = new List() // для простого шаблона }; await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); } } } logger.LogInformation("Обновление шаблонов завершено для Job {JobId}.", jobId); } private async Task CreateSimpleTemplateAsync(Guid jobId, Guid unitId, HistoryInitiator initiator) { logger.LogInformation("Создание нового простого шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId); var mqRequest = new TemplateGeneratorMq { JobId = jobId, UnitId = unitId, UnitsInTemplate = new List(), // для простого шаблона HistoryInitiator = initiator }; var msg = JsonSerializer.Serialize(mqRequest); var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg }); if (!result.IsSuccess) logger.LogError("Ошибка отправки команды создания простого шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId); } private async Task GetNextRunAsync(Job job, DateTimeOffset? currentNextRun = null) { var referenceDate = job.Group?.ReferenceDate ?? DateTimeOffset.UtcNow; return await esppScheduleTransformService.GetNextDateAsync(job.GroupId, referenceDate); } }