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

490 lines
21 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.Contracts;
using PARR.DAL.DomainServices.Interfaces;
using PARR.DAL.DomainServices.Shortcodes;
using PARR.DAL.DomainServices.Shortcodes.Models;
using PARR.DAL.Models;
using PARR.DAL.Models.Job;
using PARR.DAL.Services.Interfaces;
using PARR.DAL.Services.Interfaces.Job;
using PARR.DAL.TransformServices;
using PARR.TemplateMatcher.Services.Interfaces;
using PARR.TemplateMatcher.Settings;
using System.Text.Json;
namespace PARR.TemplateMatcher.Services.Implemetaions;
internal class SimpleTemplateSynchronizer : ITemplateSynchronizer
{
private const bool DefaultUnusedTemplateState = false;
private const bool DefaultUnusedScheduleState = false;
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 IShortcodesService shortcodesService;
private readonly IEsppScheduleTransformService esppScheduleTransformService;
public SimpleTemplateSynchronizer(
ILogger<SimpleTemplateSynchronizer> logger,
IUnitFilterService unitFilterService,
MqSettings mqSettings,
IMqService mqService,
ITemplateService templateService,
IJobService jobService,
ITemplateReuser templateReuser,
IShortcodesService shortcodesService,
IEsppScheduleTransformService esppScheduleTransformService)
{
this.logger = logger;
this.unitFilterService = unitFilterService;
this.mqSettings = mqSettings;
this.mqService = mqService;
this.templateService = templateService;
this.jobService = jobService;
this.templateReuser = templateReuser;
this.shortcodesService = shortcodesService;
this.esppScheduleTransformService = esppScheduleTransformService;
}
public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator)
{
logger.LogWarning("SimpleTemplateSynchronizer: SyncTemplatesForJobGroup вызван для JobGroupId {JobGroupId}. Это не поддерживаемая операция.", jobGroupId);
// Не делаем ничего
return;
}
public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
{
logger.LogDebug("Начало синхронизации шаблонов для JobId {JobId}", jobId);
var expectedUnitIds = await GetExpectedUnitIdsAsync(jobId) ?? new HashSet<Guid>();
logger.LogDebug("JobId {JobId}: найдено {Count} UnitId по фильтрам.", jobId, expectedUnitIds.Count);
var job = await GetJobWithGroupAndAutoControlAsync(jobId);
if (job == null)
{
logger.LogError("Job с Id {JobId} не найден.", jobId);
return;
}
// Проверяем, является ли Job "групповым"
bool isGroupJob = job.Group != null && job.Group.GroupType?.Code == JobGroupTypesEnum.Group;
if (isGroupJob && job.Group!.GroupingUnitFieldId.HasValue)
{
logger.LogInformation("Job {JobId} является групповым. Используйте SyncTemplatesForJobGroup для синхронизации.", jobId);
return; // Ничего не делаем для группового Job
}
var existingTemplates = await templateService.Get()
.AsNoTracking()
.Where(t => t.JobId == jobId)
.ToListAsync();
logger.LogDebug("JobId {JobId}: {Expected} ожидаемых UnitId, {Existing} существующих шаблонов.",
jobId, expectedUnitIds.Count, existingTemplates.Count);
// Обработка случая: фильтр вернул 0 UnitId → деактивировать ВСЕ шаблоны
if (!expectedUnitIds.Any())
{
if (existingTemplates.Any())
{
logger.LogInformation("Для JobId {JobId} фильтры не дали Unit'ов — будет деактивировано {Count} шаблонов.",
jobId, existingTemplates.Count);
foreach (var template in existingTemplates)
{
await DeactivateTemplateAsync(template, jobId, initiator);
}
}
else
{
logger.LogInformation("Для JobId {JobId} нет Unit'ов по фильтрам и нет существующих шаблонов — синхронизация завершена.", jobId);
}
logger.LogInformation("Синхронизация завершена для JobId {JobId} (фильтр пуст).", jobId);
return;
}
// Деактивация шаблонов, которые вышли из фильтра
var templatesToDeactivate = existingTemplates
.Where(t => !expectedUnitIds.Contains(t.UnitId))
.ToList();
foreach (var template in templatesToDeactivate)
{
await DeactivateTemplateAsync(template, jobId, initiator);
}
// Перечитываем шаблоны после деактивации
existingTemplates = await templateService.Get()
.AsNoTracking()
.Include(t=>t.Job)
.ThenInclude(t=>t.Tnk)
.Where(t => t.JobId == jobId)
.ToListAsync();
var unitToTemplate = existingTemplates.ToDictionary(t => t.UnitId, t => t);
// UnitId без шаблона → попытка переиспользования или создание
var unitIdsMissingTemplates = expectedUnitIds
.Where(unitId => !unitToTemplate.ContainsKey(unitId))
.ToList();
var unitIdsToCreateFresh = new List<Guid>();
foreach (var unitId in unitIdsMissingTemplates)
{
var reused = await templateReuser.TryReuseOneUnusedTemplateAsync(jobId, unitId, initiator);
if (reused != null) // если захват успешен
{
logger.LogInformation("Переиспользован шаблон {TemplateId} для UnitId {UnitId}.", reused.Id, unitId);
var expectedName = await GetNormalizedTemplateNameAsync(job, unitId);
var nextRun = await GetNextRunAsync(job); // всегда пересчитываем для нового назначения
var updateRequest = new TemplateUpdaterMq
{
TemplateId = reused.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 SendTemplateUpdateMessage(updateRequest);
}
else
{
logger.LogInformation("Нет доступных Unused-шаблонов для UnitId {UnitId} → создадим новый.", unitId);
unitIdsToCreateFresh.Add(unitId);
}
}
// Обновление/реактивация шаблонов, оставшихся в фильтре
var templatesInFilter = existingTemplates
.Where(t => expectedUnitIds.Contains(t.UnitId))
.ToList();
foreach (var template in templatesInFilter)
{
var expectedName = await GetNormalizedTemplateNameAsync(job, template.UnitId);
await ReactivateOrRenameTemplateAsync(template, job, expectedName, initiator);
}
// Создание новых шаблонов
foreach (var unitId in unitIdsToCreateFresh)
{
await SendTemplateGeneratorMessageAsync(jobId, unitId, initiator);
}
logger.LogInformation("Синхронизация завершена для JobId {JobId}.", jobId);
}
public async Task UpdateTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
{
logger.LogDebug("Начало обновления шаблонов для JobId {JobId}", jobId);
var existingTemplates = await templateService.Get()
.AsNoTracking()
.Where(t => t.JobId == jobId)
.ToListAsync();
if (!existingTemplates.Any()) return;
var job = await GetJobWithGroupAndAutoControlAsync(jobId);
if (job == null) return;
var currentUnitIds = await GetExpectedUnitIdsAsync(jobId) ?? new HashSet<Guid>();
logger.LogDebug("JobId {JobId}: {Count} UnitId по текущему фильтру.", jobId, currentUnitIds.Count);
foreach (var template in existingTemplates)
{
logger.LogDebug("Обработка шаблона {TemplateId} (UnitId {UnitId}).", template.Id, template.UnitId);
bool unitStillInFilter = currentUnitIds.Contains(template.UnitId);
var expectedName = await GetNormalizedTemplateNameAsync(job, template.UnitId);
TemplateStatusTypeEnum targetStatus;
bool targetIsActiveTemplate;
bool targetIsActiveSchedule;
if (unitStillInFilter)
{
targetStatus = TemplateStatusTypeEnum.Used;
targetIsActiveTemplate = template.IsActiveTemplate;
targetIsActiveSchedule = template.IsActiveSchedule;
}
else
{
targetStatus = TemplateStatusTypeEnum.Unused;
targetIsActiveTemplate = DefaultUnusedTemplateState;
targetIsActiveSchedule = DefaultUnusedScheduleState;
expectedName = GetTemplateNameForUnused(expectedName);
logger.LogInformation("Шаблон {TemplateId} (UnitId {UnitId}) → деактивация.", template.Id, template.UnitId);
}
bool needsUpdate = template.StatusTypeId != TemplateStatusTypeEnum.Updating &&
(
// 1. Статус изменился (например, Used → Unused или Unused → Used)
template.StatusTypeId != targetStatus ||
// 2. Для Used — имя должно соответствовать шаблону
(targetStatus == TemplateStatusTypeEnum.Used && template.Name != expectedName) ||
// 3. Флаги изменились (редко, но возможно через AutoControl изменение)
template.IsActiveTemplate != targetIsActiveTemplate ||
template.IsActiveSchedule != targetIsActiveSchedule
);
if (!needsUpdate) continue;
template.StatusTypeId = TemplateStatusTypeEnum.Updating;
template.DateModified = DateTimeOffset.UtcNow;
if (!await templateService.CommitAsync(initiator))
{
logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id);
continue;
}
var nextRun = await GetNextRunAsync(job, template.NextRun);
var updateRequest = new TemplateUpdaterMq
{
TemplateId = template.Id,
JobId = jobId,
UnitId = template.UnitId,
Name = expectedName,
IsActiveTemplate = targetIsActiveTemplate,
IsActiveSchedule = targetIsActiveSchedule,
LastRun = template.LastRun,
NextRun = nextRun,
Index = template.Index,
StatusTypeId = targetStatus,
Initiator = initiator,
UnitsInTemplate = template.UnitsInTemplate.Select(t => t.UnitId).ToList() // Для простого шаблона это список из одного элемента или пустой
};
await SendTemplateUpdateMessage(updateRequest);
}
logger.LogInformation("Обновление шаблонов завершено для JobId {JobId}.", jobId);
}
// --- Вспомогательные методы ---
private async Task<bool> DeactivateTemplateAsync(
Template template,
Guid jobId,
HistoryInitiator initiator)
{
if (template.StatusTypeId == TemplateStatusTypeEnum.Updating)
return true; // уже в обработке
logger.LogInformation("Шаблон {TemplateId} (UnitId {UnitId}) → деактивация.",
template.Id, template.UnitId);
template.StatusTypeId = TemplateStatusTypeEnum.Updating;
template.DateModified = DateTimeOffset.UtcNow;
if (!await templateService.CommitAsync(initiator))
{
logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id);
return false;
}
var updateRequest = new TemplateUpdaterMq
{
TemplateId = template.Id,
JobId = jobId,
UnitId = template.UnitId,
Name = GetTemplateNameForUnused(template.Name),
IsActiveTemplate = DefaultUnusedTemplateState,
IsActiveSchedule = DefaultUnusedScheduleState,
LastRun = template.LastRun,
NextRun = template.NextRun,
Index = template.Index,
StatusTypeId = TemplateStatusTypeEnum.Unused,
Initiator = initiator,
UnitsInTemplate = new List<Guid>() // Для простого шаблона
};
await SendTemplateUpdateMessage(updateRequest);
return true;
}
private async Task<bool> ReactivateOrRenameTemplateAsync(
Template template,
Job job,
string expectedName,
HistoryInitiator initiator)
{
if (template.StatusTypeId == TemplateStatusTypeEnum.Updating)
return true;
bool needsUpdate = template.Name != expectedName
|| template.StatusTypeId != TemplateStatusTypeEnum.Used;
if (!needsUpdate) return true;
logger.LogInformation("Шаблон {TemplateId}: требуется обновление имени или реактивация.", template.Id);
template.StatusTypeId = TemplateStatusTypeEnum.Updating;
template.DateModified = DateTimeOffset.UtcNow;
if (!await templateService.CommitAsync(initiator))
{
logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id);
return false;
}
var nextRun = await GetNextRunAsync(job, template.NextRun);
var updateRequest = new TemplateUpdaterMq
{
TemplateId = template.Id,
JobId = job.Id,
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 = template.UnitsInTemplate.Select(t => t.UnitId).ToList() // Для простого шаблона это список из одного элемента
};
await SendTemplateUpdateMessage(updateRequest);
return true;
}
private async Task<bool> SendTemplateGeneratorMessageAsync(
Guid jobId,
Guid unitId,
HistoryInitiator initiator)
{
logger.LogInformation("Создание нового шаблона для UnitId {UnitId}.", unitId);
var mqRequest = new TemplateGeneratorMq
{
JobId = jobId,
UnitId = unitId,
HistoryInitiator = initiator,
UnitsInTemplate = new List<Guid>() // Для простого шаблона
};
var msg = JsonSerializer.Serialize(mqRequest);
var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg });
if (!result.IsSuccess)
logger.LogError("Ошибка отправки команды создания шаблона для UnitId {UnitId}.", unitId);
return result.IsSuccess;
}
private async Task SendTemplateUpdateMessage(TemplateUpdaterMq updateRequest)
{
logger.LogDebug("Отправка сообщения в очередь '{Queue}' для шаблона {TemplateId}",
mqSettings.TemplateUpdater.QueueName, updateRequest.TemplateId);
var msg = JsonSerializer.Serialize(updateRequest);
var result = await mqService.SendAsync(mqSettings.TemplateUpdater, new[] { msg });
if (result.IsSuccess)
{
logger.LogInformation("Отправлен запрос на обновление шаблона {TemplateId}", updateRequest.TemplateId);
}
else
{
logger.LogError("Ошибка при отправке запроса на обновление шаблона {TemplateId} в очередь '{Queue}'.",
updateRequest.TemplateId, mqSettings.TemplateUpdater.QueueName);
}
}
private string GetTemplateNameForUnused(string templateName)
{
return templateName + "_" + DateTimeOffset.UtcNow.ToUnixTimeSeconds();
}
private async Task<Job?> GetJobWithGroupAndAutoControlAsync(Guid jobId)
{
return await jobService.Get()
.AsNoTracking()
.Include(j=>j.Tnk)
.Include(j => j.Group)
.ThenInclude(j => j.GroupType)
.Include(j => j.AutoControl)
.FirstOrDefaultAsync(j => j.Id == jobId);
}
private async Task<HashSet<Guid>> GetExpectedUnitIdsAsync(Guid jobId)
{
var units = await unitFilterService.GetUnitsIdByJobFilterAsync(jobId);
return units?.ToHashSet() ?? new HashSet<Guid>();
}
private async Task<string> GetNormalizedTemplateNameAsync(Job job, Guid unitId)
{
// Создаём TemplateForShortcodes "на лету", без запроса к БД
var templateForShortcodes = new TemplateForShortcodes
{
Id = Guid.Empty, // шаблон ещё не создан
Index = null,
JobId = job.Id,
UnitId = unitId,
Job = new JobForShortcodes
{
Group = job.Group == null ? null : new JobGroupForShortcodes
{
GroupingUnitFieldId = job.Group.GroupingUnitFieldId,
GroupType = job.Group.GroupType == null ? null : new JobGroupTypeForShortcodes
{
Code = job.Group.GroupType.Code
},
GroupName = job.Group.GroupName
},
Tnk = job.Tnk == null ? null : new TnkForShortcodes
{
Name = job.Tnk.Name,
ShortName = job.Tnk.ShortName ?? ""
},
WorkName = job.WorkName,
Name = job.Name
},
UnitsInTemplate = new List<UnitInTemplateForShortcodes>() // для простого шаблона
};
var rawName = await shortcodesService.ApplyShortcodesAsync(job.TemplateNameMask, templateForShortcodes);
return rawName.ToUpper();
}
private async Task<DateTimeOffset> GetNextRunAsync(Job job, DateTimeOffset? currentNextRun = null)
{
var now = DateTimeOffset.UtcNow;
if (currentNextRun.HasValue && currentNextRun.Value > now)
{
return currentNextRun.Value;
}
var referenceDate = job.Group?.ReferenceDate ?? now;
return await esppScheduleTransformService.GetNextDateAsync(job.GroupId, referenceDate);
}
}