Merge branch 'robot-tasks' into dev

This commit is contained in:
Mikhail Trubnikov
2026-05-26 11:53:40 +10:00
22 changed files with 4978 additions and 341 deletions

View File

@@ -1,10 +1,15 @@
namespace PARR.API.Contracts.V1.Requests.Queries
{
public class RobotTaskQuery
public record RobotTaskQuery
{
/// <summary>
/// Установить статус задания - InProgress (Робот взял в работу)
/// </summary>
public bool? SetInProgressStatus { get; set; }
public bool? SetInProgressStatus { get; init; }
/// <summary>
/// Идентификатор робота
/// </summary>
public string? RobotId { get; init; }
}
}

View File

@@ -2,14 +2,19 @@
namespace PARR.API.Contracts.V1.Requests
{
public class RobotHistoryRequest
public record RobotHistoryRequest
{
public RobotHistoryLevelEnum HistoryLevel { get; set; }
public RobotHistoryLevelEnum HistoryLevel { get; init; }
public string? RobotMessage { get; set; }
public string? RobotMessage { get; init; }
public string? EsppMessage { get; set; }
public string? EsppMessage { get; init; }
public Guid TaskId { get; set; }
public Guid TaskId { get; init; }
/// <summary>
/// ИД робота
/// </summary>
public string? RobotId { get; init; }
}
}

View File

@@ -26,6 +26,8 @@
public string? RobotIp { get; set; }
public string? RobotId { get; set; }
public UserBaseResponse? User { get; set; }
}

View File

@@ -123,7 +123,8 @@ namespace PARR.API.Controllers.V1
EsppMessage = request.EsppMessage,
TaskStatusCode = config.TaskStatusCode,
RobotConfigurationId = request.TaskId,
RobotIp = clientService.GetClientIp()?.ToString()
RobotIp = clientService.GetClientIp()?.ToString(),
RobotId = request.RobotId
};
if (!await robotHistoryService.CreateAsync(history) || !await robotHistoryService.CommitAsync())

View File

