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-30 10:35:31 +10:00
using PARR.Core.Repositories.Interfaces ;
2026-07-03 11:17:48 +10:00
using PARR.Core.Repositories.Interfaces.JobRepositories ;
2026-04-30 10:35:31 +10:00
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-07-03 11:17:48 +10:00
using PARR.Domain.Entities.JobEntities ;
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-06-11 15:09:46 +10:00
using PARR.TemplateMatcher.Constants ;
2025-12-23 18:27:31 +10:00
using PARR.TemplateMatcher.Services.Interfaces ;
2026-06-11 15:09:46 +10:00
using PARR.TemplateMatcher.Services.SimpleSync ;
2025-12-23 18:27:31 +10:00
using PARR.TemplateMatcher.Settings ;
2026-06-10 12:08:07 +10:00
using System.Diagnostics ;
2025-12-23 18:27:31 +10:00
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
2026-07-03 14:01:56 +10:00
private readonly Guid _targetUnitId = Guid . Parse ( "358437ac-1eeb-4c00-840c-998326f657ac" ) ;
2025-12-26 21:47:53 +10:00
#endif
2026-07-03 14:01:56 +10:00
private readonly IEnumerable < ISimpleSyncStage > _readStages ;
private readonly IEnumerable < ISimpleSyncWriteStage > _writeStages ;
private readonly ILogger < SimpleTemplateSynchronizer > _logger ;
private readonly IUnitFilterService _unitFilterService ;
private readonly ITemplateRepository _templateService ;
private readonly IJobRepository _jobService ;
private readonly ITemplateNameNormalizer _templateNameNormalizer ;
private readonly ITemplateMqPublisher _templateMqPublisher ;
private readonly IMatchingStatusService _matchingStatusService ;
private readonly SettingsFromDb _settingsFromDb ;
private readonly IOptions < TemplateSettings > _templateSettings ;
private readonly IUnitFieldRepository _unitFieldService ;
private readonly IUnitInValueRepository _unitInValueService ;
private readonly IUnitRepository _unitRepository ;
2025-12-23 18:27:31 +10:00
public SimpleTemplateSynchronizer (
2026-06-11 15:09:46 +10:00
IEnumerable < ISimpleSyncStage > readStages ,
IEnumerable < ISimpleSyncWriteStage > writeStages ,
2025-12-23 18:27:31 +10:00
ILogger < SimpleTemplateSynchronizer > logger ,
IUnitFilterService unitFilterService ,
2026-04-30 10:35:31 +10:00
ITemplateRepository templateService ,
IJobRepository jobService ,
2025-12-26 21:47:53 +10:00
ITemplateNameNormalizer templateNameNormalizer ,
2026-05-20 17:59:06 +10:00
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
{
2026-07-03 14:01:56 +10:00
this . _readStages = readStages ;
this . _writeStages = writeStages ;
this . _logger = logger ;
this . _unitFilterService = unitFilterService ;
this . _templateService = templateService ;
this . _jobService = jobService ;
this . _templateNameNormalizer = templateNameNormalizer ;
this . _templateMqPublisher = templateMqPublisher ;
this . _matchingStatusService = matchingStatusService ;
this . _settingsFromDb = settingsFromDb ;
this . _templateSettings = templateSettings ;
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-07-03 14:01:56 +10:00
if ( jobId = = _settingsFromDb . JobIdForUnusedTemplates )
2026-01-17 09:32:34 +10:00
{
2026-07-03 14:01:56 +10:00
_logger . LogInformation ( "Обработка синхронизации для Job неиспользуемых шаблонов '{JobId}'" , jobId ) ;
2026-01-17 09:32:34 +10:00
await SyncUnusedTemplatesAsync ( jobId , initiator ) ;
return ;
}
2026-07-03 14:01:56 +10:00
_logger . LogInformation ( "Начало синхронизации шаблонов для Job {JobId}" , jobId ) ;
2025-12-23 18:27:31 +10:00
2026-07-03 14:01:56 +10:00
var existingStatus = await _matchingStatusService . GetStatusAsync ( jobId , SyncTaskEntityTypeEnum . Job ) ;
if ( existingStatus . DetailsJobs ? . Count > 0 )
2025-12-23 18:27:31 +10:00
{
2026-07-03 14:01:56 +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 = "Начало синхронизации"
} ;
2026-07-03 14:01:56 +10:00
await _matchingStatusService . SetMatchingStatusAsync (
2026-06-11 15:09:46 +10:00
jobId , SyncTaskEntityTypeEnum . Job ,
2026-01-16 19:45:38 +10:00
new MatchingStatusItem { Data = initialStatus , Timestamp = DateTimeOffset . UtcNow , Source = nameof ( SimpleTemplateSynchronizer ) } ,
2026-06-11 15:09:46 +10:00
TimeSpan . FromMinutes ( 35 ) ) ;
// Таймер запускается ПОСЛЕ инфраструктурных операций (статус, проверка блокировки)
var totalSw = Stopwatch . StartNew ( ) ;
2026-07-03 14:01:56 +10:00
var context = new SimpleSyncContext { JobId = jobId , Initiator = initiator } ;
2026-01-16 19:45:38 +10:00
try
{
2026-07-03 14:01:56 +10:00
foreach ( var stage in _readStages )
2026-06-10 12:08:07 +10:00
{
2026-06-11 15:09:46 +10:00
var stageSw = Stopwatch . StartNew ( ) ;
await stage . ExecuteAsync ( context ) ;
stageSw . Stop ( ) ;
2026-07-03 14:01:56 +10:00
_logger . LogDebug ( "[Perf] Job '{JobName}' ({JobId}) | Этап: {Stage} | Время: {Ms} мс" ,
2026-06-11 15:09:46 +10:00
context . JobName , jobId , stage . StageName , stageSw . ElapsedMilliseconds ) ;
2026-06-10 12:08:07 +10:00
}
2026-07-03 14:01:56 +10:00
foreach ( var stage in _writeStages )
2026-01-14 10:59:17 +10:00
{
2026-06-11 15:09:46 +10:00
var stageSw = Stopwatch . StartNew ( ) ;
await stage . ExecuteAsync ( context ) ;
stageSw . Stop ( ) ;
2026-07-03 14:01:56 +10:00
_logger . LogDebug ( "[Perf] Job '{JobName}' ({JobId}) | Этап: {Stage} | Время: {Ms} мс" ,
2026-06-11 15:09:46 +10:00
context . JobName , jobId , stage . StageName , stageSw . ElapsedMilliseconds ) ;
2026-01-16 19:45:38 +10:00
}
2026-06-10 12:08:07 +10:00
totalSw . Stop ( ) ;
2026-07-03 14:01:56 +10:00
_logger . LogInformation ( "[Perf] Job '{JobName}' ({JobId}) | ИТОГО: {TotalMs} мс" ,
2026-06-11 15:09:46 +10:00
context . JobName , jobId , totalSw . ElapsedMilliseconds ) ;
2026-01-14 10:59:17 +10:00
2026-01-16 19:45:38 +10:00
await UpdateMatchingStatusAsync ( jobId , "Синхронизация завершена успешно" ) ;
2026-07-03 14:01:56 +10:00
await _matchingStatusService . DeleteMatchingStatusAsync ( jobId , SyncTaskEntityTypeEnum . Job ) ;
_logger . LogInformation ( "Синхронизация шаблонов завершена для Job '{JobName}' ({JobId})" ,
2026-06-11 15:09:46 +10:00
context . JobName , jobId ) ;
2026-01-16 19:45:38 +10:00
}
catch ( Exception ex )
2025-12-23 18:27:31 +10:00
{
2026-06-10 12:08:07 +10:00
totalSw . Stop ( ) ;
2026-07-03 14:01:56 +10:00
_logger . LogError ( ex , "Ошибка при синхронизации Job '{JobName}' ({JobId}) через {ElapsedMs} мс" ,
2026-06-11 15:09:46 +10:00
string . Empty , jobId , totalSw . ElapsedMilliseconds ) ;
2026-01-16 19:45:38 +10:00
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
2026-06-10 12:08:07 +10:00
2025-12-26 21:47:53 +10:00
public async Task SyncTemplatesForJobGroupAsync ( Guid jobGroupId , HistoryInitiator initiator )
{
2026-07-03 14:01:56 +10:00
_logger . LogWarning ( "SimpleTemplateSynchronizer: SyncTemplatesForJobGroup вызван для JobGroup {JobGroupId}. Это не поддерживаемая операция." , jobGroupId ) ;
2025-12-23 18:27:31 +10:00
}
2026-06-11 15:09:46 +10:00
2025-12-23 18:27:31 +10:00
public async Task UpdateTemplatesForJobAsync ( Guid jobId , HistoryInitiator initiator )
{
2026-07-03 14:01:56 +10:00
_logger . LogDebug ( "Обновление шаблонов для Job {JobId}" , jobId ) ;
2025-12-23 18:27:31 +10:00
2026-01-16 19:45:38 +10:00
// === Проверка: уже запущена? ===
2026-07-03 14:01:56 +10:00
var existingStatus = await _matchingStatusService . GetStatusAsync ( jobId , SyncTaskEntityTypeEnum . Job ) ;
if ( existingStatus . DetailsJobs ? . Count > 0 )
2025-12-23 18:27:31 +10:00
{
2026-07-03 14:01:56 +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 = "Начало обновления имён"
} ;
2026-07-03 14:01:56 +10:00
await _matchingStatusService . SetMatchingStatusAsync (
2026-01-16 19:45:38 +10:00
jobId ,
SyncTaskEntityTypeEnum . Job ,
new MatchingStatusItem { Data = initialStatus , Timestamp = DateTimeOffset . UtcNow , Source = nameof ( SimpleTemplateSynchronizer ) } ,
TimeSpan . FromMinutes ( 30 )
) ;
try
{
2026-07-03 14:01:56 +10:00
var job = await _jobService . Get ( )
2026-01-16 19:45:38 +10:00
. 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 )
{
2026-07-03 14:01:56 +10:00
_logger . LogWarning ( "Job {JobId} не найден." , jobId ) ;
2026-01-16 19:45:38 +10:00
await UpdateMatchingStatusAsync ( jobId , "Job не найден" ) ;
return ;
}
2026-01-21 18:25:09 +10:00
// === Получение отфильтрованных юнитов с полной информацией ===
2026-07-03 14:01:56 +10:00
var filteredUnits = await _unitFilterService . GetUnitsByJobFilterAsync ( jobId ) ;
2026-01-21 18:25:09 +10:00
if ( filteredUnits = = null | | ! filteredUnits . Any ( ) )
2026-01-16 19:45:38 +10:00
{
2026-07-03 14:01:56 +10:00
_logger . LogInformation ( "Для Job {JobId} фильтры не дали Unit'ов." , jobId ) ;
2026-01-16 19:45:38 +10:00
await UpdateMatchingStatusAsync ( jobId , "Нет Unit'ов — обновление не требуется" ) ;
2026-07-03 14:01:56 +10:00
await _matchingStatusService . DeleteMatchingStatusAsync ( jobId , SyncTaskEntityTypeEnum . Job ) ;
2026-01-16 19:45:38 +10:00
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-07-03 14:01:56 +10:00
if ( unitIds . Contains ( _targetUnitId ) )
2026-01-16 19:45:38 +10:00
{
2026-07-03 14:01:56 +10:00
_logger . LogDebug ( "Юнит {TargetUnitId} найден в unitIds." , _targetUnitId ) ;
2026-01-16 19:45:38 +10:00
}
else
{
2026-07-03 14:01:56 +10:00
_logger . LogDebug ( "Юнит {TargetUnitId} Н Е найден в unitIds." , _targetUnitId ) ;
2026-01-16 19:45:38 +10:00
}
2025-12-26 21:47:53 +10:00
#endif
2025-12-25 21:16:20 +10:00
2026-07-03 14:01:56 +10:00
var existingTemplates = await _templateService . Get ( )
2026-01-16 19:45:38 +10:00
. 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 ) )
{
2026-07-03 14:01:56 +10:00
var expectedName = await _templateNameNormalizer . GetNormalizedTemplateNameAsync ( template ) ;
2026-01-16 19:45:38 +10:00
if ( ! string . Equals ( template . Name , expectedName , StringComparison . OrdinalIgnoreCase ) )
2025-12-26 21:47:53 +10:00
{
2026-07-03 14:01:56 +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-07-03 14:01:56 +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 , "Обновление завершено" ) ;
2026-07-03 14:01:56 +10:00
await _matchingStatusService . DeleteMatchingStatusAsync ( jobId , SyncTaskEntityTypeEnum . Job ) ;
_logger . LogInformation ( "Обновление шаблонов завершено для Job {JobId}." , jobId ) ;
2026-01-16 19:45:38 +10:00
}
catch ( Exception ex )
{
2026-07-03 14:01:56 +10:00
_logger . LogError ( ex , "Ошибка при обновлении Job {JobId}" , jobId ) ;
2026-01-16 19:45:38 +10:00
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-07-03 14:01:56 +10:00
var existingStatus = await _matchingStatusService . GetStatusAsync ( unusedJobId , SyncTaskEntityTypeEnum . Job ) ;
if ( existingStatus . DetailsJobs ? . Count > 0 )
2026-01-17 09:32:34 +10:00
{
2026-07-03 14:01:56 +10:00
_logger . LogWarning ( "Синхронизация для Job неиспользуемых шаблонов {JobId} уже запущена. Пропускаем." , unusedJobId ) ;
2026-01-17 09:32:34 +10:00
return ;
}
var initialStatus = new MatchingStatusItemDto
{
DateStart = DateTimeOffset . UtcNow ,
Action = TemplateMatcherActionEnum . Sync ,
Comment = "Синхронизация неиспользуемых шаблонов"
} ;
2026-07-03 14:01:56 +10:00
await _matchingStatusService . SetMatchingStatusAsync (
2026-01-17 09:32:34 +10:00
unusedJobId ,
SyncTaskEntityTypeEnum . Job ,
new MatchingStatusItem { Data = initialStatus , Timestamp = DateTimeOffset . UtcNow , Source = nameof ( SimpleTemplateSynchronizer ) } ,
TimeSpan . FromMinutes ( 30 )
) ;
2026-07-03 14:01:56 +10:00
var totalSw = Stopwatch . StartNew ( ) ;
2026-01-17 09:32:34 +10:00
try
{
2026-05-14 16:47:27 +10:00
// 1. Находим ID нужных полей
2026-07-03 14:01:56 +10:00
var responsableAreaField = await _unitFieldService . GetByAihitNameAsync ( UnusedTemplateConstants . ResponsibilityAreaFieldName , ct ) ;
var tagField = await _unitFieldService . GetByAihitNameAsync ( UnusedTemplateConstants . ParrTagFieldName , ct ) ;
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-07-03 14:01:56 +10:00
_logger . LogError ( "Н е найдены поля '{Field1}' или '{Field2}'. Синхронизация прервана." , UnusedTemplateConstants . ResponsibilityAreaFieldName , UnusedTemplateConstants . NotUsedTagValue ) ;
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 ;
// 2. Находим ValueId для тега "ПАРР-НЕИСП"
2026-07-03 14:01:56 +10:00
var targetTagValueId = await _unitInValueService . Get ( )
2026-05-14 16:47:27 +10:00
. AsNoTracking ( )
2026-06-11 15:09:46 +10:00
. Where ( uiv = > uiv . FieldId = = tagFieldId & & uiv . Value ! = null & & uiv . Value . Value = = UnusedTemplateConstants . NotUsedTagValue )
2026-05-14 16:47:27 +10:00
. 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-07-03 14:01:56 +10:00
_logger . LogWarning ( "Значение '{TagValue}' для поля '{FieldName}' не найдено в справочнике UnitFieldValue." , UnusedTemplateConstants . NotUsedTagValue , UnusedTemplateConstants . ParrTagFieldName ) ;
2026-05-14 16:47:27 +10:00
}
2026-07-03 14:01:56 +10:00
var unusedJob = await _jobService . Get ( ) . AsNoTracking ( )
2026-05-18 12:29:55 +10:00
. 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 )
{
2026-07-03 14:01:56 +10:00
_logger . LogError ( "Job неиспользуемых шаблонов {JobId} не найден." , unusedJobId ) ;
2026-01-17 09:32:34 +10:00
await UpdateMatchingStatusAsync ( unusedJobId , "Job не найден" ) ;
return ;
}
2026-07-03 14:01:56 +10:00
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 ( ) )
{
2026-07-03 14:01:56 +10:00
_logger . LogInformation ( "Н е найдено шаблонов с о статусом Unused." ) ;
2026-01-17 09:32:34 +10:00
await UpdateMatchingStatusAsync ( unusedJobId , "Нет шаблонов для обработки" ) ;
2026-07-03 14:01:56 +10:00
await _matchingStatusService . DeleteMatchingStatusAsync ( unusedJobId , SyncTaskEntityTypeEnum . Job ) ;
2026-01-17 09:32:34 +10:00
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-07-03 14:01:56 +10:00
var unitResponsableAreaValues = await _unitInValueService . GetByUnitIdsAndFieldIdsAsync ( allTemplateUnitIds , new List < Guid > { responsableAreaFieldId } , ct ) ;
2026-05-18 12:29:55 +10:00
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 ( ) ;
2026-07-03 14:01:56 +10:00
if ( targetTagValueId ! = Guid . Empty & &
distinctResponsableAreaValues . Any ( ) )
2026-05-18 12:29:55 +10:00
{
2026-07-03 14:01:56 +10:00
_logger . LogDebug ( "Пакетный поиск целевых юнитов с тегом 'ПАРР-НЕИСП' для {Count} уникальных значений З О Н А _О Т В Е Т С Т В Е Н Н О С Т И." , distinctResponsableAreaValues . Count ) ;
2026-05-18 12:29:55 +10:00
// Передаем ct в ToListAsync
2026-07-03 14:01:56 +10:00
var matches = await _unitInValueService . Get ( ) . AsNoTracking ( )
2026-05-18 12:29:55 +10:00
. Where ( uiv = > uiv . FieldId = = responsableAreaFieldId & & distinctResponsableAreaValues . Contains ( uiv . ValueId ) )
. Join (
2026-07-03 14:01:56 +10:00
_unitInValueService . Get ( ) . AsNoTracking ( ) . Where ( t = > t . FieldId = = tagFieldId & & t . ValueId = = targetTagValueId ) ,
2026-05-18 12:29:55 +10:00
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 ) ;
2026-07-03 14:01:56 +10:00
_logger . LogDebug ( "Сформирован кэш соответствий: найдено {Count} целевых юнитов." , responsableAreaToTargetUnitMap . Count ) ;
2026-05-18 12:29:55 +10:00
}
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 )
{
2026-07-03 14:01:56 +10:00
_logger . LogWarning ( "У шаблона {TemplateId} отсутствует Unit. Пропускаем." , template . Id ) ;
2026-05-14 16:47:27 +10:00
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 )
{
2026-07-03 14:01:56 +10:00
_logger . LogInformation ( "Для шаблона {TemplateId} найден новый UnitId {NewUnitId} (был {OldUnitId})." ,
2026-05-14 16:47:27 +10:00
template . Id , targetUnitId . Value , template . UnitId ) ;
2026-05-18 12:29:55 +10:00
// Передаем ct в запрос
2026-07-03 14:01:56 +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
{
2026-07-03 14:01:56 +10:00
_logger . LogWarning ( "Н е удалось загрузить новый юнит {UnitId}. Используем старый." , targetUnitId . Value ) ;
2026-05-14 16:47:27 +10:00
finalUnitId = template . UnitId ;
}
}
2026-05-18 12:29:55 +10:00
else if ( ! hasResponsableArea )
{
2026-07-03 14:01:56 +10:00
_logger . LogDebug ( "У юнита {UnitId} шаблона {TemplateId} нет значения поля З О Н А _О Т В Е Т С Т В Е Н Н О С Т И. Оставляем текущий UnitId." , template . UnitId , template . Id ) ;
2026-05-18 12:29:55 +10:00
}
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 )
{
2026-07-03 14:01:56 +10:00
_logger . LogDebug ( "Шаблон {TemplateId} уже актуален. Пропуск отправки в MQ." , template . Id ) ;
2026-05-18 12:29:55 +10:00
processed + + ;
continue ;
}
2026-05-14 16:47:27 +10:00
await SendUpdateRequest ( template , unusedJobId , expectedName , initiator , finalUnitId ) ;
2026-07-03 14:01:56 +10:00
_logger . LogDebug ( "Отправлен запрос на обновление шаблона {TemplateId}. Изменения: Unit={U}, Job={J}, Name={N}" ,
2026-05-18 12:29:55 +10:00
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-07-03 14:01:56 +10:00
_logger . LogError ( ex , "Ошибка при обработке шаблона {TemplateId}" , template . Id ) ;
2026-05-14 16:47:27 +10:00
}
2026-01-17 09:32:34 +10:00
}
2026-07-03 14:01:56 +10:00
totalSw . Stop ( ) ;
totalSw . Stop ( ) ;
_logger . LogInformation ( "[Perf] Job '{JobName}' ({JobId}) | ИТОГО: {TotalMs} мс" ,
unusedJob . Name , unusedJob . Id , totalSw . ElapsedMilliseconds ) ;
2026-01-17 09:32:34 +10:00
await UpdateMatchingStatusAsync ( unusedJobId , "Синхронизация неиспользуемых шаблонов завершена" ) ;
2026-07-03 14:01:56 +10:00
await _matchingStatusService . DeleteMatchingStatusAsync ( unusedJobId , SyncTaskEntityTypeEnum . Job ) ;
totalSw . Stop ( ) ;
_logger . LogInformation (
"Синхронизация неиспользуемых шаблонов завершена. Обработано {Count} шаблонов" ,
unusedTemplates . Count ) ;
2026-01-17 09:32:34 +10:00
}
catch ( Exception ex )
{
2026-07-03 14:01:56 +10:00
totalSw . Stop ( ) ;
_logger . LogError ( ex ,
"Ошибка при синхронизации неиспользуемых шаблонов для Job {JobId} через {ElapsedMs} мс" ,
unusedJobId , totalSw . ElapsedMilliseconds ) ;
2026-01-17 09:32:34 +10:00
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-07-03 14:01:56 +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-07-03 14:01:56 +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 > ( )
} ;
2026-07-03 14:01:56 +10:00
return await _templateNameNormalizer . GetNormalizedTemplateNameAsync ( tempTemplateForName ) ;
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
} ;
2026-07-03 14:01:56 +10:00
await _matchingStatusService . SetMatchingStatusAsync (
2026-01-16 19:45:38 +10:00
jobId ,
SyncTaskEntityTypeEnum . Job ,
new MatchingStatusItem { Data = status , Timestamp = DateTimeOffset . UtcNow , Source = nameof ( SimpleTemplateSynchronizer ) } ,
TimeSpan . FromMinutes ( 30 )
) ;
}
2025-12-23 18:27:31 +10:00
}