feat(dal,templateMatcher): добавлена сортировка связанных юнитов группированного типа групп работ
This commit is contained in:
@@ -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})";
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user