@@ -1,62 +1,55 @@
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.Core.Services.RobotTask.Interfaces;
using PARR.Domain.Common.Roles;
using PARR.Domain.Entities;
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 IRobotTaskService robotTaskService;
private readonly IMapper mapper;
private readonly SettingsFromDb settingsFromDb;
private readonly IRobotConfigurationRepository robotConfigurationService;
//private readonly IEsppScheduleTransformService esppScheduleTransformService;
private readonly ILogger<RobotTaskController> logger;
//private readonly SettingsFromDb settingsFromDb;
//private readonly IRobotConfigurationRepository robotConfigurationService;
//private readonly ILogger<RobotTaskController> logger;
private readonly IClientService clientService;
private readonly IRobotHistoryRepository robotHistoryService;
private readonly IShortcodesService shortcodesService;
private readonly INextRunService nextRunService;
//private readonly IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService;
//private readonly IRobotHistoryRepository robotHistoryService;
//private readonly IShortcodesService shortcodesService;
//private readonly INextRunService nextRunService;
public RobotTaskController(
IMapper mapper,
SettingsFromDb settingsFromDb,
IRobotConfigurationRepository robotConfigurationService,
//IEsppScheduleTransformService esppScheduleTransformService,
ILogger<RobotTaskController> logger,
//SettingsFromDb settingsFromDb,
//IRobotConfigurationRepository robotConfigurationService,
//ILogger<RobotTaskController> logger,
IClientService clientService,
IRobotHistoryRepository robotHistoryService,
IShortcodesService shortcodesService,
INextRunService nextRunService
//IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService
//IRobotHistoryRepository robotHistoryService,
//IShortcodesService shortcodesService,
//INextRunService nextRunService
IRobotTaskService robotTaskService
)
{
this.robotTaskService = robotTaskService;
this.mapper = mapper;
this.settingsFromDb = settingsFromDb;
this.robotConfigurationService = robotConfigurationService;
//this.esppScheduleTransformService = esppScheduleTransformService;
this.logger = logger;
//this.settingsFromDb = settingsFromDb;
//this.robotConfigurationService = robotConfigurationService;
//this.logger = logger;
this.clientService = clientService;
this.robotHistoryService = robotHistoryService;
this.shortcodesService = shortcodesService;
this.nextRunService = nextRunService;
//this.scheduleResponseAreaTimeOffsetService = scheduleResponseAreaTimeOffsetService;
//this.robotHistoryService = robotHistoryService;
//this.shortcodesService = shortcodesService;
//this.nextRunService = nextRunService;
}
@@ -65,272 +58,320 @@ namespace PARR.API.Controllers.V1
/// </summary>
/// <param name="robotCode"></param>
/// <param name="taskStatusCode"></param>
/// <param name="requestQuery"></param>
/// <returns></returns>
[HttpGet(ApiRoutes.RobotTask.GetByRobotAndStatusTask)]
public async Task<IActionResult> GetByRobotAndStatusTask([FromRoute] RobotsEnum robotCode, [FromRoute] TaskStatusEnum taskStatusCode, [FromQuery] RobotTaskQuery requestQuery)
{
//Ищем все задания с превышенным кол-вом попыток и с просроченным временем и ставим им статус ошибки
await robotConfigurationService.FindUnfulfilledTaskAndSetRobotErrorStatusAsync(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;
var templateTask = await robotTaskService.GetTemplateTaskAsync(
taskStatusCode,
requestQuery.SetInProgressStatus ?? false,
clientService.GetClientIp()?.ToString(),
requestQuery.RobotId
);
var templateResponse = mapper.Map<RobotTaskTemplateResponse>(templateTask);
return Ok(new Response<RobotTaskTemplateResponse>(templateResponse, true));
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);
var historyIniciator = new HistoryInitiator
{
//выбираем только записи с созданными шаблонами (у которых статус 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;
};
var scheduleTask = await robotTaskService.GetScheduleTaskAsync(
taskStatusCode,
requestQuery.SetInProgressStatus ?? false,
historyIniciator.InitiatorIp,
requestQuery.RobotId,
historyIniciator
);
var scheduleResponse = mapper.Map<RobotTaskScheduleResponse>(scheduleTask);
return Ok(new Response<RobotTaskScheduleResponse>(scheduleResponse, true));
default:
break;
throw new AppValidationException("Некорректное значение robotCode");
}
// сортируем по 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<ErrorModel> { new ErrorModel { Message = "Ошибка при выдаче задания." } }));
}
}
switch (robotCode)
{
case RobotsEnum.TemplateOrder:
{ //RobotTaskTemplateResponse
var robotTaskTemplateResponse = mapper.Map<RobotTaskTemplateResponse>(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>(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<ErrorModel> { new ErrorModel { Message = "Ошибка при расчете NextRun" } }));
}
//RobotTaskScheduleResponse
var robotTaskScheduleResponse = mapper.Map<RobotTaskScheduleResponse>(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<ErrorModel> { new ErrorModel { Message = "Ошибка при расчете NextRun" } }));
}
robotTaskScheduleResponse.NextStart = EsppScheduleHelpers.GetNextRun(nextRunWithRobotTz);
robotTaskScheduleResponse.GenerationTime = EsppScheduleHelpers.GetGenerationTime(nextRunWithRobotTz);
return Ok(new Response<RobotTaskScheduleResponse>(robotTaskScheduleResponse, true));
}
default:
break;
}
return BadRequest();
}
/// <summary>
/// Обоновить NextRun если он устарел
/// </summary>
/// <param name="task"></param>
/// <returns></returns>
private async Task<bool> UpdateNextRunAsync(RobotConfiguration task)
{
var template = task.Template!;
#region Old
//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<Now [{nextRun}<{now}], ошибка в расчетах.", template.Id, nextRun.Value, DateTimeOffset.UtcNow);
return false;
}
if (nextRun != template.NextRun)
{
logger.LogDebug($"Для шаблона id {template.Id} обновляю nextRun, новое значение {nextRun}, старое значение {template.NextRun}");
template.LastRun = template.NextRun;
template.NextRun = nextRun.Value;
await robotConfigurationService.CommitAsync(new HistoryInitiator { InitiatorComment = "При получении задания роботом, обновил NextRun", InitiatorIp = clientService.GetClientIp()?.ToString(), InitiatorParrComponentId = ParrComponentsEnum.Api });
}
return true;
}
// /// <summary>
// /// Получить задание для робота по коду робота и по статусу задания
// /// </summary>
// /// <param name="robotCode"></param>
// /// <param name="taskStatusCode"></param>
// /// <returns></returns>
// [HttpGet(ApiRoutes.RobotTask.GetByRobotAndStatusTask)]
// public async Task<IActionResult> GetByRobotAndStatusTask([FromRoute] RobotsEnum robotCode, [FromRoute] TaskStatusEnum taskStatusCode, [FromQuery] RobotTaskQuery requestQuery)
// {
// //Ищем все задания с превышенным кол-вом попыток и с просроченным временем и ставим им статус ошибки
// await robotConfigurationService.MarkExpiredTasksAsFailedAsync(settingsFromDb.RobotAttemptsNumber, settingsFromDb.RobotWaitTime);
/// <summary>
/// Устанавливаем статус "Робот взял в работу", пишем в историю работы роботов инф о начале работ
/// </summary>
/// <param name="taskId"></param>
/// <returns></returns>
private async Task<bool> SetInProgressStatusAsync(Guid taskId)
{
var config = await robotConfigurationService.GetAsync(taskId);
// var query = robotConfigurationService.Get()
// .AsSingleQuery()
// .Where(t => t.RobotCode == (int)robotCode && t.TaskStatusCode == (int)taskStatusCode);
//изменение статуса робота
robotConfigurationService.ChangeRobotStatus(RobotStatusEnum.InProgress, config!);
// 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);
if (!await robotConfigurationService.CommitAsync())
return false;
// query = query
// .Include(t => t.Template)
// .ThenInclude(t => t!.UnitsInTemplate);
//записываем в лог робота
var history = new RobotHistory
{
Id = Guid.NewGuid(),
HistoryLevel = (int)RobotHistoryLevelEnum.Start,
TaskStatusCode = config.TaskStatusCode,
RobotConfigurationId = config.Id,
RobotIp = clientService.GetClientIp()?.ToString()
};
// break;
if (!await robotHistoryService.CreateAsync(history) || !await robotHistoryService.CommitAsync())
return false;
// 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);
return true;
}
// //выбираем только записи с созданными шаблонами (у которых статус 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;
// }
/// <summary>
/// Приводит переносы строк в тексте к формату CRLF (\r\n)
/// </summary>
/// <param name="text">Исходный текст</param>
/// <returns>Текст с унифицированными переносами строк</returns>
private string NormalizeLineEndingsToCrlf(string? text)
{
if (string.IsNullOrEmpty(text))
return string.Empty;
// // сортируем по NextRun, чтобы те у которых дата след срабатывания ближе к текущей, выполнились скорее
// query = query.OrderBy(t => t.Template!.NextRun).ThenBy(t => t.Template!.IsActiveSchedule).ThenBy(t => t.Template.IsActiveTemplate);
// Заменяем любые варианты переносов (\r\n, \r, \n) на единый \r\n
return System.Text.RegularExpressions.Regex.Replace(text, @"\r\n|\r|\n", "\r\n");
}
// 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<ErrorModel> { new ErrorModel { Message = "Ошибка при выдаче задания." } }));
// }
// }
// switch (robotCode)
// {
// case RobotsEnum.TemplateOrder:
// { //RobotTaskTemplateResponse
// var robotTaskTemplateResponse = mapper.Map<RobotTaskTemplateResponse>(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>(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<ErrorModel> { new ErrorModel { Message = "Ошибка при расчете NextRun" } }));
// }
// //RobotTaskScheduleResponse
// var robotTaskScheduleResponse = mapper.Map<RobotTaskScheduleResponse>(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<ErrorModel> { new ErrorModel { Message = "Ошибка при расчете NextRun" } }));
// }
// robotTaskScheduleResponse.NextStart = EsppScheduleHelpers.GetNextRun(nextRunWithRobotTz);
// robotTaskScheduleResponse.GenerationTime = EsppScheduleHelpers.GetGenerationTime(nextRunWithRobotTz);
// return Ok(new Response<RobotTaskScheduleResponse>(robotTaskScheduleResponse, true));
// }
// default:
// break;
// }
// return BadRequest();
// }
// /// <summary>
// /// Обоновить NextRun если он устарел
// /// </summary>
// /// <param name="task"></param>
// /// <returns></returns>
// private async Task<bool> 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<Now [{nextRun}<{now}], ошибка в расчетах.", template.Id, nextRun.Value, DateTimeOffset.UtcNow);
// return false;
// }
// if (nextRun != template.NextRun)
// {
// logger.LogDebug($"Для шаблона id {template.Id} обновляю nextRun, новое значение {nextRun}, старое значение {template.NextRun}");
// template.LastRun = template.NextRun;
// template.NextRun = nextRun.Value;
// await robotConfigurationService.CommitAsync(new HistoryInitiator { InitiatorComment = "При получении задания роботом, обновил NextRun", InitiatorIp = clientService.GetClientIp()?.ToString(), InitiatorParrComponentId = ParrComponentsEnum.Api });
// }
// return true;
// }
// /// <summary>
// /// Устанавливаем статус "Робот взял в работу", пишем в историю работы роботов инф о начале работ
// /// </summary>
// /// <param name="taskId"></param>
// /// <returns></returns>
// private async Task<bool> 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;
// }
// /// <summary>
// /// Приводит переносы строк в тексте к формату CRLF (\r\n)
// /// </summary>
// /// <param name="text">Исходный текст</param>
// /// <returns>Текст с унифицированными переносами строк</returns>
// 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
}
}

