diff --git a/PARR.DAL/DomainServices/Implementations/UnitFilterService.cs b/PARR.DAL/DomainServices/Implementations/UnitFilterService.cs index 0b166fde..485ff273 100644 --- a/PARR.DAL/DomainServices/Implementations/UnitFilterService.cs +++ b/PARR.DAL/DomainServices/Implementations/UnitFilterService.cs @@ -12,6 +12,10 @@ namespace PARR.DAL.DomainServices.Implementations { internal class UnitFilterService : IUnitFilterService { +#if DEBUG + private readonly Guid targetUnitId = Guid.Parse("d4322a08-246b-4380-8953-8ce4a8446235"); +#endif + private readonly ILogger logger; private readonly IJobService jobService; private readonly IUnitService unitService; @@ -118,6 +122,19 @@ namespace PARR.DAL.DomainServices.Implementations logger.LogDebug("Базовый фильтр по Name '{NameFilter}' дал {Count} юнитов", filter.UnitFilter, initialUnitIds.Count); +#if DEBUG + // ✅ Отладка: проверить, есть ли юнит в initialUnitIds + if (initialUnitIds.Contains(targetUnitId)) + { + logger.LogDebug("Юнит {TargetUnitId} найден в initialUnitIds.", targetUnitId); + } + else + { + logger.LogDebug("Юнит {TargetUnitId} НЕ найден в initialUnitIds.", targetUnitId); + //continue; // ❌ юнит отсеялся на этом этапе + } +#endif + if (!initialUnitIds.Any()) { logger.LogDebug("Фильтр #{Index}: 0 юнитов после UnitFilter. Пропускаем.", filterNumber); @@ -249,14 +266,42 @@ namespace PARR.DAL.DomainServices.Implementations candidateUnits = ApplyFieldFilter(candidateUnits, fieldFilter); } - logger.LogDebug("После FieldFilter осталось {Count} юнитов", candidateUnits.Count()); +#if DEBUG + // ✅ Отладка: проверить, есть ли юнит в candidateUnits после FieldFilter + var candidateListAfterFieldFilter = candidateUnits.ToList(); + if (candidateListAfterFieldFilter.Any(u => u.Id == targetUnitId)) + { + logger.LogDebug("Юнит {TargetUnitId} найден в candidateUnits после FieldFilter.", targetUnitId); + } + else + { + logger.LogDebug("Юнит {TargetUnitId} НЕ найден в candidateUnits после FieldFilter.", targetUnitId); + //continue; // ❌ юнит отсеялся на этом этапе + } + + logger.LogDebug("После FieldFilter осталось {Count} юнитов", candidateListAfterFieldFilter.Count); +#endif foreach (var relFilter in filter.RelationshipFilters) { candidateUnits = ApplyRelationshipFilterToQuery(candidateUnits, relFilter); } - logger.LogDebug("После RelationshipFilter осталось {Count} юнитов", candidateUnits.Count()); +#if DEBUG + // ✅ Отладка: проверить, есть ли юнит в candidateUnits после RelationshipFilter + var candidateListAfterRelFilter = candidateUnits.ToList(); + if (candidateListAfterRelFilter.Any(u => u.Id == targetUnitId)) + { + logger.LogDebug("Юнит {TargetUnitId} найден в candidateUnits после RelationshipFilter.", targetUnitId); + } + else + { + logger.LogDebug("Юнит {TargetUnitId} НЕ найден в candidateUnits после RelationshipFilter.", targetUnitId); + //continue; // ❌ юнит отсеялся на этом этапе + } + + logger.LogDebug("После RelationshipFilter осталось {Count} юнитов", candidateListAfterRelFilter.Count); +#endif // 9️ Umbrella-фильтр var finalUnits = candidateUnits.AsEnumerable(); @@ -264,6 +309,19 @@ namespace PARR.DAL.DomainServices.Implementations { finalUnits = ApplyRelationshipCountFilter(finalUnits, job, filter.RelationshipFilters); logger.LogDebug("После Umbrella-фильтра осталось {Count} юнитов", finalUnits.Count()); + +#if DEBUG + // ✅ Отладка: проверить, есть ли юнит в finalUnits после Umbrella + if (finalUnits.Any(u => u.Id == targetUnitId)) + { + logger.LogDebug("Юнит {TargetUnitId} найден в finalUnits после Umbrella-фильтра.", targetUnitId); + } + else + { + logger.LogDebug("Юнит {TargetUnitId} НЕ найден в finalUnits после Umbrella-фильтра.", targetUnitId); + //continue; // ❌ юнит отсеялся на этом этапе + } +#endif } // 10 Взять ID @@ -272,6 +330,19 @@ namespace PARR.DAL.DomainServices.Implementations .Take(remaining) .ToList(); +#if DEBUG + // ✅ Отладка: проверить, есть ли юнит в newIds + if (newIds.Contains(targetUnitId)) + { + logger.LogDebug("Юнит {TargetUnitId} найден в newIds.", targetUnitId); + } + else + { + logger.LogDebug("Юнит {TargetUnitId} НЕ найден в newIds.", targetUnitId); + // ❌ юнит отсеялся на этапе Take(remaining), если его не было в finalUnits + } +#endif + collectedIds.UnionWith(newIds); logger.LogDebug("Фильтр #{Index}: найдено {Count} Unit'ов. Всего: {Total}", filterNumber, newIds.Count, collectedIds.Count); @@ -287,6 +358,18 @@ namespace PARR.DAL.DomainServices.Implementations logger.LogInformation("Job {JobId}: из {FilterCount} фильтров получено {UnitCount} уникальных Unit'ов", job.Id, job.UnitFilters.Count, result.Count); +#if DEBUG + // ✅ Отладка: проверить, есть ли юнит в result + if (result.Contains(targetUnitId)) + { + logger.LogDebug("Юнит {TargetUnitId} найден в финальном результате.", targetUnitId); + } + else + { + logger.LogDebug("Юнит {TargetUnitId} НЕ найден в финальном результате.", targetUnitId); + } +#endif + return result; } @@ -403,15 +486,45 @@ namespace PARR.DAL.DomainServices.Implementations return query.AsEnumerable().Where(dto => { +#if DEBUG + // ✅ Отладка: проверить, это нужный юнит + if (dto.Id == targetUnitId) + { + logger.LogDebug("Проверяем юнит {TargetUnitId} с {ParentCount} родителями и {ChildCount} детьми.", + targetUnitId, dto.Parents.Count, dto.Children.Count); + } +#endif + var links = isParent ? dto.Parents : dto.Children; if (links == null || !links.Any()) { +#if DEBUG + // ✅ Отладка: юнит не имеет связей + if (dto.Id == targetUnitId) + { + logger.LogDebug("Юнит {TargetUnitId}: нет связей ({Direction}), результат фильтра: {Result}", + targetUnitId, isParent ? "Parent" : "Child", isFullMatch && isInverse); + } +#endif + // Для IsFullMatch/IsInverse — "все подходят", т.е. true // Для остальных — false return isFullMatch && isInverse; } +#if DEBUG + // ✅ Отладка: проверить, какие связи у юнита + if (dto.Id == targetUnitId) + { + logger.LogDebug("Юнит {TargetUnitId}: {Count} связей ({Direction}).", targetUnitId, links.Count, isParent ? "Parent" : "Child"); + foreach (var link in links) + { + logger.LogDebug(" Связь: UnitId={LinkUnitId}, Name={LinkName}, Values=[{Values}]", link.UnitId, link.Name, string.Join(", ", link.Values.Select(v => $"{v.FieldId}={v.Value}"))); + } + } +#endif + var hasMatchingLinks = links.Any(link => { var hasMatch = link.Values.Any(v => @@ -423,9 +536,25 @@ namespace PARR.DAL.DomainServices.Implementations v.Value.Contains(valueMask, StringComparison.OrdinalIgnoreCase)) ); +#if DEBUG + // ✅ Отладка: проверить, какая связь подходит + if (dto.Id == targetUnitId) + { + logger.LogDebug(" Проверка связи {LinkUnitId}: hasMatch={HasMatch} (ValueMask={ValueMask}, FieldId={FieldId})", link.UnitId, hasMatch, valueMask, fieldId); + } +#endif + return isInverse ? !hasMatch : hasMatch; }); +#if DEBUG + // ✅ Отладка: результат hasMatchingLinks + if (dto.Id == targetUnitId) + { + logger.LogDebug(" hasMatchingLinks = {HasMatchingLinks}, isInverse = {IsInverse}", hasMatchingLinks, isInverse); + } +#endif + if (isFullMatch) { var allMatch = links.All(link => @@ -439,9 +568,25 @@ namespace PARR.DAL.DomainServices.Implementations v.Value.Contains(valueMask, StringComparison.OrdinalIgnoreCase)) ); +#if DEBUG + // ✅ Отладка: проверить, все ли связи подходят + if (dto.Id == targetUnitId) + { + logger.LogDebug(" Проверка связи {LinkUnitId} для FullMatch: hasMatch={HasMatch}", link.UnitId, hasMatch); + } +#endif + return isInverse ? !hasMatch : hasMatch; }); +#if DEBUG + // ✅ Отладка: результат allMatch + if (dto.Id == targetUnitId) + { + logger.LogDebug(" allMatch = {AllMatch}, isFullMatch = {IsFullMatch}", allMatch, isFullMatch); + } +#endif + return allMatch; } diff --git a/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs b/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs index 1a6f7721..7e24b340 100644 --- a/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs +++ b/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs @@ -5,8 +5,6 @@ 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.DomainServices.Shortcodes.Models; using PARR.DAL.Models; using PARR.DAL.Models.Job; using PARR.DAL.Models.Unit; @@ -18,12 +16,14 @@ using PARR.TemplateMatcher.Services.Interfaces; using PARR.TemplateMatcher.Settings; using System.Text.Json; -namespace PARR.TemplateMatcher.Services.Implemetaions; +namespace PARR.TemplateMatcher.Services.Implementations; internal class GroupedTemplateSynchronizer : ITemplateSynchronizer { - private const bool DefaultUnusedTemplateState = false; - private const bool DefaultUnusedScheduleState = false; +#if DEBUG + private readonly Guid targetUnitId = Guid.Parse("d4322a08-246b-4380-8953-8ce4a8446235"); +#endif + private const bool DefaultUsedTemplateState = false; private const bool DefaultUsedScheduleState = false; @@ -37,11 +37,12 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer private readonly ITemplateService templateService; private readonly IJobGroupService jobGroupService; private readonly ITemplateReuser templateReuser; - private readonly IShortcodesService shortcodesService; private readonly IEsppScheduleTransformService esppScheduleTransformService; private readonly IUnitRegionalEkPtkGroupService regionalEkPtkGroupService; private readonly IUnitFieldService unitFieldService; - private readonly IJobService jobService; + private readonly ITemplateDeactivator templateDeactivator; + private readonly ITemplateNameNormalizer templateNameNormalizer; + private readonly ITemplateUpdaterMqSender templateUpdaterMqSender; public GroupedTemplateSynchronizer( ILogger logger, @@ -54,11 +55,12 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer ITemplateService templateService, IJobGroupService jobGroupService, ITemplateReuser templateReuser, - IShortcodesService shortcodesService, IEsppScheduleTransformService esppScheduleTransformService, IUnitRegionalEkPtkGroupService regionalEkPtkGroupService, IUnitFieldService unitFieldService, - IJobService jobService + ITemplateDeactivator templateDeactivator, + ITemplateNameNormalizer templateNameNormalizer, + ITemplateUpdaterMqSender templateUpdaterMqSender ) { this.logger = logger; @@ -71,11 +73,12 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer this.templateService = templateService; this.jobGroupService = jobGroupService; this.templateReuser = templateReuser; - this.shortcodesService = shortcodesService; this.esppScheduleTransformService = esppScheduleTransformService; this.regionalEkPtkGroupService = regionalEkPtkGroupService; this.unitFieldService = unitFieldService; - this.jobService = jobService; + this.templateDeactivator = templateDeactivator; + this.templateNameNormalizer = templateNameNormalizer; + this.templateUpdaterMqSender = templateUpdaterMqSender; } public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator) @@ -108,7 +111,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer var jobsInGroup = jobGroup.Jobs.ToList(); - // --- Получение FieldId и разрешённых значений для "РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК" --- + // --- НОВАЯ ЛОГИКА: Получение FieldId и разрешённых значений для "РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК" --- var workGroupField = await unitFieldService.GetByAihitNameAsync("РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК"); if (workGroupField == null) { @@ -123,17 +126,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer logger.LogDebug("Найдено {Count} значений из UnitRegionalEkPtkGroup для проверки поля 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК'.", regionalGroupValueIds.Count); - - // Загружаем заранее подготовленный Job (например, для неиспользуемых шаблонов) - var unusedJobId = Guid.Parse("8f85a91c-a223-4686-bb69-1f0ee73624f2"); - - var unusedJob = await jobService.Get() - .AsNoTracking() - .Include(j => j.Tnk) - .Include(j => j.Group) - .ThenInclude(g => g.GroupType) - .FirstOrDefaultAsync(j => j.Id == unusedJobId); - // 2. Найти Job с максимальным MaxValueRelationships var maxJob = jobsInGroup .Where(j => j.MaxValueRelationships.HasValue) @@ -162,6 +154,19 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer return; } +#if DEBUG + // ✅ Отладка: проверить, есть ли юнит в expectedUnitIds + if (expectedUnitIds.Contains(targetUnitId)) + { + logger.LogDebug("Юнит {TargetUnitId} найден в expectedUnitIds.", targetUnitId); + } + else + { + logger.LogDebug("Юнит {TargetUnitId} НЕ найден в expectedUnitIds.", targetUnitId); + //return; // ❌ юнит отсеялся на этом этапе + } +#endif + // 4. Отфильтровать expectedUnitIds по GroupingUnitFieldId (дополнительный фильтр) if (!jobGroup.GroupingUnitFieldId.HasValue) { @@ -185,6 +190,19 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer .Select(u => u.Id) .ToHashSet(); +#if DEBUG + // ✅ Отладка: проверить, есть ли юнит в unitIdsWithValidGroupingFieldSet + if (unitIdsWithValidGroupingFieldSet.Contains(targetUnitId)) + { + logger.LogDebug("Юнит {TargetUnitId} найден в unitIdsWithValidGroupingFieldSet.", targetUnitId); + } + else + { + logger.LogDebug("Юнит {TargetUnitId} НЕ найден в unitIdsWithValidGroupingFieldSet.", targetUnitId); + //return; // ❌ юнит отсеялся на этом этапе + } +#endif + logger.LogDebug("После фильтрации по GroupingUnitFieldId осталось {Count} юнитов.", unitIdsWithValidGroupingFieldSet.Count); if (!unitIdsWithValidGroupingFieldSet.Any()) @@ -204,6 +222,19 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer .Select(u => u.Id) // Выбираем Id юнита .ToHashSet(); // И снова в HashSet +#if DEBUG + // ✅ Отладка: проверить, есть ли юнит в unitIdsWithValidWorkGroupFieldSet + if (unitIdsWithValidWorkGroupFieldSet.Contains(targetUnitId)) + { + logger.LogDebug("Юнит {TargetUnitId} найден в unitIdsWithValidWorkGroupFieldSet.", targetUnitId); + } + else + { + logger.LogDebug("Юнит {TargetUnitId} НЕ найден в unitIdsWithValidWorkGroupFieldSet.", targetUnitId); + //return; // ❌ юнит отсеялся на этом этапе + } +#endif + logger.LogDebug("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' осталось {Count} юнитов.", unitIdsWithValidWorkGroupFieldSet.Count); if (!unitIdsWithValidWorkGroupFieldSet.Any()) @@ -225,6 +256,20 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer .Where(link => unitIdsWithValidGroupingFieldSet.Contains(link.ParentUnitId) || unitIdsWithValidGroupingFieldSet.Contains(link.ChildUnitId)) .ToListAsync(); +#if DEBUG + // ✅ Отладка: проверить, юнит участвует в potentialUnitInUnitLinks + var potentialLinksContainingTarget = potentialUnitInUnitLinks.Where(l => l.ParentUnitId == targetUnitId || l.ChildUnitId == targetUnitId).ToList(); + if (potentialLinksContainingTarget.Any()) + { + logger.LogDebug("Юнит {TargetUnitId} участвует в {Count} потенциальных связях UnitInUnit.", targetUnitId, potentialLinksContainingTarget.Count); + } + else + { + logger.LogDebug("Юнит {TargetUnitId} НЕ участвует в потенциальных связях UnitInUnit.", targetUnitId); + // ❌ юнит отсеялся на этом этапе, если связи не требовались + } +#endif + logger.LogDebug("Найдено {Count} потенциальных связей UnitInUnit.", potentialUnitInUnitLinks.Count); // Загрузить UnitInValue для всех ParentUnitId и ChildUnitId из potentialUnitInUnitLinks @@ -288,6 +333,20 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer } } +#if DEBUG + // ✅ Отладка: проверить, юнит участвует в filteredUnitInUnitLinks + var filteredLinksContainingTarget = filteredUnitInUnitLinks.Where(l => l.ParentUnitId == targetUnitId || l.ChildUnitId == targetUnitId).ToList(); + if (filteredLinksContainingTarget.Any()) + { + logger.LogDebug("Юнит {TargetUnitId} участвует в {Count} отфильтрованных связях UnitInUnit.", targetUnitId, filteredLinksContainingTarget.Count); + } + else + { + logger.LogDebug("Юнит {TargetUnitId} НЕ участвует в отфильтрованных связях UnitInUnit.", targetUnitId); + // ❌ юнит отсеялся на этом этапе, если связи требовались + } +#endif + logger.LogDebug("После применения RelationshipFilters осталось {Count} связей UnitInUnit.", filteredUnitInUnitLinks.Count); // --- Сгруппировать юниты из unitIdsWithValidGroupingFieldSet по связанному юниту --- @@ -317,6 +376,22 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer logger.LogDebug("Сформировано {Count} групп по связанным юнитам до разрешения конфликтов.", groupedRelationships.Count); +#if DEBUG + // ✅ Отладка: проверить, юнит есть в groupedRelationships.Values + var allUnitsInGroups = groupedRelationships.Values.SelectMany(x => x).ToList(); + if (allUnitsInGroups.Contains(targetUnitId)) + { + logger.LogDebug("Юнит {TargetUnitId} найден в groupedRelationships.Values до разрешения конфликтов.", targetUnitId); + } + else + { + logger.LogDebug("Юнит {TargetUnitId} НЕ найден в groupedRelationships.Values до разрешения конфликтов.", targetUnitId); + // ❌ юнит отсеялся на этапе группировки + } + + logger.LogDebug("Содержимое groupedRelationships до разрешения конфликтов: [{Groups}]", string.Join(", ", groupedRelationships.Select(kvp => $"Key: {kvp.Key}, Values: [{string.Join(", ", kvp.Value)}]"))); +#endif + // --- Разрешение конфликта - один юнит из unitIdsWithValidGroupingFieldSet только в одном списке значений --- var unitToKeys = new Dictionary>(); // Карта: юнит из списка -> список ключей, где он встречается @@ -334,9 +409,34 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer } } +#if DEBUG + // ✅ Отладка: проверить, юнит есть в unitToKeys до поиска конфликтов + if (unitToKeys.ContainsKey(targetUnitId)) + { + logger.LogDebug("Юнит {TargetUnitId} найден в unitToKeys до поиска конфликтов, находится в {Count} группах: [{Groups}]", targetUnitId, unitToKeys[targetUnitId].Count, string.Join(", ", unitToKeys[targetUnitId])); + } + else + { + logger.LogDebug("Юнит {TargetUnitId} НЕ найден в unitToKeys до поиска конфликтов.", targetUnitId); + } +#endif + // Найти юниты, которые находятся в нескольких списках var conflictedUnits = unitToKeys.Where(kvp => kvp.Value.Count > 1).ToList(); +#if DEBUG + // ✅ Отладка: проверить, юнит в conflictedUnits + var targetConflictedEntry = conflictedUnits.FirstOrDefault(c => c.Key == targetUnitId); + if (targetConflictedEntry.Key != default) + { + logger.LogDebug("Юнит {TargetUnitId} находится в {Count} группах (конфликт).", targetUnitId, targetConflictedEntry.Value.Count); + } + else + { + logger.LogDebug("Юнит {TargetUnitId} НЕ находится в конфликте (не в нескольких группах).", targetUnitId); + } +#endif + foreach (var conflictedUnitEntry in conflictedUnits) { var unitId = conflictedUnitEntry.Key; @@ -367,6 +467,20 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer } } +#if DEBUG + // ✅ Отладка: проверить, юнит есть в groupedRelationships.Values после разрешения конфликтов + var allUnitsInGroupsAfterConflictResolution = groupedRelationships.Values.SelectMany(x => x).ToList(); + if (allUnitsInGroupsAfterConflictResolution.Contains(targetUnitId)) + { + logger.LogDebug("Юнит {TargetUnitId} найден в groupedRelationships.Values после разрешения конфликтов.", targetUnitId); + } + else + { + logger.LogDebug("Юнит {TargetUnitId} НЕ найден в groupedRelationships.Values после разрешения конфликтов.", targetUnitId); + // ❌ юнит отсеялся на этапе разрешения конфликтов + } +#endif + // Удаляем ключи, у которых список стал пустым после разрешения конфликтов var keysToRemove = groupedRelationships.Where(kvp => kvp.Value.Count == 0).Select(kvp => kvp.Key).ToList(); foreach (var key in keysToRemove) @@ -377,11 +491,26 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer logger.LogDebug("Сформировано {Count} групп по связанным юнитам после разрешения конфликтов.", groupedRelationships.Count); +#if DEBUG + logger.LogDebug("Содержимое groupedRelationships после разрешения конфликтов: [{Groups}]", string.Join(", ", groupedRelationships.Select(kvp => $"Key: {kvp.Key}, Values: [{string.Join(", ", kvp.Value)}]"))); +#endif + // 7. Разбить каждую группу и сопоставить с Job foreach (var kvp in groupedRelationships) { var relationshipUnitId = kvp.Key; // Связанный юнит (не из unitIdsWithValidGroupingFieldSet) var childUnitIds = kvp.Value; // Юниты из unitIdsWithValidGroupingFieldSet, связанные с regionalUnitId + if (childUnitIds.Count == 0) + continue; +#if DEBUG + // ✅ Отладка: проверить, есть ли ВРТ-AOS-05-ДВС в childUnitIds + var childUnitNames = childUnitIds.Select(id => unitService.Get().AsNoTracking().Where(u => u.Id == id).Select(u => u.Name).FirstOrDefaultAsync().GetAwaiter().GetResult() ?? id.ToString()).ToList(); + if (childUnitNames.Contains("ВРТ-AOS-05-ДВС")) + { + var relationshipUnitName = await unitService.Get().AsNoTracking().Where(u => u.Id == relationshipUnitId).Select(u => u.Name).FirstOrDefaultAsync(); + logger.LogDebug("Группа с ключом {Key} (название: {Name}) содержит юнит 'ВРТ-AOS-05-ДВС' в childUnitIds: [{ChildUnitNames}]", relationshipUnitId, relationshipUnitName, string.Join(", ", childUnitNames)); + } +#endif logger.LogDebug("Обработка связанного юнита {RegionalUnitId} с {Count} юнитами из списка.", relationshipUnitId, childUnitIds.Count); @@ -431,7 +560,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer logger.LogDebug("Шаблон {TemplateId} (Job {JobId}, Regional {RegionalId}, Index {Index}) актуален по юнитам.", existingTemplateForSubGroup.Id, targetJob.Id, relationshipUnitId, i); // Проверить, изменилось ли имя шаблона (например, из-за %МАКС:...% или %ТНК-КРАТКО%) - var expectedName = await GetNormalizedTemplateNameAsync(targetJob, relationshipUnitId, i, subGroup); + var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(targetJob, relationshipUnitId, i, subGroup); if (!string.Equals(existingTemplateForSubGroup.Name, expectedName, StringComparison.OrdinalIgnoreCase)) { logger.LogDebug("Шаблон {TemplateId} требует обновления имени: старое = '{OldName}', новое = '{NewName}'", existingTemplateForSubGroup.Id, existingTemplateForSubGroup.Name, expectedName); @@ -448,13 +577,13 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer IsActiveSchedule = existingTemplateForSubGroup.IsActiveSchedule, LastRun = existingTemplateForSubGroup.LastRun, NextRun = nextRun, - Index = i , + Index = i, StatusTypeId = TemplateStatusTypeEnum.Used, Initiator = initiator, UnitsInTemplate = subGroup }; - await SendTemplateUpdateMessage(updateRequest); + await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); } else { @@ -485,7 +614,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer { logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, связанного юнита {RegionalId}, Index {Index}.", reusableTemplate.Id, targetJob.Id, relationshipUnitId, i); - var expectedName = await GetNormalizedTemplateNameAsync(targetJob, relationshipUnitId, i, subGroup); + var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(targetJob, relationshipUnitId, i, subGroup); var nextRun = await GetNextRunAsync(targetJob); var updateRequest = new TemplateUpdaterMq @@ -503,7 +632,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer UnitsInTemplate = subGroup }; - await SendTemplateUpdateMessage(updateRequest); + await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); } else { @@ -553,7 +682,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer if (!expectedTemplateKeys.Contains(key)) { logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, Regional {UnitId}, Index {Index}).", existingTemplate.Id, existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index); - await DeactivateTemplateAsync(existingTemplate, existingTemplate.JobId, unusedJob, initiator); + await templateDeactivator.DeactivateTemplateAsync(existingTemplate, initiator); } } @@ -613,7 +742,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer return; } - var expectedName = await GetNormalizedTemplateNameAsync(targetJob, template.UnitId, template.Index, newUnitIds); + var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(targetJob, template.UnitId, template.Index, newUnitIds); var nextRun = await GetNextRunAsync(targetJob, template.NextRun); var updateRequest = new TemplateUpdaterMq @@ -632,7 +761,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer UnitsInTemplate = newUnitIds }; - await SendTemplateUpdateMessage(updateRequest); + await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); } private async Task CreateGroupedTemplateAsync(Guid jobId, Guid regionalUnitId, List unitIds, int index, HistoryInitiator initiator) @@ -655,132 +784,9 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer logger.LogError("Ошибка отправки команды создания группового шаблона для Job {JobId}, связанного юнита {RegionalUnitId}, Index {Index}.", jobId, regionalUnitId, index); } - private async Task DeactivateTemplateAsync( - Template template, - Guid jobId, - Job unusedJob, - HistoryInitiator initiator) - { - if (template.StatusTypeId == TemplateStatusTypeEnum.Updating) - return true; - - if (template.StatusTypeId == TemplateStatusTypeEnum.Unused) - { - logger.LogDebug("Шаблон {TemplateId} уже неактивен (Unused), пропускаем деактивацию.", template.Id); - return true; - } - - logger.LogInformation("Шаблон {TemplateId} (UnitId {UnitId}) → деактивация.", - template.Id, template.UnitId); - - template.StatusTypeId = TemplateStatusTypeEnum.Updating; - template.DateModified = DateTimeOffset.UtcNow; - - if (!await templateService.CommitAsync(initiator)) - { - logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id); - return false; - } - - if (unusedJob == null) - { - logger.LogError("Job для деактивированных шаблонов не найден."); - return false; - } - - // Вычисляем имя шаблона с новым Job - var expectedName = await GetNormalizedTemplateNameAsync(unusedJob, template.UnitId); - - var updateRequest = new TemplateUpdaterMq - { - TemplateId = template.Id, - JobId = unusedJob.Id, - UnitId = template.UnitId, - Name = GetTemplateNameForUnused(expectedName), - IsActiveTemplate = DefaultUnusedTemplateState, - IsActiveSchedule = DefaultUnusedScheduleState, - LastRun = template.LastRun, - NextRun = template.NextRun, - Index = null, - StatusTypeId = TemplateStatusTypeEnum.Unused, - Initiator = initiator, - UnitsInTemplate = new List() - }; - - await SendTemplateUpdateMessage(updateRequest); - return true; - } - - private async Task SendTemplateUpdateMessage(TemplateUpdaterMq updateRequest) - { - logger.LogDebug("Отправка сообщения в очередь '{Queue}' для шаблона {TemplateId}", - mqSettings.TemplateUpdater.QueueName, updateRequest.TemplateId); - - var msg = JsonSerializer.Serialize(updateRequest); - var result = await mqService.SendAsync(mqSettings.TemplateUpdater, new[] { msg }); - - if (result.IsSuccess) - { - logger.LogInformation("Отправлен запрос на обновление шаблона {TemplateId}", updateRequest.TemplateId); - } - else - { - logger.LogError("Ошибка при отправке запроса на обновление шаблона {TemplateId} в очередь '{Queue}'.", - updateRequest.TemplateId, mqSettings.TemplateUpdater.QueueName); - } - } - - private string GetTemplateNameForUnused(string templateName) - { - return templateName + "_" + DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - } - - private async Task GetNormalizedTemplateNameAsync(Job targetJob, Guid unitId, int? index = null, List? templateUnitIds = null) - { - // Подготовка объекта TemplateForShortcodes для передачи в ShortcodesService - var templateForShortcodes = new TemplateForShortcodes - { - Id = Guid.Empty, // Не используется в подстановке, но нужен для структуры - Index = index+1, - JobId = targetJob.Id, - UnitId = unitId, - Job = new JobForShortcodes - { - Group = targetJob.Group != null ? new JobGroupForShortcodes - { - Id = targetJob.Group.Id, - GroupingUnitFieldId = targetJob.Group.GroupingUnitFieldId, - GroupType = targetJob.Group.GroupType != null ? new JobGroupTypeForShortcodes - { - Code = targetJob.Group.GroupType.Code - } : null, - GroupName = targetJob.Group.GroupName - } : null, - Tnk = targetJob.Tnk != null ? new TnkForShortcodes - { - Name = targetJob.Tnk.Name, - ShortName = targetJob.Tnk.ShortName ?? "" - } : null, - WorkName = targetJob.WorkName, - Name = targetJob.Name - }, - // Преобразование List в List - UnitsInTemplate = templateUnitIds?.Select(id => new UnitInTemplateForShortcodes { UnitId = id }).ToList() ?? new List() - }; - - var rawName = await shortcodesService.ApplyShortcodesAsync(targetJob.TemplateNameMask, templateForShortcodes); - return rawName.ToUpper(); - } - private async Task GetNextRunAsync(Job targetJob, DateTimeOffset? currentNextRun = null) { - var now = DateTimeOffset.UtcNow; - if (currentNextRun.HasValue && currentNextRun.Value > now) - { - return currentNextRun.Value; - } - - var referenceDate = targetJob.Group?.ReferenceDate ?? now; + var referenceDate = targetJob.Group?.ReferenceDate ?? DateTimeOffset.UtcNow; return await esppScheduleTransformService.GetNextDateAsync(targetJob.GroupId, referenceDate); } } \ No newline at end of file diff --git a/PARR.TemplateMatcher/Services/Implemetaions/SimpleTemplateSynchronizer.cs b/PARR.TemplateMatcher/Services/Implemetaions/SimpleTemplateSynchronizer.cs index 0c10d4f0..8e8f3bca 100644 --- a/PARR.TemplateMatcher/Services/Implemetaions/SimpleTemplateSynchronizer.cs +++ b/PARR.TemplateMatcher/Services/Implemetaions/SimpleTemplateSynchronizer.cs @@ -4,25 +4,25 @@ using PARR.BLL.Domain.Mq; using PARR.BLL.Services.Interfaces; using PARR.Common.Domain; using PARR.Constants; -using PARR.DAL.Contracts; using PARR.DAL.DomainServices.Interfaces; using PARR.DAL.DomainServices.Shortcodes; -using PARR.DAL.DomainServices.Shortcodes.Models; -using PARR.DAL.Models; 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.Implemetaions; +namespace PARR.TemplateMatcher.Services.Implementations; internal class SimpleTemplateSynchronizer : ITemplateSynchronizer { - private const bool DefaultUnusedTemplateState = false; - private const bool DefaultUnusedScheduleState = false; +#if DEBUG + private readonly Guid targetUnitId = Guid.Parse("358437ac-1eeb-4c00-840c-998326f657ac"); +#endif + private const bool DefaultUsedTemplateState = false; private const bool DefaultUsedScheduleState = false; @@ -33,19 +33,30 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer private readonly ITemplateService templateService; private readonly IJobService jobService; private readonly ITemplateReuser templateReuser; - private readonly IShortcodesService shortcodesService; 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) + IEsppScheduleTransformService esppScheduleTransformService, + IUnitRegionalEkPtkGroupService regionalEkPtkGroupService, + IUnitFieldService unitFieldService, + ITemplateDeactivator templateDeactivator, + ITemplateNameNormalizer templateNameNormalizer, + ITemplateUpdaterMqSender templateUpdaterMqSender + ) { this.logger = logger; this.unitFilterService = unitFilterService; @@ -54,120 +65,82 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer this.templateService = templateService; this.jobService = jobService; this.templateReuser = templateReuser; - this.shortcodesService = shortcodesService; this.esppScheduleTransformService = esppScheduleTransformService; - } - - public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator) - { - logger.LogWarning("SimpleTemplateSynchronizer: SyncTemplatesForJobGroup вызван для JobGroupId {JobGroupId}. Это не поддерживаемая операция.", jobGroupId); - // Не делаем ничего - return; + this.templateDeactivator = templateDeactivator; + this.templateNameNormalizer = templateNameNormalizer; + this.templateUpdaterMqSender = templateUpdaterMqSender; } public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator) { - logger.LogDebug("Начало синхронизации шаблонов для JobId {JobId}", jobId); + logger.LogDebug("Начало синхронизации шаблонов для Job {JobId}", jobId); - // Загружаем заранее подготовленный Job (например, для неиспользуемых шаблонов) - var unusedJobId = Guid.Parse("8f85a91c-a223-4686-bb69-1f0ee73624f2"); - - var unusedJob = await jobService.Get() + var job = await jobService.Get() .AsNoTracking() + .Include(j => j.AutoControl) .Include(j => j.Tnk) .Include(j => j.Group) - .ThenInclude(g => g.GroupType) - .FirstOrDefaultAsync(j => j.Id == unusedJobId); + .ThenInclude(g => g!.GroupType) + .Include(j => j.UnitFilters) + .ThenInclude(uf => uf.RelationshipFilters) + .FirstOrDefaultAsync(j => j.Id == jobId); - var expectedUnitIds = await GetExpectedUnitIdsAsync(jobId) ?? new HashSet(); - logger.LogDebug("JobId {JobId}: найдено {Count} UnitId по фильтрам.", jobId, expectedUnitIds.Count); - - var job = await GetJobWithGroupAndAutoControlAsync(jobId); if (job == null) { - logger.LogError("Job с Id {JobId} не найден.", jobId); + logger.LogWarning("Job {JobId} не найден.", jobId); return; } - // Проверяем, является ли Job "групповым" - bool isGroupJob = job.Group != null && job.Group.GroupType?.Code == JobGroupTypesEnum.Group; - - if (isGroupJob && job.Group!.GroupingUnitFieldId.HasValue) + var unitIds = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId); + if (unitIds == null || !unitIds.Any()) { - logger.LogInformation("Job {JobId} является групповым. Используйте SyncTemplatesForJobGroup для синхронизации.", jobId); - return; // Ничего не делаем для группового Job + 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(); - logger.LogDebug("JobId {JobId}: {Expected} ожидаемых UnitId, {Existing} существующих шаблонов.", - jobId, expectedUnitIds.Count, existingTemplates.Count); + var existingUnitIds = existingTemplates + .Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Used) + .Select(t => t.UnitId) + .ToHashSet(); - // Обработка случая: фильтр вернул 0 UnitId → деактивировать ВСЕ шаблоны - if (!expectedUnitIds.Any()) - { - if (existingTemplates.Any()) - { - logger.LogInformation("Для JobId {JobId} фильтры не дали Unit'ов — будет деактивировано {Count} шаблонов.", - jobId, existingTemplates.Count); - - foreach (var template in existingTemplates) - { - await DeactivateTemplateAsync(template, jobId, unusedJob, initiator); - } - } - else - { - logger.LogInformation("Для JobId {JobId} нет Unit'ов по фильтрам и нет существующих шаблонов — синхронизация завершена.", jobId); - } - - logger.LogInformation("Синхронизация завершена для JobId {JobId} (фильтр пуст).", jobId); - return; - } - - // Деактивация шаблонов, которые вышли из фильтра - var templatesToDeactivate = existingTemplates - .Where(t => !expectedUnitIds.Contains(t.UnitId)) + var newUnitIds = unitIds.Except(existingUnitIds).ToList(); + var unusedTemplates = existingTemplates + .Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Used && !unitIds.Contains(t.UnitId)) .ToList(); - foreach (var template in templatesToDeactivate) + foreach (var unitId in newUnitIds) { - await DeactivateTemplateAsync(template, jobId, unusedJob, initiator); - } + var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(jobId, unitId, initiator); - // Перечитываем шаблоны после деактивации - existingTemplates = await templateService.Get() - .AsNoTracking() - .Include(t => t.Job) - .ThenInclude(t => t.Tnk) - .Where(t => t.JobId == jobId) - .ToListAsync(); - - var unitToTemplate = existingTemplates.ToDictionary(t => t.UnitId, t => t); - - // UnitId без шаблона → попытка переиспользования или создание - var unitIdsMissingTemplates = expectedUnitIds - .Where(unitId => !unitToTemplate.ContainsKey(unitId)) - .ToList(); - - var unitIdsToCreateFresh = new List(); - - foreach (var unitId in unitIdsMissingTemplates) - { - var reused = await templateReuser.TryReuseOneUnusedTemplateAsync(jobId, unitId, initiator); - if (reused != null) // если захват успешен + if (reusableTemplate != null) { - logger.LogInformation("Переиспользован шаблон {TemplateId} для UnitId {UnitId}.", reused.Id, unitId); + logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, UnitId {UnitId}.", reusableTemplate.Id, jobId, unitId); - var expectedName = await GetNormalizedTemplateNameAsync(job, unitId); - var nextRun = await GetNextRunAsync(job); // всегда пересчитываем для нового назначения + var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(job, unitId); + var nextRun = await GetNextRunAsync(job); var updateRequest = new TemplateUpdaterMq { - TemplateId = reused.Id, + TemplateId = reusableTemplate.Id, JobId = jobId, UnitId = unitId, Name = expectedName, @@ -176,362 +149,135 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer StatusTypeId = TemplateStatusTypeEnum.Used, Initiator = initiator, NextRun = nextRun, - UnitsInTemplate = new List() // Для простого шаблона + UnitsInTemplate = new List() // для простого шаблона }; - await SendTemplateUpdateMessage(updateRequest); + await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); } else { - logger.LogInformation("Нет доступных Unused-шаблонов для UnitId {UnitId} → создадим новый.", unitId); - unitIdsToCreateFresh.Add(unitId); + logger.LogDebug("Создание нового шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId); + await CreateSimpleTemplateAsync(jobId, unitId, initiator); } } - // Обновление/реактивация шаблонов, оставшихся в фильтре - var templatesInFilter = existingTemplates - .Where(t => expectedUnitIds.Contains(t.UnitId)) - .ToList(); - - foreach (var template in templatesInFilter) + foreach (var unusedTemplate in unusedTemplates) { - var expectedName = await GetNormalizedTemplateNameAsync(job, template.UnitId); - await ReactivateOrRenameTemplateAsync(template, job, expectedName, initiator); + logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, UnitId {UnitId}).", unusedTemplate.Id, jobId, unusedTemplate.UnitId); + await templateDeactivator.DeactivateTemplateAsync(unusedTemplate, initiator); } - // Создание новых шаблонов - foreach (var unitId in unitIdsToCreateFresh) - { - await SendTemplateGeneratorMessageAsync(jobId, unitId, initiator); - } + logger.LogInformation("Синхронизация шаблонов завершена для Job {JobId}.", jobId); + } - logger.LogInformation("Синхронизация завершена для JobId {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("Начало обновления шаблонов для JobId {JobId}", jobId); + logger.LogDebug("Обновление шаблонов для Job {JobId}", jobId); - // Загружаем заранее подготовленный Job (например, для неиспользуемых шаблонов) - var unusedJobId = Guid.Parse("8f85a91c-a223-4686-bb69-1f0ee73624f2"); - - var unusedJob = await jobService.Get() + var job = await jobService.Get() .AsNoTracking() + .Include(j => j.AutoControl) .Include(j => j.Tnk) .Include(j => j.Group) - .ThenInclude(g => g.GroupType) - .FirstOrDefaultAsync(j => j.Id == unusedJobId); + .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() - .Where(t => t.JobId == jobId) + .Include(t => t.UnitsInTemplate) + .Where(t => t.JobId == jobId && t.StatusTypeId == TemplateStatusTypeEnum.Used) .ToListAsync(); - if (!existingTemplates.Any()) return; - - var job = await GetJobWithGroupAndAutoControlAsync(jobId); - if (job == null) return; - - var currentUnitIds = await GetExpectedUnitIdsAsync(jobId) ?? new HashSet(); - logger.LogDebug("JobId {JobId}: {Count} UnitId по текущему фильтру.", jobId, currentUnitIds.Count); - foreach (var template in existingTemplates) { - logger.LogDebug("Обработка шаблона {TemplateId} (UnitId {UnitId}).", template.Id, template.UnitId); - - bool unitStillInFilter = currentUnitIds.Contains(template.UnitId); - var expectedName = await GetNormalizedTemplateNameAsync(job, template.UnitId); - - TemplateStatusTypeEnum targetStatus; - bool targetIsActiveTemplate; - bool targetIsActiveSchedule; - - if (unitStillInFilter) + if (unitIds.Contains(template.UnitId)) { - targetStatus = TemplateStatusTypeEnum.Used; - targetIsActiveTemplate = template.IsActiveTemplate; - targetIsActiveSchedule = template.IsActiveSchedule; - } - else - { - targetStatus = TemplateStatusTypeEnum.Unused; - targetIsActiveTemplate = DefaultUnusedTemplateState; - targetIsActiveSchedule = DefaultUnusedScheduleState; - - // ✅ Вычисляем имя шаблона с новым unusedJob - if (unusedJob != null) + var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(job, template.UnitId); + if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase)) { - expectedName = await GetNormalizedTemplateNameAsync(unusedJob, template.UnitId); + 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); } - else - { - logger.LogError("Job для деактивированных шаблонов не найден."); - expectedName = GetTemplateNameForUnused(template.Name); - } - - logger.LogInformation("Шаблон {TemplateId} (UnitId {UnitId}) → деактивация.", template.Id, template.UnitId); } - - bool needsUpdate = template.StatusTypeId != TemplateStatusTypeEnum.Updating && - ( - // 1. Статус изменился (например, Used → Unused или Unused → Used) - template.StatusTypeId != targetStatus || - - // 2. Для Used — имя должно соответствовать шаблону - (targetStatus == TemplateStatusTypeEnum.Used && template.Name != expectedName) || - - // 3. Флаги изменились (редко, но возможно через AutoControl изменение) - template.IsActiveTemplate != targetIsActiveTemplate || - template.IsActiveSchedule != targetIsActiveSchedule - ); - - if (!needsUpdate) continue; - - template.StatusTypeId = TemplateStatusTypeEnum.Updating; - template.DateModified = DateTimeOffset.UtcNow; - - if (!await templateService.CommitAsync(initiator)) - { - logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id); - continue; - } - - var nextRun = await GetNextRunAsync(job, template.NextRun); - - var updateRequest = new TemplateUpdaterMq - { - TemplateId = template.Id, - JobId = unitStillInFilter ? jobId : unusedJob?.Id ?? jobId, // ✅ Используем unusedJob.Id, если деактивация - UnitId = template.UnitId, - Name = expectedName, - IsActiveTemplate = targetIsActiveTemplate, - IsActiveSchedule = targetIsActiveSchedule, - LastRun = template.LastRun, - NextRun = nextRun, - Index = template.Index, - StatusTypeId = targetStatus, - Initiator = initiator, - UnitsInTemplate = template.UnitsInTemplate.Select(t => t.UnitId).ToList() // Для простого шаблона это список из одного элемента или пустой - }; - - await SendTemplateUpdateMessage(updateRequest); } - logger.LogInformation("Обновление шаблонов завершено для JobId {JobId}.", jobId); + logger.LogInformation("Обновление шаблонов завершено для Job {JobId}.", jobId); } - // --- Вспомогательные методы --- - private async Task DeactivateTemplateAsync( - Template template, - Guid jobId, - Job unusedJob, - HistoryInitiator initiator) + private async Task CreateSimpleTemplateAsync(Guid jobId, Guid unitId, HistoryInitiator initiator) { - if (template.StatusTypeId == TemplateStatusTypeEnum.Updating) - return true; // уже в обработке + logger.LogInformation("Создание нового простого шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId); - if (template.StatusTypeId == TemplateStatusTypeEnum.Unused) - { - logger.LogDebug("Шаблон {TemplateId} уже неактивен (Unused), пропускаем деактивацию.", template.Id); - return true; - } - - logger.LogInformation("Шаблон {TemplateId} (UnitId {UnitId}) → деактивация.", - template.Id, template.UnitId); - - template.StatusTypeId = TemplateStatusTypeEnum.Updating; - template.DateModified = DateTimeOffset.UtcNow; - - if (!await templateService.CommitAsync(initiator)) - { - logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id); - return false; - } - - if (unusedJob == null) - { - logger.LogError("Job для деактивированных шаблонов не найден."); - return false; - } - - // ✅ Вычисляем имя шаблона с новым Job - var expectedName = await GetNormalizedTemplateNameAsync(unusedJob, template.UnitId); - - var updateRequest = new TemplateUpdaterMq - { - TemplateId = template.Id, - JobId = unusedJob.Id, // ✅ Используем unusedJob.Id - UnitId = template.UnitId, - Name = GetTemplateNameForUnused(expectedName), // ✅ Используем новое имя - IsActiveTemplate = DefaultUnusedTemplateState, - IsActiveSchedule = DefaultUnusedScheduleState, - LastRun = template.LastRun, - NextRun = template.NextRun, - Index = template.Index, - StatusTypeId = TemplateStatusTypeEnum.Unused, - Initiator = initiator, - UnitsInTemplate = new List() // Для простого шаблона - }; - - await SendTemplateUpdateMessage(updateRequest); - return true; - } - - private async Task ReactivateOrRenameTemplateAsync( - Template template, - Job job, - string expectedName, - HistoryInitiator initiator) - { - if (template.StatusTypeId == TemplateStatusTypeEnum.Updating) - return true; - - bool needsUpdate = template.Name != expectedName - || template.StatusTypeId != TemplateStatusTypeEnum.Used; - - if (!needsUpdate) return true; - - logger.LogInformation("Шаблон {TemplateId}: требуется обновление имени или реактивация.", template.Id); - - template.StatusTypeId = TemplateStatusTypeEnum.Updating; - template.DateModified = DateTimeOffset.UtcNow; - - if (!await templateService.CommitAsync(initiator)) - { - logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id); - return false; - } - - var nextRun = await GetNextRunAsync(job, template.NextRun); - - var updateRequest = new TemplateUpdaterMq - { - TemplateId = template.Id, - JobId = job.Id, - 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 = template.UnitsInTemplate.Select(t => t.UnitId).ToList() // Для простого шаблона это список из одного элемента - }; - - await SendTemplateUpdateMessage(updateRequest); - return true; - } - - private async Task SendTemplateGeneratorMessageAsync( - Guid jobId, - Guid unitId, - HistoryInitiator initiator) - { - logger.LogInformation("Создание нового шаблона для UnitId {UnitId}.", unitId); var mqRequest = new TemplateGeneratorMq { JobId = jobId, UnitId = unitId, - HistoryInitiator = initiator, - UnitsInTemplate = new List() // Для простого шаблона + UnitsInTemplate = new List(), // для простого шаблона + HistoryInitiator = initiator }; var msg = JsonSerializer.Serialize(mqRequest); var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg }); if (!result.IsSuccess) - logger.LogError("Ошибка отправки команды создания шаблона для UnitId {UnitId}.", unitId); - - return result.IsSuccess; - } - - private async Task SendTemplateUpdateMessage(TemplateUpdaterMq updateRequest) - { - logger.LogDebug("Отправка сообщения в очередь '{Queue}' для шаблона {TemplateId}", - mqSettings.TemplateUpdater.QueueName, updateRequest.TemplateId); - - var msg = JsonSerializer.Serialize(updateRequest); - var result = await mqService.SendAsync(mqSettings.TemplateUpdater, new[] { msg }); - - if (result.IsSuccess) - { - logger.LogInformation("Отправлен запрос на обновление шаблона {TemplateId}", updateRequest.TemplateId); - } - else - { - logger.LogError("Ошибка при отправке запроса на обновление шаблона {TemplateId} в очередь '{Queue}'.", - updateRequest.TemplateId, mqSettings.TemplateUpdater.QueueName); - } - } - - private string GetTemplateNameForUnused(string templateName) - { - return templateName + "_" + DateTimeOffset.UtcNow.ToUnixTimeSeconds(); - } - - private async Task GetJobWithGroupAndAutoControlAsync(Guid jobId) - { - return await jobService.Get() - .AsNoTracking() - .Include(j => j.Tnk) - .Include(j => j.Group) - .ThenInclude(j => j.GroupType) - .Include(j => j.AutoControl) - .FirstOrDefaultAsync(j => j.Id == jobId); - } - - private async Task> GetExpectedUnitIdsAsync(Guid jobId) - { - var units = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId); - return units?.ToHashSet() ?? new HashSet(); - } - - private async Task GetNormalizedTemplateNameAsync(Job job, Guid unitId) - { - // Создаём TemplateForShortcodes "на лету", без запроса к БД - var templateForShortcodes = new TemplateForShortcodes - { - Id = Guid.Empty, // шаблон ещё не создан - Index = null, - JobId = job.Id, - UnitId = unitId, - Job = new JobForShortcodes - { - Group = job.Group == null ? null : new JobGroupForShortcodes - { - GroupingUnitFieldId = job.Group.GroupingUnitFieldId, - GroupType = job.Group.GroupType == null ? null : new JobGroupTypeForShortcodes - { - Code = job.Group.GroupType.Code - }, - GroupName = job.Group.GroupName - }, - Tnk = job.Tnk == null ? null : new TnkForShortcodes - { - Name = job.Tnk.Name, - ShortName = job.Tnk.ShortName ?? "" - }, - WorkName = job.WorkName, - Name = job.Name - }, - UnitsInTemplate = new List() // для простого шаблона - }; - - var rawName = await shortcodesService.ApplyShortcodesAsync(job.TemplateNameMask, templateForShortcodes); - return rawName.ToUpper(); + logger.LogError("Ошибка отправки команды создания простого шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId); } private async Task GetNextRunAsync(Job job, DateTimeOffset? currentNextRun = null) { - var now = DateTimeOffset.UtcNow; - if (currentNextRun.HasValue && currentNextRun.Value > now) - { - return currentNextRun.Value; - } - - var referenceDate = job.Group?.ReferenceDate ?? now; + var referenceDate = job.Group?.ReferenceDate ?? DateTimeOffset.UtcNow; return await esppScheduleTransformService.GetNextDateAsync(job.GroupId, referenceDate); } } \ No newline at end of file diff --git a/PARR.TemplateMatcher/Services/Implemetaions/TemplateDeactivator.cs b/PARR.TemplateMatcher/Services/Implemetaions/TemplateDeactivator.cs new file mode 100644 index 00000000..3ae6e151 --- /dev/null +++ b/PARR.TemplateMatcher/Services/Implemetaions/TemplateDeactivator.cs @@ -0,0 +1,101 @@ +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.Models; +using PARR.DAL.Models.Job; +using PARR.DAL.Services.Interfaces; +using PARR.DAL.Services.Interfaces.Job; +using PARR.TemplateMatcher.Services.Interfaces; +using PARR.TemplateMatcher.Settings; +using System.Text.Json; + +namespace PARR.TemplateMatcher.Services.Implementations; + +internal class TemplateDeactivator : ITemplateDeactivator +{ + private const bool DefaultUnusedTemplateState = false; + private const bool DefaultUnusedScheduleState = false; + + private readonly ILogger logger; + private readonly ITemplateService templateService; + private readonly IJobService jobService; + private readonly ITemplateNameNormalizer namenormalizer; + private readonly ITemplateUpdaterMqSender sender; + private readonly Guid unusedJobId = Guid.Parse("8f85a91c-a223-4686-bb69-1f0ee73624f2"); + + public TemplateDeactivator( + ILogger logger, + ITemplateService templateService, + IJobService jobService, + ITemplateNameNormalizer namenormalizer, + ITemplateUpdaterMqSender sender) + { + this.logger = logger; + this.templateService = templateService; + this.jobService = jobService; + this.namenormalizer = namenormalizer; + this.sender = sender; + } + + public async Task DeactivateTemplateAsync(Template template, HistoryInitiator initiator) + { + if (template.StatusTypeId == TemplateStatusTypeEnum.Updating) + return true; // уже в обработке + + if (template.StatusTypeId == TemplateStatusTypeEnum.Unused) + { + logger.LogDebug("Шаблон {TemplateId} уже неактивен (Unused), пропускаем деактивацию.", template.Id); + return true; + } + + logger.LogInformation("Шаблон {TemplateId} (UnitId {UnitId}) → деактивация.", + template.Id, template.UnitId); + + template.StatusTypeId = TemplateStatusTypeEnum.Updating; + template.DateModified = DateTimeOffset.UtcNow; + + if (!await templateService.CommitAsync(initiator)) + { + logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id); + return false; + } + + var unusedJob = await jobService.Get() + .AsNoTracking() + .Include(j => j.Tnk) + .Include(j => j.Group) + .ThenInclude(g => g!.GroupType) + .FirstOrDefaultAsync(j => j.Id == unusedJobId); + + if (unusedJob == null) + { + logger.LogError("Job для деактивированных шаблонов не найден."); + return false; + } + + // Вычисляем имя шаблона с новым Job + var expectedName = await namenormalizer.GetNormalizedTemplateNameAsync(unusedJob, template.UnitId); + + var updateRequest = new TemplateUpdaterMq + { + TemplateId = template.Id, + JobId = unusedJob.Id, + UnitId = template.UnitId, + Name = expectedName, + IsActiveTemplate = DefaultUnusedTemplateState, + IsActiveSchedule = DefaultUnusedScheduleState, + LastRun = template.LastRun, + NextRun = template.NextRun, + Index = template.Index, + StatusTypeId = TemplateStatusTypeEnum.Unused, + Initiator = initiator, + UnitsInTemplate = new List() + }; + + await sender.SendTemplateUpdateMessageAsync(updateRequest); + return true; + } +} \ No newline at end of file diff --git a/PARR.TemplateMatcher/Services/Implemetaions/TemplateNameNormalizer.cs b/PARR.TemplateMatcher/Services/Implemetaions/TemplateNameNormalizer.cs new file mode 100644 index 00000000..707454c7 --- /dev/null +++ b/PARR.TemplateMatcher/Services/Implemetaions/TemplateNameNormalizer.cs @@ -0,0 +1,51 @@ +using PARR.DAL.DomainServices.Shortcodes; +using PARR.DAL.DomainServices.Shortcodes.Models; +using PARR.DAL.Models.Job; +using PARR.TemplateMatcher.Services.Interfaces; + +namespace PARR.TemplateMatcher.Services.Implementations; + +internal class TemplateNameNormalizer : ITemplateNameNormalizer +{ + private readonly IShortcodesService shortcodesService; + + public TemplateNameNormalizer(IShortcodesService shortcodesService) + { + this.shortcodesService = shortcodesService; + } + + public async Task GetNormalizedTemplateNameAsync(Job job, Guid unitId, int? index = null, List? templateUnitIds = null) + { + var templateForShortcodes = new TemplateForShortcodes + { + Id = Guid.Empty, + Index = (index != null) ? index + 1 : index, + JobId = job.Id, + UnitId = unitId, + Job = new JobForShortcodes + { + Group = job.Group != null ? new JobGroupForShortcodes + { + Id = job.Group.Id, + GroupingUnitFieldId = job.Group.GroupingUnitFieldId, + GroupType = job.Group.GroupType != null ? new JobGroupTypeForShortcodes + { + Code = job.Group.GroupType.Code + } : null, + GroupName = job.Group.GroupName + } : null, + Tnk = job.Tnk != null ? new TnkForShortcodes + { + Name = job.Tnk.Name, + ShortName = job.Tnk.ShortName ?? "" + } : null, + WorkName = job.WorkName, + Name = job.Name + }, + UnitsInTemplate = templateUnitIds?.Select(id => new UnitInTemplateForShortcodes { UnitId = id }).ToList() ?? new List() + }; + + var rawName = await shortcodesService.ApplyShortcodesAsync(job.TemplateNameMask, templateForShortcodes); + return rawName.ToUpper(); + } +} \ No newline at end of file diff --git a/PARR.TemplateMatcher/Services/Implemetaions/TemplateUpdaterMqSender.cs b/PARR.TemplateMatcher/Services/Implemetaions/TemplateUpdaterMqSender.cs new file mode 100644 index 00000000..d9cc5d5c --- /dev/null +++ b/PARR.TemplateMatcher/Services/Implemetaions/TemplateUpdaterMqSender.cs @@ -0,0 +1,44 @@ +using Microsoft.Extensions.Logging; +using PARR.BLL.Domain.Mq; +using PARR.BLL.Services.Interfaces; +using PARR.TemplateMatcher.Services.Interfaces; +using PARR.TemplateMatcher.Settings; +using System.Text.Json; + +namespace PARR.TemplateMatcher.Services.Implementations; + +internal class TemplateUpdaterMqSender : ITemplateUpdaterMqSender +{ + private readonly ILogger logger; + private readonly IMqService mqService; + private readonly MqSettings mqSettings; + + public TemplateUpdaterMqSender( + ILogger logger, + IMqService mqService, + MqSettings mqSettings) + { + this.logger = logger; + this.mqService = mqService; + this.mqSettings = mqSettings; + } + + public async Task SendTemplateUpdateMessageAsync(TemplateUpdaterMq updateRequest) + { + logger.LogDebug("Отправка сообщения в очередь '{Queue}' для шаблона {TemplateId}", + mqSettings.TemplateUpdater.QueueName, updateRequest.TemplateId); + + var msg = JsonSerializer.Serialize(updateRequest); + var result = await mqService.SendAsync(mqSettings.TemplateUpdater, new[] { msg }); + + if (result.IsSuccess) + { + logger.LogInformation("Отправлен запрос на обновление шаблона {TemplateId}", updateRequest.TemplateId); + } + else + { + logger.LogError("Ошибка при отправке запроса на обновление шаблона {TemplateId} в очередь '{Queue}'.", + updateRequest.TemplateId, mqSettings.TemplateUpdater.QueueName); + } + } +} \ No newline at end of file diff --git a/PARR.TemplateMatcher/Services/Interfaces/ITemplateDeactivator.cs b/PARR.TemplateMatcher/Services/Interfaces/ITemplateDeactivator.cs new file mode 100644 index 00000000..3fc6474d --- /dev/null +++ b/PARR.TemplateMatcher/Services/Interfaces/ITemplateDeactivator.cs @@ -0,0 +1,10 @@ +using PARR.Common.Domain; +using PARR.DAL.Models; + +namespace PARR.TemplateMatcher.Services.Interfaces +{ + public interface ITemplateDeactivator + { + Task DeactivateTemplateAsync(Template template, HistoryInitiator initiator); + } +} diff --git a/PARR.TemplateMatcher/Services/Interfaces/ITemplateNameNormalizer.cs b/PARR.TemplateMatcher/Services/Interfaces/ITemplateNameNormalizer.cs new file mode 100644 index 00000000..dc4dedf9 --- /dev/null +++ b/PARR.TemplateMatcher/Services/Interfaces/ITemplateNameNormalizer.cs @@ -0,0 +1,9 @@ +using PARR.DAL.Models.Job; + +namespace PARR.TemplateMatcher.Services.Interfaces +{ + public interface ITemplateNameNormalizer + { + Task GetNormalizedTemplateNameAsync(Job job, Guid unitId, int? index = null, List? templateUnitIds = null); + } +} diff --git a/PARR.TemplateMatcher/Services/Interfaces/ITemplateUpdaterMqSender.cs b/PARR.TemplateMatcher/Services/Interfaces/ITemplateUpdaterMqSender.cs new file mode 100644 index 00000000..98546970 --- /dev/null +++ b/PARR.TemplateMatcher/Services/Interfaces/ITemplateUpdaterMqSender.cs @@ -0,0 +1,9 @@ +using PARR.BLL.Domain.Mq; + +namespace PARR.TemplateMatcher.Services.Interfaces +{ + public interface ITemplateUpdaterMqSender + { + Task SendTemplateUpdateMessageAsync(TemplateUpdaterMq updateRequest); + } +} diff --git a/PARR.TemplateMatcher/TemplateMatcher.cs b/PARR.TemplateMatcher/TemplateMatcher.cs index a79609a5..0c5de48a 100644 --- a/PARR.TemplateMatcher/TemplateMatcher.cs +++ b/PARR.TemplateMatcher/TemplateMatcher.cs @@ -4,6 +4,7 @@ using PARR.Common.Domain; using PARR.DAL.Contracts; using PARR.DAL.Models.Job; using PARR.DAL.Services.Interfaces.Job; +using PARR.TemplateMatcher.Services.Implementations; using PARR.TemplateMatcher.Services.Implemetaions; using PARR.TemplateMatcher.Services.Interfaces; @@ -42,7 +43,7 @@ namespace PARR.TemplateMatcher // Проверяем, является ли Job "групповым" bool isGroupJob = job.Group != null && job.Group.GroupType?.Code == JobGroupTypesEnum.Group; - if (isGroupJob && job.Group.GroupingUnitFieldId.HasValue) + if (isGroupJob && job.Group!.GroupingUnitFieldId.HasValue) { logger.LogInformation("Job {JobId} является групповым. Передаём в GroupedSynchronizer.", jobId); // Находим нужный синхронизатор @@ -103,7 +104,7 @@ namespace PARR.TemplateMatcher // Проверяем, является ли Job "групповым" bool isGroupJob = job.Group != null && job.Group.GroupType?.Code == JobGroupTypesEnum.Group; - if (isGroupJob && job.Group.GroupingUnitFieldId.HasValue) + if (isGroupJob && job.Group!.GroupingUnitFieldId.HasValue) { logger.LogInformation("Job {JobId} является групповым. Передаём в GroupedSynchronizer для Update.", jobId); // Находим нужный синхронизатор @@ -144,7 +145,7 @@ namespace PARR.TemplateMatcher return await jobService.Get() .AsNoTracking() // Добавлено .Include(j => j.Group) - .ThenInclude(j => j.GroupType) + .ThenInclude(j => j!.GroupType) .Include(j => j.AutoControl) .FirstOrDefaultAsync(j => j.Id == jobId); } diff --git a/PARR.TemplateMatcher/TemplateMatcherInstaller.cs b/PARR.TemplateMatcher/TemplateMatcherInstaller.cs index 0bf44b09..9a8aa55f 100644 --- a/PARR.TemplateMatcher/TemplateMatcherInstaller.cs +++ b/PARR.TemplateMatcher/TemplateMatcherInstaller.cs @@ -25,6 +25,9 @@ namespace PARR.TemplateMatcher services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); }