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);
}
// 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<Guid, string> 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})";

View File

@@ -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<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
{
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<DateTimeOffset> GetNextRunAsync(Job targetJob, DateTimeOffset? currentNextRun = null)