Files
parr_api/PARR.EsppSync/SyncService.cs

311 lines
18 KiB
C#
Raw Permalink 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.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;
}
}
}