diff --git a/PARR.API/Contracts/V1/Requests/Queries/RobotTaskQuery.cs b/PARR.API/Contracts/V1/Requests/Queries/RobotTaskQuery.cs
index 84a040a9..8fc3d2d5 100644
--- a/PARR.API/Contracts/V1/Requests/Queries/RobotTaskQuery.cs
+++ b/PARR.API/Contracts/V1/Requests/Queries/RobotTaskQuery.cs
@@ -6,5 +6,10 @@
/// Установить статус задания - InProgress (Робот взял в работу)
///
public bool? SetInProgressStatus { get; init; }
+
+ ///
+ /// Идентификатор робота
+ ///
+ public string? RobotId { get; init; }
}
}
diff --git a/PARR.API/Controllers/V1/RobotTaskController.cs b/PARR.API/Controllers/V1/RobotTaskController.cs
index 1b6182cb..42047ae1 100644
--- a/PARR.API/Controllers/V1/RobotTaskController.cs
+++ b/PARR.API/Controllers/V1/RobotTaskController.cs
@@ -3,84 +3,375 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using PARR.API.Contracts.V1;
using PARR.API.Contracts.V1.Requests.Queries;
+using PARR.API.Contracts.V1.Responses;
+using PARR.API.Contracts.V1.Responses.Base;
using PARR.API.Controllers.V1.Base;
using PARR.API.Services.Interfaces;
-using PARR.Core.Repositories.Interfaces;
-using PARR.Core.Services.NextRunServices;
using PARR.Core.Services.RobotTask.Interfaces;
-using PARR.Core.Services.Shortcodes;
using PARR.Domain.Common.Roles;
+using PARR.Domain.Entities.Base.History;
using PARR.Domain.Enums;
-using PARR.Domain.Settings;
+using PARR.Domain.Exceptions;
namespace PARR.API.Controllers.V1
{
- ///
- /// Формирование заданий роботам
- ///
[Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)]
public class RobotTaskController : BaseApiController
{
- private readonly IMapper mapper;
- private readonly SettingsFromDb settingsFromDb;
- private readonly IRobotConfigurationRepository robotConfigurationService;
- private readonly ILogger logger;
- private readonly IClientService clientService;
- private readonly IRobotHistoryRepository robotHistoryService;
- private readonly IShortcodesService shortcodesService;
- private readonly INextRunService nextRunService;
private readonly IRobotTaskService robotTaskService;
+ private readonly IMapper mapper;
+ //private readonly SettingsFromDb settingsFromDb;
+ //private readonly IRobotConfigurationRepository robotConfigurationService;
+ //private readonly ILogger logger;
+ private readonly IClientService clientService;
+ //private readonly IRobotHistoryRepository robotHistoryService;
+ //private readonly IShortcodesService shortcodesService;
+ //private readonly INextRunService nextRunService;
+
public RobotTaskController(
IMapper mapper,
- SettingsFromDb settingsFromDb,
- IRobotConfigurationRepository robotConfigurationService,
- ILogger logger,
+ //SettingsFromDb settingsFromDb,
+ //IRobotConfigurationRepository robotConfigurationService,
+ //ILogger logger,
IClientService clientService,
- IRobotHistoryRepository robotHistoryService,
- IShortcodesService shortcodesService,
- INextRunService nextRunService,
-
-
+ //IRobotHistoryRepository robotHistoryService,
+ //IShortcodesService shortcodesService,
+ //INextRunService nextRunService
IRobotTaskService robotTaskService
)
{
- this.mapper = mapper;
- this.settingsFromDb = settingsFromDb;
- this.robotConfigurationService = robotConfigurationService;
- this.logger = logger;
- this.clientService = clientService;
- this.robotHistoryService = robotHistoryService;
- this.shortcodesService = shortcodesService;
- this.nextRunService = nextRunService;
this.robotTaskService = robotTaskService;
+ this.mapper = mapper;
+ //this.settingsFromDb = settingsFromDb;
+ //this.robotConfigurationService = robotConfigurationService;
+ //this.logger = logger;
+ this.clientService = clientService;
+ //this.robotHistoryService = robotHistoryService;
+ //this.shortcodesService = shortcodesService;
+ //this.nextRunService = nextRunService;
}
///
- /// Получить задание для робота по коду робота и статусу задания
+ /// Получить задание для робота по коду робота и по статусу задания
///
- /// Код робота
- /// Статус задания
- /// Параметры
+ ///
+ ///
+ ///
///
[HttpGet(ApiRoutes.RobotTask.GetByRobotAndStatusTask)]
public async Task GetByRobotAndStatusTask([FromRoute] RobotsEnum robotCode, [FromRoute] TaskStatusEnum taskStatusCode, [FromQuery] RobotTaskQuery requestQuery)
{
+ switch (robotCode)
+ {
+ case RobotsEnum.TemplateOrder:
+ var templateTask = await robotTaskService.GetTemplateTaskAsync(
+ taskStatusCode,
+ requestQuery.SetInProgressStatus ?? false,
+ clientService.GetClientIp()?.ToString(),
+ requestQuery.RobotId
+ );
+ var templateResponse = mapper.Map(templateTask);
- //robotTaskService
+ return Ok(new Response(templateResponse, true));
+ case RobotsEnum.ScheduleOrder:
+ var historyIniciator = new HistoryInitiator
+ {
+ };
+ var scheduleTask = await robotTaskService.GetScheduleTaskAsync(
+ taskStatusCode,
+ requestQuery.SetInProgressStatus ?? false,
+ historyIniciator.InitiatorIp,
+ requestQuery.RobotId,
+ historyIniciator
+ );
+ var scheduleResponse = mapper.Map(scheduleTask);
-
- return Ok();
+ return Ok(new Response(scheduleResponse, true));
+ default:
+ throw new AppValidationException("Некорректное значение robotCode");
+ }
}
+ #region Old
+
+ // ///
+ // /// Получить задание для робота по коду робота и по статусу задания
+ // ///
+ // ///
+ // ///
+ // ///
+ // [HttpGet(ApiRoutes.RobotTask.GetByRobotAndStatusTask)]
+ // public async Task GetByRobotAndStatusTask([FromRoute] RobotsEnum robotCode, [FromRoute] TaskStatusEnum taskStatusCode, [FromQuery] RobotTaskQuery requestQuery)
+ // {
+ // //Ищем все задания с превышенным кол-вом попыток и с просроченным временем и ставим им статус ошибки
+ // await robotConfigurationService.MarkExpiredTasksAsFailedAsync(settingsFromDb.RobotAttemptsNumber, settingsFromDb.RobotWaitTime);
+ // var query = robotConfigurationService.Get()
+ // .AsSingleQuery()
+ // .Where(t => t.RobotCode == (int)robotCode && t.TaskStatusCode == (int)taskStatusCode);
+
+ // switch (robotCode)
+ // {
+ // case RobotsEnum.TemplateOrder:
+ // // шаблоны
+ // query = query
+ // .Include(t => t.Template)
+ // .ThenInclude(t => t!.Unit)
+ // .ThenInclude(t => t!.UnitValues)
+ // .ThenInclude(t => t.Field)
+ // .Include(t => t.Template)
+ // .ThenInclude(t => t!.Unit)
+ // .ThenInclude(t => t!.UnitValues)
+ // .ThenInclude(t => t.Value)
+ // .Include(t => t.Template)
+ // .ThenInclude(a => a!.Job)
+ // .ThenInclude(t => t!.Group)
+ // .ThenInclude(g => g.GroupType)
+ // .Include(t => t.Template)
+ // .ThenInclude(w => w!.Job)
+ // .ThenInclude(t => t!.Tnk)
+ // .ThenInclude(s => s!.Subprocess)
+ // .ThenInclude(p => p!.Process);
+
+ // query = query
+ // .Include(t => t.Template)
+ // .ThenInclude(t => t!.UnitsInTemplate);
+
+ // break;
+
+ // case RobotsEnum.ScheduleOrder:
+ // //расписание
+ // query = query
+ // .Include(t => t.Template)
+ // .ThenInclude(t => t!.Unit)
+ // .ThenInclude(t => t!.UnitValues)
+ // .ThenInclude(t => t.Field)
+ // .Include(t => t.Template)
+ // .ThenInclude(t => t!.Unit)
+ // .ThenInclude(t => t!.UnitValues)
+ // .ThenInclude(t => t.Value)
+ // .Include(t => t.Template)
+ // .ThenInclude(a => a!.Job)
+ // .ThenInclude(t => t!.Group)
+ // .ThenInclude(g => g.GroupType)
+ // .Include(t => t.Template)
+ // .ThenInclude(a => a!.Job)
+ // .ThenInclude(t => t!.Group)
+ // .ThenInclude(t => t!.EsppSchValues)
+ // .ThenInclude(t => t!.EsppSchTypeConfig)
+ // .ThenInclude(t => t!.EsppSchTypeSchedule)
+ // .Include(t => t.Template)
+ // .ThenInclude(t => t!.Job)
+ // .ThenInclude(t => t!.Group)
+ // .ThenInclude(t => t!.ScheduleExcludeType)
+ // .Include(t => t.Template)
+ // .ThenInclude(t => t!.Job)
+ // .ThenInclude(t => t!.Group)
+ // .ThenInclude(t => t.ScheduleExcludeTypeCalendar)
+ // .Include(t => t.Template)
+ // .ThenInclude(t => t!.Job)
+ // .ThenInclude(t => t!.Tnk)
+ // .Include(t => t.Template)
+ // .ThenInclude(t => t!.UnitsInTemplate);
+
+ // //выбираем только записи с созданными шаблонами (у которых статус 20 или 30), а только потом у них ищем расписания
+ // var createdTemplates = robotConfigurationService.Get()
+ // .Where(t => t.RobotCode == (int)RobotsEnum.TemplateOrder && (t.TaskStatusCode == (int)TaskStatusEnum.Ok))
+ // .Select(t => t.TemplateId);
+ // query = query.Where(t => t.RobotCode == (int)RobotsEnum.ScheduleOrder && createdTemplates.Contains(t.TemplateId));
+ // // query = query.Where(t => t.RobotCode == (int)RobotsEnum.ScheduleOrder && t.TemplateId==Guid.Parse("7cb7c3be-506d-40e0-a63f-4554edb52459"));
+ // break;
+
+ // default:
+ // break;
+ // }
+ // // сортируем по NextRun, чтобы те у которых дата след срабатывания ближе к текущей, выполнились скорее
+ // query = query.OrderBy(t => t.Template!.NextRun).ThenBy(t => t.Template!.IsActiveSchedule).ThenBy(t => t.Template.IsActiveTemplate);
+ // RobotConfiguration? task = null;
+
+ // //ищем задание в ожидании, если нашли, выбираем его
+ // task = await query.FirstOrDefaultAsync(t => t.RobotStatusCode == (int)RobotStatusEnum.Wait);
+
+ // if (task == null)
+ // {
+ // //ищем задания в работе, которые можно перезапустить
+ // //Поиск по `RobotStatusCode` = 22.
+ // //Далее проверяется `LastStatusUpdated`, что время последнего смены статуса не превышает допустимого(берется из настроек, поле `RobotWaitTime`)
+ // //и что текущая попытка не больше разрешенной(берется из настроек, поле `RobotAttemptsNumber`) - если это так, берется эта запись.
+
+ // var endDate = DateTimeOffset.UtcNow.Add(-settingsFromDb.RobotWaitTime);
+ // task = await query
+ // .FirstOrDefaultAsync(t =>
+ // t.RobotStatusCode == (int)RobotStatusEnum.InProgress
+ // && t.AttemptsNumber < settingsFromDb.RobotAttemptsNumber
+ // && t.LastRobotStatusUpdated < endDate
+ // );
+ // }
+
+ // if (task == null)
+ // return NotFound();
+
+ // if (requestQuery?.SetInProgressStatus == true)
+ // {
+ // var resultSetStatus = await SetInProgressStatusAsync(task.Id);
+ // if (resultSetStatus == false)
+ // {
+ // logger.LogError($"Ошибка при установке статуса {RobotStatusEnum.InProgress.ToString()} для задания RobotConfigutationId {task.Id} (при выдаче задания роботу)");
+ // return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при выдаче задания." } }));
+ // }
+ // }
+
+ // switch (robotCode)
+ // {
+ // case RobotsEnum.TemplateOrder:
+ // { //RobotTaskTemplateResponse
+ // var robotTaskTemplateResponse = mapper.Map(task);
+
+ // robotTaskTemplateResponse.FullDescription = NormalizeLineEndingsToCrlf(await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.FullDescription, task.Template!));
+ // robotTaskTemplateResponse.ShortDescription = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.ShortDescription, task.Template!);
+ // robotTaskTemplateResponse.Solution = NormalizeLineEndingsToCrlf(await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.Solution, task.Template!));
+ // robotTaskTemplateResponse.TnkName = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.TnkName, task.Template!);
+ // robotTaskTemplateResponse.WorkName = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.WorkName, task.Template!);
+ // robotTaskTemplateResponse.WorkGroup = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.WorkGroup, task.Template!);
+ // robotTaskTemplateResponse.ResponseArea = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.ResponseArea, task.Template!);
+
+ // return Ok(new Response(robotTaskTemplateResponse, true));
+ // }
+ // case RobotsEnum.ScheduleOrder:
+ // { // если был запрос на расписание, проверяем у него nextRun, lastRun, обновляем их
+ // var resultUpdateNextRun = await UpdateNextRunAsync(task);
+ // if (!resultUpdateNextRun)
+ // {
+ // logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}", task.TemplateId);
+ // return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при расчете NextRun" } }));
+ // }
+
+ // //RobotTaskScheduleResponse
+ // var robotTaskScheduleResponse = mapper.Map(task);
+
+ // robotTaskScheduleResponse.Timezone = settingsFromDb.EsppScheduleTimezone;
+
+ // robotTaskScheduleResponse.WorkGroup = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.WorkGroup, task.Template!);
+ // robotTaskScheduleResponse.ResponseArea = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.ResponseArea, task.Template!);
+
+ // //var nextRunWithRobotTz = nextRunService.GetNextRunWithTimezoneEsppAndResponseArea(task.Template!.NextRun, task.Template!.Job?.Group?.IsResponseAreaTimezone, robotTaskScheduleResponse.ResponseArea);
+ // //nextRun в часовой зоне УЗ Робота ЕСПП
+ // var nextRunWithRobotTz = task.Template!.NextRun.Add(nextRunService.GetEsppAccountOffset());
+
+ // //на всякий случай еще раз проверяем, что дата не устарела и отправляем задание
+ // if (nextRunWithRobotTz < DateTimeOffset.UtcNow)
+ // {
+ // logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}, итоговое значение для робота, меньше чем сейчас {nextRunWithRobotTz}<{now}",
+ // task.TemplateId, nextRunWithRobotTz, DateTimeOffset.UtcNow);
+ // return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при расчете NextRun" } }));
+ // }
+
+ // robotTaskScheduleResponse.NextStart = EsppScheduleHelpers.GetNextRun(nextRunWithRobotTz);
+ // robotTaskScheduleResponse.GenerationTime = EsppScheduleHelpers.GetGenerationTime(nextRunWithRobotTz);
+
+ // return Ok(new Response(robotTaskScheduleResponse, true));
+ // }
+ // default:
+ // break;
+ // }
+
+ // return BadRequest();
+ // }
+
+
+ // ///
+ // /// Обоновить NextRun если он устарел
+ // ///
+ // ///
+ // ///
+ // private async Task UpdateNextRunAsync(RobotConfiguration task)
+ // {
+ // var template = task.Template!;
+
+ // //var nextRun = await esppScheduleTransformService.GetNextDateAsync(template.Job!.GroupId, template!.Job!.Group!.ReferenceDate);
+ // var nextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, false);
+
+ // if (!nextRun.HasValue)
+ // {
+ // logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun=null, ошибка в расчетах.", template.Id);
+ // return false;
+ // }
+
+ // if (nextRun.Value < DateTimeOffset.UtcNow)
+ // {
+ // logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun
+ // /// Устанавливаем статус "Робот взял в работу", пишем в историю работы роботов инф о начале работ
+ // ///
+ // ///
+ // ///
+ // private async Task SetInProgressStatusAsync(Guid taskId)
+ // {
+ // var config = await robotConfigurationService.GetAsync(taskId);
+
+ // //изменение статуса робота
+ // robotConfigurationService.ChangeRobotStatus(RobotStatusEnum.InProgress, config!);
+
+ // if (!await robotConfigurationService.CommitAsync())
+ // return false;
+
+ // //записываем в лог робота
+ // var history = new RobotHistory
+ // {
+ // Id = Guid.NewGuid(),
+ // HistoryLevel = (int)RobotHistoryLevelEnum.Start,
+ // TaskStatusCode = config.TaskStatusCode,
+ // RobotConfigurationId = config.Id,
+ // RobotIp = clientService.GetClientIp()?.ToString()
+ // };
+
+ // if (!await robotHistoryService.CreateAsync(history) || !await robotHistoryService.CommitAsync())
+ // return false;
+
+ // return true;
+ // }
+
+
+ // ///
+ // /// Приводит переносы строк в тексте к формату CRLF (\r\n)
+ // ///
+ // /// Исходный текст
+ // /// Текст с унифицированными переносами строк
+ // private string NormalizeLineEndingsToCrlf(string? text)
+ // {
+ // if (string.IsNullOrEmpty(text))
+ // return string.Empty;
+
+ // // Заменяем любые варианты переносов (\r\n, \r, \n) на единый \r\n
+ // return System.Text.RegularExpressions.Regex.Replace(text, @"\r\n|\r|\n", "\r\n");
+ // }
+
+ #endregion
}
}
diff --git a/PARR.API/Controllers/V1/RobotTaskOldController.cs b/PARR.API/Controllers/V1/RobotTaskOldController.cs
deleted file mode 100644
index e522c2d8..00000000
--- a/PARR.API/Controllers/V1/RobotTaskOldController.cs
+++ /dev/null
@@ -1,330 +0,0 @@
-using AutoMapper;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.EntityFrameworkCore;
-using PARR.API.Contracts.V1;
-using PARR.API.Contracts.V1.Requests.Queries;
-using PARR.API.Contracts.V1.Responses;
-using PARR.API.Contracts.V1.Responses.Base;
-using PARR.API.Controllers.V1.Base;
-using PARR.API.Services.Interfaces;
-using PARR.BLL.Helpers;
-using PARR.Core.Repositories.Interfaces;
-using PARR.Core.Services.NextRunServices;
-using PARR.Core.Services.Shortcodes;
-using PARR.Domain.Common.Roles;
-using PARR.Domain.Entities;
-using PARR.Domain.Entities.Base.History;
-using PARR.Domain.Enums;
-using PARR.Domain.Settings;
-
-namespace PARR.API.Controllers.V1
-{
- [Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)]
- public class RobotTaskOldController : BaseApiController
- {
- private readonly IMapper mapper;
- private readonly SettingsFromDb settingsFromDb;
- private readonly IRobotConfigurationRepository robotConfigurationService;
- private readonly ILogger logger;
- private readonly IClientService clientService;
- private readonly IRobotHistoryRepository robotHistoryService;
- private readonly IShortcodesService shortcodesService;
- private readonly INextRunService nextRunService;
-
- public RobotTaskOldController(
- IMapper mapper,
- SettingsFromDb settingsFromDb,
- IRobotConfigurationRepository robotConfigurationService,
- ILogger logger,
- IClientService clientService,
- IRobotHistoryRepository robotHistoryService,
- IShortcodesService shortcodesService,
- INextRunService nextRunService
- )
- {
- this.mapper = mapper;
- this.settingsFromDb = settingsFromDb;
- this.robotConfigurationService = robotConfigurationService;
- this.logger = logger;
- this.clientService = clientService;
- this.robotHistoryService = robotHistoryService;
- this.shortcodesService = shortcodesService;
- this.nextRunService = nextRunService;
- }
-
-
- ///
- /// Получить задание для робота по коду робота и по статусу задания
- ///
- ///
- ///
- ///
- [HttpGet(ApiRoutes.RobotTask.GetByRobotAndStatusTask)]
- public async Task GetByRobotAndStatusTask([FromRoute] RobotsEnum robotCode, [FromRoute] TaskStatusEnum taskStatusCode, [FromQuery] RobotTaskQuery requestQuery)
- {
- //Ищем все задания с превышенным кол-вом попыток и с просроченным временем и ставим им статус ошибки
- await robotConfigurationService.MarkExpiredTasksAsFailedAsync(settingsFromDb.RobotAttemptsNumber, settingsFromDb.RobotWaitTime);
-
-
- var query = robotConfigurationService.Get()
- .AsSingleQuery()
- .Where(t => t.RobotCode == (int)robotCode && t.TaskStatusCode == (int)taskStatusCode);
-
- switch (robotCode)
- {
- case RobotsEnum.TemplateOrder:
- // шаблоны
- query = query
- .Include(t => t.Template)
- .ThenInclude(t => t!.Unit)
- .ThenInclude(t => t!.UnitValues)
- .ThenInclude(t => t.Field)
- .Include(t => t.Template)
- .ThenInclude(t => t!.Unit)
- .ThenInclude(t => t!.UnitValues)
- .ThenInclude(t => t.Value)
- .Include(t => t.Template)
- .ThenInclude(a => a!.Job)
- .ThenInclude(t => t!.Group)
- .ThenInclude(g => g.GroupType)
- .Include(t => t.Template)
- .ThenInclude(w => w!.Job)
- .ThenInclude(t => t!.Tnk)
- .ThenInclude(s => s!.Subprocess)
- .ThenInclude(p => p!.Process);
-
- query = query
- .Include(t => t.Template)
- .ThenInclude(t => t!.UnitsInTemplate);
-
- break;
-
- case RobotsEnum.ScheduleOrder:
- //расписание
- query = query
- .Include(t => t.Template)
- .ThenInclude(t => t!.Unit)
- .ThenInclude(t => t!.UnitValues)
- .ThenInclude(t => t.Field)
- .Include(t => t.Template)
- .ThenInclude(t => t!.Unit)
- .ThenInclude(t => t!.UnitValues)
- .ThenInclude(t => t.Value)
- .Include(t => t.Template)
- .ThenInclude(a => a!.Job)
- .ThenInclude(t => t!.Group)
- .ThenInclude(g => g.GroupType)
- .Include(t => t.Template)
- .ThenInclude(a => a!.Job)
- .ThenInclude(t => t!.Group)
- .ThenInclude(t => t!.EsppSchValues)
- .ThenInclude(t => t!.EsppSchTypeConfig)
- .ThenInclude(t => t!.EsppSchTypeSchedule)
- .Include(t => t.Template)
- .ThenInclude(t => t!.Job)
- .ThenInclude(t => t!.Group)
- .ThenInclude(t => t!.ScheduleExcludeType)
- .Include(t => t.Template)
- .ThenInclude(t => t!.Job)
- .ThenInclude(t => t!.Group)
- .ThenInclude(t => t.ScheduleExcludeTypeCalendar)
- .Include(t => t.Template)
- .ThenInclude(t => t!.Job)
- .ThenInclude(t => t!.Tnk)
- .Include(t => t.Template)
- .ThenInclude(t => t!.UnitsInTemplate);
-
- //выбираем только записи с созданными шаблонами (у которых статус 20 или 30), а только потом у них ищем расписания
- var createdTemplates = robotConfigurationService.Get()
- .Where(t => t.RobotCode == (int)RobotsEnum.TemplateOrder && (t.TaskStatusCode == (int)TaskStatusEnum.Ok))
- .Select(t => t.TemplateId);
- query = query.Where(t => t.RobotCode == (int)RobotsEnum.ScheduleOrder && createdTemplates.Contains(t.TemplateId));
- // query = query.Where(t => t.RobotCode == (int)RobotsEnum.ScheduleOrder && t.TemplateId==Guid.Parse("7cb7c3be-506d-40e0-a63f-4554edb52459"));
- break;
-
- default:
- break;
- }
-
-
- // сортируем по NextRun, чтобы те у которых дата след срабатывания ближе к текущей, выполнились скорее
- query = query.OrderBy(t => t.Template!.NextRun).ThenBy(t => t.Template!.IsActiveSchedule).ThenBy(t => t.Template.IsActiveTemplate);
-
- RobotConfiguration? task = null;
-
- //ищем задание в ожидании, если нашли, выбираем его
- task = await query.FirstOrDefaultAsync(t => t.RobotStatusCode == (int)RobotStatusEnum.Wait);
-
- if (task == null)
- {
- //ищем задания в работе, которые можно перезапустить
- //Поиск по `RobotStatusCode` = 22.
- //Далее проверяется `LastStatusUpdated`, что время последнего смены статуса не превышает допустимого(берется из настроек, поле `RobotWaitTime`)
- //и что текущая попытка не больше разрешенной(берется из настроек, поле `RobotAttemptsNumber`) - если это так, берется эта запись.
-
- var endDate = DateTimeOffset.UtcNow.Add(-settingsFromDb.RobotWaitTime);
- task = await query
- .FirstOrDefaultAsync(t =>
- t.RobotStatusCode == (int)RobotStatusEnum.InProgress
- && t.AttemptsNumber < settingsFromDb.RobotAttemptsNumber
- && t.LastRobotStatusUpdated < endDate
- );
- }
-
- if (task == null)
- return NotFound();
-
- if (requestQuery?.SetInProgressStatus == true)
- {
- var resultSetStatus = await SetInProgressStatusAsync(task.Id);
- if (resultSetStatus == false)
- {
- logger.LogError($"Ошибка при установке статуса {RobotStatusEnum.InProgress.ToString()} для задания RobotConfigutationId {task.Id} (при выдаче задания роботу)");
- return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при выдаче задания." } }));
- }
- }
-
- switch (robotCode)
- {
- case RobotsEnum.TemplateOrder:
- { //RobotTaskTemplateResponse
- var robotTaskTemplateResponse = mapper.Map(task);
-
- robotTaskTemplateResponse.FullDescription = NormalizeLineEndingsToCrlf(await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.FullDescription, task.Template!));
- robotTaskTemplateResponse.ShortDescription = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.ShortDescription, task.Template!);
- robotTaskTemplateResponse.Solution = NormalizeLineEndingsToCrlf(await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.Solution, task.Template!));
- robotTaskTemplateResponse.TnkName = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.TnkName, task.Template!);
- robotTaskTemplateResponse.WorkName = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.WorkName, task.Template!);
- robotTaskTemplateResponse.WorkGroup = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.WorkGroup, task.Template!);
- robotTaskTemplateResponse.ResponseArea = await shortcodesService.ApplyShortcodesAsync(robotTaskTemplateResponse.ResponseArea, task.Template!);
-
- return Ok(new Response(robotTaskTemplateResponse, true));
- }
- case RobotsEnum.ScheduleOrder:
- { // если был запрос на расписание, проверяем у него nextRun, lastRun, обновляем их
- var resultUpdateNextRun = await UpdateNextRunAsync(task);
- if (!resultUpdateNextRun)
- {
- logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}", task.TemplateId);
- return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при расчете NextRun" } }));
- }
-
- //RobotTaskScheduleResponse
- var robotTaskScheduleResponse = mapper.Map(task);
-
- robotTaskScheduleResponse.Timezone = settingsFromDb.EsppScheduleTimezone;
-
- robotTaskScheduleResponse.WorkGroup = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.WorkGroup, task.Template!);
- robotTaskScheduleResponse.ResponseArea = await shortcodesService.ApplyShortcodesAsync(robotTaskScheduleResponse.ResponseArea, task.Template!);
-
- //var nextRunWithRobotTz = nextRunService.GetNextRunWithTimezoneEsppAndResponseArea(task.Template!.NextRun, task.Template!.Job?.Group?.IsResponseAreaTimezone, robotTaskScheduleResponse.ResponseArea);
- //nextRun в часовой зоне УЗ Робота ЕСПП
- var nextRunWithRobotTz = task.Template!.NextRun.Add(nextRunService.GetEsppAccountOffset());
-
- //на всякий случай еще раз проверяем, что дата не устарела и отправляем задание
- if (nextRunWithRobotTz < DateTimeOffset.UtcNow)
- {
- logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}, итоговое значение для робота, меньше чем сейчас {nextRunWithRobotTz}<{now}",
- task.TemplateId, nextRunWithRobotTz, DateTimeOffset.UtcNow);
- return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при расчете NextRun" } }));
- }
-
- robotTaskScheduleResponse.NextStart = EsppScheduleHelpers.GetNextRun(nextRunWithRobotTz);
- robotTaskScheduleResponse.GenerationTime = EsppScheduleHelpers.GetGenerationTime(nextRunWithRobotTz);
-
- return Ok(new Response(robotTaskScheduleResponse, true));
- }
- default:
- break;
- }
-
- return BadRequest();
- }
-
-
- ///
- /// Обоновить NextRun если он устарел
- ///
- ///
- ///
- private async Task UpdateNextRunAsync(RobotConfiguration task)
- {
- var template = task.Template!;
-
- //var nextRun = await esppScheduleTransformService.GetNextDateAsync(template.Job!.GroupId, template!.Job!.Group!.ReferenceDate);
- var nextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, false);
-
- if (!nextRun.HasValue)
- {
- logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun=null, ошибка в расчетах.", template.Id);
- return false;
- }
-
- if (nextRun.Value < DateTimeOffset.UtcNow)
- {
- logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun
- /// Устанавливаем статус "Робот взял в работу", пишем в историю работы роботов инф о начале работ
- ///
- ///
- ///
- private async Task SetInProgressStatusAsync(Guid taskId)
- {
- var config = await robotConfigurationService.GetAsync(taskId);
-
- //изменение статуса робота
- robotConfigurationService.ChangeRobotStatus(RobotStatusEnum.InProgress, config!);
-
- if (!await robotConfigurationService.CommitAsync())
- return false;
-
- //записываем в лог робота
- var history = new RobotHistory
- {
- Id = Guid.NewGuid(),
- HistoryLevel = (int)RobotHistoryLevelEnum.Start,
- TaskStatusCode = config.TaskStatusCode,
- RobotConfigurationId = config.Id,
- RobotIp = clientService.GetClientIp()?.ToString()
- };
-
- if (!await robotHistoryService.CreateAsync(history) || !await robotHistoryService.CommitAsync())
- return false;
-
- return true;
- }
-
-
- ///
- /// Приводит переносы строк в тексте к формату CRLF (\r\n)
- ///
- /// Исходный текст
- /// Текст с унифицированными переносами строк
- private string NormalizeLineEndingsToCrlf(string? text)
- {
- if (string.IsNullOrEmpty(text))
- return string.Empty;
-
- // Заменяем любые варианты переносов (\r\n, \r, \n) на единый \r\n
- return System.Text.RegularExpressions.Regex.Replace(text, @"\r\n|\r|\n", "\r\n");
- }
- }
-}
diff --git a/PARR.API/Infrastructure/Middleware/GlobalExceptionHandler.cs b/PARR.API/Infrastructure/Middleware/GlobalExceptionHandler.cs
index da6ce47e..1b202511 100644
--- a/PARR.API/Infrastructure/Middleware/GlobalExceptionHandler.cs
+++ b/PARR.API/Infrastructure/Middleware/GlobalExceptionHandler.cs
@@ -32,6 +32,7 @@ namespace PARR.API.Infrastructure.Middleware
AlreadyExistsException => StatusCodes.Status409Conflict,
UnauthorizedException => StatusCodes.Status401Unauthorized,
ForbiddenException => StatusCodes.Status403Forbidden,
+ NextRunException => StatusCodes.Status422UnprocessableEntity,
//todo: ---------- другие ошибки ----------
_ => StatusCodes.Status500InternalServerError
};
diff --git a/PARR.API/MappingProfiles/DomainToResponseProfile.cs b/PARR.API/MappingProfiles/DomainToResponseProfile.cs
index aa478cf1..5735968c 100644
--- a/PARR.API/MappingProfiles/DomainToResponseProfile.cs
+++ b/PARR.API/MappingProfiles/DomainToResponseProfile.cs
@@ -5,6 +5,7 @@ using PARR.API.Contracts.V1.Responses.Statistics;
using PARR.API.MappingProfiles.Resolvers;
using PARR.Core.Repositories.Interfaces.Schedule;
using PARR.Domain.DTOs.Matching;
+using PARR.Domain.DTOs.RobotTask;
using PARR.Domain.DTOs.Shortcode;
using PARR.Domain.DTOs.TaskDTO;
using PARR.Domain.DTOs.Workload;
@@ -180,64 +181,73 @@ namespace PARR.API.MappingProfiles
#region RobotConfiguration
// --- RobotConfiguration ---
- CreateMap()
- .Include()
- .Include()
- .ForMember(d => d.TaskId, o => o.MapFrom(s => s.Id));
- CreateMap()
- .ForMember(d => d.IsActive, o => o.MapFrom(s => s.Template!.IsActiveTemplate))
- .ForMember(d => d.ClosingCode, o => o.MapFrom())
- .ForMember(d => d.FullDescription, o => o.MapFrom(s => s.Template!.Job!.Group!.FullDescription))
- .ForMember(d => d.ShortDescription, o => o.MapFrom(s => s.Template!.Job!.Group!.ShortDescription))
- .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.TemplateDuration, o => o.MapFrom(s => s.Template!.Job!.Group!.TemplateDuration))
- .ForMember(d => d.Initiator, o => o.MapFrom())
- .ForMember(d => d.Category, o => o.MapFrom())
- .ForMember(d => d.WorkGroup, o => o.MapFrom(s => s.Template!.Job!.WorkGroupMask))
- .ForMember(d => d.Ek, o => o.MapFrom(s => s.Template!.Unit!.Name))
- .ForMember(d => d.Name, o => o.MapFrom(s => s.Template!.Name))
- .ForMember(d => d.ProcessName, o => o.MapFrom(s => s.Template!.Job!.Tnk!.Subprocess!.Process!.Name))
- .ForMember(d => d.ProcessEsppId, o => o.MapFrom(s => s.Template!.Job!.Tnk!.Subprocess!.Process!.EsppId))
- .ForMember(d => d.SubprocessName, o => o.MapFrom(s => s.Template!.Job!.Tnk!.Subprocess!.Name))
- .ForMember(d => d.SubprocessEsppId, o => o.MapFrom(s => s.Template!.Job!.Tnk!.Subprocess!.EsppId))
- .ForMember(d => d.TnkName, o => o.MapFrom(s => s.Template!.Job!.Tnk!.Name))
- .ForMember(d => d.TnkEsppId, o => o.MapFrom(s =>
- s.Template!.Job!.Tnk!.EsppId.HasValue
- ? s.Template!.Job!.Tnk!.EsppId.Value.ToString()
- : string.Empty
- ))
- .ForMember(d => d.WorkName, o => o.MapFrom(s => s.Template!.Job!.WorkName))
- //.ForMember(d => d.WorkEsppId, o => o.MapFrom(s => s.Template!.Job!.EsppId))
- .ForMember(d => d.ScheduleEsppId, o => o.MapFrom(s => s.Template!.ScheduleEsppId));
+ CreateMap();
+ CreateMap();
- CreateMap()
- .ForMember(d => d.EsppId, o => o.MapFrom(s => s.Template!.ScheduleEsppId))
- .ForMember(d => d.ScheduleName, o => o.MapFrom(s => s.Template!.Name))
- .ForMember(d => d.IsActive, o => o.MapFrom(s => s.Template!.IsActiveSchedule))
- .ForMember(d => d.ResponseArea, o => o.MapFrom(s => s.Template!.Unit!.BaseFields!.ResponseArea))
- .ForMember(d => d.TemplateName, o => o.MapFrom(s => s.Template!.Name))
- .ForMember(d => d.TemplateId, o => o.MapFrom(s => s.Template!.Id))
- .ForMember(d => d.WorkGroup, o => o.MapFrom(s => s.Template!.Job!.WorkGroupMask))
- //.ForMember(d => d.Exclude, o => o.MapFrom())
- .ForMember(d => d.Exclude, o => o.MapFrom(s => s.Template!.Job!.Group!.ScheduleExcludeType!.EsppName))
- //.ForMember(d => d.ExcludeCalendar, o => o.MapFrom())
- .ForMember(d => d.ExcludeCalendar, o => o.MapFrom(s => s.Template!.Job!.Group!.ScheduleExcludeTypeCalendar != null ? s.Template!.Job!.Group!.ScheduleExcludeTypeCalendar.EsppName : null))
- //.ForMember(d => d.Timezone, o => o.MapFrom())
- .ForMember(d => d.RepeatRange, o => o.MapFrom())
- //todo: GenerationTime
- //.ForMember(d => d.GenerationTime, o => o.MapFrom(s => EsppScheduleHelpers.GetGenerationTime(s.Template!.NextRun)))
- //.ForMember(d => d.GenerationTime, o => o.MapFrom())
- //.ForMember(d => d.NextStart, o => o.MapFrom(s => EsppScheduleHelpers.GetNextRun(s.Template!.NextRun)))
- //.ForMember(d => d.NextStart, o => o.MapFrom())
- .ForMember(d => d.ScheduleType, o => o.MapFrom(s =>
- s.Template!.Job!.Group!.EsppSchValues!.First()!.EsppSchTypeConfig!.EsppSchTypeSchedule!.Description))
- .ForMember(d => d.ScheduleTypeCode, o => o.MapFrom(s =>
- s.Template!.Job!.Group!.EsppSchValues!.First()!.EsppSchTypeConfig!.EsppSchTypeSchedule!.Id))
- .ForMember(d => d.Schedule, o => o.MapFrom());
+ CreateMap();
+
+ #region old robot task
+ //CreateMap()
+ // .Include()
+ // .Include()
+ // .ForMember(d => d.TaskId, o => o.MapFrom(s => s.Id));
+
+ //CreateMap()
+ // .ForMember(d => d.IsActive, o => o.MapFrom(s => s.Template!.IsActiveTemplate))
+ // .ForMember(d => d.ClosingCode, o => o.MapFrom())
+ // .ForMember(d => d.FullDescription, o => o.MapFrom(s => s.Template!.Job!.Group!.FullDescription))
+ // .ForMember(d => d.ShortDescription, o => o.MapFrom(s => s.Template!.Job!.Group!.ShortDescription))
+ // .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.TemplateDuration, o => o.MapFrom(s => s.Template!.Job!.Group!.TemplateDuration))
+ // .ForMember(d => d.Initiator, o => o.MapFrom())
+ // .ForMember(d => d.Category, o => o.MapFrom())
+ // .ForMember(d => d.WorkGroup, o => o.MapFrom(s => s.Template!.Job!.WorkGroupMask))
+ // .ForMember(d => d.Ek, o => o.MapFrom(s => s.Template!.Unit!.Name))
+ // .ForMember(d => d.Name, o => o.MapFrom(s => s.Template!.Name))
+ // .ForMember(d => d.ProcessName, o => o.MapFrom(s => s.Template!.Job!.Tnk!.Subprocess!.Process!.Name))
+ // .ForMember(d => d.ProcessEsppId, o => o.MapFrom(s => s.Template!.Job!.Tnk!.Subprocess!.Process!.EsppId))
+ // .ForMember(d => d.SubprocessName, o => o.MapFrom(s => s.Template!.Job!.Tnk!.Subprocess!.Name))
+ // .ForMember(d => d.SubprocessEsppId, o => o.MapFrom(s => s.Template!.Job!.Tnk!.Subprocess!.EsppId))
+ // .ForMember(d => d.TnkName, o => o.MapFrom(s => s.Template!.Job!.Tnk!.Name))
+ // .ForMember(d => d.TnkEsppId, o => o.MapFrom(s =>
+ // s.Template!.Job!.Tnk!.EsppId.HasValue
+ // ? s.Template!.Job!.Tnk!.EsppId.Value.ToString()
+ // : string.Empty
+ // ))
+ // .ForMember(d => d.WorkName, o => o.MapFrom(s => s.Template!.Job!.WorkName))
+ // //.ForMember(d => d.WorkEsppId, o => o.MapFrom(s => s.Template!.Job!.EsppId))
+ // .ForMember(d => d.ScheduleEsppId, o => o.MapFrom(s => s.Template!.ScheduleEsppId));
+
+ //CreateMap()
+ // .ForMember(d => d.EsppId, o => o.MapFrom(s => s.Template!.ScheduleEsppId))
+ // .ForMember(d => d.ScheduleName, o => o.MapFrom(s => s.Template!.Name))
+ // .ForMember(d => d.IsActive, o => o.MapFrom(s => s.Template!.IsActiveSchedule))
+ // .ForMember(d => d.ResponseArea, o => o.MapFrom(s => s.Template!.Unit!.BaseFields!.ResponseArea))
+ // .ForMember(d => d.TemplateName, o => o.MapFrom(s => s.Template!.Name))
+ // .ForMember(d => d.TemplateId, o => o.MapFrom(s => s.Template!.Id))
+ // .ForMember(d => d.WorkGroup, o => o.MapFrom(s => s.Template!.Job!.WorkGroupMask))
+ // //.ForMember(d => d.Exclude, o => o.MapFrom())
+ // .ForMember(d => d.Exclude, o => o.MapFrom(s => s.Template!.Job!.Group!.ScheduleExcludeType!.EsppName))
+ // //.ForMember(d => d.ExcludeCalendar, o => o.MapFrom())
+ // .ForMember(d => d.ExcludeCalendar, o => o.MapFrom(s => s.Template!.Job!.Group!.ScheduleExcludeTypeCalendar != null ? s.Template!.Job!.Group!.ScheduleExcludeTypeCalendar.EsppName : null))
+ // //.ForMember(d => d.Timezone, o => o.MapFrom())
+ // .ForMember(d => d.RepeatRange, o => o.MapFrom())
+ // //todo: GenerationTime
+ // //.ForMember(d => d.GenerationTime, o => o.MapFrom(s => EsppScheduleHelpers.GetGenerationTime(s.Template!.NextRun)))
+ // //.ForMember(d => d.GenerationTime, o => o.MapFrom())
+ // //.ForMember(d => d.NextStart, o => o.MapFrom(s => EsppScheduleHelpers.GetNextRun(s.Template!.NextRun)))
+ // //.ForMember(d => d.NextStart, o => o.MapFrom())
+ // .ForMember(d => d.ScheduleType, o => o.MapFrom(s =>
+ // s.Template!.Job!.Group!.EsppSchValues!.First()!.EsppSchTypeConfig!.EsppSchTypeSchedule!.Description))
+ // .ForMember(d => d.ScheduleTypeCode, o => o.MapFrom(s =>
+ // s.Template!.Job!.Group!.EsppSchValues!.First()!.EsppSchTypeConfig!.EsppSchTypeSchedule!.Id))
+ // .ForMember(d => d.Schedule, o => o.MapFrom());
+
+ #endregion
CreateMap();
@@ -460,7 +470,7 @@ namespace PARR.API.MappingProfiles
//CreateMap();
CreateMap();
-
+
// ---
CreateMap();
diff --git a/PARR.Core/Infrastructure/Mapping/RobotTask/Resolvers/RobotTaskScheduleSchResolver.cs b/PARR.Core/Infrastructure/Mapping/RobotTask/Resolvers/RobotTaskScheduleSchResolver.cs
new file mode 100644
index 00000000..5b40620c
--- /dev/null
+++ b/PARR.Core/Infrastructure/Mapping/RobotTask/Resolvers/RobotTaskScheduleSchResolver.cs
@@ -0,0 +1,46 @@
+using AutoMapper;
+using Microsoft.Extensions.Logging;
+using PARR.Core.Repositories.Interfaces.Schedule;
+using PARR.Domain.DTOs.RobotTask;
+using PARR.Domain.Entities;
+
+namespace PARR.Core.Infrastructure.Mapping.RobotTask.Resolvers
+{
+ public class RobotTaskScheduleSchResolver : IValueResolver?>
+ {
+ private readonly IEsppSchTypeConfigRepository esppConfigRepository;
+ private readonly ILogger logger;
+
+ public RobotTaskScheduleSchResolver(
+ IEsppSchTypeConfigRepository esppConfigRepository,
+ ILogger logger
+ )
+ {
+ this.esppConfigRepository = esppConfigRepository;
+ this.logger = logger;
+ }
+
+ public List? Resolve(RobotConfiguration source, RobotTaskSchedule destination, List? destMember, ResolutionContext context)
+ {
+ var jobGroupId = source.Template!.Job!.GroupId;
+
+ var schedule = esppConfigRepository.GetEsppScheduleDto(jobGroupId);
+
+ if (schedule == null)
+ {
+ logger.LogError("RobotTaskScheduleSchResolver: Не смог замапить расписание для робота, так как оно null. JobGroupId: {JobGroupId}", jobGroupId);
+ return null;
+ }
+
+ var response = schedule.Values.Select(t => new RobotTaskScheduleSch
+ {
+ Order = t.Order,
+ Type = t.Type.Name,
+ TypeCode = t.Type.Id,
+ Value = t.Value.Value
+ }).OrderBy(t => t.Order).ToList();
+
+ return response;
+ }
+ }
+}
diff --git a/PARR.Core/Infrastructure/Mapping/RobotTask/RobotTaskMappingProfile.cs b/PARR.Core/Infrastructure/Mapping/RobotTask/RobotTaskMappingProfile.cs
new file mode 100644
index 00000000..0bd37e14
--- /dev/null
+++ b/PARR.Core/Infrastructure/Mapping/RobotTask/RobotTaskMappingProfile.cs
@@ -0,0 +1,70 @@
+using AutoMapper;
+using PARR.Core.Infrastructure.Mapping.RobotTask.Resolvers;
+using PARR.Domain.DTOs.RobotTask;
+using PARR.Domain.Entities;
+using PARR.Domain.Settings;
+
+namespace PARR.Core.Infrastructure.Mapping.RobotTask
+{
+ ///
+ /// Маппинг RobotTask
+ ///
+ internal class RobotTaskMappingProfile : Profile
+ {
+ public RobotTaskMappingProfile()
+ {
+
+ CreateMap()
+ .Include()
+ .Include()
+ .ForMember(d => d.TaskId, o => o.MapFrom(s => s.Id));
+
+ // Задание роботам, шаблоны
+ CreateMap()
+ .ForMember(d => d.IsActive, o => o.MapFrom(s => s.Template!.IsActiveTemplate))
+ //.ForMember(d => d.ClosingCode, o => o.MapFrom())
+ .ForMember(d => d.FullDescription, o => o.MapFrom(s => s.Template!.Job!.Group!.FullDescription))
+ .ForMember(d => d.ShortDescription, o => o.MapFrom(s => s.Template!.Job!.Group!.ShortDescription))
+ .ForMember(d => d.Solution, o => o.MapFrom(s => s.Template!.Job!.Group!.Solution))
+ //ЗО берем у группы а не у хоста
+ .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())
+ //.ForMember(d => d.Category, o => o.MapFrom())
+ .ForMember(d => d.WorkGroup, o => o.MapFrom(s => s.Template!.Job!.WorkGroupMask))
+ .ForMember(d => d.Ek, o => o.MapFrom(s => s.Template!.Unit!.Name))
+ .ForMember(d => d.Name, o => o.MapFrom(s => s.Template!.Name))
+ .ForMember(d => d.ProcessName, o => o.MapFrom(s => s.Template!.Job!.Tnk!.Subprocess!.Process!.Name))
+ .ForMember(d => d.ProcessEsppId, o => o.MapFrom(s => s.Template!.Job!.Tnk!.Subprocess!.Process!.EsppId))
+ .ForMember(d => d.SubprocessName, o => o.MapFrom(s => s.Template!.Job!.Tnk!.Subprocess!.Name))
+ .ForMember(d => d.SubprocessEsppId, o => o.MapFrom(s => s.Template!.Job!.Tnk!.Subprocess!.EsppId))
+ .ForMember(d => d.TnkName, o => o.MapFrom(s => s.Template!.Job!.Tnk!.Name))
+ .ForMember(d => d.TnkEsppId, o => o.MapFrom(s =>
+ s.Template!.Job!.Tnk!.EsppId.HasValue
+ ? s.Template!.Job!.Tnk!.EsppId.Value.ToString()
+ : string.Empty
+ ))
+ .ForMember(d => d.WorkName, o => o.MapFrom(s => s.Template!.Job!.WorkName))
+ .ForMember(d => d.ScheduleEsppId, o => o.MapFrom(s => s.Template!.ScheduleEsppId));
+
+
+ // Задание роботам, расписания
+ CreateMap()
+ .ForMember(d => d.EsppId, o => o.MapFrom(s => s.Template!.ScheduleEsppId))
+ .ForMember(d => d.ScheduleName, o => o.MapFrom(s => s.Template!.Name))
+ .ForMember(d => d.IsActive, o => o.MapFrom(s => s.Template!.IsActiveSchedule))
+ .ForMember(d => d.ResponseArea, o => o.MapFrom(s => s.Template!.Unit!.BaseFields!.ResponseArea))
+ .ForMember(d => d.TemplateName, o => o.MapFrom(s => s.Template!.Name))
+ .ForMember(d => d.TemplateId, o => o.MapFrom(s => s.Template!.Id))
+ .ForMember(d => d.WorkGroup, o => o.MapFrom(s => s.Template!.Job!.WorkGroupMask))
+ .ForMember(d => d.Exclude, o => o.MapFrom(s => s.Template!.Job!.Group!.ScheduleExcludeType!.EsppName))
+ .ForMember(d => d.ExcludeCalendar, o => o.MapFrom(s => s.Template!.Job!.Group!.ScheduleExcludeTypeCalendar != null ? s.Template!.Job!.Group!.ScheduleExcludeTypeCalendar.EsppName : null))
+ //.ForMember(d => d.RepeatRange, o => o.MapFrom())
+ .ForMember(d => d.ScheduleType, o => o.MapFrom(s =>
+ s.Template!.Job!.Group!.EsppSchValues!.First()!.EsppSchTypeConfig!.EsppSchTypeSchedule!.Description))
+ .ForMember(d => d.ScheduleTypeCode, o => o.MapFrom(s =>
+ s.Template!.Job!.Group!.EsppSchValues!.First()!.EsppSchTypeConfig!.EsppSchTypeSchedule!.Id))
+ .ForMember(d => d.Schedule, o => o.MapFrom());
+ }
+ }
+}
diff --git a/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs b/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs
index 5650b697..c3dd8a0d 100644
--- a/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs
+++ b/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs
@@ -1,9 +1,14 @@
-using InfluxDB.Client.Api.Domain;
+using AutoMapper;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
+using PARR.BLL.Helpers;
using PARR.Core.Repositories.Interfaces;
+using PARR.Core.Services.NextRunServices;
using PARR.Core.Services.RobotTask.Interfaces;
+using PARR.Core.Services.Shortcodes;
+using PARR.Domain.DTOs.RobotTask;
using PARR.Domain.Entities;
+using PARR.Domain.Entities.Base.History;
using PARR.Domain.Enums;
using PARR.Domain.Exceptions;
using PARR.Domain.Settings;
@@ -21,21 +26,102 @@ namespace PARR.Core.Services.RobotTask.Implementations
private readonly IRobotConfigurationRepository robotConfigurationRepository;
private readonly SettingsFromDb settingsFromDb;
private readonly IRobotHistoryRepository robotHistoryRepository;
+ private readonly IMapper mapper;
+ private readonly IShortcodesService shortcodesService;
+ private readonly INextRunService nextRunService;
public RobotTaskService(
ILogger logger,
IRobotConfigurationRepository robotConfigurationRepository,
SettingsFromDb settingsFromDb,
- IRobotHistoryRepository robotHistoryRepository
+ IRobotHistoryRepository robotHistoryRepository,
+ IMapper mapper,
+ IShortcodesService shortcodesService,
+ INextRunService nextRunService
)
{
this.logger = logger;
this.robotConfigurationRepository = robotConfigurationRepository;
this.settingsFromDb = settingsFromDb;
this.robotHistoryRepository = robotHistoryRepository;
+ this.mapper = mapper;
+ this.shortcodesService = shortcodesService;
+ this.nextRunService = nextRunService;
}
- public async Task GetTaskAsync(RobotsEnum robotCode, TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp)
+
+ public async Task GetTemplateTaskAsync(TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp, string? robotId)
+ {
+ var templateTask = await GetTaskAsync(RobotsEnum.TemplateOrder, taskStatusCode, acquireTask, robotIp, robotId);
+
+ var task = mapper.Map(templateTask);
+
+ task = task with { FullDescription = NormalizeLineEndingsToCrlf(await shortcodesService.ApplyShortcodesAsync(task.FullDescription, templateTask.Template!)) };
+ task = task with { ShortDescription = await shortcodesService.ApplyShortcodesAsync(task.ShortDescription, templateTask.Template!) };
+ task = task with { Solution = NormalizeLineEndingsToCrlf(await shortcodesService.ApplyShortcodesAsync(task.Solution, templateTask.Template!)) };
+ task = task with { TnkName = await shortcodesService.ApplyShortcodesAsync(task.TnkName, templateTask.Template!) };
+ task = task with { WorkName = await shortcodesService.ApplyShortcodesAsync(task.WorkName, templateTask.Template!) };
+ task = task with { WorkGroup = await shortcodesService.ApplyShortcodesAsync(task.WorkGroup, templateTask.Template!) };
+ task = task with { ResponseArea = await shortcodesService.ApplyShortcodesAsync(task.ResponseArea, templateTask.Template!) };
+
+ task = task with { ClosingCode = settingsFromDb.ClosingCode };
+ task = task with { Initiator = settingsFromDb.Initiator };
+ task = task with { Category = settingsFromDb.Category };
+
+ return task;
+ }
+
+
+ public async Task GetScheduleTaskAsync(TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp, string? robotId, IHistoryInitiator historyInitiator)
+ {
+ var scheduleTask = await GetTaskAsync(RobotsEnum.ScheduleOrder, taskStatusCode, acquireTask, robotIp, robotId);
+
+ // Проверяем nextRun, lastRun, обновляем их
+
+ var resultUpdateNextRun = await UpdateNextRunAsync(scheduleTask, historyInitiator);
+ if (!resultUpdateNextRun)
+ {
+ logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}", scheduleTask.TemplateId);
+ throw new NextRunException($"Ошибка при расчете NextRun для templateId: {scheduleTask.TemplateId}");
+ }
+
+ var task = mapper.Map(scheduleTask);
+
+ task = task with { Timezone = settingsFromDb.EsppScheduleTimezone };
+ task = task with { WorkGroup = await shortcodesService.ApplyShortcodesAsync(task.WorkGroup, scheduleTask.Template!) };
+ task = task with { ResponseArea = await shortcodesService.ApplyShortcodesAsync(task.ResponseArea, scheduleTask.Template!) };
+
+ //nextRun в часовой зоне УЗ Робота ЕСПП
+ var nextRunWithRobotTz = scheduleTask.Template!.NextRun.Add(nextRunService.GetEsppAccountOffset());
+ //на всякий случай еще раз проверяем, что дата не устарела и отправляем задание
+ if (nextRunWithRobotTz < DateTimeOffset.UtcNow)
+ {
+ logger.LogError("Ошибка при расчете NextRun для templateId: {templateId}, итоговое значение для робота, меньше чем сейчас {nextRunWithRobotTz}<{now}",
+ task.TemplateId, nextRunWithRobotTz, DateTimeOffset.UtcNow);
+ throw new NextRunException($"Ошибка при расчете NextRun для templateId: {scheduleTask.TemplateId}");
+ }
+
+ task = task with { NextStart = EsppScheduleHelpers.GetNextRun(nextRunWithRobotTz) };
+ task = task with { GenerationTime = EsppScheduleHelpers.GetGenerationTime(nextRunWithRobotTz) };
+
+ task = task with { RepeatRange = settingsFromDb.ScheduleRepeatRange };
+ task = task with { };
+
+ return task;
+ }
+
+
+ ///
+ /// Получить задачу для робота.
+ /// Метод может генерировать исключения.
+ ///
+ ///
+ ///
+ /// Взять в работу
+ ///
+ ///
+ ///
+ private async Task GetTaskAsync(RobotsEnum robotCode, TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp, string? robotId)
{
// 1. Ищем все задания с превышенным кол-вом попыток и просроченным временем, ставим им статус ошибки
await robotConfigurationRepository.MarkExpiredTasksAsFailedAsync(settingsFromDb.RobotAttemptsNumber, settingsFromDb.RobotWaitTime);
@@ -52,10 +138,10 @@ namespace PARR.Core.Services.RobotTask.Implementations
if (acquireTask)
{
// Берем задание в работу, устанавливаем ей статус "В работе"
- acquiredTaskId = await AcquireTaskAsync(availableTasks, robotIp);
+ acquiredTaskId = await AcquireTaskAsync(availableTasks, robotIp, robotId);
if (acquiredTaskId == null)
- throw new NotFoundException($"Не удалось взять ни одну из доступных задач ({availableTasks.Count}) в работу");
+ throw new Exception($"Не удалось взять ни одну из доступных задач ({availableTasks.Count}) в работу");
}
else
{
@@ -65,13 +151,10 @@ namespace PARR.Core.Services.RobotTask.Implementations
}
- //3. Готовим модель ответа
+ //3. Получаем задачу со всеми нужными инклудами в зависимости от типа робота
var task = await GetTaskWithAllDataAsync(acquiredTaskId.Value, robotCode);
- //todo:
-
-
-
+ return task;
}
@@ -138,7 +221,7 @@ namespace PARR.Core.Services.RobotTask.Implementations
///
///
///
- private async Task AcquireTaskAsync(List tasks, string? robotIp)
+ private async Task AcquireTaskAsync(List tasks, string? robotIp, string? robotId)
{
foreach (var taskId in tasks)
{
@@ -159,6 +242,8 @@ namespace PARR.Core.Services.RobotTask.Implementations
TaskStatusCode = task.TaskStatusCode,
RobotConfigurationId = taskId,
RobotIp = robotIp
+ //todo: Тут добавить RobotId
+ //RobotId = robotId
};
if (!await robotHistoryRepository.CreateAsync(history) || !await robotHistoryRepository.CommitAsync())
@@ -186,19 +271,118 @@ namespace PARR.Core.Services.RobotTask.Implementations
///
private async Task GetTaskWithAllDataAsync(Guid taskId, RobotsEnum robotCode)
{
- var query = robotConfigurationRepository.Get()
- .AsNoTracking()
+ IQueryable query = robotConfigurationRepository.Get()
+ //.AsNoTracking() // нужно обязательно трекать, так как может измениться nextRun и его нужно будет сохранить
.AsSingleQuery()
- //todo: тут общие инклуды
- ;
+ // Общие инклуды для шаблонов и расписаний
+ // Units
+ .Include(t => t.Template)
+ .ThenInclude(t => t!.Unit)
+ .ThenInclude(t => t!.UnitValues)
+ .ThenInclude(t => t!.Field)
+ .Include(t => t.Template)
+ .ThenInclude(t => t!.Unit)
+ .ThenInclude(t => t!.UnitValues)
+ .ThenInclude(t => t!.Value)
+ .Include(t => t.Template)
+ .ThenInclude(t => t!.UnitsInTemplate)
+ // Группа с типом
+ .Include(t => t.Template)
+ .ThenInclude(t => t!.Job)
+ .ThenInclude(t => t!.Group)
+ .ThenInclude(t => t!.GroupType)
+ // ТНК
+ .Include(t => t.Template)
+ .ThenInclude(t => t!.Job)
+ .ThenInclude(t => t!.Tnk)
+ .ThenInclude(t => t!.Subprocess)
+ .ThenInclude(t => t!.Process);
- if(robotCode== RobotsEnum.ScheduleOrder)
+ if (robotCode == RobotsEnum.ScheduleOrder)
{
- // тут инклуды только для расписаний
+ // Инклуды только для расписаний
+ query = query
+ // Расписание
+ .Include(t => t.Template)
+ .ThenInclude(t => t!.Job)
+ .ThenInclude(t => t!.Group)
+ .ThenInclude(t => t!.EsppSchValues)
+ .ThenInclude(t => t!.EsppSchTypeConfig)
+ .ThenInclude(t => t!.EsppSchTypeSchedule)
+ // Исключения по типу
+ .Include(t => t.Template)
+ .ThenInclude(t => t!.Job)
+ .ThenInclude(t => t!.Group)
+ .ThenInclude(t => t!.ScheduleExcludeType)
+ // Исключения по календарю
+ .Include(t => t.Template)
+ .ThenInclude(t => t!.Job)
+ .ThenInclude(t => t!.Group)
+ .ThenInclude(t => t!.ScheduleExcludeTypeCalendar);
}
return await query.FirstAsync(t => t.Id == taskId);
}
+
+ ///
+ /// Приводит переносы строк в тексте к формату CRLF (\r\n)
+ ///
+ /// Исходный текст
+ /// Текст с унифицированными переносами строк
+ private string NormalizeLineEndingsToCrlf(string? text)
+ {
+ if (string.IsNullOrEmpty(text))
+ return string.Empty;
+
+ // Заменяем любые варианты переносов (\r\n, \r, \n) на единый \r\n
+ return System.Text.RegularExpressions.Regex.Replace(text, @"\r\n|\r|\n", "\r\n");
+ }
+
+
+ ///
+ /// Обоновить NextRun если он устарел
+ ///
+ ///
+ ///
+ private async Task UpdateNextRunAsync(RobotConfiguration task, IHistoryInitiator historyInitiator)
+ {
+ var template = task.Template!;
+
+ //var nextRun = await esppScheduleTransformService.GetNextDateAsync(template.Job!.GroupId, template!.Job!.Group!.ReferenceDate);
+ var nextRun = await nextRunService.GetNextRunForTemplateAsync(template.Id, false);
+
+ if (!nextRun.HasValue)
+ {
+ logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun=null, ошибка в расчетах.", template.Id);
+ return false;
+ }
+
+ if (nextRun.Value < DateTimeOffset.UtcNow)
+ {
+ logger.LogError("При обновлении nextRun для шаблона {templateId}, расчитанный nextRun
/// Управление заданиями роботам
///
public interface IRobotTaskService
{
+
+ ///
+ /// Получить задание роботу - шаблоны.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task GetTemplateTaskAsync(TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp, string? robotId);
+
+
+ ///
+ /// Получить задание роботу - расписания.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ Task GetScheduleTaskAsync(TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp, string? robotId, IHistoryInitiator historyInitiator);
}
}
diff --git a/PARR.Domain/DTOs/RobotTask/RobotTaskBase.cs b/PARR.Domain/DTOs/RobotTask/RobotTaskBase.cs
new file mode 100644
index 00000000..5020073d
--- /dev/null
+++ b/PARR.Domain/DTOs/RobotTask/RobotTaskBase.cs
@@ -0,0 +1,13 @@
+namespace PARR.Domain.DTOs.RobotTask
+{
+ ///
+ /// Базовая модель для заданий роботам
+ ///
+ public record RobotTaskBase
+ {
+ ///
+ /// RobotConfigurationId
+ ///
+ public Guid TaskId { get; init; }
+ }
+}
diff --git a/PARR.Domain/DTOs/RobotTask/RobotTaskSchedule.cs b/PARR.Domain/DTOs/RobotTask/RobotTaskSchedule.cs
new file mode 100644
index 00000000..3b3fac9e
--- /dev/null
+++ b/PARR.Domain/DTOs/RobotTask/RobotTaskSchedule.cs
@@ -0,0 +1,67 @@
+namespace PARR.Domain.DTOs.RobotTask
+{
+ ///
+ /// Модель для задания роботам - расписания
+ ///
+ public record RobotTaskSchedule : RobotTaskBase
+ {
+ ///
+ /// ИД расписания из ЕСПП. При создании, его не будет
+ ///
+ public string? EsppId { get; init; }
+
+ public required string ScheduleName { get; init; }
+
+ public bool IsActive { get; init; }
+
+ public required string ResponseArea { get; init; }
+
+ public required string TemplateName { get; init; }
+
+ public Guid TemplateId { get; init; }
+
+ public required string WorkGroup { get; init; }
+
+ public string? Description { get; init; }
+
+ public required string Exclude { get; init; }
+
+ // сделал возможный null, так как может в будущем это поле может не заполняться
+ public string? ExcludeCalendar { get; init; }
+
+ public required string Timezone { get; init; }
+
+ public required string RepeatRange { get; init; }
+
+ ///
+ /// Время создания наряда
+ ///
+ public required string GenerationTime { get; init; }
+
+ ///
+ /// Следующее срабатывание
+ ///
+ public required string NextStart { get; init; }
+
+ ///
+ /// Тип задания: Регулярно, еженедельно...
+ ///
+ public required string ScheduleType { get; init; }
+
+ ///
+ /// Код задания
+ ///
+ public int ScheduleTypeCode { get; init; }
+
+ public List? Schedule { get; init; }
+ }
+
+ public record RobotTaskScheduleSch
+ {
+ public required string Type { get; init; }
+ public int TypeCode { get; init; }
+ public required string Value { get; init; }
+ public int Order { get; init; }
+ }
+
+}
diff --git a/PARR.Domain/DTOs/RobotTask/RobotTaskTemplate.cs b/PARR.Domain/DTOs/RobotTask/RobotTaskTemplate.cs
new file mode 100644
index 00000000..8b568e17
--- /dev/null
+++ b/PARR.Domain/DTOs/RobotTask/RobotTaskTemplate.cs
@@ -0,0 +1,53 @@
+namespace PARR.Domain.DTOs.RobotTask
+{
+ ///
+ /// Модель для задания роботам - шаблоны
+ ///
+ public record RobotTaskTemplate : RobotTaskBase
+ {
+ ///
+ /// ИД расписания из ЕСПП. При создании, его не будет
+ /// (при создании шаблона, это поле не смотрим, используем только при изменении)
+ ///
+ public string? ScheduleEsppId { get; init; }
+
+ public bool IsActive { get; init; }
+
+ public required string ClosingCode { get; init; }
+
+ public required string FullDescription { get; init; }
+
+ public required string ShortDescription { get; init; }
+
+ public required string Solution { get; init; }
+
+ public required string ResponseArea { get; init; }
+
+ public required string TemplateDuration { get; init; }
+
+ public required string Initiator { get; init; }
+
+ public required string WorkGroup { get; init; }
+
+ public required string Ek { get; init; }
+
+ public required string Name { get; init; }
+
+ public required string Category { get; init; }
+
+
+ public required string ProcessName { get; init; }
+
+ public required string ProcessEsppId { get; init; }
+
+ public required string SubprocessName { get; init; }
+
+ public required string SubprocessEsppId { get; init; }
+
+ public required string TnkName { get; init; }
+
+ public required string TnkEsppId { get; init; }
+
+ public required string WorkName { get; init; }
+ }
+}
diff --git a/PARR.Domain/Exceptions/CustomExceptions.cs b/PARR.Domain/Exceptions/CustomExceptions.cs
index 0cc6e079..8cd10f0d 100644
--- a/PARR.Domain/Exceptions/CustomExceptions.cs
+++ b/PARR.Domain/Exceptions/CustomExceptions.cs
@@ -93,4 +93,17 @@
///
public ForbiddenException(string message) : base(message) { }
}
+
+ ///
+ /// Ошибка при расчете NextRun. 422 (запрос валиден, но бизнес логика не смогла нормально его обработать)
+ ///
+ public class NextRunException : BaseException
+ {
+ ///
+ /// Ошибка при расчете NextRun
+ ///
+ ///
+ public NextRunException(string message) : base(message){}
+ }
+
}