352 lines
18 KiB
C#
352 lines
18 KiB
C#
using Microsoft.EntityFrameworkCore;
|
||
using Microsoft.Extensions.DependencyInjection;
|
||
using Microsoft.Extensions.Logging;
|
||
using PARR.BLL.Helpers;
|
||
using PARR.Constants;
|
||
using PARR.DAL.Contracts;
|
||
using PARR.DAL.DomainServices.Shortcodes;
|
||
using PARR.DAL.Models;
|
||
using PARR.DAL.NextRunServices;
|
||
using PARR.DAL.Services.Interfaces;
|
||
using PARR.EsppSync.Domain;
|
||
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
|
||
)
|
||
{
|
||
this.logger = logger;
|
||
this.serviceProvider = serviceProvider;
|
||
}
|
||
|
||
|
||
public async Task SyncEsppObjectAsync(
|
||
string str,
|
||
ParserHandlerDelegate<EsppObject> parser,
|
||
ConvertDbObjToComparisonObjHandlerDelegate<EsppObject> converterToEsppObject
|
||
)
|
||
{
|
||
logger.LogDebug("Получил строку. Начинаю работать. Строка: {String}", str);
|
||
|
||
if (string.IsNullOrEmpty(str))
|
||
{
|
||
logger.LogWarning("Получил пустую строку, ничего не делаю.");
|
||
return;
|
||
}
|
||
|
||
var esppObject = parser.Invoke(str);
|
||
|
||
if (esppObject == null)
|
||
{
|
||
logger.LogWarning("После парсинга строки, esppObject = null. Дальше ничего не буду делать.");
|
||
return;
|
||
}
|
||
|
||
using (var scope = serviceProvider.CreateScope())
|
||
{
|
||
var templateService = GetServiceInScope<ITemplateService>(scope);
|
||
var robotConfigurationService = GetServiceInScope<IRobotConfigurationService>(scope);
|
||
var shortcodesService = GetServiceInScope<IShortcodesService>(scope);
|
||
var nextRunService = GetServiceInScope<INextRunServiceV2>(scope);
|
||
|
||
try
|
||
{
|
||
// Загружаем Template и TemplateForShortcodes в одном запросе
|
||
var query = templateService.Get()
|
||
//.AsNoTracking()
|
||
.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 = converterToEsppObject.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);
|
||
|
||
// если это сравнение расписаний, рассчитать nextRun, и сравнить все три nextRun, БД - ЕСПП - Расчитанное
|
||
// будем считать, только если НЕТ изменений в остальных полях, если изменения есть, зачем лишний раз напрягать, потом в контроллере все пересчитается
|
||
if (!isChanged && dbObjectInEsppObject is EsppObjectSchedule dbValues && esppObject is EsppObjectSchedule esppValues)
|
||
{
|
||
var isEqualNextRun = await IsEqualNextRunAsync(nextRunService, dbValues, esppValues, template.Id);
|
||
|
||
if (isEqualNextRun == false)
|
||
isChanged = true;
|
||
}
|
||
}
|
||
|
||
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);
|
||
|
||
if (!await templateService.CommitAsync())
|
||
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
|
||
{
|
||
// Если пред статус был 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())
|
||
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.TemplateName);
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/// <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="template"></param>
|
||
/// <param name="robotConfigurationService"></param>
|
||
/// <param name="robot"></param>
|
||
private void SetUpdateStatus(ref Template template, IRobotConfigurationService robotConfigurationService, RobotsEnum robot)
|
||
{
|
||
var robotConfig = robotConfigurationService.GetFromTemplateByRobotCode(robot, template);
|
||
robotConfigurationService.ChangeTaskStatus(TaskStatusEnum.Updating, robotConfig);
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Получить сервис из scope
|
||
/// </summary>
|
||
/// <typeparam name="Service"></typeparam>
|
||
/// <param name="scope"></param>
|
||
/// <returns></returns>
|
||
/// <exception cref="Exception"></exception>
|
||
private Service GetServiceInScope<Service>(IServiceScope scope)
|
||
{
|
||
var service = scope.ServiceProvider.GetService<Service>();
|
||
if (service == null)
|
||
throw new Exception($"Не найден сервис: {nameof(Service)}");
|
||
|
||
return service;
|
||
}
|
||
|
||
|
||
/// <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)
|
||
{
|
||
foreach (var prop in dbObj.GetType().GetProperties())
|
||
{
|
||
if (prop == null)
|
||
continue;
|
||
|
||
var dbValue = dbObj.GetType().GetProperty(prop.Name)?.GetValue(dbObj, null);
|
||
var esppValue = esppObj.GetType().GetProperty(prop.Name)?.GetValue(esppObj, null);
|
||
|
||
if (dbValue == null || esppValue == null)
|
||
continue;
|
||
|
||
//Replace("\r","").Replace("\n","") - в подробном описании могут быть переносы строк, в Rabbit прилетает без переносов. Убираем переносы для стравнения
|
||
var dbValueStr = Normalize(dbValue!.ToString()!);
|
||
var esppValueStr = Normalize(esppValue!.ToString()!);
|
||
|
||
if (dbValueStr != esppValueStr)
|
||
{
|
||
logger.LogInformation("Не совпадают поля ({PropertyName}). dbValueStr: {DbValueStr}, esppValueStr: {EsppValueStr}. Имя шаблона: {TemplateName}", prop.Name, dbValueStr, esppValueStr, templateName);
|
||
|
||
return true;
|
||
}
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Удаляет ненужные символы из строки
|
||
/// </summary>
|
||
/// <param name="str"></param>
|
||
/// <returns></returns>
|
||
private string Normalize(string str)
|
||
{
|
||
str = str.Replace("\r", string.Empty);
|
||
str = str.Replace("\n", string.Empty);
|
||
str = str.Replace(" ", string.Empty);
|
||
|
||
return str.ToLower();
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Рассчитать nextRun в часовом поясе УЗ робота
|
||
/// </summary>
|
||
/// <param name="nextRunService"></param>
|
||
/// <param name="templateId"></param>
|
||
/// <returns></returns>
|
||
private async Task<DateTimeOffset?> CalcNextRunAsync(INextRunServiceV2 nextRunService, Guid templateId)
|
||
{
|
||
var nextRun = await nextRunService.GetNextRunForTemplateAsync(templateId, isNew: false);
|
||
|
||
if (nextRun.HasValue)
|
||
{
|
||
var nextRunWithEsppAccountTz = nextRun.Value.Add(nextRunService.GetEsppAccountOffset());
|
||
logger.LogDebug("Расчитанный nextRun для шаблона {templateId}, UTC: {nextRun}, EsppAccountTz: {nextRunWithEsppAccountTz}", templateId, nextRun, nextRunWithEsppAccountTz);
|
||
|
||
return nextRunWithEsppAccountTz;
|
||
}
|
||
else
|
||
{
|
||
logger.LogError("При расчете nextRun для templateId: {templateId} верунлся null", templateId);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// Сравнить NextRun из БД, ЕСПП, рассчитанный
|
||
/// </summary>
|
||
/// <param name="dbValues"></param>
|
||
/// <param name="esppValues"></param>
|
||
/// <param name="templateId"></param>
|
||
/// <returns></returns>
|
||
private async Task<bool> IsEqualNextRunAsync(INextRunServiceV2 nextRunService, EsppObjectSchedule dbValues, EsppObjectSchedule esppValues, Guid templateId)
|
||
{
|
||
// в часовой зоне робота
|
||
var calculatedNextRun = await CalcNextRunAsync(nextRunService, templateId);
|
||
if (calculatedNextRun == null)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
var scheduledCalculated = EsppScheduleHelpers.GetNextRun(calculatedNextRun.Value);
|
||
var basisTimeCalculated = EsppScheduleHelpers.GetGenerationTime(calculatedNextRun.Value);
|
||
|
||
logger.LogDebug("Рассчитанные значения для шаблона '{templateName}', {templateId}, следующее срабатывание {scheduledCalculated}, время создания наряда: {basisTimeCalculated}",
|
||
dbValues.TemplateName, templateId, scheduledCalculated, basisTimeCalculated);
|
||
|
||
if (Normalize(dbValues.Scheduled) != Normalize(esppValues.Scheduled) || Normalize(dbValues.Scheduled) != scheduledCalculated || Normalize(esppValues.Scheduled) != scheduledCalculated)
|
||
{
|
||
logger.LogInformation("Не совпадают поля ({propName}), dbValueStr: {dbValueStr}, esppValueStr: {esppValueStr}, scheduledCalculated: {scheduledCalculated}. Имя шаблона: {templateName}",
|
||
nameof(dbValues.Scheduled), dbValues.Scheduled, esppValues.Scheduled, scheduledCalculated, dbValues.TemplateName);
|
||
return false;
|
||
}
|
||
|
||
if (Normalize(dbValues.BasisTime) != Normalize(esppValues.BasisTime) || Normalize(dbValues.BasisTime) != basisTimeCalculated || Normalize(esppValues.BasisTime) != basisTimeCalculated)
|
||
{
|
||
logger.LogInformation("Не совпадают поля ({propName}), dbValueStr: {dbValueStr}, esppValueStr: {esppValueStr}, basisTimeCalculated: {basisTimeCalculated}. Имя шаблона: {templateName}",
|
||
nameof(dbValues.BasisTime), dbValues.BasisTime, esppValues.BasisTime, basisTimeCalculated, dbValues.TemplateName);
|
||
return false;
|
||
}
|
||
|
||
logger.LogDebug("Значения nextRun в БД, ЕСПП, расчитанное, все совпадают. Scheduled: {scheduled}, basisTime: {basisTime}", scheduledCalculated, basisTimeCalculated);
|
||
|
||
return true;
|
||
}
|
||
|
||
}
|
||
} |