feat(templateMatcher): refactoring

This commit is contained in:
Mikhail Kuznetsov
2025-12-26 21:47:53 +10:00
parent e04d2b628c
commit e8db55400c
11 changed files with 679 additions and 554 deletions

View File

@@ -12,6 +12,10 @@ namespace PARR.DAL.DomainServices.Implementations
{ {
internal class UnitFilterService : IUnitFilterService internal class UnitFilterService : IUnitFilterService
{ {
#if DEBUG
private readonly Guid targetUnitId = Guid.Parse("d4322a08-246b-4380-8953-8ce4a8446235");
#endif
private readonly ILogger<UnitFilterService> logger; private readonly ILogger<UnitFilterService> logger;
private readonly IJobService jobService; private readonly IJobService jobService;
private readonly IUnitService unitService; private readonly IUnitService unitService;
@@ -118,6 +122,19 @@ namespace PARR.DAL.DomainServices.Implementations
logger.LogDebug("Базовый фильтр по Name '{NameFilter}' дал {Count} юнитов", filter.UnitFilter, initialUnitIds.Count); logger.LogDebug("Базовый фильтр по Name '{NameFilter}' дал {Count} юнитов", filter.UnitFilter, initialUnitIds.Count);
#if DEBUG
// ✅ Отладка: проверить, есть ли юнит в initialUnitIds
if (initialUnitIds.Contains(targetUnitId))
{
logger.LogDebug("Юнит {TargetUnitId} найден в initialUnitIds.", targetUnitId);
}
else
{
logger.LogDebug("Юнит {TargetUnitId} НЕ найден в initialUnitIds.", targetUnitId);
//continue; // ❌ юнит отсеялся на этом этапе
}
#endif
if (!initialUnitIds.Any()) if (!initialUnitIds.Any())
{ {
logger.LogDebug("Фильтр #{Index}: 0 юнитов после UnitFilter. Пропускаем.", filterNumber); logger.LogDebug("Фильтр #{Index}: 0 юнитов после UnitFilter. Пропускаем.", filterNumber);
@@ -249,14 +266,42 @@ namespace PARR.DAL.DomainServices.Implementations
candidateUnits = ApplyFieldFilter(candidateUnits, fieldFilter); candidateUnits = ApplyFieldFilter(candidateUnits, fieldFilter);
} }
logger.LogDebug("После FieldFilter осталось {Count} юнитов", candidateUnits.Count()); #if DEBUG
// ✅ Отладка: проверить, есть ли юнит в candidateUnits после FieldFilter
var candidateListAfterFieldFilter = candidateUnits.ToList();
if (candidateListAfterFieldFilter.Any(u => u.Id == targetUnitId))
{
logger.LogDebug("Юнит {TargetUnitId} найден в candidateUnits после FieldFilter.", targetUnitId);
}
else
{
logger.LogDebug("Юнит {TargetUnitId} НЕ найден в candidateUnits после FieldFilter.", targetUnitId);
//continue; // ❌ юнит отсеялся на этом этапе
}
logger.LogDebug("После FieldFilter осталось {Count} юнитов", candidateListAfterFieldFilter.Count);
#endif
foreach (var relFilter in filter.RelationshipFilters) foreach (var relFilter in filter.RelationshipFilters)
{ {
candidateUnits = ApplyRelationshipFilterToQuery(candidateUnits, relFilter); candidateUnits = ApplyRelationshipFilterToQuery(candidateUnits, relFilter);
} }
logger.LogDebug("После RelationshipFilter осталось {Count} юнитов", candidateUnits.Count()); #if DEBUG
// ✅ Отладка: проверить, есть ли юнит в candidateUnits после RelationshipFilter
var candidateListAfterRelFilter = candidateUnits.ToList();
if (candidateListAfterRelFilter.Any(u => u.Id == targetUnitId))
{
logger.LogDebug("Юнит {TargetUnitId} найден в candidateUnits после RelationshipFilter.", targetUnitId);
}
else
{
logger.LogDebug("Юнит {TargetUnitId} НЕ найден в candidateUnits после RelationshipFilter.", targetUnitId);
//continue; // ❌ юнит отсеялся на этом этапе
}
logger.LogDebug("После RelationshipFilter осталось {Count} юнитов", candidateListAfterRelFilter.Count);
#endif
// 9 Umbrella-фильтр // 9 Umbrella-фильтр
var finalUnits = candidateUnits.AsEnumerable(); var finalUnits = candidateUnits.AsEnumerable();
@@ -264,6 +309,19 @@ namespace PARR.DAL.DomainServices.Implementations
{ {
finalUnits = ApplyRelationshipCountFilter(finalUnits, job, filter.RelationshipFilters); finalUnits = ApplyRelationshipCountFilter(finalUnits, job, filter.RelationshipFilters);
logger.LogDebug("После Umbrella-фильтра осталось {Count} юнитов", finalUnits.Count()); logger.LogDebug("После Umbrella-фильтра осталось {Count} юнитов", finalUnits.Count());
#if DEBUG
// ✅ Отладка: проверить, есть ли юнит в finalUnits после Umbrella
if (finalUnits.Any(u => u.Id == targetUnitId))
{
logger.LogDebug("Юнит {TargetUnitId} найден в finalUnits после Umbrella-фильтра.", targetUnitId);
}
else
{
logger.LogDebug("Юнит {TargetUnitId} НЕ найден в finalUnits после Umbrella-фильтра.", targetUnitId);
//continue; // ❌ юнит отсеялся на этом этапе
}
#endif
} }
// 10 Взять ID // 10 Взять ID
@@ -272,6 +330,19 @@ namespace PARR.DAL.DomainServices.Implementations
.Take(remaining) .Take(remaining)
.ToList(); .ToList();
#if DEBUG
// ✅ Отладка: проверить, есть ли юнит в newIds
if (newIds.Contains(targetUnitId))
{
logger.LogDebug("Юнит {TargetUnitId} найден в newIds.", targetUnitId);
}
else
{
logger.LogDebug("Юнит {TargetUnitId} НЕ найден в newIds.", targetUnitId);
// ❌ юнит отсеялся на этапе Take(remaining), если его не было в finalUnits
}
#endif
collectedIds.UnionWith(newIds); collectedIds.UnionWith(newIds);
logger.LogDebug("Фильтр #{Index}: найдено {Count} Unit'ов. Всего: {Total}", logger.LogDebug("Фильтр #{Index}: найдено {Count} Unit'ов. Всего: {Total}",
filterNumber, newIds.Count, collectedIds.Count); filterNumber, newIds.Count, collectedIds.Count);
@@ -287,6 +358,18 @@ namespace PARR.DAL.DomainServices.Implementations
logger.LogInformation("Job {JobId}: из {FilterCount} фильтров получено {UnitCount} уникальных Unit'ов", logger.LogInformation("Job {JobId}: из {FilterCount} фильтров получено {UnitCount} уникальных Unit'ов",
job.Id, job.UnitFilters.Count, result.Count); job.Id, job.UnitFilters.Count, result.Count);
#if DEBUG
// ✅ Отладка: проверить, есть ли юнит в result
if (result.Contains(targetUnitId))
{
logger.LogDebug("Юнит {TargetUnitId} найден в финальном результате.", targetUnitId);
}
else
{
logger.LogDebug("Юнит {TargetUnitId} НЕ найден в финальном результате.", targetUnitId);
}
#endif
return result; return result;
} }
@@ -403,15 +486,45 @@ namespace PARR.DAL.DomainServices.Implementations
return query.AsEnumerable().Where(dto => return query.AsEnumerable().Where(dto =>
{ {
#if DEBUG
// ✅ Отладка: проверить, это нужный юнит
if (dto.Id == targetUnitId)
{
logger.LogDebug("Проверяем юнит {TargetUnitId} с {ParentCount} родителями и {ChildCount} детьми.",
targetUnitId, dto.Parents.Count, dto.Children.Count);
}
#endif
var links = isParent ? dto.Parents : dto.Children; var links = isParent ? dto.Parents : dto.Children;
if (links == null || !links.Any()) if (links == null || !links.Any())
{ {
#if DEBUG
// ✅ Отладка: юнит не имеет связей
if (dto.Id == targetUnitId)
{
logger.LogDebug("Юнит {TargetUnitId}: нет связей ({Direction}), результат фильтра: {Result}",
targetUnitId, isParent ? "Parent" : "Child", isFullMatch && isInverse);
}
#endif
// Для IsFullMatch/IsInverse — "все подходят", т.е. true // Для IsFullMatch/IsInverse — "все подходят", т.е. true
// Для остальных — false // Для остальных — false
return isFullMatch && isInverse; return isFullMatch && isInverse;
} }
#if DEBUG
// ✅ Отладка: проверить, какие связи у юнита
if (dto.Id == targetUnitId)
{
logger.LogDebug("Юнит {TargetUnitId}: {Count} связей ({Direction}).", targetUnitId, links.Count, isParent ? "Parent" : "Child");
foreach (var link in links)
{
logger.LogDebug(" Связь: UnitId={LinkUnitId}, Name={LinkName}, Values=[{Values}]", link.UnitId, link.Name, string.Join(", ", link.Values.Select(v => $"{v.FieldId}={v.Value}")));
}
}
#endif
var hasMatchingLinks = links.Any(link => var hasMatchingLinks = links.Any(link =>
{ {
var hasMatch = link.Values.Any(v => var hasMatch = link.Values.Any(v =>
@@ -423,9 +536,25 @@ namespace PARR.DAL.DomainServices.Implementations
v.Value.Contains(valueMask, StringComparison.OrdinalIgnoreCase)) v.Value.Contains(valueMask, StringComparison.OrdinalIgnoreCase))
); );
#if DEBUG
// ✅ Отладка: проверить, какая связь подходит
if (dto.Id == targetUnitId)
{
logger.LogDebug(" Проверка связи {LinkUnitId}: hasMatch={HasMatch} (ValueMask={ValueMask}, FieldId={FieldId})", link.UnitId, hasMatch, valueMask, fieldId);
}
#endif
return isInverse ? !hasMatch : hasMatch; return isInverse ? !hasMatch : hasMatch;
}); });
#if DEBUG
// ✅ Отладка: результат hasMatchingLinks
if (dto.Id == targetUnitId)
{
logger.LogDebug(" hasMatchingLinks = {HasMatchingLinks}, isInverse = {IsInverse}", hasMatchingLinks, isInverse);
}
#endif
if (isFullMatch) if (isFullMatch)
{ {
var allMatch = links.All(link => var allMatch = links.All(link =>
@@ -439,9 +568,25 @@ namespace PARR.DAL.DomainServices.Implementations
v.Value.Contains(valueMask, StringComparison.OrdinalIgnoreCase)) v.Value.Contains(valueMask, StringComparison.OrdinalIgnoreCase))
); );
#if DEBUG
// ✅ Отладка: проверить, все ли связи подходят
if (dto.Id == targetUnitId)
{
logger.LogDebug(" Проверка связи {LinkUnitId} для FullMatch: hasMatch={HasMatch}", link.UnitId, hasMatch);
}
#endif
return isInverse ? !hasMatch : hasMatch; return isInverse ? !hasMatch : hasMatch;
}); });
#if DEBUG
// ✅ Отладка: результат allMatch
if (dto.Id == targetUnitId)
{
logger.LogDebug(" allMatch = {AllMatch}, isFullMatch = {IsFullMatch}", allMatch, isFullMatch);
}
#endif
return allMatch; return allMatch;
} }

