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; } }