using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using PARR.Core.Repositories.Interfaces.Unit; using PARR.Core.Services.Shortcodes; using PARR.Domain.Constants; using PARR.Domain.Entities; using PARR.Domain.Entities.JobEntities; using PARR.Domain.Entities.JobGroupEntities; using PARR.TemplateMatcher.Models; namespace PARR.TemplateMatcher.Services.GroupedSync; internal class GroupedTemplateBuilder : IGroupedTemplateBuilder { private readonly ILogger logger; private readonly IUnitInValueRepository unitInValueRepository; private readonly IUnitFieldRepository unitFieldRepository; private readonly IShortcodesService shortcodesService; public GroupedTemplateBuilder( ILogger logger, IUnitInValueRepository unitInValueRepository, IUnitFieldRepository unitFieldRepository, IShortcodesService shortcodesService ) { this.logger = logger; this.unitInValueRepository = unitInValueRepository; this.unitFieldRepository = unitFieldRepository; this.shortcodesService = shortcodesService; } 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. Определяем стратегию внутренней группировки // Если IsGroupByResponsible != true, используем WorkGroupMask через ShortcodesService bool useWorkGroupMask = jobGroup.IsGroupByResponsible != true; logger.LogDebug("Стратегия внутренней группировки: {Strategy}", useWorkGroupMask ? "WorkGroupMask" : "Поле 'Ответственный за ЭК'"); // 2. Собираем все исходные UnitId var allSourceUnitIds = initialReverseMapping.Values.SelectMany(ids => ids).Distinct().ToList(); // 3. Загружаем UnitInValue для трансформации по полю группировки из настроек JobGroup var groupingFieldId = jobGroup.GroupingUnitFieldId!.Value; var relevantUnitInValues = await unitInValueRepository.Get() .AsNoTracking() .Where(uiv => allSourceUnitIds.Contains(uiv.UnitId) && uiv.FieldId == groupingFieldId) .OrderBy(uiv => uiv.UnitId) .ThenBy(uiv => uiv.ValueId) .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.OrderBy(k => k.Key)) { 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()) .OrderBy(e => e.UnitId) .ThenBy(e => e.UnitFieldValueId) .ToList(); if (uniqueEntries.Any()) reverseMapping[potentialUnitId] = uniqueEntries; } if (!reverseMapping.Any()) return new List(); // 5. Определяем значения для внутренней группировки Dictionary unitIdToGroupingValueMap; if (useWorkGroupMask) { // Используем ShortcodesService для получения финальных значений WorkGroupMask unitIdToGroupingValueMap = new Dictionary(); foreach (var potentialUnitId in reverseMapping.Keys) { var relatedUnitIds = reverseMapping[potentialUnitId].Select(e => e.UnitId).Distinct().ToList(); foreach (var relatedUnitId in relatedUnitIds) { // Создаем временный Template для применения шорткодов 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)); unitIdToGroupingValueMap[relatedUnitId] = workGroupValue; } } } else { // Логика разделения по ответственному за ЭК через служебный код поля var innerGroupingField = await unitFieldRepository.GetByCodeAsync(UnitFieldCodes.Responsible, ct) ?? throw new InvalidOperationException($"Поле с кодом '{UnitFieldCodes.Responsible}' не найдено в справочнике UnitField."); var innerGroupingFieldId = innerGroupingField.Id; 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); unitIdToGroupingValueMap = innerGroupingValues .Where(uv => uv.Value != null && uv.Value.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.OrderBy(k => k.Key)) { var potentialUnitId = kvp.Key; var unitsInTemplateForThisPotentialUnitId = kvp.Value; var innerGroupedUnits = unitsInTemplateForThisPotentialUnitId .GroupBy(entry => unitIdToGroupingValueMap.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) { var sortedEntries = subGroupEntries .OrderBy(e => e.UnitId) .ThenBy(e => e.UnitFieldValueId) .ToList(); subGroups.Add(new GroupedTemplateSubGroup( Entries: sortedEntries, InnerGroupName: innerGroupName, GlobalIndex: globalIndex )); globalIndex++; } } if (subGroups.Any()) { templateGroups.Add(new GroupedTemplateGroup( PotentialUnitId: potentialUnitId, SubGroups: subGroups )); } } logger.LogDebug("Построено {Count} групп шаблонов.", templateGroups.Count); return templateGroups; } }