View File

@@ -5,8 +5,6 @@ using PARR.BLL.Services.Interfaces;
using PARR.Common.Domain; using PARR.Common.Domain;
using PARR.Constants; using PARR.Constants;
using PARR.DAL.DomainServices.Interfaces; using PARR.DAL.DomainServices.Interfaces;
using PARR.DAL.DomainServices.Shortcodes;
using PARR.DAL.DomainServices.Shortcodes.Models;
using PARR.DAL.Models; using PARR.DAL.Models;
using PARR.DAL.Models.Job; using PARR.DAL.Models.Job;
using PARR.DAL.Models.Unit; using PARR.DAL.Models.Unit;
@@ -18,12 +16,14 @@ using PARR.TemplateMatcher.Services.Interfaces;
using PARR.TemplateMatcher.Settings; using PARR.TemplateMatcher.Settings;
using System.Text.Json; using System.Text.Json;
namespace PARR.TemplateMatcher.Services.Implemetaions; namespace PARR.TemplateMatcher.Services.Implementations;
internal class GroupedTemplateSynchronizer : ITemplateSynchronizer internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
{ {
private const bool DefaultUnusedTemplateState = false; #if DEBUG
private const bool DefaultUnusedScheduleState = false; private readonly Guid targetUnitId = Guid.Parse("d4322a08-246b-4380-8953-8ce4a8446235");
#endif
private const bool DefaultUsedTemplateState = false; private const bool DefaultUsedTemplateState = false;
private const bool DefaultUsedScheduleState = false; private const bool DefaultUsedScheduleState = false;
@@ -37,11 +37,12 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
private readonly ITemplateService templateService; private readonly ITemplateService templateService;
private readonly IJobGroupService jobGroupService; private readonly IJobGroupService jobGroupService;
private readonly ITemplateReuser templateReuser; private readonly ITemplateReuser templateReuser;
private readonly IShortcodesService shortcodesService;
private readonly IEsppScheduleTransformService esppScheduleTransformService; private readonly IEsppScheduleTransformService esppScheduleTransformService;
private readonly IUnitRegionalEkPtkGroupService regionalEkPtkGroupService; private readonly IUnitRegionalEkPtkGroupService regionalEkPtkGroupService;
private readonly IUnitFieldService unitFieldService; private readonly IUnitFieldService unitFieldService;
private readonly IJobService jobService; private readonly ITemplateDeactivator templateDeactivator;
private readonly ITemplateNameNormalizer templateNameNormalizer;
private readonly ITemplateUpdaterMqSender templateUpdaterMqSender;
public GroupedTemplateSynchronizer( public GroupedTemplateSynchronizer(
ILogger<GroupedTemplateSynchronizer> logger, ILogger<GroupedTemplateSynchronizer> logger,
@@ -54,11 +55,12 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
ITemplateService templateService, ITemplateService templateService,
IJobGroupService jobGroupService, IJobGroupService jobGroupService,
ITemplateReuser templateReuser, ITemplateReuser templateReuser,
IShortcodesService shortcodesService,
IEsppScheduleTransformService esppScheduleTransformService, IEsppScheduleTransformService esppScheduleTransformService,
IUnitRegionalEkPtkGroupService regionalEkPtkGroupService, IUnitRegionalEkPtkGroupService regionalEkPtkGroupService,
IUnitFieldService unitFieldService, IUnitFieldService unitFieldService,
IJobService jobService ITemplateDeactivator templateDeactivator,
ITemplateNameNormalizer templateNameNormalizer,
ITemplateUpdaterMqSender templateUpdaterMqSender
) )
{ {
this.logger = logger; this.logger = logger;
@@ -71,11 +73,12 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
this.templateService = templateService; this.templateService = templateService;
this.jobGroupService = jobGroupService; this.jobGroupService = jobGroupService;
this.templateReuser = templateReuser; this.templateReuser = templateReuser;
this.shortcodesService = shortcodesService;
this.esppScheduleTransformService = esppScheduleTransformService; this.esppScheduleTransformService = esppScheduleTransformService;
this.regionalEkPtkGroupService = regionalEkPtkGroupService; this.regionalEkPtkGroupService = regionalEkPtkGroupService;
this.unitFieldService = unitFieldService; this.unitFieldService = unitFieldService;
this.jobService = jobService; this.templateDeactivator = templateDeactivator;
this.templateNameNormalizer = templateNameNormalizer;
this.templateUpdaterMqSender = templateUpdaterMqSender;
} }
public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator) public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
@@ -108,7 +111,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
var jobsInGroup = jobGroup.Jobs.ToList(); var jobsInGroup = jobGroup.Jobs.ToList();
// --- Получение FieldId и разрешённых значений для "РАБОЧАЯ_ГР_ОТВ_ЗАК" --- // --- НОВАЯ ЛОГИКА: Получение FieldId и разрешённых значений для "РАБОЧАЯ_ГР_ОТВ_ЗАК" ---
var workGroupField = await unitFieldService.GetByAihitNameAsync("РАБОЧАЯ_ГР_ОТВ_ЗАК"); var workGroupField = await unitFieldService.GetByAihitNameAsync("РАБОЧАЯ_ГР_ОТВ_ЗАК");
if (workGroupField == null) if (workGroupField == null)
{ {
@@ -123,17 +126,6 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
logger.LogDebug("Найдено {Count} значений из UnitRegionalEkPtkGroup для проверки поля 'РАБОЧАЯ_ГР_ОТВ_ЗАК'.", regionalGroupValueIds.Count); logger.LogDebug("Найдено {Count} значений из UnitRegionalEkPtkGroup для проверки поля 'РАБОЧАЯ_ГР_ОТВ_ЗАК'.", regionalGroupValueIds.Count);
// Загружаем заранее подготовленный Job (например, для неиспользуемых шаблонов)
var unusedJobId = Guid.Parse("8f85a91c-a223-4686-bb69-1f0ee73624f2");
var unusedJob = await jobService.Get()
.AsNoTracking()
.Include(j => j.Tnk)
.Include(j => j.Group)
.ThenInclude(g => g.GroupType)
.FirstOrDefaultAsync(j => j.Id == unusedJobId);
// 2. Найти Job с максимальным MaxValueRelationships // 2. Найти Job с максимальным MaxValueRelationships
var maxJob = jobsInGroup var maxJob = jobsInGroup
.Where(j => j.MaxValueRelationships.HasValue) .Where(j => j.MaxValueRelationships.HasValue)
@@ -162,6 +154,19 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
return; return;
} }
#if DEBUG
// ✅ Отладка: проверить, есть ли юнит в expectedUnitIds
if (expectedUnitIds.Contains(targetUnitId))
{
logger.LogDebug("Юнит {TargetUnitId} найден в expectedUnitIds.", targetUnitId);
}
else
{
logger.LogDebug("Юнит {TargetUnitId} НЕ найден в expectedUnitIds.", targetUnitId);
//return; // ❌ юнит отсеялся на этом этапе
}
#endif
// 4. Отфильтровать expectedUnitIds по GroupingUnitFieldId (дополнительный фильтр) // 4. Отфильтровать expectedUnitIds по GroupingUnitFieldId (дополнительный фильтр)
if (!jobGroup.GroupingUnitFieldId.HasValue) if (!jobGroup.GroupingUnitFieldId.HasValue)
{ {
@@ -185,6 +190,19 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
.Select(u => u.Id) .Select(u => u.Id)
.ToHashSet(); .ToHashSet();
#if DEBUG
// ✅ Отладка: проверить, есть ли юнит в unitIdsWithValidGroupingFieldSet
if (unitIdsWithValidGroupingFieldSet.Contains(targetUnitId))
{
logger.LogDebug("Юнит {TargetUnitId} найден в unitIdsWithValidGroupingFieldSet.", targetUnitId);
}
else
{
logger.LogDebug("Юнит {TargetUnitId} НЕ найден в unitIdsWithValidGroupingFieldSet.", targetUnitId);
//return; // ❌ юнит отсеялся на этом этапе
}
#endif
logger.LogDebug("После фильтрации по GroupingUnitFieldId осталось {Count} юнитов.", unitIdsWithValidGroupingFieldSet.Count); logger.LogDebug("После фильтрации по GroupingUnitFieldId осталось {Count} юнитов.", unitIdsWithValidGroupingFieldSet.Count);
if (!unitIdsWithValidGroupingFieldSet.Any()) if (!unitIdsWithValidGroupingFieldSet.Any())
@@ -204,6 +222,19 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
.Select(u => u.Id) // Выбираем Id юнита .Select(u => u.Id) // Выбираем Id юнита
.ToHashSet(); // И снова в HashSet .ToHashSet(); // И снова в HashSet
#if DEBUG
// ✅ Отладка: проверить, есть ли юнит в unitIdsWithValidWorkGroupFieldSet
if (unitIdsWithValidWorkGroupFieldSet.Contains(targetUnitId))
{
logger.LogDebug("Юнит {TargetUnitId} найден в unitIdsWithValidWorkGroupFieldSet.", targetUnitId);
}
else
{
logger.LogDebug("Юнит {TargetUnitId} НЕ найден в unitIdsWithValidWorkGroupFieldSet.", targetUnitId);
//return; // ❌ юнит отсеялся на этом этапе
}
#endif
logger.LogDebug("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗАК' осталось {Count} юнитов.", unitIdsWithValidWorkGroupFieldSet.Count); logger.LogDebug("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗАК' осталось {Count} юнитов.", unitIdsWithValidWorkGroupFieldSet.Count);
if (!unitIdsWithValidWorkGroupFieldSet.Any()) if (!unitIdsWithValidWorkGroupFieldSet.Any())
@@ -225,6 +256,20 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
.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 // Загрузить UnitInValue для всех ParentUnitId и ChildUnitId из potentialUnitInUnitLinks
@@ -288,6 +333,20 @@ 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 по связанному юниту --- // --- Сгруппировать юниты из unitIdsWithValidGroupingFieldSet по связанному юниту ---
@@ -317,6 +376,22 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
logger.LogDebug("Сформировано {Count} групп по связанным юнитам до разрешения конфликтов.", groupedRelationships.Count); logger.LogDebug("Сформировано {Count} групп по связанным юнитам до разрешения конфликтов.", groupedRelationships.Count);
#if DEBUG
// ✅ Отладка: проверить, юнит есть в groupedRelationships.Values
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 только в одном списке значений --- // --- Разрешение конфликта - один юнит из unitIdsWithValidGroupingFieldSet только в одном списке значений ---
var unitToKeys = new Dictionary<Guid, List<Guid>>(); // Карта: юнит из списка -> список ключей, где он встречается var unitToKeys = new Dictionary<Guid, List<Guid>>(); // Карта: юнит из списка -> список ключей, где он встречается
@@ -334,9 +409,34 @@ 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;
@@ -367,6 +467,20 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
} }
} }
#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)
@@ -377,11 +491,26 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
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)}]")));
#endif
// 7. Разбить каждую группу и сопоставить с Job // 7. Разбить каждую группу и сопоставить с Job
foreach (var kvp in groupedRelationships) foreach (var kvp in groupedRelationships)
{ {
var relationshipUnitId = kvp.Key; // Связанный юнит (не из unitIdsWithValidGroupingFieldSet) var relationshipUnitId = kvp.Key; // Связанный юнит (не из unitIdsWithValidGroupingFieldSet)
var childUnitIds = kvp.Value; // Юниты из unitIdsWithValidGroupingFieldSet, связанные с regionalUnitId var childUnitIds = kvp.Value; // Юниты из unitIdsWithValidGroupingFieldSet, связанные с regionalUnitId
if (childUnitIds.Count == 0)
continue;
#if DEBUG
// ✅ Отладка: проверить, есть ли ВРТ-AOS-05-ДВС в childUnitIds
var childUnitNames = childUnitIds.Select(id => unitService.Get().AsNoTracking().Where(u => u.Id == id).Select(u => u.Name).FirstOrDefaultAsync().GetAwaiter().GetResult() ?? id.ToString()).ToList();
if (childUnitNames.Contains("ВРТ-AOS-05-ДВС"))
{
var relationshipUnitName = await unitService.Get().AsNoTracking().Where(u => u.Id == relationshipUnitId).Select(u => u.Name).FirstOrDefaultAsync();
logger.LogDebug("Группа с ключом {Key} (название: {Name}) содержит юнит 'ВРТ-AOS-05-ДВС' в childUnitIds: [{ChildUnitNames}]", relationshipUnitId, relationshipUnitName, string.Join(", ", childUnitNames));
}
#endif
logger.LogDebug("Обработка связанного юнита {RegionalUnitId} с {Count} юнитами из списка.", relationshipUnitId, childUnitIds.Count); logger.LogDebug("Обработка связанного юнита {RegionalUnitId} с {Count} юнитами из списка.", relationshipUnitId, childUnitIds.Count);
@@ -431,7 +560,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
logger.LogDebug("Шаблон {TemplateId} (Job {JobId}, Regional {RegionalId}, Index {Index}) актуален по юнитам.", existingTemplateForSubGroup.Id, targetJob.Id, relationshipUnitId, i); logger.LogDebug("Шаблон {TemplateId} (Job {JobId}, Regional {RegionalId}, Index {Index}) актуален по юнитам.", existingTemplateForSubGroup.Id, targetJob.Id, relationshipUnitId, i);
// Проверить, изменилось ли имя шаблона (например, из-за %МАКС:...% или %ТНК-КРАТКО%) // Проверить, изменилось ли имя шаблона (например, из-за %МАКС:...% или %ТНК-КРАТКО%)
var expectedName = await GetNormalizedTemplateNameAsync(targetJob, relationshipUnitId, i, subGroup); var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(targetJob, relationshipUnitId, i, subGroup);
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} требует обновления имени: старое = '{OldName}', новое = '{NewName}'", existingTemplateForSubGroup.Id, existingTemplateForSubGroup.Name, expectedName);
@@ -448,13 +577,13 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
IsActiveSchedule = existingTemplateForSubGroup.IsActiveSchedule, IsActiveSchedule = existingTemplateForSubGroup.IsActiveSchedule,
LastRun = existingTemplateForSubGroup.LastRun, LastRun = existingTemplateForSubGroup.LastRun,
NextRun = nextRun, NextRun = nextRun,
Index = i , Index = i,
StatusTypeId = TemplateStatusTypeEnum.Used, StatusTypeId = TemplateStatusTypeEnum.Used,
Initiator = initiator, Initiator = initiator,
UnitsInTemplate = subGroup UnitsInTemplate = subGroup
}; };
await SendTemplateUpdateMessage(updateRequest); await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
} }
else else
{ {
@@ -485,7 +614,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
{ {
logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, связанного юнита {RegionalId}, Index {Index}.", reusableTemplate.Id, targetJob.Id, relationshipUnitId, i); logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, связанного юнита {RegionalId}, Index {Index}.", reusableTemplate.Id, targetJob.Id, relationshipUnitId, i);
var expectedName = await GetNormalizedTemplateNameAsync(targetJob, relationshipUnitId, i, subGroup); var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(targetJob, relationshipUnitId, i, subGroup);
var nextRun = await GetNextRunAsync(targetJob); var nextRun = await GetNextRunAsync(targetJob);
var updateRequest = new TemplateUpdaterMq var updateRequest = new TemplateUpdaterMq
@@ -503,7 +632,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
UnitsInTemplate = subGroup UnitsInTemplate = subGroup
}; };
await SendTemplateUpdateMessage(updateRequest); await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
} }
else else
{ {
@@ -553,7 +682,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
if (!expectedTemplateKeys.Contains(key)) if (!expectedTemplateKeys.Contains(key))
{ {
logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, Regional {UnitId}, Index {Index}).", existingTemplate.Id, existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index); logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, Regional {UnitId}, Index {Index}).", existingTemplate.Id, existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index);
await DeactivateTemplateAsync(existingTemplate, existingTemplate.JobId, unusedJob, initiator); await templateDeactivator.DeactivateTemplateAsync(existingTemplate, initiator);
} }
} }
@@ -613,7 +742,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
return; return;
} }
var expectedName = await GetNormalizedTemplateNameAsync(targetJob, template.UnitId, template.Index, newUnitIds); var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(targetJob, template.UnitId, template.Index, newUnitIds);
var nextRun = await GetNextRunAsync(targetJob, template.NextRun); var nextRun = await GetNextRunAsync(targetJob, template.NextRun);
var updateRequest = new TemplateUpdaterMq var updateRequest = new TemplateUpdaterMq
@@ -632,7 +761,7 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
UnitsInTemplate = newUnitIds UnitsInTemplate = newUnitIds
}; };
await SendTemplateUpdateMessage(updateRequest); await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
} }
private async Task CreateGroupedTemplateAsync(Guid jobId, Guid regionalUnitId, List<Guid> unitIds, int index, HistoryInitiator initiator) private async Task CreateGroupedTemplateAsync(Guid jobId, Guid regionalUnitId, List<Guid> unitIds, int index, HistoryInitiator initiator)
@@ -655,132 +784,9 @@ internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
logger.LogError("Ошибка отправки команды создания группового шаблона для Job {JobId}, связанного юнита {RegionalUnitId}, Index {Index}.", jobId, regionalUnitId, index); logger.LogError("Ошибка отправки команды создания группового шаблона для Job {JobId}, связанного юнита {RegionalUnitId}, Index {Index}.", jobId, regionalUnitId, index);
} }
private async Task<bool> DeactivateTemplateAsync(
Template template,
Guid jobId,
Job unusedJob,
HistoryInitiator initiator)
{
if (template.StatusTypeId == TemplateStatusTypeEnum.Updating)
return true;
if (template.StatusTypeId == TemplateStatusTypeEnum.Unused)
{
logger.LogDebug("Шаблон {TemplateId} уже неактивен (Unused), пропускаем деактивацию.", template.Id);
return true;
}
logger.LogInformation("Шаблон {TemplateId} (UnitId {UnitId}) → деактивация.",
template.Id, template.UnitId);
template.StatusTypeId = TemplateStatusTypeEnum.Updating;
template.DateModified = DateTimeOffset.UtcNow;
if (!await templateService.CommitAsync(initiator))
{
logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id);
return false;
}
if (unusedJob == null)
{
logger.LogError("Job для деактивированных шаблонов не найден.");
return false;
}
// Вычисляем имя шаблона с новым Job
var expectedName = await GetNormalizedTemplateNameAsync(unusedJob, template.UnitId);
var updateRequest = new TemplateUpdaterMq
{
TemplateId = template.Id,
JobId = unusedJob.Id,
UnitId = template.UnitId,
Name = GetTemplateNameForUnused(expectedName),
IsActiveTemplate = DefaultUnusedTemplateState,
IsActiveSchedule = DefaultUnusedScheduleState,
LastRun = template.LastRun,
NextRun = template.NextRun,
Index = null,
StatusTypeId = TemplateStatusTypeEnum.Unused,
Initiator = initiator,
UnitsInTemplate = new List<Guid>()
};
await SendTemplateUpdateMessage(updateRequest);
return true;
}
private async Task SendTemplateUpdateMessage(TemplateUpdaterMq updateRequest)
{
logger.LogDebug("Отправка сообщения в очередь '{Queue}' для шаблона {TemplateId}",
mqSettings.TemplateUpdater.QueueName, updateRequest.TemplateId);
var msg = JsonSerializer.Serialize(updateRequest);
var result = await mqService.SendAsync(mqSettings.TemplateUpdater, new[] { msg });
if (result.IsSuccess)
{
logger.LogInformation("Отправлен запрос на обновление шаблона {TemplateId}", updateRequest.TemplateId);
}
else
{
logger.LogError("Ошибка при отправке запроса на обновление шаблона {TemplateId} в очередь '{Queue}'.",
updateRequest.TemplateId, mqSettings.TemplateUpdater.QueueName);
}
}
private string GetTemplateNameForUnused(string templateName)
{
return templateName + "_" + DateTimeOffset.UtcNow.ToUnixTimeSeconds();
}
private async Task<string> GetNormalizedTemplateNameAsync(Job targetJob, Guid unitId, int? index = null, List<Guid>? templateUnitIds = null)
{
// Подготовка объекта TemplateForShortcodes для передачи в ShortcodesService
var templateForShortcodes = new TemplateForShortcodes
{
Id = Guid.Empty, // Не используется в подстановке, но нужен для структуры
Index = index+1,
JobId = targetJob.Id,
UnitId = unitId,
Job = new JobForShortcodes
{
Group = targetJob.Group != null ? new JobGroupForShortcodes
{
Id = targetJob.Group.Id,
GroupingUnitFieldId = targetJob.Group.GroupingUnitFieldId,
GroupType = targetJob.Group.GroupType != null ? new JobGroupTypeForShortcodes
{
Code = targetJob.Group.GroupType.Code
} : null,
GroupName = targetJob.Group.GroupName
} : null,
Tnk = targetJob.Tnk != null ? new TnkForShortcodes
{
Name = targetJob.Tnk.Name,
ShortName = targetJob.Tnk.ShortName ?? ""
} : null,
WorkName = targetJob.WorkName,
Name = targetJob.Name
},
// Преобразование List<Guid> в List<UnitInTemplateForShortcodes>
UnitsInTemplate = templateUnitIds?.Select(id => new UnitInTemplateForShortcodes { UnitId = id }).ToList() ?? new List<UnitInTemplateForShortcodes>()
};
var rawName = await shortcodesService.ApplyShortcodesAsync(targetJob.TemplateNameMask, templateForShortcodes);
return rawName.ToUpper();
}
private async Task<DateTimeOffset> GetNextRunAsync(Job targetJob, DateTimeOffset? currentNextRun = null) private async Task<DateTimeOffset> GetNextRunAsync(Job targetJob, DateTimeOffset? currentNextRun = null)
{ {
var now = DateTimeOffset.UtcNow; var referenceDate = targetJob.Group?.ReferenceDate ?? DateTimeOffset.UtcNow;
if (currentNextRun.HasValue && currentNextRun.Value > now)
{
return currentNextRun.Value;
}
var referenceDate = targetJob.Group?.ReferenceDate ?? now;
return await esppScheduleTransformService.GetNextDateAsync(targetJob.GroupId, referenceDate); return await esppScheduleTransformService.GetNextDateAsync(targetJob.GroupId, referenceDate);
} }
} }

