diff --git a/PARR.API/Contracts/V1/Requests/Queries/RobotTaskQuery.cs b/PARR.API/Contracts/V1/Requests/Queries/RobotTaskQuery.cs
index c503ff61..84a040a9 100644
--- a/PARR.API/Contracts/V1/Requests/Queries/RobotTaskQuery.cs
+++ b/PARR.API/Contracts/V1/Requests/Queries/RobotTaskQuery.cs
@@ -1,10 +1,10 @@
namespace PARR.API.Contracts.V1.Requests.Queries
{
- public class RobotTaskQuery
+ public record RobotTaskQuery
{
///
/// Установить статус задания - InProgress (Робот взял в работу)
///
- public bool? SetInProgressStatus { get; set; }
+ public bool? SetInProgressStatus { get; init; }
}
}
diff --git a/PARR.API/Controllers/V1/RobotTaskController.cs b/PARR.API/Controllers/V1/RobotTaskController.cs
index 4293d917..1b6182cb 100644
--- a/PARR.API/Controllers/V1/RobotTaskController.cs
+++ b/PARR.API/Controllers/V1/RobotTaskController.cs
@@ -1,336 +1,86 @@
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.RobotTask.Interfaces;
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 RobotTaskController : BaseApiController
{
private readonly IMapper mapper;
private readonly SettingsFromDb settingsFromDb;
private readonly IRobotConfigurationRepository robotConfigurationService;
- //private readonly IEsppScheduleTransformService esppScheduleTransformService;
- private readonly ILogger logger;
+ private readonly ILogger logger;
private readonly IClientService clientService;
private readonly IRobotHistoryRepository robotHistoryService;
private readonly IShortcodesService shortcodesService;
private readonly INextRunService nextRunService;
- //private readonly IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService;
+ private readonly IRobotTaskService robotTaskService;
public RobotTaskController(
IMapper mapper,
SettingsFromDb settingsFromDb,
IRobotConfigurationRepository robotConfigurationService,
- //IEsppScheduleTransformService esppScheduleTransformService,
- ILogger logger,
+ ILogger logger,
IClientService clientService,
IRobotHistoryRepository robotHistoryService,
IShortcodesService shortcodesService,
- INextRunService nextRunService
- //IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService
+ INextRunService nextRunService,
+
+
+ IRobotTaskService robotTaskService
)
{
this.mapper = mapper;
this.settingsFromDb = settingsFromDb;
this.robotConfigurationService = robotConfigurationService;
- //this.esppScheduleTransformService = esppScheduleTransformService;
this.logger = logger;
this.clientService = clientService;
this.robotHistoryService = robotHistoryService;
this.shortcodesService = shortcodesService;
this.nextRunService = nextRunService;
- //this.scheduleResponseAreaTimeOffsetService = scheduleResponseAreaTimeOffsetService;
+ this.robotTaskService = robotTaskService;
}
///
- /// Получить задание для робота по коду робота и по статусу задания
+ /// Получить задание для робота по коду робота и статусу задания
///
- ///
- ///
+ /// Код робота
+ /// Статус задания
+ /// Параметры
///
[HttpGet(ApiRoutes.RobotTask.GetByRobotAndStatusTask)]
public async Task GetByRobotAndStatusTask([FromRoute] RobotsEnum robotCode, [FromRoute] TaskStatusEnum taskStatusCode, [FromQuery] RobotTaskQuery requestQuery)
{
- //Ищем все задания с превышенным кол-вом попыток и с просроченным временем и ставим им статус ошибки
- await robotConfigurationService.FindUnfulfilledTaskAndSetRobotErrorStatusAsync(settingsFromDb.RobotAttemptsNumber, settingsFromDb.RobotWaitTime);
+
+ //robotTaskService
- 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
- .AsSingleQuery()
- .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();
+ return Ok();
}
- ///
- /// Обоновить 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/Controllers/V1/RobotTaskOldController.cs b/PARR.API/Controllers/V1/RobotTaskOldController.cs
new file mode 100644
index 00000000..e522c2d8
--- /dev/null
+++ b/PARR.API/Controllers/V1/RobotTaskOldController.cs
@@ -0,0 +1,330 @@
+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.Core/DependencyInjection.cs b/PARR.Core/DependencyInjection.cs
index 2d5a2249..ef703236 100644
--- a/PARR.Core/DependencyInjection.cs
+++ b/PARR.Core/DependencyInjection.cs
@@ -7,6 +7,8 @@ using PARR.Core.Common.Interfaces;
using PARR.Core.Services.MatchingStatusService;
using PARR.Core.Services.NextRunServices;
using PARR.Core.Services.NextRunServices.Subservices;
+using PARR.Core.Services.RobotTask.Implementations;
+using PARR.Core.Services.RobotTask.Interfaces;
using PARR.Core.Services.Shortcodes;
using PARR.Core.Services.Shortcodes.Handlers;
using PARR.Core.Services.TaskServices.Handlers;
@@ -78,8 +80,6 @@ namespace PARR.Core
#endregion
- #region Services
-
#region Shortсodes
services.AddScoped();
@@ -95,10 +95,13 @@ namespace PARR.Core
services.AddScoped();
#endregion
+ #region Services
+
services.AddTransient();
- //services.AddScoped();
+ services.AddScoped();
+ //services.AddScoped();
#endregion
diff --git a/PARR.Core/Repositories/Interfaces/IRobotConfigurationRepository.cs b/PARR.Core/Repositories/Interfaces/IRobotConfigurationRepository.cs
index 06b019c8..5bc2d65a 100644
--- a/PARR.Core/Repositories/Interfaces/IRobotConfigurationRepository.cs
+++ b/PARR.Core/Repositories/Interfaces/IRobotConfigurationRepository.cs
@@ -26,7 +26,7 @@ namespace PARR.Core.Repositories.Interfaces
///
///
///
- Task FindUnfulfilledTaskAndSetRobotErrorStatusAsync(int robotAttemptsNumber, TimeSpan robotWaitTime);
+ Task MarkExpiredTasksAsFailedAsync(int robotAttemptsNumber, TimeSpan robotWaitTime);
///
/// Получить конфигурацию из шаблона по RobotCode. У шаблона обязательно должен быть Include таблицы RobotConfiguration
@@ -42,5 +42,12 @@ namespace PARR.Core.Repositories.Interfaces
///
/// true - если изменил статус, false - нельзя изменить статус
bool SetUpdateTaskStatusIfAllow(RobotConfiguration configuration);
+
+ ///
+ /// Атомарно установить статус задачи "В работе" если текущий статус "В ожидании".
+ ///
+ ///
+ ///
+ Task SetInProgressStatusAsync(Guid id);
}
}
diff --git a/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs b/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs
new file mode 100644
index 00000000..5650b697
--- /dev/null
+++ b/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs
@@ -0,0 +1,204 @@
+using InfluxDB.Client.Api.Domain;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using PARR.Core.Repositories.Interfaces;
+using PARR.Core.Services.RobotTask.Interfaces;
+using PARR.Domain.Entities;
+using PARR.Domain.Enums;
+using PARR.Domain.Exceptions;
+using PARR.Domain.Settings;
+
+namespace PARR.Core.Services.RobotTask.Implementations
+{
+ internal class RobotTaskService : IRobotTaskService
+ {
+ ///
+ /// Количество заданий которые рассматриваем для взятия в работу.
+ ///
+ private readonly int TakeTasks = 10;
+
+ private readonly ILogger logger;
+ private readonly IRobotConfigurationRepository robotConfigurationRepository;
+ private readonly SettingsFromDb settingsFromDb;
+ private readonly IRobotHistoryRepository robotHistoryRepository;
+
+ public RobotTaskService(
+ ILogger logger,
+ IRobotConfigurationRepository robotConfigurationRepository,
+ SettingsFromDb settingsFromDb,
+ IRobotHistoryRepository robotHistoryRepository
+ )
+ {
+ this.logger = logger;
+ this.robotConfigurationRepository = robotConfigurationRepository;
+ this.settingsFromDb = settingsFromDb;
+ this.robotHistoryRepository = robotHistoryRepository;
+ }
+
+ public async Task GetTaskAsync(RobotsEnum robotCode, TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp)
+ {
+ // 1. Ищем все задания с превышенным кол-вом попыток и просроченным временем, ставим им статус ошибки
+ await robotConfigurationRepository.MarkExpiredTasksAsFailedAsync(settingsFromDb.RobotAttemptsNumber, settingsFromDb.RobotWaitTime);
+
+
+ // 2. Ищем доступные задания
+ var availableTasks = await GetAvailableTasksAsync(robotCode, taskStatusCode);
+
+ if (availableTasks.Count == 0)
+ throw new NotFoundException("Нет доступных заданий для робота");
+
+ Guid? acquiredTaskId = null;
+
+ if (acquireTask)
+ {
+ // Берем задание в работу, устанавливаем ей статус "В работе"
+ acquiredTaskId = await AcquireTaskAsync(availableTasks, robotIp);
+
+ if (acquiredTaskId == null)
+ throw new NotFoundException($"Не удалось взять ни одну из доступных задач ({availableTasks.Count}) в работу");
+ }
+ else
+ {
+ // Берем первую задачу из списка доступных
+ acquiredTaskId = availableTasks.First();
+ logger.LogDebug("Задача не требует захвата, взята первая из доступных: {TaskId}", acquiredTaskId);
+ }
+
+
+ //3. Готовим модель ответа
+ var task = await GetTaskWithAllDataAsync(acquiredTaskId.Value, robotCode);
+
+ //todo:
+
+
+
+ }
+
+
+ ///
+ /// Получить список возможных заданий для взятия в работу.
+ /// Кол-во заданй ограничено переменной TakeTasks
+ ///
+ ///
+ ///
+ ///
+ private async Task> GetAvailableTasksAsync(RobotsEnum robotCode, TaskStatusEnum taskStatusCode)
+ {
+ var query = robotConfigurationRepository.Get()
+ .AsNoTracking()
+ .Where(t => t.RobotCode == (int)robotCode && t.TaskStatusCode == (int)taskStatusCode);
+
+ // Если это задание для робота расписаний
+ if (robotCode == RobotsEnum.ScheduleOrder)
+ {
+ // Выбираем только записи с созданными шаблонами (у которых статус 30), а только потом ищем у них расписания
+ var createdTemplates = robotConfigurationRepository.Get()
+ .Where(t => t.RobotCode == (int)RobotsEnum.TemplateOrder && t.TaskStatusCode == (int)TaskStatusEnum.Ok)
+ .Select(t => t.TemplateId);
+
+ query = query.Where(t => createdTemplates.Contains(t.TemplateId));
+ }
+
+ // Сортируем по nextRun, чтобы те, у кого nextRun ближе к текущей, выполнились скорее
+ query = query.OrderBy(t => t.Template!.NextRun).ThenBy(t => t.Template!.IsActiveSchedule).ThenBy(t => t.Template!.IsActiveTemplate);
+
+ // Кандидаты заданий
+ var tasks = new List();
+
+ // Ещем первые 10 заданий в статусе ОЖИДАНИЕ
+ tasks = await query.Where(t => t.RobotStatusCode == (int)RobotStatusEnum.Wait).Take(TakeTasks).Select(t => t.Id).ToListAsync();
+
+ logger.LogDebug("Найдено заданий в статусе 'Ожидание' {Count} шт. Робот '{Robot}'", tasks.Count, robotCode.ToString());
+
+ if (tasks.Count == 0)
+ {
+ // Ищем задания в статусе В РАБОТЕ, которые можно перезапустить
+ // Поиск по `RobotStatusCode` = 22.
+ // Далее проверяется `LastStatusUpdated`, что время последнего смены статуса не превышает допустимого(берется из настроек, поле `RobotWaitTime`)
+ // и что текущая попытка не больше разрешенной(берется из настроек, поле `RobotAttemptsNumber`) - если это так, берется эта запись.
+
+ var endDate = DateTimeOffset.UtcNow.Add(-settingsFromDb.RobotWaitTime);
+
+ tasks = await query.Where(t => t.RobotStatusCode == (int)RobotStatusEnum.InProgress
+ && t.AttemptsNumber < settingsFromDb.RobotAttemptsNumber
+ && t.LastRobotStatusUpdated < endDate)
+ .Take(TakeTasks)
+ .Select(t => t.Id)
+ .ToListAsync();
+
+ logger.LogDebug("Найдено заданий в статусе 'В работе' {Count} шт. Робот '{Robot}'", tasks.Count, robotCode.ToString());
+ }
+
+ return tasks;
+ }
+
+
+ ///
+ /// Взять задачу в работу
+ ///
+ ///
+ ///
+ private async Task AcquireTaskAsync(List tasks, string? robotIp)
+ {
+ foreach (var taskId in tasks)
+ {
+ var isChangedStatus = await robotConfigurationRepository.SetInProgressStatusAsync(taskId);
+ if (isChangedStatus)
+ {
+ logger.LogDebug("Захвачена задача {TaskId}", taskId);
+
+ var task = await robotConfigurationRepository.Get()
+ .AsNoTracking()
+ .FirstAsync(t => t.Id == taskId);
+
+ // пишем в историю робота
+ var history = new RobotHistory
+ {
+ Id = Guid.NewGuid(),
+ HistoryLevel = (int)RobotHistoryLevelEnum.Start,
+ TaskStatusCode = task.TaskStatusCode,
+ RobotConfigurationId = taskId,
+ RobotIp = robotIp
+ };
+
+ if (!await robotHistoryRepository.CreateAsync(history) || !await robotHistoryRepository.CommitAsync())
+ throw new DbErrorException("Ошибка при добавлении истории робота, при взятии задания в работу.");
+
+ return taskId;
+ }
+ else
+ {
+ logger.LogDebug("Не удалось захватить задачу {TaskId}", taskId);
+ }
+ }
+
+ logger.LogDebug("Не удалось захватить ни одну из доступных задач для робота");
+
+ return null;
+ }
+
+
+ ///
+ /// Получить задачу со всем необходимыми полями
+ ///
+ ///
+ ///
+ ///
+ private async Task GetTaskWithAllDataAsync(Guid taskId, RobotsEnum robotCode)
+ {
+ var query = robotConfigurationRepository.Get()
+ .AsNoTracking()
+ .AsSingleQuery()
+ //todo: тут общие инклуды
+ ;
+
+ if(robotCode== RobotsEnum.ScheduleOrder)
+ {
+ // тут инклуды только для расписаний
+ }
+
+ return await query.FirstAsync(t => t.Id == taskId);
+ }
+
+ }
+}
diff --git a/PARR.Core/Services/RobotTask/Interfaces/IRobotTaskService.cs b/PARR.Core/Services/RobotTask/Interfaces/IRobotTaskService.cs
new file mode 100644
index 00000000..c4ce047f
--- /dev/null
+++ b/PARR.Core/Services/RobotTask/Interfaces/IRobotTaskService.cs
@@ -0,0 +1,9 @@
+namespace PARR.Core.Services.RobotTask.Interfaces
+{
+ ///
+ /// Управление заданиями роботам
+ ///
+ public interface IRobotTaskService
+ {
+ }
+}
diff --git a/PARR.DAL/Repositories/RobotConfigurationRepository.cs b/PARR.DAL/Repositories/RobotConfigurationRepository.cs
index d8845aaa..dc37a2e7 100644
--- a/PARR.DAL/Repositories/RobotConfigurationRepository.cs
+++ b/PARR.DAL/Repositories/RobotConfigurationRepository.cs
@@ -102,6 +102,22 @@ namespace PARR.DAL.Repositories
}
}
+
+ public async Task SetInProgressStatusAsync(Guid id)
+ {
+ // Если задание все еще в статусе Wait (RobotStatusEnum.Wait), установить ему статус "InProgress"
+
+ // Выполняем атомарный UPDATE напрямую в базе данных
+ var affectedRows = await EntitySet.Where(t => t.Id == id && t.RobotStatusCode == (int)RobotStatusEnum.Wait)
+ .ExecuteUpdateAsync(s => s
+ .SetProperty(t => t.RobotStatusCode, (int)RobotStatusEnum.InProgress)
+ .SetProperty(t => t.LastRobotStatusUpdated, DateTimeOffset.UtcNow)
+ );
+
+ return affectedRows > 0;
+ }
+
+
public RobotConfiguration GetFromTemplateByRobotCode(RobotsEnum robotsEnum, Template template)
{
var config = template.RobotConfigurations.FirstOrDefault(t => t.RobotCode == (int)robotsEnum);
@@ -117,7 +133,7 @@ namespace PARR.DAL.Repositories
// Поиск невыполненных заданий и установка им статуса ошибки робота
- public async Task FindUnfulfilledTaskAndSetRobotErrorStatusAsync(int robotAttemptsNumber, TimeSpan robotWaitTime)
+ public async Task MarkExpiredTasksAsFailedAsync(int robotAttemptsNumber, TimeSpan robotWaitTime)
{
//Ищем `RobotStatusCode` = 22 и `LastStatusUpdated` истекло и `AttemptsNumber` >= допустимого значения из настроек,
//ставим всем этим записям `RobotStatusCode`= 33