Files
parr_api/PARR.TemplateMatcher/Services/Implemetaions/SimpleTemplateSynchronizer.cs

490 lines
23 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 PARR.BLL.Domain.Mq;
using PARR.BLL.Services.Interfaces;
using PARR.Common.Domain;
using PARR.Constants;
using PARR.DAL.Cache.Models;
using PARR.DAL.DomainServices.Interfaces;
using PARR.DAL.DomainServices.Shortcodes;
using PARR.DAL.Models;
using PARR.DAL.Models.Job;
using PARR.DAL.Services.Interfaces;
using PARR.DAL.Services.Interfaces.Job;
using PARR.DAL.Services.Interfaces.Unit;
using PARR.DAL.TransformServices;
using PARR.TemplateMatcher.Services.Interfaces;
using PARR.TemplateMatcher.Settings;
using System.Text.Json;
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 readonly ILogger<SimpleTemplateSynchronizer> logger;
private readonly IUnitFilterService unitFilterService;
private readonly MqSettings mqSettings;
private readonly IMqService mqService;
private readonly ITemplateService templateService;
private readonly IJobService jobService;
private readonly ITemplateReuser templateReuser;
private readonly IEsppScheduleTransformService esppScheduleTransformService;
private readonly ITemplateDeactivator templateDeactivator;
private readonly ITemplateNameNormalizer templateNameNormalizer;
private readonly ITemplateUpdaterMqSender templateUpdaterMqSender;
private readonly IMatchingStatusService matchingStatusService;
public SimpleTemplateSynchronizer(
ILogger<SimpleTemplateSynchronizer> logger,
IUnitFilterService unitFilterService,
IUnitInUnitService unitInUnitService,
IUnitInValueService unitInValueService,
IUnitService unitService,
MqSettings mqSettings,
IMqService mqService,
ITemplateService templateService,
IJobService jobService,
ITemplateReuser templateReuser,
IShortcodesService shortcodesService,
IEsppScheduleTransformService esppScheduleTransformService,
IUnitRegionalEkPtkGroupService regionalEkPtkGroupService,
IUnitFieldService unitFieldService,
ITemplateDeactivator templateDeactivator,
ITemplateNameNormalizer templateNameNormalizer,
ITemplateUpdaterMqSender templateUpdaterMqSender,
IMatchingStatusService matchingStatusService
)
{
this.logger = logger;
this.unitFilterService = unitFilterService;
this.mqSettings = mqSettings;
this.mqService = mqService;
this.templateService = templateService;
this.jobService = jobService;
this.templateReuser = templateReuser;
this.esppScheduleTransformService = esppScheduleTransformService;
this.templateDeactivator = templateDeactivator;
this.templateNameNormalizer = templateNameNormalizer;
this.templateUpdaterMqSender = templateUpdaterMqSender;
this.matchingStatusService = matchingStatusService;
}
public async Task SyncTemplatesForJobAsync(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.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 unitIds = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId);
{
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("Деактивация лишнего шаблона {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
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();
// === Подсчёт операций ===
int toCreate = newUnitIds.Count;
int toUpdate = 0;
int toDeactivate = unusedTemplates.Count;
// Подсчёт обновлений по имени
foreach (var template in existingUsedTemplates)
{
if (unitIds.Contains(template.UnitId))
{
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(template);
if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase))
toUpdate++;
}
}
await UpdateMatchingStatusAsync(jobId, $"Осталось: создать={toCreate}, обновить={toUpdate}, деактивировать={toDeactivate}");
// === Обработка новых юнитов ===
int created = 0, updated = 0, deactivated = 0;
foreach (var unitId in newUnitIds)
{
var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(jobId, unitId, initiator);
if (reusableTemplate != null)
{
logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, UnitId {UnitId}.", reusableTemplate.Id, jobId, unitId);
var tempTemplateForName = new Template
{
Id = reusableTemplate.Id,
Name = reusableTemplate.Name,
JobId = jobId,
UnitId = unitId,
Index = reusableTemplate.Index,
Job = job,
Unit = reusableTemplate.Unit,
UnitsInTemplate = new List<UnitsInTemplate>()
};
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName);
var nextRun = await GetNextRunAsync(job);
var updateRequest = new TemplateUpdaterMq
{
TemplateId = reusableTemplate.Id,
JobId = jobId,
UnitId = unitId,
Name = expectedName,
IsActiveTemplate = job.AutoControl?.InitUsedTemplateState ?? DefaultUsedTemplateState,
IsActiveSchedule = job.AutoControl?.InitUsedScheduleState ?? DefaultUsedScheduleState,
StatusTypeId = TemplateStatusTypeEnum.Used,
Initiator = initiator,
NextRun = nextRun,
UnitsInTemplate = new List<Guid>()
};
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
}
else
{
logger.LogDebug("Создание нового шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId);
await CreateSimpleTemplateAsync(jobId, unitId, initiator);
}
created++;
await UpdateMatchingStatusAsync(jobId, $"Прогресс: создано={created}, обновлено={updated}, деактивировано={deactivated}. Осталось: создать={toCreate - created}, обновить={toUpdate - updated}, деактивировать={toDeactivate - deactivated}");
}
// === Обработка существующих шаблонов (проверка имени) ===
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} требует обновления имени.", template.Id);
var nextRun = await GetNextRunAsync(job, template.NextRun);
var updateRequest = new TemplateUpdaterMq
{
TemplateId = template.Id,
JobId = jobId,
UnitId = template.UnitId,
Name = expectedName,
IsActiveTemplate = template.IsActiveTemplate,
IsActiveSchedule = template.IsActiveSchedule,
LastRun = template.LastRun,
NextRun = nextRun,
Index = template.Index,
StatusTypeId = TemplateStatusTypeEnum.Used,
Initiator = initiator,
UnitsInTemplate = new List<Guid>()
};
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
updated++;
await UpdateMatchingStatusAsync(jobId, $"Прогресс: создано={created}, обновлено={updated}, деактивировано={deactivated}. Осталось: создать={toCreate - created}, обновить={toUpdate - updated}, деактивировать={toDeactivate - deactivated}");
}
}
}
// === Деактивация лишних шаблонов ===
foreach (var unusedTemplate in unusedTemplates)
{
logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, UnitId {UnitId}).", unusedTemplate.Id, jobId, unusedTemplate.UnitId);
await templateDeactivator.DeactivateTemplateAsync(unusedTemplate, initiator);
deactivated++;
await UpdateMatchingStatusAsync(jobId, $"Прогресс: создано={created}, обновлено={updated}, деактивировано={deactivated}. Осталось: создать={toCreate - created}, обновить={toUpdate - updated}, деактивировать={toDeactivate - deactivated}");
}
// === Успешное завершение ===
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 unitIds = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId);
if (unitIds == null || !unitIds.Any())
{
logger.LogInformation("Для Job {JobId} фильтры не дали Unit'ов.", jobId);
await UpdateMatchingStatusAsync(jobId, "Нет Unit'ов — обновление не требуется");
await matchingStatusService.DeleteMatchingStatusAsync(jobId, SyncTaskEntityTypeEnum.Job);
return;
}
#if DEBUG
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();
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))
{
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(template);
if (!string.Equals(template.Name, expectedName, StringComparison.OrdinalIgnoreCase))
{
logger.LogDebug("Шаблон {TemplateId} требует обновления имени.", template.Id);
var nextRun = await GetNextRunAsync(job, template.NextRun);
var updateRequest = new TemplateUpdaterMq
{
TemplateId = template.Id,
JobId = jobId,
UnitId = template.UnitId,
Name = expectedName,
IsActiveTemplate = template.IsActiveTemplate,
IsActiveSchedule = template.IsActiveSchedule,
LastRun = template.LastRun,
NextRun = nextRun,
Index = template.Index,
StatusTypeId = TemplateStatusTypeEnum.Used,
Initiator = initiator,
UnitsInTemplate = new List<Guid>()
};
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
updated++;
await UpdateMatchingStatusAsync(jobId, $"Обновлено: {updated}/{toUpdate}");
}
}
}
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 CreateSimpleTemplateAsync(Guid jobId, Guid unitId, HistoryInitiator initiator)
{
logger.LogInformation("Создание нового простого шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId);
var mqRequest = new TemplateGeneratorMq
{
JobId = jobId,
UnitId = unitId,
UnitsInTemplate = new List<Guid>(),
HistoryInitiator = initiator
};
var msg = JsonSerializer.Serialize(mqRequest);
var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg });
if (!result.IsSuccess)
logger.LogError("Ошибка отправки команды создания простого шаблона для Job {JobId}, UnitId {UnitId}.", jobId, unitId);
}
private async Task<DateTimeOffset> GetNextRunAsync(Job job, DateTimeOffset? currentNextRun = null)
{
var referenceDate = job.Group?.ReferenceDate ?? DateTimeOffset.UtcNow;
return await esppScheduleTransformService.GetNextDateAsync(job.GroupId, referenceDate);
}
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)
);
}
}