feat(templateMatcher): добавлена обработка работы с неимпользуемыми шаблонами.

This commit is contained in:
Mikhail Kuznetsov
2026-01-17 09:32:34 +10:00
parent 5eb0ab81b9
commit 91fa14f3f2

View File

@@ -5,6 +5,7 @@ using PARR.BLL.Services.Interfaces;
using PARR.Common.Domain;
using PARR.Constants;
using PARR.DAL.Cache.Models;
using PARR.DAL.Contracts;
using PARR.DAL.DomainServices.Interfaces;
using PARR.DAL.DomainServices.Shortcodes;
using PARR.DAL.Models;
@@ -28,6 +29,9 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
private const bool DefaultUsedTemplateState = false;
private const bool DefaultUsedScheduleState = false;
// Маска для неиспользуемых шаблонов — хардкод, недоступна из GUI
private const string UnusedTemplateNameMask = "%П-1%_НЕАКТУАЛЬНЫЕ РАБОТЫ_%ЭК%_%ТИКТАК%";
private readonly ILogger<SimpleTemplateSynchronizer> logger;
private readonly IUnitFilterService unitFilterService;
private readonly MqSettings mqSettings;
@@ -40,6 +44,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
private readonly ITemplateNameNormalizer templateNameNormalizer;
private readonly ITemplateUpdaterMqSender templateUpdaterMqSender;
private readonly IMatchingStatusService matchingStatusService;
private readonly SettingsFromDb settingsFromDb;
public SimpleTemplateSynchronizer(
ILogger<SimpleTemplateSynchronizer> logger,
@@ -59,7 +64,8 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
ITemplateDeactivator templateDeactivator,
ITemplateNameNormalizer templateNameNormalizer,
ITemplateUpdaterMqSender templateUpdaterMqSender,
IMatchingStatusService matchingStatusService
IMatchingStatusService matchingStatusService,
SettingsFromDb settingsFromDb
)
{
this.logger = logger;
@@ -74,10 +80,20 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
this.templateNameNormalizer = templateNameNormalizer;
this.templateUpdaterMqSender = templateUpdaterMqSender;
this.matchingStatusService = matchingStatusService;
this.settingsFromDb = settingsFromDb;
}
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);
// === Проверка: уже запущена? ===
@@ -122,32 +138,32 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
}
var unitIds = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId);
if (unitIds == null || !unitIds.Any())
{
if (unitIds == null || !unitIds.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(ля 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;
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;
}
#if DEBUG
// Отладка: проверить, есть ли юнит в unitIds
if (unitIds.Contains(targetUnitId))
{
logger.LogDebug("Юнит {TargetUnitId} найден в unitIds.", targetUnitId);
@@ -209,6 +225,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
{
logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, UnitId {UnitId}.", reusableTemplate.Id, jobId, unitId);
// === Создаём временный Template для нормализации имени ===
var tempTemplateForName = new Template
{
Id = reusableTemplate.Id,
@@ -216,9 +233,9 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
JobId = jobId,
UnitId = unitId,
Index = reusableTemplate.Index,
Job = job,
Unit = reusableTemplate.Unit,
UnitsInTemplate = new List<UnitsInTemplate>()
Job = job, // загруженный job
Unit = reusableTemplate.Unit, // может быть null — нормально
UnitsInTemplate = new List<UnitsInTemplate>() // для простого шаблона
};
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName);
@@ -235,7 +252,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
StatusTypeId = TemplateStatusTypeEnum.Used,
Initiator = initiator,
NextRun = nextRun,
UnitsInTemplate = new List<Guid>()
UnitsInTemplate = new List<Guid>() // для простого шаблона
};
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
@@ -257,7 +274,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(template);
if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase))
{
logger.LogDebug("Шаблон {TemplateId} требует обновления имени.", template.Id);
logger.LogDebug("Шаблон {TemplateId} требует обновления имени: старое = '{OldName}', новое = '{NewName}'", template.Id, template.Name, expectedName);
var nextRun = await GetNextRunAsync(job, template.NextRun);
@@ -274,7 +291,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
Index = template.Index,
StatusTypeId = TemplateStatusTypeEnum.Used,
Initiator = initiator,
UnitsInTemplate = new List<Guid>()
UnitsInTemplate = new List<Guid>() // для простого шаблона
};
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
@@ -365,6 +382,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
}
#if DEBUG
// Отладка: проверить, есть ли юнит в unitIds
if (unitIds.Contains(targetUnitId))
{
logger.LogDebug("Юнит {TargetUnitId} найден в unitIds.", targetUnitId);
@@ -387,20 +405,6 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
.Where(t => t.JobId == jobId && t.StatusTypeId == TemplateStatusTypeEnum.Used)
.ToListAsync();
int toUpdate = 0;
foreach (var template in existingTemplates)
{
if (unitIds.Contains(template.UnitId))
{
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(template);
if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase))
toUpdate++;
}
}
await UpdateMatchingStatusAsync(jobId, $"Обновление имён: {toUpdate} шаблонов");
int updated = 0;
foreach (var template in existingTemplates)
{
if (unitIds.Contains(template.UnitId))
@@ -408,7 +412,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(template);
if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase))
{
logger.LogDebug("Шаблон {TemplateId} требует обновления имени.", template.Id);
logger.LogDebug("Шаблон {TemplateId} требует обновления имени: старое = '{OldName}', новое = '{NewName}'", template.Id, template.Name, expectedName);
var nextRun = await GetNextRunAsync(job, template.NextRun);
@@ -425,12 +429,10 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
Index = template.Index,
StatusTypeId = TemplateStatusTypeEnum.Used,
Initiator = initiator,
UnitsInTemplate = new List<Guid>()
UnitsInTemplate = new List<Guid>() // для простого шаблона
};
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
updated++;
await UpdateMatchingStatusAsync(jobId, $"Обновлено: {updated}/{toUpdate}");
}
}
}
@@ -447,6 +449,149 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
}
}
private async Task SyncUnusedTemplatesAsync(Guid unusedJobId, HistoryInitiator initiator)
{
// === Проверка: уже запущена? ===
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
{
// Загружаем Job неиспользуемых шаблонов с Group
var unusedJob = await jobService.Get()
.AsNoTracking()
.Include(j => j.Group)
.FirstOrDefaultAsync(j => j.Id == unusedJobId);
if (unusedJob == null)
{
logger.LogError("Job неиспользуемых шаблонов {JobId} не найден.", unusedJobId);
await UpdateMatchingStatusAsync(unusedJobId, "Job не найден");
return;
}
// Находим все шаблоны со статусом Unused
var unusedTemplates = await templateService.Get()
.Include(t => t.Unit)
.Where(t => t.StatusTypeId == TemplateStatusTypeEnum.Unused)
.ToListAsync();
if (!unusedTemplates.Any())
{
logger.LogInformation("Не найдено шаблонов со статусом Unused.");
await UpdateMatchingStatusAsync(unusedJobId, "Нет шаблонов для обработки");
await matchingStatusService.DeleteMatchingStatusAsync(unusedJobId, SyncTaskEntityTypeEnum.Job);
return;
}
await UpdateMatchingStatusAsync(unusedJobId, $"Найдено {unusedTemplates.Count} шаблонов для обработки");
int processed = 0;
foreach (var template in unusedTemplates)
{
// Генерируем ожидаемое имя один раз
var expectedName = await GenerateUnusedTemplateNameAsync(template, unusedJob);
// Проверяем, нужно ли обновление
bool needsUpdate = template.JobId != unusedJobId ||
!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase);
if (!needsUpdate)
{
logger.LogDebug("Шаблон {TemplateId} уже имеет актуальное имя и JobId. Пропускаем.", template.Id);
continue;
}
var nextRun = await GetNextRunAsync(unusedJob);
var updateRequest = new TemplateUpdaterMq
{
TemplateId = template.Id,
JobId = unusedJobId,
UnitId = template.UnitId,
Name = expectedName,
IsActiveTemplate = false,
IsActiveSchedule = false,
LastRun = template.LastRun,
NextRun = nextRun,
Index = null,
StatusTypeId = TemplateStatusTypeEnum.Unused,
Initiator = initiator,
UnitsInTemplate = new List<Guid>()
};
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
processed++;
await UpdateMatchingStatusAsync(unusedJobId, $"Обработано: {processed}/{unusedTemplates.Count}");
}
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<string> GenerateUnusedTemplateNameAsync(Template template, Job unusedJob)
{
var tempJob = new Job
{
Id = unusedJob.Id,
Name = unusedJob.Name,
WorkName = unusedJob.WorkName,
MinValueRelationships = unusedJob.MinValueRelationships,
MaxValueRelationships = unusedJob.MaxValueRelationships,
IsParentRelationships = unusedJob.IsParentRelationships,
TemplateNameMask = 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 = template.UnitId,
Index = null,
Job = tempJob,
Unit = template.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);
@@ -455,7 +600,7 @@ internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
{
JobId = jobId,
UnitId = unitId,
UnitsInTemplate = new List<Guid>(),
UnitsInTemplate = new List<Guid>(), // для простого шаблона
HistoryInitiator = initiator
};