diff --git a/PARR.API/Contracts/V1/ApiRoutes.cs b/PARR.API/Contracts/V1/ApiRoutes.cs
index b52b7878..a84f37ec 100644
--- a/PARR.API/Contracts/V1/ApiRoutes.cs
+++ b/PARR.API/Contracts/V1/ApiRoutes.cs
@@ -212,11 +212,21 @@ namespace PARR.API.Contracts.V1
public const string GetPeriodStatistics = BaseStat + "/robot-tasks/{robot}/period/";
}
+ public static class StatRobotTaskDetails
+ {
+ public const string Details = BaseStat + "/robot-tasks/details/{robot}/{task}";
+ }
+
public static class StatRobotStatus
{
public const string Get = BaseStat + "/robot-statuses/";
}
+ public static class StatRobotStatusDetails
+ {
+ public const string Details = BaseStat + "/robot-statuses/details/{robot}/{status}";
+ }
+
public static class StatRobotHistory
{
public const string Get = BaseStat + "/robot-histories/";
diff --git a/PARR.API/Controllers/V1/Statistics/StatRobotStatusDetailsController.cs b/PARR.API/Controllers/V1/Statistics/StatRobotStatusDetailsController.cs
new file mode 100644
index 00000000..caf87b10
--- /dev/null
+++ b/PARR.API/Controllers/V1/Statistics/StatRobotStatusDetailsController.cs
@@ -0,0 +1,40 @@
+using AutoMapper;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using PARR.API.Contracts.V1;
+using PARR.API.Controllers.V1.Base;
+using PARR.Domain.Common.Roles;
+using PARR.Domain.Enums;
+
+namespace PARR.API.Controllers.V1.Statistics
+{
+ ///
+ /// Детальная статистика по статусам заданий роботам
+ ///
+ [Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)]
+ public class StatRobotStatusDetailsController : BaseApiController
+ {
+ private readonly IMapper _mapper;
+
+ public StatRobotStatusDetailsController(
+ IMapper mapper
+ )
+ {
+ _mapper = mapper;
+ }
+
+
+ ///
+ /// Список групп работ по статусам заданий роботам
+ ///
+ ///
+ ///
+ ///
+ [HttpGet(ApiRoutes.StatRobotStatusDetails.Details)]
+ public async Task Details([FromRoute] RobotsEnum robot, [FromRoute] RobotStatusEnum status)
+ {
+ return Ok();
+ }
+
+ }
+}
diff --git a/PARR.API/Controllers/V1/Statistics/StatRobotTaskDetailsController.cs b/PARR.API/Controllers/V1/Statistics/StatRobotTaskDetailsController.cs
new file mode 100644
index 00000000..7edd3c4c
--- /dev/null
+++ b/PARR.API/Controllers/V1/Statistics/StatRobotTaskDetailsController.cs
@@ -0,0 +1,46 @@
+using AutoMapper;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
+using PARR.API.Contracts.V1;
+using PARR.API.Controllers.V1.Base;
+using PARR.Core.Services.RobotTaskDetailsServices.Interfaces;
+using PARR.Domain.Common.Roles;
+using PARR.Domain.Enums;
+
+namespace PARR.API.Controllers.V1.Statistics
+{
+ ///
+ /// Детальная статистика по заданиям роботам
+ ///
+ [Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)]
+ public class StatRobotTaskDetailsController : BaseApiController
+ {
+ private readonly IMapper _mapper;
+ private readonly IRobotTaskDetailsService _robotTaskDetailsService;
+
+ public StatRobotTaskDetailsController(
+ IMapper mapper,
+ IRobotTaskDetailsService robotTaskDetailsService
+ )
+ {
+ _mapper = mapper;
+ _robotTaskDetailsService = robotTaskDetailsService;
+ }
+
+
+ ///
+ /// Список групп работ по заданиям роботам
+ ///
+ ///
+ ///
+ ///
+ [HttpGet(ApiRoutes.StatRobotTaskDetails.Details)]
+ public async Task Details([FromRoute] RobotsEnum robot, [FromRoute] TaskStatusEnum task)
+ {
+ var details = await _robotTaskDetailsService.GetDetailsAsync(robot, task);
+
+ return Ok();
+ }
+
+ }
+}
diff --git a/PARR.API/Controllers/V1/Statistics/StatTemplateAutoControlController.cs b/PARR.API/Controllers/V1/Statistics/StatTemplateAutoControlController.cs
index 49205754..8e82fca4 100644
--- a/PARR.API/Controllers/V1/Statistics/StatTemplateAutoControlController.cs
+++ b/PARR.API/Controllers/V1/Statistics/StatTemplateAutoControlController.cs
@@ -1,4 +1,5 @@
-using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using PARR.API.Contracts.V1;
using PARR.API.Contracts.V1.Requests.BaseRequests;
@@ -8,10 +9,12 @@ using PARR.API.Controllers.V1.Base;
using PARR.API.Helpers;
using PARR.Core.Repositories.Interfaces;
using PARR.Core.Services.NextRunServices;
+using PARR.Domain.Common.Roles;
using PARR.Domain.Enums;
namespace PARR.API.Controllers.V1.Statistics
{
+ [Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)]
public class StatTemplateAutoControlController : BaseApiController
{
private readonly ITemplateRepository templateService;
diff --git a/PARR.Core/DependencyInjection.cs b/PARR.Core/DependencyInjection.cs
index e611a188..daf4c62e 100644
--- a/PARR.Core/DependencyInjection.cs
+++ b/PARR.Core/DependencyInjection.cs
@@ -12,6 +12,8 @@ using PARR.Core.Services.RobotMetrics;
using PARR.Core.Services.RobotSnapshotServices;
using PARR.Core.Services.RobotTask.Implementations;
using PARR.Core.Services.RobotTask.Interfaces;
+using PARR.Core.Services.RobotTaskDetailsServices.Implementations;
+using PARR.Core.Services.RobotTaskDetailsServices.Interfaces;
using PARR.Core.Services.RobotTaskRobotStatus.Implemetations;
using PARR.Core.Services.RobotTaskRobotStatus.Interfaces;
using PARR.Core.Services.Shortcodes;
@@ -114,6 +116,7 @@ namespace PARR.Core
services.AddScoped();
services.AddScoped();
services.AddScoped();
+ services.AddScoped();
services.AddScoped();
services.AddScoped();
diff --git a/PARR.Core/Infrastructure/Mapping/Shared/JobGroupResultMappingProfile.cs b/PARR.Core/Infrastructure/Mapping/Shared/JobGroupResultMappingProfile.cs
new file mode 100644
index 00000000..8993090d
--- /dev/null
+++ b/PARR.Core/Infrastructure/Mapping/Shared/JobGroupResultMappingProfile.cs
@@ -0,0 +1,17 @@
+using AutoMapper;
+using PARR.Domain.DTOs.Shared;
+using PARR.Domain.Entities.JobGroupEntities;
+
+namespace PARR.Core.Infrastructure.Mapping.Shared
+{
+ public class JobGroupResultMappingProfile: Profile
+ {
+ public JobGroupResultMappingProfile()
+ {
+ CreateMap()
+ .Include();
+
+ CreateMap();
+ }
+ }
+}
diff --git a/PARR.Core/PARR.Core.csproj b/PARR.Core/PARR.Core.csproj
index a13275dd..86e44fa7 100644
--- a/PARR.Core/PARR.Core.csproj
+++ b/PARR.Core/PARR.Core.csproj
@@ -25,4 +25,8 @@
+
+
+
+
diff --git a/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs b/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs
index 0ff18d5b..d058a5c3 100644
--- a/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs
+++ b/PARR.Core/Services/RobotTask/Implementations/RobotTaskService.cs
@@ -273,6 +273,9 @@ namespace PARR.Core.Services.RobotTask.Implementations
if (templatesToRename.Count == 0)
return tasks;
+ // Создаем словарь маппинга TemplateId -> OldName.
+ var templateIdToOldName = templatesToRename.ToDictionary(t => t.TemplateId, t => t.OldName);
+
// Ищем конфигурации роботов для СТАРЫХ шаблонов (которые переименовываются) по ИД, смотрим, можем ли взять их в работу
var renameTemplateIds = templatesToRename.Select(t => t.TemplateId).ToList();
var renameTasks = await _robotConfigurationRepository.Get()
@@ -299,10 +302,16 @@ namespace PARR.Core.Services.RobotTask.Implementations
{
_logger.LogDebug("Найдено старых заданий на переименование с ошибками: {ErrorCount}. Ставим ошибку целевым (новым) заданиям.", errorTasks.Count);
- var errorTemplateNames = errorTasks.Select(t => t.Template!.Name).ToHashSet();
+ //var errorTemplateNames = errorTasks.Select(t => t.Template!.Name).ToHashSet();
+ // Берем OldName из словаря
+ var errorOldNames = errorTasks
+ .Where(t => templateIdToOldName.ContainsKey(t.TemplateId))
+ .Select(t => templateIdToOldName[t.TemplateId])
+ .ToHashSet();
+
// Берем целевые таски, находим в них задания которым надо поставить ошибку
tasksToSetErrorStatus = tasks
- .Where(t => errorTemplateNames.Contains(t.TemplateName))
+ .Where(t => errorOldNames.Contains(t.TemplateName))
.Select(t => t.TaskId)
.ToList();
@@ -319,30 +328,36 @@ namespace PARR.Core.Services.RobotTask.Implementations
var endDate = DateTimeOffset.UtcNow.Add(-_settingsFromDb.RobotWaitTime);
// Фильтруем старые задачи, которые МОЖНО взять в работу. Смотрим статусы роботов, можно взять в работу, только если (RobotStatus == Wait) или (InpRogress но которые еще не просрочены)
- var allowedRenameTasks = renameTasks.Where(t =>
+ var allowedTasks = renameTasks.Where(t =>
t.RobotStatusCode == (int)RobotStatusEnum.Wait
|| (t.RobotStatusCode == (int)RobotStatusEnum.InProgress
&& t.AttemptsNumber < _settingsFromDb.RobotAttemptsNumber
&& t.LastRobotStatusUpdated < endDate)
).ToList();
- // Словарь для поиска подменной задачи по имени шаблона.
- // GroupBy + First на случай, если в бд есть дубликаты, но такого быть не может
- var renameTasksToDictionary = allowedRenameTasks
- .GroupBy(t => t.Template!.Name)
- .ToDictionary(
- t => t.Key,
- t => new RobotTaskDetails(t.First().Id, t.First().Template!.Name, t.First().Template!.NextRun)
- );
+ // Формируем список заданий
+ var originalCount = tasks.Count; // Запоминаем сколько было изначально
var errorTaskIdsSet = tasksToSetErrorStatus.ToHashSet();
- // Создаем итоговый список
+ // Из исходных тасков удалить те которым установлен статус ошибки
+ var filteredOriginalTasks = tasks.Where(t => !errorTaskIdsSet.Contains(t.TaskId)).ToList();
+ var errorCount = errorTaskIdsSet.Count;
+
+ // Создаем словарь, где КЛЮЧОМ является OldName (который совпадает с task.TemplateName),
+ // а не текущее имя шаблона (которое уже новое).
+ var renameTasksToDictionary = allowedTasks
+ .Where(t => templateIdToOldName.ContainsKey(t.TemplateId))
+ .GroupBy(t => templateIdToOldName[t.TemplateId]) // Группируем по OldName!
+ .ToDictionary(
+ g => g.Key, // Ключ = OldName (например, "СтароеИмя")
+ g => new RobotTaskDetails(g.First().Id, g.First().Template!.Name, g.First().Template!.NextRun)
+ );
+
+ // Проходим по ИСХОДНОМУ списку, чтобы сохранить порядок сортировки и корректно подменить задачи
var finalTasks = new List(tasks.Count);
int replacedCount = 0;
- int errorCount = errorTaskIdsSet.Count;
- // Проходим по ИСХОДНОМУ списку, чтобы сохранить его порядок сортировки
foreach (var task in tasks)
{
// Если задаче нужно поставить ошибку, просто пропускаем ее (она не попадет в итоговый список)
@@ -351,7 +366,7 @@ namespace PARR.Core.Services.RobotTask.Implementations
continue;
}
- // Если для этого имени шаблона есть разрешенная задача на переименование - вставляем ее на место текущей
+ // ключ в словаре - это OldName, который равен task.TemplateName
if (renameTasksToDictionary.TryGetValue(task.TemplateName, out var renameTask))
{
finalTasks.Add(renameTask);
@@ -364,13 +379,13 @@ namespace PARR.Core.Services.RobotTask.Implementations
}
}
+ // Логируем итоговую статистику трансформации пула задач
_logger.LogInformation(
- "Трансформация пула задач (Rename). Исходных: {OriginalCount}. Отклонено (Error): {ErrorCount}. " +
- "Заменено на старые: {ReplacedCount}. Итого к выдаче: {FinalCount}",
- tasks.Count, errorCount, replacedCount, finalTasks.Count
- );
+ "Трансформация пула задач завершена. Исходных: {OriginalCount} шт. " +
+ "Отклонено (ошибка): {ErrorCount} шт. Заменено на старые: {ReplacedCount} шт. " +
+ "Итого к выдаче: {FinalCount} шт.",
+ originalCount, errorCount, replacedCount, finalTasks.Count);
- // Возвращаем без дополнительной сортировки по NextRun. Порядок сохранен начального списка
return finalTasks;
}
diff --git a/PARR.Core/Services/RobotTaskDetailsServices/Implementations/RobotTaskDetailsService.cs b/PARR.Core/Services/RobotTaskDetailsServices/Implementations/RobotTaskDetailsService.cs
new file mode 100644
index 00000000..62f37e18
--- /dev/null
+++ b/PARR.Core/Services/RobotTaskDetailsServices/Implementations/RobotTaskDetailsService.cs
@@ -0,0 +1,68 @@
+using AutoMapper;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.Extensions.Logging;
+using PARR.Core.Repositories.Interfaces;
+using PARR.Core.Services.RobotTaskDetailsServices.Interfaces;
+using PARR.Domain.DTOs.RobotTaskDetails;
+using PARR.Domain.DTOs.Shared;
+using PARR.Domain.Enums;
+
+namespace PARR.Core.Services.RobotTaskDetailsServices.Implementations
+{
+ internal class RobotTaskDetailsService : IRobotTaskDetailsService
+ {
+ private readonly ILogger _logger;
+ private readonly IRobotConfigurationRepository _robotConfigurationRepository;
+ private readonly IMapper _mapper;
+ private readonly IRobotRepository _robotRepository;
+ private readonly ITaskStatusRepository _taskStatusRepository;
+
+ public RobotTaskDetailsService(
+ ILogger logger,
+ IRobotConfigurationRepository robotConfigurationRepository,
+ IMapper mapper,
+ IRobotRepository robotRepository,
+ ITaskStatusRepository taskStatusRepository
+ )
+ {
+ _logger = logger;
+ _robotConfigurationRepository = robotConfigurationRepository;
+ _mapper = mapper;
+ _robotRepository = robotRepository;
+ _taskStatusRepository = taskStatusRepository;
+ }
+
+ public async Task GetDetailsAsync(RobotsEnum robot, TaskStatusEnum task)
+ {
+ 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();
+
+ var robotObj = await _robotRepository.Get()
+ .AsNoTracking()
+ .FirstOrDefaultAsync(t => t.Code == (int)robot);
+
+ var taskObj = await _taskStatusRepository.Get()
+ .AsNoTracking()
+ .FirstOrDefaultAsync(t => t.Code == (int)task);
+
+ var result = new RobotTaskDetailsResult
+ {
+ Robot = _mapper.Map(robotObj),
+ Task = _mapper.Map(taskObj),
+ Details = details.Select(t => new RobotTaskGroupDetailsResult
+ {
+ JobGroup = _mapper.Map(t.JobGroup),
+ TemplatesCount = t.TemplatesCount
+ }).OrderBy(t => t.JobGroup.GroupName).ToList()
+ };
+
+ return result;
+ }
+ }
+}
diff --git a/PARR.Core/Services/RobotTaskDetailsServices/Interfaces/IRobotTaskDetailsService.cs b/PARR.Core/Services/RobotTaskDetailsServices/Interfaces/IRobotTaskDetailsService.cs
new file mode 100644
index 00000000..814c7ba1
--- /dev/null
+++ b/PARR.Core/Services/RobotTaskDetailsServices/Interfaces/IRobotTaskDetailsService.cs
@@ -0,0 +1,16 @@
+using PARR.Domain.DTOs.RobotTaskDetails;
+using PARR.Domain.Enums;
+
+namespace PARR.Core.Services.RobotTaskDetailsServices.Interfaces
+{
+ public interface IRobotTaskDetailsService
+ {
+ ///
+ /// Список групп работ по заданиям робота
+ ///
+ ///
+ ///
+ ///
+ Task GetDetailsAsync(RobotsEnum robot, TaskStatusEnum task);
+ }
+}
diff --git a/PARR.Domain/DTOs/RobotTaskDetails/RobotTaskDetailsResult.cs b/PARR.Domain/DTOs/RobotTaskDetails/RobotTaskDetailsResult.cs
new file mode 100644
index 00000000..1765cab9
--- /dev/null
+++ b/PARR.Domain/DTOs/RobotTaskDetails/RobotTaskDetailsResult.cs
@@ -0,0 +1,18 @@
+using PARR.Domain.DTOs.Shared;
+
+namespace PARR.Domain.DTOs.RobotTaskDetails
+{
+ public record RobotTaskDetailsResult
+ {
+ public RobotResult Robot { get; init; }
+ public RobotTaskStatusResult Task { get; init; }
+
+ public List Details { get; init; }
+ }
+
+ public record RobotTaskGroupDetailsResult
+ {
+ public JobGroupShortResult JobGroup { get; init; }
+ public int TemplatesCount { get; init; }
+ }
+}
diff --git a/PARR.Domain/DTOs/Shared/JobGroupResult.cs b/PARR.Domain/DTOs/Shared/JobGroupResult.cs
new file mode 100644
index 00000000..1a12c0ee
--- /dev/null
+++ b/PARR.Domain/DTOs/Shared/JobGroupResult.cs
@@ -0,0 +1,15 @@
+namespace PARR.Domain.DTOs.Shared
+{
+ public record JobGroupShortResult
+ {
+ public Guid Id { get; set; }
+ public required string GroupName { get; set; }
+ }
+
+
+
+ public record JobGroupResult : JobGroupShortResult
+ {
+
+ }
+}