View File

@@ -32,6 +32,7 @@ namespace PARR.API.Infrastructure.Middleware
AlreadyExistsException => StatusCodes.Status409Conflict,
UnauthorizedException => StatusCodes.Status401Unauthorized,
ForbiddenException => StatusCodes.Status403Forbidden,
NextRunException => StatusCodes.Status422UnprocessableEntity,
//todo: ---------- другие ошибки ----------
_ => StatusCodes.Status500InternalServerError
};

View File

@@ -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<RobotConfiguration, RobotTaskBaseResponse>()
.Include<RobotConfiguration, RobotTaskTemplateResponse>()
.Include<RobotConfiguration, RobotTaskScheduleResponse>()
.ForMember(d => d.TaskId, o => o.MapFrom(s => s.Id));
CreateMap<RobotConfiguration, RobotTaskTemplateResponse>()
.ForMember(d => d.IsActive, o => o.MapFrom(s => s.Template!.IsActiveTemplate))
.ForMember(d => d.ClosingCode, o => o.MapFrom<RobotTaskTemplateClosingCodeResolver>())
.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<RobotTaskTemplateInitiatorResolver>())
.ForMember(d => d.Category, o => o.MapFrom<RobotTaskTemplateCategoryResolver>())
.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<RobotTaskTemplate, RobotTaskTemplateResponse>();
CreateMap<RobotTaskSchedule, RobotTaskScheduleResponse>();
CreateMap<RobotConfiguration, RobotTaskScheduleResponse>()
.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<RobotTaskScheduleExcludeResolver>())
.ForMember(d => d.Exclude, o => o.MapFrom(s => s.Template!.Job!.Group!.ScheduleExcludeType!.EsppName))
//.ForMember(d => d.ExcludeCalendar, o => o.MapFrom<RobotTaskScheduleExcludeCalendarResolver>())
.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<RobotTaskScheduleTimezoneResolver>())
.ForMember(d => d.RepeatRange, o => o.MapFrom<RobotTaskScheduleRepeatRangeResolver>())
//todo: GenerationTime
//.ForMember(d => d.GenerationTime, o => o.MapFrom(s => EsppScheduleHelpers.GetGenerationTime(s.Template!.NextRun)))
//.ForMember(d => d.GenerationTime, o => o.MapFrom<RobotTaskScheduleGenerationTimeResolver>())
//.ForMember(d => d.NextStart, o => o.MapFrom(s => EsppScheduleHelpers.GetNextRun(s.Template!.NextRun)))
//.ForMember(d => d.NextStart, o => o.MapFrom<RobotTaskScheduleNextStartResolver>())
.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<RobotTaskScheduleSchResolver>());
CreateMap<RobotTaskScheduleSch, RobotTaskScheduleSchResponse>();
#region old robot task
//CreateMap<RobotConfiguration, RobotTaskBaseResponse>()
// .Include<RobotConfiguration, RobotTaskTemplateResponse>()
// .Include<RobotConfiguration, RobotTaskScheduleResponse>()
// .ForMember(d => d.TaskId, o => o.MapFrom(s => s.Id));
//CreateMap<RobotConfiguration, RobotTaskTemplateResponse>()
// .ForMember(d => d.IsActive, o => o.MapFrom(s => s.Template!.IsActiveTemplate))
// .ForMember(d => d.ClosingCode, o => o.MapFrom<RobotTaskTemplateClosingCodeResolver>())
// .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<RobotTaskTemplateInitiatorResolver>())
// .ForMember(d => d.Category, o => o.MapFrom<RobotTaskTemplateCategoryResolver>())
// .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<RobotConfiguration, RobotTaskScheduleResponse>()
// .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<RobotTaskScheduleExcludeResolver>())
// .ForMember(d => d.Exclude, o => o.MapFrom(s => s.Template!.Job!.Group!.ScheduleExcludeType!.EsppName))
// //.ForMember(d => d.ExcludeCalendar, o => o.MapFrom<RobotTaskScheduleExcludeCalendarResolver>())
// .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<RobotTaskScheduleTimezoneResolver>())
// .ForMember(d => d.RepeatRange, o => o.MapFrom<RobotTaskScheduleRepeatRangeResolver>())
// //todo: GenerationTime
// //.ForMember(d => d.GenerationTime, o => o.MapFrom(s => EsppScheduleHelpers.GetGenerationTime(s.Template!.NextRun)))
// //.ForMember(d => d.GenerationTime, o => o.MapFrom<RobotTaskScheduleGenerationTimeResolver>())
// //.ForMember(d => d.NextStart, o => o.MapFrom(s => EsppScheduleHelpers.GetNextRun(s.Template!.NextRun)))
// //.ForMember(d => d.NextStart, o => o.MapFrom<RobotTaskScheduleNextStartResolver>())
// .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<RobotTaskScheduleSchResolver>());
#endregion
CreateMap<Robot, RobotResponse>();
@@ -460,7 +470,7 @@ namespace PARR.API.MappingProfiles
//CreateMap<TaskItemBase, TaskItemResponse>();
CreateMap<ActiveTask, ActiveTaskResponse>();
// ---
CreateMap<TaskType, TaskItemTypeResponse>();

