Files
parr_api/PARR.TemplateMatcher/Services/Implementations/SimpleTemplateSynchronizer.cs
Mikhail Kuznetsov 513e5f869a fix(templateMatcher): Исправление ошибок в именах шаблонов и рефакторинг синхронизации
- Устранена ошибка, когда при переиспользовании шаблона для шорткодов передавался ЭК "КОМПЛЕКСЫ-[ЗО]" вместо целевого ЭК шаблона.
- Проведен небольшой рефакторинг общих процессов синхронизации различных типов групп работ.
2026-05-20 17:59:06 +10:00

714 lines
34 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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