2026-05-26 11:26:52 +10:00
using AutoMapper ;
2026-07-22 16:02:04 +10:00
using InfluxDB.Client.Api.Domain ;
2026-05-25 16:59:42 +10:00
using Microsoft.EntityFrameworkCore ;
using Microsoft.Extensions.Logging ;
2026-05-26 11:26:52 +10:00
using PARR.BLL.Helpers ;
2026-05-25 16:59:42 +10:00
using PARR.Core.Repositories.Interfaces ;
2026-07-22 16:02:04 +10:00
using PARR.Core.Repositories.Interfaces.TemplateRepositories ;
2026-05-26 11:26:52 +10:00
using PARR.Core.Services.NextRunServices ;
2026-05-25 16:59:42 +10:00
using PARR.Core.Services.RobotTask.Interfaces ;
2026-07-22 16:02:04 +10:00
using PARR.Core.Services.RobotTask.Models ;
2026-05-26 11:26:52 +10:00
using PARR.Core.Services.Shortcodes ;
2026-07-22 16:02:04 +10:00
using PARR.Domain.Common.Template ;
2026-05-26 11:26:52 +10:00
using PARR.Domain.DTOs.RobotTask ;
2026-05-25 16:59:42 +10:00
using PARR.Domain.Entities ;
2026-05-26 11:26:52 +10:00
using PARR.Domain.Entities.Base.History ;
2026-07-21 11:48:21 +10:00
using PARR.Domain.Entities.RobotEntities ;
2026-05-25 16:59:42 +10:00
using PARR.Domain.Enums ;
using PARR.Domain.Exceptions ;
using PARR.Domain.Settings ;
namespace PARR.Core.Services.RobotTask.Implementations
{
internal class RobotTaskService : IRobotTaskService
{
/// <summary>
/// Количество заданий которые рассматриваем для взятия в работу.
2026-07-22 16:02:04 +10:00
/// Рекомендованное значение, кол-во роботов * 3
2026-05-25 16:59:42 +10:00
/// </summary>
2026-07-22 16:02:04 +10:00
private readonly int TakeTasks = 15 * 3 ;
2026-05-25 16:59:42 +10:00
2026-07-22 16:02:04 +10:00
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 ;
private readonly ITemplateRenamePendingRepository _templateRenamePendingRepository ;
2026-05-25 16:59:42 +10:00
public RobotTaskService (
ILogger < RobotTaskService > logger ,
IRobotConfigurationRepository robotConfigurationRepository ,
SettingsFromDb settingsFromDb ,
2026-05-26 11:26:52 +10:00
IRobotHistoryRepository robotHistoryRepository ,
IMapper mapper ,
IShortcodesService shortcodesService ,
2026-07-22 16:02:04 +10:00
INextRunService nextRunService ,
ITemplateRenamePendingRepository templateRenamePendingRepository
2026-05-25 16:59:42 +10:00
)
{
2026-07-22 16:02:04 +10:00
this . _logger = logger ;
this . _robotConfigurationRepository = robotConfigurationRepository ;
this . _settingsFromDb = settingsFromDb ;
this . _robotHistoryRepository = robotHistoryRepository ;
this . _mapper = mapper ;
this . _shortcodesService = shortcodesService ;
this . _nextRunService = nextRunService ;
_templateRenamePendingRepository = templateRenamePendingRepository ;
2026-05-25 16:59:42 +10:00
}
2026-05-26 11:26:52 +10:00
public async Task < RobotTaskTemplate > GetTemplateTaskAsync ( TaskStatusEnum taskStatusCode , bool acquireTask , string? robotIp , string? robotId )
{
2026-07-10 14:11:16 +10:00
var templateTask = await GetTaskAsync ( RobotsEnum . TemplateOrder , taskStatusCode , acquireTask , robotIp , robotId , TimeSpan . Zero ) ;
2026-05-26 11:26:52 +10:00
2026-07-22 16:02:04 +10:00
var task = _mapper . Map < RobotTaskTemplate > ( templateTask ) ;
2026-05-26 11:26:52 +10:00
2026-07-22 16:02:04 +10:00
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 ! ) } ;
2026-05-26 11:26:52 +10:00
2026-07-22 16:02:04 +10:00
task = task with { ClosingCode = _settingsFromDb . ClosingCode } ;
task = task with { Initiator = _settingsFromDb . Initiator } ;
task = task with { Category = _settingsFromDb . Category } ;
2026-05-26 11:26:52 +10:00
return task ;
}
2026-07-10 14:11:16 +10:00
public async Task < RobotTaskSchedule > GetScheduleTaskAsync ( TaskStatusEnum taskStatusCode , bool acquireTask , string? robotIp , string? robotId , IHistoryInitiator historyInitiator , TimeSpan scheduleCooldownDuration )
2026-05-26 11:26:52 +10:00
{
2026-07-10 14:11:16 +10:00
var scheduleTask = await GetTaskAsync ( RobotsEnum . ScheduleOrder , taskStatusCode , acquireTask , robotIp , robotId , scheduleCooldownDuration ) ;
2026-05-26 11:26:52 +10:00
// Проверяем nextRun, lastRun, обновляем их
var resultUpdateNextRun = await UpdateNextRunAsync ( scheduleTask , historyInitiator ) ;
if ( ! resultUpdateNextRun )
{
2026-07-22 16:02:04 +10:00
_logger . LogError ( "Ошибка при расчете NextRun для templateId: {templateId}" , scheduleTask . TemplateId ) ;
2026-05-26 11:26:52 +10:00
throw new NextRunException ( $"Ошибка при расчете NextRun для templateId: {scheduleTask.TemplateId}" ) ;
}
2026-07-22 16:02:04 +10:00
var task = _mapper . Map < RobotTaskSchedule > ( scheduleTask ) ;
2026-05-26 11:26:52 +10:00
2026-07-22 16:02:04 +10:00
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 ! ) } ;
2026-05-26 11:26:52 +10:00
//nextRun в часовой зоне У З Робота ЕСПП
2026-07-22 16:02:04 +10:00
var nextRunWithRobotTz = scheduleTask . Template ! . NextRun . Add ( _nextRunService . GetEsppAccountOffset ( ) ) ;
2026-05-26 11:26:52 +10:00
//на всякий случай еще раз проверяем, что дата не устарела и отправляем задание
if ( nextRunWithRobotTz < DateTimeOffset . UtcNow )
{
2026-07-22 16:02:04 +10:00
_logger . LogError ( "Ошибка при расчете NextRun для templateId: {templateId}, итоговое значение для робота, меньше чем сейчас {nextRunWithRobotTz}<{now}" ,
2026-05-26 11:26:52 +10:00
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 ) } ;
2026-07-22 16:02:04 +10:00
task = task with { RepeatRange = _settingsFromDb . ScheduleRepeatRange } ;
2026-05-26 11:26:52 +10:00
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>
2026-07-10 14:11:16 +10:00
private async Task < RobotConfiguration > GetTaskAsync ( RobotsEnum robotCode , TaskStatusEnum taskStatusCode , bool acquireTask , string? robotIp , string? robotId , TimeSpan scheduleCooldownDuration )
2026-05-25 16:59:42 +10:00
{
// 1. Ищем все задания с превышенным кол-вом попыток и просроченным временем, ставим им статус ошибки
2026-07-22 16:02:04 +10:00
await _robotConfigurationRepository . MarkExpiredTasksAsFailedAsync ( _settingsFromDb . RobotAttemptsNumber , _settingsFromDb . RobotWaitTime ) ;
2026-05-25 16:59:42 +10:00
// 2. Ищем доступные задания
2026-07-10 14:11:16 +10:00
var availableTasks = await GetAvailableTasksAsync ( robotCode , taskStatusCode , scheduleCooldownDuration ) ;
2026-05-25 16:59:42 +10:00
if ( availableTasks . Count = = 0 )
throw new NotFoundException ( "Нет доступных заданий для робота" ) ;
Guid ? acquiredTaskId = null ;
if ( acquireTask )
{
2026-07-10 14:11:16 +10:00
// Берем задание в работу, устанавливаем ему статус "В работе"
2026-05-26 11:26:52 +10:00
acquiredTaskId = await AcquireTaskAsync ( availableTasks , robotIp , robotId ) ;
2026-05-25 16:59:42 +10:00
if ( acquiredTaskId = = null )
2026-05-26 11:26:52 +10:00
throw new Exception ( $"Н е удалось взять ни одну из доступных задач ({availableTasks.Count}) в работу" ) ;
2026-05-25 16:59:42 +10:00
}
else
{
// Берем первую задачу из списка доступных
acquiredTaskId = availableTasks . First ( ) ;
2026-07-22 16:02:04 +10:00
_logger . LogDebug ( "Задача не требует захвата, взята первая из доступных: {TaskId}" , acquiredTaskId ) ;
2026-05-25 16:59:42 +10:00
}
2026-05-26 11:26:52 +10:00
//3. Получаем задачу с о всеми нужными инклудами в зависимости от типа робота
2026-05-25 16:59:42 +10:00
var task = await GetTaskWithAllDataAsync ( acquiredTaskId . Value , robotCode ) ;
2026-05-26 11:26:52 +10:00
return task ;
2026-05-25 16:59:42 +10:00
}
/// <summary>
/// Получить список возможных заданий для взятия в работу.
/// Кол-во заданй ограничено переменной TakeTasks
/// </summary>
/// <param name="robotCode"></param>
/// <param name="taskStatusCode"></param>
/// <returns></returns>
2026-07-10 14:11:16 +10:00
private async Task < List < Guid > > GetAvailableTasksAsync ( RobotsEnum robotCode , TaskStatusEnum taskStatusCode , TimeSpan scheduleCooldownDuration )
2026-05-25 16:59:42 +10:00
{
2026-07-22 16:02:04 +10:00
var query = _robotConfigurationRepository . Get ( )
2026-05-25 16:59:42 +10:00
. AsNoTracking ( )
2026-07-22 16:02:04 +10:00
. Where ( t = > t . RobotCode = = ( int ) robotCode ) ;
2026-05-25 16:59:42 +10:00
// Если это задание для робота расписаний
if ( robotCode = = RobotsEnum . ScheduleOrder )
{
// Выбираем только записи с созданными шаблонами (у которых статус 30), а только потом ищем у них расписания
2026-07-10 14:11:16 +10:00
query = query . Where ( t = > t . Template ! . RobotConfigurations . Any ( x = > x . RobotCode = = ( int ) RobotsEnum . TemplateOrder & & x . TaskStatusCode = = ( int ) TaskStatusEnum . Ok ) ) ;
// Н е берем шаблоны, у которых lastRun + 3 часа < сейчас, и у них последний инициатор был или nextRun (10) или esppSchedule (5), это условие применяется только к активированным расписаниям
var cooldownThreshold = DateTimeOffset . UtcNow . Add ( - scheduleCooldownDuration ) ;
query = query . Where ( t = >
// Условие кулдауна: проверяем, попадает ли шаблон под ЗАПРЕТ
! (
t . Template ! . IsActiveSchedule
& & ( t . Template . InitiatorParrComponentId = = ParrComponentsEnum . EsppScheduleSync | | t . Template . InitiatorParrComponentId = = ParrComponentsEnum . NextRun )
& & t . Template . LastRun > = cooldownThreshold
)
) ;
2026-05-25 16:59:42 +10:00
}
// Сортируем по nextRun, чтобы те, у кого nextRun ближе к текущей, выполнились скорее
query = query . OrderBy ( t = > t . Template ! . NextRun ) . ThenBy ( t = > t . Template ! . IsActiveSchedule ) . ThenBy ( t = > t . Template ! . IsActiveTemplate ) ;
2026-07-22 16:02:04 +10:00
// Кандидаты заданий, Id задания и имя шаблона
//var tasks = new List<Guid>();
var tasks = new List < RobotTaskDetails > ( ) ;
2026-05-25 16:59:42 +10:00
2026-07-22 16:02:04 +10:00
// Ищем первые TakeTasks заданий в статусе ОЖИДАНИЕ
2026-07-10 14:11:16 +10:00
tasks = await query
. Where ( t = >
t . RobotStatusCode = = ( int ) RobotStatusEnum . Wait
& & t . TaskStatusCode = = ( int ) taskStatusCode
) . Take ( TakeTasks )
2026-07-22 16:02:04 +10:00
//.Select(t => t.Id)
. Select ( t = > new RobotTaskDetails ( t . Id , t . Template ! . Name , t . Template . NextRun ) )
2026-07-10 14:11:16 +10:00
. ToListAsync ( ) ;
2026-05-25 16:59:42 +10:00
2026-07-22 16:02:04 +10:00
_logger . LogDebug ( "Найдено заданий в статусе 'Ожидание' {Count} шт. Робот '{Robot}'" , tasks . Count , robotCode . ToString ( ) ) ;
2026-05-25 16:59:42 +10:00
if ( tasks . Count = = 0 )
{
// Ищем задания в статусе В РАБОТЕ, которые можно перезапустить
// Поиск по `RobotStatusCode` = 22.
// Далее проверяется `LastStatusUpdated`, что время последнего смены статуса не превышает допустимого(берется из настроек, поле `RobotWaitTime`)
// и что текущая попытка не больше разрешенной(берется из настроек, поле `RobotAttemptsNumber`) - если это так, берется эта запись.
2026-07-22 16:02:04 +10:00
var endDate = DateTimeOffset . UtcNow . Add ( - _settingsFromDb . RobotWaitTime ) ;
2026-05-25 16:59:42 +10:00
tasks = await query . Where ( t = > t . RobotStatusCode = = ( int ) RobotStatusEnum . InProgress
2026-07-22 16:02:04 +10:00
& & t . TaskStatusCode = = ( int ) taskStatusCode
& & t . AttemptsNumber < _settingsFromDb . RobotAttemptsNumber
2026-05-25 16:59:42 +10:00
& & t . LastRobotStatusUpdated < endDate )
. Take ( TakeTasks )
2026-07-22 16:02:04 +10:00
//.Select(t => t.Id)
. Select ( t = > new RobotTaskDetails ( t . Id , t . Template ! . Name , t . Template . NextRun ) )
2026-05-25 16:59:42 +10:00
. ToListAsync ( ) ;
2026-07-22 16:02:04 +10:00
_logger . LogDebug ( "Найдено заданий в статусе 'В работе' {Count} шт. Робот '{Robot}'" , tasks . Count , robotCode . ToString ( ) ) ;
2026-05-25 16:59:42 +10:00
}
2026-07-22 16:02:04 +10:00
if ( robotCode = = RobotsEnum . TemplateOrder )
{
// Если запрашиваем шаблоны, смотрим корректируем список заданий в зависимости от статуса переименования.
// Это не относится к расписаниям, потому что у переименованных расписаний статус Updating, а оно не возьмется в работу, пока не обновится шаблон
tasks = await ReplaceTemplateTasksForRenameAsync ( tasks , robotCode ) ;
}
return tasks . Select ( t = > t . TaskId ) . ToList ( ) ;
}
/// <summary>
/// Проверяет наличие шаблонов в процессе переименования и заменяет обычные задания на задания по переименованию.
/// Если связанный шаблон не переименован, и у него статус ошибки, целевому шаблону устанавливается статус ошибки.
/// </summary>
/// <param name="tasks"></param>
/// <returns></returns>
private async Task < List < RobotTaskDetails > > ReplaceTemplateTasksForRenameAsync ( List < RobotTaskDetails > tasks , RobotsEnum robotCode )
{
if ( tasks . Count = = 0 | | robotCode ! = RobotsEnum . TemplateOrder )
return tasks ;
// Ищем есть ли связанные шаблоны с таким имененм на переименование
var taskTemplateNames = tasks . Select ( t = > t . TemplateName ) ;
var templatesToRename = await _templateRenamePendingRepository . Get ( ) . AsNoTracking ( )
. Where ( t = > taskTemplateNames . Contains ( t . OldName ) )
. ToListAsync ( ) ;
_logger . LogDebug ( "Шаблонов для переименования в текущем задании: {Count} шт." , templatesToRename . Count ) ;
if ( templatesToRename . Count = = 0 )
return tasks ;
// Ищем связанные переименованные шаблоны по ИД, смотрим можем ли взять их в работу
var renameTemplateIds = templatesToRename . Select ( t = > t . TemplateId ) . ToList ( ) ;
var renameTasks = await _robotConfigurationRepository . Get ( )
. AsNoTracking ( )
. Include ( t = > t . Template )
. Where ( t = >
t . RobotCode = = ( int ) robotCode
& & renameTemplateIds . Contains ( t . TemplateId )
// Это может быть только обновление. Так как переименования для создаваемого шаблона быть не может
& & t . TaskStatusCode = = ( int ) TaskStatusEnum . Updating
) . ToListAsync ( ) ;
// Ищем связанные задания которые в ошибке, и ставим целевому шаблону задание в ошибку
var errorTasks = renameTasks . Where ( t = > t . RobotStatusCode = = ( int ) RobotStatusEnum . Error & & t . AttemptsNumber > = _settingsFromDb . RobotAttemptsNumber ) . ToList ( ) ;
var tasksToSetErrorStatus = new List < Guid > ( ) ;
if ( errorTasks . Count > 0 )
{
_logger . LogDebug ( "Найдено связанных заданий на переименование с ошибками: {ErrorCount}. Будем целевым шаблонам тоже ставить статус ошибки." , errorTasks . Count ) ;
var errorTemplateNames = errorTasks . Select ( t = > t . Template ! . Name ) . ToList ( ) ;
// Берем целевые таски, находим в них задания которым надо поставить ошибку
tasksToSetErrorStatus = tasks . Where ( t = > errorTemplateNames . Contains ( t . TemplateName ) ) . Select ( t = > t . TaskId ) . ToList ( ) ;
// Устанавливаем ошибку целевым + пишем комментарий от робота + нажимаем комит
var logMessage = "[RobotTaskService] Установлен статус ошибки, так как не переименован связанный шаблон" ;
await SetErrorStatusAsync ( tasksToSetErrorStatus , logMessage ) ;
}
var endDate = DateTimeOffset . UtcNow . Add ( - _settingsFromDb . RobotWaitTime ) ;
// Смотрим статусы роботов, можно взять в работу, только если (RobotStatus == Wait) или (InpRogress но которые еще не просрочены)
var allowedTasks = renameTasks . Where ( t = >
t . RobotStatusCode = = ( int ) RobotStatusEnum . Wait
| | ( t . RobotStatusCode = = ( int ) RobotStatusEnum . InProgress & & t . AttemptsNumber < _settingsFromDb . RobotAttemptsNumber & & t . LastRobotStatusUpdated < endDate )
) ;
// Формируем список заданий
var originalCount = tasks . Count ; // Запоминаем сколько было изначально
// Из исходных тасков удалить те которым установлен статус ошибки
var filteredOriginalTasks = tasks . Where ( t = > ! tasksToSetErrorStatus . Contains ( t . TaskId ) ) . ToList ( ) ;
var errorCount = tasksToSetErrorStatus . Count ; // Столько ушло в ошибку
// Определяем, какие имена шаблонов мы БУДЕМ подменять на задачи переименования
var allowedRenameTemplateNames = allowedTasks . Select ( t = > t . Template ! . Name ) . ToList ( ) ;
// Из исходных удаляем те, которые мы сейчас заменим (подменим)
var resultsTasks = filteredOriginalTasks . Where ( t = > ! allowedRenameTemplateNames . Contains ( t . TemplateName ) ) . ToList ( ) ;
// Считаем сколько именно задач мы ВЫКИНУЛИ из исходного списка ради подмены
var replacedCount = filteredOriginalTasks . Count - resultsTasks . Count ;
// В исходные добавляем подменные таски для переименования (мапим их в RobotTaskDetails)
var mappedRenameTasks = allowedTasks . Select ( t = > new RobotTaskDetails ( t . Id , t . Template ! . Name , t . Template . NextRun ) ) . ToList ( ) ;
resultsTasks . AddRange ( mappedRenameTasks ) ;
var injectedCount = mappedRenameTasks . Count ; // Столько задач переименования добавили взамен
// Логируем итоговую статистику трансформации пула задач
// TODO: Позже этот лог можно понизить до Debug!!!
_logger . LogInformation (
"Трансформация пула задач завершена. Исходных: {OriginalCount} шт. " +
"Отклонено (ошибка): {ErrorCount} шт. Удалено обычных для подмены: {ReplacedCount} шт. " +
"Внедрено задач переименования: {InjectedCount} шт. Итого к выдаче: {FinalCount} шт." ,
originalCount , errorCount , replacedCount , injectedCount , resultsTasks . Count ) ;
// Затем отсортируем по NextRun, чтоб ближайшие были выше
return resultsTasks . OrderBy ( t = > t . NextRun ) . ToList ( ) ;
}
/// <summary>
/// Установить статус задания - ошибка
/// </summary>
/// <param name="taskIds"></param>
/// <returns></returns>
private async Task SetErrorStatusAsync ( List < Guid > taskIds , string logMessage )
{
var tasks = await _robotConfigurationRepository . Get ( )
. Include ( t = > t . Template )
. Where ( t = > taskIds . Contains ( t . Id ) ) . ToListAsync ( ) ;
if ( tasks . Count = = 0 )
return ;
foreach ( var task in tasks )
{
// Так как это целевой шаблон, то ставим ему сразу максимальное кол-во попыток и ошибку, чтоб больше он не выдавался в заданиях, пока не исправим связанный
// Устанавливаем статус ошибки
_robotConfigurationRepository . SetErrorRobotStatusAndMaxAttempts ( task ) ;
// Пишем в лог роботу
var history = new RobotHistory
{
Id = Guid . NewGuid ( ) ,
HistoryLevel = ( int ) RobotStatusEnum . Error ,
TaskStatusCode = task . TaskStatusCode ,
RobotConfigurationId = task . Id ,
RobotIp = null ,
RobotId = ParrComponentsEnum . Api . ToString ( ) ,
RobotMessage = logMessage
} ;
await _robotHistoryRepository . CreateAsync ( history ) ;
_logger . LogInformation ( "Для целевого задания {TaskId}, по переименованию шаблона в {TemplateName}, установлен статус ошибки, так как связанное задание с о старым шаблоном с о статусом ошибка." , task . Id , task . Template ! . Name ) ;
}
if ( await _robotHistoryRepository . CommitAsync ( ) )
{
_logger . LogDebug ( "Установлен статус Ошибка, для заданий {TaskCount} шт." , tasks . Count ) ;
}
else
{
_logger . LogError ( "Ошибка при установке статуса задания Ошибка, для заданий {TaskCount} шт." , tasks . Count ) ;
}
2026-05-25 16:59:42 +10:00
}
/// <summary>
/// Взять задачу в работу
/// </summary>
/// <param name="taskId"></param>
/// <returns></returns>
2026-05-26 11:26:52 +10:00
private async Task < Guid ? > AcquireTaskAsync ( List < Guid > tasks , string? robotIp , string? robotId )
2026-05-25 16:59:42 +10:00
{
foreach ( var taskId in tasks )
{
2026-07-22 16:02:04 +10:00
var isChangedStatus = await _robotConfigurationRepository . SetInProgressStatusAsync ( taskId ) ;
2026-05-25 16:59:42 +10:00
if ( isChangedStatus )
{
2026-07-22 16:02:04 +10:00
_logger . LogDebug ( "Захвачена задача {TaskId}" , taskId ) ;
2026-05-25 16:59:42 +10:00
2026-07-22 16:02:04 +10:00
var task = await _robotConfigurationRepository . Get ( )
2026-05-25 16:59:42 +10:00
. AsNoTracking ( )
. FirstAsync ( t = > t . Id = = taskId ) ;
// пишем в историю робота
var history = new RobotHistory
{
Id = Guid . NewGuid ( ) ,
HistoryLevel = ( int ) RobotHistoryLevelEnum . Start ,
TaskStatusCode = task . TaskStatusCode ,
RobotConfigurationId = taskId ,
2026-05-26 11:43:15 +10:00
RobotIp = robotIp ,
RobotId = robotId
2026-05-25 16:59:42 +10:00
} ;
2026-07-22 16:02:04 +10:00
if ( ! await _robotHistoryRepository . CreateAsync ( history ) | | ! await _robotHistoryRepository . CommitAsync ( ) )
2026-05-25 16:59:42 +10:00
throw new DbErrorException ( "Ошибка при добавлении истории робота, при взятии задания в работу." ) ;
return taskId ;
}
else
{
2026-07-22 16:02:04 +10:00
_logger . LogDebug ( "Н е удалось захватить задачу {TaskId}" , taskId ) ;
2026-05-25 16:59:42 +10:00
}
}
2026-07-22 16:02:04 +10:00
_logger . LogDebug ( "Н е удалось захватить ни одну из доступных задач для робота" ) ;
2026-05-25 16:59:42 +10:00
return null ;
}
/// <summary>
/// Получить задачу с о всем необходимыми полями
/// </summary>
/// <param name="taskId"></param>
/// <param name="robotCode"></param>
/// <returns></returns>
private async Task < RobotConfiguration > GetTaskWithAllDataAsync ( Guid taskId , RobotsEnum robotCode )
{
2026-07-22 16:02:04 +10:00
IQueryable < RobotConfiguration > query = _robotConfigurationRepository . Get ( )
2026-05-26 11:26:52 +10:00
//.AsNoTracking() // нужно обязательно трекать, так как может измениться nextRun и е г о нужно будет сохранить
2026-05-25 16:59:42 +10:00
. AsSingleQuery ( )
2026-05-26 11:26:52 +10:00
// Общие инклуды для шаблонов и расписаний
// 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 ) ;
2026-05-25 16:59:42 +10:00
2026-05-26 11:26:52 +10:00
if ( robotCode = = RobotsEnum . ScheduleOrder )
2026-05-25 16:59:42 +10:00
{
2026-05-26 11:26:52 +10:00
// Инклуды только для расписаний
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 ) ;
2026-05-25 16:59:42 +10:00
}
return await query . FirstAsync ( t = > t . Id = = taskId ) ;
}
2026-05-26 11:26:52 +10:00
/// <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);
2026-07-22 16:02:04 +10:00
var nextRun = await _nextRunService . GetNextRunForTemplateAsync ( template . Id , false ) ;
2026-05-26 11:26:52 +10:00
if ( ! nextRun . HasValue )
{
2026-07-22 16:02:04 +10:00
_logger . LogError ( "При обновлении nextRun для шаблона {templateId}, расчитанный nextRun=null, ошибка в расчетах." , template . Id ) ;
2026-05-26 11:26:52 +10:00
return false ;
}
if ( nextRun . Value < DateTimeOffset . UtcNow )
{
2026-07-22 16:02:04 +10:00
_logger . LogError ( "При обновлении nextRun для шаблона {templateId}, расчитанный nextRun<Now [{nextRun}<{now}], ошибка в расчетах." , template . Id , nextRun . Value , DateTimeOffset . UtcNow ) ;
2026-05-26 11:26:52 +10:00
return false ;
}
if ( nextRun ! = template . NextRun )
{
2026-07-22 16:02:04 +10:00
_logger . LogDebug ( $"Для шаблона id {template.Id} обновляю nextRun, новое значение {nextRun}, старое значение {template.NextRun}" ) ;
2026-05-26 11:26:52 +10:00
template . LastRun = template . NextRun ;
template . NextRun = nextRun . Value ;
var suffix = "При получении задания роботом, обновил NextRun" ;
historyInitiator . InitiatorComment =
string . IsNullOrEmpty ( historyInitiator . InitiatorComment )
? suffix
: $"{historyInitiator.InitiatorComment}. {suffix}" ;
2026-07-22 16:02:04 +10:00
if ( ! await _robotConfigurationRepository . CommitAsync ( historyInitiator ) )
2026-05-26 11:26:52 +10:00
throw new DbErrorException ( "Ошибка при сохранении изменения NextRun" ) ;
}
return true ;
}
2026-05-25 16:59:42 +10:00
}
}