View File

@@ -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<IShortcodeHandler, ConstantsShortcodeHandler>();
@@ -97,10 +97,13 @@ namespace PARR.Core
services.AddScoped<IShortcodesService, ShortcodesService>();
#endregion
#region Services
services.AddTransient<IMatchingStatusService, MatchingStatusService>();
//services.AddScoped<IUserService, UserService>();
services.AddScoped<IRobotTaskService, RobotTaskService>();
//services.AddScoped<IUserService, UserService>();
#endregion

View File

@@ -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<RobotConfiguration, RobotTaskSchedule, List<RobotTaskScheduleSch>?>
{
private readonly IEsppSchTypeConfigRepository esppConfigRepository;
private readonly ILogger<RobotTaskScheduleSchResolver> logger;
public RobotTaskScheduleSchResolver(
IEsppSchTypeConfigRepository esppConfigRepository,
ILogger<RobotTaskScheduleSchResolver> logger
)
{
this.esppConfigRepository = esppConfigRepository;
this.logger = logger;
}
public List<RobotTaskScheduleSch>? Resolve(RobotConfiguration source, RobotTaskSchedule destination, List<RobotTaskScheduleSch>? 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;
}
}
}

View File

@@ -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
{
/// <summary>
/// Маппинг RobotTask
/// </summary>
internal class RobotTaskMappingProfile : Profile
{
public RobotTaskMappingProfile()
{
CreateMap<RobotConfiguration, RobotTaskBase>()
.Include<RobotConfiguration, RobotTaskTemplate>()
.Include<RobotConfiguration, RobotTaskSchedule>()
.ForMember(d => d.TaskId, o => o.MapFrom(s => s.Id));
// Задание роботам, шаблоны
CreateMap<RobotConfiguration, RobotTaskTemplate>()
.ForMember(d => d.IsActive, o => o.MapFrom(s => s.Template!.IsActiveTemplate))
//.ForMember(d => d.ClosingCode, o => o.MapFrom<RobotTaskTemplateClosingCodeResolver>())
.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<RobotTaskTemplateInitiatorResolver>())
//.ForMember(d => d.Category, o => o.MapFrom<RobotTaskTemplateCategoryResolver>())
.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<RobotConfiguration, RobotTaskSchedule>()
.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<RobotTaskScheduleRepeatRangeResolver>())
.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<RobotTaskScheduleSchResolver>());
}
}
}