View File

@@ -4,25 +4,25 @@ 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.Contracts;
using PARR.DAL.DomainServices.Interfaces; using PARR.DAL.DomainServices.Interfaces;
using PARR.DAL.DomainServices.Shortcodes; using PARR.DAL.DomainServices.Shortcodes;
using PARR.DAL.DomainServices.Shortcodes.Models;
using PARR.DAL.Models;
using PARR.DAL.Models.Job; using PARR.DAL.Models.Job;
using PARR.DAL.Services.Interfaces; using PARR.DAL.Services.Interfaces;
using PARR.DAL.Services.Interfaces.Job; using PARR.DAL.Services.Interfaces.Job;
using PARR.DAL.Services.Interfaces.Unit;
using PARR.DAL.TransformServices; using PARR.DAL.TransformServices;
using PARR.TemplateMatcher.Services.Interfaces; using PARR.TemplateMatcher.Services.Interfaces;
using PARR.TemplateMatcher.Settings; using PARR.TemplateMatcher.Settings;
using System.Text.Json; using System.Text.Json;
namespace PARR.TemplateMatcher.Services.Implemetaions; namespace PARR.TemplateMatcher.Services.Implementations;
internal class SimpleTemplateSynchronizer : ITemplateSynchronizer internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
{ {
private const bool DefaultUnusedTemplateState = false; #if DEBUG
private const bool DefaultUnusedScheduleState = false; private readonly Guid targetUnitId = Guid.Parse("358437ac-1eeb-4c00-840c-998326f657ac");
#endif
private const bool DefaultUsedTemplateState = false; private const bool DefaultUsedTemplateState = false;
private const bool DefaultUsedScheduleState = false; private const bool DefaultUsedScheduleState = false;
@@ -33,19 +33,30 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
private readonly ITemplateService templateService; private readonly ITemplateService templateService;
private readonly IJobService jobService; private readonly IJobService jobService;
private readonly ITemplateReuser templateReuser; private readonly ITemplateReuser templateReuser;
private readonly IShortcodesService shortcodesService;
private readonly IEsppScheduleTransformService esppScheduleTransformService; private readonly IEsppScheduleTransformService esppScheduleTransformService;
private readonly ITemplateDeactivator templateDeactivator;
private readonly ITemplateNameNormalizer templateNameNormalizer;
private readonly ITemplateUpdaterMqSender templateUpdaterMqSender;
public SimpleTemplateSynchronizer( public SimpleTemplateSynchronizer(
ILogger<SimpleTemplateSynchronizer> logger, ILogger<SimpleTemplateSynchronizer> logger,
IUnitFilterService unitFilterService, IUnitFilterService unitFilterService,
IUnitInUnitService unitInUnitService,
IUnitInValueService unitInValueService,
IUnitService unitService,
MqSettings mqSettings, MqSettings mqSettings,
IMqService mqService, IMqService mqService,
ITemplateService templateService, ITemplateService templateService,
IJobService jobService, IJobService jobService,
ITemplateReuser templateReuser, ITemplateReuser templateReuser,
IShortcodesService shortcodesService, IShortcodesService shortcodesService,
IEsppScheduleTransformService esppScheduleTransformService) IEsppScheduleTransformService esppScheduleTransformService,
IUnitRegionalEkPtkGroupService regionalEkPtkGroupService,
IUnitFieldService unitFieldService,
ITemplateDeactivator templateDeactivator,
ITemplateNameNormalizer templateNameNormalizer,
ITemplateUpdaterMqSender templateUpdaterMqSender
)
{ {
this.logger = logger; this.logger = logger;
this.unitFilterService = unitFilterService; this.unitFilterService = unitFilterService;
@@ -54,120 +65,82 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
this.templateService = templateService; this.templateService = templateService;
this.jobService = jobService; this.jobService = jobService;
this.templateReuser = templateReuser; this.templateReuser = templateReuser;
this.shortcodesService = shortcodesService;
this.esppScheduleTransformService = esppScheduleTransformService; this.esppScheduleTransformService = esppScheduleTransformService;
} this.templateDeactivator = templateDeactivator;
this.templateNameNormalizer = templateNameNormalizer;
public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator) this.templateUpdaterMqSender = templateUpdaterMqSender;
{
logger.LogWarning("SimpleTemplateSynchronizer: SyncTemplatesForJobGroup вызван для JobGroupId {JobGroupId}. Это не поддерживаемая операция.", jobGroupId);
// Не делаем ничего
return;
} }
public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator) public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
{ {
logger.LogDebug("Начало синхронизации шаблонов для JobId {JobId}", jobId); logger.LogDebug("Начало синхронизации шаблонов для Job {JobId}", jobId);
// Загружаем заранее подготовленный Job (например, для неиспользуемых шаблонов) var job = await jobService.Get()
var unusedJobId = Guid.Parse("8f85a91c-a223-4686-bb69-1f0ee73624f2");
var unusedJob = await jobService.Get()
.AsNoTracking() .AsNoTracking()
.Include(j => j.AutoControl)
.Include(j => j.Tnk) .Include(j => j.Tnk)
.Include(j => j.Group) .Include(j => j.Group)
.ThenInclude(g => g.GroupType) .ThenInclude(g => g!.GroupType)
.FirstOrDefaultAsync(j => j.Id == unusedJobId); .Include(j => j.UnitFilters)
.ThenInclude(uf => uf.RelationshipFilters)
.FirstOrDefaultAsync(j => j.Id == jobId);
var expectedUnitIds = await GetExpectedUnitIdsAsync(jobId) ?? new HashSet<Guid>();
logger.LogDebug("JobId {JobId}: найдено {Count} UnitId по фильтрам.", jobId, expectedUnitIds.Count);
var job = await GetJobWithGroupAndAutoControlAsync(jobId);
if (job == null) if (job == null)
{ {
logger.LogError("Job с Id {JobId} не найден.", jobId); logger.LogWarning("Job {JobId} не найден.", jobId);
return; return;
} }
// Проверяем, является ли Job "групповым" var unitIds = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId);
bool isGroupJob = job.Group != null && job.Group.GroupType?.Code == JobGroupTypesEnum.Group; if (unitIds == null || !unitIds.Any())
if (isGroupJob && job.Group!.GroupingUnitFieldId.HasValue)
{ {
logger.LogInformation("Job {JobId} является групповым. Используйте SyncTemplatesForJobGroup для синхронизации.", jobId); logger.LogInformation("Для Job {JobId} фильтры не дали Unit'ов.", jobId);
return; // Ничего не делаем для группового Job return;
} }
#if DEBUG
// ✅ Отладка: проверить, есть ли юнит в unitIds
if (unitIds.Contains(targetUnitId))
{
logger.LogDebug("Юнит {TargetUnitId} найден в unitIds.", targetUnitId);
}
else
{
logger.LogDebug("Юнит {TargetUnitId} НЕ найден в unitIds.", targetUnitId);
return; // ❌ юнит отсеялся на этом этапе
}
#endif
var existingTemplates = await templateService.Get() var existingTemplates = await templateService.Get()
.AsNoTracking() .AsNoTracking()
.Include(t => t.UnitsInTemplate)
.Where(t => t.JobId == jobId) .Where(t => t.JobId == jobId)
.ToListAsync(); .ToListAsync();
logger.LogDebug("JobId {JobId}: {Expected} ожидаемых UnitId, {Existing} существующих шаблонов.", var existingUnitIds = existingTemplates
jobId, expectedUnitIds.Count, existingTemplates.Count); .Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Used)
.Select(t => t.UnitId)
.ToHashSet();
// Обработка случая: фильтр вернул 0 UnitId → деактивировать ВСЕ шаблоны var newUnitIds = unitIds.Except(existingUnitIds).ToList();
if (!expectedUnitIds.Any()) var unusedTemplates = existingTemplates
{ .Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Used && !unitIds.Contains(t.UnitId))
if (existingTemplates.Any())
{
logger.LogInformation("Для JobId {JobId} фильтры не дали Unit'ов — будет деактивировано {Count} шаблонов.",
jobId, existingTemplates.Count);
foreach (var template in existingTemplates)
{
await DeactivateTemplateAsync(template, jobId, unusedJob, initiator);
}
}
else
{
logger.LogInformation("Для JobId {JobId} нет Unit'ов по фильтрам и нет существующих шаблонов — синхронизация завершена.", jobId);
}
logger.LogInformation("Синхронизация завершена для JobId {JobId} (фильтр пуст).", jobId);
return;
}
// Деактивация шаблонов, которые вышли из фильтра
var templatesToDeactivate = existingTemplates
.Where(t => !expectedUnitIds.Contains(t.UnitId))
.ToList(); .ToList();
foreach (var template in templatesToDeactivate) foreach (var unitId in newUnitIds)
{ {
await DeactivateTemplateAsync(template, jobId, unusedJob, initiator); var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(jobId, unitId, initiator);
}
// Перечитываем шаблоны после деактивации if (reusableTemplate != null)
existingTemplates = await templateService.Get()
.AsNoTracking()
.Include(t => t.Job)
.ThenInclude(t => t.Tnk)
.Where(t => t.JobId == jobId)
.ToListAsync();
var unitToTemplate = existingTemplates.ToDictionary(t => t.UnitId, t => t);
// UnitId без шаблона → попытка переиспользования или создание
var unitIdsMissingTemplates = expectedUnitIds
.Where(unitId => !unitToTemplate.ContainsKey(unitId))
.ToList();
var unitIdsToCreateFresh = new List<Guid>();
foreach (var unitId in unitIdsMissingTemplates)
{
var reused = await templateReuser.TryReuseOneUnusedTemplateAsync(jobId, unitId, initiator);
if (reused != null) // если захват успешен
{ {
logger.LogInformation("Переиспользован шаблон {TemplateId} для UnitId {UnitId}.", reused.Id, unitId); logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, UnitId {UnitId}.", reusableTemplate.Id, jobId, unitId);
var expectedName = await GetNormalizedTemplateNameAsync(job, unitId); var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(job, unitId);
var nextRun = await GetNextRunAsync(job); // всегда пересчитываем для нового назначения var nextRun = await GetNextRunAsync(job);
var updateRequest = new TemplateUpdaterMq var updateRequest = new TemplateUpdaterMq
{ {
TemplateId = reused.Id, TemplateId = reusableTemplate.Id,
JobId = jobId, JobId = jobId,
UnitId = unitId, UnitId = unitId,
Name = expectedName, Name = expectedName,
@@ -176,362 +149,135 @@ 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 SendTemplateUpdateMessage(updateRequest); await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
} }
else else
{ {
logger.LogInformation("Нет доступных Unused-шаблонов для UnitId {UnitId} → создадим новый.", unitId); logger.LogDebug("Создание нового шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId);
unitIdsToCreateFresh.Add(unitId); await CreateSimpleTemplateAsync(jobId, unitId, initiator);
} }
} }
// Обновление/реактивация шаблонов, оставшихся в фильтре foreach (var unusedTemplate in unusedTemplates)
var templatesInFilter = existingTemplates
.Where(t => expectedUnitIds.Contains(t.UnitId))
.ToList();
foreach (var template in templatesInFilter)
{ {
var expectedName = await GetNormalizedTemplateNameAsync(job, template.UnitId); logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, UnitId {UnitId}).", unusedTemplate.Id, jobId, unusedTemplate.UnitId);
await ReactivateOrRenameTemplateAsync(template, job, expectedName, initiator); await templateDeactivator.DeactivateTemplateAsync(unusedTemplate, initiator);
} }
// Создание новых шаблонов logger.LogInformation("Синхронизация шаблонов завершена для Job {JobId}.", jobId);
foreach (var unitId in unitIdsToCreateFresh) }
{
await SendTemplateGeneratorMessageAsync(jobId, unitId, initiator);
}
logger.LogInformation("Синхронизация завершена для JobId {JobId}.", jobId); public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator)
{
logger.LogWarning("SimpleTemplateSynchronizer: SyncTemplatesForJobGroup вызван для JobGroup {JobGroupId}. Это не поддерживаемая операция.", jobGroupId);
} }
public async Task UpdateTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator) public async Task UpdateTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
{ {
logger.LogDebug("Начало обновления шаблонов для JobId {JobId}", jobId); logger.LogDebug("Обновление шаблонов для Job {JobId}", jobId);
// Загружаем заранее подготовленный Job (например, для неиспользуемых шаблонов) var job = await jobService.Get()
var unusedJobId = Guid.Parse("8f85a91c-a223-4686-bb69-1f0ee73624f2");
var unusedJob = await jobService.Get()
.AsNoTracking() .AsNoTracking()
.Include(j => j.AutoControl)
.Include(j => j.Tnk) .Include(j => j.Tnk)
.Include(j => j.Group) .Include(j => j.Group)
.ThenInclude(g => g.GroupType) .ThenInclude(g => g!.GroupType)
.FirstOrDefaultAsync(j => j.Id == unusedJobId); .Include(j => j.UnitFilters)
.ThenInclude(uf => uf.RelationshipFilters)
.FirstOrDefaultAsync(j => j.Id == jobId);
if (job == null)
{
logger.LogWarning("Job {JobId} не найден.", jobId);
return;
}
var unitIds = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId);
if (unitIds == null || !unitIds.Any())
{
logger.LogInformation("Для Job {JobId} фильтры не дали Unit'ов.", jobId);
return;
}
#if DEBUG
// ✅ Отладка: проверить, есть ли юнит в unitIds
if (unitIds.Contains(targetUnitId))
{
logger.LogDebug("Юнит {TargetUnitId} найден в unitIds.", targetUnitId);
}
else
{
logger.LogDebug("Юнит {TargetUnitId} НЕ найден в unitIds.", targetUnitId);
return; // ❌ юнит отсеялся на этом этапе
}
#endif
var existingTemplates = await templateService.Get() var existingTemplates = await templateService.Get()
.AsNoTracking() .AsNoTracking()
.Where(t => t.JobId == jobId) .Include(t => t.UnitsInTemplate)
.Where(t => t.JobId == jobId && t.StatusTypeId == TemplateStatusTypeEnum.Used)
.ToListAsync(); .ToListAsync();
if (!existingTemplates.Any()) return;
var job = await GetJobWithGroupAndAutoControlAsync(jobId);
if (job == null) return;
var currentUnitIds = await GetExpectedUnitIdsAsync(jobId) ?? new HashSet<Guid>();
logger.LogDebug("JobId {JobId}: {Count} UnitId по текущему фильтру.", jobId, currentUnitIds.Count);
foreach (var template in existingTemplates) foreach (var template in existingTemplates)
{ {
logger.LogDebug("Обработка шаблона {TemplateId} (UnitId {UnitId}).", template.Id, template.UnitId); if (unitIds.Contains(template.UnitId))
bool unitStillInFilter = currentUnitIds.Contains(template.UnitId);
var expectedName = await GetNormalizedTemplateNameAsync(job, template.UnitId);
TemplateStatusTypeEnum targetStatus;
bool targetIsActiveTemplate;
bool targetIsActiveSchedule;
if (unitStillInFilter)
{ {
targetStatus = TemplateStatusTypeEnum.Used; var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(job, template.UnitId);
targetIsActiveTemplate = template.IsActiveTemplate; if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase))
targetIsActiveSchedule = template.IsActiveSchedule;
}
else
{
targetStatus = TemplateStatusTypeEnum.Unused;
targetIsActiveTemplate = DefaultUnusedTemplateState;
targetIsActiveSchedule = DefaultUnusedScheduleState;
// ✅ Вычисляем имя шаблона с новым unusedJob
if (unusedJob != null)
{ {
expectedName = await GetNormalizedTemplateNameAsync(unusedJob, template.UnitId); logger.LogDebug("Шаблон {TemplateId} требует обновления имени: старое = '{OldName}', новое = '{NewName}'", template.Id, template.Name, expectedName);
var nextRun = await GetNextRunAsync(job, template.NextRun);
var updateRequest = new TemplateUpdaterMq
{
TemplateId = template.Id,
JobId = jobId,
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 = new List<Guid>() // для простого шаблона
};
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
} }
else
{
logger.LogError("Job для деактивированных шаблонов не найден.");
expectedName = GetTemplateNameForUnused(template.Name);
}
logger.LogInformation("Шаблон {TemplateId} (UnitId {UnitId}) → деактивация.", template.Id, template.UnitId);
} }
bool needsUpdate = template.StatusTypeId != TemplateStatusTypeEnum.Updating &&
(
// 1. Статус изменился (например, Used → Unused или Unused → Used)
template.StatusTypeId != targetStatus ||
// 2. Для Used — имя должно соответствовать шаблону
(targetStatus == TemplateStatusTypeEnum.Used && template.Name != expectedName) ||
// 3. Флаги изменились (редко, но возможно через AutoControl изменение)
template.IsActiveTemplate != targetIsActiveTemplate ||
template.IsActiveSchedule != targetIsActiveSchedule
);
if (!needsUpdate) continue;
template.StatusTypeId = TemplateStatusTypeEnum.Updating;
template.DateModified = DateTimeOffset.UtcNow;
if (!await templateService.CommitAsync(initiator))
{
logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id);
continue;
}
var nextRun = await GetNextRunAsync(job, template.NextRun);
var updateRequest = new TemplateUpdaterMq
{
TemplateId = template.Id,
JobId = unitStillInFilter ? jobId : unusedJob?.Id ?? jobId, // ✅ Используем unusedJob.Id, если деактивация
UnitId = template.UnitId,
Name = expectedName,
IsActiveTemplate = targetIsActiveTemplate,
IsActiveSchedule = targetIsActiveSchedule,
LastRun = template.LastRun,
NextRun = nextRun,
Index = template.Index,
StatusTypeId = targetStatus,
Initiator = initiator,
UnitsInTemplate = template.UnitsInTemplate.Select(t => t.UnitId).ToList() // Для простого шаблона это список из одного элемента или пустой
};
await SendTemplateUpdateMessage(updateRequest);
} }
logger.LogInformation("Обновление шаблонов завершено для JobId {JobId}.", jobId); logger.LogInformation("Обновление шаблонов завершено для Job {JobId}.", jobId);
} }
// --- Вспомогательные методы --- private async Task CreateSimpleTemplateAsync(Guid jobId, Guid unitId, HistoryInitiator initiator)
private async Task<bool> DeactivateTemplateAsync(
Template template,
Guid jobId,
Job unusedJob,
HistoryInitiator initiator)
{ {
if (template.StatusTypeId == TemplateStatusTypeEnum.Updating) logger.LogInformation("Создание нового простого шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId);
return true; // уже в обработке
if (template.StatusTypeId == TemplateStatusTypeEnum.Unused)
{
logger.LogDebug("Шаблон {TemplateId} уже неактивен (Unused), пропускаем деактивацию.", template.Id);
return true;
}
logger.LogInformation("Шаблон {TemplateId} (UnitId {UnitId}) → деактивация.",
template.Id, template.UnitId);
template.StatusTypeId = TemplateStatusTypeEnum.Updating;
template.DateModified = DateTimeOffset.UtcNow;
if (!await templateService.CommitAsync(initiator))
{
logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id);
return false;
}
if (unusedJob == null)
{
logger.LogError("Job для деактивированных шаблонов не найден.");
return false;
}
// ✅ Вычисляем имя шаблона с новым Job
var expectedName = await GetNormalizedTemplateNameAsync(unusedJob, template.UnitId);
var updateRequest = new TemplateUpdaterMq
{
TemplateId = template.Id,
JobId = unusedJob.Id, // ✅ Используем unusedJob.Id
UnitId = template.UnitId,
Name = GetTemplateNameForUnused(expectedName), // ✅ Используем новое имя
IsActiveTemplate = DefaultUnusedTemplateState,
IsActiveSchedule = DefaultUnusedScheduleState,
LastRun = template.LastRun,
NextRun = template.NextRun,
Index = template.Index,
StatusTypeId = TemplateStatusTypeEnum.Unused,
Initiator = initiator,
UnitsInTemplate = new List<Guid>() // Для простого шаблона
};
await SendTemplateUpdateMessage(updateRequest);
return true;
}
private async Task<bool> ReactivateOrRenameTemplateAsync(
Template template,
Job job,
string expectedName,
HistoryInitiator initiator)
{
if (template.StatusTypeId == TemplateStatusTypeEnum.Updating)
return true;
bool needsUpdate = template.Name != expectedName
|| template.StatusTypeId != TemplateStatusTypeEnum.Used;
if (!needsUpdate) return true;
logger.LogInformation("Шаблон {TemplateId}: требуется обновление имени или реактивация.", template.Id);
template.StatusTypeId = TemplateStatusTypeEnum.Updating;
template.DateModified = DateTimeOffset.UtcNow;
if (!await templateService.CommitAsync(initiator))
{
logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id);
return false;
}
var nextRun = await GetNextRunAsync(job, template.NextRun);
var updateRequest = new TemplateUpdaterMq
{
TemplateId = template.Id,
JobId = job.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 = template.UnitsInTemplate.Select(t => t.UnitId).ToList() // Для простого шаблона это список из одного элемента
};
await SendTemplateUpdateMessage(updateRequest);
return true;
}
private async Task<bool> SendTemplateGeneratorMessageAsync(
Guid jobId,
Guid unitId,
HistoryInitiator initiator)
{
logger.LogInformation("Создание нового шаблона для UnitId {UnitId}.", unitId);
var mqRequest = new TemplateGeneratorMq var mqRequest = new TemplateGeneratorMq
{ {
JobId = jobId, JobId = jobId,
UnitId = unitId, UnitId = unitId,
HistoryInitiator = initiator, UnitsInTemplate = new List<Guid>(), // для простого шаблона
UnitsInTemplate = new List<Guid>() // Для простого шаблона HistoryInitiator = initiator
}; };
var msg = JsonSerializer.Serialize(mqRequest); var msg = JsonSerializer.Serialize(mqRequest);
var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg }); var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg });
if (!result.IsSuccess) if (!result.IsSuccess)
logger.LogError("Ошибка отправки команды создания шаблона для UnitId {UnitId}.", unitId); logger.LogError("Ошибка отправки команды создания простого шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId);
return result.IsSuccess;
}
private async Task SendTemplateUpdateMessage(TemplateUpdaterMq updateRequest)
{
logger.LogDebug("Отправка сообщения в очередь '{Queue}' для шаблона {TemplateId}",
mqSettings.TemplateUpdater.QueueName, updateRequest.TemplateId);
var msg = JsonSerializer.Serialize(updateRequest);
var result = await mqService.SendAsync(mqSettings.TemplateUpdater, new[] { msg });
if (result.IsSuccess)
{
logger.LogInformation("Отправлен запрос на обновление шаблона {TemplateId}", updateRequest.TemplateId);
}
else
{
logger.LogError("Ошибка при отправке запроса на обновление шаблона {TemplateId} в очередь '{Queue}'.",
updateRequest.TemplateId, mqSettings.TemplateUpdater.QueueName);
}
}
private string GetTemplateNameForUnused(string templateName)
{
return templateName + "_" + DateTimeOffset.UtcNow.ToUnixTimeSeconds();
}
private async Task<Job?> GetJobWithGroupAndAutoControlAsync(Guid jobId)
{
return await jobService.Get()
.AsNoTracking()
.Include(j => j.Tnk)
.Include(j => j.Group)
.ThenInclude(j => j.GroupType)
.Include(j => j.AutoControl)
.FirstOrDefaultAsync(j => j.Id == jobId);
}
private async Task<HashSet<Guid>> GetExpectedUnitIdsAsync(Guid jobId)
{
var units = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId);
return units?.ToHashSet() ?? new HashSet<Guid>();
}
private async Task<string> GetNormalizedTemplateNameAsync(Job job, Guid unitId)
{
// Создаём TemplateForShortcodes "на лету", без запроса к БД
var templateForShortcodes = new TemplateForShortcodes
{
Id = Guid.Empty, // шаблон ещё не создан
Index = null,
JobId = job.Id,
UnitId = unitId,
Job = new JobForShortcodes
{
Group = job.Group == null ? null : new JobGroupForShortcodes
{
GroupingUnitFieldId = job.Group.GroupingUnitFieldId,
GroupType = job.Group.GroupType == null ? null : new JobGroupTypeForShortcodes
{
Code = job.Group.GroupType.Code
},
GroupName = job.Group.GroupName
},
Tnk = job.Tnk == null ? null : new TnkForShortcodes
{
Name = job.Tnk.Name,
ShortName = job.Tnk.ShortName ?? ""
},
WorkName = job.WorkName,
Name = job.Name
},
UnitsInTemplate = new List<UnitInTemplateForShortcodes>() // для простого шаблона
};
var rawName = await shortcodesService.ApplyShortcodesAsync(job.TemplateNameMask, templateForShortcodes);
return rawName.ToUpper();
} }
private async Task<DateTimeOffset> GetNextRunAsync(Job job, DateTimeOffset? currentNextRun = null) private async Task<DateTimeOffset> GetNextRunAsync(Job job, DateTimeOffset? currentNextRun = null)
{ {
var now = DateTimeOffset.UtcNow; var referenceDate = job.Group?.ReferenceDate ?? DateTimeOffset.UtcNow;
if (currentNextRun.HasValue && currentNextRun.Value > now)
{
return currentNextRun.Value;
}
var referenceDate = job.Group?.ReferenceDate ?? now;
return await esppScheduleTransformService.GetNextDateAsync(job.GroupId, referenceDate); return await esppScheduleTransformService.GetNextDateAsync(job.GroupId, referenceDate);
} }
} }

