feat(dal,templateMatcher): добавлена сортировка связанных юнитов группированного типа групп работ

This commit is contained in:
Mikhail Kuznetsov
2026-01-12 15:28:28 +10:00
parent ba465f4b26
commit 0925c97e6b
2 changed files with 55 additions and 27 deletions

View File

@@ -136,7 +136,7 @@ namespace PARR.DAL.DomainServices.Shortcodes
resultName = await ReplaceLettersShortcodesAsync(template.UnitId, resultName, lettersShortcodes); 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))) if (shortcodesInMask.Any(m => string.Equals(m.Value, "%ГРОЛЕ-ПН%", StringComparison.OrdinalIgnoreCase)))
{ {
var unitsInTemplate = template.UnitsInTemplate; var unitsInTemplate = template.UnitsInTemplate;
@@ -154,6 +154,9 @@ namespace PARR.DAL.DomainServices.Shortcodes
.Where(u => unitIds.Contains(u.Id)) .Where(u => unitIds.Contains(u.Id))
.ToListAsync(); .ToListAsync();
// Словарь: UnitId -> Unit (для быстрого поиска)
var unitDict = units.ToDictionary(u => u.Id, u => u);
var groupingFieldId = job.Group?.GroupingUnitFieldId; var groupingFieldId = job.Group?.GroupingUnitFieldId;
Dictionary<Guid, string> valuesByUnit = new(); Dictionary<Guid, string> valuesByUnit = new();
@@ -161,7 +164,6 @@ namespace PARR.DAL.DomainServices.Shortcodes
{ {
var fieldValues = await unitInValueService.Get() var fieldValues = await unitInValueService.Get()
.AsNoTracking() .AsNoTracking()
//.AsSplitQuery()
.Include(uv => uv.Value) .Include(uv => uv.Value)
.Where(uv => .Where(uv =>
uv.FieldId == groupingFieldId.Value && 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) => .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 unitName = unitInList?.Name ?? $"(UnitId={uit.UnitId})";
var valuesStr = valuesByUnit.TryGetValue(uit.UnitId, out var vals) ? vals : ""; var valuesStr = valuesByUnit.TryGetValue(uit.UnitId, out var vals) ? vals : "";
return $"{indexInList + 1}. {unitName} ({valuesStr})"; return $"{indexInList + 1}. {unitName} ({valuesStr})";

View File

@@ -15,7 +15,6 @@ using PARR.DAL.Services.Interfaces.Unit;
using PARR.DAL.TransformServices; using PARR.DAL.TransformServices;
using PARR.TemplateMatcher.Services.Interfaces; using PARR.TemplateMatcher.Services.Interfaces;
using PARR.TemplateMatcher.Settings; using PARR.TemplateMatcher.Settings;
using System.Configuration;
using System.Text.Json; using System.Text.Json;
namespace PARR.TemplateMatcher.Services.Implementations; namespace PARR.TemplateMatcher.Services.Implementations;
@@ -502,20 +501,31 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
foreach (var kvp in groupedRelationships) foreach (var kvp in groupedRelationships)
{ {
var relationshipUnitId = kvp.Key; // Связанный юнит (не из unitIdsWithValidGroupingFieldSet) var relationshipUnitId = kvp.Key; // Связанный юнит (не из unitIdsWithValidGroupingFieldSet)
var childUnitIds = kvp.Value; // Юниты из unitIdsWithValidGroupingFieldSet, связанные с regionalUnitId var childUnitIds = kvp.Value; // Юниты из unitIdsWithValidGroupingFieldSet, связанные с relationshipUnitId
if (childUnitIds.Count == 0) if (childUnitIds.Count == 0)
continue; continue;
#if DEBUG #if DEBUG
// Отладка: проверить, есть ли ВРТ-AOS-05-ДВС в childUnitIds // Отладка: проверить, есть ли ВРТ-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(); var childUnitNamesForDebug = 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-ДВС")) if (childUnitNamesForDebug.Contains("ВРТ-AOS-05-ДВС"))
{ {
var relationshipUnitName = await unitService.Get().AsNoTracking().Where(u => u.Id == relationshipUnitId).Select(u => u.Name).FirstOrDefaultAsync(); 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 #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 // Применяем ограничение MaxValueRelationships maxJob
if (!maxJob.MaxValueRelationships.HasValue) if (!maxJob.MaxValueRelationships.HasValue)
@@ -524,13 +534,13 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
return; return;
} }
int maxValueForSplitting = maxJob.MaxValueRelationships.Value; int maxValueForSplitting = maxJob.MaxValueRelationships.Value;
var childUnitGroups = childUnitIds var childUnitGroups = sortedChildUnitIds
.Select((id, index) => new { id, groupIndex = index / maxValueForSplitting }) .Select((id, index) => new { id, groupIndex = index / maxValueForSplitting })
.GroupBy(x => x.groupIndex) .GroupBy(x => x.groupIndex)
.Select(g => g.Select(x => x.id).ToList()) .Select(g => g.Select(x => x.id).ToList())
.ToList(); .ToList();
logger.LogDebug("Связанный юнит {RegionalUnitId}: разбит на {GroupCount} подгрупп.", relationshipUnitId, childUnitGroups.Count); logger.LogDebug("Связанный юнит {RelationshipUnitId}: разбит на {GroupCount} подгрупп (после сортировки по имени).", relationshipUnitId, childUnitGroups.Count);
// Для каждой подгруппы: // Для каждой подгруппы:
for (int i = 0; i < childUnitGroups.Count; i++) for (int i = 0; i < childUnitGroups.Count; i++)
@@ -538,7 +548,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
var subGroup = childUnitGroups[i]; var subGroup = childUnitGroups[i];
var subGroupSize = subGroup.Count; var subGroupSize = subGroup.Count;
logger.LogDebug("Обработка подгруппы {Index} связанного юнита {RegionalUnitId}, размер {Size}.", i, relationshipUnitId, subGroupSize); logger.LogDebug("Обработка подгруппы {Index} связанного юнита {RelationshipUnitId}, размер {Size}.", i, relationshipUnitId, subGroupSize);
// Выбираем targetJob для подгруппы // Выбираем targetJob для подгруппы
Job? targetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob); Job? targetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
@@ -560,7 +570,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
if (existingUnitIds.SetEquals(newUnitIds)) 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); var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(targetJob, relationshipUnitId, i, subGroup);
@@ -595,7 +605,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
} }
else 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 // Пересчитать targetJob для нового размера subGroup
var newTargetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob); var newTargetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
@@ -615,7 +625,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
if (reusableTemplate != null) 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 expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(targetJob, relationshipUnitId, i, subGroup);
var nextRun = await GetNextRunAsync(targetJob); var nextRun = await GetNextRunAsync(targetJob);
@@ -639,21 +649,32 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
} }
else 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); await CreateGroupedTemplateAsync(targetJob.Id, relationshipUnitId, subGroup, i, initiator);
} }
} }
} }
} }
// 10. Деактивировать шаблоны, которые больше не соответствуют ни одной подгруппе (логика без изменений) // 10. Деактивировать шаблоны, которые больше не соответствуют ни одной подгруппе
var expectedTemplateKeys = new HashSet<(Guid JobId, Guid UnitId, int Index)>(); var expectedTemplateKeys = new HashSet<(Guid JobId, Guid UnitId, int Index)>();
foreach (var kvp in groupedRelationships) foreach (var kvp in groupedRelationships)
{ {
var regionalUnitId = kvp.Key; var relationshipUnitId = kvp.Key;
var childUnitIds = kvp.Value; 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; int maxValueForSplitting = maxJob.MaxValueRelationships!.Value;
var childUnitGroups = childUnitIds var childUnitGroups = sortedChildUnitIdsForDeactivate
.Select((id, index) => new { id, groupIndex = index / maxValueForSplitting }) .Select((id, index) => new { id, groupIndex = index / maxValueForSplitting })
.GroupBy(x => x.groupIndex) .GroupBy(x => x.groupIndex)
.Select(g => g.Select(x => x.id).ToList()) .Select(g => g.Select(x => x.id).ToList())
@@ -666,17 +687,17 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
Job? targetJobForExpectedKey = SelectTargetJob(jobsInGroup, subGroupSize, maxJob); 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 allJobIdsInGroup = jobsInGroup.Select(j => j.Id).ToHashSet();
var allExistingTemplatesInGroup = await templateService.Get() var allExistingTemplatesInGroup = await templateService.Get()
.AsNoTracking() .AsNoTracking()
.Include(t => t.UnitsInTemplate) .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(); .ToListAsync();
foreach (var existingTemplate in allExistingTemplatesInGroup) foreach (var existingTemplate in allExistingTemplatesInGroup)
@@ -767,14 +788,14 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest); await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
} }
private async Task CreateGroupedTemplateAsync(Guid jobId, Guid regionalUnitId, List<Guid> unitIds, int index, HistoryInitiator initiator) private async Task CreateGroupedTemplateAsync(Guid jobId, Guid relationshipUnitId, List<Guid> 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 var mqRequest = new TemplateGeneratorMq
{ {
JobId = jobId, JobId = jobId,
UnitId = regionalUnitId, // UnitId шаблона UnitId = relationshipUnitId, // UnitId шаблона
UnitsInTemplate = unitIds, // Юниты для UnitsInTemplate UnitsInTemplate = unitIds, // Юниты для UnitsInTemplate
Index = index, // Индекс шаблона Index = index, // Индекс шаблона
HistoryInitiator = initiator HistoryInitiator = initiator
@@ -784,7 +805,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg }); var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg });
if (!result.IsSuccess) 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<DateTimeOffset> GetNextRunAsync(Job targetJob, DateTimeOffset? currentNextRun = null) private async Task<DateTimeOffset> GetNextRunAsync(Job targetJob, DateTimeOffset? currentNextRun = null)