View File

@@ -26,7 +26,7 @@ namespace PARR.Core.Repositories.Interfaces
/// <param name="robotAttemptsNumber"></param>
/// <param name="robotWaitTime"></param>
/// <returns></returns>
Task FindUnfulfilledTaskAndSetRobotErrorStatusAsync(int robotAttemptsNumber, TimeSpan robotWaitTime);
Task MarkExpiredTasksAsFailedAsync(int robotAttemptsNumber, TimeSpan robotWaitTime);
/// <summary>
/// Получить конфигурацию из шаблона по RobotCode. У шаблона обязательно должен быть Include таблицы RobotConfiguration
@@ -42,5 +42,12 @@ namespace PARR.Core.Repositories.Interfaces
/// <param name="configuration"></param>
/// <returns>true - если изменил статус, false - нельзя изменить статус</returns>
bool SetUpdateTaskStatusIfAllow(RobotConfiguration configuration);
/// <summary>
/// Атомарно установить статус задачи "В работе" если текущий статус "В ожидании".
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
Task<bool> SetInProgressStatusAsync(Guid id);
}
}

View File

@@ -0,0 +1,387 @@
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;
namespace PARR.Core.Services.RobotTask.Implementations
{
internal class RobotTaskService : IRobotTaskService
{
/// <summary>
/// Количество заданий которые рассматриваем для взятия в работу.
/// </summary>
private readonly int TakeTasks = 10;
private readonly ILogger<RobotTaskService> logger;
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<RobotTaskService> logger,
IRobotConfigurationRepository robotConfigurationRepository,
SettingsFromDb settingsFromDb,
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<RobotTaskTemplate> GetTemplateTaskAsync(TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp, string? robotId)
{
var templateTask = await GetTaskAsync(RobotsEnum.TemplateOrder, taskStatusCode, acquireTask, robotIp, robotId);
var task = mapper.Map<RobotTaskTemplate>(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<RobotTaskSchedule> 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<RobotTaskSchedule>(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;
}
/// <summary>
/// Получить задачу для робота.
/// Метод может генерировать исключения.
/// </summary>
/// <param name="robotCode"></param>
/// <param name="taskStatusCode"></param>
/// <param name="acquireTask">Взять в работу</param>
/// <param name="robotIp"></param>
/// <returns></returns>
/// <exception cref="NotFoundException"></exception>
private async Task<RobotConfiguration> GetTaskAsync(RobotsEnum robotCode, TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp, string? robotId)
{
// 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, robotId);
if (acquiredTaskId == null)
throw new Exception($"Не удалось взять ни одну из доступных задач ({availableTasks.Count}) в работу");
}
else
{
// Берем первую задачу из списка доступных
acquiredTaskId = availableTasks.First();
logger.LogDebug("Задача не требует захвата, взята первая из доступных: {TaskId}", acquiredTaskId);
}
//3. Получаем задачу со всеми нужными инклудами в зависимости от типа робота
var task = await GetTaskWithAllDataAsync(acquiredTaskId.Value, robotCode);
return task;
}
/// <summary>
/// Получить список возможных заданий для взятия в работу.
/// Кол-во заданй ограничено переменной TakeTasks
/// </summary>
/// <param name="robotCode"></param>
/// <param name="taskStatusCode"></param>
/// <returns></returns>
private async Task<List<Guid>> 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<Guid>();
// Ещем первые 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;
}
/// <summary>
/// Взять задачу в работу
/// </summary>
/// <param name="taskId"></param>
/// <returns></returns>
private async Task<Guid?> AcquireTaskAsync(List<Guid> tasks, string? robotIp, string? robotId)
{
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,
RobotId = robotId
};
if (!await robotHistoryRepository.CreateAsync(history) || !await robotHistoryRepository.CommitAsync())
throw new DbErrorException("Ошибка при добавлении истории робота, при взятии задания в работу.");
return taskId;
}
else
{
logger.LogDebug("Не удалось захватить задачу {TaskId}", taskId);
}
}
logger.LogDebug("Не удалось захватить ни одну из доступных задач для робота");
return null;
}
/// <summary>
/// Получить задачу со всем необходимыми полями
/// </summary>
/// <param name="taskId"></param>
/// <param name="robotCode"></param>
/// <returns></returns>
private async Task<RobotConfiguration> GetTaskWithAllDataAsync(Guid taskId, RobotsEnum robotCode)
{
IQueryable<RobotConfiguration> query = robotConfigurationRepository.Get()
//.AsNoTracking() // нужно обязательно трекать, так как может измениться nextRun и его нужно будет сохранить
.AsSingleQuery()
// Общие инклуды для шаблонов и расписаний
// 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)
{
// Инклуды только для расписаний
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);
}
/// <summary>
/// Приводит переносы строк в тексте к формату CRLF (\r\n)
/// </summary>
/// <param name="text">Исходный текст</param>
/// <returns>Текст с унифицированными переносами строк</returns>
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");
}
/// <summary>
/// Обоновить NextRun если он устарел
/// </summary>
/// <param name="task"></param>
/// <returns></returns>
private async Task<bool> 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<Now [{nextRun}<{now}], ошибка в расчетах.", template.Id, nextRun.Value, DateTimeOffset.UtcNow);
return false;
}
if (nextRun != template.NextRun)
{
logger.LogDebug($"Для шаблона id {template.Id} обновляю nextRun, новое значение {nextRun}, старое значение {template.NextRun}");
template.LastRun = template.NextRun;
template.NextRun = nextRun.Value;
var suffix = "При получении задания роботом, обновил NextRun";
historyInitiator.InitiatorComment =
string.IsNullOrEmpty(historyInitiator.InitiatorComment)
? suffix
: $"{historyInitiator.InitiatorComment}. {suffix}";
if (!await robotConfigurationRepository.CommitAsync(historyInitiator))
throw new DbErrorException("Ошибка при сохранении изменения NextRun");
}
return true;
}
}
}

