using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using PARR.Constants; using PARR.DAL.Contracts; using PARR.DAL.DomainServices.Shortcodes; using PARR.DAL.Models; using PARR.DAL.Services.Interfaces; using PARR.EsppSync.Helpers; using System.Reflection; namespace PARR.EsppSync { internal class SyncService : ISyncService where EsppObject : class, IEsppObject { private readonly ILogger> logger; private readonly IServiceProvider serviceProvider; public SyncService( ILogger> logger, IServiceProvider serviceProvider ) { this.logger = logger; this.serviceProvider = serviceProvider; } public async Task SyncEsppObjectAsync( string str, ParseStringToEsppObject parseStringToEsppObject, ConvertDbToEsppObject converterDbToEsppObject, CustomComparisionCheckAsync? customComparisionAsync = null, AfterParseStringToEsppObjectAsync? 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 = GetServiceInScope(scope); var robotConfigurationService = GetServiceInScope(scope); var shortcodesService = GetServiceInScope(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 = 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); 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); } } } /// /// Применяем шорткоды /// /// /// /// /// 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); } } } } /// /// Установить статус - Обновить /// /// /// /// private void SetUpdateStatus(ref Template template, IRobotConfigurationService robotConfigurationService, RobotsEnum robot) { var robotConfig = robotConfigurationService.GetFromTemplateByRobotCode(robot, template); robotConfigurationService.ChangeTaskStatus(TaskStatusEnum.Updating, robotConfig); } /// /// Получить сервис из scope /// /// /// /// /// private Service GetServiceInScope(IServiceScope scope) { var service = scope.ServiceProvider.GetService(); if (service == null) throw new Exception($"Не найден сервис: {nameof(Service)}"); return service; } /// /// Сравнение объектов /// /// /// /// /// 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 = EsppSyncHelpers.Normalize(dbValue!.ToString()); var esppValueStr = EsppSyncHelpers.Normalize(esppValue!.ToString()); if (dbValueStr != esppValueStr) { logger.LogInformation("Не совпадают поля ({PropertyName}). dbValueStr: {DbValueStr}, esppValueStr: {EsppValueStr}. Имя шаблона: {TemplateName}", prop.Name, dbValueStr, esppValueStr, templateName); return true; } } return false; } } }