243 lines
13 KiB
C#
243 lines
13 KiB
C#
|
|
using Microsoft.EntityFrameworkCore;
|
|||
|
|
using Microsoft.Extensions.Logging;
|
|||
|
|
using PARR.Core.Repositories.Interfaces;
|
|||
|
|
using PARR.Core.Repositories.Interfaces.Job;
|
|||
|
|
using PARR.Core.Services.MatchingStatusService;
|
|||
|
|
using PARR.Core.Services.UnitFilterService;
|
|||
|
|
using PARR.Domain.Cache.Models;
|
|||
|
|
using PARR.Domain.Entities.Base.History;
|
|||
|
|
using PARR.Domain.Entities.Job;
|
|||
|
|
using PARR.Domain.Enums;
|
|||
|
|
using PARR.TemplateMatcher.Services.Interfaces;
|
|||
|
|
|
|||
|
|
namespace PARR.TemplateMatcher.Services.Implementations;
|
|||
|
|
|
|||
|
|
internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
|
|||
|
|
{
|
|||
|
|
private readonly ILogger<GroupedTemplateSynchronizer> logger;
|
|||
|
|
private readonly IJobGroupRepository jobGroupService;
|
|||
|
|
private readonly IUnitFilterService unitFilterService;
|
|||
|
|
private readonly IGroupedTemplateUnitFilter groupedTemplateUnitFilter;
|
|||
|
|
private readonly IUnitInTemplateConflictMapper unitInTemplateConflictMapper;
|
|||
|
|
private readonly IGroupedTemplateBuilder groupedTemplateBuilder;
|
|||
|
|
private readonly IGroupedTemplateProcessor groupedTemplateProcessor;
|
|||
|
|
private readonly ITemplateRepository templateService;
|
|||
|
|
private readonly ITemplateDeactivator templateDeactivator;
|
|||
|
|
private readonly IMatchingStatusService matchingStatusService;
|
|||
|
|
|
|||
|
|
public GroupedTemplateSynchronizer(
|
|||
|
|
ILogger<GroupedTemplateSynchronizer> logger,
|
|||
|
|
IJobGroupRepository jobGroupService,
|
|||
|
|
IUnitFilterService unitFilterService,
|
|||
|
|
IGroupedTemplateUnitFilter groupedTemplateUnitFilter,
|
|||
|
|
IUnitInTemplateConflictMapper unitInTemplateConflictMapper,
|
|||
|
|
IGroupedTemplateBuilder groupedTemplateBuilder,
|
|||
|
|
IGroupedTemplateProcessor groupedTemplateProcessor,
|
|||
|
|
ITemplateRepository templateService,
|
|||
|
|
ITemplateDeactivator templateDeactivator,
|
|||
|
|
IMatchingStatusService matchingStatusService)
|
|||
|
|
{
|
|||
|
|
this.logger = logger;
|
|||
|
|
this.jobGroupService = jobGroupService;
|
|||
|
|
this.unitFilterService = unitFilterService;
|
|||
|
|
this.groupedTemplateUnitFilter = groupedTemplateUnitFilter;
|
|||
|
|
this.unitInTemplateConflictMapper = unitInTemplateConflictMapper;
|
|||
|
|
this.groupedTemplateBuilder = groupedTemplateBuilder;
|
|||
|
|
this.groupedTemplateProcessor = groupedTemplateProcessor;
|
|||
|
|
this.templateService = templateService;
|
|||
|
|
this.templateDeactivator = templateDeactivator;
|
|||
|
|
this.matchingStatusService = matchingStatusService;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public async Task SyncTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
|
|||
|
|
{
|
|||
|
|
logger.LogWarning("GroupedTemplateSynchronizer: SyncTemplatesForJob вызван для JobId {JobId}. Это не поддерживаемая операция.", jobId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public async Task SyncTemplatesForJobGroupAsync(Guid jobGroupId, HistoryInitiator initiator)
|
|||
|
|
{
|
|||
|
|
logger.LogDebug("Начало синхронизации шаблонов для JobGroup {JobGroupId}", jobGroupId);
|
|||
|
|
|
|||
|
|
// === Проверка: уже запущена? ===
|
|||
|
|
var existingStatus = await matchingStatusService.GetStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup);
|
|||
|
|
if (existingStatus.DetailsJobGroups?.Any() == true)
|
|||
|
|
{
|
|||
|
|
logger.LogWarning("Синхронизация для JobGroup {JobGroupId} уже запущена. Пропускаем.", jobGroupId);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// === Устанавливаем статус "в процессе" ===
|
|||
|
|
var initialStatus = new MatchingStatusItemDto
|
|||
|
|
{
|
|||
|
|
DateStart = DateTimeOffset.UtcNow,
|
|||
|
|
Action = TemplateMatcherActionEnum.Sync,
|
|||
|
|
Comment = "Начало синхронизации"
|
|||
|
|
};
|
|||
|
|
await matchingStatusService.SetMatchingStatusAsync(
|
|||
|
|
jobGroupId,
|
|||
|
|
SyncTaskEntityTypeEnum.JobGroup,
|
|||
|
|
new MatchingStatusItem { Data = initialStatus, Timestamp = DateTimeOffset.UtcNow, Source = nameof(GroupedTemplateSynchronizer) },
|
|||
|
|
TimeSpan.FromMinutes(35)
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
try
|
|||
|
|
{
|
|||
|
|
// 1. Загрузка JobGroup и связанных Job'ов
|
|||
|
|
var jobGroup = await jobGroupService.Get()
|
|||
|
|
.AsNoTracking()
|
|||
|
|
.AsSingleQuery()
|
|||
|
|
.Include(jg => jg.GroupType)
|
|||
|
|
.Include(jg => jg.Jobs)
|
|||
|
|
.ThenInclude(j => j.AutoControl)
|
|||
|
|
.Include(jg => jg.Jobs)
|
|||
|
|
.ThenInclude(j => j.UnitFilters)
|
|||
|
|
.ThenInclude(uf => uf.RelationshipFilters)
|
|||
|
|
.ThenInclude(rf => rf.UnitField)
|
|||
|
|
.Include(jg => jg.Jobs)
|
|||
|
|
.ThenInclude(jg => jg.Tnk)
|
|||
|
|
.FirstOrDefaultAsync(jg => jg.Id == jobGroupId);
|
|||
|
|
|
|||
|
|
if (jobGroup == null || jobGroup.Jobs == null || !jobGroup.Jobs.Any())
|
|||
|
|
{
|
|||
|
|
logger.LogWarning("JobGroup {JobGroupId} не найден или не содержит Job'ов.", jobGroupId);
|
|||
|
|
await UpdateMatchingStatusAsync(jobGroupId, "JobGroup не найден или пуст");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var jobsInGroup = jobGroup.Jobs.ToList();
|
|||
|
|
|
|||
|
|
// 2. Поиск Job с максимальным MaxValueRelationships
|
|||
|
|
var maxJob = jobsInGroup
|
|||
|
|
.Where(j => j.MaxValueRelationships.HasValue)
|
|||
|
|
.OrderByDescending(j => j.MaxValueRelationships)
|
|||
|
|
.FirstOrDefault();
|
|||
|
|
|
|||
|
|
if (maxJob == null)
|
|||
|
|
{
|
|||
|
|
logger.LogWarning("В JobGroup {JobGroupId} не найдено Job с установленным MaxValueRelationships.", jobGroupId);
|
|||
|
|
await UpdateMatchingStatusAsync(jobGroupId, "Не найден Job с MaxValueRelationships");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
logger.LogDebug("Используется Job {JobId} с максимальным MaxValueRelationships ({MaxValue}).", maxJob.Id, maxJob.MaxValueRelationships);
|
|||
|
|
|
|||
|
|
// 3. Получение отфильтрованных юнитов через UnitFilterService
|
|||
|
|
logger.LogDebug("Получение отфильтрованных юнитов через UnitFilterService для Job {JobId}.", maxJob.Id);
|
|||
|
|
var unitFilterResults = await unitFilterService.GetUnitsByJobFilterAsync(maxJob.Id);
|
|||
|
|
|
|||
|
|
if (unitFilterResults == null || !unitFilterResults.Any())
|
|||
|
|
{
|
|||
|
|
logger.LogInformation("Для JobGroup {JobGroupId} фильтры не дали Unit'ов с подходящими связями.", jobGroupId);
|
|||
|
|
await UpdateMatchingStatusAsync(jobGroupId, "Фильтры не дали Unit'ов с подходящими связями");
|
|||
|
|
await matchingStatusService.DeleteMatchingStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 4. Применение специфичных правил фильтрации для групповых шаблонов
|
|||
|
|
logger.LogDebug("Применение специфичных правил фильтрации для групповых шаблонов.");
|
|||
|
|
var finalFilteredUnits = await groupedTemplateUnitFilter.FilterAsync(unitFilterResults, jobGroup);
|
|||
|
|
|
|||
|
|
if (!finalFilteredUnits.Any())
|
|||
|
|
{
|
|||
|
|
logger.LogInformation("После применения правил фильтрации в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId);
|
|||
|
|
await UpdateMatchingStatusAsync(jobGroupId, "Нет юнитов после фильтрации");
|
|||
|
|
await matchingStatusService.DeleteMatchingStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup);
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 5. Разрешение конфликтов связей и построение первичного маппинга
|
|||
|
|
logger.LogDebug("Разрешение конфликтов связей и построение первичного маппинга.");
|
|||
|
|
var initialReverseMapping = await unitInTemplateConflictMapper.BuildMappingAsync(finalFilteredUnits, maxJob);
|
|||
|
|
|
|||
|
|
if (!initialReverseMapping.Any())
|
|||
|
|
{
|
|||
|
|
logger.LogInformation("После разрешения конфликтов в JobGroup {JobGroupId} не осталось связей.", jobGroupId);
|
|||
|
|
await UpdateMatchingStatusAsync(jobGroupId, "Нет связей после разрешения конфликтов");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 6. Построение структуры групп (трансформация, внутренняя группировка, разбиение)
|
|||
|
|
logger.LogDebug("Построение структуры групп для шаблонов.");
|
|||
|
|
var templateGroups = await groupedTemplateBuilder.BuildAsync(initialReverseMapping, jobGroup, maxJob);
|
|||
|
|
|
|||
|
|
if (!templateGroups.Any())
|
|||
|
|
{
|
|||
|
|
logger.LogInformation("После построения структуры групп в JobGroup {JobGroupId} не осталось данных.", jobGroupId);
|
|||
|
|
await UpdateMatchingStatusAsync(jobGroupId, "Нет данных после построения групп");
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 7. Обработка групп: сравнение, обновление, создание, отправка MQ
|
|||
|
|
logger.LogDebug("Обработка групп шаблонов: сравнение, обновление и создание.");
|
|||
|
|
var expectedTemplateKeys = await groupedTemplateProcessor.ProcessAsync(
|
|||
|
|
templateGroups,
|
|||
|
|
jobsInGroup,
|
|||
|
|
maxJob,
|
|||
|
|
initiator);
|
|||
|
|
|
|||
|
|
// 8. Деактивация лишних шаблонов
|
|||
|
|
await DeactivateUnusedTemplatesAsync(expectedTemplateKeys, jobGroupId, jobsInGroup, initiator);
|
|||
|
|
|
|||
|
|
// 9. Успешное завершение
|
|||
|
|
await UpdateMatchingStatusAsync(jobGroupId, "Синхронизация завершена успешно");
|
|||
|
|
await matchingStatusService.DeleteMatchingStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup);
|
|||
|
|
logger.LogInformation("Синхронизация шаблонов завершена для JobGroup {JobGroupId}.", jobGroupId);
|
|||
|
|
}
|
|||
|
|
catch (Exception ex)
|
|||
|
|
{
|
|||
|
|
logger.LogError(ex, "Ошибка при синхронизации JobGroup {JobGroupId}", jobGroupId);
|
|||
|
|
await UpdateMatchingStatusAsync(jobGroupId, $"Ошибка: {ex.Message}");
|
|||
|
|
throw;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public async Task UpdateTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
|
|||
|
|
{
|
|||
|
|
logger.LogWarning("GroupedTemplateSynchronizer: UpdateTemplatesForJob вызван для JobId {JobId}. Это не поддерживаемая операция. Используйте SyncTemplatesForJobGroup для обновления.", jobId);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async Task DeactivateUnusedTemplatesAsync(
|
|||
|
|
HashSet<(Guid JobId, Guid UnitId, int Index)> expectedKeys,
|
|||
|
|
Guid jobGroupId,
|
|||
|
|
List<Job> jobsInGroup,
|
|||
|
|
HistoryInitiator initiator)
|
|||
|
|
{
|
|||
|
|
var allJobIdsInGroup = jobsInGroup.Select(j => j.Id).ToHashSet();
|
|||
|
|
var allExistingTemplatesInGroup = await templateService.Get()
|
|||
|
|
.AsNoTracking()
|
|||
|
|
.Include(t => t.Unit)
|
|||
|
|
.Include(t => t.UnitsInTemplate)
|
|||
|
|
.Where(t => allJobIdsInGroup.Contains(t.JobId) &&
|
|||
|
|
t.StatusTypeId == TemplateStatusTypeEnum.Used &&
|
|||
|
|
t.Job!.GroupId == jobGroupId)
|
|||
|
|
.ToListAsync();
|
|||
|
|
|
|||
|
|
foreach (var existingTemplate in allExistingTemplatesInGroup)
|
|||
|
|
{
|
|||
|
|
var key = (existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index ?? -1);
|
|||
|
|
if (!expectedKeys.Contains(key))
|
|||
|
|
{
|
|||
|
|
logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, Unit {UnitId}, Index {Index}).",
|
|||
|
|
existingTemplate.Id, existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index);
|
|||
|
|
await templateDeactivator.DeactivateTemplateAsync(existingTemplate, initiator);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
private async Task UpdateMatchingStatusAsync(Guid jobGroupId, string comment)
|
|||
|
|
{
|
|||
|
|
var status = new MatchingStatusItemDto
|
|||
|
|
{
|
|||
|
|
DateStart = DateTimeOffset.UtcNow,
|
|||
|
|
Action = TemplateMatcherActionEnum.Sync,
|
|||
|
|
Comment = comment
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
await matchingStatusService.SetMatchingStatusAsync(
|
|||
|
|
jobGroupId,
|
|||
|
|
SyncTaskEntityTypeEnum.JobGroup,
|
|||
|
|
new MatchingStatusItem { Data = status, Timestamp = DateTimeOffset.UtcNow, Source = nameof(GroupedTemplateSynchronizer) },
|
|||
|
|
TimeSpan.FromMinutes(30)
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
}
|