View File

@@ -0,0 +1,35 @@
using PARR.Domain.DTOs.RobotTask;
using PARR.Domain.Entities.Base.History;
using PARR.Domain.Enums;
namespace PARR.Core.Services.RobotTask.Interfaces
{
/// <summary>
/// Управление заданиями роботам
/// </summary>
public interface IRobotTaskService
{
/// <summary>
/// Получить задание роботу - шаблоны.
/// </summary>
/// <param name="taskStatusCode"></param>
/// <param name="acquireTask"></param>
/// <param name="robotIp"></param>
/// <param name="robotId"></param>
/// <returns></returns>
Task<RobotTaskTemplate> GetTemplateTaskAsync(TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp, string? robotId);
/// <summary>
/// Получить задание роботу - расписания.
/// </summary>
/// <param name="taskStatusCode"></param>
/// <param name="acquireTask"></param>
/// <param name="robotIp"></param>
/// <param name="robotId"></param>
/// <param name="historyInitiator"></param>
/// <returns></returns>
Task<RobotTaskSchedule> GetScheduleTaskAsync(TaskStatusEnum taskStatusCode, bool acquireTask, string? robotIp, string? robotId, IHistoryInitiator historyInitiator);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace PARR.DAL.Migrations
{
/// <inheritdoc />
public partial class tblRobotHistoriesAddRobotId : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "RobotId",
table: "RobotHistories",
type: "text",
nullable: true,
comment: "Идентификатор робота. Форматы: H20-R1-T, H20-R1-S, H20-R1-ET, H20-R1-ES.");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "RobotId",
table: "RobotHistories");
}
}
}

