Files
parr_api/PARR.EsppSync/SyncService.cs

311 lines
18 KiB
C#
Raw Normal View History

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PARR.Core.Repositories.Interfaces;
using PARR.Core.Repositories.Interfaces.TemplateRepositories;
using PARR.Core.Services.Shortcodes;
using PARR.Domain.Entities;
using PARR.Domain.Entities.Base.History;
using PARR.Domain.Enums;
using PARR.EsppSync.Attributes;
using PARR.EsppSync.Helpers;
using System.Reflection;
namespace PARR.EsppSync
{
internal class SyncService<EsppObject> : ISyncService<EsppObject> where EsppObject : class, IEsppObject
{
private readonly ILogger<SyncService<EsppObject>> _logger;
private readonly IServiceProvider _serviceProvider;
public SyncService(
ILogger<SyncService<EsppObject>> logger,
IServiceProvider serviceProvider
)
{
_logger = logger;
_serviceProvider = serviceProvider;
}
public async Task SyncEsppObjectAsync(
string str,
ParseStringToEsppObject<EsppObject> parseStringToEsppObject,
ConvertDbToEsppObject<EsppObject> converterDbToEsppObject,
CustomComparisionCheckAsync<EsppObject>? customComparisionAsync = null,
AfterParseStringToEsppObjectAsync<EsppObject>? afterParseStringToEsppObjectAsync = null
)
{
_logger.LogDebug("Получил строку. Начинаю работать. Строка: {String}", str);
if (string.IsNullOrEmpty(str))
{
_logger.LogWarning("Получил пустую строку, ничего не делаю.");
return;
}
var esppObject = parseStringToEsppObject.Invoke(str);
if (esppObject == null)
{
_logger.LogWarning("После парсинга строки, esppObject = null. Дальше ничего не буду делать.");
return;
}
// Вызовем кастомный метод, если он есть
if (afterParseStringToEsppObjectAsync != null)
await afterParseStringToEsppObjectAsync.Invoke(esppObject);
using (var scope = _serviceProvider.CreateScope())
{
var templateService = scope.ServiceProvider.GetRequiredService<ITemplateRepository>();
var robotConfigurationService = scope.ServiceProvider.GetRequiredService<IRobotConfigurationRepository>();
var shortcodesService = scope.ServiceProvider.GetRequiredService<IShortcodesService>();
var templateRenamePendingRepository = scope.ServiceProvider.GetRequiredService<ITemplateRenamePendingRepository>();
try
{
#region Проверка, идет ли переименование
// Если есть у этого шаблона запись в TemplateRenamePending, значит сейчас идет переименование, пропускаем синхронизацию этого шаблона
// Если имя этого шаблона находится в таблице TemplateRenamePending, значит идет переименование связанного шаблона, пропускаем синхронизацию этого шаблона
var isPartOfRename = await templateRenamePendingRepository.Get()
.AnyAsync(t => t.Template.Name == esppObject.TemplateName || t.OldName == esppObject.TemplateName);
if (isPartOfRename)
{
_logger.LogInformation("Пропускаем синхронизацию '{TemplateName}', так как шаблон ({EsppObject}) участвует в процессе переименования.", esppObject.TemplateName, nameof(EsppObject));
// Выходим сразу
return;
}
#endregion
// Загружаем Template и TemplateForShortcodes в одном запросе
var query = templateService.Get()
.Include(h => h.Unit)
.ThenInclude(t => t!.UnitValues)
.ThenInclude(t => t.Value)
.Include(h => h.Unit)
.ThenInclude(t => t!.UnitValues)
.ThenInclude(t => t.Field)
.Include(t => t.RobotConfigurations)
.Include(t => t.Job)
.ThenInclude(j => j.Group)
.ThenInclude(g => g.GroupType)
.Include(t => t.Job)
.ThenInclude(t => t.Group)
.ThenInclude(t => t.ScheduleExcludeType)
.Include(t => t.Job)
.ThenInclude(t => t.Group)
.ThenInclude(t => t.ScheduleExcludeTypeCalendar)
.Include(t => t.Job)
.ThenInclude(j => j.Tnk)
.ThenInclude(s => s!.Subprocess)
.ThenInclude(p => p!.Process)
.Include(t => t.UnitsInTemplate);
var template = await query.FirstOrDefaultAsync(t => t.Name == esppObject.TemplateName);
if (template == null)
{
_logger.LogWarning("Найден объект в ЕСПП с именем шаблона '{TemplateName}' незарегистрированный в ПАРР. Строка: {String}", esppObject.TemplateName, str);
return;
}
var dbObjectInEsppObject = converterDbToEsppObject.Invoke(template);
await ApplyShortcodesAsync(dbObjectInEsppObject, template, shortcodesService);
bool isChanged = false;
// если в БД isActive == false, то синхронизировать только по полям из IEsppObject
if (dbObjectInEsppObject.IsActive == false)
{
_logger.LogDebug("Объект деактивирован в ПАРР. Сравниваем только обязательные поля. {TemplateName}", esppObject.TemplateName);
var lightDbObj = new EsppLightObject(dbObjectInEsppObject);
var lightEsppObject = new EsppLightObject(esppObject);
isChanged = IsChanged(lightEsppObject, lightDbObj, esppObject.TemplateName);
}
else
{
_logger.LogDebug("Объект активирован в ПАРР. Сравниваем все поля. {TemplateName}", esppObject.TemplateName);
isChanged = IsChanged(esppObject, dbObjectInEsppObject, esppObject.TemplateName);
// выполняем кастомную дополнительную проверку (только если isChanged==false, чтоб лишний раз не гонять)
if (!isChanged)
{
if (customComparisionAsync != null)
{
// добавлена дополнительная проверка
var isCustomComparision = await customComparisionAsync.Invoke(esppObject, dbObjectInEsppObject, template.Id);
if (isCustomComparision)
{
_logger.LogDebug("Дополнительная проверка прошла.");
}
else
{
// если дополнительная проверка не прошла, то говорим что есть изменения
isChanged = true;
_logger.LogDebug("Дополнительная проверка не прошла, ставим статус isChanged: {isChanged}", isChanged);
}
}
else
{
_logger.LogDebug("Дополнительная проверка отсутствует");
}
}
}
if (isChanged)
{
_logger.LogDebug("Есть изменения, требуется обновление. {TemplateName}", esppObject.TemplateName);
var config = robotConfigurationService.GetFromTemplateByRobotCode(esppObject.Robot, template);
// Если предыдущий статус был Create или Ок, то ставим ему Update
// пусть даже Create завершился с ошибками, но раз он уже есть в ЕСПП, то изменим на Update, сбросим все счетчики и пусть попробует обновить и исправить все
if (config.TaskStatusCode != (int)TaskStatusEnum.Updating)
{
//SetUpdateStatus(ref template, robotConfigurationService, esppObject.Robot);
var isChangedStatus = robotConfigurationService.SetUpdateTaskStatusIfAllow(config);
if (isChangedStatus)
{
if (!await templateService.CommitAsync(GetInitiator()))
_logger.LogError("Не удалось изменить запись Template {TemplateName}, Robot: {Robot}", template.Name, esppObject.Robot);
else
_logger.LogInformation("Установлен принудительный статус {TaskStatus}, Template {templateName}, Robot: {Robot}", TaskStatusEnum.Updating, template.Name, esppObject.Robot);
}
else
{
_logger.LogInformation("Нельзя установить статус Updating для шаблона {templateName}, так как текущий статус это запрещает.", template.Name);
}
}
else
{
// Если пред статус был Update, то ничего не делаем, так его и оставляем, не сбрасывам кол-во попыток и ошибок
_logger.LogInformation("Есть изменения в Template {TemplateName}, но предыдущий статус TaskStatusCode: {TaskStatusCode}. Не меняем статус, будем разбираться вручную.", template.Name, (TaskStatusEnum)config.TaskStatusCode);
}
}//надо ли проверять если не изменился, но был статус Updating не понятно. Доверяем роботу пока, что после окончания работ он точно сообщит
else
{
_logger.LogDebug("Нет изменений, обновление не требуется. {TemplateName}", esppObject.TemplateName);
//если все поля совпали
//проверяем, какой был статус предыдущий статус в БД, если он был не Ок, то ставим ему ОК
var robotConfig = robotConfigurationService.GetFromTemplateByRobotCode(esppObject.Robot, template);
if (robotConfig.TaskStatusCode != (int)TaskStatusEnum.Ok)
{
robotConfigurationService.ChangeTaskStatus(TaskStatusEnum.Ok, robotConfig);
if (!await templateService.CommitAsync(GetInitiator()))
_logger.LogError("Не удалось изменить запись Template {TemplateName}, Robot: {Robot}", template.Name, esppObject.Robot);
else
_logger.LogInformation("Установлен принудительный статус {TaskStatus}, Template {TemplateName}, Robot: {Robot}", TaskStatusEnum.Ok, template.Name, esppObject.Robot);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Ошибка синхронизации объекта АСУ ЕСПП {TemplateName}, {EsppObject}", esppObject.TemplateName, nameof(EsppObject));
}
}
}
/// <summary>
/// Получить инициатора изменений для сохранения в БД
/// </summary>
/// <returns></returns>
private IHistoryInitiator GetInitiator()
{
return new HistoryInitiator
{
InitiatorComment = "Обновление статуса синхронизации, SyncService.",
InitiatorParrComponentId = typeof(EsppObject).Name.Contains("Template") ? ParrComponentsEnum.EsppTemplateSync : ParrComponentsEnum.EsppScheduleSync
};
}
/// <summary>
/// Применяем шорткоды
/// </summary>
/// <param name="dbObjectInEsppObject"></param>
/// <param name="template"></param>
/// <param name="shortcodesService"></param>
/// <returns></returns>
private async Task ApplyShortcodesAsync(EsppObject dbObjectInEsppObject, Template template, IShortcodesService shortcodesService)
{
// Проверяем наличие Shortcode в полях объекта из БД
var properties = dbObjectInEsppObject.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
if (property.PropertyType == typeof(string))
{
var value = property.GetValue(dbObjectInEsppObject)?.ToString();
if (!string.IsNullOrEmpty(value))
{
// Передаём исходный template — он уже загружен с Include
var processedValue = await shortcodesService.ApplyShortcodesAsync(value, template);
property.SetValue(dbObjectInEsppObject, processedValue);
}
}
}
}
/// <summary>
/// Сравнение объектов
/// </summary>
/// <param name="esppObj"></param>
/// <param name="dbObj"></param>
/// <param name="templateName"></param>
/// <returns></returns>
private bool IsChanged(object esppObj, object dbObj, string templateName)
{
var dbType = dbObj.GetType();
var esppType = esppObj.GetType();
foreach (var dbProp in dbType.GetProperties())
{
// Пропускаем свойства, помеченные атрибутом SkipComparison
if (Attribute.IsDefined(dbProp, typeof(SkipComparisonAttribute)))
{
_logger.LogDebug("Пропущено сравнение поля {PropertyName} (помечено [SkipComparison]). Шаблон: {TemplateName}", dbProp.Name, templateName);
continue;
}
// Значение из объекта БД
//var dbValue = dbObj.GetType().GetProperty(prop.Name)?.GetValue(dbObj, null);
var dbValue = dbProp.GetValue(dbObj);
// Ищем аналогичное свойство в объекте из ЕСПП по имени
//var esppValue = esppObj.GetType().GetProperty(dbProp.Name)?.GetValue(esppObj, null);
var esppProp = esppType.GetProperty(dbProp.Name);
var esppValue = esppProp?.GetValue(esppObj);
// Replace("\r","").Replace("\n","") - в подробном описании могут быть переносы строк, в Rabbit прилетает без переносов. Убираем переносы для стравнения
// Если значение null, хелпер Normalize вернет string.Empty, что предотвратит NRE и ложные срабатывания.
var dbValueStr = EsppSyncHelpers.Normalize(dbValue?.ToString());
var esppValueStr = EsppSyncHelpers.Normalize(esppValue?.ToString());
if (dbValueStr != esppValueStr)
{
_logger.LogInformation("Не совпадают поля ({PropertyName}). dbValueStr: {DbValueStr}, esppValueStr: {EsppValueStr}. Имя шаблона: {TemplateName}", dbProp.Name, dbValueStr, esppValueStr, templateName);
return true;
}
}
return false;
}
}
}