Files
parr_api/PARR.TemplateUpdater/Services/TemplateUpdaterService.cs

344 lines
16 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.Extensions;
using PARR.Core.Repositories.Interfaces;
using PARR.Core.Repositories.Interfaces.JobRepositories;
using PARR.Core.Repositories.Interfaces.TemplateRepositories;
using PARR.Core.Repositories.Interfaces.Unit;
using PARR.Core.Services.NextRunServices;
using PARR.Domain.Common.Rabbit.Messages.TemplateMatching;
using PARR.Domain.Entities;
using PARR.Domain.Entities.JobEntities;
using PARR.Domain.Entities.TemplateEntities;
using PARR.Domain.Enums;
namespace PARR.TemplateUpdater.Services
{
internal class TemplateUpdaterService : ITemplateUpdaterService
{
private readonly ILogger<TemplateUpdaterService> logger;
private readonly ITemplateRepository templateService;
private readonly IJobRepository jobService;
private readonly IUnitRepository unitService;
private readonly IRobotConfigurationRepository robotConfigurationService;
private readonly INextRunService nextRunService;
private readonly IUnitInValueRepository unitInValueService;
private readonly ITemplateRenamePendingRepository _templateRenamePendingRepository;
public TemplateUpdaterService(
ILogger<TemplateUpdaterService> logger,
ITemplateRepository templateService,
IJobRepository jobService,
IUnitRepository unitService,
IRobotConfigurationRepository robotConfigurationService,
INextRunService nextRunService,
IUnitInValueRepository unitInValueService,
ITemplateRenamePendingRepository templateRenamePendingRepository
)
{
this.logger = logger;
this.templateService = templateService;
this.jobService = jobService;
this.unitService = unitService;
this.robotConfigurationService = robotConfigurationService;
this.nextRunService = nextRunService;
this.unitInValueService = unitInValueService;
_templateRenamePendingRepository = templateRenamePendingRepository;
}
public async Task UpdateTemplateAsync(TemplateUpdaterMessage query)
{
var isValid = await IsValidAsync(query);
if (!isValid)
{
logger.LogError("Не валидны входные данные. Конец обработки сообщения {Query}", query.ToJson());
return;
}
var template = await templateService.Get()
.Include(t => t.RobotConfigurations)
.Include(t => t.UnitsInTemplate)
//.AsSplitQuery()
.AsSingleQuery()
.FirstOrDefaultAsync(t => t.Id == query.TemplateId);
if (template == null)
{
logger.LogError("Не найден шаблон с id {TemplateId}", query.TemplateId);
return;
}
var templateIsChanged = false;
var scheduleIsChanged = false;
var trimmedNewName = query.Name.Trim();
if (template.Name != trimmedNewName)
{
var prepareOldNameResult = await PrepareOldTemplateNameAsync(template.Name, trimmedNewName, template);
if (!prepareOldNameResult)
return;
template.Name = trimmedNewName;
templateIsChanged = true;
scheduleIsChanged = true;
}
if (template.JobId != query.JobId)
{
template.JobId = query.JobId;
templateIsChanged = true;
scheduleIsChanged = true;
}
if (template.IsActiveTemplate != query.IsActiveTemplate)
{
template.IsActiveTemplate = query.IsActiveTemplate;
templateIsChanged = true;
}
if (template.IsActiveSchedule != query.IsActiveSchedule)
{
template.IsActiveSchedule = query.IsActiveSchedule;
scheduleIsChanged = true;
}
if (template.UnitId != query.UnitId)
{
template.UnitId = query.UnitId;
templateIsChanged = true;
scheduleIsChanged = true;
}
template.Index = query.Index;
template.StatusTypeId = query.StatusTypeId;
// === Обработка изменения состава UnitsInTemplate ===
// Формируем HashSet пар (UnitId, UnitFieldValueId) для быстрого сравнения
var currentEntries = template.UnitsInTemplate
.Select(u => new UnitInTemplateMessage { UnitId = u.UnitId, UnitFieldValueId = u.UnitFieldValueId })
.ToHashSet();
var newEntries = query.UnitsInTemplate.ToHashSet();
if (!currentEntries.SetEquals(newEntries))
{
// Удаляем связи, которых нет в новом наборе
var toRemove = template.UnitsInTemplate
.Where(u => !newEntries.Contains(new UnitInTemplateMessage { UnitId = u.UnitId, UnitFieldValueId = u.UnitFieldValueId }))
.ToList();
foreach (var item in toRemove)
template.UnitsInTemplate.Remove(item);
// Добавляем новые связи
var toAdd = newEntries.Except(currentEntries);
foreach (var entry in toAdd)
{
template.UnitsInTemplate.Add(new UnitsInTemplate
{
TemplateId = template.Id,
UnitId = entry.UnitId,
UnitFieldValueId = entry.UnitFieldValueId,
DateCreated = DateTimeOffset.UtcNow
});
}
templateIsChanged = true;
}
// === Конец обработки UnitsInTemplate ===
// Для правильного расчета shortcod'ов в БД должны быть сохранены данные UnitsInTemplate, JobId, UnitId и т.д.
if (templateIsChanged && !await templateService.CommitAsync(query.Initiator))
{
logger.LogError("Не удалось сохранить изменения в БД. {QueryType}: {Query}", nameof(TemplateUpdaterMessage), query.ToJson());
return;
}
var nextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, query.IsNew);
if (!nextRun.HasValue)
{
logger.LogError("Ошибка при расчете нового nextRun (вернулся null) для шаблона {TemplateId}, {TemplateName}", template.Id, template.Name);
return;
}
if (template.NextRun != nextRun.Value)
{
template.LastRun = template.NextRun;
template.NextRun = nextRun.Value;
scheduleIsChanged = true;
}
if (templateIsChanged)
{
var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.TemplateOrder, template);
robotConfigurationService.SetUpdateTaskStatusIfAllow(config);
logger.LogDebug("Для шаблона {TemplateId} устанавливаю статус {Status}", template.Id, RobotStatusEnum.Wait.ToString());
}
if (scheduleIsChanged)
{
var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.ScheduleOrder, template);
robotConfigurationService.SetUpdateTaskStatusIfAllow(config);
logger.LogDebug("Для расписания {TemplateId} устанавливаю статус {Status}", template.Id, RobotStatusEnum.Wait.ToString());
}
if (!await templateService.CommitAsync(query.Initiator))
{
logger.LogError("Не удалось сохранить изменения в БД. {QueryType}: {Query}", nameof(TemplateUpdaterMessage), query.ToJson());
return;
}
logger.LogInformation("Выполнено изменение шаблона в БД. Отправлен запрос на синхронизацию шаблона: {TemplateIsChanged}, расписания: {ScheduleIsChanged}. Query {Query}", templateIsChanged, scheduleIsChanged, query.ToJson());
}
/// <summary>
/// Валидация входящего сообщения
/// </summary>
/// <param name="query"></param>
/// <returns></returns>
private async Task<bool> IsValidAsync(TemplateUpdaterMessage query)
{
var template = await templateService.Get().AsNoTracking()
.Include(t => t.RobotConfigurations)
.FirstOrDefaultAsync(t => t.Id == query.TemplateId);
if (template == null)
{
logger.LogError("Сообщение не валидно. Не найден шаблон с id: {TemplateId}", query.TemplateId);
return false;
}
//if (template.ScheduleEsppId == null)
//{
// logger.LogError("Сообщение не валидно. Номер расписания не может быть null, робот не сможет обновить шаблон: {TemplateName}", template.Name);
// return false;
//}
var config = robotConfigurationService.GetFromTemplateByRobotCode(RobotsEnum.TemplateOrder, template);
if (config == null)
{
logger.LogError("Сообщение не валидно. Не создана конфигурация роботов: {TemplateName}", template.Name);
return false;
}
//if (config.TaskStatusCode != (int)TaskStatusEnum.Ok)
//{
// logger.LogError("Сообщение не валидно. Не закончено создание или предыдущее обновление - нельзя начинать новое изменение шаблона: {TemplateName}", template.Name);
// return false;
//}
if (string.IsNullOrEmpty(query.Name?.Trim()))
{
logger.LogError("Сообщение не валидно. Имя шаблона не может быть пустым: {TemplateName}", query.Name);
return false;
}
var existTemplateName = await templateService.Get().AsNoTracking().AnyAsync(t =>
t.Id != query.TemplateId
&& t.Name!.ToUpper() == query.Name.Trim().ToUpper()
&& t.Index == query.Index);
if (existTemplateName)
{
logger.LogError("Сообщение не валидно. Имя шаблона не уникально: {TemplateName}", query.Name);
return false;
}
var job = await jobService.Get().AsNoTracking().AnyAsync(t => t.Id == query.JobId);
if (!job)
{
logger.LogError("Сообщение не валидно. Не найдена работа с JobId: {JobId}", query.JobId);
return false;
}
var unit = await unitService.Get().AsNoTracking().AnyAsync(t => t.Id == query.UnitId);
if (!unit)
{
logger.LogError("Сообщение не валидно. Не найден unit с UnitId: {UnitId}", query.UnitId);
return false;
}
if (query.UnitsInTemplate != null && query.UnitsInTemplate.Any())
{
var pairsToCheck = query.UnitsInTemplate.Distinct().ToList();
var unitIdsToCheck = pairsToCheck.Select(p => p.UnitId).Distinct().ToList();
var valueIdsToCheck = pairsToCheck.Select(p => p.UnitFieldValueId).Distinct().ToList();
// Загружаем только те связи, которые относятся к проверяемым ID
var existingLinks = await unitInValueService.Get()
.AsNoTracking()
.Where(uiv => unitIdsToCheck.Contains(uiv.UnitId) && valueIdsToCheck.Contains(uiv.ValueId))
.Select(uiv => new { uiv.UnitId, uiv.ValueId })
.ToListAsync();
// Формируем HashSet существующих пар для быстрой проверки в памяти
var existingPairsSet = existingLinks
.Select(x => (x.UnitId, UnitFieldValueId: x.ValueId))
.ToHashSet();
// Проверяем, что каждая запрошенная пара реально существует в БД
var allPairsValid = pairsToCheck.All(p => existingPairsSet.Contains((p.UnitId, p.UnitFieldValueId)));
if (!allPairsValid)
{
logger.LogError("Сообщение не валидно. Не все пары (UnitId, UnitFieldValueId) из UnitsInTemplate существуют в UnitInValue.");
return false;
}
}
return true;
}
/// <summary>
/// Добавление записи в таблицу ожидания переименования
/// </summary>
/// <param name="oldName"></param>
/// <param name="newName"></param>
/// <param name="template"></param>
/// <returns></returns>
private async Task<bool> PrepareOldTemplateNameAsync(string oldName, string newName, Template template)
{
// Проверяем, не запущено ли уже переименование для этого шаблона
var alreadyPending = await _templateRenamePendingRepository.Get()
.AsNoTracking()
.FirstOrDefaultAsync(t => t.TemplateId == template.Id);
if (alreadyPending != null)
{
logger.LogError("При попытке переименования шаблона {TemplateId}, из '{OldName}' в '{NewName}', " +
"произошла ошибка, этот шаблон уже находится в процессе переименования (старое имя {PendingName})", template.Id, oldName, newName, alreadyPending.OldName);
return false;
}
// Уникально ли имя в таблице ожидания переименования
var existPendingOldName = await _templateRenamePendingRepository.Get()
.AsNoTracking()
.FirstOrDefaultAsync(t => t.OldName == oldName);
if (existPendingOldName != null)
{
logger.LogError("При добавлении старого имени в таблицу ожидания для шаблона {TemplateId} обнаружен конфликт: " +
"имя '{ExistOldName}' уже зарезервировано другим процессом для шаблона {ExistTemplateId}",
template.Id, existPendingOldName.OldName, existPendingOldName.TemplateId);
return false;
}
// Все нормально, добавляем запись в таблицу
var pendingRename = new TemplateRenamePending
{
TemplateId = template.Id,
DateCreated = DateTimeOffset.UtcNow,
OldName = oldName,
Template = template
};
var addResult = await _templateRenamePendingRepository.CreateAsync(pendingRename);
if (!addResult)
return false;
return true;
}
}
}