View File

@@ -0,0 +1,101 @@
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.Models;
using PARR.DAL.Models.Job;
using PARR.DAL.Services.Interfaces;
using PARR.DAL.Services.Interfaces.Job;
using PARR.TemplateMatcher.Services.Interfaces;
using PARR.TemplateMatcher.Settings;
using System.Text.Json;
namespace PARR.TemplateMatcher.Services.Implementations;
internal class TemplateDeactivator : ITemplateDeactivator
{
private const bool DefaultUnusedTemplateState = false;
private const bool DefaultUnusedScheduleState = false;
private readonly ILogger<TemplateDeactivator> logger;
private readonly ITemplateService templateService;
private readonly IJobService jobService;
private readonly ITemplateNameNormalizer namenormalizer;
private readonly ITemplateUpdaterMqSender sender;
private readonly Guid unusedJobId = Guid.Parse("8f85a91c-a223-4686-bb69-1f0ee73624f2");
public TemplateDeactivator(
ILogger<TemplateDeactivator> logger,
ITemplateService templateService,
IJobService jobService,
ITemplateNameNormalizer namenormalizer,
ITemplateUpdaterMqSender sender)
{
this.logger = logger;
this.templateService = templateService;
this.jobService = jobService;
this.namenormalizer = namenormalizer;
this.sender = sender;
}
public async Task<bool> DeactivateTemplateAsync(Template template, HistoryInitiator initiator)
{
if (template.StatusTypeId == TemplateStatusTypeEnum.Updating)
return true; // уже в обработке
if (template.StatusTypeId == TemplateStatusTypeEnum.Unused)
{
logger.LogDebug("Шаблон {TemplateId} уже неактивен (Unused), пропускаем деактивацию.", template.Id);
return true;
}
logger.LogInformation("Шаблон {TemplateId} (UnitId {UnitId}) → деактивация.",
template.Id, template.UnitId);
template.StatusTypeId = TemplateStatusTypeEnum.Updating;
template.DateModified = DateTimeOffset.UtcNow;
if (!await templateService.CommitAsync(initiator))
{
logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id);
return false;
}
var unusedJob = await jobService.Get()
.AsNoTracking()
.Include(j => j.Tnk)
.Include(j => j.Group)
.ThenInclude(g => g!.GroupType)
.FirstOrDefaultAsync(j => j.Id == unusedJobId);
if (unusedJob == null)
{
logger.LogError("Job для деактивированных шаблонов не найден.");
return false;
}
// Вычисляем имя шаблона с новым Job
var expectedName = await namenormalizer.GetNormalizedTemplateNameAsync(unusedJob, template.UnitId);
var updateRequest = new TemplateUpdaterMq
{
TemplateId = template.Id,
JobId = unusedJob.Id,
UnitId = template.UnitId,
Name = expectedName,
IsActiveTemplate = DefaultUnusedTemplateState,
IsActiveSchedule = DefaultUnusedScheduleState,
LastRun = template.LastRun,
NextRun = template.NextRun,
Index = template.Index,
StatusTypeId = TemplateStatusTypeEnum.Unused,
Initiator = initiator,
UnitsInTemplate = new List<Guid>()
};
await sender.SendTemplateUpdateMessageAsync(updateRequest);
return true;
}
}

