diff --git a/PARR.TemplateMatcher/Services/GroupedSync/GroupedTemplateBuilder.cs b/PARR.TemplateMatcher/Services/GroupedSync/GroupedTemplateBuilder.cs index cd40b171..4d7f8918 100644 --- a/PARR.TemplateMatcher/Services/GroupedSync/GroupedTemplateBuilder.cs +++ b/PARR.TemplateMatcher/Services/GroupedSync/GroupedTemplateBuilder.cs @@ -103,33 +103,29 @@ internal class GroupedTemplateBuilder : IGroupedTemplateBuilder // 5. Определяем значения для внутренней группировки Dictionary unitIdToGroupingValueMap; + // Загружаем список разрешённых региональных ПТК (если не нужно пропускать фильтрацию) + bool skipRegionalFilter = maxJob.WorkGroupMask.Contains("_в_ЗО_РГ", StringComparison.OrdinalIgnoreCase); + HashSet? allowedWorkGroupSet = null; + + if (!skipRegionalFilter) + { + var allowedWorkGroupValues = await regionalEkPtkGroupRepository.Get() + .AsNoTracking() + .Include(g => g.FieldValue) + .Where(g => g.FieldValue != null && g.FieldValue.Value != null) + .Select(g => g.FieldValue!.Value!) + .ToListAsync(ct); + + allowedWorkGroupSet = new HashSet(allowedWorkGroupValues, StringComparer.OrdinalIgnoreCase); + logger.LogDebug("Загружено {Count} разрешённых значений рабочих групп из RegionalEkPtkGroup.", allowedWorkGroupSet.Count); + } + else + { + logger.LogDebug("Маска содержит тег с '_в_ЗО_РГ'. Фильтрация по списку рабочих групп ПТК пропущена."); + } + if (useWorkGroupMask) { - // Определяем, нужно ли выполнять фильтрацию по списку рабочих групп ПТК. - // Если маска содержит тег с "_в_ЗО_РГ", фильтрация не выполняется. - var skipRegionalFilter = maxJob.WorkGroupMask.Contains("_в_ЗО_РГ", StringComparison.OrdinalIgnoreCase); - - HashSet? allowedWorkGroupSet = null; - - if (!skipRegionalFilter) - { - // Загружаем разрешённые значения из regionalEkPtkGroupRepository - var allowedWorkGroupValues = await regionalEkPtkGroupRepository.Get() - .AsNoTracking() - .Include(g => g.FieldValue) - .Where(g => g.FieldValue != null && g.FieldValue.Value != null) - .Select(g => g.FieldValue!.Value!) - .ToListAsync(ct); - - allowedWorkGroupSet = new HashSet(allowedWorkGroupValues, StringComparer.OrdinalIgnoreCase); - - logger.LogDebug("Загружено {Count} разрешённых значений рабочих групп из RegionalEkPtkGroup.", allowedWorkGroupSet.Count); - } - else - { - logger.LogDebug("Маска содержит тег с '_в_ЗО_РГ'. Фильтрация по списку рабочих групп ПТК пропущена."); - } - // Применяем WorkGroupMask для каждого юнита отдельно unitIdToGroupingValueMap = new Dictionary(); @@ -169,16 +165,16 @@ internal class GroupedTemplateBuilder : IGroupedTemplateBuilder continue; } - // Фильтрация по списку рабочих групп ПТК выполняется только если не используется тег с "_в_ЗО_РГ" + // Фильтрация по списку рабочих групп ПТК if (!skipRegionalFilter && !allowedWorkGroupSet!.Contains(workGroupValue)) { - logger.LogDebug( // Изменено с LogDebug на LogInformation для тестирования + logger.LogDebug( "[FILTER] Юнит {UnitId} ИСКЛЮЧЁН: рабочая группа '{WorkGroup}' не найдена в списке разрешённых ({AllowedCount} значений).", relatedUnitId, workGroupValue, allowedWorkGroupSet!.Count); continue; } - logger.LogDebug( // Добавляем лог для успешного прохождения + logger.LogDebug( "[FILTER] Юнит {UnitId} ПРОШЁЛ: рабочая группа '{WorkGroup}' для PotentialUnit {PotentialUnitId}.", relatedUnitId, workGroupValue, potentialUnitId); @@ -201,9 +197,79 @@ internal class GroupedTemplateBuilder : IGroupedTemplateBuilder new HashSet { innerGroupingFieldId }, ct); - unitIdToGroupingValueMap = innerGroupingValues - .Where(uv => uv.Value != null && uv.Value.Value != null && !string.IsNullOrEmpty(uv.Value.Value)) + // Собираем значения поля "Ответственный" для группировки + var responsibleValueByUnit = innerGroupingValues + .Where(uv => uv.Value != null && !string.IsNullOrEmpty(uv.Value.Value)) .ToDictionary(uv => uv.UnitId, uv => uv.Value!.Value!); + + unitIdToGroupingValueMap = new Dictionary(); + + // Для каждого юнита дополнительно применяем WorkGroupMask и фильтруем по RegionalEkPtkGroup + foreach (var potentialUnitId in reverseMapping.Keys) + { + var relatedUnitIds = reverseMapping[potentialUnitId] + .Select(e => e.UnitId) + .Distinct() + .ToList(); + + foreach (var relatedUnitId in relatedUnitIds) + { + // Юнит исключается, если у него нет значения поля "Ответственный" + if (!responsibleValueByUnit.TryGetValue(relatedUnitId, out var responsibleValue)) + { + logger.LogDebug( + "Юнит {UnitId} исключён: отсутствует значение поля 'Ответственный за ЭК'.", + relatedUnitId); + continue; + } + + // Фильтрация по списку региональных ПТК выполняется только если не используется тег с "_в_ЗО_РГ" + if (!skipRegionalFilter) + { + // Формируем временный шаблон с одним связанным юнитом для применения WorkGroupMask + var tempTemplate = new Template + { + Id = Guid.NewGuid(), + Name = "temp", + JobId = maxJob.Id, + UnitId = potentialUnitId, + Job = maxJob, + UnitsInTemplate = new List + { + new UnitsInTemplate { UnitId = relatedUnitId, UnitFieldValueId = Guid.Empty } + } + }; + + var workGroupValue = await shortcodesService.ApplyShortcodesAsync( + maxJob.WorkGroupMask, + tempTemplate, + nameof(GroupedTemplateBuilder)); + + if (string.IsNullOrEmpty(workGroupValue)) + { + logger.LogDebug( + "Юнит {UnitId} исключён: WorkGroupMask вернула пустую строку.", + relatedUnitId); + continue; + } + + if (!allowedWorkGroupSet!.Contains(workGroupValue)) + { + logger.LogDebug( + "[FILTER] Юнит {UnitId} ИСКЛЮЧЁН: рабочая группа '{WorkGroup}' не найдена в списке разрешённых ({AllowedCount} значений).", + relatedUnitId, workGroupValue, allowedWorkGroupSet!.Count); + continue; + } + + logger.LogDebug( + "[FILTER] Юнит {UnitId} ПРОШЁЛ: рабочая группа '{WorkGroup}' для PotentialUnit {PotentialUnitId}.", + relatedUnitId, workGroupValue, potentialUnitId); + } + + // В маппинг группировки пишем значение поля "Ответственный" — по нему идёт группировка в этапе 6 + unitIdToGroupingValueMap[relatedUnitId] = responsibleValue; + } + } } // 6. Формируем итоговую структуру