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.DAL.Contracts; using PARR.DAL.DomainServices.Shortcodes; using PARR.DAL.NextRunServices; using PARR.Domain.Common.Roles; using PARR.Domain.Entities; 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 IRobotConfigurationRepository robotConfigurationService; //private readonly IEsppScheduleTransformService esppScheduleTransformService; private readonly ILogger logger; private readonly IClientService clientService; private readonly IRobotHistoryRepository robotHistoryService; private readonly IShortcodesService shortcodesService; private readonly INextRunService nextRunService; //private readonly IScheduleResponseAreaTimeOffsetService scheduleResponseAreaTimeOffsetService; public RobotTaskController( IMapper mapper, SettingsFromDb settingsFromDb, IRobotConfigurationRepository robotConfigurationService, //IEsppScheduleTransformService esppScheduleTransformService, ILogger logger, IClientService clientService, IRobotHistoryRepository robotHistoryService, IShortcodesService shortcodesService, INextRunService nextRunService //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; } /// /// Получить задание для робота по коду робота и по статусу задания /// /// /// /// [HttpGet(ApiRoutes.RobotTask.GetByRobotAndStatusTask)] public async Task 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 { 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"); } } }