View File

@@ -0,0 +1,51 @@
using PARR.DAL.DomainServices.Shortcodes;
using PARR.DAL.DomainServices.Shortcodes.Models;
using PARR.DAL.Models.Job;
using PARR.TemplateMatcher.Services.Interfaces;
namespace PARR.TemplateMatcher.Services.Implementations;
internal class TemplateNameNormalizer : ITemplateNameNormalizer
{
private readonly IShortcodesService shortcodesService;
public TemplateNameNormalizer(IShortcodesService shortcodesService)
{
this.shortcodesService = shortcodesService;
}
public async Task<string> GetNormalizedTemplateNameAsync(Job job, Guid unitId, int? index = null, List<Guid>? templateUnitIds = null)
{
var templateForShortcodes = new TemplateForShortcodes
{
Id = Guid.Empty,
Index = (index != null) ? index + 1 : index,
JobId = job.Id,
UnitId = unitId,
Job = new JobForShortcodes
{
Group = job.Group != null ? new JobGroupForShortcodes
{
Id = job.Group.Id,
GroupingUnitFieldId = job.Group.GroupingUnitFieldId,
GroupType = job.Group.GroupType != null ? new JobGroupTypeForShortcodes
{
Code = job.Group.GroupType.Code
} : null,
GroupName = job.Group.GroupName
} : null,
Tnk = job.Tnk != null ? new TnkForShortcodes
{
Name = job.Tnk.Name,
ShortName = job.Tnk.ShortName ?? ""
} : null,
WorkName = job.WorkName,
Name = job.Name
},
UnitsInTemplate = templateUnitIds?.Select(id => new UnitInTemplateForShortcodes { UnitId = id }).ToList() ?? new List<UnitInTemplateForShortcodes>()
};
var rawName = await shortcodesService.ApplyShortcodesAsync(job.TemplateNameMask, templateForShortcodes);
return rawName.ToUpper();
}
}

