From 90e63521175a67b774d7f1729723b760c3a46f53 Mon Sep 17 00:00:00 2001 From: Mikhail Kuznetsov Date: Thu, 14 May 2026 16:47:27 +1000 Subject: [PATCH] =?UTF-8?q?feat(templateMatcher):=20=D0=A0=D0=B5=D1=84?= =?UTF-8?q?=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3,=20=D0=BE?= =?UTF-8?q?=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D0=BE=D0=B9=20=D0=BC=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B4=20=D1=81=D0=B8=D0=BD=D1=85=D1=80=D0=BE=D0=BD=D0=B8?= =?UTF-8?q?=D0=B7=D0=B0=D1=86=D0=B8=D0=B8=20GroupedTemplateSynchronizer=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B7=D0=B1=D0=B8=D1=82=20=D0=BD=D0=B0=20=D0=BE?= =?UTF-8?q?=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5=20=D0=BA=D0=BB?= =?UTF-8?q?=D0=B0=D1=81=D1=81=D1=8B;=20=D0=9D=D0=B5=D0=B8=D1=81=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=8C=D0=B7=D1=83=D0=B5=D0=BC=D1=8B=D0=B5=20=D1=88?= =?UTF-8?q?=D0=B0=D0=B1=D0=BB=D0=BE=D0=BD=D1=8B=20=D1=82=D0=B5=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D1=8C=20=D0=BF=D1=80=D0=B8=D0=B2=D1=8F=D0=B7=D1=8B=D0=B2?= =?UTF-8?q?=D0=B0=D1=8E=D1=82=D1=81=D1=8F=20=D0=BA=20=D0=AD=D0=9A=20=D0=9A?= =?UTF-8?q?=D0=9E=D0=A1=D0=9C=D0=9F=D0=9B=D0=95=D0=9A=D0=A1=D0=AB-[=D0=97?= =?UTF-8?q?=D0=9E].?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../UnitFilterService/UnitFilterService.cs | 4 +- .../Rabbit/Messages/GeneratorTemplateMq.cs | 50 -- .../TemplateGeneratorMessage.cs} | 18 +- .../TemplateUpdaterMessage.cs} | 32 +- .../TemplateMatching/UnitInTemplateMessage.cs | 15 + .../Messages/TemplateTaskGeneratorMq.cs | 13 - .../TemplateGenerator.cs | 4 +- .../Models/GroupedTemplateGroup.cs | 10 + .../Models/GroupedTemplateSubGroup.cs | 11 + .../Implementations/GroupedTemplateBuilder.cs | 158 +++++ .../GroupedTemplateProcessor.cs | 316 +++++++++ .../GroupedTemplateSynchronizer.cs | 243 +++++++ .../GroupedTemplateUnitFilter.cs | 100 +++ .../JobGroupValidatorService.cs | 0 .../JobValidatorService.cs | 0 .../SimpleTemplateSynchronizer.cs | 256 +++++--- .../TemplateDeactivator.cs | 8 +- .../TemplateNameNormalizer.cs | 1 + .../TemplateReuser.cs | 13 +- .../TemplateUpdaterMqSender.cs | 22 +- .../UnitInTemplateConflictMapper.cs | 2 + .../GroupedTemplateSynchronizer.cs | 616 ------------------ .../Interfaces/IGroupedTemplateBuilder.cs | 18 + .../Interfaces/IGroupedTemplateProcessor.cs | 22 + .../Interfaces/IGroupedTemplateUnitFilter.cs | 18 + .../Interfaces/ITemplateUpdaterMqSender.cs | 4 +- .../TemplateMatcherInstaller.cs | 3 + .../Services/ITemplateUpdaterService.cs | 4 +- .../Services/TemplateUpdaterService.cs | 49 +- PARR.TemplateUpdater/TemplateUpdater.cs | 4 +- 30 files changed, 1150 insertions(+), 864 deletions(-) delete mode 100644 PARR.Domain/Common/Rabbit/Messages/GeneratorTemplateMq.cs rename PARR.Domain/Common/Rabbit/Messages/{TemplateGeneratorMq.cs => TemplateMatching/TemplateGeneratorMessage.cs} (68%) rename PARR.Domain/Common/Rabbit/Messages/{TemplateUpdaterMq.cs => TemplateMatching/TemplateUpdaterMessage.cs} (59%) create mode 100644 PARR.Domain/Common/Rabbit/Messages/TemplateMatching/UnitInTemplateMessage.cs delete mode 100644 PARR.Domain/Common/Rabbit/Messages/TemplateTaskGeneratorMq.cs create mode 100644 PARR.TemplateMatcher/Models/GroupedTemplateGroup.cs create mode 100644 PARR.TemplateMatcher/Models/GroupedTemplateSubGroup.cs create mode 100644 PARR.TemplateMatcher/Services/Implementations/GroupedTemplateBuilder.cs create mode 100644 PARR.TemplateMatcher/Services/Implementations/GroupedTemplateProcessor.cs create mode 100644 PARR.TemplateMatcher/Services/Implementations/GroupedTemplateSynchronizer.cs create mode 100644 PARR.TemplateMatcher/Services/Implementations/GroupedTemplateUnitFilter.cs rename PARR.TemplateMatcher/Services/{Implemetaions => Implementations}/JobGroupValidatorService.cs (100%) rename PARR.TemplateMatcher/Services/{Implemetaions => Implementations}/JobValidatorService.cs (100%) rename PARR.TemplateMatcher/Services/{Implemetaions => Implementations}/SimpleTemplateSynchronizer.cs (75%) rename PARR.TemplateMatcher/Services/{Implemetaions => Implementations}/TemplateDeactivator.cs (95%) rename PARR.TemplateMatcher/Services/{Implemetaions => Implementations}/TemplateNameNormalizer.cs (99%) rename PARR.TemplateMatcher/Services/{Implemetaions => Implementations}/TemplateReuser.cs (91%) rename PARR.TemplateMatcher/Services/{Implemetaions => Implementations}/TemplateUpdaterMqSender.cs (62%) rename PARR.TemplateMatcher/Services/{Implemetaions => Implementations}/UnitInTemplateConflictMapper.cs (99%) delete mode 100644 PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs create mode 100644 PARR.TemplateMatcher/Services/Interfaces/IGroupedTemplateBuilder.cs create mode 100644 PARR.TemplateMatcher/Services/Interfaces/IGroupedTemplateProcessor.cs create mode 100644 PARR.TemplateMatcher/Services/Interfaces/IGroupedTemplateUnitFilter.cs diff --git a/PARR.Core/Services/UnitFilterService/UnitFilterService.cs b/PARR.Core/Services/UnitFilterService/UnitFilterService.cs index 9896fb07..e12d2cca 100644 --- a/PARR.Core/Services/UnitFilterService/UnitFilterService.cs +++ b/PARR.Core/Services/UnitFilterService/UnitFilterService.cs @@ -981,7 +981,7 @@ internal class UnitFilterService : IUnitFilterService // var cacheKey = cacheService.GetKey(new[] { "uf_ids", filter.UnitFilter }, isUseHash: true); var cacheKey = cacheService.GetKey(new[] { "unit filter", "unit name mask" }, new[] { filter.UnitFilter }); - var cachedData = await cacheService.GetCachedDataAsync(cacheKey); + var cachedData = await cacheService.GetCachedDataAsync(cacheKey, true); if (cachedData != null) { return cachedData.Data.UnitIds; @@ -998,7 +998,7 @@ internal class UnitFilterService : IUnitFilterService Source = GetType().Name }; - await cacheService.SetCachedDataAsync(cacheKey, toCache, TimeSpan.FromHours(1)); + await cacheService.SetCachedDataAsync(cacheKey, toCache, TimeSpan.FromHours(1), true); return initialUnitIds; } diff --git a/PARR.Domain/Common/Rabbit/Messages/GeneratorTemplateMq.cs b/PARR.Domain/Common/Rabbit/Messages/GeneratorTemplateMq.cs deleted file mode 100644 index d76d955b..00000000 --- a/PARR.Domain/Common/Rabbit/Messages/GeneratorTemplateMq.cs +++ /dev/null @@ -1,50 +0,0 @@ -using PARR.Domain.Entities.Base.History; - -namespace PARR.Domain.Common.Rabbit.Messages -{ - /// - /// Модель в MQ, запрос на генерацию шаблона - /// - public class GeneratorTemplateMq - { - /// - /// Id работы - /// - public Guid WorkId { get; set; } - - /// - /// Id приложения - /// - public Guid ApplicationId { get; set; } - - /// - /// Действие: create, deactivate - /// - public required string Action { get; set; } - - /// - /// ЭК, принимает регулярное выражение. Например: *, ВРТ-*-ДВС - /// - public required string Ek { get; set; } - - /// - /// Статус ЭК. Допустимые значения: 0 - все статусы, 1,2,3,4,5,6,7,9 - /// - public int StatusEk { get; set; } - - /// - /// Активировать шаблон - /// - public bool? IsActiveTemplate { get; set; } - - /// - /// Активировать расписание - /// - public bool? IsActiveSchedule { get; set; } - - /// - /// Инициатор запроса к генератору - /// - public HistoryInitiator? HistoryInitiator { get; set; } - } -} diff --git a/PARR.Domain/Common/Rabbit/Messages/TemplateGeneratorMq.cs b/PARR.Domain/Common/Rabbit/Messages/TemplateMatching/TemplateGeneratorMessage.cs similarity index 68% rename from PARR.Domain/Common/Rabbit/Messages/TemplateGeneratorMq.cs rename to PARR.Domain/Common/Rabbit/Messages/TemplateMatching/TemplateGeneratorMessage.cs index 2abe3bdb..2a2b1205 100644 --- a/PARR.Domain/Common/Rabbit/Messages/TemplateGeneratorMq.cs +++ b/PARR.Domain/Common/Rabbit/Messages/TemplateMatching/TemplateGeneratorMessage.cs @@ -1,46 +1,46 @@ using PARR.Domain.Entities.Base.History; -namespace PARR.Domain.Common.Rabbit.Messages +namespace PARR.Domain.Common.Rabbit.Messages.TemplateMatching { /// /// Модель в MQ, простого создания Template /// - public class TemplateGeneratorMq + public record TemplateGeneratorMessage { /// /// Id регламентной работы /// - public Guid JobId { get; set; } + public Guid JobId { get; init; } /// /// Id Юнита(единицы обслуживания)/ЭК /// - public Guid UnitId { get; set; } + public Guid UnitId { get; init; } /// /// Актировать шаблон при инициализации /// - public bool? IsActiveTemplate { get; set; } + public bool? IsActiveTemplate { get; init; } /// /// Актировать расписание при инициализации /// - public bool? IsActiveSchedule { get; set; } + public bool? IsActiveSchedule { get; init; } /// /// Инициатор запроса к генератору /// - public HistoryInitiator? HistoryInitiator { get; set; } + public HistoryInitiator? HistoryInitiator { get; init; } /// /// Связанные ЭК для сгруппированного типа JobGroup /// - public required List UnitsInTemplate { get; set; } + public required List UnitsInTemplate { get; init; } /// /// Индекс, используется в групповых шаблонах /// - public int? Index { get; set; } + public int? Index { get; init; } } } diff --git a/PARR.Domain/Common/Rabbit/Messages/TemplateUpdaterMq.cs b/PARR.Domain/Common/Rabbit/Messages/TemplateMatching/TemplateUpdaterMessage.cs similarity index 59% rename from PARR.Domain/Common/Rabbit/Messages/TemplateUpdaterMq.cs rename to PARR.Domain/Common/Rabbit/Messages/TemplateMatching/TemplateUpdaterMessage.cs index 9cab0794..c2ebf9b2 100644 --- a/PARR.Domain/Common/Rabbit/Messages/TemplateUpdaterMq.cs +++ b/PARR.Domain/Common/Rabbit/Messages/TemplateMatching/TemplateUpdaterMessage.cs @@ -1,58 +1,60 @@ using PARR.Domain.Entities.Base.History; using PARR.Domain.Enums; -namespace PARR.Domain.Common.Rabbit.Messages +namespace PARR.Domain.Common.Rabbit.Messages.TemplateMatching { - public class TemplateUpdaterMq + public record TemplateUpdaterMessage { - public Guid TemplateId { get; set; } + public Guid TemplateId { get; init; } /// /// Id регламентной работы /// - public Guid JobId { get; set; } + public Guid JobId { get; init; } - public required string Name { get; set; } + public required string Name { get; init; } /// /// Актировать шаблон при инициализации /// - public bool IsActiveTemplate { get; set; } + public bool IsActiveTemplate { get; init; } /// /// Актировать расписание при инициализации /// - public bool IsActiveSchedule { get; set; } + public bool IsActiveSchedule { get; init; } - //public DateTimeOffset? LastRun { get; set; } + //public DateTimeOffinit? LastRun { get; init; } - //public DateTimeOffset NextRun { get; set; } + //public DateTimeOffinit NextRun { get; init; } /// /// Это новый шаблон? (true - перемещаем существующий в новый Job) (false - шаблон остается в своем Job) /// - public bool IsNew { get; set; } + public bool IsNew { get; init; } /// /// Id Юнита(единицы обслуживания)/ЭК /// - public Guid UnitId { get; set; } + public Guid UnitId { get; init; } /// /// Индекс, используется в групповых шаблонах /// - public int? Index { get; set; } + public int? Index { get; init; } - public TemplateStatusTypeEnum StatusTypeId { get; set; } + public TemplateStatusTypeEnum StatusTypeId { get; init; } /// /// Инициатор запроса к генератору /// - public required HistoryInitiator Initiator { get; set; } + public required HistoryInitiator Initiator { get; init; } /// /// Связанные ЭК для сгруппированного типа JobGroup /// - public required List UnitsInTemplate { get; set; } + public required List UnitsInTemplate { get; init; } } + + } diff --git a/PARR.Domain/Common/Rabbit/Messages/TemplateMatching/UnitInTemplateMessage.cs b/PARR.Domain/Common/Rabbit/Messages/TemplateMatching/UnitInTemplateMessage.cs new file mode 100644 index 00000000..5eac81a8 --- /dev/null +++ b/PARR.Domain/Common/Rabbit/Messages/TemplateMatching/UnitInTemplateMessage.cs @@ -0,0 +1,15 @@ +namespace PARR.Domain.Common.Rabbit.Messages.TemplateMatching +{ + public record UnitInTemplateMessage + { + /// + /// Id ЭК + /// + public Guid UnitId { get; init; } + + /// + /// Id значения атрибута + /// + public Guid UnitFieldValueId { get; init; } + } +} diff --git a/PARR.Domain/Common/Rabbit/Messages/TemplateTaskGeneratorMq.cs b/PARR.Domain/Common/Rabbit/Messages/TemplateTaskGeneratorMq.cs deleted file mode 100644 index 40a739b4..00000000 --- a/PARR.Domain/Common/Rabbit/Messages/TemplateTaskGeneratorMq.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace PARR.Domain.Common.Rabbit.Messages -{ - /// - /// Модель в MQ, генератора заданий на генерацию шаблонов - /// - public class TemplateTaskGeneratorMq - { - /// - /// Id регламентной работы - /// - public Guid JobId { get; set; } - } -} diff --git a/PARR.TemplateGeneratorWorker/TemplateGenerator.cs b/PARR.TemplateGeneratorWorker/TemplateGenerator.cs index fd824805..9f71acb6 100644 --- a/PARR.TemplateGeneratorWorker/TemplateGenerator.cs +++ b/PARR.TemplateGeneratorWorker/TemplateGenerator.cs @@ -4,7 +4,7 @@ using PARR.Core.Repositories.Interfaces; using PARR.Core.Repositories.Interfaces.Job; using PARR.Core.Services.NextRunServices; using PARR.Core.Services.Shortcodes; -using PARR.Domain.Common.Rabbit.Messages; +using PARR.Domain.Common.Rabbit.Messages.TemplateMatching; using PARR.Domain.Entities; using PARR.Domain.Entities.Base.History; using PARR.Domain.Entities.Job; @@ -54,7 +54,7 @@ namespace PARR.TemplateGeneratorWorker { logger.LogInformation($"Получили запрос: {msg}"); - var query = transformService.GetModelFromJson(msg); + var query = transformService.GetModelFromJson(msg); if (query == null) { diff --git a/PARR.TemplateMatcher/Models/GroupedTemplateGroup.cs b/PARR.TemplateMatcher/Models/GroupedTemplateGroup.cs new file mode 100644 index 00000000..1111c4ca --- /dev/null +++ b/PARR.TemplateMatcher/Models/GroupedTemplateGroup.cs @@ -0,0 +1,10 @@ +namespace PARR.TemplateMatcher.Models +{ + /// + /// Группа шаблонов для одного PotentialUnitId (связанного юнита). + /// + public record GroupedTemplateGroup( + Guid PotentialUnitId, + List SubGroups + ); +} diff --git a/PARR.TemplateMatcher/Models/GroupedTemplateSubGroup.cs b/PARR.TemplateMatcher/Models/GroupedTemplateSubGroup.cs new file mode 100644 index 00000000..91d6c4ab --- /dev/null +++ b/PARR.TemplateMatcher/Models/GroupedTemplateSubGroup.cs @@ -0,0 +1,11 @@ +namespace PARR.TemplateMatcher.Models +{ + /// + /// Итоговая подгруппа юнитов, готовая к созданию/обновлению одного шаблона. + /// + public record GroupedTemplateSubGroup( + List<(Guid UnitId, Guid UnitFieldValueId)> Entries, + string InnerGroupName, + int GlobalIndex + ); +} diff --git a/PARR.TemplateMatcher/Services/Implementations/GroupedTemplateBuilder.cs b/PARR.TemplateMatcher/Services/Implementations/GroupedTemplateBuilder.cs new file mode 100644 index 00000000..f4d4b654 --- /dev/null +++ b/PARR.TemplateMatcher/Services/Implementations/GroupedTemplateBuilder.cs @@ -0,0 +1,158 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using PARR.Core.Repositories.Interfaces.Unit; +using PARR.Domain.Entities.Job; +using PARR.TemplateMatcher.Models; +using PARR.TemplateMatcher.Services.Interfaces; + +namespace PARR.TemplateMatcher.Services.Implementations; + +internal class GroupedTemplateBuilder : IGroupedTemplateBuilder +{ + private readonly ILogger logger; + private readonly IUnitInValueRepository unitInValueRepository; + private readonly IUnitFieldRepository unitFieldRepository; + + public GroupedTemplateBuilder( + ILogger logger, + IUnitInValueRepository unitInValueRepository, + IUnitFieldRepository unitFieldRepository) + { + this.logger = logger; + this.unitInValueRepository = unitInValueRepository; + this.unitFieldRepository = unitFieldRepository; + } + + public async Task> BuildAsync( + Dictionary> initialReverseMapping, + JobGroup jobGroup, + Job maxJob, + CancellationToken ct = default) + { + logger.LogDebug("Начало построения структуры групп для JobGroup {JobGroupId}.", jobGroup.Id); + + if (!initialReverseMapping.Any()) + return new List(); + + // 1. Определяем поле для внутренней группировки + var isGroupByResponsible = jobGroup.IsGroupByResponsible == true; + var innerGroupingFieldName = isGroupByResponsible ? "ОТВЕТСТВЕННЫЙ_ЗА_ЭК" : "РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК"; + + var innerGroupingField = await unitFieldRepository.GetByAihitNameAsync(innerGroupingFieldName) + ?? throw new InvalidOperationException($"Поле '{innerGroupingFieldName}' не найдено."); + + var innerGroupingFieldId = innerGroupingField.Id; + var groupingFieldId = jobGroup.GroupingUnitFieldId!.Value; + + // 2. Собираем все исходные UnitId + var allSourceUnitIds = initialReverseMapping.Values.SelectMany(ids => ids).Distinct().ToList(); + + // 3. Загружаем UnitInValue для трансформации + var relevantUnitInValues = await unitInValueRepository.Get() + .AsNoTracking() + .Where(uiv => allSourceUnitIds.Contains(uiv.UnitId) && uiv.FieldId == groupingFieldId) + .Select(uiv => new { uiv.UnitId, uiv.ValueId }) + .ToListAsync(ct); + + var uivLookup = relevantUnitInValues + .GroupBy(x => x.UnitId) + .ToDictionary(g => g.Key, g => g.Select(x => x.ValueId).ToList()); + + // 4. Трансформируем в reverseMapping с парами + var reverseMapping = new Dictionary>(); + + foreach (var kvp in initialReverseMapping) + { + var potentialUnitId = kvp.Key; + var sourceDtoIds = kvp.Value; + var entries = new List<(Guid UnitId, Guid UnitFieldValueId)>(); + + foreach (var dtoId in sourceDtoIds) + { + if (uivLookup.TryGetValue(dtoId, out var valueIds)) + { + foreach (Guid valId in valueIds) + entries.Add((UnitId: dtoId, UnitFieldValueId: valId)); + } + } + + var uniqueEntries = entries + .GroupBy(e => (e.UnitId, e.UnitFieldValueId)) + .Select(g => g.First()) + .ToList(); + + if (uniqueEntries.Any()) + reverseMapping[potentialUnitId] = uniqueEntries; + } + + if (!reverseMapping.Any()) + return new List(); + + // 5. Загружаем значения для внутренней группировки + var allUnitsInTemplatePairs = reverseMapping.Values.SelectMany(list => list).ToList(); + var allUnitIdsForInnerGrouping = allUnitsInTemplatePairs.Select(e => e.UnitId).Distinct().ToList(); + + var innerGroupingValues = await unitInValueRepository.GetByUnitIdsAndFieldIdsAsync( + allUnitIdsForInnerGrouping, + (new HashSet { innerGroupingFieldId }), + ct); + + var unitIdToInnerGroupingValueMap = innerGroupingValues + .Where(uv => uv.Value != null && !string.IsNullOrEmpty(uv.Value.Value)) + .ToDictionary(uv => uv.UnitId, uv => uv.Value!.Value); + + // 6. Формируем итоговую структуру + var templateGroups = new List(); + int maxValueForSplitting = maxJob.MaxValueRelationships!.Value; + + foreach (var kvp in reverseMapping) + { + var potentialUnitId = kvp.Key; + var unitsInTemplateForThisPotentialUnitId = kvp.Value; + + var innerGroupedUnits = unitsInTemplateForThisPotentialUnitId + .GroupBy(entry => unitIdToInnerGroupingValueMap.GetValueOrDefault(entry.UnitId, "Нет данных")) + .OrderBy(g => g.Key, StringComparer.Ordinal) + .ToList(); + + var subGroups = new List(); + int globalIndex = 1; + + foreach (var innerGroup in innerGroupedUnits) + { + var innerGroupName = innerGroup.Key; + if (innerGroupName == null) + continue; + + var unitsInInnerGroup = innerGroup.ToList(); + + var splitSubGroups = unitsInInnerGroup + .Select((entry, index) => new { entry, groupIndex = index / maxValueForSplitting }) + .GroupBy(x => x.groupIndex) + .Select(g => g.Select(x => x.entry).ToList()) + .ToList(); + + foreach (var subGroupEntries in splitSubGroups) + { + subGroups.Add(new GroupedTemplateSubGroup( + Entries: subGroupEntries, + InnerGroupName: innerGroupName, + GlobalIndex: globalIndex + )); + globalIndex++; + } + } + + if (subGroups.Any()) + { + templateGroups.Add(new GroupedTemplateGroup( + PotentialUnitId: potentialUnitId, + SubGroups: subGroups + )); + } + } + + logger.LogDebug("Построено {Count} групп шаблонов.", templateGroups.Count); + return templateGroups; + } +} \ No newline at end of file diff --git a/PARR.TemplateMatcher/Services/Implementations/GroupedTemplateProcessor.cs b/PARR.TemplateMatcher/Services/Implementations/GroupedTemplateProcessor.cs new file mode 100644 index 00000000..f7e83b64 --- /dev/null +++ b/PARR.TemplateMatcher/Services/Implementations/GroupedTemplateProcessor.cs @@ -0,0 +1,316 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using PARR.Core.Common.Interfaces.RabbitServices; +using PARR.Core.Repositories.Interfaces; +using PARR.Core.Repositories.Interfaces.Unit; +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.Enums; +using PARR.TemplateMatcher.Models; +using PARR.TemplateMatcher.Services.Interfaces; +using PARR.TemplateMatcher.Settings; + +namespace PARR.TemplateMatcher.Services.Implementations; + +internal class GroupedTemplateProcessor : IGroupedTemplateProcessor +{ + private readonly ILogger logger; + private readonly ITemplateRepository templateRepository; + private readonly IUnitRepository unitRepository; + private readonly ITemplateReuser templateReuser; + private readonly ITemplateNameNormalizer templateNameNormalizer; + private readonly ITemplateUpdaterMqSender templateUpdaterMqSender; + private readonly MqSettings mqSettings; + private readonly IRabbitService mqService; + + public GroupedTemplateProcessor( + ILogger logger, + ITemplateRepository templateRepository, + IUnitRepository unitRepository, + ITemplateReuser templateReuser, + ITemplateNameNormalizer templateNameNormalizer, + ITemplateUpdaterMqSender templateUpdaterMqSender, + MqSettings mqSettings, + IRabbitService mqService) + { + this.logger = logger; + this.templateRepository = templateRepository; + this.unitRepository = unitRepository; + this.templateReuser = templateReuser; + this.templateNameNormalizer = templateNameNormalizer; + this.templateUpdaterMqSender = templateUpdaterMqSender; + this.mqSettings = mqSettings; + this.mqService = mqService; + } + + public async Task> ProcessAsync( + List groups, + List jobsInGroup, + Job maxJob, + HistoryInitiator initiator, + CancellationToken ct = default) + { + var expectedTemplateKeys = new HashSet<(Guid JobId, Guid UnitId, int Index)>(); + + foreach (var group in groups) + { + var potentialUnitId = group.PotentialUnitId; + + foreach (var subGroup in group.SubGroups) + { + var unitsInTemplateSubGroup = subGroup.Entries; + var globalIndex = subGroup.GlobalIndex; + var originatingInnerGroupName = subGroup.InnerGroupName; + + logger.LogDebug("Обработка подгруппы {Index} ('{GroupingValue}') для UnitId {PotentialUnitId}, размер {Size}.", + globalIndex, originatingInnerGroupName, potentialUnitId, unitsInTemplateSubGroup.Count); + + Job targetJob = SelectTargetJob(jobsInGroup, unitsInTemplateSubGroup.Count, maxJob); + expectedTemplateKeys.Add((targetJob.Id, potentialUnitId, globalIndex)); + + // Поиск существующего шаблона + var existingTemplate = 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) + .Include(t => t.UnitsInTemplate).ThenInclude(uit => uit.Unit) + .Where(t => t.JobId == targetJob.Id && + t.UnitId == potentialUnitId && + t.Index == globalIndex && + t.StatusTypeId == TemplateStatusTypeEnum.Used) + .FirstOrDefaultAsync(ct); + + if (existingTemplate != null) + { + await HandleExistingTemplateAsync(existingTemplate, unitsInTemplateSubGroup, targetJob, globalIndex, initiator, ct); + } + else + { + await HandleNewOrReusableTemplateAsync(potentialUnitId, unitsInTemplateSubGroup, targetJob, globalIndex, initiator); + } + } + } + + return expectedTemplateKeys; + } + + private async Task HandleExistingTemplateAsync( + Template existingTemplate, + List<(Guid UnitId, Guid UnitFieldValueId)> proposedEntries, + Job targetJob, + int globalIndex, + HistoryInitiator initiator, + CancellationToken ct) + { + var currentEntries = existingTemplate.UnitsInTemplate + .Select(uit => (uit.UnitId, uit.UnitFieldValueId)) + .ToList(); + + // Сравнение + var allUnitIdsForSort = currentEntries.Select(e => e.UnitId) + .Concat(proposedEntries.Select(e => e.UnitId)) + .Distinct() + .ToList(); + + var unitNamesForSort = await unitRepository.Get() + .AsNoTracking() + .Where(u => allUnitIdsForSort.Contains(u.Id)) + .ToDictionaryAsync(u => u.Id, u => u.Name ?? u.Id.ToString(), ct); + + var sortedCurrent = currentEntries + .OrderBy(e => unitNamesForSort.GetValueOrDefault(e.UnitId, e.UnitId.ToString())) + .ThenBy(e => e.UnitFieldValueId) + .ToList(); + + var sortedProposed = proposedEntries + .OrderBy(e => unitNamesForSort.GetValueOrDefault(e.UnitId, e.UnitId.ToString())) + .ThenBy(e => e.UnitFieldValueId) + .ToList(); + + bool unitsAreEqual = sortedCurrent.SequenceEqual(sortedProposed); + + if (unitsAreEqual) + { + logger.LogDebug("Шаблон {TemplateId} актуален по составу.", existingTemplate.Id); + + // Проверка имени + var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(existingTemplate); + if (!string.Equals(existingTemplate.Name, expectedName, StringComparison.OrdinalIgnoreCase)) + { + logger.LogDebug("Шаблон {TemplateId} требует обновления имени.", existingTemplate.Id); + var updateRequest = new TemplateUpdaterMessage + { + TemplateId = existingTemplate.Id, + JobId = targetJob.Id, + UnitId = existingTemplate.UnitId, + Name = expectedName, + IsActiveTemplate = existingTemplate.IsActiveTemplate, + IsActiveSchedule = existingTemplate.IsActiveSchedule, + IsNew = false, + Index = globalIndex, + StatusTypeId = TemplateStatusTypeEnum.Used, + Initiator = initiator, + UnitsInTemplate = sortedProposed.Select(t => new UnitInTemplateMessage + { + UnitId = t.UnitId, + UnitFieldValueId = t.UnitFieldValueId + }).ToList() + }; + await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); + } + } + else + { + logger.LogDebug("Шаблон {TemplateId} требует обновления состава.", existingTemplate.Id); + await UpdateTemplateUnitsAsync(existingTemplate, sortedProposed, targetJob, globalIndex, initiator); + } + } + + private async Task HandleNewOrReusableTemplateAsync( + Guid potentialUnitId, + List<(Guid UnitId, Guid UnitFieldValueId)> unitsInTemplateSubGroup, + Job targetJob, + int globalIndex, + HistoryInitiator initiator) + { + var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(targetJob.Id, potentialUnitId, initiator); + + if (reusableTemplate != null) + { + logger.LogInformation("Переиспользован шаблон {TemplateId}.", reusableTemplate.Id); + + var tempTemplateForName = new Template + { + Id = reusableTemplate.Id, + Name = reusableTemplate.Name, + JobId = targetJob.Id, + UnitId = potentialUnitId, + Index = globalIndex, + Job = targetJob, + Unit = reusableTemplate.Unit, + UnitsInTemplate = unitsInTemplateSubGroup.Select(e => new UnitsInTemplate { UnitId = e.UnitId, UnitFieldValueId = e.UnitFieldValueId }).ToList() + }; + + var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName); + + var updateRequest = new TemplateUpdaterMessage + { + TemplateId = reusableTemplate.Id, + JobId = targetJob.Id, + UnitId = potentialUnitId, + Name = expectedName, + IsActiveTemplate = targetJob.AutoControl?.InitUsedTemplateState ?? false, + IsActiveSchedule = targetJob.AutoControl?.InitUsedScheduleState ?? false, + StatusTypeId = TemplateStatusTypeEnum.Used, + Initiator = initiator, + IsNew = true, + Index = globalIndex, + UnitsInTemplate = unitsInTemplateSubGroup.Select(e => new UnitInTemplateMessage { UnitId = e.UnitId, UnitFieldValueId = e.UnitFieldValueId }).ToList() + }; + + await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); + } + else + { + logger.LogDebug("Создание нового шаблона."); + await CreateGroupedTemplateAsync(targetJob.Id, potentialUnitId, unitsInTemplateSubGroup, globalIndex, initiator); + } + } + + private async Task UpdateTemplateUnitsAsync( + Template template, + List<(Guid UnitId, Guid UnitFieldValueId)> newUnitEntries, + Job targetJob, + int newIndex, + HistoryInitiator initiator) + { + // 1. Устанавливаем статус и дату + template.StatusTypeId = TemplateStatusTypeEnum.Updating; + template.DateModified = DateTimeOffset.UtcNow; + + // 2. КОММИТ В БАЗУ СРАЗУ + // Важно зафиксировать изменение статуса до отправки сообщения в очередь + if (!await templateRepository.CommitAsync(initiator)) + { + logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id); + return; + } + + // 3. Формируем временный объект для генерации имени + var tempTemplateForName = new Template + { + Id = template.Id, + Name = template.Name, + JobId = targetJob.Id, + UnitId = template.UnitId, + Index = newIndex, + Job = targetJob, + Unit = template.Unit, + UnitsInTemplate = newUnitEntries.Select(e => new UnitsInTemplate { UnitId = e.UnitId, UnitFieldValueId = e.UnitFieldValueId }).ToList() + }; + + var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName); + + // 4. Отправляем сообщение в очередь + var updateRequest = new TemplateUpdaterMessage + { + TemplateId = template.Id, + JobId = targetJob.Id, + UnitId = template.UnitId, + Name = expectedName, + IsActiveTemplate = template.IsActiveTemplate, + IsActiveSchedule = template.IsActiveSchedule, + IsNew = false, + Index = newIndex, + StatusTypeId = TemplateStatusTypeEnum.Used, + Initiator = initiator, + UnitsInTemplate = newUnitEntries.Select(e => new UnitInTemplateMessage { UnitId = e.UnitId, UnitFieldValueId = e.UnitFieldValueId }).ToList() + }; + + await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); + } + + private async Task CreateGroupedTemplateAsync( + Guid jobId, + Guid relationshipUnitId, + List<(Guid UnitId, Guid UnitFieldValueId)> unitsInTemplate, + int index, + HistoryInitiator initiator) + { + logger.LogInformation("Создание нового группового шаблона."); + + var mqRequest = new TemplateGeneratorMessage + { + JobId = jobId, + UnitId = relationshipUnitId, + UnitsInTemplate = unitsInTemplate.Select(e => new UnitInTemplateMessage { UnitId = e.UnitId, UnitFieldValueId = e.UnitFieldValueId }).ToList(), + Index = index, + HistoryInitiator = initiator + }; + + var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new List { mqRequest }); + + if (!result.IsSuccess) + logger.LogError("Ошибка отправки команды создания шаблона."); + } + + private static Job SelectTargetJob(List jobsInGroup, int subGroupSize, Job maxJob) + { + Job? targetJob = jobsInGroup + .Where(j => j.MaxValueRelationships.HasValue && j.MaxValueRelationships.Value == subGroupSize) + .FirstOrDefault(); + + if (targetJob == null) + { + targetJob = jobsInGroup + .Where(j => j.MaxValueRelationships.HasValue && j.MaxValueRelationships.Value >= subGroupSize) + .OrderBy(j => j.MaxValueRelationships!.Value) + .FirstOrDefault(); + } + + return targetJob ?? maxJob; + } +} \ No newline at end of file diff --git a/PARR.TemplateMatcher/Services/Implementations/GroupedTemplateSynchronizer.cs b/PARR.TemplateMatcher/Services/Implementations/GroupedTemplateSynchronizer.cs new file mode 100644 index 00000000..f9089bb6 --- /dev/null +++ b/PARR.TemplateMatcher/Services/Implementations/GroupedTemplateSynchronizer.cs @@ -0,0 +1,243 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using PARR.Core.Repositories.Interfaces; +using PARR.Core.Repositories.Interfaces.Job; +using PARR.Core.Services.MatchingStatusService; +using PARR.Core.Services.UnitFilterService; +using PARR.Domain.Cache.Models; +using PARR.Domain.Entities.Base.History; +using PARR.Domain.Entities.Job; +using PARR.Domain.Enums; +using PARR.TemplateMatcher.Services.Interfaces; + +namespace PARR.TemplateMatcher.Services.Implementations; + +internal class GroupedTemplateSynchronizer : ITemplateSynchronizer +{ + private readonly ILogger logger; + private readonly IJobGroupRepository jobGroupService; + private readonly IUnitFilterService unitFilterService; + private readonly IGroupedTemplateUnitFilter groupedTemplateUnitFilter; + private readonly IUnitInTemplateConflictMapper unitInTemplateConflictMapper; + private readonly IGroupedTemplateBuilder groupedTemplateBuilder; + private readonly IGroupedTemplateProcessor groupedTemplateProcessor; + private readonly ITemplateRepository templateService; + private readonly ITemplateDeactivator templateDeactivator; + private readonly IMatchingStatusService matchingStatusService; + + public GroupedTemplateSynchronizer( + ILogger logger, + IJobGroupRepository jobGroupService, + IUnitFilterService unitFilterService, + IGroupedTemplateUnitFilter groupedTemplateUnitFilter, + IUnitInTemplateConflictMapper unitInTemplateConflictMapper, + IGroupedTemplateBuilder groupedTemplateBuilder, + IGroupedTemplateProcessor groupedTemplateProcessor, + ITemplateRepository templateService, + ITemplateDeactivator templateDeactivator, + IMatchingStatusService matchingStatusService) + { + this.logger = logger; + this.jobGroupService = jobGroupService; + this.unitFilterService = unitFilterService; + this.groupedTemplateUnitFilter = groupedTemplateUnitFilter; + this.unitInTemplateConflictMapper = unitInTemplateConflictMapper; + this.groupedTemplateBuilder = groupedTemplateBuilder; + this.groupedTemplateProcessor = groupedTemplateProcessor; + this.templateService = templateService; + this.templateDeactivator = templateDeactivator; + this.matchingStatusService = matchingStatusService; + } + + public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator) + { + logger.LogWarning("GroupedTemplateSynchronizer: SyncTemplatesForJob вызван для JobId {JobId}. Это не поддерживаемая операция.", jobId); + } + + public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator) + { + logger.LogDebug("Начало синхронизации шаблонов для JobGroup {JobGroupId}", jobGroupId); + + // === Проверка: уже запущена? === + var existingStatus = await matchingStatusService.GetStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup); + if (existingStatus.DetailsJobGroups?.Any() == true) + { + logger.LogWarning("Синхронизация для JobGroup {JobGroupId} уже запущена. Пропускаем.", jobGroupId); + return; + } + + // === Устанавливаем статус "в процессе" === + var initialStatus = new MatchingStatusItemDto + { + DateStart = DateTimeOffset.UtcNow, + Action = TemplateMatcherActionEnum.Sync, + Comment = "Начало синхронизации" + }; + await matchingStatusService.SetMatchingStatusAsync( + jobGroupId, + SyncTaskEntityTypeEnum.JobGroup, + new MatchingStatusItem { Data = initialStatus, Timestamp = DateTimeOffset.UtcNow, Source = nameof(GroupedTemplateSynchronizer) }, + TimeSpan.FromMinutes(35) + ); + + try + { + // 1. Загрузка JobGroup и связанных Job'ов + var jobGroup = await jobGroupService.Get() + .AsNoTracking() + .AsSingleQuery() + .Include(jg => jg.GroupType) + .Include(jg => jg.Jobs) + .ThenInclude(j => j.AutoControl) + .Include(jg => jg.Jobs) + .ThenInclude(j => j.UnitFilters) + .ThenInclude(uf => uf.RelationshipFilters) + .ThenInclude(rf => rf.UnitField) + .Include(jg => jg.Jobs) + .ThenInclude(jg => jg.Tnk) + .FirstOrDefaultAsync(jg => jg.Id == jobGroupId); + + if (jobGroup == null || jobGroup.Jobs == null || !jobGroup.Jobs.Any()) + { + logger.LogWarning("JobGroup {JobGroupId} не найден или не содержит Job'ов.", jobGroupId); + await UpdateMatchingStatusAsync(jobGroupId, "JobGroup не найден или пуст"); + return; + } + + var jobsInGroup = jobGroup.Jobs.ToList(); + + // 2. Поиск Job с максимальным MaxValueRelationships + var maxJob = jobsInGroup + .Where(j => j.MaxValueRelationships.HasValue) + .OrderByDescending(j => j.MaxValueRelationships) + .FirstOrDefault(); + + if (maxJob == null) + { + logger.LogWarning("В JobGroup {JobGroupId} не найдено Job с установленным MaxValueRelationships.", jobGroupId); + await UpdateMatchingStatusAsync(jobGroupId, "Не найден Job с MaxValueRelationships"); + return; + } + + logger.LogDebug("Используется Job {JobId} с максимальным MaxValueRelationships ({MaxValue}).", maxJob.Id, maxJob.MaxValueRelationships); + + // 3. Получение отфильтрованных юнитов через UnitFilterService + logger.LogDebug("Получение отфильтрованных юнитов через UnitFilterService для Job {JobId}.", maxJob.Id); + var unitFilterResults = await unitFilterService.GetUnitsByJobFilterAsync(maxJob.Id); + + if (unitFilterResults == null || !unitFilterResults.Any()) + { + logger.LogInformation("Для JobGroup {JobGroupId} фильтры не дали Unit'ов с подходящими связями.", jobGroupId); + await UpdateMatchingStatusAsync(jobGroupId, "Фильтры не дали Unit'ов с подходящими связями"); + await matchingStatusService.DeleteMatchingStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup); + return; + } + + // 4. Применение специфичных правил фильтрации для групповых шаблонов + logger.LogDebug("Применение специфичных правил фильтрации для групповых шаблонов."); + var finalFilteredUnits = await groupedTemplateUnitFilter.FilterAsync(unitFilterResults, jobGroup); + + if (!finalFilteredUnits.Any()) + { + logger.LogInformation("После применения правил фильтрации в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId); + await UpdateMatchingStatusAsync(jobGroupId, "Нет юнитов после фильтрации"); + await matchingStatusService.DeleteMatchingStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup); + return; + } + + // 5. Разрешение конфликтов связей и построение первичного маппинга + logger.LogDebug("Разрешение конфликтов связей и построение первичного маппинга."); + var initialReverseMapping = await unitInTemplateConflictMapper.BuildMappingAsync(finalFilteredUnits, maxJob); + + if (!initialReverseMapping.Any()) + { + logger.LogInformation("После разрешения конфликтов в JobGroup {JobGroupId} не осталось связей.", jobGroupId); + await UpdateMatchingStatusAsync(jobGroupId, "Нет связей после разрешения конфликтов"); + return; + } + + // 6. Построение структуры групп (трансформация, внутренняя группировка, разбиение) + logger.LogDebug("Построение структуры групп для шаблонов."); + var templateGroups = await groupedTemplateBuilder.BuildAsync(initialReverseMapping, jobGroup, maxJob); + + if (!templateGroups.Any()) + { + logger.LogInformation("После построения структуры групп в JobGroup {JobGroupId} не осталось данных.", jobGroupId); + await UpdateMatchingStatusAsync(jobGroupId, "Нет данных после построения групп"); + return; + } + + // 7. Обработка групп: сравнение, обновление, создание, отправка MQ + logger.LogDebug("Обработка групп шаблонов: сравнение, обновление и создание."); + var expectedTemplateKeys = await groupedTemplateProcessor.ProcessAsync( + templateGroups, + jobsInGroup, + maxJob, + initiator); + + // 8. Деактивация лишних шаблонов + await DeactivateUnusedTemplatesAsync(expectedTemplateKeys, jobGroupId, jobsInGroup, initiator); + + // 9. Успешное завершение + await UpdateMatchingStatusAsync(jobGroupId, "Синхронизация завершена успешно"); + await matchingStatusService.DeleteMatchingStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup); + logger.LogInformation("Синхронизация шаблонов завершена для JobGroup {JobGroupId}.", jobGroupId); + } + catch (Exception ex) + { + logger.LogError(ex, "Ошибка при синхронизации JobGroup {JobGroupId}", jobGroupId); + await UpdateMatchingStatusAsync(jobGroupId, $"Ошибка: {ex.Message}"); + throw; + } + } + + public async Task UpdateTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator) + { + logger.LogWarning("GroupedTemplateSynchronizer: UpdateTemplatesForJob вызван для JobId {JobId}. Это не поддерживаемая операция. Используйте SyncTemplatesForJobGroup для обновления.", jobId); + } + + private async Task DeactivateUnusedTemplatesAsync( + HashSet<(Guid JobId, Guid UnitId, int Index)> expectedKeys, + Guid jobGroupId, + List jobsInGroup, + HistoryInitiator initiator) + { + var allJobIdsInGroup = jobsInGroup.Select(j => j.Id).ToHashSet(); + var allExistingTemplatesInGroup = await templateService.Get() + .AsNoTracking() + .Include(t => t.Unit) + .Include(t => t.UnitsInTemplate) + .Where(t => allJobIdsInGroup.Contains(t.JobId) && + t.StatusTypeId == TemplateStatusTypeEnum.Used && + t.Job!.GroupId == jobGroupId) + .ToListAsync(); + + foreach (var existingTemplate in allExistingTemplatesInGroup) + { + var key = (existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index ?? -1); + if (!expectedKeys.Contains(key)) + { + logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, Unit {UnitId}, Index {Index}).", + existingTemplate.Id, existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index); + await templateDeactivator.DeactivateTemplateAsync(existingTemplate, initiator); + } + } + } + + private async Task UpdateMatchingStatusAsync(Guid jobGroupId, string comment) + { + var status = new MatchingStatusItemDto + { + DateStart = DateTimeOffset.UtcNow, + Action = TemplateMatcherActionEnum.Sync, + Comment = comment + }; + + await matchingStatusService.SetMatchingStatusAsync( + jobGroupId, + SyncTaskEntityTypeEnum.JobGroup, + new MatchingStatusItem { Data = status, Timestamp = DateTimeOffset.UtcNow, Source = nameof(GroupedTemplateSynchronizer) }, + TimeSpan.FromMinutes(30) + ); + } +} \ No newline at end of file diff --git a/PARR.TemplateMatcher/Services/Implementations/GroupedTemplateUnitFilter.cs b/PARR.TemplateMatcher/Services/Implementations/GroupedTemplateUnitFilter.cs new file mode 100644 index 00000000..49f8cf74 --- /dev/null +++ b/PARR.TemplateMatcher/Services/Implementations/GroupedTemplateUnitFilter.cs @@ -0,0 +1,100 @@ +using Microsoft.Extensions.Logging; +using PARR.Core.Repositories.Interfaces.Unit; +using PARR.Core.Services.UnitFilterService.Models; +using PARR.Domain.Entities.Job; +using PARR.TemplateMatcher.Services.Interfaces; + +namespace PARR.TemplateMatcher.Services.Implementations; + +internal class GroupedTemplateUnitFilter : IGroupedTemplateUnitFilter +{ + private readonly ILogger logger; + private readonly IUnitInValueRepository unitInValueRepository; + private readonly IUnitRegionalEkPtkGroupRepository regionalEkPtkGroupRepository; + private readonly IUnitFieldRepository unitFieldRepository; + + public GroupedTemplateUnitFilter( + ILogger logger, + IUnitInValueRepository unitInValueRepository, + IUnitRegionalEkPtkGroupRepository regionalEkPtkGroupRepository, + IUnitFieldRepository unitFieldRepository) + { + this.logger = logger; + this.unitInValueRepository = unitInValueRepository; + this.regionalEkPtkGroupRepository = regionalEkPtkGroupRepository; + this.unitFieldRepository = unitFieldRepository; + } + + public async Task> FilterAsync( + IEnumerable initialUnits, + JobGroup jobGroup, + CancellationToken ct = default) + { + var unitsList = initialUnits.ToList(); + if (!unitsList.Any()) + { + logger.LogDebug("Входной список юнитов пуст. Фильтрация пропущена."); + return unitsList; + } + + logger.LogDebug("Начало фильтрации юнитов для JobGroup {JobGroupId}.", jobGroup.Id); + + // 1. Проверка наличия GroupingUnitFieldId + if (!jobGroup.GroupingUnitFieldId.HasValue) + { + throw new InvalidOperationException($"JobGroup {jobGroup.Id} не имеет GroupingUnitFieldId, необходимого для группировки."); + } + + var groupingFieldId = jobGroup.GroupingUnitFieldId.Value; + logger.LogDebug("Фильтрация по GroupingUnitFieldId (FieldId={FieldId}).", groupingFieldId); + + // 2. Фильтрация по GroupingUnitFieldId + var allUnitIds = unitsList.Select(u => u.Id).ToList(); + var groupingValues = await unitInValueRepository.GetByUnitIdsAndFieldIdsAsync(allUnitIds, new HashSet { groupingFieldId }); + + var validUnitIdsAfterGrouping = groupingValues + .Where(uv => uv.Value != null && !string.IsNullOrEmpty(uv.Value.Value)) + .Select(uv => uv.UnitId) + .ToHashSet(); + + var filteredByGrouping = unitsList + .Where(u => validUnitIdsAfterGrouping.Contains(u.Id)) + .ToList(); + + logger.LogDebug("После фильтрации по GroupingUnitFieldId осталось {Count} юнитов.", filteredByGrouping.Count); + + if (!filteredByGrouping.Any()) + { + return filteredByGrouping; + } + + // 3. Фильтрация по РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК + var workGroupField = await unitFieldRepository.GetByAihitNameAsync("РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК") + ?? throw new InvalidOperationException("Поле 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' не найдено в справочнике полей."); + + var workGroupFieldId = workGroupField.Id; + logger.LogDebug("Фильтрация по полю 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' (FieldId={FieldId}).", workGroupFieldId); + + var filteredUnitIds = filteredByGrouping.Select(u => u.Id).ToList(); + var workGroupValues = await unitInValueRepository.GetByUnitIdsAndFieldIdsAsync(filteredUnitIds, new HashSet { workGroupFieldId }); + + var allowedValueIds = regionalEkPtkGroupRepository.Get() + .Select(g => g.FieldValueId) + .ToHashSet(); + + logger.LogDebug("Найдено {Count} разрешенных значений для поля 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК'.", allowedValueIds.Count); + + var validUnitIdsAfterWorkGroup = workGroupValues + .Where(uv => uv.Value != null && allowedValueIds.Contains(uv.Value.Id)) + .Select(uv => uv.UnitId) + .ToHashSet(); + + var finalFiltered = filteredByGrouping + .Where(u => validUnitIdsAfterWorkGroup.Contains(u.Id)) + .ToList(); + + logger.LogDebug("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' осталось {Count} юнитов.", finalFiltered.Count); + + return finalFiltered; + } +} \ No newline at end of file diff --git a/PARR.TemplateMatcher/Services/Implemetaions/JobGroupValidatorService.cs b/PARR.TemplateMatcher/Services/Implementations/JobGroupValidatorService.cs similarity index 100% rename from PARR.TemplateMatcher/Services/Implemetaions/JobGroupValidatorService.cs rename to PARR.TemplateMatcher/Services/Implementations/JobGroupValidatorService.cs diff --git a/PARR.TemplateMatcher/Services/Implemetaions/JobValidatorService.cs b/PARR.TemplateMatcher/Services/Implementations/JobValidatorService.cs similarity index 100% rename from PARR.TemplateMatcher/Services/Implemetaions/JobValidatorService.cs rename to PARR.TemplateMatcher/Services/Implementations/JobValidatorService.cs diff --git a/PARR.TemplateMatcher/Services/Implemetaions/SimpleTemplateSynchronizer.cs b/PARR.TemplateMatcher/Services/Implementations/SimpleTemplateSynchronizer.cs similarity index 75% rename from PARR.TemplateMatcher/Services/Implemetaions/SimpleTemplateSynchronizer.cs rename to PARR.TemplateMatcher/Services/Implementations/SimpleTemplateSynchronizer.cs index 5638122f..01ff266d 100644 --- a/PARR.TemplateMatcher/Services/Implemetaions/SimpleTemplateSynchronizer.cs +++ b/PARR.TemplateMatcher/Services/Implementations/SimpleTemplateSynchronizer.cs @@ -6,13 +6,13 @@ using PARR.Core.Repositories.Interfaces; using PARR.Core.Repositories.Interfaces.Job; using PARR.Core.Repositories.Interfaces.Unit; using PARR.Core.Services.MatchingStatusService; -using PARR.Core.Services.Shortcodes; using PARR.Core.Services.UnitFilterService; using PARR.Domain.Cache.Models; -using PARR.Domain.Common.Rabbit.Messages; +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.Domain.Settings; using PARR.TemplateMatcher.Services.Interfaces; @@ -41,30 +41,28 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer private readonly ITemplateUpdaterMqSender templateUpdaterMqSender; private readonly IMatchingStatusService matchingStatusService; private readonly SettingsFromDb settingsFromDb; - //private readonly INextRunService nextRunService; private readonly IOptions templateSettings; + private readonly IUnitFieldRepository unitFieldService; + private readonly IUnitInValueRepository unitInValueService; + private readonly IUnitRepository unitRepository; public SimpleTemplateSynchronizer( ILogger logger, IUnitFilterService unitFilterService, - IUnitInUnitRepository unitInUnitService, - IUnitInValueRepository unitInValueService, - IUnitRepository unitService, MqSettings mqSettings, IRabbitService mqService, ITemplateRepository templateService, IJobRepository jobService, ITemplateReuser templateReuser, - IShortcodesService shortcodesService, - IUnitRegionalEkPtkGroupRepository regionalEkPtkGroupService, - IUnitFieldRepository unitFieldService, ITemplateDeactivator templateDeactivator, ITemplateNameNormalizer templateNameNormalizer, ITemplateUpdaterMqSender templateUpdaterMqSender, IMatchingStatusService matchingStatusService, SettingsFromDb settingsFromDb, - //INextRunService nextRunService, - IOptions templateSettings + IOptions templateSettings, + IUnitFieldRepository unitFieldService, + IUnitInValueRepository unitInValueService, + IUnitRepository unitRepository ) { this.logger = logger; @@ -79,8 +77,10 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer this.templateUpdaterMqSender = templateUpdaterMqSender; this.matchingStatusService = matchingStatusService; this.settingsFromDb = settingsFromDb; - //this.nextRunService = nextRunService; this.templateSettings = templateSettings; + this.unitFieldService = unitFieldService; + this.unitInValueService = unitInValueService; + this.unitRepository = unitRepository; } public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator) @@ -182,7 +182,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer .Include(t => t.UnitsInTemplate) .Include(t => t.Job) .ThenInclude(t => t!.Group) - .ThenInclude(t => t.GroupType) + .ThenInclude(t => t!.GroupType) .Include(t => t.Job) .ThenInclude(t => t!.Tnk) .Include(t => t.Unit) @@ -225,7 +225,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName); //var nextRun = await nextRunService.GetNextRunForTemplateAsync(reusableTemplate.Id, true); - var updateRequest = new TemplateUpdaterMq + var updateRequest = new TemplateUpdaterMessage { TemplateId = reusableTemplate.Id, JobId = jobId, @@ -237,7 +237,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer Initiator = initiator, //NextRun = nextRun, IsNew = true, - UnitsInTemplate = new List() // для простого шаблона + UnitsInTemplate = new List() // для простого шаблона }; await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); @@ -259,9 +259,8 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer { logger.LogDebug("Шаблон {TemplateId} требует обновления имени: старое = '{OldName}', новое = '{NewName}'", template.Id, template.Name, expectedName); - //var nextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, false); - var updateRequest = new TemplateUpdaterMq + var updateRequest = new TemplateUpdaterMessage { TemplateId = template.Id, JobId = jobId, @@ -269,13 +268,11 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer Name = expectedName, IsActiveTemplate = template.IsActiveTemplate, IsActiveSchedule = template.IsActiveSchedule, - //LastRun = template.LastRun, - //NextRun = nextRun, IsNew = false, Index = template.Index, StatusTypeId = TemplateStatusTypeEnum.Used, Initiator = initiator, - UnitsInTemplate = new List() // для простого шаблона + UnitsInTemplate = new List() // для простого шаблона }; await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); @@ -400,7 +397,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer //var nextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, false); - var updateRequest = new TemplateUpdaterMq + var updateRequest = new TemplateUpdaterMessage { TemplateId = template.Id, JobId = jobId, @@ -414,7 +411,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer Index = template.Index, StatusTypeId = TemplateStatusTypeEnum.Used, Initiator = initiator, - UnitsInTemplate = new List() // для простого шаблона + UnitsInTemplate = new List() // для простого шаблона }; await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); @@ -434,9 +431,9 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer } } + private async Task SyncUnusedTemplatesAsync(Guid unusedJobId, HistoryInitiator initiator) { - // === Проверка: уже запущена? === var existingStatus = await matchingStatusService.GetStatusAsync(unusedJobId, SyncTaskEntityTypeEnum.Job); if (existingStatus.DetailsJobs?.Any() == true) { @@ -444,7 +441,6 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer return; } - // === Устанавливаем статус "в процессе" === var initialStatus = new MatchingStatusItemDto { DateStart = DateTimeOffset.UtcNow, @@ -460,7 +456,33 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer try { - // Загружаем Job неиспользуемых шаблонов с Group + // 1. Находим ID нужных полей + var zoRgField = await unitFieldService.GetByAihitNameAsync("ЗОНА_ОТВЕТСТВЕННОСТИ"); + var tagField = await unitFieldService.GetByAihitNameAsync("ПАРР тег"); + + if (zoRgField == null || tagField == null) + { + logger.LogError("Не найдены поля 'ЗОНА_ОТВЕТСТВЕННОСТИ' или 'Тег'. Синхронизация прервана."); + await UpdateMatchingStatusAsync(unusedJobId, "Ошибка конфигурации полей"); + return; + } + + var zoRgFieldId = zoRgField.Id; + var tagFieldId = tagField.Id; + const string targetTagValue = "ПАРР-НЕИСП"; + + // 2. Находим ValueId для тега "ПАРР-НЕИСП" + var targetTagValueId = await unitInValueService.Get() + .AsNoTracking() + .Where(uiv => uiv.FieldId == tagFieldId && uiv.Value != null && uiv.Value.Value == targetTagValue) + .Select(uiv => uiv.ValueId) + .FirstOrDefaultAsync(); + + if (targetTagValueId == Guid.Empty) + { + logger.LogWarning("Значение '{TagValue}' для поля 'Тег' не найдено в справочнике UnitFieldValue.", targetTagValue); + } + var unusedJob = await jobService.Get() .AsNoTracking() .Include(j => j.Group) @@ -473,9 +495,13 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer return; } - // Находим все шаблоны со статусом Unused var unusedTemplates = await templateService.Get() .Include(t => t.Unit) + .Include(t => t.Job) + .ThenInclude(t => t.Group) + .ThenInclude(t => t.GroupType) + .Include(t => t.Job) + .ThenInclude(t => t.Tnk) .Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Unused) .ToListAsync(); @@ -490,47 +516,96 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer await UpdateMatchingStatusAsync(unusedJobId, $"Найдено {unusedTemplates.Count} шаблонов для обработки"); int processed = 0; + var allTemplateUnitIds = unusedTemplates.Select(t => t.UnitId).Distinct().ToList(); + + // Получаем значения ЗОНА_ОТВЕТСТВЕННОСТИ для всех юнитов шаблонов + var unitZoRgValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(allTemplateUnitIds, new List { zoRgFieldId }); + var unitToZoRgValueMap = unitZoRgValues + .Where(uiv => uiv.ValueId != Guid.Empty) + .ToDictionary(uiv => uiv.UnitId, uiv => uiv.ValueId); + foreach (var template in unusedTemplates) { - // Генерируем ожидаемое имя один раз - var expectedName = await GenerateUnusedTemplateNameAsync(template, unusedJob); - - // Извлекаем "базовые" имена - var currentBaseName = ExtractBaseName(template.Name); - var expectedBaseName = ExtractBaseName(expectedName); - - // Проверяем, нужно ли обновление - bool needsUpdate = template.JobId != unusedJobId || - currentBaseName != expectedBaseName; - - if (!needsUpdate) + try { - logger.LogDebug("Шаблон {TemplateId} уже имеет актуальное имя и JobId. Пропускаем.", template.Id); - continue; + if (template.Unit == null) + { + logger.LogWarning("У шаблона {TemplateId} отсутствует Unit. Пропускаем.", template.Id); + processed++; + continue; + } + + if (!unitToZoRgValueMap.TryGetValue(template.UnitId, out var currentZoRgValueId)) + { + logger.LogDebug("У юнита {UnitId} шаблона {TemplateId} нет значения поля ЗОНА_ОТВЕТСТВЕННОСТИ. Пропускаем замену UnitId.", template.UnitId, template.Id); + + // Исправлено: используем другое имя или просто вызываем метод + var nameForUpdate = await GenerateUnusedTemplateNameAsync(template, unusedJob, template.Unit); + await SendUpdateRequest(template, unusedJobId, nameForUpdate, initiator, template.UnitId); + processed++; + continue; + } + + Guid? targetUnitId = null; + + if (targetTagValueId != Guid.Empty) + { + var candidatesWithZoRg = await unitInValueService.Get() + .AsNoTracking() + .Where(uiv => uiv.FieldId == zoRgFieldId && uiv.ValueId == currentZoRgValueId) + .Select(uiv => uiv.UnitId) + .Distinct() + .ToListAsync(); + + if (candidatesWithZoRg.Any()) + { + // Сначала проверим, есть ли вообще такие юниты + var foundUnitId = await unitInValueService.Get() + .AsNoTracking() + .Where(uiv => uiv.FieldId == tagFieldId && uiv.ValueId == targetTagValueId && candidatesWithZoRg.Contains(uiv.UnitId)) + .Select(uiv => uiv.UnitId) + .FirstOrDefaultAsync(); + + // Если нашли реальный Guid (не Empty), то используем его + if (foundUnitId != Guid.Empty) + { + targetUnitId = foundUnitId; + } + } + } + + Guid finalUnitId = targetUnitId ?? template.UnitId; + Unit finalUnit = template.Unit; + + if (targetUnitId.HasValue && targetUnitId.Value != template.UnitId) + { + logger.LogInformation("Для шаблона {TemplateId} найден новый UnitId {NewUnitId} (был {OldUnitId}).", + template.Id, targetUnitId.Value, template.UnitId); + + // Загружаем новый юнит для генерации имени + var newUnit = await unitRepository.Get().AsNoTracking() + .FirstOrDefaultAsync(u => u.Id == targetUnitId.Value); + + if (newUnit != null) + finalUnit = newUnit; + else + { + logger.LogWarning("Не удалось загрузить новый юнит {UnitId}. Используем старый.", targetUnitId.Value); + finalUnitId = template.UnitId; + } + } + + // Здесь expectedName объявляется впервые в этой итерации цикла, конфликта нет + var expectedName = await GenerateUnusedTemplateNameAsync(template, unusedJob, finalUnit); + await SendUpdateRequest(template, unusedJobId, expectedName, initiator, finalUnitId); + + processed++; + await UpdateMatchingStatusAsync(unusedJobId, $"Обработано: {processed}/{unusedTemplates.Count}"); } - - //var nextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, false); - - var updateRequest = new TemplateUpdaterMq + catch (Exception ex) { - TemplateId = template.Id, - JobId = unusedJobId, - UnitId = template.UnitId, - Name = expectedName, - IsActiveTemplate = false, - IsActiveSchedule = false, - //LastRun = template.LastRun, - //NextRun = nextRun, - IsNew = false, - Index = null, - StatusTypeId = template.StatusTypeId, - Initiator = initiator, - UnitsInTemplate = new List() - }; - - await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); - processed++; - await UpdateMatchingStatusAsync(unusedJobId, $"Обработано: {processed}/{unusedTemplates.Count}"); + logger.LogError(ex, "Ошибка при обработке шаблона {TemplateId}", template.Id); + } } await UpdateMatchingStatusAsync(unusedJobId, "Синхронизация неиспользуемых шаблонов завершена"); @@ -545,8 +620,28 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer } } + private async Task SendUpdateRequest(Template template, Guid jobId, string name, HistoryInitiator initiator, Guid unitId) + { + var updateRequest = new TemplateUpdaterMessage + { + TemplateId = template.Id, + JobId = jobId, + UnitId = unitId, + Name = name, + IsActiveTemplate = false, + IsActiveSchedule = false, + IsNew = false, + Index = null, + StatusTypeId = template.StatusTypeId, + Initiator = initiator, + UnitsInTemplate = new List() + }; - private async Task GenerateUnusedTemplateNameAsync(Template template, Job unusedJob) + await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); + } + + + private async Task GenerateUnusedTemplateNameAsync(Template template, Job unusedJob, Unit unit) { var tempJob = new Job { @@ -573,10 +668,10 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer Id = template.Id, Name = template.Name, JobId = unusedJob.Id, - UnitId = template.UnitId, + UnitId = unit.Id, Index = null, Job = tempJob, - Unit = template.Unit, + Unit = unit, UnitsInTemplate = new List() }; @@ -588,16 +683,14 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer { logger.LogInformation("Создание нового простого шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId); - var mqRequest = new TemplateGeneratorMq + var mqRequest = new TemplateGeneratorMessage { JobId = jobId, UnitId = unitId, - UnitsInTemplate = new List(), // для простого шаблона + UnitsInTemplate = new List(), // для простого шаблона HistoryInitiator = initiator }; - //var msg = JsonSerializer.Serialize(mqRequest); - //var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg }); var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new List { mqRequest }); if (!result.IsSuccess) @@ -620,29 +713,4 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer TimeSpan.FromMinutes(30) ); } - - - private static string ExtractBaseName(string name) - { - var lastUnderscoreIndex = name.LastIndexOf('_'); - if (lastUnderscoreIndex > 0) - { - var suffix = name.Substring(lastUnderscoreIndex + 1); - if (long.TryParse(suffix, out long unixTimestampMs)) - { - try - { - // Проверяем, является ли это валидным временем - var dto = DateTimeOffset.FromUnixTimeMilliseconds(unixTimestampMs); - return name.Substring(0, lastUnderscoreIndex); - } - catch (ArgumentOutOfRangeException) - { - // Значение вне диапазона для DateTimeOffset - return name; // Не трогаем имя, если суффикс невалиден - } - } - } - return name; - } } \ No newline at end of file diff --git a/PARR.TemplateMatcher/Services/Implemetaions/TemplateDeactivator.cs b/PARR.TemplateMatcher/Services/Implementations/TemplateDeactivator.cs similarity index 95% rename from PARR.TemplateMatcher/Services/Implemetaions/TemplateDeactivator.cs rename to PARR.TemplateMatcher/Services/Implementations/TemplateDeactivator.cs index 008b6b86..a0efa2bc 100644 --- a/PARR.TemplateMatcher/Services/Implemetaions/TemplateDeactivator.cs +++ b/PARR.TemplateMatcher/Services/Implementations/TemplateDeactivator.cs @@ -3,7 +3,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using PARR.Core.Repositories.Interfaces; using PARR.Core.Repositories.Interfaces.Job; -using PARR.Domain.Common.Rabbit.Messages; +using PARR.Domain.Common.Rabbit.Messages.TemplateMatching; using PARR.Domain.Entities; using PARR.Domain.Entities.Base.History; using PARR.Domain.Entities.Job; @@ -100,7 +100,7 @@ internal class TemplateDeactivator : ITemplateDeactivator var expectedName = await namenormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName); - var updateRequest = new TemplateUpdaterMq + var updateRequest = new TemplateUpdaterMessage { TemplateId = template.Id, JobId = unusedJob.Id, @@ -108,13 +108,11 @@ internal class TemplateDeactivator : ITemplateDeactivator Name = expectedName, IsActiveTemplate = DefaultUnusedTemplateState, IsActiveSchedule = DefaultUnusedScheduleState, - //LastRun = template.LastRun, - //NextRun = template.NextRun, IsNew = false, Index = template.Index, StatusTypeId = TemplateStatusTypeEnum.Unused, Initiator = initiator, - UnitsInTemplate = new List() + UnitsInTemplate = new List() }; await sender.SendTemplateUpdateMessageAsync(updateRequest); diff --git a/PARR.TemplateMatcher/Services/Implemetaions/TemplateNameNormalizer.cs b/PARR.TemplateMatcher/Services/Implementations/TemplateNameNormalizer.cs similarity index 99% rename from PARR.TemplateMatcher/Services/Implemetaions/TemplateNameNormalizer.cs rename to PARR.TemplateMatcher/Services/Implementations/TemplateNameNormalizer.cs index 67bf594c..bebd747c 100644 --- a/PARR.TemplateMatcher/Services/Implemetaions/TemplateNameNormalizer.cs +++ b/PARR.TemplateMatcher/Services/Implementations/TemplateNameNormalizer.cs @@ -14,6 +14,7 @@ internal class TemplateNameNormalizer : ITemplateNameNormalizer this.shortcodesService = shortcodesService; } + public async Task GetNormalizedTemplateNameAsync(Template template, [CallerMemberName] string? caller = null) { var callerName = caller ?? "Unknown"; diff --git a/PARR.TemplateMatcher/Services/Implemetaions/TemplateReuser.cs b/PARR.TemplateMatcher/Services/Implementations/TemplateReuser.cs similarity index 91% rename from PARR.TemplateMatcher/Services/Implemetaions/TemplateReuser.cs rename to PARR.TemplateMatcher/Services/Implementations/TemplateReuser.cs index 8012e22e..92812e1c 100644 --- a/PARR.TemplateMatcher/Services/Implemetaions/TemplateReuser.cs +++ b/PARR.TemplateMatcher/Services/Implementations/TemplateReuser.cs @@ -20,6 +20,7 @@ internal class TemplateReuser : ITemplateReuser this.templateService = templateService; } + public async Task TryReuseOneUnusedTemplateAsync( Guid jobId, Guid unitId, @@ -42,18 +43,18 @@ internal class TemplateReuser : ITemplateReuser // Загружаем зарезервированный шаблон var template = await templateService.Get() .AsNoTracking() - .Include(t=>t.Unit) - .Include(t=>t.Job) - .ThenInclude(t=>t!.Tnk) + .Include(t => t.Unit) + .Include(t => t.Job) + .ThenInclude(t => t!.Tnk) .Include(t => t.Job) .ThenInclude(t => t!.Group) - .ThenInclude(t=>t!.GroupType) + .ThenInclude(t => t!.GroupType) .FirstOrDefaultAsync(t => t.Id == templateId); if (template == null) { logger.LogWarning("Зарезервированный шаблон {TemplateId} не найден при загрузке.", templateId); - continue; + continue; } logger.LogInformation( @@ -67,7 +68,7 @@ internal class TemplateReuser : ITemplateReuser logger.LogError(ex, "Ошибка при попытке захвата шаблона (попытка {Attempt}).", attempt); if (attempt == maxAttempts) - throw; + throw; // Небольшая задержка перед повтором await Task.Delay(Random.Shared.Next(10, 50)); diff --git a/PARR.TemplateMatcher/Services/Implemetaions/TemplateUpdaterMqSender.cs b/PARR.TemplateMatcher/Services/Implementations/TemplateUpdaterMqSender.cs similarity index 62% rename from PARR.TemplateMatcher/Services/Implemetaions/TemplateUpdaterMqSender.cs rename to PARR.TemplateMatcher/Services/Implementations/TemplateUpdaterMqSender.cs index 33bbb2e6..ba825d69 100644 --- a/PARR.TemplateMatcher/Services/Implemetaions/TemplateUpdaterMqSender.cs +++ b/PARR.TemplateMatcher/Services/Implementations/TemplateUpdaterMqSender.cs @@ -1,8 +1,6 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging; using PARR.Core.Common.Interfaces.RabbitServices; -using PARR.Core.Repositories.Interfaces; -using PARR.Domain.Common.Rabbit.Messages; +using PARR.Domain.Common.Rabbit.Messages.TemplateMatching; using PARR.TemplateMatcher.Services.Interfaces; using PARR.TemplateMatcher.Settings; @@ -13,35 +11,23 @@ internal class TemplateUpdaterMqSender : ITemplateUpdaterMqSender private readonly ILogger logger; private readonly IRabbitService mqService; private readonly MqSettings mqSettings; - private readonly ITemplateRepository templateService; public TemplateUpdaterMqSender( ILogger logger, IRabbitService mqService, - MqSettings mqSettings, - ITemplateRepository templateService + MqSettings mqSettings ) { this.logger = logger; this.mqService = mqService; this.mqSettings = mqSettings; - this.templateService = templateService; } - public async Task SendTemplateUpdateMessageAsync(TemplateUpdaterMq updateRequest) + public async Task SendTemplateUpdateMessageAsync(TemplateUpdaterMessage updateRequest) { logger.LogDebug("Отправка сообщения в очередь '{Queue}' для шаблона {TemplateId}", mqSettings.TemplateUpdater.QueueName, updateRequest.TemplateId); - - //var existingTemplateByName = await templateService.Get().AsNoTracking() - // .FirstOrDefaultAsync(t => EF.Functions.ILike(t.Name, updateRequest.Name) && updateRequest.TemplateId != t.Id); - //if (existingTemplateByName != null) - //{ - // logger.LogError("Шаблон с именем {TemplateName} уже существует в базе данных. Текущий Id:{TemplateId}", existingTemplateByName.Name, existingTemplateByName.Id); - // return; - //} - var result = await mqService.SendAsync(mqSettings.TemplateUpdater, new List { updateRequest }); if (result.IsSuccess) diff --git a/PARR.TemplateMatcher/Services/Implemetaions/UnitInTemplateConflictMapper.cs b/PARR.TemplateMatcher/Services/Implementations/UnitInTemplateConflictMapper.cs similarity index 99% rename from PARR.TemplateMatcher/Services/Implemetaions/UnitInTemplateConflictMapper.cs rename to PARR.TemplateMatcher/Services/Implementations/UnitInTemplateConflictMapper.cs index 99a285c8..55ea2605 100644 --- a/PARR.TemplateMatcher/Services/Implemetaions/UnitInTemplateConflictMapper.cs +++ b/PARR.TemplateMatcher/Services/Implementations/UnitInTemplateConflictMapper.cs @@ -11,6 +11,7 @@ internal class UnitInTemplateConflictMapper : IUnitInTemplateConflictMapper private readonly IUnitRepository unitRepository; private readonly IUnitKiiUnitRepository unitKiiUnitRepository; + public UnitInTemplateConflictMapper( ILogger logger, IUnitRepository unitRepository, @@ -21,6 +22,7 @@ internal class UnitInTemplateConflictMapper : IUnitInTemplateConflictMapper this.unitKiiUnitRepository = unitKiiUnitRepository; } + public async Task>> BuildMappingAsync( IEnumerable unitFilterResults, Job maxJob, diff --git a/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs b/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs deleted file mode 100644 index 6faa91ec..00000000 --- a/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs +++ /dev/null @@ -1,616 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Logging; -using PARR.Core.Common.Interfaces.RabbitServices; -using PARR.Core.Repositories.Interfaces; -using PARR.Core.Repositories.Interfaces.Job; -using PARR.Core.Repositories.Interfaces.Unit; -using PARR.Core.Services.MatchingStatusService; -using PARR.Core.Services.UnitFilterService; -using PARR.Domain.Cache.Models; -using PARR.Domain.Common.Rabbit.Messages; -using PARR.Domain.Entities; -using PARR.Domain.Entities.Base.History; -using PARR.Domain.Entities.Job; -using PARR.Domain.Enums; -using PARR.TemplateMatcher.Services.Interfaces; -using PARR.TemplateMatcher.Settings; - -namespace PARR.TemplateMatcher.Services.Implementations; - -internal class GroupedTemplateSynchronizer : ITemplateSynchronizer -{ - private const bool DefaultUsedTemplateState = false; - private const bool DefaultUsedScheduleState = false; - - private readonly ILogger logger; - private readonly IUnitFilterService unitFilterService; - private readonly IUnitInValueRepository unitInValueService; - private readonly IUnitRepository unitService; - private readonly MqSettings mqSettings; - private readonly IRabbitService mqService; - private readonly ITemplateRepository templateService; - private readonly IJobGroupRepository jobGroupService; - private readonly ITemplateReuser templateReuser; - private readonly IUnitRegionalEkPtkGroupRepository regionalEkPtkGroupService; - private readonly IUnitFieldRepository unitFieldService; - private readonly ITemplateDeactivator templateDeactivator; - private readonly ITemplateNameNormalizer templateNameNormalizer; - private readonly ITemplateUpdaterMqSender templateUpdaterMqSender; - private readonly IMatchingStatusService matchingStatusService; - private readonly IUnitInTemplateConflictMapper unitInTemplateConflictMapper; - - public GroupedTemplateSynchronizer( - ILogger logger, - IUnitFilterService unitFilterService, - IUnitInValueRepository unitInValueService, - IUnitRepository unitService, - MqSettings mqSettings, - IRabbitService mqService, - ITemplateRepository templateService, - IJobGroupRepository jobGroupService, - ITemplateReuser templateReuser, - IUnitRegionalEkPtkGroupRepository regionalEkPtkGroupService, - IUnitFieldRepository unitFieldService, - ITemplateDeactivator templateDeactivator, - ITemplateNameNormalizer templateNameNormalizer, - ITemplateUpdaterMqSender templateUpdaterMqSender, - IMatchingStatusService matchingStatusService, - IUnitInTemplateConflictMapper unitInTemplateConflictMapper - ) - { - this.logger = logger; - this.unitFilterService = unitFilterService; - this.unitInValueService = unitInValueService; - this.unitService = unitService; - this.mqSettings = mqSettings; - this.mqService = mqService; - this.templateService = templateService; - this.jobGroupService = jobGroupService; - this.templateReuser = templateReuser; - this.regionalEkPtkGroupService = regionalEkPtkGroupService; - this.unitFieldService = unitFieldService; - this.templateDeactivator = templateDeactivator; - this.templateNameNormalizer = templateNameNormalizer; - this.templateUpdaterMqSender = templateUpdaterMqSender; - this.matchingStatusService = matchingStatusService; - this.unitInTemplateConflictMapper = unitInTemplateConflictMapper; - } - - public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator) - { - logger.LogWarning("GroupedTemplateSynchronizer: SyncTemplatesForJob вызван для JobId {JobId}. Это не поддерживаемая операция.", jobId); - } - - public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator) - { - logger.LogDebug("Начало синхронизации шаблонов для JobGroup {JobGroupId}", jobGroupId); - - // === Проверка: уже запущена? === - var existingStatus = await matchingStatusService.GetStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup); - if (existingStatus.DetailsJobGroups?.Any() == true) - { - logger.LogWarning("Синхронизация для JobGroup {JobGroupId} уже запущена. Пропускаем.", jobGroupId); - return; - } - - // === Устанавливаем статус "в процессе" === - var initialStatus = new MatchingStatusItemDto - { - DateStart = DateTimeOffset.UtcNow, - Action = TemplateMatcherActionEnum.Sync, - Comment = "Начало синхронизации" - }; - await matchingStatusService.SetMatchingStatusAsync( - jobGroupId, - SyncTaskEntityTypeEnum.JobGroup, - new MatchingStatusItem { Data = initialStatus, Timestamp = DateTimeOffset.UtcNow, Source = nameof(GroupedTemplateSynchronizer) }, - TimeSpan.FromMinutes(35) - ); - - try - { - // 1. Получить JobGroup и связанные Job'ы - var jobGroup = await jobGroupService.Get() - .AsNoTracking() - .AsSingleQuery() - .Include(jg => jg.GroupType) - .Include(jg => jg.Jobs) - .ThenInclude(j => j.AutoControl) - .Include(jg => jg.Jobs) - .ThenInclude(j => j.UnitFilters) - .ThenInclude(uf => uf.RelationshipFilters) - .ThenInclude(rf => rf.UnitField) // Подгрузим поля для фильтрации - .Include(jg => jg.Jobs) - .ThenInclude(jg => jg.Tnk) - .FirstOrDefaultAsync(jg => jg.Id == jobGroupId); - - if (jobGroup == null || jobGroup.Jobs == null || !jobGroup.Jobs.Any()) - { - logger.LogWarning("JobGroup {JobGroupId} не найден или не содержит Job'ов.", jobGroupId); - await UpdateMatchingStatusAsync(jobGroupId, "JobGroup не найден или пуст"); - return; - } - - var jobsInGroup = jobGroup.Jobs.ToList(); - - // 2. Найти Job с максимальным MaxValueRelationships - var maxJob = jobsInGroup - .Where(j => j.MaxValueRelationships.HasValue) - .OrderByDescending(j => j.MaxValueRelationships) - .FirstOrDefault(); - - if (maxJob == null) - { - logger.LogWarning("В JobGroup {JobGroupId} не найдено Job с установленным MaxValueRelationships.", jobGroupId); - await UpdateMatchingStatusAsync(jobGroupId, "Не найден Job с MaxValueRelationships"); - return; - } - - logger.LogDebug("Используется Job {JobId} с максимальным MaxValueRelationships ({MaxValue}) для фильтрации.", maxJob.Id, maxJob.MaxValueRelationships); - - // 3. Использовать unitFilterService для получения отфильтрованных юнитов с их связями - // Это включает в себя все фильтры: UnitFilter, FieldFilter, RelationshipFilter, UmbrellaFilter - logger.LogDebug("Получение отфильтрованных юнитов с их связями через UnitFilterService для Job {JobId}.", maxJob.Id); - var unitFilterResults = await unitFilterService.GetUnitsByJobFilterAsync(maxJob.Id); - - if (unitFilterResults == null || !unitFilterResults.Any()) - { - logger.LogInformation("Для JobGroup {JobGroupId} фильтры не дали Unit'ов с подходящими связями.", jobGroupId); - await UpdateMatchingStatusAsync(jobGroupId, "Фильтры не дали Unit'ов с подходящими связями"); - await matchingStatusService.DeleteMatchingStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup); - logger.LogInformation("Синхронизация шаблонов завершена для JobGroup {JobGroupId}.", jobGroupId); - return; - } - - logger.LogDebug("Получено {Count} юнитов с подходящими связями через UnitFilterService.", unitFilterResults.Count()); - - // --- ФИЛЬТРАЦИЯ unitFilterResults (dto.Id) --- - - // 4. Фильтрация unitFilterResults по GroupingUnitFieldId (проверяем dto.Id) - if (!jobGroup.GroupingUnitFieldId.HasValue) - { - logger.LogError("JobGroup {JobGroupId} не имеет GroupingUnitFieldId, необходимого для группировки.", jobGroupId); - await UpdateMatchingStatusAsync(jobGroupId, "Отсутствует GroupingUnitFieldId"); - return; - } - - var groupingFieldId = jobGroup.GroupingUnitFieldId.Value; - logger.LogDebug("Фильтрация юнитов (UnitFilterResultDto.Id) по GroupingUnitFieldId (FieldId={FieldId}).", groupingFieldId); - - // Загрузим значения поля GroupingUnitFieldId для всех Id из unitFilterResults - var allUnitFilterResultIds = unitFilterResults.Select(dto => dto.Id).ToList(); - var groupingUnitValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(allUnitFilterResultIds, new HashSet { groupingFieldId }); - - // Найдем Id юнитов, у которых есть значение в GroupingUnitFieldId - var validUnitFilterResultIds = groupingUnitValues - .Where(uv => uv.Value != null && !string.IsNullOrEmpty(uv.Value.Value)) - .Select(uv => uv.UnitId) - .ToHashSet(); - - // Оставляем только те UnitFilterResultDto, чей Id проходит фильтр - var filteredUnitFilterResultsByGrouping = unitFilterResults - .Where(dto => validUnitFilterResultIds.Contains(dto.Id)) - .ToList(); - - logger.LogDebug("После фильтрации по GroupingUnitFieldId осталось {Count} UnitFilterResultDto.", filteredUnitFilterResultsByGrouping.Count); - - if (!filteredUnitFilterResultsByGrouping.Any()) - { - logger.LogInformation("После фильтрации по GroupingUnitFieldId в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId); - await UpdateMatchingStatusAsync(jobGroupId, "Нет юнитов после фильтрации по GroupingUnitFieldId"); - return; - } - - // 5. Фильтрация unitFilterResults по РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК (проверяем dto.Id) - var workGroupFieldId = await GetFieldIdByAihitNameAsync("РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК"); - - logger.LogDebug("Фильтрация юнитов (UnitFilterResultDto.Id) по полю 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' (FieldId={FieldId}).", workGroupFieldId); - - // Загрузим значения поля РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК для Id из filteredUnitFilterResultsByGrouping - var allFilteredUnitFilterResultIds = filteredUnitFilterResultsByGrouping.Select(dto => dto.Id).ToList(); - var workGroupValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(allFilteredUnitFilterResultIds, new HashSet { workGroupFieldId }); - - // Получим разрешенные значения из regionalEkPtkGroupService - var allowedValueIds = regionalEkPtkGroupService.Get() - .Select(g => g.FieldValueId) - .ToHashSet(); - logger.LogDebug("Найдено {Count} разрешенных значений для поля 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК'.", allowedValueIds.Count); - - // Найдем Id юнитов, у которых значение в РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК разрешено - var validUnitFilterResultIdsForWorkGroup = workGroupValues - .Where(uv => uv.Value != null && allowedValueIds.Contains(uv.Value.Id)) - .Select(uv => uv.UnitId) - .ToHashSet(); - - // Оставляем только те UnitFilterResultDto, чей Id проходит фильтр - var finalFilteredUnitFilterResults = filteredUnitFilterResultsByGrouping - .Where(dto => validUnitFilterResultIdsForWorkGroup.Contains(dto.Id)) - .ToList(); - - logger.LogDebug("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' осталось {Count} UnitFilterResultDto.", finalFilteredUnitFilterResults.Count); - - if (!finalFilteredUnitFilterResults.Any()) - { - logger.LogInformation("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId); - await UpdateMatchingStatusAsync(jobGroupId, "Нет юнитов после фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК'"); - return; - } - - logger.LogDebug("Построение обратного отображения: связанные юниты -> юниты, связанные с ними (после фильтрации)."); - var reverseMapping = await unitInTemplateConflictMapper.BuildMappingAsync(finalFilteredUnitFilterResults, maxJob); - - logger.LogDebug("Построено {Count} записей в обратном отображении.", reverseMapping.Count); - - if (!reverseMapping.Any()) - { - logger.LogInformation("После построения обратного отображения в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId); - await UpdateMatchingStatusAsync(jobGroupId, "Нет юнитов после построения обратного отображения"); - return; - } - - // 6. Внутренняя группировка по ОТВЕТСТВЕННЫЙ_ЗА_ЭК / РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК (для юнитов в UnitsInTemplate) - logger.LogDebug("Внутренняя группировка по полю (IsGroupByResponsible={IsGroupByResponsible}).", jobGroup.IsGroupByResponsible); - - var groupingFieldIdForInnerGrouping = jobGroup.IsGroupByResponsible == true - ? await GetFieldIdByAihitNameAsync("ОТВЕТСТВЕННЫЙ_ЗА_ЭК") - : await GetFieldIdByAihitNameAsync("РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК"); - - // Загрузим значения поля для *всех* юнитов, которые могут быть в UnitsInTemplate - // Это все юниты из всех списков в reverseMapping.Values - var allUnitsInTemplate = reverseMapping.Values.SelectMany(list => list).Distinct().ToList(); - var innerGroupingValues = await unitInValueService.GetByUnitIdsAndFieldIdsAsync(allUnitsInTemplate, new HashSet { groupingFieldIdForInnerGrouping }); - - // Создадим маппинг UnitId (из UnitsInTemplate) -> значение поля для внутренней группировки - var unitInTemplateToInnerGroupingValueMap = innerGroupingValues - .Where(uv => uv.Value != null && !string.IsNullOrEmpty(uv.Value.Value)) - .ToDictionary(uv => uv.UnitId, uv => uv.Value!.Value); - - // 7. Основной цикл обработки: итерируемся по potentialUnitIds (UnitId шаблонов) - var expectedTemplateKeys = new HashSet<(Guid JobId, Guid UnitId, int Index)>(); - - foreach (var kvpOuter in reverseMapping) - { - var potentialUnitId = kvpOuter.Key; - var unitsInTemplateForThisPotentialUnitId = kvpOuter.Value; - - logger.LogDebug("Обработка потенциального шаблона для UnitId {PotentialUnitId} с {Count} юнитами в UnitsInTemplate до внутренней группировки.", potentialUnitId, unitsInTemplateForThisPotentialUnitId.Count); - - // --- ВНУТРЕННЯЯ ГРУППИРОВКА --- - // Сгруппируем *юниты из UnitsInTemplate* для *этого* potentialUnitId по значению поля - var innerGroupedUnitsInTemplate = unitsInTemplateForThisPotentialUnitId - .GroupBy(unitId => unitInTemplateToInnerGroupingValueMap.GetValueOrDefault(unitId, "Нет данных")) - .OrderBy(g => g.Key, StringComparer.Ordinal) // <-- Сортировка по имени внутренней группы - .ToList(); - - logger.LogDebug("Для UnitId {PotentialUnitId}: сформировано {Count} внутренних групп UnitsInTemplate (отсортировано).", potentialUnitId, innerGroupedUnitsInTemplate.Count); - - // --- СОБЕРЕМ ВСЕ ИТОГОВЫЕ ПОДГРУППЫ ДЛЯ ЭТОГО potentialUnitId --- - var allFinalSubGroups = new List<(List UnitsInTemplateSubGroup, string InnerGroupName, int SubGroupSizeWithinInnerGroup)>(); // (UnitsInTemplate, Имя_внутренней_группы, размер_подгруппы_внутри_внутренней_группы) - - foreach (var innerGroup in innerGroupedUnitsInTemplate) // Теперь проходит в отсортированном порядке по groupingValueName - { - var groupingValueName = innerGroup.Key; - var unitsInTemplateInInnerGroup = innerGroup.ToList(); // Список юнитов (UnitId), связанных с potentialUnitId и имеющих одно и то же значение поля - - logger.LogDebug("Обработка внутренней группы '{GroupingValue}' для UnitId {PotentialUnitId} с {Count} юнитами.", groupingValueName, potentialUnitId, unitsInTemplateInInnerGroup.Count); - - // Разбиваем юниты из *этой* внутренней группы на подгруппы по maxJob.MaxValueRelationships - // Это нужно делать для каждой внутренней группы отдельно - int maxValueForSplitting = maxJob.MaxValueRelationships!.Value; - var unitsInTemplateSubGroups = unitsInTemplateInInnerGroup - .Select((id, index) => new { id, groupIndex = index / maxValueForSplitting }) - .GroupBy(x => x.groupIndex) - .Select(g => g.Select(x => x.id).ToList()) - .ToList(); - - logger.LogDebug("Внутренняя группа '{GroupingValue}' для UnitId {PotentialUnitId}: разбит на {GroupCount} подгрупп UnitsInTemplate.", groupingValueName, potentialUnitId, unitsInTemplateSubGroups.Count); - - // Добавим каждую *итоговую* подгруппу в общий список - foreach (var subGroup in unitsInTemplateSubGroups) - allFinalSubGroups.Add((subGroup, groupingValueName!, subGroup.Count)); - } - - int globalIndexForThisUnitId = 1; - foreach (var finalSubGroupData in allFinalSubGroups) - { - var unitsInTemplateSubGroup = finalSubGroupData.UnitsInTemplateSubGroup; - var subGroupSize = finalSubGroupData.SubGroupSizeWithinInnerGroup; - var originatingInnerGroupName = finalSubGroupData.InnerGroupName; - - logger.LogDebug("Обработка подгруппы {Index} внутренней группы '{GroupingValue}' для UnitId {PotentialUnitId}, размер UnitsInTemplate {Size}.", globalIndexForThisUnitId, originatingInnerGroupName, potentialUnitId, subGroupSize); - - Job? targetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob); - - // --- Добавляем УНИКАЛЬНЫЙ ключ в список ожидаемых --- - expectedTemplateKeys.Add((targetJob.Id, potentialUnitId, globalIndexForThisUnitId)); - - var existingTemplatesForRelationship = await templateService.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) - .Include(t => t.UnitsInTemplate) - .ThenInclude(uit => uit.Unit) - .Where(t => t.JobId == targetJob.Id && t.UnitId == potentialUnitId && t.Index == globalIndexForThisUnitId && t.StatusTypeId == TemplateStatusTypeEnum.Used) - .ToListAsync(); - - var existingTemplateForSubGroup = existingTemplatesForRelationship.FirstOrDefault(); - - if (existingTemplateForSubGroup != null) - { - // === 1. Получаем текущие и новые ID юнитов === - var currentUnitIds = existingTemplateForSubGroup.UnitsInTemplate.Select(uit => uit.UnitId).ToList(); - var proposedUnitIds = unitsInTemplateSubGroup.ToList(); - - // === 2. Сравниваем детерминированно с сортировкой по имени === - var allUnitIdsForSort = currentUnitIds.Concat(proposedUnitIds).Distinct().ToList(); - var unitNamesForSort = await unitService.Get() - .AsNoTracking() - .Where(u => allUnitIdsForSort.Contains(u.Id)) - .ToDictionaryAsync(u => u.Id, u => u.Name ?? u.Id.ToString()); - - var sortedCurrentUnitIds = currentUnitIds - .OrderBy(id => unitNamesForSort.GetValueOrDefault(id, id.ToString())) - .ToList(); - - var sortedProposedUnitIds = proposedUnitIds - .OrderBy(id => unitNamesForSort.GetValueOrDefault(id, id.ToString())) - .ToList(); - - bool unitsAreEqual = sortedCurrentUnitIds.SequenceEqual(sortedProposedUnitIds); - - if (unitsAreEqual) - { - logger.LogDebug("Шаблон {TemplateId} актуален по юнитам и их порядку (после сортировки).", existingTemplateForSubGroup.Id); - existingTemplateForSubGroup.UnitsInTemplate = sortedProposedUnitIds.Select(id => new UnitsInTemplate { UnitId = id }).ToList(); - var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(existingTemplateForSubGroup); - - if (!string.Equals(existingTemplateForSubGroup.Name, expectedName, StringComparison.OrdinalIgnoreCase)) - { - logger.LogDebug("Шаблон {TemplateId} требует обновления имени.", existingTemplateForSubGroup.Id); - var updateRequest = new TemplateUpdaterMq - { - TemplateId = existingTemplateForSubGroup.Id, - JobId = targetJob.Id, - UnitId = potentialUnitId, - Name = expectedName, - IsActiveTemplate = existingTemplateForSubGroup.IsActiveTemplate, - IsActiveSchedule = existingTemplateForSubGroup.IsActiveSchedule, - IsNew = false, - Index = globalIndexForThisUnitId, - StatusTypeId = TemplateStatusTypeEnum.Used, - Initiator = initiator, - UnitsInTemplate = sortedProposedUnitIds - }; - await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); - } - else - { - logger.LogDebug("Шаблон {TemplateId} полностью актуален.", existingTemplateForSubGroup.Id); - } - } - else - { - logger.LogDebug("Шаблон {TemplateId} требует обновления юнитов или их порядка (после сортировки).", existingTemplateForSubGroup.Id); - var newTargetJob = SelectTargetJob(jobsInGroup, unitsInTemplateSubGroup.Count, maxJob); // Размер - из подмножества - if (newTargetJob.Id != existingTemplateForSubGroup.JobId) - { - logger.LogDebug("Job для шаблона {TemplateId} изменился.", existingTemplateForSubGroup.Id); - } - await UpdateTemplateUnitsAsync(existingTemplateForSubGroup, sortedProposedUnitIds, newTargetJob, initiator, globalIndexForThisUnitId); - } - } - else - { - var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(targetJob.Id, potentialUnitId, initiator); - if (reusableTemplate != null) - { - logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, связанного юнита {RelationshipId}, Index {Index}.", reusableTemplate.Id, targetJob.Id, potentialUnitId, globalIndexForThisUnitId); - - var tempTemplateForName = new Template - { - Id = reusableTemplate.Id, - Name = reusableTemplate.Name, - JobId = targetJob.Id, - UnitId = potentialUnitId, - Index = globalIndexForThisUnitId, - Job = targetJob, - Unit = reusableTemplate.Unit, - UnitsInTemplate = unitsInTemplateSubGroup.Select(id => new UnitsInTemplate { UnitId = id }).ToList() - }; - - var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName); - - var updateRequest = new TemplateUpdaterMq - { - TemplateId = reusableTemplate.Id, - JobId = targetJob.Id, - UnitId = potentialUnitId, - Name = expectedName, - IsActiveTemplate = targetJob.AutoControl?.InitUsedTemplateState ?? DefaultUsedTemplateState, - IsActiveSchedule = targetJob.AutoControl?.InitUsedScheduleState ?? DefaultUsedScheduleState, - StatusTypeId = TemplateStatusTypeEnum.Used, - Initiator = initiator, - IsNew = true, - Index = globalIndexForThisUnitId, - UnitsInTemplate = unitsInTemplateSubGroup - }; - - await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); - } - else - { - logger.LogDebug("Создание нового шаблона для Job {JobId}, связанного юнита {RelationshipId}, Index {Index}, с {Count} юнитами.", targetJob.Id, potentialUnitId, globalIndexForThisUnitId, unitsInTemplateSubGroup.Count); - await CreateGroupedTemplateAsync(targetJob.Id, potentialUnitId, unitsInTemplateSubGroup, globalIndexForThisUnitId, initiator); - } - } - - globalIndexForThisUnitId++; // Увеличиваем индекс для следующей итоговой подгруппы - } - } - - // === Деактивация === - var allJobIdsInGroup = jobsInGroup.Select(j => j.Id).ToHashSet(); - var allExistingTemplatesInGroup = await templateService.Get() - .AsNoTracking() - .Include(t => t.Unit) - .Include(t => t.UnitsInTemplate) - .Where(t => allJobIdsInGroup.Contains(t.JobId) && - t.StatusTypeId == TemplateStatusTypeEnum.Used && - t.Job!.GroupId == jobGroupId) - .ToListAsync(); - - foreach (var existingTemplate in allExistingTemplatesInGroup) - { - var key = (existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index ?? -1); - if (!expectedTemplateKeys.Contains(key)) - { - logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, Relationship {UnitId}, Index {Index}).", - existingTemplate.Id, existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index); - await templateDeactivator.DeactivateTemplateAsync(existingTemplate, initiator); - } - } - - // === Успешное завершение === - await UpdateMatchingStatusAsync(jobGroupId, "Синхронизация завершена успешно"); - await matchingStatusService.DeleteMatchingStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup); - logger.LogInformation("Синхронизация шаблонов завершена для JobGroup {JobGroupId}.", jobGroupId); - } - catch (Exception ex) - { - logger.LogError(ex, "Ошибка при синхронизации JobGroup {JobGroupId}", jobGroupId); - await UpdateMatchingStatusAsync(jobGroupId, $"Ошибка: {ex.Message}"); - throw; - } - } - - public async Task UpdateTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator) - { - logger.LogWarning("GroupedTemplateSynchronizer: UpdateTemplatesForJob вызван для JobId {JobId}. Это не поддерживаемая операция. Используйте SyncTemplatesForJobGroup для обновления.", jobId); - return; - } - - private Job SelectTargetJob(List jobsInGroup, int subGroupSize, Job maxJob) - { - Job? targetJob = jobsInGroup - .Where(j => j.MaxValueRelationships.HasValue && j.MaxValueRelationships.Value == subGroupSize) - .FirstOrDefault(); - - if (targetJob == null) - { - targetJob = jobsInGroup - .Where(j => j.MaxValueRelationships.HasValue && j.MaxValueRelationships.Value >= subGroupSize) - .OrderBy(j => j.MaxValueRelationships!.Value) - .FirstOrDefault(); - } - - if (targetJob == null) - { - targetJob = maxJob; - logger.LogDebug("Для подгруппы размером {Size} не найден подходящий Job, используем maxJob {MaxJobId}.", subGroupSize, maxJob.Id); - } - else - { - logger.LogDebug("Для подгруппы размером {Size} выбран Job {TargetJobId} с MaxValueRelationships {MaxValue}.", subGroupSize, targetJob.Id, targetJob.MaxValueRelationships); - } - - return targetJob; - } - - private async Task UpdateTemplateUnitsAsync(Template template, List newUnitIds, Job targetJob, HistoryInitiator initiator, int newIndex) - { - template.StatusTypeId = TemplateStatusTypeEnum.Updating; - template.DateModified = DateTimeOffset.UtcNow; - if (!await templateService.CommitAsync(initiator)) - { - logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating для обновления юнитов.", template.Id); - return; - } - - var tempTemplateForName = new Template - { - Id = template.Id, - Name = template.Name, - JobId = targetJob.Id, - UnitId = template.UnitId, - Index = newIndex, - Job = targetJob, - Unit = template.Unit, - UnitsInTemplate = newUnitIds.Select(id => new UnitsInTemplate { UnitId = id }).ToList() - }; - - var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName); - - var updateRequest = new TemplateUpdaterMq - { - TemplateId = template.Id, - JobId = targetJob.Id, - UnitId = template.UnitId, - Name = expectedName, - IsActiveTemplate = template.IsActiveTemplate, - IsActiveSchedule = template.IsActiveSchedule, - IsNew = false, - Index = newIndex, - StatusTypeId = TemplateStatusTypeEnum.Used, - Initiator = initiator, - UnitsInTemplate = newUnitIds - }; - - await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); - } - - private async Task CreateGroupedTemplateAsync(Guid jobId, Guid relationshipUnitId, List unitIds, int index, HistoryInitiator initiator) - { - logger.LogInformation("Создание нового группового шаблона для Job {JobId}, связанного юнита {RelationshipUnitId}, Index {Index}, с {Count} юнитами.", jobId, relationshipUnitId, index, unitIds.Count); - - var mqRequest = new TemplateGeneratorMq - { - JobId = jobId, - UnitId = relationshipUnitId, - UnitsInTemplate = unitIds, - Index = index, - HistoryInitiator = initiator - }; - - var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new List { mqRequest }); - - if (!result.IsSuccess) - logger.LogError("Ошибка отправки команды создания группового шаблона для Job {JobId}, связанного юнита {RelationshipUnitId}, Index {Index}.", jobId, relationshipUnitId, index); - } - - - private async Task UpdateMatchingStatusAsync(Guid jobGroupId, string comment) - { - var status = new MatchingStatusItemDto - { - DateStart = DateTimeOffset.UtcNow, - Action = TemplateMatcherActionEnum.Sync, - Comment = comment - }; - - await matchingStatusService.SetMatchingStatusAsync( - jobGroupId, - SyncTaskEntityTypeEnum.JobGroup, - new MatchingStatusItem { Data = status, Timestamp = DateTimeOffset.UtcNow, Source = nameof(GroupedTemplateSynchronizer) }, - TimeSpan.FromMinutes(30) - ); - } - - - #region Вспомогательные методы - private async Task GetFieldIdByAihitNameAsync(string fieldName) - { - var field = await unitFieldService.GetByAihitNameAsync(fieldName); - if (field == null) - { - logger.LogError("Поле '{FieldName}' не найдено в справочнике полей.", fieldName); - throw new InvalidOperationException($"Поле '{fieldName}' не найдено в справочнике полей."); - } - return field.Id; - } - - #endregion -} \ No newline at end of file diff --git a/PARR.TemplateMatcher/Services/Interfaces/IGroupedTemplateBuilder.cs b/PARR.TemplateMatcher/Services/Interfaces/IGroupedTemplateBuilder.cs new file mode 100644 index 00000000..0333a167 --- /dev/null +++ b/PARR.TemplateMatcher/Services/Interfaces/IGroupedTemplateBuilder.cs @@ -0,0 +1,18 @@ +using PARR.Domain.Entities.Job; +using PARR.TemplateMatcher.Models; + +namespace PARR.TemplateMatcher.Services.Interfaces; + +public interface IGroupedTemplateBuilder +{ + /// + /// Строит структуру групп для создания групповых шаблонов. + /// Трансформирует связи в пары (UnitId, UnitFieldValueId), + /// выполняет внутреннюю группировку и разбиение на подгруппы. + /// + Task> BuildAsync( + Dictionary> initialReverseMapping, + JobGroup jobGroup, + Job maxJob, + CancellationToken ct = default); +} diff --git a/PARR.TemplateMatcher/Services/Interfaces/IGroupedTemplateProcessor.cs b/PARR.TemplateMatcher/Services/Interfaces/IGroupedTemplateProcessor.cs new file mode 100644 index 00000000..5d869eeb --- /dev/null +++ b/PARR.TemplateMatcher/Services/Interfaces/IGroupedTemplateProcessor.cs @@ -0,0 +1,22 @@ +using PARR.Domain.Entities.Base.History; +using PARR.Domain.Entities.Job; +using PARR.TemplateMatcher.Models; + +namespace PARR.TemplateMatcher.Services.Interfaces; + +public interface IGroupedTemplateProcessor +{ + /// + /// Обрабатывает построенные группы шаблонов: + /// 1. Ищет существующие шаблоны. + /// 2. Сравнивает состав юнитов. + /// 3. Обновляет, переиспользует или создает новые шаблоны. + /// 4. Возвращает набор ключей ожидаемых шаблонов для последующей деактивации лишних. + /// + Task> ProcessAsync( + List groups, + List jobsInGroup, + Job maxJob, + HistoryInitiator initiator, + CancellationToken ct = default); +} \ No newline at end of file diff --git a/PARR.TemplateMatcher/Services/Interfaces/IGroupedTemplateUnitFilter.cs b/PARR.TemplateMatcher/Services/Interfaces/IGroupedTemplateUnitFilter.cs new file mode 100644 index 00000000..11d83bd7 --- /dev/null +++ b/PARR.TemplateMatcher/Services/Interfaces/IGroupedTemplateUnitFilter.cs @@ -0,0 +1,18 @@ +using PARR.Core.Services.UnitFilterService.Models; +using PARR.Domain.Entities.Job; + +namespace PARR.TemplateMatcher.Services.Interfaces +{ + public interface IGroupedTemplateUnitFilter + { + /// + /// Применяет специфичные правила фильтрации для групповых шаблонов. + /// 1. Отбирает юниты, имеющие заполненное значение в GroupingUnitFieldId. + /// 2. Оставляет только юниты, значение которых в поле РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК разрешено. + /// + Task> FilterAsync( + IEnumerable initialUnits, + JobGroup jobGroup, + CancellationToken ct = default); + } +} diff --git a/PARR.TemplateMatcher/Services/Interfaces/ITemplateUpdaterMqSender.cs b/PARR.TemplateMatcher/Services/Interfaces/ITemplateUpdaterMqSender.cs index 45756145..479c305e 100644 --- a/PARR.TemplateMatcher/Services/Interfaces/ITemplateUpdaterMqSender.cs +++ b/PARR.TemplateMatcher/Services/Interfaces/ITemplateUpdaterMqSender.cs @@ -1,9 +1,9 @@ -using PARR.Domain.Common.Rabbit.Messages; +using PARR.Domain.Common.Rabbit.Messages.TemplateMatching; namespace PARR.TemplateMatcher.Services.Interfaces { public interface ITemplateUpdaterMqSender { - Task SendTemplateUpdateMessageAsync(TemplateUpdaterMq updateRequest); + Task SendTemplateUpdateMessageAsync(TemplateUpdaterMessage updateRequest); } } diff --git a/PARR.TemplateMatcher/TemplateMatcherInstaller.cs b/PARR.TemplateMatcher/TemplateMatcherInstaller.cs index 3c4bc89c..128dd6bb 100644 --- a/PARR.TemplateMatcher/TemplateMatcherInstaller.cs +++ b/PARR.TemplateMatcher/TemplateMatcherInstaller.cs @@ -35,6 +35,9 @@ namespace PARR.TemplateMatcher services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); services.AddTransient(); services.AddTransient(); } diff --git a/PARR.TemplateUpdater/Services/ITemplateUpdaterService.cs b/PARR.TemplateUpdater/Services/ITemplateUpdaterService.cs index 554c9a7c..bc0cc4cb 100644 --- a/PARR.TemplateUpdater/Services/ITemplateUpdaterService.cs +++ b/PARR.TemplateUpdater/Services/ITemplateUpdaterService.cs @@ -1,9 +1,9 @@ -using PARR.Domain.Common.Rabbit.Messages; +using PARR.Domain.Common.Rabbit.Messages.TemplateMatching; namespace PARR.TemplateUpdater.Services { internal interface ITemplateUpdaterService { - Task UpdateTemplateAsync(TemplateUpdaterMq query); + Task UpdateTemplateAsync(TemplateUpdaterMessage query); } } diff --git a/PARR.TemplateUpdater/Services/TemplateUpdaterService.cs b/PARR.TemplateUpdater/Services/TemplateUpdaterService.cs index 5959bc87..7f48ba52 100644 --- a/PARR.TemplateUpdater/Services/TemplateUpdaterService.cs +++ b/PARR.TemplateUpdater/Services/TemplateUpdaterService.cs @@ -5,8 +5,7 @@ using PARR.Core.Repositories.Interfaces; using PARR.Core.Repositories.Interfaces.Job; using PARR.Core.Repositories.Interfaces.Unit; using PARR.Core.Services.NextRunServices; -using PARR.Domain.Common.Rabbit.Messages; -using PARR.Domain.Entities; +using PARR.Domain.Common.Rabbit.Messages.TemplateMatching; using PARR.Domain.Entities.Job; using PARR.Domain.Enums; @@ -39,7 +38,7 @@ namespace PARR.TemplateUpdater.Services } - public async Task UpdateTemplateAsync(TemplateUpdaterMq query) + public async Task UpdateTemplateAsync(TemplateUpdaterMessage query) { var isValid = await IsValidAsync(query); if (!isValid) @@ -95,52 +94,55 @@ namespace PARR.TemplateUpdater.Services scheduleIsChanged = true; } - //просто обнволяем. не влияет ни на шаблон, ни на расписание - //template.LastRun = query.LastRun; template.Index = query.Index; template.StatusTypeId = query.StatusTypeId; // === Обработка изменения состава UnitsInTemplate === - var currentUnitIds = template.UnitsInTemplate.Select(u => u.UnitId).ToHashSet(); - var newUnitIds = query.UnitsInTemplate.ToHashSet(); + // Формируем HashSet пар (UnitId, UnitFieldValueId) для быстрого сравнения + var currentEntries = template.UnitsInTemplate + .Select(u => new UnitInTemplateMessage { UnitId = u.UnitId, UnitFieldValueId = u.UnitFieldValueId }) + .ToHashSet(); - if (!currentUnitIds.SetEquals(newUnitIds)) + var newEntries = query.UnitsInTemplate.ToHashSet(); + + if (!currentEntries.SetEquals(newEntries)) { - // Удаляем старые связи + // Удаляем связи, которых нет в новом наборе var toRemove = template.UnitsInTemplate - .Where(u => !newUnitIds.Contains(u.UnitId)) + .Where(u => !newEntries.Contains(new UnitInTemplateMessage { UnitId = u.UnitId, UnitFieldValueId = u.UnitFieldValueId })) .ToList(); foreach (var item in toRemove) template.UnitsInTemplate.Remove(item); // Добавляем новые связи - var toAdd = newUnitIds.Except(currentUnitIds); - foreach (var unitId in toAdd) + var toAdd = newEntries.Except(currentEntries); + foreach (var entry in toAdd) { template.UnitsInTemplate.Add(new UnitsInTemplate { TemplateId = template.Id, - UnitId = unitId, + UnitId = entry.UnitId, + UnitFieldValueId = entry.UnitFieldValueId, DateCreated = DateTimeOffset.UtcNow }); } - // Поскольку коллекция изменилась — шаблон считается изменённым templateIsChanged = true; } + // === Конец обработки UnitsInTemplate === - //Для правильного расчета shortcod'ов в БД должны быть сохранены данные UnitInTemplates, JobId, UnitId и т.д. + // Для правильного расчета shortcod'ов в БД должны быть сохранены данные UnitsInTemplate, JobId, UnitId и т.д. if (templateIsChanged && !await templateService.CommitAsync(query.Initiator)) { - logger.LogError("Не удалось сохранить изменения в БД. {QueryType}: {Query}", nameof(TemplateUpdaterMq), query.ToJson()); + 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); + logger.LogError("Ошибка при расчете нового nextRun (вернулся null) для шаблона {TemplateId}, {TemplateName}", template.Id, template.Name); return; } @@ -151,32 +153,23 @@ namespace PARR.TemplateUpdater.Services scheduleIsChanged = true; } - // === конец обработки UnitsInTemplate === - if (templateIsChanged) { - // ставим задачу на обновление шаблона var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.TemplateOrder, template); - //robotConfigurationService.ChangeTaskStatus(TaskStatusEnum.Updating, config); robotConfigurationService.SetUpdateTaskStatusIfAllow(config); - logger.LogDebug("Для шаблона {TemplateId} устанавливаю статус {Status}", template.Id, RobotStatusEnum.Wait.ToString()); } if (scheduleIsChanged) { - // ставим задачу на обновление расписания var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.ScheduleOrder, template); - //robotConfigurationService.ChangeTaskStatus(TaskStatusEnum.Updating, config); robotConfigurationService.SetUpdateTaskStatusIfAllow(config); - logger.LogDebug("Для расписания {TemplateId} устанавливаю статус {Status}", template.Id, RobotStatusEnum.Wait.ToString()); } - if (!await templateService.CommitAsync(query.Initiator)) { - logger.LogError("Не удалось сохранить изменения в БД. {QueryType}: {Query}", nameof(TemplateUpdaterMq), query.ToJson()); + logger.LogError("Не удалось сохранить изменения в БД. {QueryType}: {Query}", nameof(TemplateUpdaterMessage), query.ToJson()); return; } @@ -189,7 +182,7 @@ namespace PARR.TemplateUpdater.Services /// /// /// - private async Task IsValidAsync(TemplateUpdaterMq query) + private async Task IsValidAsync(TemplateUpdaterMessage query) { var template = await templateService.Get().AsNoTracking() .Include(t => t.RobotConfigurations) diff --git a/PARR.TemplateUpdater/TemplateUpdater.cs b/PARR.TemplateUpdater/TemplateUpdater.cs index 2b77afc4..3c1fbd28 100644 --- a/PARR.TemplateUpdater/TemplateUpdater.cs +++ b/PARR.TemplateUpdater/TemplateUpdater.cs @@ -2,7 +2,7 @@ using Microsoft.Extensions.Logging; using PARR.Core.Common.Interfaces; using PARR.Core.Common.Interfaces.RabbitServices; -using PARR.Domain.Common.Rabbit.Messages; +using PARR.Domain.Common.Rabbit.Messages.TemplateMatching; using PARR.TemplateUpdater.Services; using PARR.TemplateUpdater.Settings; @@ -51,7 +51,7 @@ namespace PARR.TemplateUpdater { logger.LogInformation("Получили запрос: {msg}", msg); - var query = transformService.GetModelFromJson(msg); + var query = transformService.GetModelFromJson(msg); if (query == null) return;