feat(tempateMatcher): в матчеры добавлено взаимодействие с MatchingStatusService, для отображения статуса операции
This commit is contained in:
@@ -4,6 +4,7 @@ using PARR.BLL.Domain.Mq;
|
|||||||
using PARR.BLL.Services.Interfaces;
|
using PARR.BLL.Services.Interfaces;
|
||||||
using PARR.Common.Domain;
|
using PARR.Common.Domain;
|
||||||
using PARR.Constants;
|
using PARR.Constants;
|
||||||
|
using PARR.DAL.Cache.Models;
|
||||||
using PARR.DAL.DomainServices.Interfaces;
|
using PARR.DAL.DomainServices.Interfaces;
|
||||||
using PARR.DAL.Models;
|
using PARR.DAL.Models;
|
||||||
using PARR.DAL.Models.Job;
|
using PARR.DAL.Models.Job;
|
||||||
@@ -43,6 +44,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
private readonly ITemplateDeactivator templateDeactivator;
|
private readonly ITemplateDeactivator templateDeactivator;
|
||||||
private readonly ITemplateNameNormalizer templateNameNormalizer;
|
private readonly ITemplateNameNormalizer templateNameNormalizer;
|
||||||
private readonly ITemplateUpdaterMqSender templateUpdaterMqSender;
|
private readonly ITemplateUpdaterMqSender templateUpdaterMqSender;
|
||||||
|
private readonly IMatchingStatusService matchingStatusService;
|
||||||
|
|
||||||
public GroupedTemplateSynchronizer(
|
public GroupedTemplateSynchronizer(
|
||||||
ILogger<GroupedTemplateSynchronizer> logger,
|
ILogger<GroupedTemplateSynchronizer> logger,
|
||||||
@@ -60,7 +62,8 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
IUnitFieldService unitFieldService,
|
IUnitFieldService unitFieldService,
|
||||||
ITemplateDeactivator templateDeactivator,
|
ITemplateDeactivator templateDeactivator,
|
||||||
ITemplateNameNormalizer templateNameNormalizer,
|
ITemplateNameNormalizer templateNameNormalizer,
|
||||||
ITemplateUpdaterMqSender templateUpdaterMqSender
|
ITemplateUpdaterMqSender templateUpdaterMqSender,
|
||||||
|
IMatchingStatusService matchingStatusService
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
@@ -79,6 +82,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
this.templateDeactivator = templateDeactivator;
|
this.templateDeactivator = templateDeactivator;
|
||||||
this.templateNameNormalizer = templateNameNormalizer;
|
this.templateNameNormalizer = templateNameNormalizer;
|
||||||
this.templateUpdaterMqSender = templateUpdaterMqSender;
|
this.templateUpdaterMqSender = templateUpdaterMqSender;
|
||||||
|
this.matchingStatusService = matchingStatusService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
|
public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
|
||||||
@@ -90,6 +94,30 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
{
|
{
|
||||||
logger.LogDebug("Начало синхронизации шаблонов для JobGroup {JobGroupId}", jobGroupId);
|
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'ы
|
// 1. Получить JobGroup и связанные Job'ы
|
||||||
var jobGroup = await jobGroupService.Get()
|
var jobGroup = await jobGroupService.Get()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
@@ -106,6 +134,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
if (jobGroup == null || jobGroup.Jobs == null || !jobGroup.Jobs.Any())
|
if (jobGroup == null || jobGroup.Jobs == null || !jobGroup.Jobs.Any())
|
||||||
{
|
{
|
||||||
logger.LogWarning("JobGroup {JobGroupId} не найден или не содержит Job'ов.", jobGroupId);
|
logger.LogWarning("JobGroup {JobGroupId} не найден или не содержит Job'ов.", jobGroupId);
|
||||||
|
await UpdateMatchingStatusAsync(jobGroupId, "JobGroup не найден или пуст");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,13 +145,14 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
if (workGroupField == null)
|
if (workGroupField == null)
|
||||||
{
|
{
|
||||||
logger.LogError("Поле 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' не найдено в справочнике полей. Синхронизация прервана.");
|
logger.LogError("Поле 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' не найдено в справочнике полей. Синхронизация прервана.");
|
||||||
|
await UpdateMatchingStatusAsync(jobGroupId, "Ошибка: поле 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' не найдено");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var workGroupFieldId = workGroupField.Id;
|
var workGroupFieldId = workGroupField.Id;
|
||||||
|
|
||||||
var regionalGroupValueIds = regionalEkPtkGroupService.Get()
|
var regionalGroupValueIds = regionalEkPtkGroupService.Get()
|
||||||
.Select(g => g.FieldValueId)
|
.Select(g => g.FieldValueId)
|
||||||
.ToList(); // Получаем список UnitFieldValue.Id
|
.ToList();
|
||||||
|
|
||||||
logger.LogDebug("Найдено {Count} значений из UnitRegionalEkPtkGroup для проверки поля 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК'.", regionalGroupValueIds.Count);
|
logger.LogDebug("Найдено {Count} значений из UnitRegionalEkPtkGroup для проверки поля 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК'.", regionalGroupValueIds.Count);
|
||||||
|
|
||||||
@@ -135,10 +165,10 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
if (maxJob == null)
|
if (maxJob == null)
|
||||||
{
|
{
|
||||||
logger.LogWarning("В JobGroup {JobGroupId} не найдено Job с установленным MaxValueRelationships.", jobGroupId);
|
logger.LogWarning("В JobGroup {JobGroupId} не найдено Job с установленным MaxValueRelationships.", jobGroupId);
|
||||||
|
await UpdateMatchingStatusAsync(jobGroupId, "Не найден Job с MaxValueRelationships");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что UnitFilters и RelationshipFilters загружены (если используется для выбора targetJob)
|
|
||||||
if (maxJob.UnitFilters == null)
|
if (maxJob.UnitFilters == null)
|
||||||
{
|
{
|
||||||
logger.LogWarning("Job {JobId} не содержит UnitFilters.", maxJob.Id);
|
logger.LogWarning("Job {JobId} не содержит UnitFilters.", maxJob.Id);
|
||||||
@@ -151,11 +181,11 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
if (expectedUnitIds == null || !expectedUnitIds.Any())
|
if (expectedUnitIds == null || !expectedUnitIds.Any())
|
||||||
{
|
{
|
||||||
logger.LogInformation("Для JobGroup {JobGroupId} фильтры не дали Unit'ов.", jobGroupId);
|
logger.LogInformation("Для JobGroup {JobGroupId} фильтры не дали Unit'ов.", jobGroupId);
|
||||||
|
await UpdateMatchingStatusAsync(jobGroupId, "Фильтры не дали Unit'ов");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
// Отладка: проверить, есть ли юнит в expectedUnitIds
|
|
||||||
if (expectedUnitIds.Contains(targetUnitId))
|
if (expectedUnitIds.Contains(targetUnitId))
|
||||||
{
|
{
|
||||||
logger.LogDebug("Юнит {TargetUnitId} найден в expectedUnitIds.", targetUnitId);
|
logger.LogDebug("Юнит {TargetUnitId} найден в expectedUnitIds.", targetUnitId);
|
||||||
@@ -166,16 +196,16 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// 4. Отфильтровать expectedUnitIds по GroupingUnitFieldId (дополнительный фильтр)
|
// 4. Отфильтровать expectedUnitIds по GroupingUnitFieldId
|
||||||
if (!jobGroup.GroupingUnitFieldId.HasValue)
|
if (!jobGroup.GroupingUnitFieldId.HasValue)
|
||||||
{
|
{
|
||||||
logger.LogError("JobGroup {JobGroupId} не имеет GroupingUnitFieldId, необходимого для группировки.", jobGroupId);
|
logger.LogError("JobGroup {JobGroupId} не имеет GroupingUnitFieldId, необходимого для группировки.", jobGroupId);
|
||||||
|
await UpdateMatchingStatusAsync(jobGroupId, "Отсутствует GroupingUnitFieldId");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var groupingFieldId = jobGroup.GroupingUnitFieldId.Value;
|
var groupingFieldId = jobGroup.GroupingUnitFieldId.Value;
|
||||||
|
|
||||||
// Загрузить UnitValues для юнитов из expectedUnitIds, чтобы проверить GroupingUnitFieldId
|
|
||||||
var expectedUnitsWithGroupingField = await unitService.Get()
|
var expectedUnitsWithGroupingField = await unitService.Get()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(u => u.UnitValues)
|
.Include(u => u.UnitValues)
|
||||||
@@ -189,7 +219,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
.ToHashSet();
|
.ToHashSet();
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
// Отладка: проверить, есть ли юнит в unitIdsWithValidGroupingFieldSet
|
|
||||||
if (unitIdsWithValidGroupingFieldSet.Contains(targetUnitId))
|
if (unitIdsWithValidGroupingFieldSet.Contains(targetUnitId))
|
||||||
{
|
{
|
||||||
logger.LogDebug("Юнит {TargetUnitId} найден в unitIdsWithValidGroupingFieldSet.", targetUnitId);
|
logger.LogDebug("Юнит {TargetUnitId} найден в unitIdsWithValidGroupingFieldSet.", targetUnitId);
|
||||||
@@ -205,6 +234,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
if (!unitIdsWithValidGroupingFieldSet.Any())
|
if (!unitIdsWithValidGroupingFieldSet.Any())
|
||||||
{
|
{
|
||||||
logger.LogInformation("После фильтрации по GroupingUnitFieldId в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId);
|
logger.LogInformation("После фильтрации по GroupingUnitFieldId в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId);
|
||||||
|
await UpdateMatchingStatusAsync(jobGroupId, "Нет юнитов после фильтрации по GroupingUnitFieldId");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,7 +247,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
);
|
);
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
// Отладка: проверить, есть ли юнит в unitIdsWithValidWorkGroupFieldSet
|
|
||||||
if (unitIdsWithValidWorkGroupFieldSet.Contains(targetUnitId))
|
if (unitIdsWithValidWorkGroupFieldSet.Contains(targetUnitId))
|
||||||
{
|
{
|
||||||
logger.LogDebug("Юнит {TargetUnitId} найден в unitIdsWithValidWorkGroupFieldSet.", targetUnitId);
|
logger.LogDebug("Юнит {TargetUnitId} найден в unitIdsWithValidWorkGroupFieldSet.", targetUnitId);
|
||||||
@@ -233,38 +262,23 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
if (!unitIdsWithValidWorkGroupFieldSet.Any())
|
if (!unitIdsWithValidWorkGroupFieldSet.Any())
|
||||||
{
|
{
|
||||||
logger.LogInformation("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId);
|
logger.LogInformation("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК' в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId);
|
||||||
|
await UpdateMatchingStatusAsync(jobGroupId, "Нет юнитов после фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК'");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обновляем список юнитов, прошедших оба фильтра
|
|
||||||
unitIdsWithValidGroupingFieldSet = unitIdsWithValidWorkGroupFieldSet;
|
unitIdsWithValidGroupingFieldSet = unitIdsWithValidWorkGroupFieldSet;
|
||||||
|
|
||||||
// 5. Получить RelationshipFilters из maxJob
|
// 5. Получить RelationshipFilters из maxJob
|
||||||
var relationshipFilters = maxJob.UnitFilters?.SelectMany(uf => uf.RelationshipFilters).ToList() ?? new List<JobRelationshipFilter>();
|
var relationshipFilters = maxJob.UnitFilters?.SelectMany(uf => uf.RelationshipFilters).ToList() ?? new List<JobRelationshipFilter>();
|
||||||
|
|
||||||
// --- Найти и отфильтровать UnitInUnit связи ---
|
logger.LogDebug("Получение связей UnitInUnit для юнитов, прошедших фильтрацию...");
|
||||||
logger.LogDebug("Получение связей UnitInUnit для юнитов, прошедших фильтрацию по GroupingUnitFieldId и 'РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК'.");
|
|
||||||
var potentialUnitInUnitLinks = await unitInUnitService.Get()
|
var potentialUnitInUnitLinks = await unitInUnitService.Get()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(link => unitIdsWithValidGroupingFieldSet.Contains(link.ParentUnitId) || unitIdsWithValidGroupingFieldSet.Contains(link.ChildUnitId))
|
.Where(link => unitIdsWithValidGroupingFieldSet.Contains(link.ParentUnitId) || unitIdsWithValidGroupingFieldSet.Contains(link.ChildUnitId))
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
// Отладка: проверить, юнит участвует в potentialUnitInUnitLinks
|
|
||||||
var potentialLinksContainingTarget = potentialUnitInUnitLinks.Where(l => l.ParentUnitId == targetUnitId || l.ChildUnitId == targetUnitId).ToList();
|
|
||||||
if (potentialLinksContainingTarget.Any())
|
|
||||||
{
|
|
||||||
logger.LogDebug("Юнит {TargetUnitId} участвует в {Count} потенциальных связях UnitInUnit.", targetUnitId, potentialLinksContainingTarget.Count);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.LogDebug("Юнит {TargetUnitId} НЕ участвует в потенциальных связях UnitInUnit.", targetUnitId);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
logger.LogDebug("Найдено {Count} потенциальных связей UnitInUnit.", potentialUnitInUnitLinks.Count);
|
logger.LogDebug("Найдено {Count} потенциальных связей UnitInUnit.", potentialUnitInUnitLinks.Count);
|
||||||
|
|
||||||
// Загрузить UnitInValue для всех ParentUnitId и ChildUnitId из potentialUnitInUnitLinks
|
|
||||||
var allParentIds = potentialUnitInUnitLinks.Select(l => l.ParentUnitId).ToHashSet();
|
var allParentIds = potentialUnitInUnitLinks.Select(l => l.ParentUnitId).ToHashSet();
|
||||||
var allChildIds = potentialUnitInUnitLinks.Select(l => l.ChildUnitId).ToHashSet();
|
var allChildIds = potentialUnitInUnitLinks.Select(l => l.ChildUnitId).ToHashSet();
|
||||||
|
|
||||||
@@ -282,7 +296,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
.Where(uv => allChildIds.Contains(uv.UnitId))
|
.Where(uv => allChildIds.Contains(uv.UnitId))
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
// Сгруппировать значения по UnitId для быстрого доступа
|
|
||||||
var parentValuesMap = parentUnitValues
|
var parentValuesMap = parentUnitValues
|
||||||
.GroupBy(uv => uv.UnitId)
|
.GroupBy(uv => uv.UnitId)
|
||||||
.ToDictionary(g => g.Key, g => g.ToList());
|
.ToDictionary(g => g.Key, g => g.ToList());
|
||||||
@@ -291,7 +304,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
.GroupBy(uv => uv.UnitId)
|
.GroupBy(uv => uv.UnitId)
|
||||||
.ToDictionary(g => g.Key, g => g.ToList());
|
.ToDictionary(g => g.Key, g => g.ToList());
|
||||||
|
|
||||||
// Применить фильтры к связям
|
|
||||||
logger.LogDebug("Применение {Count} RelationshipFilters к найденным связям.", relationshipFilters.Count);
|
logger.LogDebug("Применение {Count} RelationshipFilters к найденным связям.", relationshipFilters.Count);
|
||||||
var filteredUnitInUnitLinks = new List<UnitInUnit>();
|
var filteredUnitInUnitLinks = new List<UnitInUnit>();
|
||||||
foreach (var link in potentialUnitInUnitLinks)
|
foreach (var link in potentialUnitInUnitLinks)
|
||||||
@@ -325,22 +337,9 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
// Отладка: проверить, юнит участвует в filteredUnitInUnitLinks
|
|
||||||
var filteredLinksContainingTarget = filteredUnitInUnitLinks.Where(l => l.ParentUnitId == targetUnitId || l.ChildUnitId == targetUnitId).ToList();
|
|
||||||
if (filteredLinksContainingTarget.Any())
|
|
||||||
{
|
|
||||||
logger.LogDebug("Юнит {TargetUnitId} участвует в {Count} отфильтрованных связях UnitInUnit.", targetUnitId, filteredLinksContainingTarget.Count);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.LogDebug("Юнит {TargetUnitId} НЕ участвует в отфильтрованных связях UnitInUnit.", targetUnitId);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
logger.LogDebug("После применения RelationshipFilters осталось {Count} связей UnitInUnit.", filteredUnitInUnitLinks.Count);
|
logger.LogDebug("После применения RelationshipFilters осталось {Count} связей UnitInUnit.", filteredUnitInUnitLinks.Count);
|
||||||
|
|
||||||
// --- Сгруппировать юниты из unitIdsWithValidGroupingFieldSet по связанному юниту ---
|
// --- Сгруппировать юниты ---
|
||||||
var groupedRelationships = new Dictionary<Guid, List<Guid>>();
|
var groupedRelationships = new Dictionary<Guid, List<Guid>>();
|
||||||
foreach (var link in filteredUnitInUnitLinks)
|
foreach (var link in filteredUnitInUnitLinks)
|
||||||
{
|
{
|
||||||
@@ -367,24 +366,8 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
|
|
||||||
logger.LogDebug("Сформировано {Count} групп по связанным юнитам до разрешения конфликтов.", groupedRelationships.Count);
|
logger.LogDebug("Сформировано {Count} групп по связанным юнитам до разрешения конфликтов.", groupedRelationships.Count);
|
||||||
|
|
||||||
#if DEBUG
|
// --- Разрешение конфликта ---
|
||||||
// Отладка: проверить, юнит есть в groupedRelationships.Values
|
var unitToKeys = new Dictionary<Guid, List<Guid>>();
|
||||||
var allUnitsInGroups = groupedRelationships.Values.SelectMany(x => x).ToList();
|
|
||||||
if (allUnitsInGroups.Contains(targetUnitId))
|
|
||||||
{
|
|
||||||
logger.LogDebug("Юнит {TargetUnitId} найден в groupedRelationships.Values до разрешения конфликтов.", targetUnitId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.LogDebug("Юнит {TargetUnitId} НЕ найден в groupedRelationships.Values до разрешения конфликтов.", targetUnitId);
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.LogDebug("Содержимое groupedRelationships до разрешения конфликтов: [{Groups}]", string.Join(", ", groupedRelationships.Select(kvp => $"Key: {kvp.Key}, Values: [{string.Join(", ", kvp.Value)}]")));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// --- Разрешение конфликта - один юнит из unitIdsWithValidGroupingFieldSet только в одном списке значений ---
|
|
||||||
var unitToKeys = new Dictionary<Guid, List<Guid>>(); // Карта: юнит из списка -> список ключей, где он встречается
|
|
||||||
|
|
||||||
foreach (var kvp in groupedRelationships)
|
foreach (var kvp in groupedRelationships)
|
||||||
{
|
{
|
||||||
var key = kvp.Key;
|
var key = kvp.Key;
|
||||||
@@ -399,40 +382,13 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
// Отладка: проверить, юнит есть в unitToKeys до поиска конфликтов
|
|
||||||
if (unitToKeys.ContainsKey(targetUnitId))
|
|
||||||
{
|
|
||||||
logger.LogDebug("Юнит {TargetUnitId} найден в unitToKeys до поиска конфликтов, находится в {Count} группах: [{Groups}]", targetUnitId, unitToKeys[targetUnitId].Count, string.Join(", ", unitToKeys[targetUnitId]));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.LogDebug("Юнит {TargetUnitId} НЕ найден в unitToKeys до поиска конфликтов.", targetUnitId);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Найти юниты, которые находятся в нескольких списках
|
|
||||||
var conflictedUnits = unitToKeys.Where(kvp => kvp.Value.Count > 1).ToList();
|
var conflictedUnits = unitToKeys.Where(kvp => kvp.Value.Count > 1).ToList();
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
// Отладка: проверить, юнит в conflictedUnits
|
|
||||||
var targetConflictedEntry = conflictedUnits.FirstOrDefault(c => c.Key == targetUnitId);
|
|
||||||
if (targetConflictedEntry.Key != default)
|
|
||||||
{
|
|
||||||
logger.LogDebug("Юнит {TargetUnitId} находится в {Count} группах (конфликт).", targetUnitId, targetConflictedEntry.Value.Count);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.LogDebug("Юнит {TargetUnitId} НЕ находится в конфликте (не в нескольких группах).", targetUnitId);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
foreach (var conflictedUnitEntry in conflictedUnits)
|
foreach (var conflictedUnitEntry in conflictedUnits)
|
||||||
{
|
{
|
||||||
var unitId = conflictedUnitEntry.Key;
|
var unitId = conflictedUnitEntry.Key;
|
||||||
var keysForUnit = conflictedUnitEntry.Value;
|
var keysForUnit = conflictedUnitEntry.Value;
|
||||||
|
|
||||||
Guid bestKey = keysForUnit[0]; // Инициализируем первым ключом
|
Guid bestKey = keysForUnit[0];
|
||||||
int maxCount = groupedRelationships[bestKey].Count;
|
int maxCount = groupedRelationships[bestKey].Count;
|
||||||
|
|
||||||
for (int i = 1; i < keysForUnit.Count; i++)
|
for (int i = 1; i < keysForUnit.Count; i++)
|
||||||
@@ -446,54 +402,150 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Удалить юнит из списков всех ключей, кроме bestKey
|
|
||||||
foreach (var key in keysForUnit)
|
foreach (var key in keysForUnit)
|
||||||
{
|
{
|
||||||
if (key != bestKey)
|
if (key != bestKey)
|
||||||
{
|
{
|
||||||
groupedRelationships[key].Remove(unitId);
|
groupedRelationships[key].Remove(unitId);
|
||||||
logger.LogDebug("Юнит {UnitId} перемещён из группы {OldKey} в группу {BestKey} (по кол-ву).", unitId, key, bestKey);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
|
||||||
// Отладка: проверить, юнит есть в groupedRelationships.Values после разрешения конфликтов
|
|
||||||
var allUnitsInGroupsAfterConflictResolution = groupedRelationships.Values.SelectMany(x => x).ToList();
|
|
||||||
if (allUnitsInGroupsAfterConflictResolution.Contains(targetUnitId))
|
|
||||||
{
|
|
||||||
logger.LogDebug("Юнит {TargetUnitId} найден в groupedRelationships.Values после разрешения конфликтов.", targetUnitId);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.LogDebug("Юнит {TargetUnitId} НЕ найден в groupedRelationships.Values после разрешения конфликтов.", targetUnitId);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Удаляем ключи, у которых список стал пустым после разрешения конфликтов
|
|
||||||
var keysToRemove = groupedRelationships.Where(kvp => kvp.Value.Count == 0).Select(kvp => kvp.Key).ToList();
|
var keysToRemove = groupedRelationships.Where(kvp => kvp.Value.Count == 0).Select(kvp => kvp.Key).ToList();
|
||||||
foreach (var key in keysToRemove)
|
foreach (var key in keysToRemove)
|
||||||
{
|
{
|
||||||
groupedRelationships.Remove(key);
|
groupedRelationships.Remove(key);
|
||||||
logger.LogDebug("Ключ {Key} удалён, так как его список юнитов стал пустым после разрешения конфликтов.", key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.LogDebug("Сформировано {Count} групп по связанным юнитам после разрешения конфликтов.", groupedRelationships.Count);
|
logger.LogDebug("Сформировано {Count} групп по связанным юнитам после разрешения конфликтов.", groupedRelationships.Count);
|
||||||
|
|
||||||
#if DEBUG
|
// === Подсчёт операций ===
|
||||||
logger.LogDebug("Содержимое groupedRelationships после разрешения конфликтов: [{Groups}]", string.Join(", ", groupedRelationships.Select(kvp => $"Key: {kvp.Key}, Values: [{string.Join(", ", kvp.Value)}]")));
|
int toCreate = 0;
|
||||||
#endif
|
int toUpdate = 0;
|
||||||
|
int toDeactivate = 0;
|
||||||
|
|
||||||
// 7. Разбить каждую группу и сопоставить с Job
|
|
||||||
foreach (var kvp in groupedRelationships)
|
foreach (var kvp in groupedRelationships)
|
||||||
{
|
{
|
||||||
var relationshipUnitId = kvp.Key; // Связанный юнит (не из unitIdsWithValidGroupingFieldSet)
|
var childUnitIds = kvp.Value;
|
||||||
var childUnitIds = kvp.Value; // Юниты из unitIdsWithValidGroupingFieldSet, связанные с relationshipUnitId
|
if (childUnitIds.Count == 0) continue;
|
||||||
if (childUnitIds.Count == 0)
|
|
||||||
continue;
|
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();
|
||||||
|
|
||||||
|
for (int i = 0; i < childUnitGroups.Count; i++)
|
||||||
|
{
|
||||||
|
var subGroup = childUnitGroups[i];
|
||||||
|
var subGroupSize = subGroup.Count;
|
||||||
|
Job? targetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
||||||
|
|
||||||
|
var existingTemplatesForRelationship = await templateService.Get()
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(t=>t.Unit)
|
||||||
|
.Include(t=>t.Job)
|
||||||
|
.ThenInclude(t=>t.Group)
|
||||||
|
.ThenInclude(t=>t.GroupType)
|
||||||
|
.Include(t => t.Job)
|
||||||
|
.ThenInclude(t => t.Tnk)
|
||||||
|
.Include(t => t.UnitsInTemplate)
|
||||||
|
.Where(t => t.JobId == targetJob.Id && t.UnitId == kvp.Key && t.Index == i && t.StatusTypeId == TemplateStatusTypeEnum.Used)
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
if (existingTemplatesForRelationship.Any())
|
||||||
|
{
|
||||||
|
var existingTemplate = existingTemplatesForRelationship.First();
|
||||||
|
var existingUnitIds = existingTemplate.UnitsInTemplate.Select(uit => uit.UnitId).ToHashSet();
|
||||||
|
var newUnitIds = subGroup.ToHashSet();
|
||||||
|
|
||||||
|
if (!existingUnitIds.SetEquals(newUnitIds))
|
||||||
|
toUpdate++;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Обновляем UnitsInTemplate для корректной генерации имени
|
||||||
|
existingTemplate.UnitsInTemplate = subGroup.Select(id => new UnitsInTemplate { UnitId = id }).ToList();
|
||||||
|
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(existingTemplate);
|
||||||
|
if (!string.Equals(existingTemplate.Name, expectedName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
toUpdate++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
toCreate++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Подсчёт деактивации
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var allRelationshipUnitIds = groupedRelationships.Keys.ToHashSet();
|
||||||
|
var allJobIdsInGroup = jobsInGroup.Select(j => j.Id).ToHashSet();
|
||||||
|
|
||||||
|
var allExistingTemplatesInGroup = await templateService.Get()
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(t => t.UnitsInTemplate)
|
||||||
|
.Where(t => allJobIdsInGroup.Contains(t.JobId) && allRelationshipUnitIds.Contains(t.UnitId))
|
||||||
|
.ToListAsync();
|
||||||
|
|
||||||
|
foreach (var existingTemplate in allExistingTemplatesInGroup)
|
||||||
|
{
|
||||||
|
var key = (existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index ?? -1);
|
||||||
|
if (!expectedTemplateKeys.Contains(key))
|
||||||
|
toDeactivate++;
|
||||||
|
}
|
||||||
|
|
||||||
|
await UpdateMatchingStatusAsync(jobGroupId, $"Осталось: создать={toCreate}, обновить={toUpdate}, деактивировать={toDeactivate}");
|
||||||
|
|
||||||
|
// === Основной цикл обработки ===
|
||||||
|
int created = 0, updated = 0, deactivated = 0;
|
||||||
|
foreach (var kvp in groupedRelationships)
|
||||||
|
{
|
||||||
|
var relationshipUnitId = kvp.Key;
|
||||||
|
var childUnitIds = kvp.Value;
|
||||||
|
if (childUnitIds.Count == 0) continue;
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
// === Безопасная асинхронная загрузка имён для отладки ===
|
|
||||||
var debugUnitIds = childUnitIds.Concat(new[] { relationshipUnitId }).Distinct().ToList();
|
var debugUnitIds = childUnitIds.Concat(new[] { relationshipUnitId }).Distinct().ToList();
|
||||||
var debugUnits = await unitService.Get()
|
var debugUnits = await unitService.Get()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
@@ -511,7 +563,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
|
|
||||||
logger.LogDebug("Обработка связанного юнита {RelationshipUnitId} с {Count} юнитами из списка.", relationshipUnitId, childUnitIds.Count);
|
logger.LogDebug("Обработка связанного юнита {RelationshipUnitId} с {Count} юнитами из списка.", relationshipUnitId, childUnitIds.Count);
|
||||||
|
|
||||||
// --- СОРТИРОВКА childUnitIds ПО ИМЕНИ ---
|
|
||||||
var childUnitNameMap = await unitService.Get()
|
var childUnitNameMap = await unitService.Get()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Where(u => childUnitIds.Contains(u.Id))
|
.Where(u => childUnitIds.Contains(u.Id))
|
||||||
@@ -521,22 +572,15 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
.OrderBy(id => childUnitNameMap.GetValueOrDefault(id, id.ToString()))
|
.OrderBy(id => childUnitNameMap.GetValueOrDefault(id, id.ToString()))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Применяем ограничение MaxValueRelationships maxJob
|
int maxValueForSplitting = maxJob.MaxValueRelationships!.Value;
|
||||||
if (!maxJob.MaxValueRelationships.HasValue)
|
|
||||||
{
|
|
||||||
logger.LogWarning("Job {JobId} не заполнено MaxValueRelationships.", maxJob.Id);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int maxValueForSplitting = maxJob.MaxValueRelationships.Value;
|
|
||||||
var childUnitGroups = sortedChildUnitIds
|
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("Связанный юнит {RelationshipUnitId}: разбит на {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++)
|
||||||
{
|
{
|
||||||
var subGroup = childUnitGroups[i];
|
var subGroup = childUnitGroups[i];
|
||||||
@@ -544,19 +588,16 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
|
|
||||||
logger.LogDebug("Обработка подгруппы {Index} связанного юнита {RelationshipUnitId}, размер {Size}.", i, relationshipUnitId, subGroupSize);
|
logger.LogDebug("Обработка подгруппы {Index} связанного юнита {RelationshipUnitId}, размер {Size}.", i, relationshipUnitId, subGroupSize);
|
||||||
|
|
||||||
// Выбираем targetJob для подгруппы
|
|
||||||
Job? targetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
Job? targetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
||||||
|
|
||||||
// 9. Загрузить существующие шаблоны для targetJob, связанные с relationshipUnitId
|
|
||||||
var existingTemplatesForRelationship = await templateService.Get()
|
var existingTemplatesForRelationship = await templateService.Get()
|
||||||
|
.AsNoTracking()
|
||||||
.Include(t => t.Unit)
|
.Include(t => t.Unit)
|
||||||
.Include(t => t.Job)
|
.Include(t => t.Job)
|
||||||
.ThenInclude(t => t!.Tnk)
|
.ThenInclude(t => t!.Tnk)
|
||||||
.Include(t => t.Job)
|
.Include(t => t.Job)
|
||||||
.ThenInclude(t => t.Group)
|
.ThenInclude(t => t!.Group)
|
||||||
.ThenInclude(t => t.GroupType)
|
.ThenInclude(t => t!.GroupType)
|
||||||
.AsNoTracking()
|
|
||||||
.Include(t => t.UnitsInTemplate)
|
|
||||||
.Where(t => t.JobId == targetJob.Id && t.UnitId == relationshipUnitId && t.Index == i && t.StatusTypeId == TemplateStatusTypeEnum.Used)
|
.Where(t => t.JobId == targetJob.Id && t.UnitId == relationshipUnitId && t.Index == i && t.StatusTypeId == TemplateStatusTypeEnum.Used)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
@@ -564,19 +605,19 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
|
|
||||||
if (existingTemplateForSubGroup != null)
|
if (existingTemplateForSubGroup != null)
|
||||||
{
|
{
|
||||||
// Проверить, изменились ли юниты
|
|
||||||
var existingUnitIds = existingTemplateForSubGroup.UnitsInTemplate.Select(uit => uit.UnitId).ToHashSet();
|
var existingUnitIds = existingTemplateForSubGroup.UnitsInTemplate.Select(uit => uit.UnitId).ToHashSet();
|
||||||
var newUnitIds = subGroup.ToHashSet();
|
var newUnitIds = subGroup.ToHashSet();
|
||||||
|
|
||||||
if (existingUnitIds.SetEquals(newUnitIds))
|
if (existingUnitIds.SetEquals(newUnitIds))
|
||||||
{
|
{
|
||||||
logger.LogDebug("Шаблон {TemplateId} (Job {JobId}, Relationship {RelationshipId}, Index {Index}) актуален по юнитам.", existingTemplateForSubGroup.Id, targetJob.Id, relationshipUnitId, i);
|
logger.LogDebug("Шаблон {TemplateId} актуален по юнитам.", existingTemplateForSubGroup.Id);
|
||||||
|
|
||||||
// Проверить, изменилось ли имя шаблона
|
// Обновляем UnitsInTemplate для генерации имени
|
||||||
|
existingTemplateForSubGroup.UnitsInTemplate = subGroup.Select(id => new UnitsInTemplate { UnitId = id }).ToList();
|
||||||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(existingTemplateForSubGroup);
|
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(existingTemplateForSubGroup);
|
||||||
if (!string.Equals(existingTemplateForSubGroup.Name, expectedName, StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(existingTemplateForSubGroup.Name, expectedName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
logger.LogDebug("Шаблон {TemplateId} требует обновления имени: старое = '{OldName}', новое = '{NewName}'", existingTemplateForSubGroup.Id, existingTemplateForSubGroup.Name, expectedName);
|
logger.LogDebug("Шаблон {TemplateId} требует обновления имени.", existingTemplateForSubGroup.Id);
|
||||||
|
|
||||||
var nextRun = await GetNextRunAsync(targetJob, existingTemplateForSubGroup.NextRun);
|
var nextRun = await GetNextRunAsync(targetJob, existingTemplateForSubGroup.NextRun);
|
||||||
|
|
||||||
@@ -597,26 +638,27 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
};
|
};
|
||||||
|
|
||||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||||
|
updated++;
|
||||||
|
await UpdateMatchingStatusAsync(jobGroupId, $"Прогресс: создано={created}, обновлено={updated}, деактивировано={deactivated}. Осталось: создать={toCreate - created}, обновить={toUpdate - updated}, деактивировать={toDeactivate - deactivated}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.LogDebug("Шаблон {TemplateId} полностью актуален (юниты и имя).", existingTemplateForSubGroup.Id);
|
logger.LogDebug("Шаблон {TemplateId} полностью актуален.", existingTemplateForSubGroup.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.LogDebug("Шаблон {TemplateId} (Job {JobId}, Relationship {RelationshipId}, Index {Index}) требует обновления юнитов.", existingTemplateForSubGroup.Id, targetJob.Id, relationshipUnitId, i);
|
logger.LogDebug("Шаблон {TemplateId} требует обновления юнитов.", existingTemplateForSubGroup.Id);
|
||||||
|
|
||||||
// Пересчитать targetJob для нового размера subGroup
|
|
||||||
var newTargetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
var newTargetJob = SelectTargetJob(jobsInGroup, subGroupSize, maxJob);
|
||||||
|
|
||||||
// Если Job изменился — нужно обновить шаблон с новым JobId
|
|
||||||
if (newTargetJob.Id != existingTemplateForSubGroup.JobId)
|
if (newTargetJob.Id != existingTemplateForSubGroup.JobId)
|
||||||
{
|
{
|
||||||
logger.LogDebug("Job для шаблона {TemplateId} изменился: старый {OldJobId}, новый {NewJobId}.", existingTemplateForSubGroup.Id, existingTemplateForSubGroup.JobId, newTargetJob.Id);
|
logger.LogDebug("Job для шаблона {TemplateId} изменился.", existingTemplateForSubGroup.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
await UpdateTemplateUnitsAsync(existingTemplateForSubGroup, subGroup, newTargetJob, initiator);
|
await UpdateTemplateUnitsAsync(existingTemplateForSubGroup, subGroup, newTargetJob, initiator);
|
||||||
|
updated++;
|
||||||
|
await UpdateMatchingStatusAsync(jobGroupId, $"Прогресс: создано={created}, обновлено={updated}, деактивировано={deactivated}. Осталось: создать={toCreate - created}, обновить={toUpdate - updated}, деактивировать={toDeactivate - deactivated}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -627,7 +669,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
{
|
{
|
||||||
logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, связанного юнита {RelationshipId}, Index {Index}.", reusableTemplate.Id, targetJob.Id, relationshipUnitId, i);
|
logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, связанного юнита {RelationshipId}, Index {Index}.", reusableTemplate.Id, targetJob.Id, relationshipUnitId, i);
|
||||||
|
|
||||||
// === Создаём временный Template для нормализации имени ===
|
|
||||||
var tempTemplateForName = new Template
|
var tempTemplateForName = new Template
|
||||||
{
|
{
|
||||||
Id = reusableTemplate.Id,
|
Id = reusableTemplate.Id,
|
||||||
@@ -659,60 +700,21 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
};
|
};
|
||||||
|
|
||||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||||
|
created++;
|
||||||
|
await UpdateMatchingStatusAsync(jobGroupId, $"Прогресс: создано={created}, обновлено={updated}, деактивировано={deactivated}. Осталось: создать={toCreate - created}, обновить={toUpdate - updated}, деактивировать={toDeactivate - deactivated}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
logger.LogDebug("Создание нового шаблона для Job {JobId}, связанного юнита {RelationshipUnitId}, 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);
|
||||||
|
created++;
|
||||||
|
await UpdateMatchingStatusAsync(jobGroupId, $"Прогресс: создано={created}, обновлено={updated}, деактивировано={deactivated}. Осталось: создать={toCreate - created}, обновить={toUpdate - updated}, деактивировать={toDeactivate - deactivated}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10. Деактивировать шаблоны, которые больше не соответствуют ни одной подгруппе
|
// === Деактивация ===
|
||||||
var expectedTemplateKeys = new HashSet<(Guid JobId, Guid UnitId, int Index)>();
|
|
||||||
foreach (var kvp in groupedRelationships)
|
|
||||||
{
|
|
||||||
var relationshipUnitId = kvp.Key;
|
|
||||||
var childUnitIds = kvp.Value;
|
|
||||||
|
|
||||||
// --- СОРТИРОВКА ПЕРЕД ФОРМИРОВАНИЕМ expectedTemplateKeys ---
|
|
||||||
var childUnitNameMapForDeactivate = await unitService.Get()
|
|
||||||
.AsNoTracking()
|
|
||||||
.Where(u => childUnitIds.Contains(u.Id))
|
|
||||||
.ToDictionaryAsync(u => u.Id, u => u.Name);
|
|
||||||
|
|
||||||
var sortedChildUnitIdsForDeactivate = childUnitIds
|
|
||||||
.OrderBy(id => childUnitNameMapForDeactivate.GetValueOrDefault(id, id.ToString()))
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
int maxValueForSplitting = maxJob.MaxValueRelationships!.Value;
|
|
||||||
var childUnitGroups = 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var allRelationshipUnitIds = groupedRelationships.Keys.ToHashSet();
|
|
||||||
var allJobIdsInGroup = jobsInGroup.Select(j => j.Id).ToHashSet();
|
|
||||||
|
|
||||||
var allExistingTemplatesInGroup = await templateService.Get()
|
|
||||||
.AsNoTracking()
|
|
||||||
.Include(t => t.UnitsInTemplate)
|
|
||||||
.Where(t => allJobIdsInGroup.Contains(t.JobId) && allRelationshipUnitIds.Contains(t.UnitId))
|
|
||||||
.ToListAsync();
|
|
||||||
|
|
||||||
foreach (var existingTemplate in allExistingTemplatesInGroup)
|
foreach (var existingTemplate in allExistingTemplatesInGroup)
|
||||||
{
|
{
|
||||||
var key = (existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index ?? -1);
|
var key = (existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index ?? -1);
|
||||||
@@ -720,11 +722,23 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
{
|
{
|
||||||
logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, Relationship {UnitId}, Index {Index}).", existingTemplate.Id, existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index);
|
logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, Relationship {UnitId}, Index {Index}).", existingTemplate.Id, existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index);
|
||||||
await templateDeactivator.DeactivateTemplateAsync(existingTemplate, initiator);
|
await templateDeactivator.DeactivateTemplateAsync(existingTemplate, initiator);
|
||||||
|
deactivated++;
|
||||||
|
await UpdateMatchingStatusAsync(jobGroupId, $"Прогресс: создано={created}, обновлено={updated}, деактивировано={deactivated}. Осталось: создать={toCreate - created}, обновить={toUpdate - updated}, деактивировать={toDeactivate - deactivated}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === Успешное завершение ===
|
||||||
|
await UpdateMatchingStatusAsync(jobGroupId, "Синхронизация завершена успешно");
|
||||||
|
await matchingStatusService.DeleteMatchingStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup);
|
||||||
logger.LogInformation("Синхронизация шаблонов завершена для JobGroup {JobGroupId}.", jobGroupId);
|
logger.LogInformation("Синхронизация шаблонов завершена для JobGroup {JobGroupId}.", jobGroupId);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Ошибка при синхронизации JobGroup {JobGroupId}", jobGroupId);
|
||||||
|
await UpdateMatchingStatusAsync(jobGroupId, $"Ошибка: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task UpdateTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
|
public async Task UpdateTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
|
||||||
{
|
{
|
||||||
@@ -734,9 +748,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
|
|
||||||
// --- Вспомогательные методы ---
|
// --- Вспомогательные методы ---
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Фильтрует юниты по полю "РАБОЧАЯ_ГР_ОТВ_ЗА_ЭК"
|
|
||||||
/// </summary>
|
|
||||||
private async Task<HashSet<Guid>> FilterByWorkGroupFieldAsync(
|
private async Task<HashSet<Guid>> FilterByWorkGroupFieldAsync(
|
||||||
List<Unit> units,
|
List<Unit> units,
|
||||||
HashSet<Guid> candidateUnitIds,
|
HashSet<Guid> candidateUnitIds,
|
||||||
@@ -753,9 +764,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
.ToHashSet();
|
.ToHashSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Выбирает Job, соответствующий размеру подгруппы
|
|
||||||
/// </summary>
|
|
||||||
private Job SelectTargetJob(List<Job> jobsInGroup, int subGroupSize, Job maxJob)
|
private Job SelectTargetJob(List<Job> jobsInGroup, int subGroupSize, Job maxJob)
|
||||||
{
|
{
|
||||||
Job? targetJob = jobsInGroup
|
Job? targetJob = jobsInGroup
|
||||||
@@ -794,7 +802,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// === Создаём временный Template для нормализации имени ===
|
|
||||||
var tempTemplateForName = new Template
|
var tempTemplateForName = new Template
|
||||||
{
|
{
|
||||||
Id = template.Id,
|
Id = template.Id,
|
||||||
@@ -854,4 +861,20 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
var referenceDate = targetJob.Group?.ReferenceDate ?? DateTimeOffset.UtcNow;
|
var referenceDate = targetJob.Group?.ReferenceDate ?? DateTimeOffset.UtcNow;
|
||||||
return await esppScheduleTransformService.GetNextDateAsync(targetJob.GroupId, referenceDate);
|
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)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ using PARR.BLL.Domain.Mq;
|
|||||||
using PARR.BLL.Services.Interfaces;
|
using PARR.BLL.Services.Interfaces;
|
||||||
using PARR.Common.Domain;
|
using PARR.Common.Domain;
|
||||||
using PARR.Constants;
|
using PARR.Constants;
|
||||||
|
using PARR.DAL.Cache.Models;
|
||||||
using PARR.DAL.DomainServices.Interfaces;
|
using PARR.DAL.DomainServices.Interfaces;
|
||||||
using PARR.DAL.DomainServices.Shortcodes;
|
using PARR.DAL.DomainServices.Shortcodes;
|
||||||
using PARR.DAL.Models;
|
using PARR.DAL.Models;
|
||||||
@@ -38,6 +39,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
private readonly ITemplateDeactivator templateDeactivator;
|
private readonly ITemplateDeactivator templateDeactivator;
|
||||||
private readonly ITemplateNameNormalizer templateNameNormalizer;
|
private readonly ITemplateNameNormalizer templateNameNormalizer;
|
||||||
private readonly ITemplateUpdaterMqSender templateUpdaterMqSender;
|
private readonly ITemplateUpdaterMqSender templateUpdaterMqSender;
|
||||||
|
private readonly IMatchingStatusService matchingStatusService;
|
||||||
|
|
||||||
public SimpleTemplateSynchronizer(
|
public SimpleTemplateSynchronizer(
|
||||||
ILogger<SimpleTemplateSynchronizer> logger,
|
ILogger<SimpleTemplateSynchronizer> logger,
|
||||||
@@ -56,7 +58,8 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
IUnitFieldService unitFieldService,
|
IUnitFieldService unitFieldService,
|
||||||
ITemplateDeactivator templateDeactivator,
|
ITemplateDeactivator templateDeactivator,
|
||||||
ITemplateNameNormalizer templateNameNormalizer,
|
ITemplateNameNormalizer templateNameNormalizer,
|
||||||
ITemplateUpdaterMqSender templateUpdaterMqSender
|
ITemplateUpdaterMqSender templateUpdaterMqSender,
|
||||||
|
IMatchingStatusService matchingStatusService
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
@@ -70,12 +73,37 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
this.templateDeactivator = templateDeactivator;
|
this.templateDeactivator = templateDeactivator;
|
||||||
this.templateNameNormalizer = templateNameNormalizer;
|
this.templateNameNormalizer = templateNameNormalizer;
|
||||||
this.templateUpdaterMqSender = templateUpdaterMqSender;
|
this.templateUpdaterMqSender = templateUpdaterMqSender;
|
||||||
|
this.matchingStatusService = matchingStatusService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
|
public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
|
||||||
{
|
{
|
||||||
logger.LogDebug("Начало синхронизации шаблонов для Job {JobId}", jobId);
|
logger.LogDebug("Начало синхронизации шаблонов для Job {JobId}", jobId);
|
||||||
|
|
||||||
|
// === Проверка: уже запущена? ===
|
||||||
|
var existingStatus = await matchingStatusService.GetStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
|
||||||
|
if (existingStatus.DetailsJobs?.Any() == true)
|
||||||
|
{
|
||||||
|
logger.LogWarning("Синхронизация для Job {JobId} уже запущена. Пропускаем.", jobId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// === Устанавливаем статус "в процессе" ===
|
||||||
|
var initialStatus = new MatchingStatusItemDto
|
||||||
|
{
|
||||||
|
DateStart = DateTimeOffset.UtcNow,
|
||||||
|
Action = TemplateMatcherActionEnum.Sync,
|
||||||
|
Comment = "Начало синхронизации"
|
||||||
|
};
|
||||||
|
await matchingStatusService.SetMatchingStatusAsync(
|
||||||
|
jobId,
|
||||||
|
SyncTaskEntityTypeEnum.Job,
|
||||||
|
new MatchingStatusItem { Data = initialStatus, Timestamp = DateTimeOffset.UtcNow, Source = nameof(SimpleTemplateSynchronizer) },
|
||||||
|
TimeSpan.FromMinutes(35)
|
||||||
|
);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
var job = await jobService.Get()
|
var job = await jobService.Get()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(j => j.AutoControl)
|
.Include(j => j.AutoControl)
|
||||||
@@ -89,33 +117,37 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
if (job == null)
|
if (job == null)
|
||||||
{
|
{
|
||||||
logger.LogWarning("Job {JobId} не найден.", jobId);
|
logger.LogWarning("Job {JobId} не найден.", jobId);
|
||||||
|
await UpdateMatchingStatusAsync(jobId, "Job не найден");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var unitIds = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId);
|
var unitIds = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId);
|
||||||
|
{
|
||||||
if (unitIds == null || !unitIds.Any())
|
if (unitIds == null || !unitIds.Any())
|
||||||
{
|
{
|
||||||
logger.LogInformation("Для Job {JobId} фильтры не дали Unit'ов.", jobId);
|
logger.LogInformation("Для Job {JobId} фильтры не дали Unit'ов.", jobId);
|
||||||
|
|
||||||
// Деактивировать все существующие шаблоны для этого Job
|
|
||||||
var existingTemplatesForDeactivation = await templateService.Get()
|
var existingTemplatesForDeactivation = await templateService.Get()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(t => t.UnitsInTemplate)
|
.Include(t => t.UnitsInTemplate)
|
||||||
.Where(t => t.JobId == jobId && t.StatusTypeId == TemplateStatusTypeEnum.Used)
|
.Where(t => t.JobId == jobId && t.StatusTypeId == TemplateStatusTypeEnum.Used)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
await UpdateMatchingStatusAsync(jobId, $"Нет Unit'ов. Деактивация {existingTemplatesForDeactivation.Count} шаблонов...");
|
||||||
|
|
||||||
foreach (var unusedTemplate in existingTemplatesForDeactivation)
|
foreach (var unusedTemplate in existingTemplatesForDeactivation)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, UnitId {UnitId}).", unusedTemplate.Id, jobId, unusedTemplate.UnitId);
|
logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, UnitId {UnitId}).", unusedTemplate.Id, jobId, unusedTemplate.UnitId);
|
||||||
await templateDeactivator.DeactivateTemplateAsync(unusedTemplate, initiator);
|
await templateDeactivator.DeactivateTemplateAsync(unusedTemplate, initiator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await UpdateMatchingStatusAsync(jobId, "Синхронизация завершена: нет Unit'ов");
|
||||||
|
await matchingStatusService.DeleteMatchingStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
|
||||||
logger.LogInformation("Синхронизация шаблонов завершена для Job {JobId}.", jobId);
|
logger.LogInformation("Синхронизация шаблонов завершена для Job {JobId}.", jobId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
// Отладка: проверить, есть ли юнит в unitIds
|
|
||||||
if (unitIds.Contains(targetUnitId))
|
if (unitIds.Contains(targetUnitId))
|
||||||
{
|
{
|
||||||
logger.LogDebug("Юнит {TargetUnitId} найден в unitIds.", targetUnitId);
|
logger.LogDebug("Юнит {TargetUnitId} найден в unitIds.", targetUnitId);
|
||||||
@@ -150,7 +182,26 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
.Where(t => !unitIds.Contains(t.UnitId))
|
.Where(t => !unitIds.Contains(t.UnitId))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Обработка новых юнитов
|
// === Подсчёт операций ===
|
||||||
|
int toCreate = newUnitIds.Count;
|
||||||
|
int toUpdate = 0;
|
||||||
|
int toDeactivate = unusedTemplates.Count;
|
||||||
|
|
||||||
|
// Подсчёт обновлений по имени
|
||||||
|
foreach (var template in existingUsedTemplates)
|
||||||
|
{
|
||||||
|
if (unitIds.Contains(template.UnitId))
|
||||||
|
{
|
||||||
|
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(template);
|
||||||
|
if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
toUpdate++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await UpdateMatchingStatusAsync(jobId, $"Осталось: создать={toCreate}, обновить={toUpdate}, деактивировать={toDeactivate}");
|
||||||
|
|
||||||
|
// === Обработка новых юнитов ===
|
||||||
|
int created = 0, updated = 0, deactivated = 0;
|
||||||
foreach (var unitId in newUnitIds)
|
foreach (var unitId in newUnitIds)
|
||||||
{
|
{
|
||||||
var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(jobId, unitId, initiator);
|
var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(jobId, unitId, initiator);
|
||||||
@@ -158,7 +209,6 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
{
|
{
|
||||||
logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, UnitId {UnitId}.", reusableTemplate.Id, jobId, unitId);
|
logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, UnitId {UnitId}.", reusableTemplate.Id, jobId, unitId);
|
||||||
|
|
||||||
// === Создаём временный Template для нормализации имени ===
|
|
||||||
var tempTemplateForName = new Template
|
var tempTemplateForName = new Template
|
||||||
{
|
{
|
||||||
Id = reusableTemplate.Id,
|
Id = reusableTemplate.Id,
|
||||||
@@ -166,9 +216,9 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
JobId = jobId,
|
JobId = jobId,
|
||||||
UnitId = unitId,
|
UnitId = unitId,
|
||||||
Index = reusableTemplate.Index,
|
Index = reusableTemplate.Index,
|
||||||
Job = job, // загруженный job
|
Job = job,
|
||||||
Unit = reusableTemplate.Unit, // может быть null — нормально
|
Unit = reusableTemplate.Unit,
|
||||||
UnitsInTemplate = new List<UnitsInTemplate>() // для простого шаблона
|
UnitsInTemplate = new List<UnitsInTemplate>()
|
||||||
};
|
};
|
||||||
|
|
||||||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName);
|
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName);
|
||||||
@@ -185,7 +235,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||||||
Initiator = initiator,
|
Initiator = initiator,
|
||||||
NextRun = nextRun,
|
NextRun = nextRun,
|
||||||
UnitsInTemplate = new List<Guid>() // для простого шаблона
|
UnitsInTemplate = new List<Guid>()
|
||||||
};
|
};
|
||||||
|
|
||||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||||
@@ -195,9 +245,11 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
logger.LogDebug("Создание нового шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId);
|
logger.LogDebug("Создание нового шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId);
|
||||||
await CreateSimpleTemplateAsync(jobId, unitId, initiator);
|
await CreateSimpleTemplateAsync(jobId, unitId, initiator);
|
||||||
}
|
}
|
||||||
|
created++;
|
||||||
|
await UpdateMatchingStatusAsync(jobId, $"Прогресс: создано={created}, обновлено={updated}, деактивировано={deactivated}. Осталось: создать={toCreate - created}, обновить={toUpdate - updated}, деактивировать={toDeactivate - deactivated}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обработка существующих шаблонов (проверка имени)
|
// === Обработка существующих шаблонов (проверка имени) ===
|
||||||
foreach (var template in existingUsedTemplates)
|
foreach (var template in existingUsedTemplates)
|
||||||
{
|
{
|
||||||
if (unitIds.Contains(template.UnitId))
|
if (unitIds.Contains(template.UnitId))
|
||||||
@@ -205,7 +257,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(template);
|
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(template);
|
||||||
if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
logger.LogDebug("Шаблон {TemplateId} требует обновления имени: старое = '{OldName}', новое = '{NewName}'", template.Id, template.Name, expectedName);
|
logger.LogDebug("Шаблон {TemplateId} требует обновления имени.", template.Id);
|
||||||
|
|
||||||
var nextRun = await GetNextRunAsync(job, template.NextRun);
|
var nextRun = await GetNextRunAsync(job, template.NextRun);
|
||||||
|
|
||||||
@@ -222,23 +274,37 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
Index = template.Index,
|
Index = template.Index,
|
||||||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||||||
Initiator = initiator,
|
Initiator = initiator,
|
||||||
UnitsInTemplate = new List<Guid>() // для простого шаблона
|
UnitsInTemplate = new List<Guid>()
|
||||||
};
|
};
|
||||||
|
|
||||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||||
|
updated++;
|
||||||
|
await UpdateMatchingStatusAsync(jobId, $"Прогресс: создано={created}, обновлено={updated}, деактивировано={deactivated}. Осталось: создать={toCreate - created}, обновить={toUpdate - updated}, деактивировать={toDeactivate - deactivated}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Деактивация лишних шаблонов
|
// === Деактивация лишних шаблонов ===
|
||||||
foreach (var unusedTemplate in unusedTemplates)
|
foreach (var unusedTemplate in unusedTemplates)
|
||||||
{
|
{
|
||||||
logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, UnitId {UnitId}).", unusedTemplate.Id, jobId, unusedTemplate.UnitId);
|
logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, UnitId {UnitId}).", unusedTemplate.Id, jobId, unusedTemplate.UnitId);
|
||||||
await templateDeactivator.DeactivateTemplateAsync(unusedTemplate, initiator);
|
await templateDeactivator.DeactivateTemplateAsync(unusedTemplate, initiator);
|
||||||
|
deactivated++;
|
||||||
|
await UpdateMatchingStatusAsync(jobId, $"Прогресс: создано={created}, обновлено={updated}, деактивировано={deactivated}. Осталось: создать={toCreate - created}, обновить={toUpdate - updated}, деактивировать={toDeactivate - deactivated}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// === Успешное завершение ===
|
||||||
|
await UpdateMatchingStatusAsync(jobId, "Синхронизация завершена успешно");
|
||||||
|
await matchingStatusService.DeleteMatchingStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
|
||||||
logger.LogInformation("Синхронизация шаблонов завершена для Job {JobId}.", jobId);
|
logger.LogInformation("Синхронизация шаблонов завершена для Job {JobId}.", jobId);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Ошибка при синхронизации Job {JobId}", jobId);
|
||||||
|
await UpdateMatchingStatusAsync(jobId, $"Ошибка: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator)
|
public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator)
|
||||||
{
|
{
|
||||||
@@ -249,6 +315,29 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
{
|
{
|
||||||
logger.LogDebug("Обновление шаблонов для Job {JobId}", jobId);
|
logger.LogDebug("Обновление шаблонов для Job {JobId}", jobId);
|
||||||
|
|
||||||
|
// === Проверка: уже запущена? ===
|
||||||
|
var existingStatus = await matchingStatusService.GetStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
|
||||||
|
if (existingStatus.DetailsJobs?.Any() == true)
|
||||||
|
{
|
||||||
|
logger.LogWarning("Обновление для Job {JobId} уже запущено. Пропускаем.", jobId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var initialStatus = new MatchingStatusItemDto
|
||||||
|
{
|
||||||
|
DateStart = DateTimeOffset.UtcNow,
|
||||||
|
Action = TemplateMatcherActionEnum.Update,
|
||||||
|
Comment = "Начало обновления имён"
|
||||||
|
};
|
||||||
|
await matchingStatusService.SetMatchingStatusAsync(
|
||||||
|
jobId,
|
||||||
|
SyncTaskEntityTypeEnum.Job,
|
||||||
|
new MatchingStatusItem { Data = initialStatus, Timestamp = DateTimeOffset.UtcNow, Source = nameof(SimpleTemplateSynchronizer) },
|
||||||
|
TimeSpan.FromMinutes(30)
|
||||||
|
);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
var job = await jobService.Get()
|
var job = await jobService.Get()
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Include(j => j.AutoControl)
|
.Include(j => j.AutoControl)
|
||||||
@@ -262,6 +351,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
if (job == null)
|
if (job == null)
|
||||||
{
|
{
|
||||||
logger.LogWarning("Job {JobId} не найден.", jobId);
|
logger.LogWarning("Job {JobId} не найден.", jobId);
|
||||||
|
await UpdateMatchingStatusAsync(jobId, "Job не найден");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,11 +359,12 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
if (unitIds == null || !unitIds.Any())
|
if (unitIds == null || !unitIds.Any())
|
||||||
{
|
{
|
||||||
logger.LogInformation("Для Job {JobId} фильтры не дали Unit'ов.", jobId);
|
logger.LogInformation("Для Job {JobId} фильтры не дали Unit'ов.", jobId);
|
||||||
|
await UpdateMatchingStatusAsync(jobId, "Нет Unit'ов — обновление не требуется");
|
||||||
|
await matchingStatusService.DeleteMatchingStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
// Отладка: проверить, есть ли юнит в unitIds
|
|
||||||
if (unitIds.Contains(targetUnitId))
|
if (unitIds.Contains(targetUnitId))
|
||||||
{
|
{
|
||||||
logger.LogDebug("Юнит {TargetUnitId} найден в unitIds.", targetUnitId);
|
logger.LogDebug("Юнит {TargetUnitId} найден в unitIds.", targetUnitId);
|
||||||
@@ -296,6 +387,20 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
.Where(t => t.JobId == jobId && t.StatusTypeId == TemplateStatusTypeEnum.Used)
|
.Where(t => t.JobId == jobId && t.StatusTypeId == TemplateStatusTypeEnum.Used)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
|
int toUpdate = 0;
|
||||||
|
foreach (var template in existingTemplates)
|
||||||
|
{
|
||||||
|
if (unitIds.Contains(template.UnitId))
|
||||||
|
{
|
||||||
|
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(template);
|
||||||
|
if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase))
|
||||||
|
toUpdate++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await UpdateMatchingStatusAsync(jobId, $"Обновление имён: {toUpdate} шаблонов");
|
||||||
|
|
||||||
|
int updated = 0;
|
||||||
foreach (var template in existingTemplates)
|
foreach (var template in existingTemplates)
|
||||||
{
|
{
|
||||||
if (unitIds.Contains(template.UnitId))
|
if (unitIds.Contains(template.UnitId))
|
||||||
@@ -303,7 +408,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(template);
|
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(template);
|
||||||
if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase))
|
if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
logger.LogDebug("Шаблон {TemplateId} требует обновления имени: старое = '{OldName}', новое = '{NewName}'", template.Id, template.Name, expectedName);
|
logger.LogDebug("Шаблон {TemplateId} требует обновления имени.", template.Id);
|
||||||
|
|
||||||
var nextRun = await GetNextRunAsync(job, template.NextRun);
|
var nextRun = await GetNextRunAsync(job, template.NextRun);
|
||||||
|
|
||||||
@@ -320,16 +425,27 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
Index = template.Index,
|
Index = template.Index,
|
||||||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||||||
Initiator = initiator,
|
Initiator = initiator,
|
||||||
UnitsInTemplate = new List<Guid>() // для простого шаблона
|
UnitsInTemplate = new List<Guid>()
|
||||||
};
|
};
|
||||||
|
|
||||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||||
|
updated++;
|
||||||
|
await UpdateMatchingStatusAsync(jobId, $"Обновлено: {updated}/{toUpdate}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await UpdateMatchingStatusAsync(jobId, "Обновление завершено");
|
||||||
|
await matchingStatusService.DeleteMatchingStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
|
||||||
logger.LogInformation("Обновление шаблонов завершено для Job {JobId}.", jobId);
|
logger.LogInformation("Обновление шаблонов завершено для Job {JobId}.", jobId);
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.LogError(ex, "Ошибка при обновлении Job {JobId}", jobId);
|
||||||
|
await UpdateMatchingStatusAsync(jobId, $"Ошибка: {ex.Message}");
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task CreateSimpleTemplateAsync(Guid jobId, Guid unitId, HistoryInitiator initiator)
|
private async Task CreateSimpleTemplateAsync(Guid jobId, Guid unitId, HistoryInitiator initiator)
|
||||||
{
|
{
|
||||||
@@ -339,7 +455,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
{
|
{
|
||||||
JobId = jobId,
|
JobId = jobId,
|
||||||
UnitId = unitId,
|
UnitId = unitId,
|
||||||
UnitsInTemplate = new List<Guid>(), // для простого шаблона
|
UnitsInTemplate = new List<Guid>(),
|
||||||
HistoryInitiator = initiator
|
HistoryInitiator = initiator
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -355,4 +471,20 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
|
|||||||
var referenceDate = job.Group?.ReferenceDate ?? DateTimeOffset.UtcNow;
|
var referenceDate = job.Group?.ReferenceDate ?? DateTimeOffset.UtcNow;
|
||||||
return await esppScheduleTransformService.GetNextDateAsync(job.GroupId, referenceDate);
|
return await esppScheduleTransformService.GetNextDateAsync(job.GroupId, referenceDate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task UpdateMatchingStatusAsync(Guid jobId, string comment)
|
||||||
|
{
|
||||||
|
var status = new MatchingStatusItemDto
|
||||||
|
{
|
||||||
|
DateStart = DateTimeOffset.UtcNow,
|
||||||
|
Action = TemplateMatcherActionEnum.Sync,
|
||||||
|
Comment = comment
|
||||||
|
};
|
||||||
|
await matchingStatusService.SetMatchingStatusAsync(
|
||||||
|
jobId,
|
||||||
|
SyncTaskEntityTypeEnum.Job,
|
||||||
|
new MatchingStatusItem { Data = status, Timestamp = DateTimeOffset.UtcNow, Source = nameof(SimpleTemplateSynchronizer) },
|
||||||
|
TimeSpan.FromMinutes(30)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user