2025-12-23 18:27:31 +10:00
using Microsoft.EntityFrameworkCore ;
using Microsoft.Extensions.Logging ;
2026-02-20 15:57:51 +10:00
using Microsoft.Extensions.Options ;
2026-04-14 16:27:27 +10:00
using PARR.Core.Common.Interfaces.RabbitServices ;
2026-04-30 10:35:31 +10:00
using PARR.Core.Repositories.Interfaces ;
using PARR.Core.Repositories.Interfaces.Job ;
using PARR.Core.Repositories.Interfaces.Unit ;
2026-05-04 11:55:07 +10:00
using PARR.Core.Services.MatchingStatusService ;
using PARR.Core.Services.UnitFilterService ;
2026-04-14 16:27:27 +10:00
using PARR.Domain.Cache.Models ;
2026-05-14 16:47:27 +10:00
using PARR.Domain.Common.Rabbit.Messages.TemplateMatching ;
2026-04-30 10:35:31 +10:00
using PARR.Domain.Entities ;
2026-04-13 16:58:30 +10:00
using PARR.Domain.Entities.Base.History ;
2026-04-30 10:35:31 +10:00
using PARR.Domain.Entities.Job ;
2026-05-14 16:47:27 +10:00
using PARR.Domain.Entities.Unit ;
2026-04-14 12:01:49 +10:00
using PARR.Domain.Enums ;
2026-05-04 11:55:07 +10:00
using PARR.Domain.Settings ;
2026-05-20 17:59:06 +10:00
using PARR.TemplateMatcher.Models ;
2025-12-23 18:27:31 +10:00
using PARR.TemplateMatcher.Services.Interfaces ;
using PARR.TemplateMatcher.Settings ;
2025-12-26 21:47:53 +10:00
namespace PARR.TemplateMatcher.Services.Implementations ;
2025-12-23 18:27:31 +10:00
internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
{
2025-12-26 21:47:53 +10:00
#if DEBUG
private readonly Guid targetUnitId = Guid . Parse ( "358437ac-1eeb-4c00-840c-998326f657ac" ) ;
#endif
2025-12-23 18:27:31 +10:00
private const bool DefaultUsedTemplateState = false ;
private const bool DefaultUsedScheduleState = false ;
2026-05-18 12:29:55 +10:00
// === Константы для логики неиспользуемых шаблонов ===
private const string FieldNameResponsibilityArea = "З О Н А _О Т В Е Т С Т В Е Н Н О С Т И" ;
private const string FieldNameParrTag = "ПАРР тег" ;
private const string TagValueNotWorking = "ПАРР-НЕИСП" ;
2025-12-23 18:27:31 +10:00
private readonly ILogger < SimpleTemplateSynchronizer > logger ;
private readonly IUnitFilterService unitFilterService ;
private readonly MqSettings mqSettings ;
2026-04-14 12:01:49 +10:00
private readonly IRabbitService mqService ;
2026-04-30 10:35:31 +10:00
private readonly ITemplateRepository templateService ;
private readonly IJobRepository jobService ;
2025-12-26 21:47:53 +10:00
private readonly ITemplateDeactivator templateDeactivator ;
private readonly ITemplateNameNormalizer templateNameNormalizer ;
2026-05-20 17:59:06 +10:00
private readonly ITemplateAllocationService templateAllocationService ;
private readonly ITemplateMqPublisher templateMqPublisher ;
2026-01-16 19:45:38 +10:00
private readonly IMatchingStatusService matchingStatusService ;
2026-01-17 09:32:34 +10:00
private readonly SettingsFromDb settingsFromDb ;
2026-02-20 15:57:51 +10:00
private readonly IOptions < TemplateSettings > templateSettings ;
2026-05-14 16:47:27 +10:00
private readonly IUnitFieldRepository unitFieldService ;
private readonly IUnitInValueRepository unitInValueService ;
private readonly IUnitRepository unitRepository ;
2025-12-23 18:27:31 +10:00
public SimpleTemplateSynchronizer (
ILogger < SimpleTemplateSynchronizer > logger ,
IUnitFilterService unitFilterService ,
MqSettings mqSettings ,
2026-04-14 12:01:49 +10:00
IRabbitService mqService ,
2026-04-30 10:35:31 +10:00
ITemplateRepository templateService ,
IJobRepository jobService ,
2025-12-26 21:47:53 +10:00
ITemplateDeactivator templateDeactivator ,
ITemplateNameNormalizer templateNameNormalizer ,
2026-05-20 17:59:06 +10:00
ITemplateAllocationService templateAllocationService ,
ITemplateMqPublisher templateMqPublisher ,
2026-01-17 09:32:34 +10:00
IMatchingStatusService matchingStatusService ,
2026-01-22 12:06:57 +10:00
SettingsFromDb settingsFromDb ,
2026-05-14 16:47:27 +10:00
IOptions < TemplateSettings > templateSettings ,
IUnitFieldRepository unitFieldService ,
IUnitInValueRepository unitInValueService ,
IUnitRepository unitRepository
2025-12-26 21:47:53 +10:00
)
2025-12-23 18:27:31 +10:00
{
this . logger = logger ;
this . unitFilterService = unitFilterService ;
this . mqSettings = mqSettings ;
this . mqService = mqService ;
this . templateService = templateService ;
this . jobService = jobService ;
2025-12-26 21:47:53 +10:00
this . templateDeactivator = templateDeactivator ;
this . templateNameNormalizer = templateNameNormalizer ;
2026-05-20 17:59:06 +10:00
this . templateAllocationService = templateAllocationService ;
this . templateMqPublisher = templateMqPublisher ;
2026-01-16 19:45:38 +10:00
this . matchingStatusService = matchingStatusService ;
2026-01-17 09:32:34 +10:00
this . settingsFromDb = settingsFromDb ;
2026-02-20 15:57:51 +10:00
this . templateSettings = templateSettings ;
2026-05-14 16:47:27 +10:00
this . unitFieldService = unitFieldService ;
this . unitInValueService = unitInValueService ;
this . unitRepository = unitRepository ;
2025-12-23 18:27:31 +10:00
}
public async Task SyncTemplatesForJobAsync ( Guid jobId , HistoryInitiator initiator )
{
2026-01-17 09:32:34 +10:00
// === Специальная обработка для Job неиспользуемых шаблонов ===
if ( jobId = = settingsFromDb . JobIdForUnusedTemplates )
{
logger . LogInformation ( "Обработка синхронизации для Job неиспользуемых шаблонов {JobId}" , jobId ) ;
await SyncUnusedTemplatesAsync ( jobId , initiator ) ;
return ;
}
// === Обычная логика для всех остальных Job ===
2025-12-26 21:47:53 +10:00
logger . LogDebug ( "Начало синхронизации шаблонов для Job {JobId}" , jobId ) ;
2025-12-23 18:27:31 +10:00
2026-01-16 19:45:38 +10:00
// === Проверка: уже запущена? ===
var existingStatus = await matchingStatusService . GetStatusAsync ( jobId , SyncTaskEntityTypeEnum . Job ) ;
if ( existingStatus . DetailsJobs ? . Any ( ) = = true )
2025-12-23 18:27:31 +10:00
{
2026-01-16 19:45:38 +10:00
logger . LogWarning ( "Синхронизация для Job {JobId} уже запущена. Пропускаем." , jobId ) ;
2025-12-23 18:27:31 +10:00
return ;
}
2026-01-16 19:45:38 +10:00
// === Устанавливаем статус "в процессе" ===
var initialStatus = new MatchingStatusItemDto
2025-12-23 18:27:31 +10:00
{
2026-01-16 19:45:38 +10:00
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 ( )
2025-12-30 14:27:59 +10:00
. AsNoTracking ( )
2026-01-16 19:45:38 +10:00
. Include ( j = > j . AutoControl )
. Include ( j = > j . Tnk )
. Include ( j = > j . Group )
. ThenInclude ( g = > g ! . GroupType )
. Include ( j = > j . UnitFilters )
. ThenInclude ( uf = > uf . RelationshipFilters )
. FirstOrDefaultAsync ( j = > j . Id = = jobId ) ;
if ( job = = null )
2025-12-30 14:27:59 +10:00
{
2026-01-16 19:45:38 +10:00
logger . LogWarning ( "Job {JobId} не найден." , jobId ) ;
await UpdateMatchingStatusAsync ( jobId , "Job не найден" ) ;
return ;
2025-12-30 14:27:59 +10:00
}
2026-01-17 09:32:34 +10:00
2026-01-21 18:25:09 +10:00
// === Получение отфильтрованных юнитов с полной информацией ===
var filteredUnits = await unitFilterService . GetUnitsByJobFilterAsync ( jobId ) ;
if ( filteredUnits = = null | | ! filteredUnits . Any ( ) )
2026-01-16 19:45:38 +10:00
{
2026-01-17 09:32:34 +10:00
logger . LogInformation ( "Для Job {JobId} фильтры не дали Unit'ов." , jobId ) ;
2025-12-23 18:27:31 +10:00
2026-01-17 09:32:34 +10:00
var existingTemplatesForDeactivation = await templateService . Get ( )
. AsNoTracking ( )
. Include ( t = > t . UnitsInTemplate )
. Where ( t = > t . JobId = = jobId & & t . StatusTypeId = = TemplateStatusTypeEnum . Used )
. ToListAsync ( ) ;
2025-12-23 18:27:31 +10:00
2026-01-17 09:32:34 +10:00
await UpdateMatchingStatusAsync ( jobId , $"Нет Unit'ов. Деактивация {existingTemplatesForDeactivation.Count} шаблонов..." ) ;
2026-01-16 19:45:38 +10:00
2026-01-17 09:32:34 +10:00
foreach ( var unusedTemplate in existingTemplatesForDeactivation )
{
logger . LogInformation ( "Деактивация лишнего шаблона {TemplateId} (Job {JobId}, UnitId {UnitId})." , unusedTemplate . Id , jobId , unusedTemplate . UnitId ) ;
await templateDeactivator . DeactivateTemplateAsync ( unusedTemplate , initiator ) ;
2026-01-16 19:45:38 +10:00
}
2026-01-17 09:32:34 +10:00
await UpdateMatchingStatusAsync ( jobId , "Синхронизация завершена: нет Unit'ов" ) ;
await matchingStatusService . DeleteMatchingStatusAsync ( jobId , SyncTaskEntityTypeEnum . Job ) ;
logger . LogInformation ( "Синхронизация шаблонов завершена для Job {JobId}." , jobId ) ;
return ;
2026-01-16 19:45:38 +10:00
}
2026-01-17 09:32:34 +10:00
2026-01-21 18:25:09 +10:00
// Извлекаем ID юнитов для последующих операций
var unitIds = filteredUnits . Select ( u = > u . Id ) . ToList ( ) ;
2026-01-16 19:45:38 +10:00
#if DEBUG
2026-01-17 09:32:34 +10:00
// Отладка: проверить, есть ли юнит в unitIds
2026-01-16 19:45:38 +10:00
if ( unitIds . Contains ( targetUnitId ) )
2025-12-23 18:27:31 +10:00
{
2026-01-16 19:45:38 +10:00
logger . LogDebug ( "Юнит {TargetUnitId} найден в unitIds." , targetUnitId ) ;
2025-12-23 18:27:31 +10:00
}
else
{
2026-01-16 19:45:38 +10:00
logger . LogDebug ( "Юнит {TargetUnitId} Н Е найден в unitIds." , targetUnitId ) ;
2025-12-23 18:27:31 +10:00
}
2026-01-16 19:45:38 +10:00
#endif
2025-12-23 18:27:31 +10:00
2026-01-16 19:45:38 +10:00
var existingTemplates = await templateService . Get ( )
. Include ( t = > t . UnitsInTemplate )
. Include ( t = > t . Job )
. ThenInclude ( t = > t ! . Group )
2026-05-14 16:47:27 +10:00
. ThenInclude ( t = > t ! . GroupType )
2026-01-16 19:45:38 +10:00
. Include ( t = > t . Job )
. ThenInclude ( t = > t ! . Tnk )
. Include ( t = > t . Unit )
. Where ( t = > t . JobId = = jobId )
. ToListAsync ( ) ;
var existingUsedTemplates = existingTemplates
. Where ( t = > t . StatusTypeId = = TemplateStatusTypeEnum . Used )
. ToList ( ) ;
var existingUnitIds = existingUsedTemplates
. Select ( t = > t . UnitId )
. ToHashSet ( ) ;
var newUnitIds = unitIds . Except ( existingUnitIds ) . ToList ( ) ;
var unusedTemplates = existingUsedTemplates
. Where ( t = > ! unitIds . Contains ( t . UnitId ) )
. ToList ( ) ;
foreach ( var unitId in newUnitIds )
2026-01-14 10:59:17 +10:00
{
2026-05-20 17:59:06 +10:00
// Вычисляем флаги активности из конфигурации автоконтрола
var isActiveTemplate = job . AutoControl ? . InitUsedTemplateState ? ? DefaultUsedTemplateState ;
var isActiveSchedule = job . AutoControl ? . InitUsedScheduleState ? ? DefaultUsedScheduleState ;
var request = new TemplateAllocationRequest (
TargetJob : job ,
TargetUnitId : unitId ,
TargetUnit : null , // Для простого шаблона не критично, нормализатор загрузит при необходимости
Index : null ,
UnitsInTemplate : new List < UnitInTemplateMessage > ( ) ,
IsActiveTemplate : isActiveTemplate ,
IsActiveSchedule : isActiveSchedule ,
Initiator : initiator ) ;
await templateAllocationService . AllocateAsync ( request ) ;
2026-01-16 19:45:38 +10:00
}
// === Обработка существующих шаблонов (проверка имени) ===
foreach ( var template in existingUsedTemplates )
{
if ( unitIds . Contains ( template . UnitId ) )
{
var expectedName = await templateNameNormalizer . GetNormalizedTemplateNameAsync ( template ) ;
if ( ! string . Equals ( template . Name , expectedName , StringComparison . OrdinalIgnoreCase ) )
{
2026-01-17 09:32:34 +10:00
logger . LogDebug ( "Шаблон {TemplateId} требует обновления имени: старое = '{OldName}', новое = '{NewName}'" , template . Id , template . Name , expectedName ) ;
2026-01-16 19:45:38 +10:00
2026-05-14 16:47:27 +10:00
var updateRequest = new TemplateUpdaterMessage
2026-01-16 19:45:38 +10:00
{
TemplateId = template . Id ,
JobId = jobId ,
UnitId = template . UnitId ,
Name = expectedName ,
IsActiveTemplate = template . IsActiveTemplate ,
IsActiveSchedule = template . IsActiveSchedule ,
2026-02-26 14:42:53 +10:00
IsNew = false ,
2026-01-16 19:45:38 +10:00
Index = template . Index ,
StatusTypeId = TemplateStatusTypeEnum . Used ,
Initiator = initiator ,
2026-05-14 16:47:27 +10:00
UnitsInTemplate = new List < UnitInTemplateMessage > ( ) // для простого шаблона
2026-01-16 19:45:38 +10:00
} ;
2026-05-20 17:59:06 +10:00
await templateMqPublisher . PublishUpdateAsync ( updateRequest ) ;
2026-01-16 19:45:38 +10:00
}
}
}
// === Деактивация лишних шаблонов ===
foreach ( var unusedTemplate in unusedTemplates )
{
logger . LogInformation ( "Деактивация лишнего шаблона {TemplateId} (Job {JobId}, UnitId {UnitId})." , unusedTemplate . Id , jobId , unusedTemplate . UnitId ) ;
await templateDeactivator . DeactivateTemplateAsync ( unusedTemplate , initiator ) ;
2026-01-14 10:59:17 +10:00
}
2026-01-16 19:45:38 +10:00
// === Успешное завершение ===
await UpdateMatchingStatusAsync ( jobId , "Синхронизация завершена успешно" ) ;
await matchingStatusService . DeleteMatchingStatusAsync ( jobId , SyncTaskEntityTypeEnum . Job ) ;
logger . LogInformation ( "Синхронизация шаблонов завершена для Job {JobId}." , jobId ) ;
}
catch ( Exception ex )
2025-12-23 18:27:31 +10:00
{
2026-01-16 19:45:38 +10:00
logger . LogError ( ex , "Ошибка при синхронизации Job {JobId}" , jobId ) ;
await UpdateMatchingStatusAsync ( jobId , $"Ошибка: {ex.Message}" ) ;
throw ;
2025-12-23 18:27:31 +10:00
}
2025-12-26 21:47:53 +10:00
}
2025-12-23 18:27:31 +10:00
2025-12-26 21:47:53 +10:00
public async Task SyncTemplatesForJobGroupAsync ( Guid jobGroupId , HistoryInitiator initiator )
{
logger . LogWarning ( "SimpleTemplateSynchronizer: SyncTemplatesForJobGroup вызван для JobGroup {JobGroupId}. Это не поддерживаемая операция." , jobGroupId ) ;
2025-12-23 18:27:31 +10:00
}
public async Task UpdateTemplatesForJobAsync ( Guid jobId , HistoryInitiator initiator )
{
2025-12-26 21:47:53 +10:00
logger . LogDebug ( "Обновление шаблонов для Job {JobId}" , jobId ) ;
2025-12-23 18:27:31 +10:00
2026-01-16 19:45:38 +10:00
// === Проверка: уже запущена? ===
var existingStatus = await matchingStatusService . GetStatusAsync ( jobId , SyncTaskEntityTypeEnum . Job ) ;
if ( existingStatus . DetailsJobs ? . Any ( ) = = true )
2025-12-23 18:27:31 +10:00
{
2026-01-16 19:45:38 +10:00
logger . LogWarning ( "Обновление для Job {JobId} уже запущено. Пропускаем." , jobId ) ;
2025-12-26 21:47:53 +10:00
return ;
2025-12-23 18:27:31 +10:00
}
2026-01-16 19:45:38 +10:00
var initialStatus = new MatchingStatusItemDto
2025-12-25 16:05:55 +10:00
{
2026-01-16 19:45:38 +10:00
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 ( )
. AsNoTracking ( )
. Include ( j = > j . AutoControl )
. Include ( j = > j . Tnk )
. Include ( j = > j . Group )
. ThenInclude ( g = > g ! . GroupType )
. Include ( j = > j . UnitFilters )
. ThenInclude ( uf = > uf . RelationshipFilters )
. FirstOrDefaultAsync ( j = > j . Id = = jobId ) ;
if ( job = = null )
{
logger . LogWarning ( "Job {JobId} не найден." , jobId ) ;
await UpdateMatchingStatusAsync ( jobId , "Job не найден" ) ;
return ;
}
2026-01-21 18:25:09 +10:00
// === Получение отфильтрованных юнитов с полной информацией ===
var filteredUnits = await unitFilterService . GetUnitsByJobFilterAsync ( jobId ) ;
if ( filteredUnits = = null | | ! filteredUnits . Any ( ) )
2026-01-16 19:45:38 +10:00
{
logger . LogInformation ( "Для Job {JobId} фильтры не дали Unit'ов." , jobId ) ;
await UpdateMatchingStatusAsync ( jobId , "Нет Unit'ов — обновление не требуется" ) ;
await matchingStatusService . DeleteMatchingStatusAsync ( jobId , SyncTaskEntityTypeEnum . Job ) ;
return ;
}
2025-12-25 16:05:55 +10:00
2026-01-21 18:25:09 +10:00
// Извлекаем ID юнитов для последующих операций
var unitIds = filteredUnits . Select ( u = > u . Id ) . ToList ( ) ;
2025-12-26 21:47:53 +10:00
#if DEBUG
2026-01-17 09:32:34 +10:00
// Отладка: проверить, есть ли юнит в unitIds
2026-01-16 19:45:38 +10:00
if ( unitIds . Contains ( targetUnitId ) )
{
logger . LogDebug ( "Юнит {TargetUnitId} найден в unitIds." , targetUnitId ) ;
}
else
{
logger . LogDebug ( "Юнит {TargetUnitId} Н Е найден в unitIds." , targetUnitId ) ;
}
2025-12-26 21:47:53 +10:00
#endif
2025-12-25 21:16:20 +10:00
2026-01-16 19:45:38 +10:00
var existingTemplates = await templateService . Get ( )
. AsNoTracking ( )
. Include ( t = > t . Unit )
. Include ( t = > t . UnitsInTemplate )
. Include ( t = > t . Job )
. ThenInclude ( t = > t ! . Group )
. ThenInclude ( t = > t ! . GroupType )
. Include ( t = > t . Job )
. ThenInclude ( t = > t ! . Tnk )
. Where ( t = > t . JobId = = jobId & & t . StatusTypeId = = TemplateStatusTypeEnum . Used )
. ToListAsync ( ) ;
foreach ( var template in existingTemplates )
{
if ( unitIds . Contains ( template . UnitId ) )
{
var expectedName = await templateNameNormalizer . GetNormalizedTemplateNameAsync ( template ) ;
if ( ! string . Equals ( template . Name , expectedName , StringComparison . OrdinalIgnoreCase ) )
2025-12-26 21:47:53 +10:00
{
2026-01-17 09:32:34 +10:00
logger . LogDebug ( "Шаблон {TemplateId} требует обновления имени: старое = '{OldName}', новое = '{NewName}'" , template . Id , template . Name , expectedName ) ;
2026-01-16 19:45:38 +10:00
2026-05-14 16:47:27 +10:00
var updateRequest = new TemplateUpdaterMessage
2026-01-16 19:45:38 +10:00
{
TemplateId = template . Id ,
JobId = jobId ,
UnitId = template . UnitId ,
Name = expectedName ,
IsActiveTemplate = template . IsActiveTemplate ,
IsActiveSchedule = template . IsActiveSchedule ,
2026-02-26 14:42:53 +10:00
IsNew = false ,
2026-01-16 19:45:38 +10:00
Index = template . Index ,
StatusTypeId = TemplateStatusTypeEnum . Used ,
Initiator = initiator ,
2026-05-14 16:47:27 +10:00
UnitsInTemplate = new List < UnitInTemplateMessage > ( ) // для простого шаблона
2026-01-16 19:45:38 +10:00
} ;
2026-05-20 17:59:06 +10:00
await templateMqPublisher . PublishUpdateAsync ( updateRequest ) ;
2026-01-16 19:45:38 +10:00
}
2025-12-26 21:47:53 +10:00
}
}
2025-12-23 18:27:31 +10:00
2026-01-16 19:45:38 +10:00
await UpdateMatchingStatusAsync ( jobId , "Обновление завершено" ) ;
await matchingStatusService . DeleteMatchingStatusAsync ( jobId , SyncTaskEntityTypeEnum . Job ) ;
logger . LogInformation ( "Обновление шаблонов завершено для Job {JobId}." , jobId ) ;
}
catch ( Exception ex )
{
logger . LogError ( ex , "Ошибка при обновлении Job {JobId}" , jobId ) ;
await UpdateMatchingStatusAsync ( jobId , $"Ошибка: {ex.Message}" ) ;
throw ;
}
2025-12-23 18:27:31 +10:00
}
2026-05-14 16:47:27 +10:00
2026-05-18 12:29:55 +10:00
private async Task SyncUnusedTemplatesAsync ( Guid unusedJobId , HistoryInitiator initiator , CancellationToken ct = default )
2026-01-17 09:32:34 +10:00
{
2026-05-18 12:29:55 +10:00
// Проверка отмены в самом начале
ct . ThrowIfCancellationRequested ( ) ;
2026-01-17 09:32:34 +10:00
var existingStatus = await matchingStatusService . GetStatusAsync ( unusedJobId , SyncTaskEntityTypeEnum . Job ) ;
if ( existingStatus . DetailsJobs ? . Any ( ) = = true )
{
logger . LogWarning ( "Синхронизация для Job неиспользуемых шаблонов {JobId} уже запущена. Пропускаем." , unusedJobId ) ;
return ;
}
var initialStatus = new MatchingStatusItemDto
{
DateStart = DateTimeOffset . UtcNow ,
Action = TemplateMatcherActionEnum . Sync ,
Comment = "Синхронизация неиспользуемых шаблонов"
} ;
await matchingStatusService . SetMatchingStatusAsync (
unusedJobId ,
SyncTaskEntityTypeEnum . Job ,
new MatchingStatusItem { Data = initialStatus , Timestamp = DateTimeOffset . UtcNow , Source = nameof ( SimpleTemplateSynchronizer ) } ,
TimeSpan . FromMinutes ( 30 )
) ;
try
{
2026-05-14 16:47:27 +10:00
// 1. Находим ID нужных полей
2026-05-18 12:29:55 +10:00
var responsableAreaField = await unitFieldService . GetByAihitNameAsync ( FieldNameResponsibilityArea ) ;
var tagField = await unitFieldService . GetByAihitNameAsync ( FieldNameParrTag ) ;
2026-05-14 16:47:27 +10:00
2026-05-18 12:29:55 +10:00
if ( responsableAreaField = = null | | tagField = = null )
2026-05-14 16:47:27 +10:00
{
2026-05-18 12:29:55 +10:00
logger . LogError ( "Н е найдены поля '{Field1}' или '{Field2}'. Синхронизация прервана." , FieldNameResponsibilityArea , FieldNameParrTag ) ;
2026-05-14 16:47:27 +10:00
await UpdateMatchingStatusAsync ( unusedJobId , "Ошибка конфигурации полей" ) ;
return ;
}
2026-05-18 12:29:55 +10:00
var responsableAreaFieldId = responsableAreaField . Id ;
2026-05-14 16:47:27 +10:00
var tagFieldId = tagField . Id ;
2026-05-18 12:29:55 +10:00
const string targetTagValue = TagValueNotWorking ;
2026-05-14 16:47:27 +10:00
// 2. Находим ValueId для тега "ПАРР-НЕИСП"
var targetTagValueId = await unitInValueService . Get ( )
. AsNoTracking ( )
. Where ( uiv = > uiv . FieldId = = tagFieldId & & uiv . Value ! = null & & uiv . Value . Value = = targetTagValue )
. Select ( uiv = > uiv . ValueId )
2026-05-18 12:29:55 +10:00
. FirstOrDefaultAsync ( ct ) ;
2026-05-14 16:47:27 +10:00
if ( targetTagValueId = = Guid . Empty )
{
2026-05-18 12:29:55 +10:00
logger . LogWarning ( "Значение '{TagValue}' для поля '{FieldName}' не найдено в справочнике UnitFieldValue." , targetTagValue , FieldNameParrTag ) ;
2026-05-14 16:47:27 +10:00
}
2026-05-18 12:29:55 +10:00
var unusedJob = await jobService . Get ( ) . AsNoTracking ( )
. Include ( t = > t ! . Group ) . ThenInclude ( t = > t ! . GroupType )
. Include ( t = > t ! . Tnk )
. FirstOrDefaultAsync ( j = > j . Id = = unusedJobId , ct ) ;
2026-01-17 09:32:34 +10:00
if ( unusedJob = = null )
{
logger . LogError ( "Job неиспользуемых шаблонов {JobId} не найден." , unusedJobId ) ;
await UpdateMatchingStatusAsync ( unusedJobId , "Job не найден" ) ;
return ;
}
var unusedTemplates = await templateService . Get ( )
2026-05-18 12:29:55 +10:00
. Include ( t = > t . Unit )
. Where ( t = > t . StatusTypeId = = TemplateStatusTypeEnum . Unused )
. ToListAsync ( ct ) ;
2026-01-17 09:32:34 +10:00
if ( ! unusedTemplates . Any ( ) )
{
logger . LogInformation ( "Н е найдено шаблонов с о статусом Unused." ) ;
await UpdateMatchingStatusAsync ( unusedJobId , "Нет шаблонов для обработки" ) ;
await matchingStatusService . DeleteMatchingStatusAsync ( unusedJobId , SyncTaskEntityTypeEnum . Job ) ;
return ;
}
await UpdateMatchingStatusAsync ( unusedJobId , $"Найдено {unusedTemplates.Count} шаблонов для обработки" ) ;
int processed = 0 ;
2026-05-14 16:47:27 +10:00
var allTemplateUnitIds = unusedTemplates . Select ( t = > t . UnitId ) . Distinct ( ) . ToList ( ) ;
// Получаем значения З О Н А _О Т В Е Т С Т В Е Н Н О С Т И для всех юнитов шаблонов
2026-05-18 12:29:55 +10:00
var unitResponsableAreaValues = await unitInValueService . GetByUnitIdsAndFieldIdsAsync ( allTemplateUnitIds , new List < Guid > { responsableAreaFieldId } ) ;
var unitToResponsableAreaValueMap = unitResponsableAreaValues
2026-05-14 16:47:27 +10:00
. Where ( uiv = > uiv . ValueId ! = Guid . Empty )
. ToDictionary ( uiv = > uiv . UnitId , uiv = > uiv . ValueId ) ;
2026-05-18 12:29:55 +10:00
// Пакетный поиск целевых юнитов (Один запрос к БД вместо N)
var responsableAreaToTargetUnitMap = new Dictionary < Guid , Guid > ( ) ;
var distinctResponsableAreaValues = unitToResponsableAreaValueMap . Values . Distinct ( ) . ToList ( ) ;
if ( targetTagValueId ! = Guid . Empty & & distinctResponsableAreaValues . Any ( ) )
{
logger . LogDebug ( "Пакетный поиск целевых юнитов с тегом 'ПАРР-НЕИСП' для {Count} уникальных значений З О Н А _О Т В Е Т С Т В Е Н Н О С Т И." , distinctResponsableAreaValues . Count ) ;
// Передаем ct в ToListAsync
var matches = await unitInValueService . Get ( ) . AsNoTracking ( )
. Where ( uiv = > uiv . FieldId = = responsableAreaFieldId & & distinctResponsableAreaValues . Contains ( uiv . ValueId ) )
. Join (
unitInValueService . Get ( ) . AsNoTracking ( ) . Where ( t = > t . FieldId = = tagFieldId & & t . ValueId = = targetTagValueId ) ,
responsableArea = > responsableArea . UnitId ,
tag = > tag . UnitId ,
( responsableArea , tag ) = > new { responsableArea . ValueId , responsableArea . UnitId }
)
. ToListAsync ( ct ) ;
responsableAreaToTargetUnitMap = matches
. GroupBy ( x = > x . ValueId )
. ToDictionary ( g = > g . Key , g = > g . First ( ) . UnitId ) ;
logger . LogDebug ( "Сформирован кэш соответствий: найдено {Count} целевых юнитов." , responsableAreaToTargetUnitMap . Count ) ;
}
2026-01-17 09:32:34 +10:00
foreach ( var template in unusedTemplates )
{
2026-05-14 16:47:27 +10:00
try
{
2026-05-18 12:29:55 +10:00
// Проверка отмены внутри цикла (на случай долгих вычислений)
ct . ThrowIfCancellationRequested ( ) ;
2026-05-14 16:47:27 +10:00
if ( template . Unit = = null )
{
logger . LogWarning ( "У шаблона {TemplateId} отсутствует Unit. Пропускаем." , template . Id ) ;
processed + + ;
continue ;
}
2026-01-17 09:32:34 +10:00
2026-05-18 12:29:55 +10:00
// 1. Определяем текущее значение З О Н А _О Т В Е Т С Т В Е Н Н О С Т И
var currentResponsableAreaValueId = Guid . Empty ;
var hasResponsableArea = unitToResponsableAreaValueMap . TryGetValue ( template . UnitId , out currentResponsableAreaValueId ) ;
2026-01-17 09:32:34 +10:00
2026-05-18 12:29:55 +10:00
// 2. Ищем целевой юнит в кэше
2026-05-14 16:47:27 +10:00
Guid ? targetUnitId = null ;
2026-05-18 12:29:55 +10:00
if ( hasResponsableArea & & currentResponsableAreaValueId ! = Guid . Empty )
2026-05-14 16:47:27 +10:00
{
2026-05-18 12:29:55 +10:00
if ( responsableAreaToTargetUnitMap . TryGetValue ( currentResponsableAreaValueId , out var foundUnitId ) & & foundUnitId ! = Guid . Empty )
2026-05-14 16:47:27 +10:00
{
2026-05-18 12:29:55 +10:00
targetUnitId = foundUnitId ;
2026-05-14 16:47:27 +10:00
}
}
2026-05-18 12:29:55 +10:00
// 3. Финализируем UnitId и Unit
2026-05-14 16:47:27 +10:00
Guid finalUnitId = targetUnitId ? ? template . UnitId ;
Unit finalUnit = template . Unit ;
if ( targetUnitId . HasValue & & targetUnitId . Value ! = template . UnitId )
{
logger . LogInformation ( "Для шаблона {TemplateId} найден новый UnitId {NewUnitId} (был {OldUnitId})." ,
template . Id , targetUnitId . Value , template . UnitId ) ;
2026-05-18 12:29:55 +10:00
// Передаем ct в запрос
2026-05-14 16:47:27 +10:00
var newUnit = await unitRepository . Get ( ) . AsNoTracking ( )
2026-05-18 12:29:55 +10:00
. FirstOrDefaultAsync ( u = > u . Id = = targetUnitId . Value , ct ) ;
2026-05-14 16:47:27 +10:00
if ( newUnit ! = null )
2026-05-18 12:29:55 +10:00
{
2026-05-14 16:47:27 +10:00
finalUnit = newUnit ;
2026-05-18 12:29:55 +10:00
}
2026-05-14 16:47:27 +10:00
else
{
logger . LogWarning ( "Н е удалось загрузить новый юнит {UnitId}. Используем старый." , targetUnitId . Value ) ;
finalUnitId = template . UnitId ;
}
}
2026-05-18 12:29:55 +10:00
else if ( ! hasResponsableArea )
{
logger . LogDebug ( "У юнита {UnitId} шаблона {TemplateId} нет значения поля З О Н А _О Т В Е Т С Т В Е Н Н О С Т И. Оставляем текущий UnitId." , template . UnitId , template . Id ) ;
}
2026-05-14 16:47:27 +10:00
2026-05-18 12:29:55 +10:00
// 4. Генерация целевого имени
2026-05-14 16:47:27 +10:00
var expectedName = await GenerateUnusedTemplateNameAsync ( template , unusedJob , finalUnit ) ;
2026-05-18 12:29:55 +10:00
// 5. Проверка необходимости обновления
bool unitChanged = template . UnitId ! = finalUnitId ;
bool jobChanged = template . JobId ! = unusedJobId ;
bool nameChanged = ! string . Equals ( template . Name , expectedName , StringComparison . OrdinalIgnoreCase ) ;
if ( ! unitChanged & & ! jobChanged & & ! nameChanged )
{
logger . LogDebug ( "Шаблон {TemplateId} уже актуален. Пропуск отправки в MQ." , template . Id ) ;
processed + + ;
continue ;
}
2026-05-14 16:47:27 +10:00
await SendUpdateRequest ( template , unusedJobId , expectedName , initiator , finalUnitId ) ;
2026-05-18 12:29:55 +10:00
logger . LogDebug ( "Отправлен запрос на обновление шаблона {TemplateId}. Изменения: Unit={U}, Job={J}, Name={N}" ,
template . Id , unitChanged , jobChanged , nameChanged ) ;
2026-01-17 09:32:34 +10:00
2026-05-14 16:47:27 +10:00
processed + + ;
}
catch ( Exception ex )
2026-01-17 09:32:34 +10:00
{
2026-05-14 16:47:27 +10:00
logger . LogError ( ex , "Ошибка при обработке шаблона {TemplateId}" , template . Id ) ;
}
2026-01-17 09:32:34 +10:00
}
await UpdateMatchingStatusAsync ( unusedJobId , "Синхронизация неиспользуемых шаблонов завершена" ) ;
await matchingStatusService . DeleteMatchingStatusAsync ( unusedJobId , SyncTaskEntityTypeEnum . Job ) ;
logger . LogInformation ( "Синхронизация неиспользуемых шаблонов завершена. Обработано {Count} шаблонов." , unusedTemplates . Count ) ;
}
catch ( Exception ex )
{
logger . LogError ( ex , "Ошибка при синхронизации неиспользуемых шаблонов для Job {JobId}" , unusedJobId ) ;
await UpdateMatchingStatusAsync ( unusedJobId , $"Ошибка: {ex.Message}" ) ;
throw ;
}
}
2026-05-18 12:29:55 +10:00
2026-05-14 16:47:27 +10:00
private async Task SendUpdateRequest ( Template template , Guid jobId , string name , HistoryInitiator initiator , Guid unitId )
{
var updateRequest = new TemplateUpdaterMessage
{
TemplateId = template . Id ,
JobId = jobId ,
UnitId = unitId ,
Name = name ,
IsActiveTemplate = false ,
IsActiveSchedule = false ,
IsNew = false ,
Index = null ,
StatusTypeId = template . StatusTypeId ,
Initiator = initiator ,
UnitsInTemplate = new List < UnitInTemplateMessage > ( )
} ;
2026-05-20 17:59:06 +10:00
await templateMqPublisher . PublishUpdateAsync ( updateRequest ) ;
2026-05-14 16:47:27 +10:00
}
2026-01-23 11:29:42 +10:00
2026-05-14 16:47:27 +10:00
private async Task < string > GenerateUnusedTemplateNameAsync ( Template template , Job unusedJob , Unit unit )
2026-01-17 09:32:34 +10:00
{
var tempJob = new Job
{
Id = unusedJob . Id ,
Name = unusedJob . Name ,
WorkName = unusedJob . WorkName ,
MinValueRelationships = unusedJob . MinValueRelationships ,
MaxValueRelationships = unusedJob . MaxValueRelationships ,
IsParentRelationships = unusedJob . IsParentRelationships ,
2026-02-20 15:57:51 +10:00
TemplateNameMask = templateSettings . Value . UnusedTemplateNameMask ,
2026-01-17 09:32:34 +10:00
WorkGroupMask = unusedJob . WorkGroupMask ,
ResponseAreaMask = unusedJob . ResponseAreaMask ,
TnkId = unusedJob . TnkId ,
GroupId = unusedJob . GroupId ,
Group = unusedJob . Group ,
Tnk = unusedJob . Tnk ,
UnitFilters = unusedJob . UnitFilters ,
Templates = unusedJob . Templates ,
AutoControl = unusedJob . AutoControl
} ;
var tempTemplateForName = new Template
{
Id = template . Id ,
Name = template . Name ,
JobId = unusedJob . Id ,
2026-05-14 16:47:27 +10:00
UnitId = unit . Id ,
2026-01-17 09:32:34 +10:00
Index = null ,
Job = tempJob ,
2026-05-14 16:47:27 +10:00
Unit = unit ,
2026-01-17 09:32:34 +10:00
UnitsInTemplate = new List < UnitsInTemplate > ( )
} ;
return await templateNameNormalizer . GetNormalizedTemplateNameAsync ( tempTemplateForName ) ;
}
2026-01-23 11:29:42 +10:00
2025-12-26 21:47:53 +10:00
private async Task CreateSimpleTemplateAsync ( Guid jobId , Guid unitId , HistoryInitiator initiator )
2025-12-23 18:27:31 +10:00
{
2025-12-26 21:47:53 +10:00
logger . LogInformation ( "Создание нового простого шаблона для Job {JobId}, UnitId {UnitId}." , jobId , unitId ) ;
2026-05-14 16:47:27 +10:00
var mqRequest = new TemplateGeneratorMessage
2025-12-23 18:27:31 +10:00
{
JobId = jobId ,
UnitId = unitId ,
2026-05-14 16:47:27 +10:00
UnitsInTemplate = new List < UnitInTemplateMessage > ( ) , // для простого шаблона
2025-12-26 21:47:53 +10:00
HistoryInitiator = initiator
2025-12-23 18:27:31 +10:00
} ;
2026-01-23 11:29:42 +10:00
var result = await mqService . SendAsync ( mqSettings . TemplateGenerator , new List < object > { mqRequest } ) ;
2025-12-23 18:27:31 +10:00
if ( ! result . IsSuccess )
2025-12-26 21:47:53 +10:00
logger . LogError ( "Ошибка отправки команды создания простого шаблона для Job {JobId}, UnitId {UnitId}." , jobId , unitId ) ;
2025-12-23 18:27:31 +10:00
}
2026-01-16 19:45:38 +10:00
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 )
) ;
}
2025-12-23 18:27:31 +10:00
}