View File

@@ -0,0 +1,44 @@
using Microsoft.Extensions.Logging;
using PARR.BLL.Domain.Mq;
using PARR.BLL.Services.Interfaces;
using PARR.TemplateMatcher.Services.Interfaces;
using PARR.TemplateMatcher.Settings;
using System.Text.Json;
namespace PARR.TemplateMatcher.Services.Implementations;
internal class TemplateUpdaterMqSender : ITemplateUpdaterMqSender
{
private readonly ILogger<TemplateUpdaterMqSender> logger;
private readonly IMqService mqService;
private readonly MqSettings mqSettings;
public TemplateUpdaterMqSender(
ILogger<TemplateUpdaterMqSender> logger,
IMqService mqService,
MqSettings mqSettings)
{
this.logger = logger;
this.mqService = mqService;
this.mqSettings = mqSettings;
}
public async Task SendTemplateUpdateMessageAsync(TemplateUpdaterMq updateRequest)
{
logger.LogDebug("Отправка сообщения в очередь '{Queue}' для шаблона {TemplateId}",
mqSettings.TemplateUpdater.QueueName, updateRequest.TemplateId);
var msg = JsonSerializer.Serialize(updateRequest);
var result = await mqService.SendAsync(mqSettings.TemplateUpdater, new[] { msg });
if (result.IsSuccess)
{
logger.LogInformation("Отправлен запрос на обновление шаблона {TemplateId}", updateRequest.TemplateId);
}
else
{
logger.LogError("Ошибка при отправке запроса на обновление шаблона {TemplateId} в очередь '{Queue}'.",
updateRequest.TemplateId, mqSettings.TemplateUpdater.QueueName);
}
}
}