View File

@@ -891,6 +891,10 @@ namespace PARR.DAL.Migrations
b.Property<Guid>("RobotConfigurationId")
.HasColumnType("uuid");
b.Property<string>("RobotId")
.HasColumnType("text")
.HasComment("Идентификатор робота. Форматы: H20-R1-T, H20-R1-S, H20-R1-ET, H20-R1-ES.");
b.Property<string>("RobotIp")
.HasColumnType("text");

View File

@@ -102,6 +102,22 @@ namespace PARR.DAL.Repositories
}
}
public async Task<bool> 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

View File

@@ -0,0 +1,13 @@
namespace PARR.Domain.DTOs.RobotTask
{
/// <summary>
/// Базовая модель для заданий роботам
/// </summary>
public record RobotTaskBase
{
/// <summary>
/// RobotConfigurationId
/// </summary>
public Guid TaskId { get; init; }
}
}

View File

@@ -0,0 +1,67 @@
namespace PARR.Domain.DTOs.RobotTask
{
/// <summary>
/// Модель для задания роботам - расписания
/// </summary>
public record RobotTaskSchedule : RobotTaskBase
{
/// <summary>
/// ИД расписания из ЕСПП. При создании, его не будет
/// </summary>
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; }
/// <summary>
/// Время создания наряда
/// </summary>
public required string GenerationTime { get; init; }
/// <summary>
/// Следующее срабатывание
/// </summary>
public required string NextStart { get; init; }
/// <summary>
/// Тип задания: Регулярно, еженедельно...
/// </summary>
public required string ScheduleType { get; init; }
/// <summary>
/// Код задания
/// </summary>
public int ScheduleTypeCode { get; init; }
public List<RobotTaskScheduleSch>? 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; }
}
}

