using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using PARR.Core.Extensions; using PARR.Core.Repositories.Interfaces; using PARR.Core.Repositories.Interfaces.JobRepositories; using PARR.Core.Repositories.Interfaces.TemplateRepositories; using PARR.Core.Repositories.Interfaces.Unit; using PARR.Core.Services.NextRunServices; using PARR.Domain.Common.Rabbit.Messages.TemplateMatching; using PARR.Domain.Entities; using PARR.Domain.Entities.JobEntities; using PARR.Domain.Entities.TemplateEntities; using PARR.Domain.Enums; namespace PARR.TemplateUpdater.Services { internal class TemplateUpdaterService : ITemplateUpdaterService { private readonly ILogger logger; private readonly ITemplateRepository templateService; private readonly IJobRepository jobService; private readonly IUnitRepository unitService; private readonly IRobotConfigurationRepository robotConfigurationService; private readonly INextRunService nextRunService; private readonly IUnitInValueRepository unitInValueService; private readonly ITemplateRenamePendingRepository _templateRenamePendingRepository; public TemplateUpdaterService( ILogger logger, ITemplateRepository templateService, IJobRepository jobService, IUnitRepository unitService, IRobotConfigurationRepository robotConfigurationService, INextRunService nextRunService, IUnitInValueRepository unitInValueService, ITemplateRenamePendingRepository templateRenamePendingRepository ) { this.logger = logger; this.templateService = templateService; this.jobService = jobService; this.unitService = unitService; this.robotConfigurationService = robotConfigurationService; this.nextRunService = nextRunService; this.unitInValueService = unitInValueService; _templateRenamePendingRepository = templateRenamePendingRepository; } public async Task UpdateTemplateAsync(TemplateUpdaterMessage query) { var isValid = await IsValidAsync(query); if (!isValid) { logger.LogError("Не валидны входные данные. Конец обработки сообщения {Query}", query.ToJson()); return; } var template = await templateService.Get() .Include(t => t.RobotConfigurations) .Include(t => t.UnitsInTemplate) //.AsSplitQuery() .AsSingleQuery() .FirstOrDefaultAsync(t => t.Id == query.TemplateId); if (template == null) { logger.LogError("Не найден шаблон с id {TemplateId}", query.TemplateId); return; } var templateIsChanged = false; var scheduleIsChanged = false; var trimmedNewName = query.Name.Trim(); if (template.Name != trimmedNewName) { var prepareOldNameResult = await PrepareOldTemplateNameAsync(template.Name, trimmedNewName, template); if (!prepareOldNameResult) return; template.Name = trimmedNewName; templateIsChanged = true; scheduleIsChanged = true; } if (template.JobId != query.JobId) { template.JobId = query.JobId; templateIsChanged = true; scheduleIsChanged = true; } if (template.IsActiveTemplate != query.IsActiveTemplate) { template.IsActiveTemplate = query.IsActiveTemplate; templateIsChanged = true; } if (template.IsActiveSchedule != query.IsActiveSchedule) { template.IsActiveSchedule = query.IsActiveSchedule; scheduleIsChanged = true; } if (template.UnitId != query.UnitId) { template.UnitId = query.UnitId; templateIsChanged = true; scheduleIsChanged = true; } template.Index = query.Index; template.StatusTypeId = query.StatusTypeId; // === Обработка изменения состава UnitsInTemplate === // Формируем HashSet пар (UnitId, UnitFieldValueId) для быстрого сравнения var currentEntries = template.UnitsInTemplate .Select(u => new UnitInTemplateMessage { UnitId = u.UnitId, UnitFieldValueId = u.UnitFieldValueId }) .ToHashSet(); var newEntries = query.UnitsInTemplate.ToHashSet(); if (!currentEntries.SetEquals(newEntries)) { // Удаляем связи, которых нет в новом наборе var toRemove = template.UnitsInTemplate .Where(u => !newEntries.Contains(new UnitInTemplateMessage { UnitId = u.UnitId, UnitFieldValueId = u.UnitFieldValueId })) .ToList(); foreach (var item in toRemove) template.UnitsInTemplate.Remove(item); // Добавляем новые связи var toAdd = newEntries.Except(currentEntries); foreach (var entry in toAdd) { template.UnitsInTemplate.Add(new UnitsInTemplate { TemplateId = template.Id, UnitId = entry.UnitId, UnitFieldValueId = entry.UnitFieldValueId, DateCreated = DateTimeOffset.UtcNow }); } templateIsChanged = true; } // === Конец обработки UnitsInTemplate === // Для правильного расчета shortcod'ов в БД должны быть сохранены данные UnitsInTemplate, JobId, UnitId и т.д. if (templateIsChanged && !await templateService.CommitAsync(query.Initiator)) { logger.LogError("Не удалось сохранить изменения в БД. {QueryType}: {Query}", nameof(TemplateUpdaterMessage), query.ToJson()); return; } var nextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, query.IsNew); if (!nextRun.HasValue) { logger.LogError("Ошибка при расчете нового nextRun (вернулся null) для шаблона {TemplateId}, {TemplateName}", template.Id, template.Name); return; } if (template.NextRun != nextRun.Value) { template.LastRun = template.NextRun; template.NextRun = nextRun.Value; scheduleIsChanged = true; } if (templateIsChanged) { var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.TemplateOrder, template); robotConfigurationService.SetUpdateTaskStatusIfAllow(config); logger.LogDebug("Для шаблона {TemplateId} устанавливаю статус {Status}", template.Id, RobotStatusEnum.Wait.ToString()); } if (scheduleIsChanged) { var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.ScheduleOrder, template); robotConfigurationService.SetUpdateTaskStatusIfAllow(config); logger.LogDebug("Для расписания {TemplateId} устанавливаю статус {Status}", template.Id, RobotStatusEnum.Wait.ToString()); } if (!await templateService.CommitAsync(query.Initiator)) { logger.LogError("Не удалось сохранить изменения в БД. {QueryType}: {Query}", nameof(TemplateUpdaterMessage), query.ToJson()); return; } logger.LogInformation("Выполнено изменение шаблона в БД. Отправлен запрос на синхронизацию шаблона: {TemplateIsChanged}, расписания: {ScheduleIsChanged}. Query {Query}", templateIsChanged, scheduleIsChanged, query.ToJson()); } /// /// Валидация входящего сообщения /// /// /// private async Task IsValidAsync(TemplateUpdaterMessage query) { var template = await templateService.Get().AsNoTracking() .Include(t => t.RobotConfigurations) .FirstOrDefaultAsync(t => t.Id == query.TemplateId); if (template == null) { logger.LogError("Сообщение не валидно. Не найден шаблон с id: {TemplateId}", query.TemplateId); return false; } //if (template.ScheduleEsppId == null) //{ // logger.LogError("Сообщение не валидно. Номер расписания не может быть null, робот не сможет обновить шаблон: {TemplateName}", template.Name); // return false; //} var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.TemplateOrder, template); if (config == null) { logger.LogError("Сообщение не валидно. Не создана конфигурация роботов: {TemplateName}", template.Name); return false; } //if (config.TaskStatusCode != (int)TaskStatusEnum.Ok) //{ // logger.LogError("Сообщение не валидно. Не закончено создание или предыдущее обновление - нельзя начинать новое изменение шаблона: {TemplateName}", template.Name); // return false; //} if (string.IsNullOrEmpty(query.Name?.Trim())) { logger.LogError("Сообщение не валидно. Имя шаблона не может быть пустым: {TemplateName}", query.Name); return false; } 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("Сообщение не валидно. Имя шаблона не уникально: {TemplateName}", query.Name); return false; } var job = await jobService.Get().AsNoTracking().AnyAsync(t => t.Id == query.JobId); if (!job) { logger.LogError("Сообщение не валидно. Не найдена работа с JobId: {JobId}", query.JobId); return false; } var unit = await unitService.Get().AsNoTracking().AnyAsync(t => t.Id == query.UnitId); if (!unit) { logger.LogError("Сообщение не валидно. Не найден unit с UnitId: {UnitId}", query.UnitId); return false; } if (query.UnitsInTemplate != null && query.UnitsInTemplate.Any()) { var pairsToCheck = query.UnitsInTemplate.Distinct().ToList(); var unitIdsToCheck = pairsToCheck.Select(p => p.UnitId).Distinct().ToList(); var valueIdsToCheck = pairsToCheck.Select(p => p.UnitFieldValueId).Distinct().ToList(); // Загружаем только те связи, которые относятся к проверяемым ID var existingLinks = await unitInValueService.Get() .AsNoTracking() .Where(uiv => unitIdsToCheck.Contains(uiv.UnitId) && valueIdsToCheck.Contains(uiv.ValueId)) .Select(uiv => new { uiv.UnitId, uiv.ValueId }) .ToListAsync(); // Формируем HashSet существующих пар для быстрой проверки в памяти var existingPairsSet = existingLinks .Select(x => (x.UnitId, UnitFieldValueId: x.ValueId)) .ToHashSet(); // Проверяем, что каждая запрошенная пара реально существует в БД var allPairsValid = pairsToCheck.All(p => existingPairsSet.Contains((p.UnitId, p.UnitFieldValueId))); if (!allPairsValid) { logger.LogError("Сообщение не валидно. Не все пары (UnitId, UnitFieldValueId) из UnitsInTemplate существуют в UnitInValue."); return false; } } return true; } /// /// Добавление записи в таблицу ожидания переименования /// /// /// /// /// private async Task PrepareOldTemplateNameAsync(string oldName, string newName, Template template) { var existRenamePending = await _templateRenamePendingRepository.Get() .FirstOrDefaultAsync(t => t.TemplateId == template.Id); if (existRenamePending != null) { logger.LogInformation( "Найдено существующее задание на переименование шаблона {TemplateId}. " + "Обновляю OldName с '{OriginalOldName}' на '{NewOldName}'.", template.Id, existRenamePending.OldName, oldName); // Подменяем имя шаблона existRenamePending.OldName = oldName; existRenamePending.DateModified = DateTimeOffset.UtcNow; return true; } else { // Создаем запись var pendingRename = new TemplateRenamePending { TemplateId = template.Id, DateCreated = DateTimeOffset.UtcNow, OldName = oldName }; logger.LogInformation( "Добавлен шаблон в таблицу ожидания переименования. TemplateId: {TemplateId}, OldName: '{OldName}', NewName: '{NewName}'.", template.Id, oldName, newName); var addResult = await _templateRenamePendingRepository.CreateAsync(pendingRename); if (!addResult) return false; return true; } #region Old //// Проверяем, не запущено ли уже переименование для этого шаблона //var alreadyPending = await _templateRenamePendingRepository.Get() // .AsNoTracking() // .FirstOrDefaultAsync(t => t.TemplateId == template.Id); //if (alreadyPending != null) //{ // logger.LogError("При попытке переименования шаблона {TemplateId}, из '{OldName}' в '{NewName}', " + // "произошла ошибка, этот шаблон уже находится в процессе переименования (старое имя {PendingName})", template.Id, oldName, newName, alreadyPending.OldName); // return false; //} //// Уникально ли имя в таблице ожидания переименования //var existPendingOldName = await _templateRenamePendingRepository.Get() // .AsNoTracking() // .FirstOrDefaultAsync(t => t.OldName == oldName); //if (existPendingOldName != null) //{ // logger.LogError("При добавлении старого имени в таблицу ожидания для шаблона {TemplateId} обнаружен конфликт: " + // "имя '{ExistOldName}' уже зарезервировано другим процессом для шаблона {ExistTemplateId}", // template.Id, existPendingOldName.OldName, existPendingOldName.TemplateId); // return false; //} //// Все нормально, добавляем запись в таблицу //var pendingRename = new TemplateRenamePending //{ // TemplateId = template.Id, // DateCreated = DateTimeOffset.UtcNow, // OldName = oldName, // Template = template //}; //var addResult = await _templateRenamePendingRepository.CreateAsync(pendingRename); //if (!addResult) // return false; //return true; #endregion } } }