999 lines
50 KiB
C#
999 lines
50 KiB
C#
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.Extensions.Logging;
|
||
using PARR.BLL.Domain.Mq;
|
||
using PARR.BLL.Services.Interfaces;
|
||
using PARR.Common.Domain;
|
||
using PARR.Constants;
|
||
using PARR.DAL.Cache.Models;
|
||
using PARR.DAL.DomainServices.Interfaces;
|
||
using PARR.DAL.Models;
|
||
using PARR.DAL.Models.Job;
|
||
using PARR.DAL.Models.Unit;
|
||
using PARR.DAL.Services.Interfaces;
|
||
using PARR.DAL.Services.Interfaces.Job;
|
||
using PARR.DAL.Services.Interfaces.Unit;
|
||
using PARR.DAL.TransformServices;
|
||
using PARR.TemplateMatcher.Services.Interfaces;
|
||
using PARR.TemplateMatcher.Settings;
|
||
using System.Text.Json;
|
||
|
||
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;
|
||
|
||
private readonly ILogger<GroupedTemplateSynchronizer> logger;
|
||
private readonly IUnitFilterService unitFilterService;
|
||
private readonly IUnitInUnitService unitInUnitService;
|
||
private readonly IUnitInValueService unitInValueService;
|
||
private readonly IUnitService unitService;
|
||
private readonly MqSettings mqSettings;
|
||
private readonly IMqService mqService;
|
||
private readonly ITemplateService templateService;
|
||
private readonly IJobGroupService jobGroupService;
|
||
private readonly ITemplateReuser templateReuser;
|
||
private readonly IEsppScheduleTransformService esppScheduleTransformService;
|
||
private readonly IUnitRegionalEkPtkGroupService regionalEkPtkGroupService;
|
||
private readonly IUnitFieldService unitFieldService;
|
||
private readonly ITemplateDeactivator templateDeactivator;
|
||
private readonly ITemplateNameNormalizer templateNameNormalizer;
|
||
private readonly ITemplateUpdaterMqSender templateUpdaterMqSender;
|
||
private readonly IMatchingStatusService matchingStatusService;
|
||
|
||
public GroupedTemplateSynchronizer(
|
||
ILogger<GroupedTemplateSynchronizer> logger,
|
||
IUnitFilterService unitFilterService,
|
||
IUnitInUnitService unitInUnitService,
|
||
IUnitInValueService unitInValueService,
|
||
IUnitService unitService,
|
||
MqSettings mqSettings,
|
||
IMqService mqService,
|
||
ITemplateService templateService,
|
||
IJobGroupService jobGroupService,
|
||
ITemplateReuser templateReuser,
|
||
IEsppScheduleTransformService esppScheduleTransformService,
|
||
IUnitRegionalEkPtkGroupService regionalEkPtkGroupService,
|
||
IUnitFieldService unitFieldService,
|
||
ITemplateDeactivator templateDeactivator,
|
||
ITemplateNameNormalizer templateNameNormalizer,
|
||
ITemplateUpdaterMqSender templateUpdaterMqSender,
|
||
IMatchingStatusService matchingStatusService
|
||
)
|
||
{
|
||
this.logger = logger;
|
||
this.unitFilterService = unitFilterService;
|
||
this.unitInUnitService = unitInUnitService;
|
||
this.unitInValueService = unitInValueService;
|
||
this.unitService = unitService;
|
||
this.mqSettings = mqSettings;
|
||
this.mqService = mqService;
|
||
this.templateService = templateService;
|
||
this.jobGroupService = jobGroupService;
|
||
this.templateReuser = templateReuser;
|
||
this.esppScheduleTransformService = esppScheduleTransformService;
|
||
this.regionalEkPtkGroupService = regionalEkPtkGroupService;
|
||
this.unitFieldService = unitFieldService;
|
||
this.templateDeactivator = templateDeactivator;
|
||
this.templateNameNormalizer = templateNameNormalizer;
|
||
this.templateUpdaterMqSender = templateUpdaterMqSender;
|
||
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);
|
||
}
|
||
|
||
public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator)
|
||
{
|
||
#if DEBUG
|
||
await InitializeDebugUnitsAsync();
|
||
#endif
|
||
|
||
logger.LogDebug("Начало синхронизации шаблонов для JobGroup {JobGroupId}", jobGroupId);
|
||
|
||
// === Проверка: уже запущена? ===
|
||
var existingStatus = await matchingStatusService.GetStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup);
|
||
if (existingStatus.DetailsJobGroups?.Any() == true)
|
||
{
|
||
logger.LogWarning("Синхронизация для JobGroup {JobGroupId} уже запущена. Пропускаем.", jobGroupId);
|
||
return;
|
||
}
|
||
|
||
// === Устанавливаем статус "в процессе" ===
|
||
var initialStatus = new MatchingStatusItemDto
|
||
{
|
||
DateStart = DateTimeOffset.UtcNow,
|
||
Action = TemplateMatcherActionEnum.Sync,
|
||
Comment = "Начало синхронизации"
|
||
};
|
||
await matchingStatusService.SetMatchingStatusAsync(
|
||
jobGroupId,
|
||
SyncTaskEntityTypeEnum.JobGroup,
|
||
new MatchingStatusItem { Data = initialStatus, Timestamp = DateTimeOffset.UtcNow, Source = nameof(GroupedTemplateSynchronizer) },
|
||
TimeSpan.FromMinutes(35)
|
||
);
|
||
|
||
try
|
||
{
|
||
// 1. Получить JobGroup и связанные Job'ы
|
||
var jobGroup = await jobGroupService.Get()
|
||
.AsNoTracking()
|
||
.Include(jg => jg.GroupType)
|
||
.Include(jg => jg.Jobs)
|
||
.ThenInclude(j => j.AutoControl)
|
||
.Include(jg => jg.Jobs)
|
||
.ThenInclude(j => j.UnitFilters)
|
||
.ThenInclude(uf => uf.RelationshipFilters)
|
||
.Include(jg => jg.Jobs)
|
||
.ThenInclude(jg => jg.Tnk)
|
||
.FirstOrDefaultAsync(jg => jg.Id == jobGroupId);
|
||
|
||
if (jobGroup == null || jobGroup.Jobs == null || !jobGroup.Jobs.Any())
|
||
{
|
||
logger.LogWarning("JobGroup {JobGroupId} не найден или не содержит Job'ов.", jobGroupId);
|
||
await UpdateMatchingStatusAsync(jobGroupId, "JobGroup не найден или пуст");
|
||
return;
|
||
}
|
||
|
||
var jobsInGroup = jobGroup.Jobs.ToList();
|
||
|
||
// --- Получение FieldId и разрешённых значений для "РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК" ---
|
||
var workGroupField = await unitFieldService.GetByAihitNameAsync("РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК");
|
||
if (workGroupField == null)
|
||
{
|
||
logger.LogError("Поле 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' не найдено в справочнике полей. Синхронизация прервана.");
|
||
await UpdateMatchingStatusAsync(jobGroupId, "Ошибка: поле 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' не найдено");
|
||
return;
|
||
}
|
||
var workGroupFieldId = workGroupField.Id;
|
||
var regionalGroupValueIds = regionalEkPtkGroupService.Get()
|
||
.Select(g => g.FieldValueId)
|
||
.ToList();
|
||
logger.LogDebug("Найдено {Count} значений из UnitRegionalEkPtkGroup для проверки поля 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК'.", regionalGroupValueIds.Count);
|
||
|
||
// 2. Найти Job с максимальным MaxValueRelationships
|
||
var maxJob = jobsInGroup
|
||
.Where(j => j.MaxValueRelationships.HasValue)
|
||
.OrderByDescending(j => j.MaxValueRelationships)
|
||
.FirstOrDefault();
|
||
|
||
if (maxJob == null)
|
||
{
|
||
logger.LogWarning("В JobGroup {JobGroupId} не найдено Job с установленным MaxValueRelationships.", jobGroupId);
|
||
await UpdateMatchingStatusAsync(jobGroupId, "Не найден Job с MaxValueRelationships");
|
||
return;
|
||
}
|
||
|
||
if (maxJob.UnitFilters == null)
|
||
{
|
||
logger.LogWarning("Job {JobId} не содержит UnitFilters.", maxJob.Id);
|
||
}
|
||
|
||
logger.LogDebug("Используется Job {JobId} с максимальным MaxValueRelationships ({MaxValue}) для фильтрации.", maxJob.Id, maxJob.MaxValueRelationships);
|
||
|
||
// 3. Использовать фильтры maxJob для получения expectedUnitIds
|
||
var expectedUnitIds = await unitFilterService.GetUnitsIdByJobFilterAsync(maxJob.Id);
|
||
if (expectedUnitIds == null || !expectedUnitIds.Any())
|
||
{
|
||
logger.LogInformation("Для JobGroup {JobGroupId} фильтры не дали Unit'ов.", jobGroupId);
|
||
await UpdateMatchingStatusAsync(jobGroupId, "Фильтры не дали Unit'ов");
|
||
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)
|
||
{
|
||
logger.LogError("JobGroup {JobGroupId} не имеет GroupingUnitFieldId, необходимого для группировки.", jobGroupId);
|
||
await UpdateMatchingStatusAsync(jobGroupId, "Отсутствует GroupingUnitFieldId");
|
||
return;
|
||
}
|
||
|
||
var groupingFieldId = jobGroup.GroupingUnitFieldId.Value;
|
||
var expectedUnitsWithGroupingField = 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)))
|
||
.Select(u => u.Id)
|
||
.ToHashSet();
|
||
|
||
logger.LogDebug("После фильтрации по GroupingUnitFieldId осталось {Count} юнитов.", unitIdsWithValidGroupingFieldSet.Count);
|
||
|
||
if (!unitIdsWithValidGroupingFieldSet.Any())
|
||
{
|
||
logger.LogInformation("После фильтрации по GroupingUnitFieldId в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId);
|
||
await UpdateMatchingStatusAsync(jobGroupId, "Нет юнитов после фильтрации по GroupingUnitFieldId");
|
||
return;
|
||
}
|
||
|
||
// --- Дополнительная фильтрация по "РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК" ---
|
||
var unitIdsWithValidWorkGroupFieldSet = await FilterByWorkGroupFieldAsync(
|
||
expectedUnitsWithGroupingField,
|
||
unitIdsWithValidGroupingFieldSet,
|
||
workGroupFieldId,
|
||
regionalGroupValueIds
|
||
);
|
||
|
||
logger.LogDebug("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' осталось {Count} юнитов.", unitIdsWithValidWorkGroupFieldSet.Count);
|
||
|
||
if (!unitIdsWithValidWorkGroupFieldSet.Any())
|
||
{
|
||
logger.LogInformation("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId);
|
||
await UpdateMatchingStatusAsync(jobGroupId, "Нет юнитов после фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК'");
|
||
return;
|
||
}
|
||
|
||
unitIdsWithValidGroupingFieldSet = unitIdsWithValidWorkGroupFieldSet;
|
||
|
||
// 5. Получить RelationshipFilters из maxJob
|
||
var relationshipFilters = maxJob.UnitFilters?.SelectMany(uf => uf.RelationshipFilters).ToList() ?? new List<JobRelationshipFilter>();
|
||
|
||
logger.LogDebug("Получение связей UnitInUnit для юнитов, прошедших фильтрацию...");
|
||
var potentialUnitInUnitLinks = await unitInUnitService.Get()
|
||
.AsNoTracking()
|
||
.Where(link => unitIdsWithValidGroupingFieldSet.Contains(link.ParentUnitId) || unitIdsWithValidGroupingFieldSet.Contains(link.ChildUnitId))
|
||
.ToListAsync();
|
||
|
||
logger.LogDebug("Найдено {Count} потенциальных связей UnitInUnit.", potentialUnitInUnitLinks.Count);
|
||
|
||
var allParentIds = potentialUnitInUnitLinks.Select(l => l.ParentUnitId).ToHashSet();
|
||
var allChildIds = potentialUnitInUnitLinks.Select(l => l.ChildUnitId).ToHashSet();
|
||
|
||
var parentUnitValues = await unitInValueService.Get()
|
||
.AsNoTracking()
|
||
.Include(uv => uv.Field)
|
||
.Include(uv => uv.Value)
|
||
.Where(uv => allParentIds.Contains(uv.UnitId))
|
||
.ToListAsync();
|
||
|
||
var childUnitValues = await unitInValueService.Get()
|
||
.AsNoTracking()
|
||
.Include(uv => uv.Field)
|
||
.Include(uv => uv.Value)
|
||
.Where(uv => allChildIds.Contains(uv.UnitId))
|
||
.ToListAsync();
|
||
|
||
var parentValuesMap = parentUnitValues
|
||
.GroupBy(uv => uv.UnitId)
|
||
.ToDictionary(g => g.Key, g => g.ToList());
|
||
|
||
var childValuesMap = childUnitValues
|
||
.GroupBy(uv => uv.UnitId)
|
||
.ToDictionary(g => g.Key, g => g.ToList());
|
||
|
||
logger.LogDebug("Применение {Count} RelationshipFilters к найденным связям.", relationshipFilters.Count);
|
||
|
||
var filteredUnitInUnitLinks = new List<UnitInUnit>();
|
||
foreach (var link in potentialUnitInUnitLinks)
|
||
{
|
||
bool linkMatchesAllFilters = true;
|
||
foreach (var rf in relationshipFilters)
|
||
{
|
||
var valuesToCheck = rf.IsParent ? parentValuesMap.GetValueOrDefault(link.ParentUnitId, new List<UnitInValue>()) : childValuesMap.GetValueOrDefault(link.ChildUnitId, new List<UnitInValue>());
|
||
|
||
bool filterMatch = valuesToCheck.Any(uv =>
|
||
uv.FieldId == rf.FieldId &&
|
||
uv.Value != null &&
|
||
uv.Value.Value != null &&
|
||
uv.Value.Value.Contains(rf.ValueMask ?? "", StringComparison.OrdinalIgnoreCase)
|
||
);
|
||
|
||
if (rf.IsInverse)
|
||
filterMatch = !filterMatch;
|
||
|
||
if (!filterMatch)
|
||
{
|
||
linkMatchesAllFilters = false;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (linkMatchesAllFilters)
|
||
{
|
||
filteredUnitInUnitLinks.Add(link);
|
||
}
|
||
}
|
||
|
||
logger.LogDebug("После применения RelationshipFilters осталось {Count} связей UnitInUnit.", filteredUnitInUnitLinks.Count);
|
||
|
||
// --- Сгруппировать юниты ---
|
||
var groupedRelationships = new Dictionary<Guid, List<Guid>>();
|
||
foreach (var link in filteredUnitInUnitLinks)
|
||
{
|
||
var parentUnitId = link.ParentUnitId;
|
||
var childUnitId = link.ChildUnitId;
|
||
|
||
if (unitIdsWithValidGroupingFieldSet.Contains(parentUnitId))
|
||
{
|
||
if (!groupedRelationships.ContainsKey(childUnitId))
|
||
{
|
||
groupedRelationships[childUnitId] = new List<Guid>();
|
||
}
|
||
groupedRelationships[childUnitId].Add(parentUnitId);
|
||
}
|
||
else if (unitIdsWithValidGroupingFieldSet.Contains(childUnitId))
|
||
{
|
||
if (!groupedRelationships.ContainsKey(parentUnitId))
|
||
{
|
||
groupedRelationships[parentUnitId] = new List<Guid>();
|
||
}
|
||
groupedRelationships[parentUnitId].Add(childUnitId);
|
||
}
|
||
}
|
||
|
||
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)
|
||
{
|
||
var key = kvp.Key;
|
||
var units = kvp.Value;
|
||
foreach (var unitId in units)
|
||
{
|
||
if (!unitToKeys.ContainsKey(unitId))
|
||
{
|
||
unitToKeys[unitId] = new List<Guid>();
|
||
}
|
||
unitToKeys[unitId].Add(key);
|
||
}
|
||
}
|
||
|
||
// Получаем имена всех конфликтующих ключей
|
||
var conflictKeys = unitToKeys
|
||
.Where(kvp => kvp.Value.Count > 1)
|
||
.SelectMany(kvp => kvp.Value)
|
||
.Distinct()
|
||
.ToList();
|
||
|
||
var unitNamesMap = await unitService.Get()
|
||
.Where(u => conflictKeys.Contains(u.Id))
|
||
.ToDictionaryAsync(u => u.Id, u => u.Name ?? string.Empty);
|
||
|
||
foreach (var conflictedUnitEntry in unitToKeys.Where(kvp => kvp.Value.Count > 1))
|
||
{
|
||
var unitId = conflictedUnitEntry.Key;
|
||
var keysForUnit = conflictedUnitEntry.Value;
|
||
|
||
// Сортируем по: 1) кол-во юнитов (убывание), 2) имя ключа (возрастание)
|
||
var sortedKeys = keysForUnit
|
||
.Select(key => (
|
||
key,
|
||
count: groupedRelationships[key].Count,
|
||
name: unitNamesMap.GetValueOrDefault(key, "")
|
||
))
|
||
.OrderByDescending(x => x.count)
|
||
.ThenBy(x => x.name)
|
||
.ToList();
|
||
|
||
var bestKey = sortedKeys.First().key;
|
||
|
||
// Удаляем юнит из ВСЕХ групп, кроме лучшей
|
||
foreach (var key in keysForUnit)
|
||
{
|
||
if (key != bestKey && groupedRelationships.ContainsKey(key))
|
||
{
|
||
groupedRelationships[key].Remove(unitId);
|
||
}
|
||
}
|
||
}
|
||
|
||
// Удаляем пустые группы
|
||
var emptyKeys = groupedRelationships
|
||
.Where(kvp => !kvp.Value.Any())
|
||
.Select(kvp => kvp.Key)
|
||
.ToList();
|
||
|
||
foreach (var key in emptyKeys)
|
||
{
|
||
groupedRelationships.Remove(key);
|
||
}
|
||
// ===============================================
|
||
|
||
logger.LogDebug("Сформировано {Count} групп по связанным юнитам после разрешения конфликтов.", groupedRelationships.Count);
|
||
|
||
// === DEBUG: ЛОГИРОВАНИЕ ПОСЛЕ РАЗРЕШЕНИЯ КОНФЛИКТОВ ===
|
||
#if DEBUG
|
||
await LogGroupingDebugInfo(jobGroup, groupedRelationships, true);
|
||
#endif
|
||
// ======================================================
|
||
|
||
// === Основной цикл обработки ===
|
||
foreach (var kvp in groupedRelationships)
|
||
{
|
||
var relationshipUnitId = kvp.Key;
|
||
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()
|
||
.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();
|
||
|
||
int maxValueForSplitting = maxJob.MaxValueRelationships!.Value;
|
||
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("Связанный юнит {RelationshipUnitId}: разбит на {GroupCount} подгрупп.", relationshipUnitId, childUnitGroups.Count);
|
||
|
||
for (int i = 0; i < childUnitGroups.Count; i++)
|
||
{
|
||
var subGroup = childUnitGroups[i];
|
||
var subGroupSize = subGroup.Count;
|
||
logger.LogDebug("Обработка подгруппы {Index} связанного юнита {RelationshipUnitId}, размер {Size}.", i, relationshipUnitId, subGroupSize);
|
||
|
||
Job? targetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
||
|
||
var existingTemplatesForRelationship = await templateService.Get()
|
||
.AsNoTracking()
|
||
.Include(t => t.Unit)
|
||
.Include(t => t.Job)
|
||
.ThenInclude(t => t!.Tnk)
|
||
.Include(t => t.Job)
|
||
.ThenInclude(t => t!.Group)
|
||
.ThenInclude(t => t!.GroupType)
|
||
.Include(t => t.UnitsInTemplate)
|
||
.ThenInclude(uit => uit.Unit)
|
||
.Where(t => t.JobId == targetJob.Id && t.UnitId == relationshipUnitId && t.Index == i && t.StatusTypeId == TemplateStatusTypeEnum.Used)
|
||
.ToListAsync();
|
||
|
||
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
|
||
// ===========================================
|
||
|
||
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();
|
||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(existingTemplateForSubGroup);
|
||
|
||
if (!string.Equals(existingTemplateForSubGroup.Name, expectedName, StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
logger.LogDebug("Шаблон {TemplateId} требует обновления имени.", existingTemplateForSubGroup.Id);
|
||
var nextRun = await GetNextRunAsync(targetJob, existingTemplateForSubGroup.NextRun);
|
||
var updateRequest = new TemplateUpdaterMq
|
||
{
|
||
TemplateId = existingTemplateForSubGroup.Id,
|
||
JobId = targetJob.Id,
|
||
UnitId = relationshipUnitId,
|
||
Name = expectedName,
|
||
IsActiveTemplate = existingTemplateForSubGroup.IsActiveTemplate,
|
||
IsActiveSchedule = existingTemplateForSubGroup.IsActiveSchedule,
|
||
LastRun = existingTemplateForSubGroup.LastRun,
|
||
NextRun = nextRun,
|
||
Index = i,
|
||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||
Initiator = initiator,
|
||
UnitsInTemplate = subGroup
|
||
};
|
||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||
}
|
||
else
|
||
{
|
||
logger.LogDebug("Шаблон {TemplateId} полностью актуален.", existingTemplateForSubGroup.Id);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
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);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(targetJob.Id, relationshipUnitId, initiator);
|
||
if (reusableTemplate != null)
|
||
{
|
||
logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, связанного юнита {RelationshipId}, Index {Index}.", reusableTemplate.Id, targetJob.Id, relationshipUnitId, i);
|
||
|
||
var tempTemplateForName = new Template
|
||
{
|
||
Id = reusableTemplate.Id,
|
||
Name = reusableTemplate.Name,
|
||
JobId = targetJob.Id,
|
||
UnitId = relationshipUnitId,
|
||
Index = i,
|
||
Job = targetJob,
|
||
Unit = reusableTemplate.Unit,
|
||
UnitsInTemplate = subGroup.Select(id => new UnitsInTemplate { UnitId = id }).ToList()
|
||
};
|
||
|
||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName);
|
||
var nextRun = await GetNextRunAsync(targetJob);
|
||
|
||
var updateRequest = new TemplateUpdaterMq
|
||
{
|
||
TemplateId = reusableTemplate.Id,
|
||
JobId = targetJob.Id,
|
||
UnitId = relationshipUnitId,
|
||
Name = expectedName,
|
||
IsActiveTemplate = targetJob.AutoControl?.InitUsedTemplateState ?? DefaultUsedTemplateState,
|
||
IsActiveSchedule = targetJob.AutoControl?.InitUsedScheduleState ?? DefaultUsedScheduleState,
|
||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||
Initiator = initiator,
|
||
NextRun = nextRun,
|
||
Index = i,
|
||
UnitsInTemplate = subGroup
|
||
};
|
||
|
||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||
}
|
||
else
|
||
{
|
||
logger.LogDebug("Создание нового шаблона для Job {JobId}, связанного юнита {RelationshipUnitId}, Index {Index}, с {Count} юнитами.", targetJob.Id, relationshipUnitId, i, subGroup.Count);
|
||
await CreateGroupedTemplateAsync(targetJob.Id, relationshipUnitId, subGroup, i, initiator);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// === Деактивация ===
|
||
var expectedTemplateKeys = new HashSet<(Guid JobId, Guid UnitId, int Index)>();
|
||
foreach (var kvp in groupedRelationships)
|
||
{
|
||
var relationshipUnitId = kvp.Key;
|
||
var childUnitIds = kvp.Value;
|
||
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 = sortedChildUnitIdsForDeactivate
|
||
.Select((id, index) => new { id, groupIndex = index / maxValueForSplitting })
|
||
.GroupBy(x => x.groupIndex)
|
||
.Select(g => g.Select(x => x.id).ToList())
|
||
.ToList();
|
||
for (int i = 0; i < childUnitGroups.Count; i++)
|
||
{
|
||
var subGroup = childUnitGroups[i];
|
||
var subGroupSize = subGroup.Count;
|
||
Job? targetJobForExpectedKey = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
||
expectedTemplateKeys.Add((targetJobForExpectedKey.Id, relationshipUnitId, i));
|
||
}
|
||
}
|
||
|
||
// Получаем ВСЕ шаблоны для JobGroup (не только для текущих relationshipUnitIds)
|
||
var allJobIdsInGroup = jobsInGroup.Select(j => j.Id).ToHashSet();
|
||
var allExistingTemplatesInGroup = await templateService.Get()
|
||
.AsNoTracking()
|
||
.Include(t=>t.Unit)
|
||
.Include(t => t.UnitsInTemplate)
|
||
.Where(t => allJobIdsInGroup.Contains(t.JobId) &&
|
||
t.StatusTypeId == TemplateStatusTypeEnum.Used &&
|
||
t.Job!.GroupId == jobGroupId)
|
||
.ToListAsync();
|
||
|
||
foreach (var existingTemplate in allExistingTemplatesInGroup)
|
||
{
|
||
var key = (existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index ?? -1);
|
||
if (!expectedTemplateKeys.Contains(key))
|
||
{
|
||
logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, Relationship {UnitId}, Index {Index}).",
|
||
existingTemplate.Id, existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index);
|
||
await templateDeactivator.DeactivateTemplateAsync(existingTemplate, initiator);
|
||
}
|
||
}
|
||
|
||
// === Успешное завершение ===
|
||
await UpdateMatchingStatusAsync(jobGroupId, "Синхронизация завершена успешно");
|
||
await matchingStatusService.DeleteMatchingStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup);
|
||
logger.LogInformation("Синхронизация шаблонов завершена для JobGroup {JobGroupId}.", jobGroupId);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
logger.LogError(ex, "Ошибка при синхронизации JobGroup {JobGroupId}", jobGroupId);
|
||
await UpdateMatchingStatusAsync(jobGroupId, $"Ошибка: {ex.Message}");
|
||
throw;
|
||
}
|
||
}
|
||
|
||
#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
|
||
.Where(j => j.MaxValueRelationships.HasValue && j.MaxValueRelationships.Value == subGroupSize)
|
||
.FirstOrDefault();
|
||
|
||
if (targetJob == null)
|
||
{
|
||
targetJob = jobsInGroup
|
||
.Where(j => j.MaxValueRelationships.HasValue && j.MaxValueRelationships.Value >= subGroupSize)
|
||
.OrderBy(j => j.MaxValueRelationships!.Value)
|
||
.FirstOrDefault();
|
||
}
|
||
|
||
if (targetJob == null)
|
||
{
|
||
targetJob = maxJob;
|
||
logger.LogDebug("Для подгруппы размером {Size} не найден подходящий Job, используем maxJob {MaxJobId}.", subGroupSize, maxJob.Id);
|
||
}
|
||
else
|
||
{
|
||
logger.LogDebug("Для подгруппы размером {Size} выбран Job {TargetJobId} с MaxValueRelationships {MaxValue}.", subGroupSize, targetJob.Id, targetJob.MaxValueRelationships);
|
||
}
|
||
|
||
return targetJob;
|
||
}
|
||
|
||
private async Task UpdateTemplateUnitsAsync(Template template, List<Guid> newUnitIds, Job targetJob, HistoryInitiator initiator)
|
||
{
|
||
template.StatusTypeId = TemplateStatusTypeEnum.Updating;
|
||
template.DateModified = DateTimeOffset.UtcNow;
|
||
if (!await templateService.CommitAsync(initiator))
|
||
{
|
||
logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating для обновления юнитов.", template.Id);
|
||
return;
|
||
}
|
||
|
||
var tempTemplateForName = new Template
|
||
{
|
||
Id = template.Id,
|
||
Name = template.Name,
|
||
JobId = targetJob.Id,
|
||
UnitId = template.UnitId,
|
||
Index = template.Index,
|
||
Job = targetJob,
|
||
Unit = template.Unit,
|
||
UnitsInTemplate = newUnitIds.Select(id => new UnitsInTemplate { UnitId = id }).ToList()
|
||
};
|
||
|
||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName);
|
||
var nextRun = await GetNextRunAsync(targetJob, template.NextRun);
|
||
var updateRequest = new TemplateUpdaterMq
|
||
{
|
||
TemplateId = template.Id,
|
||
JobId = targetJob.Id,
|
||
UnitId = template.UnitId,
|
||
Name = expectedName,
|
||
IsActiveTemplate = template.IsActiveTemplate,
|
||
IsActiveSchedule = template.IsActiveSchedule,
|
||
LastRun = template.LastRun,
|
||
NextRun = nextRun,
|
||
Index = template.Index,
|
||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||
Initiator = initiator,
|
||
UnitsInTemplate = newUnitIds
|
||
};
|
||
|
||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||
}
|
||
|
||
private async Task CreateGroupedTemplateAsync(Guid jobId, Guid relationshipUnitId, List<Guid> unitIds, int index, HistoryInitiator initiator)
|
||
{
|
||
logger.LogInformation("Создание нового группового шаблона для Job {JobId}, связанного юнита {RelationshipUnitId}, Index {Index}, с {Count} юнитами.", jobId, relationshipUnitId, index, unitIds.Count);
|
||
|
||
var mqRequest = new TemplateGeneratorMq
|
||
{
|
||
JobId = jobId,
|
||
UnitId = relationshipUnitId,
|
||
UnitsInTemplate = unitIds,
|
||
Index = index,
|
||
HistoryInitiator = initiator
|
||
};
|
||
|
||
var msg = JsonSerializer.Serialize(mqRequest);
|
||
var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg });
|
||
|
||
if (!result.IsSuccess)
|
||
logger.LogError("Ошибка отправки команды создания группового шаблона для Job {JobId}, связанного юнита {RelationshipUnitId}, Index {Index}.", jobId, relationshipUnitId, index);
|
||
}
|
||
|
||
private async Task<DateTimeOffset> GetNextRunAsync(Job targetJob, DateTimeOffset? currentNextRun = null)
|
||
{
|
||
var referenceDate = targetJob.Group?.ReferenceDate ?? DateTimeOffset.UtcNow;
|
||
return await esppScheduleTransformService.GetNextDateAsync(targetJob.GroupId, referenceDate);
|
||
}
|
||
|
||
private async Task UpdateMatchingStatusAsync(Guid jobGroupId, string comment)
|
||
{
|
||
var status = new MatchingStatusItemDto
|
||
{
|
||
DateStart = DateTimeOffset.UtcNow,
|
||
Action = TemplateMatcherActionEnum.Sync,
|
||
Comment = comment
|
||
};
|
||
|
||
await matchingStatusService.SetMatchingStatusAsync(
|
||
jobGroupId,
|
||
SyncTaskEntityTypeEnum.JobGroup,
|
||
new MatchingStatusItem { Data = status, Timestamp = DateTimeOffset.UtcNow, Source = nameof(GroupedTemplateSynchronizer) },
|
||
TimeSpan.FromMinutes(30)
|
||
);
|
||
}
|
||
} |