View File

@@ -0,0 +1,10 @@
using PARR.Common.Domain;
using PARR.DAL.Models;
namespace PARR.TemplateMatcher.Services.Interfaces
{
public interface ITemplateDeactivator
{
Task<bool> DeactivateTemplateAsync(Template template, HistoryInitiator initiator);
}
}

View File

@@ -0,0 +1,9 @@
using PARR.DAL.Models.Job;
namespace PARR.TemplateMatcher.Services.Interfaces
{
public interface ITemplateNameNormalizer
{
Task<string> GetNormalizedTemplateNameAsync(Job job, Guid unitId, int? index = null, List<Guid>? templateUnitIds = null);
}
}

View File

@@ -0,0 +1,9 @@
using PARR.BLL.Domain.Mq;
namespace PARR.TemplateMatcher.Services.Interfaces
{
public interface ITemplateUpdaterMqSender
{
Task SendTemplateUpdateMessageAsync(TemplateUpdaterMq updateRequest);
}
}

View File

@@ -4,6 +4,7 @@ using PARR.Common.Domain;
using PARR.DAL.Contracts; using PARR.DAL.Contracts;
using PARR.DAL.Models.Job; using PARR.DAL.Models.Job;
using PARR.DAL.Services.Interfaces.Job; using PARR.DAL.Services.Interfaces.Job;
using PARR.TemplateMatcher.Services.Implementations;
using PARR.TemplateMatcher.Services.Implemetaions; using PARR.TemplateMatcher.Services.Implemetaions;
using PARR.TemplateMatcher.Services.Interfaces; using PARR.TemplateMatcher.Services.Interfaces;
@@ -42,7 +43,7 @@ namespace PARR.TemplateMatcher
// Проверяем, является ли Job "групповым" // Проверяем, является ли Job "групповым"
bool isGroupJob = job.Group != null && job.Group.GroupType?.Code == JobGroupTypesEnum.Group; bool isGroupJob = job.Group != null && job.Group.GroupType?.Code == JobGroupTypesEnum.Group;
if (isGroupJob && job.Group.GroupingUnitFieldId.HasValue) if (isGroupJob && job.Group!.GroupingUnitFieldId.HasValue)
{ {
logger.LogInformation("Job {JobId} является групповым. Передаём в GroupedSynchronizer.", jobId); logger.LogInformation("Job {JobId} является групповым. Передаём в GroupedSynchronizer.", jobId);
// Находим нужный синхронизатор // Находим нужный синхронизатор
@@ -103,7 +104,7 @@ namespace PARR.TemplateMatcher
// Проверяем, является ли Job "групповым" // Проверяем, является ли Job "групповым"
bool isGroupJob = job.Group != null && job.Group.GroupType?.Code == JobGroupTypesEnum.Group; bool isGroupJob = job.Group != null && job.Group.GroupType?.Code == JobGroupTypesEnum.Group;
if (isGroupJob && job.Group.GroupingUnitFieldId.HasValue) if (isGroupJob && job.Group!.GroupingUnitFieldId.HasValue)
{ {
logger.LogInformation("Job {JobId} является групповым. Передаём в GroupedSynchronizer для Update.", jobId); logger.LogInformation("Job {JobId} является групповым. Передаём в GroupedSynchronizer для Update.", jobId);
// Находим нужный синхронизатор // Находим нужный синхронизатор
@@ -144,7 +145,7 @@ namespace PARR.TemplateMatcher
return await jobService.Get() return await jobService.Get()
.AsNoTracking() // Добавлено .AsNoTracking() // Добавлено
.Include(j => j.Group) .Include(j => j.Group)
.ThenInclude(j => j.GroupType) .ThenInclude(j => j!.GroupType)
.Include(j => j.AutoControl) .Include(j => j.AutoControl)
.FirstOrDefaultAsync(j => j.Id == jobId); .FirstOrDefaultAsync(j => j.Id == jobId);
} }

View File

@@ -25,6 +25,9 @@ namespace PARR.TemplateMatcher
services.AddTransient<IJobGroupValidatorService, JobGroupValidatorService>(); services.AddTransient<IJobGroupValidatorService, JobGroupValidatorService>();
services.AddTransient<ITemplateMatcher, TemplateMatcher>(); services.AddTransient<ITemplateMatcher, TemplateMatcher>();
services.AddTransient<ITemplateReuser, TemplateReuser>(); services.AddTransient<ITemplateReuser, TemplateReuser>();
services.AddTransient<ITemplateNameNormalizer, TemplateNameNormalizer>();
services.AddTransient<ITemplateUpdaterMqSender, TemplateUpdaterMqSender>();
services.AddTransient<ITemplateDeactivator, TemplateDeactivator>();
services.AddTransient<ITemplateSynchronizer, SimpleTemplateSynchronizer>(); services.AddTransient<ITemplateSynchronizer, SimpleTemplateSynchronizer>();
services.AddTransient<ITemplateSynchronizer, GroupedTemplateSynchronizer>(); services.AddTransient<ITemplateSynchronizer, GroupedTemplateSynchronizer>();
} }