feat(api,dal,esppSync,esppTemplateSync,esppScheduleSync): Реализован сервис ShortcodesService подменяющий динамические составляющие в полях шаблонов и расписаний. В API контроллер выдачи заданий роботу теперь выдаёт значение полй в новом виде, реализуя Shortcodes.

This commit is contained in:
Mikhail Kuznetsov
2025-08-06 10:33:55 +10:00
parent 46a96c666e
commit f351a8863a
11 changed files with 106 additions and 58 deletions

View File

@@ -11,6 +11,7 @@ using PARR.API.Services.Interfaces;
using PARR.Common.Domain;
using PARR.Constants;
using PARR.DAL.Contracts;
using PARR.DAL.DomainServices;
using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces;
using PARR.DAL.TransformServices;
@@ -27,6 +28,7 @@ namespace PARR.API.Controllers.V1
private readonly ILogger<RobotTaskController> logger;
private readonly IClientService clientService;
private readonly IRobotHistoryService robotHistoryService;
private readonly IShortcodesService shortcodesService;
public RobotTaskController(
IMapper mapper,
@@ -35,7 +37,8 @@ namespace PARR.API.Controllers.V1
IEsppScheduleTransformService esppScheduleTransformService,
ILogger<RobotTaskController> logger,
IClientService clientService,
IRobotHistoryService robotHistoryService
IRobotHistoryService robotHistoryService,
IShortcodesService shortcodesService
)
{
this.mapper = mapper;
@@ -45,6 +48,7 @@ namespace PARR.API.Controllers.V1
this.logger = logger;
this.clientService = clientService;
this.robotHistoryService = robotHistoryService;
this.shortcodesService = shortcodesService;
}
@@ -158,15 +162,29 @@ namespace PARR.API.Controllers.V1
{
case RobotsEnum.TemplateOrder:
//RobotTaskTemplateResponse
return Ok(new Response<RobotTaskTemplateResponse>(mapper.Map<RobotTaskTemplateResponse>(task), true));
var robotTaskTemplateResponse = mapper.Map<RobotTaskTemplateResponse>(task);
//TODO Вынести в отдельный метод ShortcodesService
if (shortcodesService.isAnyShortcodes(robotTaskTemplateResponse.FullDescription))
robotTaskTemplateResponse.FullDescription = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.FullDescription, task!.Template!.UnitId, task!.Template!.JobId);
if (shortcodesService.isAnyShortcodes(robotTaskTemplateResponse.ShortDescription))
robotTaskTemplateResponse.ShortDescription = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.ShortDescription, task!.Template!.UnitId, task!.Template!.JobId);
if (shortcodesService.isAnyShortcodes(robotTaskTemplateResponse.Solution))
robotTaskTemplateResponse.Solution = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.Solution, task!.Template!.UnitId, task!.Template!.JobId);
if (shortcodesService.isAnyShortcodes(robotTaskTemplateResponse.TnkName))
robotTaskTemplateResponse.TnkName = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.TnkName, task!.Template!.UnitId, task!.Template!.JobId);
if (shortcodesService.isAnyShortcodes(robotTaskTemplateResponse.WorkName))
robotTaskTemplateResponse.WorkName = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.WorkName, task!.Template!.UnitId, task!.Template!.JobId);
return Ok(new Response<RobotTaskTemplateResponse>(robotTaskTemplateResponse, true));
case RobotsEnum.ScheduleOrder:
// если был запрос на расписание, проверяем у него nextRun, lastRun, обновляем их
await UpdateLastNextRunDate(task);
//RobotTaskScheduleResponse
var response = mapper.Map<RobotTaskScheduleResponse>(task);
var robotTaskScheduleResponse = mapper.Map<RobotTaskScheduleResponse>(task);
return Ok(new Response<RobotTaskScheduleResponse>(response, true));
return Ok(new Response<RobotTaskScheduleResponse>(robotTaskScheduleResponse, true));
default:
break;
}

View File

@@ -160,7 +160,7 @@ namespace PARR.API.MappingProfiles
.ForMember(d => d.Solution, o => o.MapFrom(s => s.Template!.Job!.Group!.Solution))
//.ForMember(d => d.ResponseArea, o => o.MapFrom(s => s.Template!.Host!.ResponseArea!.Name))
//ЗО берем у группы а не у хоста
.ForMember(d => d.ResponseArea, o => o.MapFrom(s => s.Template!.Unit.BaseFields!.ResponseArea))
.ForMember(d => d.ResponseArea, o => o.MapFrom(s => s.Template!.Unit!.BaseFields!.ResponseArea))
.ForMember(d => d.TemplateDuration, o => o.MapFrom(s => s.Template!.Job!.Group!.TemplateDuration))
.ForMember(d => d.Initiator, o => o.MapFrom<RobotTaskTemplateInitiatorResolver>())
.ForMember(d => d.Category, o => o.MapFrom<RobotTaskTemplateCategoryResolver>())

