diff --git a/PARR.DAL/DomainServices/Shortcodes/ShortcodesService.cs b/PARR.DAL/DomainServices/Shortcodes/ShortcodesService.cs index 094de983..b2be0eb9 100644 --- a/PARR.DAL/DomainServices/Shortcodes/ShortcodesService.cs +++ b/PARR.DAL/DomainServices/Shortcodes/ShortcodesService.cs @@ -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) diff --git a/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs b/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs index a8d9516f..3c423ba0 100644 --- a/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs +++ b/PARR.TemplateMatcher/Services/Implemetaions/GroupedTemplateSynchronizer.cs @@ -21,11 +21,6 @@ namespace PARR.TemplateMatcher.Services.Implementations; internal class GroupedTemplateSynchronizer : ITemplateSynchronizer { -#if DEBUG - private readonly string[] debugWatchUnitNames = { "МОНИТОР-АСОУП-3-ВСИБ", "ЦЕКОН-ВСИБ", "ВРТ-ASOUP2-ВСИБ" }; - private Dictionary debugWatchUnitIds = new Dictionary(); -#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>(); 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))) + // === 1. Получаем текущие и новые ID юнитов === + var currentUnitIds = existingTemplateForSubGroup.UnitsInTemplate.Select(uit => uit.UnitId).ToList(); + var proposedUnitIds = 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()); + + var sortedCurrentUnitIds = currentUnitIds + .OrderBy(id => unitNamesForSort.GetValueOrDefault(id, id.ToString())) + .ToList(); + + var sortedProposedUnitIds = proposedUnitIds + .OrderBy(id => unitNamesForSort.GetValueOrDefault(id, id.ToString())) + .ToList(); + + bool unitsAreEqual = sortedCurrentUnitIds.SequenceEqual(sortedProposedUnitIds); + + if (unitsAreEqual) { - await LogUnitComparisonDebugAsync(existingTemplateForSubGroup, subGroup, relationshipUnit.Name); - } - } -#endif - // =========================================== - - if (existingTemplateForSubGroup != null) - { - var existingUnitIds = existingTemplateForSubGroup.UnitsInTemplate.Select(uit => uit.UnitId).ToList(); - var newUnitIds = subGroup.ToList(); - - // Сравниваем не только наборы, но и порядок - bool areUnitsEqual = existingUnitIds.SequenceEqual(newUnitIds); - - if (areUnitsEqual) - { - 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> 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 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> FilterByWorkGroupFieldAsync( - List units, - HashSet candidateUnitIds, - Guid workGroupFieldId, - List 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 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 newUnitIds, Job targetJob, HistoryInitiator initiator) + private async Task UpdateTemplateUnitsAsync(Template template, List 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 diff --git a/PARR.TemplateMatcher/Services/Implemetaions/SimpleTemplateSynchronizer.cs b/PARR.TemplateMatcher/Services/Implemetaions/SimpleTemplateSynchronizer.cs index 1d1af62d..c59409ca 100644 --- a/PARR.TemplateMatcher/Services/Implemetaions/SimpleTemplateSynchronizer.cs +++ b/PARR.TemplateMatcher/Services/Implemetaions/SimpleTemplateSynchronizer.cs @@ -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 logger; private readonly IUnitFilterService unitFilterService;