feat(templateMatcher): Рефакторинг, основной метод синхронизации GroupedTemplateSynchronizer разбит на отдельные классы; Неиспользуемые шаблоны теперь привязываются к ЭК КОСМПЛЕКСЫ-[ЗО].
This commit is contained in:
@@ -0,0 +1,316 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Common.Interfaces.RabbitServices;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Repositories.Interfaces.Unit;
|
||||
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.Enums;
|
||||
using PARR.TemplateMatcher.Models;
|
||||
using PARR.TemplateMatcher.Services.Interfaces;
|
||||
using PARR.TemplateMatcher.Settings;
|
||||
|
||||
namespace PARR.TemplateMatcher.Services.Implementations;
|
||||
|
||||
internal class GroupedTemplateProcessor : IGroupedTemplateProcessor
|
||||
{
|
||||
private readonly ILogger<GroupedTemplateProcessor> logger;
|
||||
private readonly ITemplateRepository templateRepository;
|
||||
private readonly IUnitRepository unitRepository;
|
||||
private readonly ITemplateReuser templateReuser;
|
||||
private readonly ITemplateNameNormalizer templateNameNormalizer;
|
||||
private readonly ITemplateUpdaterMqSender templateUpdaterMqSender;
|
||||
private readonly MqSettings mqSettings;
|
||||
private readonly IRabbitService mqService;
|
||||
|
||||
public GroupedTemplateProcessor(
|
||||
ILogger<GroupedTemplateProcessor> logger,
|
||||
ITemplateRepository templateRepository,
|
||||
IUnitRepository unitRepository,
|
||||
ITemplateReuser templateReuser,
|
||||
ITemplateNameNormalizer templateNameNormalizer,
|
||||
ITemplateUpdaterMqSender templateUpdaterMqSender,
|
||||
MqSettings mqSettings,
|
||||
IRabbitService mqService)
|
||||
{
|
||||
this.logger = logger;
|
||||
this.templateRepository = templateRepository;
|
||||
this.unitRepository = unitRepository;
|
||||
this.templateReuser = templateReuser;
|
||||
this.templateNameNormalizer = templateNameNormalizer;
|
||||
this.templateUpdaterMqSender = templateUpdaterMqSender;
|
||||
this.mqSettings = mqSettings;
|
||||
this.mqService = mqService;
|
||||
}
|
||||
|
||||
public async Task<HashSet<(Guid JobId, Guid UnitId, int Index)>> ProcessAsync(
|
||||
List<GroupedTemplateGroup> groups,
|
||||
List<Job> jobsInGroup,
|
||||
Job maxJob,
|
||||
HistoryInitiator initiator,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
var expectedTemplateKeys = new HashSet<(Guid JobId, Guid UnitId, int Index)>();
|
||||
|
||||
foreach (var group in groups)
|
||||
{
|
||||
var potentialUnitId = group.PotentialUnitId;
|
||||
|
||||
foreach (var subGroup in group.SubGroups)
|
||||
{
|
||||
var unitsInTemplateSubGroup = subGroup.Entries;
|
||||
var globalIndex = subGroup.GlobalIndex;
|
||||
var originatingInnerGroupName = subGroup.InnerGroupName;
|
||||
|
||||
logger.LogDebug("Обработка подгруппы {Index} ('{GroupingValue}') для UnitId {PotentialUnitId}, размер {Size}.",
|
||||
globalIndex, originatingInnerGroupName, potentialUnitId, unitsInTemplateSubGroup.Count);
|
||||
|
||||
Job targetJob = SelectTargetJob(jobsInGroup, unitsInTemplateSubGroup.Count, maxJob);
|
||||
expectedTemplateKeys.Add((targetJob.Id, potentialUnitId, globalIndex));
|
||||
|
||||
// Поиск существующего шаблона
|
||||
var existingTemplate = await templateRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Include(t => t.Unit)
|
||||
.Include(t => t.Job).ThenInclude(t => t!.Tnk)
|
||||
.Include(t => t.Job).ThenInclude(t => t!.Group).ThenInclude(t => t!.GroupType)
|
||||
.Include(t => t.UnitsInTemplate).ThenInclude(uit => uit.Unit)
|
||||
.Where(t => t.JobId == targetJob.Id &&
|
||||
t.UnitId == potentialUnitId &&
|
||||
t.Index == globalIndex &&
|
||||
t.StatusTypeId == TemplateStatusTypeEnum.Used)
|
||||
.FirstOrDefaultAsync(ct);
|
||||
|
||||
if (existingTemplate != null)
|
||||
{
|
||||
await HandleExistingTemplateAsync(existingTemplate, unitsInTemplateSubGroup, targetJob, globalIndex, initiator, ct);
|
||||
}
|
||||
else
|
||||
{
|
||||
await HandleNewOrReusableTemplateAsync(potentialUnitId, unitsInTemplateSubGroup, targetJob, globalIndex, initiator);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return expectedTemplateKeys;
|
||||
}
|
||||
|
||||
private async Task HandleExistingTemplateAsync(
|
||||
Template existingTemplate,
|
||||
List<(Guid UnitId, Guid UnitFieldValueId)> proposedEntries,
|
||||
Job targetJob,
|
||||
int globalIndex,
|
||||
HistoryInitiator initiator,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var currentEntries = existingTemplate.UnitsInTemplate
|
||||
.Select(uit => (uit.UnitId, uit.UnitFieldValueId))
|
||||
.ToList();
|
||||
|
||||
// Сравнение
|
||||
var allUnitIdsForSort = currentEntries.Select(e => e.UnitId)
|
||||
.Concat(proposedEntries.Select(e => e.UnitId))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
var unitNamesForSort = await unitRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Where(u => allUnitIdsForSort.Contains(u.Id))
|
||||
.ToDictionaryAsync(u => u.Id, u => u.Name ?? u.Id.ToString(), ct);
|
||||
|
||||
var sortedCurrent = currentEntries
|
||||
.OrderBy(e => unitNamesForSort.GetValueOrDefault(e.UnitId, e.UnitId.ToString()))
|
||||
.ThenBy(e => e.UnitFieldValueId)
|
||||
.ToList();
|
||||
|
||||
var sortedProposed = proposedEntries
|
||||
.OrderBy(e => unitNamesForSort.GetValueOrDefault(e.UnitId, e.UnitId.ToString()))
|
||||
.ThenBy(e => e.UnitFieldValueId)
|
||||
.ToList();
|
||||
|
||||
bool unitsAreEqual = sortedCurrent.SequenceEqual(sortedProposed);
|
||||
|
||||
if (unitsAreEqual)
|
||||
{
|
||||
logger.LogDebug("Шаблон {TemplateId} актуален по составу.", existingTemplate.Id);
|
||||
|
||||
// Проверка имени
|
||||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(existingTemplate);
|
||||
if (!string.Equals(existingTemplate.Name, expectedName, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
logger.LogDebug("Шаблон {TemplateId} требует обновления имени.", existingTemplate.Id);
|
||||
var updateRequest = new TemplateUpdaterMessage
|
||||
{
|
||||
TemplateId = existingTemplate.Id,
|
||||
JobId = targetJob.Id,
|
||||
UnitId = existingTemplate.UnitId,
|
||||
Name = expectedName,
|
||||
IsActiveTemplate = existingTemplate.IsActiveTemplate,
|
||||
IsActiveSchedule = existingTemplate.IsActiveSchedule,
|
||||
IsNew = false,
|
||||
Index = globalIndex,
|
||||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||||
Initiator = initiator,
|
||||
UnitsInTemplate = sortedProposed.Select(t => new UnitInTemplateMessage
|
||||
{
|
||||
UnitId = t.UnitId,
|
||||
UnitFieldValueId = t.UnitFieldValueId
|
||||
}).ToList()
|
||||
};
|
||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogDebug("Шаблон {TemplateId} требует обновления состава.", existingTemplate.Id);
|
||||
await UpdateTemplateUnitsAsync(existingTemplate, sortedProposed, targetJob, globalIndex, initiator);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleNewOrReusableTemplateAsync(
|
||||
Guid potentialUnitId,
|
||||
List<(Guid UnitId, Guid UnitFieldValueId)> unitsInTemplateSubGroup,
|
||||
Job targetJob,
|
||||
int globalIndex,
|
||||
HistoryInitiator initiator)
|
||||
{
|
||||
var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(targetJob.Id, potentialUnitId, initiator);
|
||||
|
||||
if (reusableTemplate != null)
|
||||
{
|
||||
logger.LogInformation("Переиспользован шаблон {TemplateId}.", reusableTemplate.Id);
|
||||
|
||||
var tempTemplateForName = new Template
|
||||
{
|
||||
Id = reusableTemplate.Id,
|
||||
Name = reusableTemplate.Name,
|
||||
JobId = targetJob.Id,
|
||||
UnitId = potentialUnitId,
|
||||
Index = globalIndex,
|
||||
Job = targetJob,
|
||||
Unit = reusableTemplate.Unit,
|
||||
UnitsInTemplate = unitsInTemplateSubGroup.Select(e => new UnitsInTemplate { UnitId = e.UnitId, UnitFieldValueId = e.UnitFieldValueId }).ToList()
|
||||
};
|
||||
|
||||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName);
|
||||
|
||||
var updateRequest = new TemplateUpdaterMessage
|
||||
{
|
||||
TemplateId = reusableTemplate.Id,
|
||||
JobId = targetJob.Id,
|
||||
UnitId = potentialUnitId,
|
||||
Name = expectedName,
|
||||
IsActiveTemplate = targetJob.AutoControl?.InitUsedTemplateState ?? false,
|
||||
IsActiveSchedule = targetJob.AutoControl?.InitUsedScheduleState ?? false,
|
||||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||||
Initiator = initiator,
|
||||
IsNew = true,
|
||||
Index = globalIndex,
|
||||
UnitsInTemplate = unitsInTemplateSubGroup.Select(e => new UnitInTemplateMessage { UnitId = e.UnitId, UnitFieldValueId = e.UnitFieldValueId }).ToList()
|
||||
};
|
||||
|
||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||
}
|
||||
else
|
||||
{
|
||||
logger.LogDebug("Создание нового шаблона.");
|
||||
await CreateGroupedTemplateAsync(targetJob.Id, potentialUnitId, unitsInTemplateSubGroup, globalIndex, initiator);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task UpdateTemplateUnitsAsync(
|
||||
Template template,
|
||||
List<(Guid UnitId, Guid UnitFieldValueId)> newUnitEntries,
|
||||
Job targetJob,
|
||||
int newIndex,
|
||||
HistoryInitiator initiator)
|
||||
{
|
||||
// 1. Устанавливаем статус и дату
|
||||
template.StatusTypeId = TemplateStatusTypeEnum.Updating;
|
||||
template.DateModified = DateTimeOffset.UtcNow;
|
||||
|
||||
// 2. КОММИТ В БАЗУ СРАЗУ
|
||||
// Важно зафиксировать изменение статуса до отправки сообщения в очередь
|
||||
if (!await templateRepository.CommitAsync(initiator))
|
||||
{
|
||||
logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating.", template.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Формируем временный объект для генерации имени
|
||||
var tempTemplateForName = new Template
|
||||
{
|
||||
Id = template.Id,
|
||||
Name = template.Name,
|
||||
JobId = targetJob.Id,
|
||||
UnitId = template.UnitId,
|
||||
Index = newIndex,
|
||||
Job = targetJob,
|
||||
Unit = template.Unit,
|
||||
UnitsInTemplate = newUnitEntries.Select(e => new UnitsInTemplate { UnitId = e.UnitId, UnitFieldValueId = e.UnitFieldValueId }).ToList()
|
||||
};
|
||||
|
||||
var expectedName = await templateNameNormalizer.GetNormalizedTemplateNameAsync(tempTemplateForName);
|
||||
|
||||
// 4. Отправляем сообщение в очередь
|
||||
var updateRequest = new TemplateUpdaterMessage
|
||||
{
|
||||
TemplateId = template.Id,
|
||||
JobId = targetJob.Id,
|
||||
UnitId = template.UnitId,
|
||||
Name = expectedName,
|
||||
IsActiveTemplate = template.IsActiveTemplate,
|
||||
IsActiveSchedule = template.IsActiveSchedule,
|
||||
IsNew = false,
|
||||
Index = newIndex,
|
||||
StatusTypeId = TemplateStatusTypeEnum.Used,
|
||||
Initiator = initiator,
|
||||
UnitsInTemplate = newUnitEntries.Select(e => new UnitInTemplateMessage { UnitId = e.UnitId, UnitFieldValueId = e.UnitFieldValueId }).ToList()
|
||||
};
|
||||
|
||||
await templateUpdaterMqSender.SendTemplateUpdateMessageAsync(updateRequest);
|
||||
}
|
||||
|
||||
private async Task CreateGroupedTemplateAsync(
|
||||
Guid jobId,
|
||||
Guid relationshipUnitId,
|
||||
List<(Guid UnitId, Guid UnitFieldValueId)> unitsInTemplate,
|
||||
int index,
|
||||
HistoryInitiator initiator)
|
||||
{
|
||||
logger.LogInformation("Создание нового группового шаблона.");
|
||||
|
||||
var mqRequest = new TemplateGeneratorMessage
|
||||
{
|
||||
JobId = jobId,
|
||||
UnitId = relationshipUnitId,
|
||||
UnitsInTemplate = unitsInTemplate.Select(e => new UnitInTemplateMessage { UnitId = e.UnitId, UnitFieldValueId = e.UnitFieldValueId }).ToList(),
|
||||
Index = index,
|
||||
HistoryInitiator = initiator
|
||||
};
|
||||
|
||||
var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new List<object> { mqRequest });
|
||||
|
||||
if (!result.IsSuccess)
|
||||
logger.LogError("Ошибка отправки команды создания шаблона.");
|
||||
}
|
||||
|
||||
private static Job SelectTargetJob(List<Job> jobsInGroup, int subGroupSize, Job maxJob)
|
||||
{
|
||||
Job? targetJob = jobsInGroup
|
||||
.Where(j => j.MaxValueRelationships.HasValue && j.MaxValueRelationships.Value == subGroupSize)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (targetJob == null)
|
||||
{
|
||||
targetJob = jobsInGroup
|
||||
.Where(j => j.MaxValueRelationships.HasValue && j.MaxValueRelationships.Value >= subGroupSize)
|
||||
.OrderBy(j => j.MaxValueRelationships!.Value)
|
||||
.FirstOrDefault();
|
||||
}
|
||||
|
||||
return targetJob ?? maxJob;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user