feat(api,esppScheduleSync): добавлена логика подстановки часового пояса рабочей группы ответственной за эк в расписании генератора

This commit is contained in:
Mikhail Kuznetsov
2026-01-30 12:29:37 +10:00
parent fddee13fff
commit 9f7111f2e9
2 changed files with 193 additions and 152 deletions

View File

@@ -12,11 +12,10 @@ using PARR.Common.Domain;
using PARR.Constants; using PARR.Constants;
using PARR.DAL.Contracts; using PARR.DAL.Contracts;
using PARR.DAL.DomainServices.Shortcodes; using PARR.DAL.DomainServices.Shortcodes;
using PARR.DAL.DomainServices.Shortcodes.Models;
using PARR.DAL.Models; using PARR.DAL.Models;
using PARR.DAL.NextRunServices; using PARR.DAL.NextRunServices;
using PARR.DAL.Services.Interfaces; using PARR.DAL.Services.Interfaces;
using PARR.DAL.TransformServices; using PARR.DAL.Services.Interfaces.Schedule;
namespace PARR.API.Controllers.V1 namespace PARR.API.Controllers.V1
{ {
@@ -32,6 +31,7 @@ namespace PARR.API.Controllers.V1
private readonly IRobotHistoryService robotHistoryService; private readonly IRobotHistoryService robotHistoryService;
private readonly IShortcodesService shortcodesService; private readonly IShortcodesService shortcodesService;
private readonly INextRunService nextRunService; private readonly INextRunService nextRunService;
private readonly IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService;
public RobotTaskController( public RobotTaskController(
IMapper mapper, IMapper mapper,
@@ -42,7 +42,8 @@ namespace PARR.API.Controllers.V1
IClientService clientService, IClientService clientService,
IRobotHistoryService robotHistoryService, IRobotHistoryService robotHistoryService,
IShortcodesService shortcodesService, IShortcodesService shortcodesService,
INextRunService nextRunService INextRunService nextRunService,
IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService
) )
{ {
this.mapper = mapper; this.mapper = mapper;
@@ -54,6 +55,7 @@ namespace PARR.API.Controllers.V1
this.robotHistoryService = robotHistoryService; this.robotHistoryService = robotHistoryService;
this.shortcodesService = shortcodesService; this.shortcodesService = shortcodesService;
this.nextRunService = nextRunService; this.nextRunService = nextRunService;
this.scheduleResponseAreaTimeOffsetService = scheduleResponseAreaTimeOffsetService;
} }
@@ -198,38 +200,6 @@ namespace PARR.API.Controllers.V1
{ //RobotTaskTemplateResponse { //RobotTaskTemplateResponse
var robotTaskTemplateResponse = mapper.Map<RobotTaskTemplateResponse>(task); var robotTaskTemplateResponse = mapper.Map<RobotTaskTemplateResponse>(task);
//TODO Вынести в отдельный метод ShortcodesService
// Построим TemplateForShortcodes из уже загруженного task.Template
//var templateForShortcodes = new TemplateForShortcodes
//{
// Id = task.Template!.Id,
// Index = task.Template.Index,
// JobId = task.Template.JobId,
// UnitId = task.Template.UnitId,
// Job = task.Template.Job == null ? null : new JobForShortcodes
// {
// Group = task.Template.Job.Group == null ? null : new JobGroupForShortcodes
// {
// Id = task.Template.Job.Group.Id,
// GroupingUnitFieldId = task.Template.Job.Group.GroupingUnitFieldId,
// GroupType = task.Template.Job.Group.GroupType == null ? null : new JobGroupTypeForShortcodes
// {
// Code = task.Template.Job.Group.GroupType.Code
// },
// GroupName = task.Template.Job.Group.GroupName
// },
// Tnk = task.Template.Job.Tnk == null ? null : new TnkForShortcodes
// {
// Name = task.Template.Job.Tnk.Name,
// ShortName = task.Template.Job.Tnk.ShortName ?? ""
// },
// WorkName = task.Template.Job.WorkName,
// Name = task.Template.Job.Name
// },
// UnitsInTemplate = task.Template.UnitsInTemplate?.Select(uit => new UnitInTemplateForShortcodes { UnitId = uit.UnitId }).ToList() ?? new List<UnitInTemplateForShortcodes>()
//};
//if (shortcodesService.IsAnyShortcodes(robotTaskTemplateResponse.FullDescription)) //if (shortcodesService.IsAnyShortcodes(robotTaskTemplateResponse.FullDescription))
robotTaskTemplateResponse.FullDescription = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.FullDescription, task.Template!); robotTaskTemplateResponse.FullDescription = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.FullDescription, task.Template!);
//if (shortcodesService.IsAnyShortcodes(robotTaskTemplateResponse.ShortDescription)) //if (shortcodesService.IsAnyShortcodes(robotTaskTemplateResponse.ShortDescription))
@@ -253,39 +223,13 @@ namespace PARR.API.Controllers.V1
//RobotTaskScheduleResponse //RobotTaskScheduleResponse
var robotTaskScheduleResponse = mapper.Map<RobotTaskScheduleResponse>(task); var robotTaskScheduleResponse = mapper.Map<RobotTaskScheduleResponse>(task);
//// Построим TemplateForShortcodes из уже загруженного task.Template robotTaskScheduleResponse.Timezone = await GetTimezoneForTemplateAsync(task.Template!);
//var templateForShortcodes = new TemplateForShortcodes
//{
// Id = task.Template!.Id,
// Index = task.Template.Index,
// JobId = task.Template.JobId,
// UnitId = task.Template.UnitId,
// Job = task.Template.Job == null ? null : new JobForShortcodes
// {
// Group = task.Template.Job.Group == null ? null : new JobGroupForShortcodes
// {
// GroupingUnitFieldId = task.Template.Job.Group.GroupingUnitFieldId,
// GroupType = task.Template.Job.Group.GroupType == null ? null : new JobGroupTypeForShortcodes
// {
// Code = task.Template.Job.Group.GroupType.Code
// },
// GroupName = task.Template.Job.Group.GroupName
// },
// Tnk = task.Template.Job.Tnk == null ? null : new TnkForShortcodes
// {
// Name = task.Template.Job.Tnk.Name,
// ShortName = task.Template.Job.Tnk.ShortName
// },
// WorkName = task.Template.Job.WorkName,
// Name = task.Template.Job.Name
// },
// UnitsInTemplate = task.Template.UnitsInTemplate?.Select(uit => new UnitInTemplateForShortcodes { UnitId = uit.UnitId }).ToList() ?? new List<UnitInTemplateForShortcodes>()
//};
//if (shortcodesService.IsAnyShortcodes(robotTaskScheduleResponse.WorkGroup))
robotTaskScheduleResponse.WorkGroup = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.WorkGroup, task.Template!); robotTaskScheduleResponse.WorkGroup = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.WorkGroup, task.Template!);
robotTaskScheduleResponse.ResponseArea = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.ResponseArea, task.Template!); robotTaskScheduleResponse.ResponseArea = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.ResponseArea, task.Template!);
return Ok(new Response<RobotTaskScheduleResponse>(robotTaskScheduleResponse, true)); return Ok(new Response<RobotTaskScheduleResponse>(robotTaskScheduleResponse, true));
} }
default: default:
@@ -387,5 +331,33 @@ namespace PARR.API.Controllers.V1
return true; return true;
} }
private async Task<string> GetTimezoneForTemplateAsync(Template template)
{
if (template.Job?.Group?.IsWorkGroupTimezone != true)
return settingsFromDb.ScheduleTimezone;
var responseArea = template.Unit?.BaseFields?.ResponseArea;
if (string.IsNullOrEmpty(responseArea))
{
throw new InvalidOperationException(
$"У шаблона Id={template.Id}, Name='{template.Name}' не задана ResponseArea в Unit.BaseFields, " +
"но включена настройка 'использовать часовой пояс рабочей группы'.");
}
var offsets = await scheduleResponseAreaTimeOffsetService
.Get()
.ToDictionaryAsync(t => t.ResponseArea, t => t.TimeOffset);
if (!offsets.TryGetValue(responseArea, out var offset))
{
throw new InvalidOperationException(
$"Не найдено временное смещение для ResponseArea '{responseArea}' у шаблона Id={template.Id}, Name='{template.Name}'. " +
"Проверьте наличие записи в таблице ScheduleResponseAreaTimeOffset.");
}
return offset;
}
} }
} }