View File

@@ -0,0 +1,53 @@
namespace PARR.Domain.DTOs.RobotTask
{
/// <summary>
/// Модель для задания роботам - шаблоны
/// </summary>
public record RobotTaskTemplate : RobotTaskBase
{
/// <summary>
/// ИД расписания из ЕСПП. При создании, его не будет
/// (при создании шаблона, это поле не смотрим, используем только при изменении)
/// </summary>
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; }
}
}

View File

@@ -40,6 +40,13 @@ namespace PARR.Domain.Entities
///// </summary>
//public Guid IdSeries { get; set; }
/// <summary>
/// Идентификатор робота.
/// Форматы: H20-R1-T, H20-R1-S, H20-R1-ET, H20-R1-ES
/// </summary>
[Comment("Идентификатор робота. Форматы: H20-R1-T, H20-R1-S, H20-R1-ET, H20-R1-ES.")]
public string? RobotId { get; set; }
[ForeignKey(nameof(HistoryLevel))]
public RobotHistoryLevel? RobotHistoryLevel { get; set; }

View File

@@ -93,4 +93,17 @@
/// <param name="message"></param>
public ForbiddenException(string message) : base(message) { }
}
/// <summary>
/// Ошибка при расчете NextRun. 422 (запрос валиден, но бизнес логика не смогла нормально его обработать)
/// </summary>
public class NextRunException : BaseException
{
/// <summary>
/// Ошибка при расчете NextRun
/// </summary>
/// <param name="message"></param>
public NextRunException(string message) : base(message){}
}
}