337 lines
19 KiB
C#
337 lines
19 KiB
C#
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.DAL.Contracts;
|
||
using PARR.DAL.DomainServices.Shortcodes;
|
||
using PARR.DAL.Models;
|
||
using PARR.DAL.NextRunServices;
|
||
using PARR.DAL.Services.Interfaces;
|
||
using PARR.DAL.Services.Interfaces.Schedule;
|
||
using PARR.Domain.Common.Roles;
|
||
using PARR.Domain.Entities.Base.History;
|
||
using PARR.Domain.Enums;
|
||
|
||
namespace PARR.API.Controllers.V1
|
||
{
|
||
[Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)]
|
||
public class RobotTaskController : BaseApiController
|
||
{
|
||
private readonly IMapper mapper;
|
||
private readonly SettingsFromDb settingsFromDb;
|
||
private readonly IRobotConfigurationService robotConfigurationService;
|
||
//private readonly IEsppScheduleTransformService esppScheduleTransformService;
|
||
private readonly ILogger<RobotTaskController> logger;
|
||
private readonly IClientService clientService;
|
||
private readonly IRobotHistoryService robotHistoryService;
|
||
private readonly IShortcodesService shortcodesService;
|
||
private readonly INextRunService nextRunService;
|
||
//private readonly IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService;
|
||
|
||
public RobotTaskController(
|
||
IMapper mapper,
|
||
SettingsFromDb settingsFromDb,
|
||
IRobotConfigurationService robotConfigurationService,
|
||
//IEsppScheduleTransformService esppScheduleTransformService,
|
||
ILogger<RobotTaskController> logger,
|
||
IClientService clientService,
|
||
IRobotHistoryService robotHistoryService,
|
||
IShortcodesService shortcodesService,
|
||
INextRunService nextRunServic
|
||
//IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService
|
||
)
|
||
{
|
||
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;
|
||
}
|
||
|
||
|
||
/// <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.FindUnfulfilledTaskAndSetRobotErrorStatusAsync(settingsFromDb.RobotAttemptsNumber, settingsFromDb.RobotWaitTime);
|
||
|
||
|
||
var query = robotConfigurationService.Get()
|
||
.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<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");
|
||
}
|
||
}
|
||
}
|