diff --git a/PARR.DAL/DomainServices/Shortcodes/ShortcodesService.cs b/PARR.DAL/DomainServices/Shortcodes/ShortcodesService.cs index 87e9b8dd..2ecd6da4 100644 --- a/PARR.DAL/DomainServices/Shortcodes/ShortcodesService.cs +++ b/PARR.DAL/DomainServices/Shortcodes/ShortcodesService.cs @@ -136,7 +136,7 @@ namespace PARR.DAL.DomainServices.Shortcodes resultName = await ReplaceLettersShortcodesAsync(template.UnitId, resultName, lettersShortcodes); } - // 2.7. %ГР_ПОЛЕ-ПН% — нумерованный список UnitsInTemplate с GroupingUnitFieldId + // 2.7. %ГР_ПОЛЕ-ПН% — нумерованный список UnitsInTemplate с GroupingUnitFieldId, отсортированный по имени юнита if (shortcodesInMask.Any(m => string.Equals(m.Value, "%ГР_ПОЛЕ-ПН%", StringComparison.OrdinalIgnoreCase))) { var unitsInTemplate = template.UnitsInTemplate; @@ -154,6 +154,9 @@ namespace PARR.DAL.DomainServices.Shortcodes .Where(u => unitIds.Contains(u.Id)) .ToListAsync(); + // Словарь: UnitId -> Unit (для быстрого поиска) + var unitDict = units.ToDictionary(u => u.Id, u => u); + var groupingFieldId = job.Group?.GroupingUnitFieldId; Dictionary valuesByUnit = new(); @@ -161,7 +164,6 @@ namespace PARR.DAL.DomainServices.Shortcodes { var fieldValues = await unitInValueService.Get() .AsNoTracking() - //.AsSplitQuery() .Include(uv => uv.Value) .Where(uv => uv.FieldId == groupingFieldId.Value && @@ -179,10 +181,15 @@ namespace PARR.DAL.DomainServices.Shortcodes ); } - var lines = unitsInTemplate + // Сортируем UnitsInTemplate по имени юнита + var sortedUnitsInTemplate = unitsInTemplate + .OrderBy(uit => unitDict.TryGetValue(uit.UnitId, out var unit) ? unit.Name : $"(UnitId={uit.UnitId})") + .ToList(); + + var lines = sortedUnitsInTemplate .Select((uit, indexInList) => { - var unitInList = units.FirstOrDefault(u => u.Id == uit.UnitId); + var unitInList = unitDict.TryGetValue(uit.UnitId, out var unit) ? unit : null; var unitName = unitInList?.Name ?? $"(UnitId={uit.UnitId})"; var valuesStr = valuesByUnit.TryGetValue(uit.UnitId, out var vals) ? vals : ""; return $"{indexInList + 1}. {unitName} ({valuesStr})"; diff --git a/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs b/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs index 1dbdb404..3188a6e5 100644 --- a/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs +++ b/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs @@ -15,7 +15,6 @@ using PARR.DAL.Services.Interfaces.Unit; using PARR.DAL.TransformServices; using PARR.TemplateMatcher.Services.Interfaces; using PARR.TemplateMatcher.Settings; -using System.Configuration; using System.Text.Json; namespace PARR.TemplateMatcher.Services.Implementations; @@ -502,20 +501,31 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer foreach (var kvp in groupedRelationships) { var relationshipUnitId = kvp.Key; // Связанный юнит (не из unitIdsWithValidGroupingFieldSet) - var childUnitIds = kvp.Value; // Юниты из unitIdsWithValidGroupingFieldSet, связанные с regionalUnitId + var childUnitIds = kvp.Value; // Юниты из unitIdsWithValidGroupingFieldSet, связанные с relationshipUnitId if (childUnitIds.Count == 0) continue; + #if DEBUG // Отладка: проверить, есть ли ВРТ-AOS-05-ДВС в childUnitIds - var childUnitNames = childUnitIds.Select(id => unitService.Get().AsNoTracking().Where(u => u.Id == id).Select(u => u.Name).FirstOrDefaultAsync().GetAwaiter().GetResult() ?? id.ToString()).ToList(); - if (childUnitNames.Contains("ВРТ-AOS-05-ДВС")) + var childUnitNamesForDebug = childUnitIds.Select(id => unitService.Get().AsNoTracking().Where(u => u.Id == id).Select(u => u.Name).FirstOrDefaultAsync().GetAwaiter().GetResult() ?? id.ToString()).ToList(); + if (childUnitNamesForDebug.Contains("ВРТ-AOS-05-ДВС")) { var relationshipUnitName = await unitService.Get().AsNoTracking().Where(u => u.Id == relationshipUnitId).Select(u => u.Name).FirstOrDefaultAsync(); - logger.LogDebug("Группа с ключом {Key} (название: {Name}) содержит юнит 'ВРТ-AOS-05-ДВС' в childUnitIds: [{ChildUnitNames}]", relationshipUnitId, relationshipUnitName, string.Join(", ", childUnitNames)); + logger.LogDebug("Группа с ключом {Key} (название: {Name}) содержит юнит 'ВРТ-AOS-05-ДВС' в childUnitIds: [{ChildUnitNames}]", relationshipUnitId, relationshipUnitName, string.Join(", ", childUnitNamesForDebug)); } #endif - logger.LogDebug("Обработка связанного юнита {RegionalUnitId} с {Count} юнитами из списка.", relationshipUnitId, childUnitIds.Count); + logger.LogDebug("Обработка связанного юнита {RelationshipUnitId} с {Count} юнитами из списка.", relationshipUnitId, childUnitIds.Count); + + // --- СОРТИРОВКА childUnitIds ПО ИМЕНИ --- + var childUnitNameMap = await unitService.Get() + .AsNoTracking() + .Where(u => childUnitIds.Contains(u.Id)) + .ToDictionaryAsync(u => u.Id, u => u.Name); + + var sortedChildUnitIds = childUnitIds + .OrderBy(id => childUnitNameMap.GetValueOrDefault(id, id.ToString())) + .ToList(); // Применяем ограничение MaxValueRelationships maxJob if (!maxJob.MaxValueRelationships.HasValue) @@ -524,13 +534,13 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer return; } int maxValueForSplitting = maxJob.MaxValueRelationships.Value; - var childUnitGroups = childUnitIds + var childUnitGroups = sortedChildUnitIds .Select((id, index) => new { id, groupIndex = index / maxValueForSplitting }) .GroupBy(x => x.groupIndex) .Select(g => g.Select(x => x.id).ToList()) .ToList(); - logger.LogDebug("Связанный юнит {RegionalUnitId}: разбит на {GroupCount} подгрупп.", relationshipUnitId, childUnitGroups.Count); + logger.LogDebug("Связанный юнит {RelationshipUnitId}: разбит на {GroupCount} подгрупп (после сортировки по имени).", relationshipUnitId, childUnitGroups.Count); // Для каждой подгруппы: for (int i = 0; i < childUnitGroups.Count; i++) @@ -538,7 +548,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer var subGroup = childUnitGroups[i]; var subGroupSize = subGroup.Count; - logger.LogDebug("Обработка подгруппы {Index} связанного юнита {RegionalUnitId}, размер {Size}.", i, relationshipUnitId, subGroupSize); + logger.LogDebug("Обработка подгруппы {Index} связанного юнита {RelationshipUnitId}, размер {Size}.", i, relationshipUnitId, subGroupSize); // Выбираем targetJob для подгруппы Job? targetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob); @@ -560,7 +570,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer if (existingUnitIds.SetEquals(newUnitIds)) { - logger.LogDebug("Шаблон {TemplateId} (Job {JobId}, Regional {RegionalId}, Index {Index}) актуален по юнитам.", existingTemplateForSubGroup.Id, targetJob.Id, relationshipUnitId, i); + logger.LogDebug("Шаблон {TemplateId} (Job {JobId}, Relationship {RelationshipId}, Index {Index}) актуален по юнитам.", existingTemplateForSubGroup.Id, targetJob.Id, relationshipUnitId, i); // Проверить, изменилось ли имя шаблона (например, из-за %МАКС:...% или %ТНК-КРАТКО%) var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(targetJob, relationshipUnitId, i, subGroup); @@ -595,7 +605,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer } else { - logger.LogDebug("Шаблон {TemplateId} (Job {JobId}, Regional {RegionalId}, Index {Index}) требует обновления юнитов.", existingTemplateForSubGroup.Id, targetJob.Id, relationshipUnitId, i); + logger.LogDebug("Шаблон {TemplateId} (Job {JobId}, Relationship {RelationshipId}, Index {Index}) требует обновления юнитов.", existingTemplateForSubGroup.Id, targetJob.Id, relationshipUnitId, i); // Пересчитать targetJob для нового размера subGroup var newTargetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob); @@ -615,7 +625,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer if (reusableTemplate != null) { - logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, связанного юнита {RegionalId}, Index {Index}.", reusableTemplate.Id, targetJob.Id, relationshipUnitId, i); + logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, связанного юнита {RelationshipId}, Index {Index}.", reusableTemplate.Id, targetJob.Id, relationshipUnitId, i); var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(targetJob, relationshipUnitId, i, subGroup); var nextRun = await GetNextRunAsync(targetJob); @@ -639,21 +649,32 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer } else { - logger.LogDebug("Создание нового шаблона для Job {JobId}, связанного юнита {RegionalId}, Index {Index}, с {Count} юнитами.", targetJob.Id, relationshipUnitId, i, subGroup.Count); + logger.LogDebug("Создание нового шаблона для Job {JobId}, связанного юнита {RelationshipUnitId}, Index {Index}, с {Count} юнитами.", targetJob.Id, relationshipUnitId, i, subGroup.Count); await CreateGroupedTemplateAsync(targetJob.Id, relationshipUnitId, subGroup, i, initiator); } } } } - // 10. Деактивировать шаблоны, которые больше не соответствуют ни одной подгруппе (логика без изменений) + // 10. Деактивировать шаблоны, которые больше не соответствуют ни одной подгруппе var expectedTemplateKeys = new HashSet<(Guid JobId, Guid UnitId, int Index)>(); foreach (var kvp in groupedRelationships) { - var regionalUnitId = kvp.Key; + var relationshipUnitId = kvp.Key; var childUnitIds = kvp.Value; + + // --- СОРТИРОВКА ПЕРЕД ФОРМИРОВАНИЕМ expectedTemplateKeys --- + var childUnitNameMapForDeactivate = await unitService.Get() + .AsNoTracking() + .Where(u => childUnitIds.Contains(u.Id)) + .ToDictionaryAsync(u => u.Id, u => u.Name); + + var sortedChildUnitIdsForDeactivate = childUnitIds + .OrderBy(id => childUnitNameMapForDeactivate.GetValueOrDefault(id, id.ToString())) + .ToList(); + int maxValueForSplitting = maxJob.MaxValueRelationships!.Value; - var childUnitGroups = childUnitIds + var childUnitGroups = sortedChildUnitIdsForDeactivate .Select((id, index) => new { id, groupIndex = index / maxValueForSplitting }) .GroupBy(x => x.groupIndex) .Select(g => g.Select(x => x.id).ToList()) @@ -666,17 +687,17 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer Job? targetJobForExpectedKey = SelectTargetJob(jobsInGroup, subGroupSize, maxJob); - expectedTemplateKeys.Add((targetJobForExpectedKey.Id, regionalUnitId, i)); + expectedTemplateKeys.Add((targetJobForExpectedKey.Id, relationshipUnitId, i)); } } - var allRegionalUnitIds = groupedRelationships.Keys.ToHashSet(); + var allRelationshipUnitIds = groupedRelationships.Keys.ToHashSet(); var allJobIdsInGroup = jobsInGroup.Select(j => j.Id).ToHashSet(); var allExistingTemplatesInGroup = await templateService.Get() .AsNoTracking() .Include(t => t.UnitsInTemplate) - .Where(t => allJobIdsInGroup.Contains(t.JobId) && allRegionalUnitIds.Contains(t.UnitId)) + .Where(t => allJobIdsInGroup.Contains(t.JobId) && allRelationshipUnitIds.Contains(t.UnitId)) .ToListAsync(); foreach (var existingTemplate in allExistingTemplatesInGroup) @@ -767,14 +788,14 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); } - private async Task CreateGroupedTemplateAsync(Guid jobId, Guid regionalUnitId, List unitIds, int index, HistoryInitiator initiator) + private async Task CreateGroupedTemplateAsync(Guid jobId, Guid relationshipUnitId, List unitIds, int index, HistoryInitiator initiator) { - logger.LogInformation("Создание нового группового шаблона для Job {JobId}, связанного юнита {RegionalUnitId}, Index {Index}, с {Count} юнитами.", jobId, regionalUnitId, index, unitIds.Count); + logger.LogInformation("Создание нового группового шаблона для Job {JobId}, связанного юнита {RelationshipUnitId}, Index {Index}, с {Count} юнитами.", jobId, relationshipUnitId, index, unitIds.Count); var mqRequest = new TemplateGeneratorMq { JobId = jobId, - UnitId = regionalUnitId, // UnitId шаблона + UnitId = relationshipUnitId, // UnitId шаблона UnitsInTemplate = unitIds, // Юниты для UnitsInTemplate Index = index, // Индекс шаблона HistoryInitiator = initiator @@ -784,7 +805,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg }); if (!result.IsSuccess) - logger.LogError("Ошибка отправки команды создания группового шаблона для Job {JobId}, связанного юнита {RegionalUnitId}, Index {Index}.", jobId, regionalUnitId, index); + logger.LogError("Ошибка отправки команды создания группового шаблона для Job {JobId}, связанного юнита {RelationshipUnitId}, Index {Index}.", jobId, relationshipUnitId, index); } private async Task GetNextRunAsync(Job targetJob, DateTimeOffset? currentNextRun = null)