2025-12-23 18:27:31 +10:00
using Microsoft.EntityFrameworkCore ;
using Microsoft.Extensions.DependencyInjection ;
2023-11-22 11:37:22 +10:00
using Microsoft.Extensions.Logging ;
2026-04-30 10:35:31 +10:00
using PARR.Core.Repositories.Interfaces ;
2026-05-04 11:55:07 +10:00
using PARR.Core.Services.Shortcodes ;
2026-04-30 10:35:31 +10:00
using PARR.Domain.Entities ;
2026-04-13 16:58:30 +10:00
using PARR.Domain.Entities.Base.History ;
using PARR.Domain.Enums ;
2026-03-03 10:19:59 +10:00
using PARR.EsppSync.Attributes ;
2026-02-18 11:40:25 +10:00
using PARR.EsppSync.Helpers ;
2025-08-06 10:33:55 +10:00
using System.Reflection ;
2023-11-22 11:37:22 +10:00
namespace PARR.EsppSync
{
internal class SyncService < EsppObject > : ISyncService < EsppObject > where EsppObject : class , IEsppObject
{
private readonly ILogger < SyncService < EsppObject > > logger ;
2025-08-20 14:10:22 +10:00
private readonly IServiceProvider serviceProvider ;
2023-11-22 11:37:22 +10:00
public SyncService (
ILogger < SyncService < EsppObject > > logger ,
2025-08-20 14:10:22 +10:00
IServiceProvider serviceProvider
2023-11-22 11:37:22 +10:00
)
{
this . logger = logger ;
2025-08-20 14:10:22 +10:00
this . serviceProvider = serviceProvider ;
2023-11-22 11:37:22 +10:00
}
public async Task SyncEsppObjectAsync (
string str ,
2026-02-18 11:40:25 +10:00
ParseStringToEsppObject < EsppObject > parseStringToEsppObject ,
ConvertDbToEsppObject < EsppObject > converterDbToEsppObject ,
CustomComparisionCheckAsync < EsppObject > ? customComparisionAsync = null ,
AfterParseStringToEsppObjectAsync < EsppObject > ? afterParseStringToEsppObjectAsync = null
2023-11-22 11:37:22 +10:00
)
{
2026-01-26 17:16:56 +10:00
logger . LogDebug ( "Получил строку. Начинаю работать. Строка: {String}" , str ) ;
2023-11-22 11:37:22 +10:00
if ( string . IsNullOrEmpty ( str ) )
{
logger . LogWarning ( "Получил пустую строку, ничего не делаю." ) ;
return ;
}
2026-02-18 11:40:25 +10:00
var esppObject = parseStringToEsppObject . Invoke ( str ) ;
2023-11-22 11:37:22 +10:00
if ( esppObject = = null )
{
logger . LogWarning ( "После парсинга строки, esppObject = null. Дальше ничего не буду делать." ) ;
return ;
}
2026-02-18 11:40:25 +10:00
// Вызовем кастомный метод, если он есть
if ( afterParseStringToEsppObjectAsync ! = null )
await afterParseStringToEsppObjectAsync . Invoke ( esppObject ) ;
2025-08-20 14:10:22 +10:00
using ( var scope = serviceProvider . CreateScope ( ) )
2023-11-22 11:37:22 +10:00
{
2026-04-30 10:35:31 +10:00
var templateService = GetServiceInScope < ITemplateRepository > ( scope ) ;
var robotConfigurationService = GetServiceInScope < IRobotConfigurationRepository > ( scope ) ;
2025-08-20 14:10:22 +10:00
var shortcodesService = GetServiceInScope < IShortcodesService > ( scope ) ;
2023-11-22 11:37:22 +10:00
2025-08-20 14:10:22 +10:00
try
2023-11-22 11:37:22 +10:00
{
2025-12-23 18:27:31 +10:00
// Загружаем Template и TemplateForShortcodes в одном запросе
var query = templateService . Get ( )
2026-02-16 16:54:06 +10:00
//.AsNoTracking()
2025-12-29 19:21:51 +10:00
. Include ( h = > h . Unit )
. ThenInclude ( t = > t ! . UnitValues )
2026-01-13 14:36:07 +10:00
. ThenInclude ( t = > t . Value )
2025-12-29 19:21:51 +10:00
. Include ( h = > h . Unit )
. ThenInclude ( t = > t ! . UnitValues )
2026-01-13 14:36:07 +10:00
. ThenInclude ( t = > t . Field )
2025-12-29 19:21:51 +10:00
. Include ( t = > t . RobotConfigurations )
2025-12-23 18:27:31 +10:00
. Include ( t = > t . Job )
. ThenInclude ( j = > j . Group )
. ThenInclude ( g = > g . GroupType )
2025-12-25 17:01:42 +10:00
. Include ( t = > t . Job )
. ThenInclude ( t = > t . Group )
. ThenInclude ( t = > t . ScheduleExcludeType )
. Include ( t = > t . Job )
. ThenInclude ( t = > t . Group )
. ThenInclude ( t = > t . ScheduleExcludeTypeCalendar )
2025-12-23 18:27:31 +10:00
. Include ( t = > t . Job )
. ThenInclude ( j = > j . Tnk )
2025-12-29 19:21:51 +10:00
. ThenInclude ( s = > s ! . Subprocess )
. ThenInclude ( p = > p ! . Process )
2025-12-23 18:27:31 +10:00
. Include ( t = > t . UnitsInTemplate ) ;
var template = await query . FirstOrDefaultAsync ( t = > t . Name = = esppObject . TemplateName ) ;
2025-08-06 10:33:55 +10:00
2025-08-20 14:10:22 +10:00
if ( template = = null )
2025-08-06 10:33:55 +10:00
{
2026-02-17 16:27:10 +10:00
logger . LogWarning ( "Найден объект в ЕСПП с именем шаблона '{TemplateName}' незарегистрированный в ПАРР. Строка: {String}" , esppObject . TemplateName , str ) ;
2025-08-20 14:10:22 +10:00
return ;
2025-08-06 10:33:55 +10:00
}
2025-12-23 18:27:31 +10:00
2026-02-18 11:40:25 +10:00
var dbObjectInEsppObject = converterDbToEsppObject . Invoke ( template ) ;
2024-02-28 10:17:01 +10:00
2026-02-16 16:54:06 +10:00
await ApplyShortcodesAsync ( dbObjectInEsppObject , template , shortcodesService ) ;
2025-12-23 18:27:31 +10:00
bool isChanged = false ;
2023-11-22 11:37:22 +10:00
2025-12-23 18:27:31 +10:00
// если в БД isActive == false, то синхронизировать только по полям из IEsppObject
if ( dbObjectInEsppObject . IsActive = = false )
{
2026-01-26 17:16:56 +10:00
logger . LogDebug ( "Объект деактивирован в ПАРР. Сравниваем только обязательные поля. {TemplateName}" , esppObject . TemplateName ) ;
2025-12-23 18:27:31 +10:00
var lightDbObj = new EsppLightObject ( dbObjectInEsppObject ) ;
var lightEsppObject = new EsppLightObject ( esppObject ) ;
2023-11-24 16:52:56 +10:00
2025-12-23 18:27:31 +10:00
isChanged = IsChanged ( lightEsppObject , lightDbObj , esppObject . TemplateName ) ;
}
else
{
2026-01-26 17:16:56 +10:00
logger . LogDebug ( "Объект активирован в ПАРР. Сравниваем все поля. {TemplateName}" , esppObject . TemplateName ) ;
2025-12-23 18:27:31 +10:00
isChanged = IsChanged ( esppObject , dbObjectInEsppObject , esppObject . TemplateName ) ;
2024-08-02 09:38:05 +10:00
2026-02-18 11:40:25 +10:00
// выполняем кастомную дополнительную проверку (только если isChanged==false, чтоб лишний раз не гонять)
if ( ! isChanged )
2026-02-17 16:27:10 +10:00
{
2026-02-18 11:40:25 +10:00
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 ( "Дополнительная проверка отсутствует" ) ;
}
2026-02-17 16:27:10 +10:00
}
}
2026-02-16 16:54:06 +10:00
2025-12-23 18:27:31 +10:00
if ( isChanged )
{
2026-01-26 17:16:56 +10:00
logger . LogDebug ( "Есть изменения, требуется обновление. {TemplateName}" , esppObject . TemplateName ) ;
2025-12-23 18:27:31 +10:00
var config = robotConfigurationService . GetFromTemplateByRobotCode ( esppObject . Robot , template ) ;
// Если предыдущий статус был Create или Ок, то ставим ему Update
// пусть даже Create завершился с ошибками, но раз он уже есть в ЕСПП, то изменим на Update, сбросим все счетчики и пусть попробует обновить и исправить все
if ( config . TaskStatusCode ! = ( int ) TaskStatusEnum . Updating )
2024-08-02 09:38:05 +10:00
{
2026-03-11 12:10:11 +10:00
//SetUpdateStatus(ref template, robotConfigurationService, esppObject.Robot);
2024-08-02 09:38:05 +10:00
2026-03-11 12:10:11 +10:00
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 ) ;
}
2025-12-23 18:27:31 +10:00
else
2026-03-11 12:10:11 +10:00
{
logger . LogInformation ( "Нельзя установить статус Updating для шаблона {templateName}, так как текущий статус это запрещает." , template . Name ) ;
}
2024-08-02 09:38:05 +10:00
}
2023-11-22 11:37:22 +10:00
else
2024-08-02 09:38:05 +10:00
{
2025-12-23 18:27:31 +10:00
// Если пред статус был Update, то ничего не делаем, так е г о и оставляем, не сбрасывам кол-во попыток и ошибок
2026-01-26 17:16:56 +10:00
logger . LogInformation ( "Есть изменения в Template {TemplateName}, но предыдущий статус TaskStatusCode: {TaskStatusCode}. Н е меняем статус, будем разбираться вручную." , template . Name , ( TaskStatusEnum ) config . TaskStatusCode ) ;
2024-08-02 09:38:05 +10:00
}
2025-12-23 18:27:31 +10:00
} //надо ли проверять если не изменился, но был статус Updating не понятно. Доверяем роботу пока, что после окончания работ он точно сообщит
else
{
2026-01-26 17:16:56 +10:00
logger . LogDebug ( "Нет изменений, обновление не требуется. {TemplateName}" , esppObject . TemplateName ) ;
2025-08-20 14:10:22 +10:00
2025-12-23 18:27:31 +10:00
//если все поля совпали
//проверяем, какой был статус предыдущий статус в БД, если он был не Ок, то ставим ему О К
var robotConfig = robotConfigurationService . GetFromTemplateByRobotCode ( esppObject . Robot , template ) ;
if ( robotConfig . TaskStatusCode ! = ( int ) TaskStatusEnum . Ok )
2025-08-20 14:10:22 +10:00
{
2025-12-23 18:27:31 +10:00
robotConfigurationService . ChangeTaskStatus ( TaskStatusEnum . Ok , robotConfig ) ;
2026-03-11 12:10:11 +10:00
if ( ! await templateService . CommitAsync ( GetInitiator ( ) ) )
2026-01-26 17:16:56 +10:00
logger . LogError ( "Н е удалось изменить запись Template {TemplateName}, Robot: {Robot}" , template . Name , esppObject . Robot ) ;
2025-12-23 18:27:31 +10:00
else
2026-01-26 17:16:56 +10:00
logger . LogInformation ( "Установлен принудительный статус {TaskStatus}, Template {TemplateName}, Robot: {Robot}" , TaskStatusEnum . Ok , template . Name , esppObject . Robot ) ;
2023-11-22 11:37:22 +10:00
}
}
}
2025-08-20 14:10:22 +10:00
catch ( Exception ex )
{
2026-01-26 17:16:56 +10:00
logger . LogError ( ex , "Ошибка синхронизации объекта А С У ЕСПП {TemplateName}" , esppObject . TemplateName ) ;
2025-08-20 14:10:22 +10:00
}
2025-08-06 10:33:55 +10:00
}
2023-11-22 11:37:22 +10:00
}
2026-02-17 16:27:10 +10:00
2026-03-11 12:10:11 +10:00
/// <summary>
/// Получить инициатора изменений для сохранения в БД
/// </summary>
/// <returns></returns>
private IHistoryInitiator GetInitiator ( )
{
return new HistoryInitiator
{
InitiatorComment = "Обновление статуса синхронизации, SyncService." ,
InitiatorParrComponentId = typeof ( EsppObject ) . Name . Contains ( "Template" ) ? ParrComponentsEnum . EsppTemplateSync : ParrComponentsEnum . EsppScheduleSync
} ;
}
2026-02-16 16:54:06 +10:00
/// <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 ) ;
}
}
}
}
2023-11-22 11:37:22 +10:00
2026-02-17 16:27:10 +10:00
2026-03-11 12:10:11 +10:00
///// <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);
//}
2023-11-22 11:37:22 +10:00
2026-02-17 16:27:10 +10:00
/// <summary>
/// Получить сервис из scope
/// </summary>
/// <typeparam name="Service"></typeparam>
/// <param name="scope"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
2023-11-22 11:37:22 +10:00
private Service GetServiceInScope < Service > ( IServiceScope scope )
{
var service = scope . ServiceProvider . GetService < Service > ( ) ;
if ( service = = null )
throw new Exception ( $"Н е найден сервис: {nameof(Service)}" ) ;
return service ;
}
2026-02-17 16:27:10 +10:00
/// <summary>
/// Сравнение объектов
/// </summary>
/// <param name="esppObj"></param>
/// <param name="dbObj"></param>
/// <param name="templateName"></param>
/// <returns></returns>
2025-12-04 09:26:17 +10:00
private bool IsChanged ( object esppObj , object dbObj , string templateName )
2023-11-22 11:37:22 +10:00
{
foreach ( var prop in dbObj . GetType ( ) . GetProperties ( ) )
{
2026-03-03 10:19:59 +10:00
// Пропускаем свойства, помеченные атрибутом SkipComparison
if ( Attribute . IsDefined ( prop , typeof ( SkipComparisonAttribute ) ) )
{
logger . LogDebug ( "Пропущено сравнение поля {PropertyName} (помечено [SkipComparison]). Шаблон: {TemplateName}" , prop . Name , templateName ) ;
continue ;
}
2023-11-22 11:37:22 +10:00
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 прилетает без переносов. Убираем переносы для стравнения
2026-02-18 11:40:25 +10:00
var dbValueStr = EsppSyncHelpers . Normalize ( dbValue ! . ToString ( ) ) ;
var esppValueStr = EsppSyncHelpers . Normalize ( esppValue ! . ToString ( ) ) ;
2023-11-22 11:37:22 +10:00
if ( dbValueStr ! = esppValueStr )
{
2026-01-26 17:16:56 +10:00
logger . LogInformation ( "Н е совпадают поля ({PropertyName}). dbValueStr: {DbValueStr}, esppValueStr: {EsppValueStr}. Имя шаблона: {TemplateName}" , prop . Name , dbValueStr , esppValueStr , templateName ) ;
2023-11-22 11:37:22 +10:00
return true ;
}
}
return false ;
}
2025-11-28 09:12:26 +10:00
2023-11-22 11:37:22 +10:00
}
2026-01-26 17:16:56 +10:00
}