fix(templateMatcher): Добавлена сортировка связанных ЭК при сравнении группированных шаблонов.
This commit is contained in:
@@ -628,7 +628,7 @@ namespace PARR.DAL.DomainServices.Shortcodes
|
||||
// %ТИКТАК% — не зависит от Job или Unit
|
||||
if (result.Contains("%ТИКТАК%", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
result = ReplaceSingleShortcode(result, "%ТИКТАК%", DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString());
|
||||
result = ReplaceSingleShortcode(result, "%ТИКТАК%", DateTimeOffset.UtcNow.ToUnixTimeMilliseconds().ToString());
|
||||
}
|
||||
|
||||
if (job != null)
|
||||
|
||||
@@ -21,11 +21,6 @@ namespace PARR.TemplateMatcher.Services.Implementations;
|
||||
|
||||
internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
{
|
||||
#if DEBUG
|
||||
private readonly string[] debugWatchUnitNames = { "МОНИТОР-АСОУП-3-ВСИБ", "ЦЕКОН-ВСИБ", "ВРТ-ASOUP2-ВСИБ" };
|
||||
private Dictionary<string, Guid> debugWatchUnitIds = new Dictionary<string, Guid>();
|
||||
#endif
|
||||
|
||||
private const bool DefaultUsedTemplateState = false;
|
||||
private const bool DefaultUsedScheduleState = false;
|
||||
|
||||
@@ -86,36 +81,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
this.matchingStatusService = matchingStatusService;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private async Task InitializeDebugUnitsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var watchUnits = await unitService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => debugWatchUnitNames.Contains(u.Name))
|
||||
.ToDictionaryAsync(u => u.Name, u => u.Id);
|
||||
|
||||
foreach (var name in debugWatchUnitNames)
|
||||
{
|
||||
if (watchUnits.TryGetValue(name, out var id))
|
||||
{
|
||||
debugWatchUnitIds[name] = id;
|
||||
logger.LogWarning("[DEBUG] Найден юнит для отслеживания '{UnitName}' с ID: {UnitId}", name, id);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogWarning("[DEBUG] Юнит для отслеживания '{UnitName}' не найден в БД", name);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogError(ex, "[DEBUG] Ошибка при поиске юнитов для отслеживания");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
|
||||
{
|
||||
logger.LogWarning("GroupedTemplateSynchronizer: SyncTemplatesForJob вызван для JobId {JobId}. Это не поддерживаемая операция.", jobId);
|
||||
@@ -123,10 +88,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
|
||||
public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator)
|
||||
{
|
||||
#if DEBUG
|
||||
await InitializeDebugUnitsAsync();
|
||||
#endif
|
||||
|
||||
logger.LogDebug("Начало синхронизации шаблонов для JobGroup {JobGroupId}", jobGroupId);
|
||||
|
||||
// === Проверка: уже запущена? ===
|
||||
@@ -184,10 +145,10 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
return;
|
||||
}
|
||||
var workGroupFieldId = workGroupField.Id;
|
||||
var regionalGroupValueIds = regionalEkPtkGroupService.Get()
|
||||
var relationshipGroupValueIds = regionalEkPtkGroupService.Get()
|
||||
.Select(g => g.FieldValueId)
|
||||
.ToList();
|
||||
logger.LogDebug("Найдено {Count} значений из UnitRegionalEkPtkGroup для проверки поля 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК'.", regionalGroupValueIds.Count);
|
||||
logger.LogDebug("Найдено {Count} значений из UnitRegionalEkPtkGroup для проверки поля 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК'.", relationshipGroupValueIds.Count);
|
||||
|
||||
// 2. Найти Job с максимальным MaxValueRelationships
|
||||
var maxJob = jobsInGroup
|
||||
@@ -218,25 +179,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
return;
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
var debugMonitorUnitId = debugWatchUnitIds.GetValueOrDefault("МОНИТОР-АСОУП-3-ВСИБ", Guid.Empty);
|
||||
var debugCekonUnitId = debugWatchUnitIds.GetValueOrDefault("ЦЕКОН-ВСИБ", Guid.Empty);
|
||||
var debugVrtUnitId = debugWatchUnitIds.GetValueOrDefault("ВРТ-ASOUP2-ВСИБ", Guid.Empty);
|
||||
|
||||
if (debugMonitorUnitId != Guid.Empty && expectedUnitIds.Contains(debugMonitorUnitId))
|
||||
{
|
||||
logger.LogDebug("Юнит МОНИТОР-АСОУП-3-ВСИБ найден в expectedUnitIds.");
|
||||
}
|
||||
if (debugCekonUnitId != Guid.Empty && expectedUnitIds.Contains(debugCekonUnitId))
|
||||
{
|
||||
logger.LogDebug("Юнит ЦЕКОН-ВСИБ найден в expectedUnitIds.");
|
||||
}
|
||||
if (debugVrtUnitId != Guid.Empty && expectedUnitIds.Contains(debugVrtUnitId))
|
||||
{
|
||||
logger.LogDebug("Юнит ВРТ-ASOUP2-ВСИБ найден в expectedUnitIds.");
|
||||
}
|
||||
#endif
|
||||
|
||||
// 4. Отфильтровать expectedUnitIds по GroupingUnitFieldId
|
||||
if (!jobGroup.GroupingUnitFieldId.HasValue)
|
||||
{
|
||||
@@ -246,17 +188,15 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
}
|
||||
|
||||
var groupingFieldId = jobGroup.GroupingUnitFieldId.Value;
|
||||
var expectedUnitsWithGroupingField = await unitService.Get()
|
||||
var unitIdsWithValidGroupingFieldSet = await unitService.Get()
|
||||
.AsNoTracking()
|
||||
.Include(u => u.UnitValues)
|
||||
.ThenInclude(uv => uv.Value)
|
||||
.Where(u => expectedUnitIds.Contains(u.Id))
|
||||
.ToListAsync();
|
||||
|
||||
var unitIdsWithValidGroupingFieldSet = expectedUnitsWithGroupingField
|
||||
.Where(u => u.UnitValues.Any(uv => uv.FieldId == groupingFieldId && uv.Value != null && !string.IsNullOrEmpty(uv.Value.Value)))
|
||||
.Where(u => u.UnitValues.Any(uv =>
|
||||
uv.FieldId == groupingFieldId &&
|
||||
uv.Value != null &&
|
||||
!string.IsNullOrEmpty(uv.Value.Value)))
|
||||
.Select(u => u.Id)
|
||||
.ToHashSet();
|
||||
.ToListAsync();
|
||||
|
||||
logger.LogDebug("После фильтрации по GroupingUnitFieldId осталось {Count} юнитов.", unitIdsWithValidGroupingFieldSet.Count);
|
||||
|
||||
@@ -267,13 +207,16 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Дополнительная фильтрация по "РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК" ---
|
||||
var unitIdsWithValidWorkGroupFieldSet = await FilterByWorkGroupFieldAsync(
|
||||
expectedUnitsWithGroupingField,
|
||||
unitIdsWithValidGroupingFieldSet,
|
||||
workGroupFieldId,
|
||||
regionalGroupValueIds
|
||||
);
|
||||
// Аналогично для фильтрации по "РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК"
|
||||
var unitIdsWithValidWorkGroupFieldSet = await unitService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => unitIdsWithValidGroupingFieldSet.Contains(u.Id))
|
||||
.Where(u => u.UnitValues.Any(uv =>
|
||||
uv.FieldId == workGroupFieldId &&
|
||||
uv.Value != null &&
|
||||
relationshipGroupValueIds.Contains(uv.Value.Id)))
|
||||
.Select(u => u.Id)
|
||||
.ToListAsync();
|
||||
|
||||
logger.LogDebug("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' осталось {Count} юнитов.", unitIdsWithValidWorkGroupFieldSet.Count);
|
||||
|
||||
@@ -384,12 +327,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
|
||||
logger.LogDebug("Сформировано {Count} групп по связанным юнитам до разрешения конфликтов.", groupedRelationships.Count);
|
||||
|
||||
// === DEBUG: СПЕЦИАЛЬНАЯ ПРОВЕРКА ДУБЛИРОВАНИЯ ===
|
||||
#if DEBUG
|
||||
await LogGroupingDebugInfo(jobGroup, groupedRelationships);
|
||||
#endif
|
||||
// ===============================================
|
||||
|
||||
// --- Разрешение конфликта с детерминированной сортировкой ---
|
||||
var unitToKeys = new Dictionary<Guid, List<Guid>>();
|
||||
foreach (var kvp in groupedRelationships)
|
||||
@@ -455,16 +392,9 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
{
|
||||
groupedRelationships.Remove(key);
|
||||
}
|
||||
// ===============================================
|
||||
|
||||
logger.LogDebug("Сформировано {Count} групп по связанным юнитам после разрешения конфликтов.", groupedRelationships.Count);
|
||||
|
||||
// === DEBUG: ЛОГИРОВАНИЕ ПОСЛЕ РАЗРЕШЕНИЯ КОНФЛИКТОВ ===
|
||||
#if DEBUG
|
||||
await LogGroupingDebugInfo(jobGroup, groupedRelationships, true);
|
||||
#endif
|
||||
// ======================================================
|
||||
|
||||
// === Основной цикл обработки ===
|
||||
foreach (var kvp in groupedRelationships)
|
||||
{
|
||||
@@ -472,20 +402,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
var childUnitIds = kvp.Value;
|
||||
if (childUnitIds.Count == 0) continue;
|
||||
|
||||
#if DEBUG
|
||||
var debugUnitIds = childUnitIds.Concat(new[] { relationshipUnitId }).Distinct().ToList();
|
||||
var debugUnits = await unitService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => debugUnitIds.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, u => u.Name);
|
||||
var childUnitNamesForDebug = childUnitIds.Select(id => debugUnits.GetValueOrDefault(id, id.ToString())).ToList();
|
||||
var relationshipUnitName = debugUnits.GetValueOrDefault(relationshipUnitId, relationshipUnitId.ToString());
|
||||
if (debugWatchUnitIds.Values.Contains(relationshipUnitId) || childUnitNamesForDebug.Contains("ВРТ-ASOUP2-ВСИБ"))
|
||||
{
|
||||
logger.LogDebug("Группа с ключом {Key} (название: {Name}) содержит юниты: [{ChildUnitNames}]", relationshipUnitId, relationshipUnitName, string.Join(", ", childUnitNamesForDebug));
|
||||
}
|
||||
#endif
|
||||
|
||||
logger.LogDebug("Обработка связанного юнита {RelationshipUnitId} с {Count} юнитами из списка.", relationshipUnitId, childUnitIds.Count);
|
||||
|
||||
var childUnitNameMap = await unitService.Get()
|
||||
@@ -506,9 +422,10 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
|
||||
logger.LogDebug("Связанный юнит {RelationshipUnitId}: разбит на {GroupCount} подгрупп.", relationshipUnitId, childUnitGroups.Count);
|
||||
|
||||
for (int i = 0; i < childUnitGroups.Count; i++)
|
||||
// Индекс начинается с 1
|
||||
for (int i = 1; i <= childUnitGroups.Count; i++)
|
||||
{
|
||||
var subGroup = childUnitGroups[i];
|
||||
var subGroup = childUnitGroups[i - 1]; // корректируем индекс для доступа к коллекции
|
||||
var subGroupSize = subGroup.Count;
|
||||
logger.LogDebug("Обработка подгруппы {Index} связанного юнита {RelationshipUnitId}, размер {Size}.", i, relationshipUnitId, subGroupSize);
|
||||
|
||||
@@ -529,34 +446,33 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
|
||||
var existingTemplateForSubGroup = existingTemplatesForRelationship.FirstOrDefault();
|
||||
|
||||
// === DEBUG: ДЕТАЛЬНОЕ СРАВНЕНИЕ UnitsInTemplate ===
|
||||
#if DEBUG
|
||||
if (existingTemplateForSubGroup != null)
|
||||
{
|
||||
var relationshipUnit = await unitService.Get().AsNoTracking().FirstOrDefaultAsync(u => u.Id == relationshipUnitId);
|
||||
if (relationshipUnit != null &&
|
||||
(relationshipUnit.Name == "МОНИТОР-АСОУП-3-ВСИБ" || relationshipUnit.Name == "ЦЕКОН-ВСИБ") &&
|
||||
subGroup.Contains(debugWatchUnitIds.GetValueOrDefault("ВРТ-ASOUP2-ВСИБ", Guid.Empty)))
|
||||
{
|
||||
await LogUnitComparisonDebugAsync(existingTemplateForSubGroup, subGroup, relationshipUnit.Name);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
// ===========================================
|
||||
// === 1. Получаем текущие и новые ID юнитов ===
|
||||
var currentUnitIds = existingTemplateForSubGroup.UnitsInTemplate.Select(uit => uit.UnitId).ToList();
|
||||
var proposedUnitIds = subGroup.ToList();
|
||||
|
||||
if (existingTemplateForSubGroup != null)
|
||||
{
|
||||
var existingUnitIds = existingTemplateForSubGroup.UnitsInTemplate.Select(uit => uit.UnitId).ToList();
|
||||
var newUnitIds = subGroup.ToList();
|
||||
// === 2. Сравниваем детерминированно с сортировкой по имени ===
|
||||
var allUnitIdsForSort = currentUnitIds.Concat(proposedUnitIds).Distinct().ToList();
|
||||
var unitNamesForSort = await unitService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => allUnitIdsForSort.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, u => u.Name ?? u.Id.ToString());
|
||||
|
||||
// Сравниваем не только наборы, но и порядок
|
||||
bool areUnitsEqual = existingUnitIds.SequenceEqual(newUnitIds);
|
||||
var sortedCurrentUnitIds = currentUnitIds
|
||||
.OrderBy(id => unitNamesForSort.GetValueOrDefault(id, id.ToString()))
|
||||
.ToList();
|
||||
|
||||
if (areUnitsEqual)
|
||||
var sortedProposedUnitIds = proposedUnitIds
|
||||
.OrderBy(id => unitNamesForSort.GetValueOrDefault(id, id.ToString()))
|
||||
.ToList();
|
||||
|
||||
bool unitsAreEqual = sortedCurrentUnitIds.SequenceEqual(sortedProposedUnitIds);
|
||||
|
||||
if (unitsAreEqual)
|
||||
{
|
||||
logger.LogDebug("Шаблон {TemplateId} актуален по юнитам и их порядку.", existingTemplateForSubGroup.Id);
|
||||
// Обновляем UnitsInTemplate для генерации имени
|
||||
existingTemplateForSubGroup.UnitsInTemplate = subGroup.Select(id => new UnitsInTemplate { UnitId = id }).ToList();
|
||||
logger.LogDebug("Шаблон {TemplateId} актуален по юнитам и их порядку (после сортировки).", existingTemplateForSubGroup.Id);
|
||||
existingTemplateForSubGroup.UnitsInTemplate = sortedProposedUnitIds.Select(id => new UnitsInTemplate { UnitId = id }).ToList();
|
||||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(existingTemplateForSubGroup);
|
||||
|
||||
if (!string.Equals(existingTemplateForSubGroup.Name, expectedName, StringComparison.OrdinalIgnoreCase))
|
||||
@@ -576,7 +492,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
Index = i,
|
||||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||||
Initiator = initiator,
|
||||
UnitsInTemplate = subGroup
|
||||
UnitsInTemplate = sortedProposedUnitIds
|
||||
};
|
||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||
}
|
||||
@@ -587,13 +503,13 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogDebug("Шаблон {TemplateId} требует обновления юнитов или их порядка.", existingTemplateForSubGroup.Id);
|
||||
logger.LogDebug("Шаблон {TemplateId} требует обновления юнитов или их порядка (после сортировки).", existingTemplateForSubGroup.Id);
|
||||
var newTargetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
||||
if (newTargetJob.Id != existingTemplateForSubGroup.JobId)
|
||||
{
|
||||
logger.LogDebug("Job для шаблона {TemplateId} изменился.", existingTemplateForSubGroup.Id);
|
||||
}
|
||||
await UpdateTemplateUnitsAsync(existingTemplateForSubGroup, subGroup, newTargetJob, initiator);
|
||||
await UpdateTemplateUnitsAsync(existingTemplateForSubGroup, sortedProposedUnitIds, newTargetJob, initiator, i);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -663,9 +579,10 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
.GroupBy(x => x.groupIndex)
|
||||
.Select(g => g.Select(x => x.id).ToList())
|
||||
.ToList();
|
||||
for (int i = 0; i < childUnitGroups.Count; i++)
|
||||
// Индекс начинается с 1
|
||||
for (int i = 1; i <= childUnitGroups.Count; i++)
|
||||
{
|
||||
var subGroup = childUnitGroups[i];
|
||||
var subGroup = childUnitGroups[i - 1]; // корректируем индекс для доступа к коллекции
|
||||
var subGroupSize = subGroup.Count;
|
||||
Job? targetJobForExpectedKey = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
||||
expectedTemplateKeys.Add((targetJobForExpectedKey.Id, relationshipUnitId, i));
|
||||
@@ -676,7 +593,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
var allJobIdsInGroup = jobsInGroup.Select(j => j.Id).ToHashSet();
|
||||
var allExistingTemplatesInGroup = await templateService.Get()
|
||||
.AsNoTracking()
|
||||
.Include(t=>t.Unit)
|
||||
.Include(t => t.Unit)
|
||||
.Include(t => t.UnitsInTemplate)
|
||||
.Where(t => allJobIdsInGroup.Contains(t.JobId) &&
|
||||
t.StatusTypeId == TemplateStatusTypeEnum.Used &&
|
||||
@@ -707,183 +624,12 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
private async Task LogGroupingDebugInfo(JobGroup jobGroup, Dictionary<Guid, List<Guid>> groupedRelationships, bool isAfterConflictResolution = false)
|
||||
{
|
||||
logger.LogDebug("===[ DEBUG: {Stage} ГРУППИРОВКА ДЛЯ ОТСЛЕЖИВАНИЯ ДУБЛИРОВАНИЯ ВРТ-ASOUP2-ВСИБ ]===",
|
||||
isAfterConflictResolution ? "ПОСЛЕ РАЗРЕШЕНИЯ КОНФЛИКТОВ" : "ДО РАЗРЕШЕНИЯ КОНФЛИКТОВ");
|
||||
|
||||
// Проверяем, есть ли в группах наши целевые юниты
|
||||
var watchUnitsInGroups = groupedRelationships
|
||||
.Where(g => debugWatchUnitIds.Values.Contains(g.Key) || g.Value.Any(id => debugWatchUnitIds.Values.Contains(id)))
|
||||
.ToList();
|
||||
|
||||
if (!watchUnitsInGroups.Any())
|
||||
{
|
||||
logger.LogDebug("Целевые юниты не найдены в группах");
|
||||
return;
|
||||
}
|
||||
|
||||
// Загружаем имена для всех юнитов в группах
|
||||
var allUnitIds = watchUnitsInGroups.SelectMany(g => new[] { g.Key }.Concat(g.Value)).Distinct().ToList();
|
||||
var unitNames = await unitService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => allUnitIds.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, u => u.Name ?? $"(ID={u.Id})");
|
||||
|
||||
// Ищем все группы, содержащие ВРТ-ASOUP2-ВСИБ
|
||||
var vrtUnitId = debugWatchUnitIds.GetValueOrDefault("ВРТ-ASOUP2-ВСИБ", Guid.Empty);
|
||||
if (vrtUnitId != Guid.Empty)
|
||||
{
|
||||
var groupsWithVrt = groupedRelationships
|
||||
.Where(g => g.Key == vrtUnitId || g.Value.Contains(vrtUnitId))
|
||||
.ToList();
|
||||
|
||||
if (groupsWithVrt.Any())
|
||||
{
|
||||
logger.LogWarning("===[ DEBUG: ВРТ-ASOUP2-ВСИБ НАЙДЕН В СЛЕДУЮЩИХ ГРУППАХ ]===");
|
||||
foreach (var group in groupsWithVrt)
|
||||
{
|
||||
var groupName = unitNames.GetValueOrDefault(group.Key, $"(ID={group.Key})");
|
||||
var members = string.Join(", ", group.Value.Select(id => unitNames.GetValueOrDefault(id, $"(ID={id})")));
|
||||
logger.LogWarning("Группа [Ключ: {GroupName}]: Участники [{Members}]", groupName, members);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogDebug("ВРТ-ASOUP2-ВСИБ не найден в группах");
|
||||
}
|
||||
}
|
||||
|
||||
// Дополнительно: проверяем принадлежность ВРТ-ASOUP2-ВСИБ к целевым группам
|
||||
var monitorGroupId = debugWatchUnitIds.GetValueOrDefault("МОНИТОР-АСОУП-3-ВСИБ", Guid.Empty);
|
||||
var cekonGroupId = debugWatchUnitIds.GetValueOrDefault("ЦЕКОН-ВСИБ", Guid.Empty);
|
||||
|
||||
if (vrtUnitId != Guid.Empty && (monitorGroupId != Guid.Empty || cekonGroupId != Guid.Empty))
|
||||
{
|
||||
bool vrtInMonitorGroup = false;
|
||||
if (monitorGroupId != Guid.Empty && groupedRelationships.TryGetValue(monitorGroupId, out var monitorGroup))
|
||||
{
|
||||
vrtInMonitorGroup = monitorGroup.Contains(vrtUnitId);
|
||||
}
|
||||
|
||||
bool vrtInCekonGroup = false;
|
||||
if (cekonGroupId != Guid.Empty && groupedRelationships.TryGetValue(cekonGroupId, out var cekonGroup))
|
||||
{
|
||||
vrtInCekonGroup = cekonGroup.Contains(vrtUnitId);
|
||||
}
|
||||
|
||||
logger.LogWarning("=== АНАЛИЗ ПРИНАДЛЕЖНОСТИ ВРТ-ASOUP2-ВСИБ ===");
|
||||
logger.LogWarning("ВРТ-ASOUP2-ВСИБ в группе МОНИТОР-АСОУП-3-ВСИБ: {InGroup}", vrtInMonitorGroup);
|
||||
logger.LogWarning("ВРТ-ASOUP2-ВСИБ в группе ЦЕКОН-ВСИБ: {InGroup}", vrtInCekonGroup);
|
||||
|
||||
if (vrtInMonitorGroup && vrtInCekonGroup)
|
||||
{
|
||||
logger.LogError("!!! КРИТИЧНО: ВРТ-ASOUP2-ВСИБ ПРИНАДЛЕЖИТ ОБЕИМ ГРУППАМ !!!");
|
||||
}
|
||||
}
|
||||
|
||||
// Дополнительная информация о составе целевых групп
|
||||
if (monitorGroupId != Guid.Empty || cekonGroupId != Guid.Empty)
|
||||
{
|
||||
logger.LogWarning("=== СОСТАВ ЦЕЛЕВЫХ ГРУПП ===");
|
||||
|
||||
if (monitorGroupId != Guid.Empty && groupedRelationships.TryGetValue(monitorGroupId, out var monitorGroup))
|
||||
{
|
||||
var members = string.Join(", ", monitorGroup.Select(id => unitNames.GetValueOrDefault(id, $"(ID={id})")));
|
||||
logger.LogWarning("Группа МОНИТОР-АСОУП-3-ВСИБ ({Count} участников): [{Members}]",
|
||||
monitorGroup.Count, members);
|
||||
}
|
||||
|
||||
if (cekonGroupId != Guid.Empty && groupedRelationships.TryGetValue(cekonGroupId, out var cekonGroup))
|
||||
{
|
||||
var members = string.Join(", ", cekonGroup.Select(id => unitNames.GetValueOrDefault(id, $"(ID={id})")));
|
||||
logger.LogWarning("Группа ЦЕКОН-ВСИБ ({Count} участников): [{Members}]",
|
||||
cekonGroup.Count, members);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task LogUnitComparisonDebugAsync(Template template, List<Guid> newUnitIds, string groupName)
|
||||
{
|
||||
logger.LogWarning("===[ DEBUG: СРАВНЕНИЕ UnitsInTemplate ДЛЯ {GroupName} ]===", groupName);
|
||||
|
||||
// Загружаем ИМЕНА для юнитов из базы
|
||||
var dbUnitIds = template.UnitsInTemplate.Select(uit => uit.UnitId).ToList();
|
||||
var dbUnitsMap = await unitService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => dbUnitIds.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, u => u.Name);
|
||||
|
||||
var dbUnitNames = template.UnitsInTemplate
|
||||
.Select(uit => dbUnitsMap.GetValueOrDefault(uit.UnitId, $"(ID={uit.UnitId})"))
|
||||
.ToList();
|
||||
|
||||
// Загружаем ИМЕНА для новых юнитов
|
||||
var newUnitsMap = await unitService.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => newUnitIds.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, u => u.Name);
|
||||
|
||||
var newUnitNames = newUnitIds
|
||||
.Select(id => newUnitsMap.GetValueOrDefault(id, $"(ID={id})"))
|
||||
.ToList();
|
||||
|
||||
logger.LogWarning("Шаблон ID={TemplateId}, Имя='{TemplateName}'",
|
||||
template.Id, template.Name);
|
||||
|
||||
logger.LogWarning("Юниты в базе ({Count}): [{Units}]",
|
||||
dbUnitNames.Count, string.Join(", ", dbUnitNames));
|
||||
|
||||
logger.LogWarning("Новые юниты ({Count}): [{Units}]",
|
||||
newUnitNames.Count, string.Join(", ", newUnitNames));
|
||||
|
||||
// Сравниваем ID
|
||||
var existingUnitIds = template.UnitsInTemplate.Select(uit => uit.UnitId).ToList();
|
||||
var newUnitIdsList = newUnitIds.ToList();
|
||||
|
||||
bool idSetsEqual = existingUnitIds.ToHashSet().SetEquals(newUnitIdsList.ToHashSet());
|
||||
bool orderEqual = existingUnitIds.SequenceEqual(newUnitIdsList);
|
||||
|
||||
logger.LogWarning("Сравнение: ID-наборы совпадают={IdMatch}, Порядок совпадает={OrderMatch}",
|
||||
idSetsEqual, orderEqual);
|
||||
|
||||
if (!orderEqual)
|
||||
{
|
||||
logger.LogWarning("ПОРЯДОК ОТЛИЧАЕТСЯ:");
|
||||
logger.LogWarning(" База: [{Order}]", string.Join(", ", dbUnitNames));
|
||||
logger.LogWarning(" Новое: [{Order}]", string.Join(", ", newUnitNames));
|
||||
|
||||
// Дополнительно: показываем ID для точного сравнения
|
||||
logger.LogWarning(" ID базы: [{Ids}]", string.Join(", ", existingUnitIds));
|
||||
logger.LogWarning(" ID новых: [{Ids}]", string.Join(", ", newUnitIdsList));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public async Task UpdateTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
|
||||
{
|
||||
logger.LogWarning("GroupedTemplateSynchronizer: UpdateTemplatesForJob вызван для JobId {JobId}. Это не поддерживаемая операция. Используйте SyncTemplatesForJobGroup для обновления.", jobId);
|
||||
return;
|
||||
}
|
||||
|
||||
// --- Вспомогательные методы ---
|
||||
private async Task<HashSet<Guid>> FilterByWorkGroupFieldAsync(
|
||||
List<Unit> units,
|
||||
HashSet<Guid> candidateUnitIds,
|
||||
Guid workGroupFieldId,
|
||||
List<Guid> regionalGroupValueIds)
|
||||
{
|
||||
return units
|
||||
.Where(u => candidateUnitIds.Contains(u.Id) &&
|
||||
u.UnitValues.Any(uv =>
|
||||
uv.FieldId == workGroupFieldId &&
|
||||
uv.Value != null &&
|
||||
regionalGroupValueIds.Contains(uv.Value.Id)))
|
||||
.Select(u => u.Id)
|
||||
.ToHashSet();
|
||||
}
|
||||
|
||||
private Job SelectTargetJob(List<Job> jobsInGroup, int subGroupSize, Job maxJob)
|
||||
{
|
||||
Job? targetJob = jobsInGroup
|
||||
@@ -911,7 +657,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
return targetJob;
|
||||
}
|
||||
|
||||
private async Task UpdateTemplateUnitsAsync(Template template, List<Guid> newUnitIds, Job targetJob, HistoryInitiator initiator)
|
||||
private async Task UpdateTemplateUnitsAsync(Template template, List<Guid> newUnitIds, Job targetJob, HistoryInitiator initiator, int newIndex)
|
||||
{
|
||||
template.StatusTypeId = TemplateStatusTypeEnum.Updating;
|
||||
template.DateModified = DateTimeOffset.UtcNow;
|
||||
@@ -927,7 +673,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
Name = template.Name,
|
||||
JobId = targetJob.Id,
|
||||
UnitId = template.UnitId,
|
||||
Index = template.Index,
|
||||
Index = newIndex,
|
||||
Job = targetJob,
|
||||
Unit = template.Unit,
|
||||
UnitsInTemplate = newUnitIds.Select(id => new UnitsInTemplate { UnitId = id }).ToList()
|
||||
@@ -945,7 +691,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
||||
IsActiveSchedule = template.IsActiveSchedule,
|
||||
LastRun = template.LastRun,
|
||||
NextRun = nextRun,
|
||||
Index = template.Index,
|
||||
Index = newIndex,
|
||||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||||
Initiator = initiator,
|
||||
UnitsInTemplate = newUnitIds
|
||||
|
||||
@@ -30,7 +30,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
||||
private const bool DefaultUsedScheduleState = false;
|
||||
|
||||
// Маска для неиспользуемых шаблонов — хардкод, недоступна из GUI
|
||||
private const string UnusedTemplateNameMask = "%П-1%_НЕАКТУАЛЬНЫЕ РАБОТЫ_%ЭК%_%ТИКТАК%";
|
||||
private const string UnusedTemplateNameMask = "%П-1%_НЕАКТУАЛЬНЫЕ_%ЭК%_%ТИКТАК%";
|
||||
|
||||
private readonly ILogger<SimpleTemplateSynchronizer> logger;
|
||||
private readonly IUnitFilterService unitFilterService;
|
||||
|
||||
Reference in New Issue
Block a user