View File

@@ -9,5 +9,7 @@ namespace PARR.DAL.DomainServices
public interface IShortcodesService
{
Task<string> ApplyShortcodesAsync(string str, Guid unitId, Guid jobId);
bool isAnyShortcodes(string str);
}
}

View File

@@ -1,20 +1,14 @@
using Microsoft.EntityFrameworkCore;
using PARR.DAL.Contracts;
using PARR.DAL.Services.Implementations.Job;
using PARR.DAL.Services.Implementations.Unit;
using PARR.DAL.Services.Interfaces.Job;
using PARR.DAL.Services.Interfaces.Unit;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace PARR.DAL.DomainServices
{
internal class ShortcodesService : IShortcodesService
{
private const string shortcodePattern = "%[^%\\s]+%";
private readonly SettingsFromDb settingsFromDb;
private readonly IJobService jobService;
private readonly IUnitService unitService;
@@ -41,7 +35,7 @@ namespace PARR.DAL.DomainServices
if (job != null && unit != null && job.TemplateNameMask != null)
{
var resultName = job.TemplateNameMask;
var resultName = str;
var shortcodesInMask = GetShortCodes(resultName);
@@ -72,6 +66,12 @@ namespace PARR.DAL.DomainServices
}
public bool isAnyShortcodes(string str)
{
return Regex.IsMatch(str, shortcodePattern);
}
private async Task<string> ReplaceFieldValues(Guid unitId, string resultName, List<Match> shortcodesInMask)
{
var unitWithFields = await unitService.Get()
@@ -120,7 +120,6 @@ namespace PARR.DAL.DomainServices
private static List<Match> GetShortCodes(string resultName)
{
var shortcodePattern = "%[^%\\s]+%";
var shortcodesInMask = Regex.Matches(resultName, shortcodePattern).ToList();
return shortcodesInMask;
}

View File

@@ -35,6 +35,11 @@ namespace PARR.DAL.Services.Implementations
{
return 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(a => a.Job)
.ThenInclude(t => t!.Tnk)
.ThenInclude(s => s!.Subprocess)
@@ -42,6 +47,7 @@ namespace PARR.DAL.Services.Implementations
.Include(t => t.Job)
.ThenInclude(t => t!.Group);
//return Get()
// .Include(h => h.Host)
// .ThenInclude(t => t!.ResponseArea)

View File

@@ -78,7 +78,7 @@ namespace PARR.EsppScheduleSync
/// <param name="template"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private async Task<EsppObjectSchedule> ConvertDbObjToEsppObj(Template template)
private EsppObjectSchedule ConvertDbObjToEsppObj(Template template)
{
var esppObjectFromDb = new EsppObjectSchedule
{
@@ -102,7 +102,6 @@ namespace PARR.EsppScheduleSync
};
FillScheduleFromDb(template, ref esppObjectFromDb);
await Task.Delay(1);//TODO Нет парни нет!!!
return ClearOptionalFields(esppObjectFromDb);
}

View File

@@ -16,7 +16,7 @@ namespace PARR.EsppSync
/// <typeparam name="EsppObject"></typeparam>
/// <param name="templateName"></param>
/// <returns></returns>
public delegate Task<EsppObject> ConvertDbObjToComparisonObjHandlerDelegate<EsppObject>(Template template) where EsppObject : class, IEsppObject;
public delegate EsppObject ConvertDbObjToComparisonObjHandlerDelegate<EsppObject>(Template template) where EsppObject : class, IEsppObject;
public interface ISyncService<EsppObject> where EsppObject : class, IEsppObject
{

View File

@@ -2,23 +2,31 @@
using Microsoft.Extensions.Logging;
using PARR.Constants;
using PARR.DAL.Contracts;
using PARR.DAL.DomainServices;
using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces;
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;
private readonly ITemplateService templateService;
private readonly IRobotConfigurationService robotConfigurationService;
private readonly IShortcodesService shortcodesService;
public SyncService(
ILogger<SyncService<EsppObject>> logger,
IServiceProvider serviceProvider
ITemplateService templateService,
IRobotConfigurationService robotConfigurationService,
IShortcodesService shortcodesService
)
{
this.logger = logger;
this.serviceProvider = serviceProvider;
this.templateService = templateService;
this.robotConfigurationService = robotConfigurationService;
this.shortcodesService = shortcodesService;
}
@@ -44,11 +52,8 @@ namespace PARR.EsppSync
return;
}
using (var scope = serviceProvider.CreateScope())
try
{
var templateService = GetServiceInScope<ITemplateService>(scope);
var robotConfigurationService = GetServiceInScope<IRobotConfigurationService>(scope);
var template = await templateService.GetTemplateByNameAsync(esppObject.TemplateName);
//todo: существует в ЕСПП но отсутствует в ПАРР. Может его деактивировать или еще что-то сделать. Пока просто пропустим
@@ -62,26 +67,36 @@ namespace PARR.EsppSync
{
var dbObjectInEsppObject = converterToEsppObject.Invoke(template);
//Проверяем наличие Shortcode в полях объекта из БД
var properties = dbObjectInEsppObject.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
var value = property.GetValue(dbObjectInEsppObject)?.ToString();
if (value != null && shortcodesService.isAnyShortcodes(value))
property.SetValue(dbObjectInEsppObject, await shortcodesService.ApplyShortcodesAsync(value, template.UnitId, template.JobId));
}
bool isChanged = false;
//TODO: FIX ME Please, BRO
//bool isChanged;
// если в БД isActive == false, то синхронизировать только по полям из IEsppObject
//if (dbObjectInEsppObject.IsActive == false)
//{
// logger.LogDebug($"Объект деактивирован в ПАРР. Сравниваем только обязательные поля. {esppObject.TemplateName}");
// var lightDbObj = new EsppLightObject(dbObjectInEsppObject);
// var lightEsppObject = new EsppLightObject(esppObject);
// isChanged = IsChanged(lightEsppObject, lightDbObj);
//}
//else
//{
// logger.LogDebug($"Объект активирован в ПАРР. Сравниваем все поля. {esppObject.TemplateName}");
// isChanged = IsChanged(esppObject, dbObjectInEsppObject);
//}
if (dbObjectInEsppObject.IsActive == false)
{
logger.LogDebug($"Объект деактивирован в ПАРР. Сравниваем только обязательные поля. {esppObject.TemplateName}");
var lightDbObj = new EsppLightObject(dbObjectInEsppObject);
var lightEsppObject = new EsppLightObject(esppObject);
isChanged = IsChanged(lightEsppObject, lightDbObj);
}
else
{
logger.LogDebug($"Объект активирован в ПАРР. Сравниваем все поля. {esppObject.TemplateName}");
isChanged = IsChanged(esppObject, dbObjectInEsppObject);
}
if (isChanged)
{
@@ -134,6 +149,10 @@ namespace PARR.EsppSync
}
}
}
catch (Exception ex)
{
logger.LogError(ex, $"Ошибка синхронизации объекта АСУ ЕСПП {esppObject.TemplateName}");
}
}

View File

@@ -12,8 +12,8 @@ namespace PARR.EsppTemplateSync
{
public static void InstallEsppTemplateSyncServices(this IServiceCollection services, IConfiguration configuration)
{
services.InstallDalServices(configuration);
services.InstallBllServices(configuration);
//services.InstallDalServices(configuration);
services.InstallEsppSyncServices<EsppObjectTemplate>(configuration);
var globalSettings = new GlobalSettings();

View File

@@ -1,13 +1,11 @@
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using PARR.BLL.Services.Interfaces;
using PARR.DAL.Contracts;
using PARR.DAL.DomainServices;
using PARR.DAL.Extensions;
using PARR.DAL.Models;
using PARR.EsppSync;
using PARR.EsppTemplateSync.Domain;
using PARR.EsppTemplateSync.Settings;
using System.Threading.Tasks;
namespace PARR.EsppTemplateSync
{
@@ -16,25 +14,22 @@ namespace PARR.EsppTemplateSync
private readonly ILogger<TemplateMQSyncer> logger;
private readonly GlobalSettings globalSettings;
private readonly IMqService mqService;
private readonly ISyncService<EsppObjectTemplate> syncService;
private readonly SettingsFromDb settingsFromDb;
private readonly IShortcodesService shortcodesService;
private readonly IServiceProvider serviceProvider;
public TemplateMQSyncer(
ILogger<TemplateMQSyncer> logger,
GlobalSettings globalSettings,
IMqService mqService,
ISyncService<EsppObjectTemplate> syncService,
SettingsFromDb settingsFromDb,
IShortcodesService shortcodesService
IServiceProvider serviceProvider
)
{
this.logger = logger;
this.globalSettings = globalSettings;
this.mqService = mqService;
this.syncService = syncService;
this.settingsFromDb = settingsFromDb;
this.shortcodesService = shortcodesService;
this.serviceProvider = serviceProvider;
if (globalSettings.MqSettings == null)
{
logger.LogError("Нет секции настроек хранилища. MqSettings, EsppTemplates");
@@ -44,6 +39,7 @@ namespace PARR.EsppTemplateSync
public void Start()
{
var isConnected = mqService.InitConsumer(globalSettings!.MqSettings!, SyncTemplateAsync);
if (!isConnected)
@@ -62,7 +58,16 @@ namespace PARR.EsppTemplateSync
private async Task SyncTemplateAsync(string str)
{
await syncService.SyncEsppObjectAsync(str, ParseStrToEsppObject, ConvertDbObjToEsppObjAsync);
using (var scope = serviceProvider.CreateScope())
{
var syncService = scope.ServiceProvider.GetService<ISyncService<EsppObjectTemplate>>();
if (syncService == null)
throw new Exception($"Не смог получить серивс {nameof(ITemplateSyncer)}");
await syncService.SyncEsppObjectAsync(str, ParseStrToEsppObject, ConvertDbObjToEsppObj);
}
}
@@ -71,7 +76,7 @@ namespace PARR.EsppTemplateSync
/// </summary>
/// <param name="template">template должен обязательно содержать в себе информацию о Job+Group и Unit+BaseFields</param>
/// <returns></returns>
private async Task<EsppObjectTemplate> ConvertDbObjToEsppObjAsync(Template template)
private EsppObjectTemplate ConvertDbObjToEsppObj(Template template)
{
var templateFromDb = new EsppObjectTemplate
{
@@ -80,7 +85,7 @@ namespace PARR.EsppTemplateSync
//WorkGroup = template.Host!.WorkGroup!,
WorkGroup = template.Unit!.BaseFields!.WorkGroup!,
//ShortDescription = Normalize(template.Job.Group.ShortDescription.ApplyShortcode(Constants.Shortcodes.ShortcodeEnum.EK,template.Unit.Name)),
ShortDescription = Normalize(await shortcodesService.ApplyShortcodesAsync(template.Job!.Group!.ShortDescription,template.UnitId,template.JobId)),//TODO Вынести в переменную
ShortDescription = Normalize(template.Job!.Group!.ShortDescription),//TODO Вынести в переменную
//ResponseArea = template.Host!.ResponseArea!.Name,
//ЗО берем РГ а не хоста
ResponseArea = template.Unit.BaseFields.ResponseArea!,
@@ -91,8 +96,8 @@ namespace PARR.EsppTemplateSync
Process = template.Job.Tnk!.Subprocess!.Process!.Name,
SubProcess = template.Job.Tnk.Subprocess.Name,
//TNK = template.Job.Tnk.Name.ApplyShortcode(Constants.Shortcodes.ShortcodeEnum.EK, template.Unit.Name),
TNK = Normalize(await shortcodesService.ApplyShortcodesAsync(template.Job.Tnk.Name, template.UnitId, template.JobId)),
Work = Normalize(await shortcodesService.ApplyShortcodesAsync(template.Job.WorkName, template.UnitId, template.JobId)),
TNK = template.Job.Tnk.Name,
Work = template.Job.WorkName,
Initiator = settingsFromDb.Initiator
};

View File

@@ -1,11 +1,13 @@
using Microsoft.Extensions.DependencyInjection;
using PARR.EsppTemplateSync;
using System;
namespace PARR.EsppTemplateSyncWorker
{
public class Worker : BackgroundService
{
private readonly ILogger<Worker> _logger;
private ITemplateSyncer templateSyncer;
private readonly ITemplateSyncer templateSyncer;
public Worker(
ILogger<Worker> logger,
@@ -19,13 +21,11 @@ namespace PARR.EsppTemplateSyncWorker
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
templateSyncer.Start();
//TODO Периодично нужно проверять шаблоны в ПАРР и создавать их если они отсутствуют в ЕСПП. По ним то ничего не приходит из экспорта
}
public override Task StopAsync(CancellationToken cancellationToken)
{
templateSyncer.Stop();
return base.StopAsync(cancellationToken);
}
}