View File

@@ -6,6 +6,7 @@ using PARR.BLL.Services.Interfaces;
using PARR.DAL.Contracts; using PARR.DAL.Contracts;
using PARR.DAL.Models; using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces; using PARR.DAL.Services.Interfaces;
using PARR.DAL.Services.Interfaces.Schedule;
using PARR.DAL.TransformServices; using PARR.DAL.TransformServices;
using PARR.EsppScheduleSync.Domain; using PARR.EsppScheduleSync.Domain;
using PARR.EsppScheduleSync.Settings; using PARR.EsppScheduleSync.Settings;
@@ -21,7 +22,9 @@ namespace PARR.EsppScheduleSync
private readonly ISyncService<EsppObjectSchedule> syncService; private readonly ISyncService<EsppObjectSchedule> syncService;
private readonly SettingsFromDb settingsFromDb; private readonly SettingsFromDb settingsFromDb;
private readonly IServiceProvider serviceProvider; private readonly IServiceProvider serviceProvider;
//private readonly INextRunModifierService nextRunModifierService; private string noneExcludeCalendarEsppValue;
private Dictionary<string, string> responseAreaTimeOffsetDict;
public ScheduleSyncher( public ScheduleSyncher(
ILogger<ScheduleSyncher> logger, ILogger<ScheduleSyncher> logger,
@@ -30,7 +33,6 @@ namespace PARR.EsppScheduleSync
ISyncService<EsppObjectSchedule> syncService, ISyncService<EsppObjectSchedule> syncService,
SettingsFromDb settingsFromDb, SettingsFromDb settingsFromDb,
IServiceProvider serviceProvider IServiceProvider serviceProvider
//INextRunModifierService nextRunModifierService
) )
{ {
this.logger = logger; this.logger = logger;
@@ -39,17 +41,30 @@ namespace PARR.EsppScheduleSync
this.syncService = syncService; this.syncService = syncService;
this.settingsFromDb = settingsFromDb; this.settingsFromDb = settingsFromDb;
this.serviceProvider = serviceProvider; this.serviceProvider = serviceProvider;
//this.nextRunModifierService = nextRunModifierService;
this.responseAreaTimeOffsetDict = new Dictionary<string, string>();
this.noneExcludeCalendarEsppValue = string.Empty;
if (globalSettings.MqSettings == null) if (globalSettings.MqSettings == null)
{ {
logger.LogError("Нет секции настроек хранилища. MqSettings, EsppTemplates"); logger.LogError("Нет секции настроек хранилища. MqSettings, EsppTemplates");
throw new Exception("Нет секции настроек хранилища. MqSettings, EsppTemplates"); throw new Exception("Нет секции настроек хранилища. MqSettings, EsppTemplates");
} }
if (string.IsNullOrEmpty(globalSettings.ParsingSeparator))
throw new ArgumentException("ParsingSeparator не задан в GlobalSettings.");
} }
public async Task StartAsync() public async Task StartAsync()
{ {
if (string.IsNullOrEmpty(noneExcludeCalendarEsppValue))
await GetNoneExcludeCalendarEsppValueAsync();
if (responseAreaTimeOffsetDict.Count == 0)
await GetResponseAreaTimeOffsetsAsync();
var isConnected = await mqService.InitConsumerAsync(globalSettings!.MqSettings!, SyncScheduleAsync); var isConnected = await mqService.InitConsumerAsync(globalSettings!.MqSettings!, SyncScheduleAsync);
if (!isConnected) if (!isConnected)
@@ -58,6 +73,26 @@ namespace PARR.EsppScheduleSync
logger.LogInformation("Запущена проверка очереди {QueueName}.", globalSettings.MqSettings!.QueueName); logger.LogInformation("Запущена проверка очереди {QueueName}.", globalSettings.MqSettings!.QueueName);
} }
private async Task GetResponseAreaTimeOffsetsAsync()
{
using var scope = serviceProvider.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<IScheduleResponseAreaTimeOffsetService>();
responseAreaTimeOffsetDict = await service.Get().ToDictionaryAsync(t => t.ResponseArea, t => t.TimeOffset);
}
private async Task GetNoneExcludeCalendarEsppValueAsync()
{
using var scope = serviceProvider.CreateScope();
var service = scope.ServiceProvider.GetRequiredService<IScheduleExcludeTypeService>();
var noneExcludeType = await service.Get().AsNoTracking().FirstOrDefaultAsync(t => t.Code == nameof(ScheduleExcludeTypeEnum.None));
if (noneExcludeType == null)
throw new InvalidOperationException($"Не найден тип исключения с кодом '{nameof(ScheduleExcludeTypeEnum.None)}'");
noneExcludeCalendarEsppValue = noneExcludeType.EsppValue;
}
public async Task StopAsync() public async Task StopAsync()
{ {
@@ -68,6 +103,8 @@ namespace PARR.EsppScheduleSync
private async Task SyncScheduleAsync(string str) private async Task SyncScheduleAsync(string str)
{
try
{ {
const int expectedParts = 24; const int expectedParts = 24;
var separator = globalSettings.ParsingSeparator; var separator = globalSettings.ParsingSeparator;
@@ -172,6 +209,12 @@ namespace PARR.EsppScheduleSync
// Передаём оригинальную строку в стандартный синхронизатор // Передаём оригинальную строку в стандартный синхронизатор
await syncService.SyncEsppObjectAsync(str, ParseStrToEsppObject, ConvertDbObjToEsppObj); await syncService.SyncEsppObjectAsync(str, ParseStrToEsppObject, ConvertDbObjToEsppObj);
} }
catch (Exception ex)
{
logger.LogError(ex, "Ошибка при синхронизации расписания из строки: {InputString}", str);
}
}
/// <summary> /// <summary>
@@ -194,7 +237,6 @@ namespace PARR.EsppScheduleSync
nextRunByAccountRobotTimeZone = nextRunModifierService.GetNextRunByAccountRobotTimeZone(template.NextRun); nextRunByAccountRobotTimeZone = nextRunModifierService.GetNextRunByAccountRobotTimeZone(template.NextRun);
} }
var esppObjectFromDb = new EsppObjectSchedule var esppObjectFromDb = new EsppObjectSchedule
{ {
TemplateName = template.Name.ToUpper(), TemplateName = template.Name.ToUpper(),
@@ -213,7 +255,7 @@ namespace PARR.EsppScheduleSync
//BasisTime = EsppScheduleHelpers.GetGenerationTime(nextRunModifierService.GetNextRunByAccountRobotTimeZone(template.NextRun)), //BasisTime = EsppScheduleHelpers.GetGenerationTime(nextRunModifierService.GetNextRunByAccountRobotTimeZone(template.NextRun)),
Scheduled = EsppScheduleHelpers.GetNextRun(nextRunByAccountRobotTimeZone), Scheduled = EsppScheduleHelpers.GetNextRun(nextRunByAccountRobotTimeZone),
BasisTime = EsppScheduleHelpers.GetGenerationTime(nextRunByAccountRobotTimeZone), BasisTime = EsppScheduleHelpers.GetGenerationTime(nextRunByAccountRobotTimeZone),
Timezone = settingsFromDb.ScheduleTimezone, Timezone = GetTimezone(template),
//Мы решили, что для всех расписаний "Отсутствует дата завершения", если что-то поменяется, тут нужно переделать //Мы решили, что для всех расписаний "Отсутствует дата завершения", если что-то поменяется, тут нужно переделать
TerminationType = settingsFromDb.ScheduleRepeatRange == "Отсутствует дата завершения" ? "forever" : "", TerminationType = settingsFromDb.ScheduleRepeatRange == "Отсутствует дата завершения" ? "forever" : "",
CompleteAfter = "" CompleteAfter = ""
@@ -223,6 +265,31 @@ namespace PARR.EsppScheduleSync
return ClearOptionalFields(esppObjectFromDb); return ClearOptionalFields(esppObjectFromDb);
} }
private string GetTimezone(Template template)
{
if (template.Job?.Group?.IsWorkGroupTimezone != true)
return settingsFromDb.ScheduleTimezone;
var responseArea = template.Unit?.BaseFields?.ResponseArea;
if (string.IsNullOrEmpty(responseArea))
{
throw new InvalidOperationException(
$"У шаблона Id={template.Id}, Name='{template.Name}' не задана ResponseArea в Unit.BaseFields, " +
"но включена настройка 'использовать часовой пояс рабочей группы'.");
}
if (!responseAreaTimeOffsetDict.TryGetValue(responseArea, out var offset))
{
throw new InvalidOperationException(
$"Не найдено временное смещение для ResponseArea '{responseArea}' у шаблона Id={template.Id}, Name='{template.Name}'. " +
"Проверьте наличие записи в таблице ScheduleResponseAreaTimeOffset.");
}
return offset;
}
/// <summary> /// <summary>
/// Получить исключение - Календарь /// Получить исключение - Календарь
@@ -476,7 +543,9 @@ namespace PARR.EsppScheduleSync
esppObject.ResponseArea = string.Empty; esppObject.ResponseArea = string.Empty;
esppObject.WorkGroup = string.Empty; esppObject.WorkGroup = string.Empty;
esppObject.CompleteAfter = string.Empty; esppObject.CompleteAfter = string.Empty;
//esppObject.V60calendar = string.Empty;
if (esppObject.TypeV60calendar == noneExcludeCalendarEsppValue)
esppObject.V60calendar = string.Empty;
// В ЕСПП, при изменении "Повторять задачу", остаются предыдущие значения, их не нужно синхронизировать (касается только данных полученных из ЕСПП, в БД все ок) // В ЕСПП, при изменении "Повторять задачу", остаются предыдущие значения, их не нужно синхронизировать (касается только данных полученных из ЕСПП, в БД все ок)
// т.е. если стояло Ежедненвно:понедельник, а изменили например на Еженедельно..., то в ежедневно значения останутся, но будут отрабатывать значения из Еженедельно. // т.е. если стояло Ежедненвно:понедельник, а изменили например на Еженедельно..., то в ежедневно значения останутся, но будут отрабатывать значения из Еженедельно.