Files
parr_api/PARR.TemplateMatcher/Services/Implementations/GroupedTemplateProcessor.cs

316 lines
13 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.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;
}
}