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

710 lines
35 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.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.Models.Unit;
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.Implemetaions;
internal class GroupedTemplateSynchronizer : ITemplateSynchronizer
{
private const bool DefaultUnusedTemplateState = false;
private const bool DefaultUnusedScheduleState = false;
private const bool DefaultUsedTemplateState = false;
private const bool DefaultUsedScheduleState = false;
private readonly ILogger<GroupedTemplateSynchronizer> logger;
private readonly IUnitFilterService unitFilterService;
private readonly IUnitInUnitService unitInUnitService;
private readonly IUnitInValueService unitInValueService;
private readonly IUnitService unitService;
private readonly MqSettings mqSettings;
private readonly IMqService mqService;
private readonly ITemplateService templateService;
private readonly IJobGroupService jobGroupService;
private readonly ITemplateReuser templateReuser;
private readonly IShortcodesService shortcodesService;
private readonly IEsppScheduleTransformService esppScheduleTransformService;
private readonly IUnitRegionalEkPtkGroupService regionalEkPtkGroupService;
private readonly IUnitFieldService unitFieldService;
public GroupedTemplateSynchronizer(
ILogger<GroupedTemplateSynchronizer> logger,
IUnitFilterService unitFilterService,
IUnitInUnitService unitInUnitService,
IUnitInValueService unitInValueService,
IUnitService unitService,
MqSettings mqSettings,
IMqService mqService,
ITemplateService templateService,
IJobGroupService jobGroupService,
ITemplateReuser templateReuser,
IShortcodesService shortcodesService,
IEsppScheduleTransformService esppScheduleTransformService,
IUnitRegionalEkPtkGroupService regionalEkPtkGroupService,
IUnitFieldService unitFieldService
)
{
this.logger = logger;
this.unitFilterService = unitFilterService;
this.unitInUnitService = unitInUnitService;
this.unitInValueService = unitInValueService;
this.unitService = unitService;
this.mqSettings = mqSettings;
this.mqService = mqService;
this.templateService = templateService;
this.jobGroupService = jobGroupService;
this.templateReuser = templateReuser;
this.shortcodesService = shortcodesService;
this.esppScheduleTransformService = esppScheduleTransformService;
this.regionalEkPtkGroupService = regionalEkPtkGroupService;
this.unitFieldService = unitFieldService;
}
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);
// 1. Получить JobGroup и связанные Job'ы
var jobGroup = await jobGroupService.Get()
.AsNoTracking()
.Include(jg => jg.Jobs)
.ThenInclude(j => j.AutoControl)
.Include(jg => jg.Jobs)
.ThenInclude(j => j.UnitFilters)
.ThenInclude(uf => uf.RelationshipFilters)
.FirstOrDefaultAsync(jg => jg.Id == jobGroupId);
if (jobGroup == null || jobGroup.Jobs == null || !jobGroup.Jobs.Any())
{
logger.LogWarning("JobGroup {JobGroupId} не найден или не содержит Job'ов.", jobGroupId);
return;
}
var jobsInGroup = jobGroup.Jobs.ToList();
// --- НОВАЯ ЛОГИКА: Получение FieldId и разрешённых значений для "РАБОЧАЯ_ГР_ОТВ_ЗАК" ---
var workGroupField = await unitFieldService.GetByAihitNameAsync("РАБОЧАЯ_ГР_ОТВ_ЗАК");
if (workGroupField == null)
{
logger.LogError("Поле 'РАБОЧАЯ_ГР_ОТВ_ЗАК' не найдено в справочнике полей. Синхронизация прервана.");
return;
}
var workGroupFieldId = workGroupField.Id;
var regionalGroupValueIds = regionalEkPtkGroupService.Get()
.Select(g => g.FieldValueId)
.ToList(); // Получаем список UnitFieldValue.Id
logger.LogDebug("Найдено {Count} значений из UnitRegionalEkPtkGroup для проверки поля 'РАБОЧАЯ_ГР_ОТВ_ЗАК'.", regionalGroupValueIds.Count);
// 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);
return;
}
// Проверяем, что UnitFilters и RelationshipFilters загружены (если используется для выбора targetJob)
if (maxJob.UnitFilters == null)
{
logger.LogWarning("Job {JobId} не содержит UnitFilters.", maxJob.Id);
}
logger.LogDebug("Используется Job {JobId} с максимальным MaxValueRelationships ({MaxValue}) для фильтрации.", maxJob.Id, maxJob.MaxValueRelationships);
// 3. Использовать фильтры maxJob для получения expectedUnitIds
var expectedUnitIds = await unitFilterService.GetUnitsIdByJobFilterAsync(maxJob.Id);
if (expectedUnitIds == null || !expectedUnitIds.Any())
{
logger.LogInformation("Для JobGroup {JobGroupId} фильтры не дали Unit'ов.", jobGroupId);
return;
}
// 4. Отфильтровать expectedUnitIds по GroupingUnitFieldId (дополнительный фильтр)
if (!jobGroup.GroupingUnitFieldId.HasValue)
{
logger.LogError("JobGroup {JobGroupId} не имеет GroupingUnitFieldId, необходимого для группировки.", jobGroupId);
return;
}
var groupingFieldId = jobGroup.GroupingUnitFieldId.Value;
// Загрузить UnitValues для юнитов из expectedUnitIds, чтобы проверить GroupingUnitFieldId
var expectedUnitsWithGroupingField = await unitService.Get()
.AsNoTracking()
.AsSplitQuery() // Для Unit -> UnitValues
.Include(u => u.UnitValues)
.ThenInclude(uv => uv.Value)
.Where(u => expectedUnitIds.Contains(u.Id))
.ToListAsync();
var unitIdsWithValidGroupingFieldSet = expectedUnitsWithGroupingField
.Where(u => u.UnitValues.Any(uv => uv.FieldId == groupingFieldId && uv.Value != null && !string.IsNullOrEmpty(uv.Value.Value)))
.Select(u => u.Id)
.ToHashSet();
logger.LogDebug("После фильтрации по GroupingUnitFieldId осталось {Count} юнитов.", unitIdsWithValidGroupingFieldSet.Count);
if (!unitIdsWithValidGroupingFieldSet.Any())
{
logger.LogInformation("После фильтрации по GroupingUnitFieldId в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId);
return;
}
// --- НОВАЯ ЛОГИКА: Дополнительная фильтрация по "РАБОЧАЯ_ГР_ОТВ_ЗАК" ---
var unitIdsWithValidWorkGroupFieldSet = expectedUnitsWithGroupingField
.Where(u => unitIdsWithValidGroupingFieldSet.Contains(u.Id) && // Убедимся, что юнит уже прошёл фильтр по GroupingFieldId
u.UnitValues.Any(uv =>
uv.FieldId == workGroupFieldId && // Поле "РАБОЧАЯ_ГР_ОТВ_ЗАК"
uv.Value != null && // Значение существует
regionalGroupValueIds.Contains(uv.Value.Id) // Значение в списке разрешённых
))
.Select(u => u.Id) // Выбираем Id юнита
.ToHashSet(); // И снова в HashSet
logger.LogDebug("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗАК' осталось {Count} юнитов.", unitIdsWithValidWorkGroupFieldSet.Count);
if (!unitIdsWithValidWorkGroupFieldSet.Any())
{
logger.LogInformation("После фильтрации по 'РАБОЧАЯ_ГР_ОТВ_ЗАК' в JobGroup {JobGroupId} не осталось юнитов.", jobGroupId);
return;
}
// Обновляем список юнитов, прошедших оба фильтра
unitIdsWithValidGroupingFieldSet = unitIdsWithValidWorkGroupFieldSet;
// 5. Получить RelationshipFilters из maxJob
var relationshipFilters = maxJob.UnitFilters?.SelectMany(uf => uf.RelationshipFilters).ToList() ?? new List<JobRelationshipFilter>();
// --- Найти и отфильтровать UnitInUnit связи ---
logger.LogDebug("Получение связей UnitInUnit для юнитов, прошедших фильтрацию по GroupingUnitFieldId и 'РАБОЧАЯ_ГР_ОТВ_ЗАК'.");
var potentialUnitInUnitLinks = await unitInUnitService.Get()
.AsNoTracking()
.Where(link => unitIdsWithValidGroupingFieldSet.Contains(link.ParentUnitId) || unitIdsWithValidGroupingFieldSet.Contains(link.ChildUnitId))
.ToListAsync();
logger.LogDebug("Найдено {Count} потенциальных связей UnitInUnit.", potentialUnitInUnitLinks.Count);
// Загрузить UnitInValue для всех ParentUnitId и ChildUnitId из potentialUnitInUnitLinks
var allParentIds = potentialUnitInUnitLinks.Select(l => l.ParentUnitId).ToHashSet();
var allChildIds = potentialUnitInUnitLinks.Select(l => l.ChildUnitId).ToHashSet();
var parentUnitValues = await unitInValueService.Get()
.AsNoTracking()
.Include(uv => uv.Field)
.Include(uv => uv.Value)
.Where(uv => allParentIds.Contains(uv.UnitId))
.ToListAsync();
var childUnitValues = await unitInValueService.Get()
.AsNoTracking()
.Include(uv => uv.Field)
.Include(uv => uv.Value)
.Where(uv => allChildIds.Contains(uv.UnitId))
.ToListAsync();
// Сгруппировать значения по UnitId для быстрого доступа
var parentValuesMap = parentUnitValues
.GroupBy(uv => uv.UnitId)
.ToDictionary(g => g.Key, g => g.ToList());
var childValuesMap = childUnitValues
.GroupBy(uv => uv.UnitId)
.ToDictionary(g => g.Key, g => g.ToList());
// Применить фильтры к связям
logger.LogDebug("Применение {Count} RelationshipFilters к найденным связям.", relationshipFilters.Count);
var filteredUnitInUnitLinks = new List<UnitInUnit>();
foreach (var link in potentialUnitInUnitLinks)
{
bool linkMatchesAllFilters = true;
foreach (var rf in relationshipFilters)
{
var valuesToCheck = rf.IsParent ? parentValuesMap.GetValueOrDefault(link.ParentUnitId, new List<UnitInValue>()) : childValuesMap.GetValueOrDefault(link.ChildUnitId, new List<UnitInValue>());
bool filterMatch = valuesToCheck.Any(uv =>
uv.FieldId == rf.FieldId &&
uv.Value != null &&
uv.Value.Value != null &&
uv.Value.Value.Contains(rf.ValueMask ?? "", StringComparison.OrdinalIgnoreCase)
);
if (rf.IsInverse)
filterMatch = !filterMatch;
if (!filterMatch)
{
linkMatchesAllFilters = false;
break;
}
}
if (linkMatchesAllFilters)
{
filteredUnitInUnitLinks.Add(link);
}
}
logger.LogDebug("После применения RelationshipFilters осталось {Count} связей UnitInUnit.", filteredUnitInUnitLinks.Count);
// --- НОВАЯ ЛОГИКА: Сгруппировать юниты из unitIdsWithValidGroupingFieldSet по связанному юниту ---
var groupedRelationships = new Dictionary<Guid, List<Guid>>();
foreach (var link in filteredUnitInUnitLinks)
{
var parentUnitId = link.ParentUnitId;
var childUnitId = link.ChildUnitId;
if (unitIdsWithValidGroupingFieldSet.Contains(parentUnitId))
{
if (!groupedRelationships.ContainsKey(childUnitId))
{
groupedRelationships[childUnitId] = new List<Guid>();
}
groupedRelationships[childUnitId].Add(parentUnitId);
}
else if (unitIdsWithValidGroupingFieldSet.Contains(childUnitId))
{
if (!groupedRelationships.ContainsKey(parentUnitId))
{
groupedRelationships[parentUnitId] = new List<Guid>();
}
groupedRelationships[parentUnitId].Add(childUnitId);
}
}
logger.LogDebug("Сформировано {Count} групп по связанным юнитам до разрешения конфликтов.", groupedRelationships.Count);
// --- НОВАЯ ЛОГИКА: Разрешение конфликта - один юнит из unitIdsWithValidGroupingFieldSet только в одном списке значений ---
var unitToKeys = new Dictionary<Guid, List<Guid>>(); // Карта: юнит из списка -> список ключей, где он встречается
foreach (var kvp in groupedRelationships)
{
var key = kvp.Key;
var units = kvp.Value;
foreach (var unitId in units)
{
if (!unitToKeys.ContainsKey(unitId))
{
unitToKeys[unitId] = new List<Guid>();
}
unitToKeys[unitId].Add(key);
}
}
// Найти юниты, которые находятся в нескольких списках
var conflictedUnits = unitToKeys.Where(kvp => kvp.Value.Count > 1).ToList();
foreach (var conflictedUnitEntry in conflictedUnits)
{
var unitId = conflictedUnitEntry.Key;
var keysForUnit = conflictedUnitEntry.Value;
Guid bestKey = keysForUnit[0]; // Инициализируем первым ключом
int maxCount = groupedRelationships[bestKey].Count;
for (int i = 1; i < keysForUnit.Count; i++)
{
var currentKey = keysForUnit[i];
var currentCount = groupedRelationships[currentKey].Count;
if (currentCount > maxCount)
{
bestKey = currentKey;
maxCount = currentCount;
}
}
// Удалить юнит из списков всех ключей, кроме bestKey
foreach (var key in keysForUnit)
{
if (key != bestKey)
{
groupedRelationships[key].Remove(unitId);
logger.LogDebug("Юнит {UnitId} перемещён из группы {OldKey} в группу {BestKey} (по кол-ву).", unitId, key, bestKey);
}
}
}
// Удаляем ключи, у которых список стал пустым после разрешения конфликтов
var keysToRemove = groupedRelationships.Where(kvp => kvp.Value.Count == 0).Select(kvp => kvp.Key).ToList();
foreach (var key in keysToRemove)
{
groupedRelationships.Remove(key);
logger.LogDebug("Ключ {Key} удалён, так как его список юнитов стал пустым после разрешения конфликтов.", key);
}
logger.LogDebug("Сформировано {Count} групп по связанным юнитам после разрешения конфликтов.", groupedRelationships.Count);
// 7. Разбить каждую группу и сопоставить с Job
foreach (var kvp in groupedRelationships)
{
var relationshipUnitId = kvp.Key; // Связанный юнит (не из unitIdsWithValidGroupingFieldSet)
var childUnitIds = kvp.Value; // Юниты из unitIdsWithValidGroupingFieldSet, связанные с regionalUnitId
logger.LogDebug("Обработка связанного юнита {RegionalUnitId} с {Count} юнитами из списка.", relationshipUnitId, childUnitIds.Count);
// Применяем ограничение MaxValueRelationships maxJob
if (!maxJob.MaxValueRelationships.HasValue)
{
logger.LogWarning("Job {JobId} не заполнено MaxValueRelationships.", maxJob.Id);
return;
}
int maxValueForSplitting = maxJob.MaxValueRelationships.Value;
var childUnitGroups = childUnitIds
.Select((id, index) => new { id, groupIndex = index / maxValueForSplitting })
.GroupBy(x => x.groupIndex)
.Select(g => g.Select(x => x.id).ToList())
.ToList();
logger.LogDebug("Связанный юнит {RegionalUnitId}: разбит на {GroupCount} подгрупп.", relationshipUnitId, childUnitGroups.Count);
// Для каждой подгруппы:
for (int i = 0; i < childUnitGroups.Count; i++)
{
var subGroup = childUnitGroups[i];
var subGroupSize = subGroup.Count;
logger.LogDebug("Обработка подгруппы {Index} связанного юнита {RegionalUnitId}, размер {Size}.", i, relationshipUnitId, subGroupSize);
// 8. Найти подходящий Job для подгруппы (логика без изменений)
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();
}
if (targetJob == null)
{
targetJob = maxJob; // maxJob уже проверен на null ранее
logger.LogDebug("Для подгруппы {Index} связанного юнита {RegionalUnitId} не найден подходящий Job, используем maxJob {MaxJobId}.", i, relationshipUnitId, maxJob.Id);
}
else
{
logger.LogDebug("Для подгруппы {Index} связанного юнита {RegionalUnitId} выбран Job {TargetJobId} с MaxValueRelationships {MaxValue}.", i, relationshipUnitId, targetJob.Id, targetJob.MaxValueRelationships);
}
// 9. Загрузить существующие шаблоны для targetJob, связанные с regionalUnitId
var existingTemplatesForRelationship = await templateService.Get()
.AsNoTracking()
.Include(t => t.UnitsInTemplate)
.Where(t => t.JobId == targetJob.Id && t.UnitId == relationshipUnitId && t.Index == i)
.ToListAsync();
var existingTemplateForSubGroup = existingTemplatesForRelationship.FirstOrDefault();
if (existingTemplateForSubGroup != null)
{
// Проверить, изменились ли юниты
var existingUnitIds = existingTemplateForSubGroup.UnitsInTemplate.Select(uit => uit.UnitId).ToHashSet();
var newUnitIds = subGroup.ToHashSet();
if (existingUnitIds.SetEquals(newUnitIds))
{
logger.LogDebug("Шаблон {TemplateId} (Job {JobId}, Regional {RegionalId}, Index {Index}) актуален.", existingTemplateForSubGroup.Id, targetJob.Id, relationshipUnitId, i);
}
else
{
logger.LogDebug("Шаблон {TemplateId} (Job {JobId}, Regional {RegionalId}, Index {Index}) требует обновления юнитов.", existingTemplateForSubGroup.Id, targetJob.Id, relationshipUnitId, i);
await UpdateTemplateUnitsAsync(existingTemplateForSubGroup, subGroup, targetJob, initiator);
}
}
else
{
var reusableTemplate = await templateReuser.TryReuseOneUnusedTemplateAsync(targetJob.Id, relationshipUnitId, initiator);
if (reusableTemplate != null)
{
logger.LogInformation("Переиспользован шаблон {TemplateId} для Job {JobId}, связанного юнита {RegionalId}, Index {Index}.", reusableTemplate.Id, targetJob.Id, relationshipUnitId, i);
var expectedName = await GetNormalizedTemplateNameAsync(targetJob, relationshipUnitId, i, subGroup);
var nextRun = await GetNextRunAsync(targetJob);
var updateRequest = new TemplateUpdaterMq
{
TemplateId = reusableTemplate.Id,
JobId = targetJob.Id,
UnitId = relationshipUnitId,
Name = expectedName,
IsActiveTemplate = targetJob.AutoControl?.InitUsedTemplateState ?? DefaultUsedTemplateState,
IsActiveSchedule = targetJob.AutoControl?.InitUsedScheduleState ?? DefaultUsedScheduleState,
StatusTypeId = TemplateStatusTypeEnum.Used,
Initiator = initiator,
NextRun = nextRun,
Index = i,
UnitsInTemplate = subGroup
};
await SendTemplateUpdateMessage(updateRequest);
}
else
{
logger.LogDebug("Создание нового шаблона для Job {JobId}, связанного юнита {RegionalId}, Index {Index}, с {Count} юнитами.", targetJob.Id, relationshipUnitId, i, subGroup.Count);
await CreateGroupedTemplateAsync(targetJob.Id, relationshipUnitId, subGroup, i, initiator);
}
}
}
}
// 10. Деактивировать шаблоны, которые больше не соответствуют ни одной подгруппе (логика без изменений)
var expectedTemplateKeys = new HashSet<(Guid JobId, Guid UnitId, int Index)>();
foreach (var kvp in groupedRelationships)
{
var regionalUnitId = kvp.Key;
var childUnitIds = kvp.Value;
int maxValueForSplitting = maxJob.MaxValueRelationships!.Value;
var childUnitGroups = childUnitIds
.Select((id, index) => new { id, groupIndex = index / maxValueForSplitting })
.GroupBy(x => x.groupIndex)
.Select(g => g.Select(x => x.id).ToList())
.ToList();
for (int i = 0; i < childUnitGroups.Count; i++)
{
var subGroup = childUnitGroups[i];
var subGroupSize = subGroup.Count;
Job? targetJobForExpectedKey = jobsInGroup
.Where(j => j.MaxValueRelationships.HasValue && j.MaxValueRelationships.Value == subGroupSize)
.FirstOrDefault();
if (targetJobForExpectedKey == null)
{
targetJobForExpectedKey = jobsInGroup
.Where(j => j.MaxValueRelationships.HasValue && j.MaxValueRelationships.Value >= subGroupSize)
.OrderBy(j => j.MaxValueRelationships!.Value)
.FirstOrDefault();
}
if (targetJobForExpectedKey == null) targetJobForExpectedKey = maxJob; // maxJob уже проверен на null
expectedTemplateKeys.Add((targetJobForExpectedKey.Id, regionalUnitId, i));
}
}
var allRegionalUnitIds = groupedRelationships.Keys.ToHashSet();
var allJobIdsInGroup = jobsInGroup.Select(j => j.Id).ToHashSet();
var allExistingTemplatesInGroup = await templateService.Get()
.AsNoTracking()
.Include(t => t.UnitsInTemplate)
.Where(t => allJobIdsInGroup.Contains(t.JobId) && allRegionalUnitIds.Contains(t.UnitId))
.ToListAsync();
foreach (var existingTemplate in allExistingTemplatesInGroup)
{
var key = (existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index ?? -1);
if (!expectedTemplateKeys.Contains(key))
{
logger.LogInformation("Деактивация лишнего шаблона {TemplateId} (Job {JobId}, Regional {UnitId}, Index {Index}).", existingTemplate.Id, existingTemplate.JobId, existingTemplate.UnitId, existingTemplate.Index);
await DeactivateTemplateAsync(existingTemplate, existingTemplate.JobId, initiator);
}
}
logger.LogInformation("Синхронизация шаблонов завершена для JobGroup {JobGroupId}.", jobGroupId);
}
public async Task UpdateTemplatesForJobAsync(Guid jobId, HistoryInitiator initiator)
{
logger.LogWarning("GroupedTemplateSynchronizer: UpdateTemplatesForJob вызван для JobId {JobId}. Это не поддерживаемая операция. Используйте SyncTemplatesForJobGroup для обновления.", jobId);
return;
}
// --- Вспомогательные методы ---
private async Task UpdateTemplateUnitsAsync(Template template, List<Guid> newUnitIds, Job targetJob, HistoryInitiator initiator)
{
template.StatusTypeId = TemplateStatusTypeEnum.Updating;
template.DateModified = DateTimeOffset.UtcNow;
if (!await templateService.CommitAsync(initiator))
{
logger.LogError("Не удалось перевести шаблон {TemplateId} в Updating для обновления юнитов.", template.Id);
return;
}
var expectedName = await GetNormalizedTemplateNameAsync(targetJob, template.UnitId, template.Index, newUnitIds);
var nextRun = await GetNextRunAsync(targetJob, template.NextRun);
var updateRequest = new TemplateUpdaterMq
{
TemplateId = template.Id,
JobId = targetJob.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 = newUnitIds
};
await SendTemplateUpdateMessage(updateRequest);
}
private async Task CreateGroupedTemplateAsync(Guid jobId, Guid regionalUnitId, List<Guid> unitIds, int index, HistoryInitiator initiator)
{
logger.LogInformation("Создание нового группового шаблона для Job {JobId}, связанного юнита {RegionalUnitId}, Index {Index}, с {Count} юнитами.", jobId, regionalUnitId, index, unitIds.Count);
var mqRequest = new TemplateGeneratorMq
{
JobId = jobId,
UnitId = regionalUnitId, // UnitId шаблона
UnitsInTemplate = unitIds, // Юниты для UnitsInTemplate
Index = index, // Индекс шаблона
HistoryInitiator = initiator
};
var msg = JsonSerializer.Serialize(mqRequest);
var result = await mqService.SendAsync(mqSettings.TemplateGenerator, new[] { msg });
if (!result.IsSuccess)
logger.LogError("Ошибка отправки команды создания группового шаблона для Job {JobId}, связанного юнита {RegionalUnitId}, Index {Index}.", jobId, regionalUnitId, index);
}
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 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();
}
// Изменённая сигнатура: добавлен templateUnitIds
private async Task<string> GetNormalizedTemplateNameAsync(Job targetJob, Guid unitId, int? index = null, List<Guid>? templateUnitIds = null)
{
// Подготовка объекта TemplateForShortcodes для передачи в ShortcodesService
var templateForShortcodes = new TemplateForShortcodes
{
Id = Guid.Empty, // Не используется в подстановке, но нужен для структуры
Index = index,
JobId = targetJob.Id,
UnitId = unitId,
Job = new JobForShortcodes
{
Group = targetJob.Group != null ? new JobGroupForShortcodes
{
Id = targetJob.Group.Id,
GroupingUnitFieldId = targetJob.Group.GroupingUnitFieldId,
GroupType = targetJob.Group.GroupType != null ? new JobGroupTypeForShortcodes
{
Code = targetJob.Group.GroupType.Code
} : null,
GroupName = targetJob.Group.GroupName
} : null,
Tnk = targetJob.Tnk != null ? new TnkForShortcodes
{
Name = targetJob.Tnk.Name,
ShortName = targetJob.Tnk.ShortName ?? ""
} : null,
WorkName = targetJob.WorkName,
Name = targetJob.Name
},
// Преобразование List<Guid> в List<UnitInTemplateForShortcodes>
UnitsInTemplate = templateUnitIds?.Select(id => new UnitInTemplateForShortcodes { UnitId = id }).ToList() ?? new List<UnitInTemplateForShortcodes>()
};
var rawName = await shortcodesService.ApplyShortcodesAsync(targetJob.TemplateNameMask, templateForShortcodes);
return rawName.ToUpper();
}
private async Task<DateTimeOffset> GetNextRunAsync(Job targetJob, DateTimeOffset? currentNextRun = null)
{
var now = DateTimeOffset.UtcNow;
if (currentNextRun.HasValue && currentNextRun.Value > now)
{
return currentNextRun.Value;
}
var referenceDate = targetJob.Group?.ReferenceDate ?? now;
return await esppScheduleTransformService.GetNextDateAsync(targetJob.GroupId, referenceDate);
}
}