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