Merge branch 'dev' of http://gitlab.dvgd.oao.rzd/devptk/parr/parr_api into dev
This commit is contained in:
@@ -1,12 +1,17 @@
|
||||
namespace PARR.API.Contracts.V1.Responses
|
||||
{
|
||||
public class JobGroupBaseResponse
|
||||
public class JobGroupShortResponse
|
||||
{
|
||||
public Guid Id { get; set; }
|
||||
|
||||
public required string Name { get; set; }
|
||||
}
|
||||
|
||||
//public bool? IsUmbrella { get; set; }
|
||||
public class JobGroupBaseResponse : JobGroupShortResponse
|
||||
{
|
||||
//public Guid Id { get; set; }
|
||||
|
||||
//public required string Name { get; set; }
|
||||
|
||||
public required string ShortDescription { get; set; }
|
||||
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace PARR.API.Contracts.V1.Responses.Statistics
|
||||
{
|
||||
public record StatRobotTaskDetailsResponse
|
||||
{
|
||||
public RobotResponse Robot { get; init; } = null!;
|
||||
public TaskStatusResponse Task { get; init; } = null!;
|
||||
|
||||
public List<StatRobotTaskGroupDetailsResponse> Details { get; init; } = null!;
|
||||
}
|
||||
|
||||
public record StatRobotTaskGroupDetailsResponse
|
||||
{
|
||||
public JobGroupShortResponse JobGroup { get; init; } = null!;
|
||||
public int TemplatesCount { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,9 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PARR.API.Contracts.V1;
|
||||
using PARR.API.Contracts.V1.Responses.Base;
|
||||
using PARR.API.Controllers.V1.Base;
|
||||
using PARR.Core.Services.RobotStatusDetails.Interfaces;
|
||||
using PARR.Domain.Common.Roles;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
@@ -15,12 +17,15 @@ namespace PARR.API.Controllers.V1.Statistics
|
||||
public class StatRobotStatusDetailsController : BaseApiController
|
||||
{
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IRobotStatusDetailsService _robotStatusDetailsService;
|
||||
|
||||
public StatRobotStatusDetailsController(
|
||||
IMapper mapper
|
||||
IMapper mapper,
|
||||
IRobotStatusDetailsService robotStatusDetailsService
|
||||
)
|
||||
{
|
||||
_mapper = mapper;
|
||||
_robotStatusDetailsService = robotStatusDetailsService;
|
||||
}
|
||||
|
||||
|
||||
@@ -33,7 +38,10 @@ namespace PARR.API.Controllers.V1.Statistics
|
||||
[HttpGet(ApiRoutes.StatRobotStatusDetails.Details)]
|
||||
public async Task<IActionResult> Details([FromRoute] RobotsEnum robot, [FromRoute] RobotStatusEnum status)
|
||||
{
|
||||
return Ok();
|
||||
var details = await _robotStatusDetailsService.GetDetailsAsync(robot, status);
|
||||
var response = _mapper.Map<StatRobotStatusDetailsResponse>(details);
|
||||
|
||||
return Ok(new Response<StatRobotStatusDetailsResponse>(response, true));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
using PARR.API.Contracts.V1.Responses;
|
||||
|
||||
namespace PARR.API.Controllers.V1.Statistics
|
||||
{
|
||||
public record StatRobotStatusDetailsResponse
|
||||
{
|
||||
public RobotResponse Robot { get; init; } = null!;
|
||||
public RobotStatusResponse Status { get; init; } = null!;
|
||||
|
||||
public List<StatRobotStatusGroupDetailsResponse> Details { get; init; } = null!;
|
||||
}
|
||||
|
||||
public record StatRobotStatusGroupDetailsResponse
|
||||
{
|
||||
public JobGroupShortResponse JobGroup { get; init; } = null!;
|
||||
public int TemplatesCount { get; init; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PARR.API.Contracts.V1;
|
||||
using PARR.API.Contracts.V1.Responses.Base;
|
||||
using PARR.API.Contracts.V1.Responses.Statistics;
|
||||
using PARR.API.Controllers.V1.Base;
|
||||
using PARR.Core.Services.RobotTaskDetailsServices.Interfaces;
|
||||
using PARR.Domain.Common.Roles;
|
||||
@@ -38,8 +40,9 @@ namespace PARR.API.Controllers.V1.Statistics
|
||||
public async Task<IActionResult> Details([FromRoute] RobotsEnum robot, [FromRoute] TaskStatusEnum task)
|
||||
{
|
||||
var details = await _robotTaskDetailsService.GetDetailsAsync(robot, task);
|
||||
var response = _mapper.Map<StatRobotTaskDetailsResponse>(details);
|
||||
|
||||
return Ok();
|
||||
return Ok(new Response<StatRobotTaskDetailsResponse>(response, true));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
using PARR.API.Authentication.Models;
|
||||
using PARR.API.Contracts.V1.Responses;
|
||||
using PARR.API.Contracts.V1.Responses.Statistics;
|
||||
using PARR.API.Controllers.V1.Statistics;
|
||||
using PARR.API.MappingProfiles.Resolvers;
|
||||
using PARR.Core.Repositories.Interfaces.Schedule;
|
||||
using PARR.Domain.DTOs.Matching;
|
||||
using PARR.Domain.DTOs.RobotMetrics;
|
||||
using PARR.Domain.DTOs.RobotSnapshotDTO;
|
||||
using PARR.Domain.DTOs.RobotStatusDetails;
|
||||
using PARR.Domain.DTOs.RobotTask;
|
||||
using PARR.Domain.DTOs.RobotTaskDetails;
|
||||
using PARR.Domain.DTOs.RobotTaskRobotStatus;
|
||||
using PARR.Domain.DTOs.Shared;
|
||||
using PARR.Domain.DTOs.Shortcode;
|
||||
@@ -376,6 +379,9 @@ namespace PARR.API.MappingProfiles
|
||||
|
||||
#region JobGroup
|
||||
|
||||
CreateMap<JobGroupShortResult, JobGroupShortResponse>()
|
||||
.ForMember(d => d.Name, o => o.MapFrom(s => s.GroupName));
|
||||
|
||||
CreateMap<JobGroup, JobGroupBaseResponse>()
|
||||
.Include<JobGroup, JobGroupResponse>()
|
||||
.Include<JobGroup, JobGroupWithDistributionConfigResponse>()
|
||||
@@ -521,6 +527,27 @@ namespace PARR.API.MappingProfiles
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region StatRobotTaskDetailsResponse
|
||||
|
||||
CreateMap<RobotTaskGroupDetailsResult, StatRobotTaskGroupDetailsResponse>();
|
||||
//todo: ForMember не нужен?
|
||||
//.ForMember(d => d.JobGroup, o => o.MapFrom(s => s.JobGroup));
|
||||
|
||||
CreateMap<RobotTaskDetailsResult, StatRobotTaskDetailsResponse>();
|
||||
//todo: ForMember не нужен?
|
||||
//.ForMember(d => d.Details, o => o.MapFrom(s => s.Details));
|
||||
|
||||
#endregion
|
||||
|
||||
#region StatRobotStatusDetailsResponse
|
||||
|
||||
CreateMap<RobotStatusDetailsResult, StatRobotStatusDetailsResponse>();
|
||||
|
||||
CreateMap<RobotStatusGroupDetailsResult, StatRobotStatusGroupDetailsResponse>();
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@ using PARR.Core.Services.NextRunServices;
|
||||
using PARR.Core.Services.NextRunServices.Subservices;
|
||||
using PARR.Core.Services.RobotMetrics;
|
||||
using PARR.Core.Services.RobotSnapshotServices;
|
||||
using PARR.Core.Services.RobotStatusDetails.Implementations;
|
||||
using PARR.Core.Services.RobotStatusDetails.Interfaces;
|
||||
using PARR.Core.Services.RobotTask.Implementations;
|
||||
using PARR.Core.Services.RobotTask.Interfaces;
|
||||
using PARR.Core.Services.RobotTaskDetailsServices.Implementations;
|
||||
@@ -117,6 +119,7 @@ namespace PARR.Core
|
||||
services.AddScoped<IRobotSnapshotService, RobotSnapshotService>();
|
||||
services.AddScoped<IRobotTaskRobotStatusService, RobotTaskRobotStatusService>();
|
||||
services.AddScoped<IRobotTaskDetailsService, RobotTaskDetailsService>();
|
||||
services.AddScoped<IRobotStatusDetailsService, RobotStatusDetailsService>();
|
||||
|
||||
services.AddScoped<IUnitService, UnitService>();
|
||||
services.AddScoped<UnitCacheService>();
|
||||
|
||||
@@ -25,8 +25,4 @@
|
||||
<!-- Разрешаем Castle DynamicProxy (Moq / NSubstitute) видеть internal классы PARR.Core -->
|
||||
<InternalsVisibleTo Include="DynamicProxyGenAssembly2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Services\RobotStatusDetails\" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
using AutoMapper;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using PARR.Core.Repositories.Interfaces;
|
||||
using PARR.Core.Services.RobotStatusDetails.Interfaces;
|
||||
using PARR.Domain.DTOs.RobotStatusDetails;
|
||||
using PARR.Domain.DTOs.Shared;
|
||||
using PARR.Domain.Enums;
|
||||
using PARR.Domain.Exceptions;
|
||||
|
||||
namespace PARR.Core.Services.RobotStatusDetails.Implementations
|
||||
{
|
||||
internal class RobotStatusDetailsService : IRobotStatusDetailsService
|
||||
{
|
||||
private readonly ILogger<RobotStatusDetailsService> _logger;
|
||||
private readonly IRobotConfigurationRepository _robotConfigurationRepository;
|
||||
private readonly IMapper _mapper;
|
||||
private readonly IRobotRepository _robotRepository;
|
||||
private readonly IRobotStatusRepository _robotStatusRepository;
|
||||
|
||||
public RobotStatusDetailsService(
|
||||
ILogger<RobotStatusDetailsService> logger,
|
||||
IRobotConfigurationRepository robotConfigurationRepository,
|
||||
IMapper mapper,
|
||||
IRobotRepository robotRepository,
|
||||
IRobotStatusRepository robotStatusRepository
|
||||
)
|
||||
{
|
||||
_logger = logger;
|
||||
_robotConfigurationRepository = robotConfigurationRepository;
|
||||
_mapper = mapper;
|
||||
_robotRepository = robotRepository;
|
||||
_robotStatusRepository = robotStatusRepository;
|
||||
}
|
||||
|
||||
|
||||
public async Task<RobotStatusDetailsResult> GetDetailsAsync(RobotsEnum robot, RobotStatusEnum status, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var groupedDetails = await _robotConfigurationRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Where(config => config.RobotCode == (int)robot && config.RobotStatusCode == (int)status)
|
||||
.GroupBy(config => config.Template!.Job!.Group)
|
||||
.Select(t => new
|
||||
{
|
||||
JobGroup = t.Key,
|
||||
TemplatesCount = t.Count()
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var robotEntity = await _robotRepository.Get()
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(r => r.Code == (int)robot, cancellationToken);
|
||||
|
||||
var robotStatusEntity = await _robotStatusRepository.Get()
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(t => t.Code == (int)status, cancellationToken);
|
||||
|
||||
if (robotEntity == null)
|
||||
{
|
||||
_logger.LogWarning("Робот с кодом {RobotCode} не найден в БД", robot);
|
||||
throw new AppValidationException($"Робот с кодом {(int)robot} не найден");
|
||||
}
|
||||
|
||||
if (robotStatusEntity == null)
|
||||
{
|
||||
_logger.LogWarning("Статус робота с кодом {StatusCode} не найден в БД", status);
|
||||
throw new AppValidationException($"Статус робота с кодом {(int)status} не найден");
|
||||
}
|
||||
|
||||
var result = new RobotStatusDetailsResult
|
||||
{
|
||||
Robot = _mapper.Map<RobotResult>(robotEntity),
|
||||
Status = _mapper.Map<RobotStatusResult>(robotStatusEntity),
|
||||
Details = groupedDetails
|
||||
.Select(t => new RobotStatusGroupDetailsResult
|
||||
{
|
||||
JobGroup = _mapper.Map<JobGroupShortResult>(t.JobGroup),
|
||||
TemplatesCount = t.TemplatesCount
|
||||
}).OrderBy(t => t.JobGroup.GroupName)
|
||||
.ToList()
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
using PARR.Domain.DTOs.RobotStatusDetails;
|
||||
using PARR.Domain.Enums;
|
||||
|
||||
namespace PARR.Core.Services.RobotStatusDetails.Interfaces
|
||||
{
|
||||
public interface IRobotStatusDetailsService
|
||||
{
|
||||
Task<RobotStatusDetailsResult> GetDetailsAsync(RobotsEnum robot, RobotStatusEnum status, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
@@ -260,15 +260,18 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
if (tasks.Count == 0 || robotCode != RobotsEnum.TemplateOrder)
|
||||
return tasks;
|
||||
|
||||
_logger.LogDebug("Исходный пул задач для проверки переименования: {Tasks}",
|
||||
string.Join(" | ", tasks.Select(t => $"[Id: {t.TaskId}, Name: '{t.TemplateName}']")));
|
||||
|
||||
// Ищем есть ли связанные шаблоны с таким имененм на переименование
|
||||
var taskTemplateNames = tasks.Select(t => t.TemplateName).Distinct().ToList();
|
||||
// Ищем записи в таблице переименований, где OldName совпадает с именами наших новых задач
|
||||
var templatesToRename = await _templateRenamePendingRepository.Get().AsNoTracking()
|
||||
var templatesToRename = await _templateRenamePendingRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Where(t => taskTemplateNames.Contains(t.OldName))
|
||||
.ToListAsync();
|
||||
|
||||
_logger.LogDebug("Найдено шаблонов в процессе переименования для текущих задач: {Count} шт.", templatesToRename.Count);
|
||||
_logger.LogDebug("Найдено записей в TemplateRenamePending для текущих задач: {Count} шт.", templatesToRename.Count);
|
||||
|
||||
if (templatesToRename.Count == 0)
|
||||
return tasks;
|
||||
@@ -288,7 +291,9 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
&& t.TaskStatusCode == (int)TaskStatusEnum.Updating
|
||||
).ToListAsync();
|
||||
|
||||
// --- Блок обработки ошибок ---
|
||||
// =========================================================================
|
||||
// БЛОК 1: ОБРАБОТКА ОШИБОК (Правило: если ХОТЯ БЫ ОДНА упала в ошибку -> оригинал в ошибку)
|
||||
// =========================================================================
|
||||
|
||||
// Если старый шаблон в ошибке и лимит попыток исчерпан, ставим ошибку и новому шаблону
|
||||
var errorTasks = renameTasks
|
||||
@@ -300,16 +305,16 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
var tasksToSetErrorStatus = new List<Guid>();
|
||||
if (errorTasks.Count > 0)
|
||||
{
|
||||
_logger.LogDebug("Найдено старых заданий на переименование с ошибками: {ErrorCount}. Ставим ошибку целевым (новым) заданиям.", errorTasks.Count);
|
||||
_logger.LogDebug("Найдено связанных заданий на переименование с ошибками: {ErrorCount}. Ставим ошибку целевым (новым) заданиям.", errorTasks.Count);
|
||||
|
||||
//var errorTemplateNames = errorTasks.Select(t => t.Template!.Name).ToHashSet();
|
||||
// Берем OldName из словаря
|
||||
// Собираем ВСЕ OldName, для которых есть хотя бы одна упавшая в ошибку задача.
|
||||
// Использование ToHashSet() гарантирует, что если 1 или 10 задач в ошибке, OldName попадет в набор один раз.
|
||||
var errorOldNames = errorTasks
|
||||
.Where(t => templateIdToOldName.ContainsKey(t.TemplateId))
|
||||
.Select(t => templateIdToOldName[t.TemplateId])
|
||||
.ToHashSet();
|
||||
|
||||
// Берем целевые таски, находим в них задания которым надо поставить ошибку
|
||||
// Находим оригинальные задачи, чье имя совпадает с любым из "ошибочных" OldName
|
||||
tasksToSetErrorStatus = tasks
|
||||
.Where(t => errorOldNames.Contains(t.TemplateName))
|
||||
.Select(t => t.TaskId)
|
||||
@@ -318,16 +323,17 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
if (tasksToSetErrorStatus.Count > 0)
|
||||
{
|
||||
// Устанавливаем ошибку целевым + пишем комментарий от робота + нажимаем комит
|
||||
var logMessage = "[RobotTaskService] Установлен статус ошибки, так как не переименован связанный шаблон";
|
||||
var logMessage = "[RobotTaskService] Установлен статус ошибки, так как хотя бы одна из связанных задач переименования не была успешно выполнена.";
|
||||
await SetErrorStatusAsync(tasksToSetErrorStatus, logMessage);
|
||||
}
|
||||
}
|
||||
|
||||
// --- Блок подмены задач ---
|
||||
|
||||
// =========================================================================
|
||||
// БЛОК 2: ПОДМЕНА ЗАДАЧ (Правило: берем ПЕРВУЮ валидную задачу для подмены)
|
||||
// =========================================================================
|
||||
var endDate = DateTimeOffset.UtcNow.Add(-_settingsFromDb.RobotWaitTime);
|
||||
|
||||
// Фильтруем старые задачи, которые МОЖНО взять в работу. Смотрим статусы роботов, можно взять в работу, только если (RobotStatus == Wait) или (InpRogress но которые еще не просрочены)
|
||||
// Фильтруем старые задачи, которые МОЖНО взять в работу. Смотрим статусы роботов, можно взять в работу, только если (RobotStatus == Wait) или (InProgress но которые еще не просрочены)
|
||||
var allowedTasks = renameTasks.Where(t =>
|
||||
t.RobotStatusCode == (int)RobotStatusEnum.Wait
|
||||
|| (t.RobotStatusCode == (int)RobotStatusEnum.InProgress
|
||||
@@ -336,20 +342,28 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
).ToList();
|
||||
|
||||
// Проверим StatusTypeId у старых шаблонов в процессе переименования
|
||||
// 1. Находим задачи переименования, у которых StatusTypeId шаблона != Used
|
||||
// 1. Находим задачи переименования, у которых StatusTypeId шаблона НЕ является допустимым (!= Used и != Unused)
|
||||
var invalidRenameTasks = allowedTasks
|
||||
.Where(t => t.Template != null && t.Template.StatusTypeId != TemplateStatusTypeEnum.Used)
|
||||
.Where(t => t.Template != null && t.Template.StatusTypeId != TemplateStatusTypeEnum.Used && t.Template.StatusTypeId != TemplateStatusTypeEnum.Unused)
|
||||
.ToList();
|
||||
|
||||
// 2. Создаем словарь для быстрого поиска и логирования: OldName -> StatusTypeId
|
||||
// Так как OldName не уникален, используем GroupBy, чтобы избежать ArgumentException, при наличии нескольких невалидных задач с одинаковым OldName.
|
||||
//var invalidOldNamesWithStatus = invalidRenameTasks
|
||||
// .Where(t => templateIdToOldName.ContainsKey(t.TemplateId))
|
||||
// .Select(t => new { OldName = templateIdToOldName[t.TemplateId], StatusTypeId = t.Template!.StatusTypeId })
|
||||
// .ToDictionary(x => x.OldName, x => x.StatusTypeId);
|
||||
var invalidOldNamesWithStatus = invalidRenameTasks
|
||||
.Where(t => templateIdToOldName.ContainsKey(t.TemplateId))
|
||||
.Select(t => new { OldName = templateIdToOldName[t.TemplateId], StatusTypeId = t.Template!.StatusTypeId })
|
||||
.ToDictionary(x => x.OldName, x => x.StatusTypeId);
|
||||
.GroupBy(t => templateIdToOldName[t.TemplateId]) // Группируем по OldName
|
||||
.ToDictionary(
|
||||
g => g.Key, // Ключ = OldName
|
||||
g => g.First().Template!.StatusTypeId // Значение = StatusTypeId первой задачи в группе (для лога)
|
||||
);
|
||||
|
||||
// 3. Оставляем для подмены только те задачи, у которых StatusTypeId == 0
|
||||
// 3. Оставляем для подмены только те задачи, у которых StatusTypeId является допустимым (== Used или == Unused)
|
||||
var validAllowedTasks = allowedTasks
|
||||
.Where(t => t.Template != null && t.Template.StatusTypeId == TemplateStatusTypeEnum.Used)
|
||||
.Where(t => t.Template != null && (t.Template.StatusTypeId == TemplateStatusTypeEnum.Used || t.Template.StatusTypeId == TemplateStatusTypeEnum.Unused))
|
||||
.ToList();
|
||||
|
||||
// Формируем список заданий
|
||||
@@ -357,14 +371,16 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
var errorTaskIdsSet = tasksToSetErrorStatus.ToHashSet();
|
||||
var errorCount = errorTaskIdsSet.Count;
|
||||
|
||||
// Создаем словарь подмены ТОЛЬКО из валидных задач (где StatusTypeId == Used)
|
||||
// Создаем словарь подмены ТОЛЬКО из валидных задач (где StatusTypeId == Used или Unused)
|
||||
// ГРУППИРУЕМ по OldName и берем .First()!
|
||||
// Это реализует правило: "если записей несколько, берем из них первую и подменяем ей оригинальное задание".
|
||||
var renameTasksToDictionary = validAllowedTasks
|
||||
.Where(t => templateIdToOldName.ContainsKey(t.TemplateId))
|
||||
.GroupBy(t => templateIdToOldName[t.TemplateId])
|
||||
.ToDictionary(
|
||||
g => g.Key,
|
||||
g => new RobotTaskDetails(g.First().Id, g.First().Template!.Name, g.First().Template!.NextRun)
|
||||
);
|
||||
.Where(t => templateIdToOldName.ContainsKey(t.TemplateId))
|
||||
.GroupBy(t => templateIdToOldName[t.TemplateId])
|
||||
.ToDictionary(
|
||||
g => g.Key, // Ключ = OldName
|
||||
g => new RobotTaskDetails(g.First().Id, g.First().Template!.Name, g.First().Template!.NextRun)
|
||||
);
|
||||
|
||||
// Проходим по ИСХОДНОМУ списку, чтобы сохранить порядок сортировки
|
||||
var finalTasks = new List<RobotTaskDetails>(tasks.Count);
|
||||
@@ -384,7 +400,7 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"Задача для шаблона '{TemplateName}' (TaskId: {TaskId}) ИСКЛЮЧЕНА из выдачи. " +
|
||||
"Связанный шаблон в процессе переименования имеет недопустимый StatusTypeId = {StatusTypeId} (ожидалось Used). " +
|
||||
"Связанный шаблон в процессе переименования имеет недопустимый StatusTypeId = {StatusTypeId} (ожидалось Used или Unused). " +
|
||||
"Исходная задача также не выполняется.",
|
||||
task.TemplateName, task.TaskId, badStatusId);
|
||||
|
||||
@@ -393,8 +409,13 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
}
|
||||
|
||||
// 3. Если для этого имени шаблона есть разрешенная задача на переименование (и она валидна) - вставляем ее
|
||||
// Подменяем оригинальную задачу на ПЕРВУЮ валидную задачу переименования
|
||||
if (renameTasksToDictionary.TryGetValue(task.TemplateName, out var renameTask))
|
||||
{
|
||||
_logger.LogDebug("ПОДМЕНА ЗАДАЧИ: Исходная [Id: {OriginalId}, Name: '{OriginalName}'] " +
|
||||
"-> Заменена на [Id: {NewId}, Name: '{NewName}']",
|
||||
task.TaskId, task.TemplateName, renameTask.TaskId, renameTask.TemplateName);
|
||||
|
||||
finalTasks.Add(renameTask);
|
||||
replacedCount++;
|
||||
}
|
||||
@@ -405,11 +426,13 @@ namespace PARR.Core.Services.RobotTask.Implementations
|
||||
}
|
||||
}
|
||||
|
||||
// Логируем итоговую статистику трансформации пула задач
|
||||
_logger.LogDebug("Итоговый пул задач после трансформации: {Tasks}",
|
||||
string.Join(" | ", finalTasks.Select(t => $"[Id: {t.TaskId}, Name: '{t.TemplateName}']")));
|
||||
|
||||
_logger.LogInformation(
|
||||
"Трансформация пула задач завершена. Исходных: {OriginalCount} шт. " +
|
||||
"Отклонено (ошибка): {ErrorCount} шт. Исключено (невалидный StatusTypeId): {ExcludedCount} шт. " +
|
||||
"Заменено на старые: {ReplacedCount} шт. Итого к выдаче: {FinalCount} шт.",
|
||||
"Заменено на старые (взята первая из группы): {ReplacedCount} шт. Итого к выдаче: {FinalCount} шт.",
|
||||
originalCount, errorCount, excludedByStatusCount, replacedCount, finalTasks.Count);
|
||||
|
||||
// Возвращаем без дополнительной сортировки по NextRun. Порядок сохранен начального списка
|
||||
|
||||
@@ -6,6 +6,7 @@ using PARR.Core.Services.RobotTaskDetailsServices.Interfaces;
|
||||
using PARR.Domain.DTOs.RobotTaskDetails;
|
||||
using PARR.Domain.DTOs.Shared;
|
||||
using PARR.Domain.Enums;
|
||||
using PARR.Domain.Exceptions;
|
||||
|
||||
namespace PARR.Core.Services.RobotTaskDetailsServices.Implementations
|
||||
{
|
||||
@@ -32,34 +33,55 @@ namespace PARR.Core.Services.RobotTaskDetailsServices.Implementations
|
||||
_taskStatusRepository = taskStatusRepository;
|
||||
}
|
||||
|
||||
public async Task<RobotTaskDetailsResult> GetDetailsAsync(RobotsEnum robot, TaskStatusEnum task)
|
||||
public async Task<RobotTaskDetailsResult> GetDetailsAsync(RobotsEnum robot, TaskStatusEnum task, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var details = await _robotConfigurationRepository.Get()
|
||||
.Where(t => t.RobotCode == (int)robot && t.TaskStatusCode == (int)task)
|
||||
.GroupBy(t => t.Template!.Job!.Group)
|
||||
.Select(t => new
|
||||
{
|
||||
JobGroup = t.Key,
|
||||
TemplatesCount = t.Count()
|
||||
}).ToListAsync();
|
||||
// 1. Получаем группировку конфигураций
|
||||
var groupedDetails = await _robotConfigurationRepository.Get()
|
||||
.AsNoTracking()
|
||||
.Where(config => config.RobotCode == (int)robot && config.TaskStatusCode == (int)task)
|
||||
.GroupBy(config => config.Template!.Job!.Group)
|
||||
.Select(t => new
|
||||
{
|
||||
JobGroup = t.Key,
|
||||
TemplatesCount = t.Count()
|
||||
})
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
var robotObj = await _robotRepository.Get()
|
||||
// 2. Получаем сущности робота и статуса задачи)
|
||||
var robotEntity = await _robotRepository.Get()
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(t => t.Code == (int)robot);
|
||||
.FirstOrDefaultAsync(r => r.Code == (int)robot, cancellationToken);
|
||||
|
||||
var taskObj = await _taskStatusRepository.Get()
|
||||
var taskStatusEntity = await _taskStatusRepository.Get()
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(t => t.Code == (int)task);
|
||||
.FirstOrDefaultAsync(t => t.Code == (int)task, cancellationToken);
|
||||
|
||||
|
||||
if (robotEntity == null)
|
||||
{
|
||||
_logger.LogWarning("Робот с кодом {RobotCode} не найден в БД", robot);
|
||||
throw new AppValidationException($"Робот с кодом {(int)robot} не найден");
|
||||
}
|
||||
|
||||
if (taskStatusEntity == null)
|
||||
{
|
||||
_logger.LogWarning("Статус задачи с кодом {TaskCode} не найден в БД", task);
|
||||
throw new AppValidationException($"Статус задачи с кодом {(int)task} не найден");
|
||||
}
|
||||
|
||||
// 3. Маппинг и сборка результирующего DTO
|
||||
var result = new RobotTaskDetailsResult
|
||||
{
|
||||
Robot = _mapper.Map<RobotResult>(robotObj),
|
||||
Task = _mapper.Map<RobotTaskStatusResult>(taskObj),
|
||||
Details = details.Select(t => new RobotTaskGroupDetailsResult
|
||||
{
|
||||
JobGroup = _mapper.Map<JobGroupShortResult>(t.JobGroup),
|
||||
TemplatesCount = t.TemplatesCount
|
||||
}).OrderBy(t => t.JobGroup.GroupName).ToList()
|
||||
Robot = _mapper.Map<RobotResult>(robotEntity),
|
||||
Task = _mapper.Map<RobotTaskStatusResult>(taskStatusEntity),
|
||||
Details = groupedDetails
|
||||
.Select(t => new RobotTaskGroupDetailsResult
|
||||
{
|
||||
JobGroup = _mapper.Map<JobGroupShortResult>(t.JobGroup),
|
||||
TemplatesCount = t.TemplatesCount
|
||||
})
|
||||
.OrderBy(d => d.JobGroup.GroupName)
|
||||
.ToList()
|
||||
};
|
||||
|
||||
return result;
|
||||
|
||||
@@ -11,6 +11,6 @@ namespace PARR.Core.Services.RobotTaskDetailsServices.Interfaces
|
||||
/// <param name="robot"></param>
|
||||
/// <param name="task"></param>
|
||||
/// <returns></returns>
|
||||
Task<RobotTaskDetailsResult> GetDetailsAsync(RobotsEnum robot, TaskStatusEnum task);
|
||||
Task<RobotTaskDetailsResult> GetDetailsAsync(RobotsEnum robot, TaskStatusEnum task, CancellationToken cancellationToken = default);
|
||||
}
|
||||
}
|
||||
|
||||
4135
PARR.DAL/Migrations/20260728234721_tblRobotConfigurationsAddIndexes.Designer.cs
generated
Normal file
4135
PARR.DAL/Migrations/20260728234721_tblRobotConfigurationsAddIndexes.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,45 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PARR.DAL.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class tblRobotConfigurationsAddIndexes : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_RobotConfigurations_RobotCode",
|
||||
table: "RobotConfigurations");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RobotConfigurations_RobotCode_RobotStatusCode",
|
||||
table: "RobotConfigurations",
|
||||
columns: new[] { "RobotCode", "RobotStatusCode" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RobotConfigurations_RobotCode_TaskStatusCode",
|
||||
table: "RobotConfigurations",
|
||||
columns: new[] { "RobotCode", "TaskStatusCode" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_RobotConfigurations_RobotCode_RobotStatusCode",
|
||||
table: "RobotConfigurations");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_RobotConfigurations_RobotCode_TaskStatusCode",
|
||||
table: "RobotConfigurations");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RobotConfigurations_RobotCode",
|
||||
table: "RobotConfigurations",
|
||||
column: "RobotCode");
|
||||
}
|
||||
}
|
||||
}
|
||||
4137
PARR.DAL/Migrations/20260729041405_tblTemplateRenamePendingAddDateModifiedRemUniqueIndex.Designer.cs
generated
Normal file
4137
PARR.DAL/Migrations/20260729041405_tblTemplateRenamePendingAddDateModifiedRemUniqueIndex.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace PARR.DAL.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class tblTemplateRenamePendingAddDateModifiedRemUniqueIndex : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_TemplateRenamePendings_OldName",
|
||||
schema: "template",
|
||||
table: "TemplateRenamePendings");
|
||||
|
||||
migrationBuilder.AddColumn<DateTimeOffset>(
|
||||
name: "DateModified",
|
||||
schema: "template",
|
||||
table: "TemplateRenamePendings",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TemplateRenamePendings_OldName",
|
||||
schema: "template",
|
||||
table: "TemplateRenamePendings",
|
||||
column: "OldName");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_TemplateRenamePendings_OldName",
|
||||
schema: "template",
|
||||
table: "TemplateRenamePendings");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "DateModified",
|
||||
schema: "template",
|
||||
table: "TemplateRenamePendings");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TemplateRenamePendings_OldName",
|
||||
schema: "template",
|
||||
table: "TemplateRenamePendings",
|
||||
column: "OldName",
|
||||
unique: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -950,12 +950,14 @@ namespace PARR.DAL.Migrations
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RobotCode");
|
||||
|
||||
b.HasIndex("RobotStatusCode");
|
||||
|
||||
b.HasIndex("TaskStatusCode");
|
||||
|
||||
b.HasIndex("RobotCode", "RobotStatusCode");
|
||||
|
||||
b.HasIndex("RobotCode", "TaskStatusCode");
|
||||
|
||||
b.HasIndex("TemplateId", "RobotCode")
|
||||
.IsUnique();
|
||||
|
||||
@@ -2858,6 +2860,9 @@ namespace PARR.DAL.Migrations
|
||||
b.Property<DateTimeOffset>("DateCreated")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<DateTimeOffset?>("DateModified")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("OldName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text")
|
||||
@@ -2865,8 +2870,7 @@ namespace PARR.DAL.Migrations
|
||||
|
||||
b.HasKey("TemplateId");
|
||||
|
||||
b.HasIndex("OldName")
|
||||
.IsUnique();
|
||||
b.HasIndex("OldName");
|
||||
|
||||
b.ToTable("TemplateRenamePendings", "template", t =>
|
||||
{
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
using PARR.Domain.DTOs.Shared;
|
||||
|
||||
namespace PARR.Domain.DTOs.RobotStatusDetails
|
||||
{
|
||||
public record RobotStatusDetailsResult
|
||||
{
|
||||
public RobotResult Robot { get; init; } = null!;
|
||||
public RobotStatusResult Status { get; init; } = null!;
|
||||
|
||||
public List<RobotStatusGroupDetailsResult> Details { get; init; } = null!;
|
||||
}
|
||||
|
||||
public record RobotStatusGroupDetailsResult
|
||||
{
|
||||
public JobGroupShortResult JobGroup { get; init; } = null!;
|
||||
public int TemplatesCount { get; init; }
|
||||
}
|
||||
}
|
||||
@@ -4,15 +4,15 @@ namespace PARR.Domain.DTOs.RobotTaskDetails
|
||||
{
|
||||
public record RobotTaskDetailsResult
|
||||
{
|
||||
public RobotResult Robot { get; init; }
|
||||
public RobotTaskStatusResult Task { get; init; }
|
||||
public RobotResult Robot { get; init; } = null!;
|
||||
public RobotTaskStatusResult Task { get; init; } = null!;
|
||||
|
||||
public List<RobotTaskGroupDetailsResult> Details { get; init; }
|
||||
public List<RobotTaskGroupDetailsResult> Details { get; init; } = null!;
|
||||
}
|
||||
|
||||
public record RobotTaskGroupDetailsResult
|
||||
{
|
||||
public JobGroupShortResult JobGroup { get; init; }
|
||||
public JobGroupShortResult JobGroup { get; init; } = null!;
|
||||
public int TemplatesCount { get; init; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ namespace PARR.Domain.Entities
|
||||
[Index(nameof(TemplateId), nameof(RobotCode), IsUnique = true)]
|
||||
[Index(nameof(TemplateId), nameof(TaskStatusCode))]
|
||||
[Index(nameof(TemplateId), nameof(RobotStatusCode))]
|
||||
[Index(nameof(RobotCode), nameof(TaskStatusCode))]
|
||||
[Index(nameof(RobotCode), nameof(RobotStatusCode))]
|
||||
public class RobotConfiguration : IBaseEntity
|
||||
{
|
||||
[Key]
|
||||
|
||||
@@ -10,7 +10,8 @@ namespace PARR.Domain.Entities.TemplateEntities
|
||||
/// </summary>
|
||||
[Table("TemplateRenamePendings", Schema = DatabaseSchemas.Template)]
|
||||
[Comment("Шаблоны находящиеся в процессе переименования")]
|
||||
[Index(nameof(OldName), IsUnique = true)]
|
||||
//[Index(nameof(OldName), IsUnique = true)]
|
||||
[Index(nameof(OldName))]
|
||||
public class TemplateRenamePending
|
||||
{
|
||||
[Key]
|
||||
@@ -18,6 +19,8 @@ namespace PARR.Domain.Entities.TemplateEntities
|
||||
|
||||
public DateTimeOffset DateCreated { get; set; }
|
||||
|
||||
public DateTimeOffset? DateModified { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Старое имя шаблона
|
||||
/// </summary>
|
||||
@@ -26,6 +29,6 @@ namespace PARR.Domain.Entities.TemplateEntities
|
||||
|
||||
|
||||
[ForeignKey(nameof(TemplateId))]
|
||||
public required Template Template { get; set; }
|
||||
public Template? Template { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,46 +299,87 @@ namespace PARR.TemplateUpdater.Services
|
||||
/// <returns></returns>
|
||||
private async Task<bool> PrepareOldTemplateNameAsync(string oldName, string newName, Template template)
|
||||
{
|
||||
// Проверяем, не запущено ли уже переименование для этого шаблона
|
||||
var alreadyPending = await _templateRenamePendingRepository.Get()
|
||||
.AsNoTracking()
|
||||
var existRenamePending = await _templateRenamePendingRepository.Get()
|
||||
.FirstOrDefaultAsync(t => t.TemplateId == template.Id);
|
||||
|
||||
if (alreadyPending != null)
|
||||
if (existRenamePending != null)
|
||||
{
|
||||
logger.LogError("При попытке переименования шаблона {TemplateId}, из '{OldName}' в '{NewName}', " +
|
||||
"произошла ошибка, этот шаблон уже находится в процессе переименования (старое имя {PendingName})", template.Id, oldName, newName, alreadyPending.OldName);
|
||||
return false;
|
||||
logger.LogInformation(
|
||||
"Найдено существующее задание на переименование шаблона {TemplateId}. " +
|
||||
"Обновляю OldName с '{OriginalOldName}' на '{NewOldName}'.",
|
||||
template.Id, existRenamePending.OldName, oldName);
|
||||
|
||||
// Подменяем имя шаблона
|
||||
existRenamePending.OldName = oldName;
|
||||
existRenamePending.DateModified = DateTimeOffset.UtcNow;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Создаем запись
|
||||
var pendingRename = new TemplateRenamePending
|
||||
{
|
||||
TemplateId = template.Id,
|
||||
DateCreated = DateTimeOffset.UtcNow,
|
||||
OldName = oldName
|
||||
};
|
||||
|
||||
logger.LogInformation(
|
||||
"Добавлен шаблон в таблицу ожидания переименования. TemplateId: {TemplateId}, OldName: '{OldName}', NewName: '{NewName}'.",
|
||||
template.Id, oldName, newName);
|
||||
|
||||
var addResult = await _templateRenamePendingRepository.CreateAsync(pendingRename);
|
||||
if (!addResult)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Уникально ли имя в таблице ожидания переименования
|
||||
var existPendingOldName = await _templateRenamePendingRepository.Get()
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(t => t.OldName == oldName);
|
||||
#region Old
|
||||
|
||||
if (existPendingOldName != null)
|
||||
{
|
||||
logger.LogError("При добавлении старого имени в таблицу ожидания для шаблона {TemplateId} обнаружен конфликт: " +
|
||||
"имя '{ExistOldName}' уже зарезервировано другим процессом для шаблона {ExistTemplateId}",
|
||||
template.Id, existPendingOldName.OldName, existPendingOldName.TemplateId);
|
||||
//// Проверяем, не запущено ли уже переименование для этого шаблона
|
||||
//var alreadyPending = await _templateRenamePendingRepository.Get()
|
||||
// .AsNoTracking()
|
||||
// .FirstOrDefaultAsync(t => t.TemplateId == template.Id);
|
||||
|
||||
return false;
|
||||
}
|
||||
//if (alreadyPending != null)
|
||||
//{
|
||||
// logger.LogError("При попытке переименования шаблона {TemplateId}, из '{OldName}' в '{NewName}', " +
|
||||
// "произошла ошибка, этот шаблон уже находится в процессе переименования (старое имя {PendingName})", template.Id, oldName, newName, alreadyPending.OldName);
|
||||
// return false;
|
||||
//}
|
||||
|
||||
// Все нормально, добавляем запись в таблицу
|
||||
var pendingRename = new TemplateRenamePending
|
||||
{
|
||||
TemplateId = template.Id,
|
||||
DateCreated = DateTimeOffset.UtcNow,
|
||||
OldName = oldName,
|
||||
Template = template
|
||||
};
|
||||
//// Уникально ли имя в таблице ожидания переименования
|
||||
//var existPendingOldName = await _templateRenamePendingRepository.Get()
|
||||
// .AsNoTracking()
|
||||
// .FirstOrDefaultAsync(t => t.OldName == oldName);
|
||||
|
||||
var addResult = await _templateRenamePendingRepository.CreateAsync(pendingRename);
|
||||
if (!addResult)
|
||||
return false;
|
||||
//if (existPendingOldName != null)
|
||||
//{
|
||||
// logger.LogError("При добавлении старого имени в таблицу ожидания для шаблона {TemplateId} обнаружен конфликт: " +
|
||||
// "имя '{ExistOldName}' уже зарезервировано другим процессом для шаблона {ExistTemplateId}",
|
||||
// template.Id, existPendingOldName.OldName, existPendingOldName.TemplateId);
|
||||
|
||||
return true;
|
||||
// return false;
|
||||
//}
|
||||
|
||||
//// Все нормально, добавляем запись в таблицу
|
||||
//var pendingRename = new TemplateRenamePending
|
||||
//{
|
||||
// TemplateId = template.Id,
|
||||
// DateCreated = DateTimeOffset.UtcNow,
|
||||
// OldName = oldName,
|
||||
// Template = template
|
||||
//};
|
||||
|
||||
//var addResult = await _templateRenamePendingRepository.CreateAsync(pendingRename);
|
||||
//if (!addResult)
|
||||
// return false;
|
||||
|
||||
//return true;
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user