From 911e6febcd91be3dd96243a166784fb9711c0999 Mon Sep 17 00:00:00 2001 From: Mikhail Trubnikov Date: Fri, 15 May 2026 16:09:49 +1000 Subject: [PATCH] =?UTF-8?q?fix(api):=20Task=20-=20=D0=BF=D0=BE=D0=BB=D1=83?= =?UTF-8?q?=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D1=82=D0=B0=D1=82=D1=83?= =?UTF-8?q?=D1=81=D0=BE=D0=B2=20=D0=B7=D0=B0=D0=B4=D0=B0=D1=87,=20=D0=B4?= =?UTF-8?q?=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B0=20=D0=BE=D1=82?= =?UTF-8?q?=D1=87=D0=B5=D1=82=D0=BE=D0=B2=20workload?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PARR.API/Contracts/V1/ApiRoutes.cs | 9 + .../V1/Responses/ActiveTaskResponse.cs | 9 + .../V1/Responses/TaskItemResponse.cs | 21 ++ .../V1/Responses/TaskItemStatusResponse.cs | 13 ++ .../V1/Responses/TaskItemTypeResponse.cs | 33 +++ .../V1/GeneratorTemplateController.cs | 95 -------- .../V1/JobAutoControlController.cs | 212 ------------------ .../V1/ShortcodeApplyController.cs | 5 +- .../V1/Statistics/StatWorkloadController.cs | 1 - PARR.API/Controllers/V1/SyncTaskController.cs | 5 +- PARR.API/Controllers/V1/TaskController.cs | 51 +++++ .../Controllers/V1/TaskStatusController.cs | 5 +- .../DomainToResponseProfile.cs | 20 ++ .../Mapping/TaskMappingProfile.cs | 36 +++ PARR.Core/PARR.Core.csproj | 4 - .../Implementations/TaskManagementService.cs | 35 ++- .../Interfaces/ITaskManagementService.cs | 11 +- PARR.DAL/Context/DataContext.cs | 2 +- PARR.Domain/DTOs/TaskDTO/ActiveTask.cs | 12 + PARR.Domain/DTOs/TaskDTO/TaskItem.cs | 26 +++ PARR.Domain/DTOs/TaskDTO/TaskStatus.cs | 16 ++ PARR.Domain/DTOs/TaskDTO/TaskType.cs | 36 +++ 22 files changed, 334 insertions(+), 323 deletions(-) create mode 100644 PARR.API/Contracts/V1/Responses/ActiveTaskResponse.cs create mode 100644 PARR.API/Contracts/V1/Responses/TaskItemResponse.cs create mode 100644 PARR.API/Contracts/V1/Responses/TaskItemStatusResponse.cs create mode 100644 PARR.API/Contracts/V1/Responses/TaskItemTypeResponse.cs delete mode 100644 PARR.API/Controllers/V1/GeneratorTemplateController.cs delete mode 100644 PARR.API/Controllers/V1/JobAutoControlController.cs create mode 100644 PARR.API/Controllers/V1/TaskController.cs create mode 100644 PARR.Core/Infrastructure/Mapping/TaskMappingProfile.cs create mode 100644 PARR.Domain/DTOs/TaskDTO/ActiveTask.cs create mode 100644 PARR.Domain/DTOs/TaskDTO/TaskItem.cs create mode 100644 PARR.Domain/DTOs/TaskDTO/TaskStatus.cs create mode 100644 PARR.Domain/DTOs/TaskDTO/TaskType.cs diff --git a/PARR.API/Contracts/V1/ApiRoutes.cs b/PARR.API/Contracts/V1/ApiRoutes.cs index 3108d749..198ab02a 100644 --- a/PARR.API/Contracts/V1/ApiRoutes.cs +++ b/PARR.API/Contracts/V1/ApiRoutes.cs @@ -564,6 +564,15 @@ #endregion + #region Tasks + + public static class Tasks + { + public const string GetActiveTasks = Base + "/tasks/active"; + } + + #endregion + } } diff --git a/PARR.API/Contracts/V1/Responses/ActiveTaskResponse.cs b/PARR.API/Contracts/V1/Responses/ActiveTaskResponse.cs new file mode 100644 index 00000000..cc7b8310 --- /dev/null +++ b/PARR.API/Contracts/V1/Responses/ActiveTaskResponse.cs @@ -0,0 +1,9 @@ +namespace PARR.API.Contracts.V1.Responses +{ + public record ActiveTaskResponse : TaskItemBaseResponse + { + public required TaskItemTypeResponse Type { get; init; } + + public required TaskItemStatusResponse Status { get; init; } + } +} diff --git a/PARR.API/Contracts/V1/Responses/TaskItemResponse.cs b/PARR.API/Contracts/V1/Responses/TaskItemResponse.cs new file mode 100644 index 00000000..caf99b0d --- /dev/null +++ b/PARR.API/Contracts/V1/Responses/TaskItemResponse.cs @@ -0,0 +1,21 @@ +namespace PARR.API.Contracts.V1.Responses +{ + public record TaskItemBaseResponse + { + public Guid Id { get; init; } + + public DateTimeOffset DateCreated { get; init; } + + public DateTimeOffset? DateModified { get; init; } + + public int RetryCount { get; init; } + + public DateTimeOffset? ProcessedAt { get; init; } + } + + public record TaskItemResponse : TaskItemBaseResponse + { + + } + +} diff --git a/PARR.API/Contracts/V1/Responses/TaskItemStatusResponse.cs b/PARR.API/Contracts/V1/Responses/TaskItemStatusResponse.cs new file mode 100644 index 00000000..69d949cb --- /dev/null +++ b/PARR.API/Contracts/V1/Responses/TaskItemStatusResponse.cs @@ -0,0 +1,13 @@ +using PARR.Domain.Enums; + +namespace PARR.API.Contracts.V1.Responses +{ + public record TaskItemStatusResponse + { + public TaskItemStatusEnum Code { get; init; } + + public required string Name { get; init; } + + public required string Description { get; init; } + } +} diff --git a/PARR.API/Contracts/V1/Responses/TaskItemTypeResponse.cs b/PARR.API/Contracts/V1/Responses/TaskItemTypeResponse.cs new file mode 100644 index 00000000..6539114f --- /dev/null +++ b/PARR.API/Contracts/V1/Responses/TaskItemTypeResponse.cs @@ -0,0 +1,33 @@ +using PARR.Domain.Enums; + +namespace PARR.API.Contracts.V1.Responses +{ + public record TaskItemTypeResponse + { + public TaskTypeEnum Code { get; init; } + + public required string Name { get; init; } + + public required string Description { get; init; } + + /// + /// Максимальное кол-во попыток + /// + public int MaxRetries { get; init; } + + /// + /// Максимальное время выполнения + /// + public int MaxExecutionTimeMinutes { get; init; } + + /// + /// Одновременно может быть только одна задача или несколько + /// + public bool IsSingleton { get; init; } + + /// + /// Сколько дней хранить в БД + /// + public int RetentionDays { get; init; } + } +} diff --git a/PARR.API/Controllers/V1/GeneratorTemplateController.cs b/PARR.API/Controllers/V1/GeneratorTemplateController.cs deleted file mode 100644 index 9a832002..00000000 --- a/PARR.API/Controllers/V1/GeneratorTemplateController.cs +++ /dev/null @@ -1,95 +0,0 @@ -namespace PARR.API.Controllers.V1 -{ - //[Authorize(Roles = ParrRoles.Administrator.Role)] - //public class GeneratorTemplateController : BaseApiController - //{ - // private readonly IValidator validator; - // private readonly IRabbitService mqService; - // private readonly IUriService uriService; - // private readonly MqSettings mqSettings; - // private readonly IClientService clientService; - - // public GeneratorTemplateController( - // IValidator validator, - // IRabbitService mqService, - // IUriService uriService, - // MqSettings mqSettings, - // IClientService clientService - // ) - // { - // this.validator = validator; - // this.mqService = mqService; - // this.uriService = uriService; - // this.mqSettings = mqSettings; - // this.clientService = clientService; - // } - - // /// !!!!!!!!!! Это старый контроллер, удалить !!!!!!!!!! - - - // /// - // /// Запрос на генерацию шаблонов - // /// - // /// - // [HttpPost(ApiRoutes.GeneratorTemplate.Create)] - // public async Task Create([FromBody] GeneratorTemplateRequest request) - // { - // var resultValidate = await validator.ValidateAsync(request); - // if (!resultValidate.IsValid) - // return BadRequest(new Response(resultValidate.Errors)); - - // var result = true; - - // request.Ek.ForEach(ek => - // { - // //todo: проверить в ForEach(async status - отрабатывает ли нормально async, возможно надо переделать в обычный foreach - // request.Status.ForEach(async status => - // { - // var obj = new GeneratorTemplateMq - // { - // Action = request.Action, - // ApplicationId = request.ApplicationId, - // Ek = ek, - // WorkId = request.WorkId, - // StatusEk = status, - // IsActiveTemplate = request.IsActiveTemplate, - // IsActiveSchedule = request.IsActiveSchedule, - // HistoryInitiator = new HistoryInitiator { InitiatorIp = clientService.GetClientIp()?.ToString(), InitiatorParrComponentId = ParrComponentsEnum.Api } - // }; - - // //var msg = JsonSerializer.Serialize(obj); - - // //var _result = await mqService.SendAsync(mqSettings.GenerateTemplates, new[] { msg }); - // var _result = await mqService.SendAsync(mqSettings.GenerateTemplates, new List { obj }); - - // if (_result.IsSuccess == false) - // result = false; - - // }); - // }); - - // //var obj = new GeneratorTemplateMq - // //{ - // // Action = request.Action, - // // ApplicationId = request.ApplicationId, - // // Ek = request.Ek, - // // WorkId = request.WorkId, - // // StatusEk = request.Status, - // // IsActiveTemplate = request.IsActiveTemplate, - // // IsActiveSchedule = request.IsActiveSchedule - // //}; - - // //var msg = JsonSerializer.Serialize(obj); - - // //var result = mqService.Send(mqSettings.GenerateTemplates, new[] { msg }); - - // //if (!result.IsSuccess) - // if (!result) - // return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при отправке данных." } })); - - // var createdUri = uriService.GetAllUri(ApiRoutes.Template.GetAll); - - // return Created(createdUri, new Response(null, true, new List(), "Отправлен запрос на генерацию шаблонов.")); - // } - //} -} diff --git a/PARR.API/Controllers/V1/JobAutoControlController.cs b/PARR.API/Controllers/V1/JobAutoControlController.cs deleted file mode 100644 index 813919ea..00000000 --- a/PARR.API/Controllers/V1/JobAutoControlController.cs +++ /dev/null @@ -1,212 +0,0 @@ -using Microsoft.AspNetCore.Authorization; -using PARR.API.Controllers.V1.Base; -using PARR.Domain.Common.Roles; - -namespace PARR.API.Controllers.V1 -{ - /// - /// Управление автоматическим созданием/деактивацией/активацией РР - /// - [Authorize(Roles = ParrRoles.Administrator.Role)] - public class JobAutoControlController : BaseApiController - { - //private readonly IJobAutoControlService jobAutoControlService; - //private readonly IApplicationsInWorkService applicationsInWorkService; - //private readonly IMapper mapper; - //private readonly IValidator validator; - //private readonly IUriService uriService; - - //public JobAutoControlController( - // IJobAutoControlService jobAutoControlService, - // IApplicationsInWorkService applicationsInWorkService, - // IMapper mapper, - // IValidator validator, - // IUriService uriService - // ) - //{ - // this.jobAutoControlService = jobAutoControlService; - // this.applicationsInWorkService = applicationsInWorkService; - // this.mapper = mapper; - // this.validator = validator; - // this.uriService = uriService; - //} - - - ///// - ///// Получить настройки автоматического управления РР - ///// - ///// - ///// - //[HttpGet(ApiRoutes.JobAutoControl.Get)] - //public async Task Get([FromRoute] Guid applicationInWorkId) - //{ - // var obj = await jobAutoControlService.Get() - // .Include(t => t.ApplicationsInWork).ThenInclude(t => t.WorkGroups).ThenInclude(t => t.WorkGroup) - // .Include(t => t.JobEkMasks) - // .Include(t => t.JobAutoControlInEkStatuses).ThenInclude(t => t.EkStatus) - // .FirstOrDefaultAsync(t => t.ApplicationInWorkId == applicationInWorkId); - - // if (obj == null) - // { - // // проверим, есть ли вообще такой AppInWork - // var appInWork = await applicationsInWorkService.GetAsync(applicationInWorkId); - // if (appInWork == null) - // return NotFound(); - - // return NoContent(); - // } - - // var response = mapper.Map(obj); - - // return Ok(new Response(response, true)); - //} - - - ///// - ///// Создать настройки автоматического управления РР - ///// - ///// - ///// - ///// - //[HttpPost(ApiRoutes.JobAutoControl.Create)] - //public async Task Create([FromRoute] Guid applicationInWorkId, [FromBody] JobAutoControlRequest request) - //{ - // var exist = await jobAutoControlService.Get().FirstOrDefaultAsync(t => t.ApplicationInWorkId == applicationInWorkId); - // if (exist != null) - // return BadRequest(new Response(false, new List { - // new ErrorModel { FieldName = nameof(applicationInWorkId), Message = $"Запись с {nameof(applicationInWorkId)}: {applicationInWorkId} уже существует." } - // })); - - // var resultValidate = await validator.ValidateAsync(request); - // if (!resultValidate.IsValid) - // return BadRequest(new Response(resultValidate.Errors)); - - // var newObj = new DAL.Models.JobAutoControl - // { - // Id = Guid.NewGuid(), - // ApplicationInWorkId = applicationInWorkId, - // CreateNew = request.CreateNew, - // Deactivate = request.Deactivate, - // Activate = request.Activate, - // OnTemplateIsActive = request.OnTemplateIsActive, - // OnScheduleIsActive = request.OnScheduleIsActive, - // OffTemplateIsActive = request.OffTemplateIsActive, - // OffScheduleIsActive = request.OffScheduleIsActive - // }; - - // // убираем пробелы и дубли - // request.EkMasks.Where(t => t.Trim() != string.Empty).Distinct().ToList().ForEach(mask => newObj.JobEkMasks.Add(new JobEkMask - // { - // Id = Guid.NewGuid(), - // DateCreated = DateTimeOffset.UtcNow, - // Name = mask.Trim(), - // JobAutoControlId = newObj.Id - // })); - - // request.EnabledEkStatuses.ForEach(status => newObj.JobAutoControlInEkStatuses.Add(new JobAutoControlInEkStatus - // { - // EkStatusCode = (int)status, - // JobAutoControlId = newObj.Id - // })); - - // if (!await jobAutoControlService.CreateAsync(newObj) || !await jobAutoControlService.CommitAsync()) - // return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при создани записи." } })); - - // var locationUri = uriService.GetUri(ApiRoutes.JobAutoControl.Get, ApiRoutes.JobAutoControl.appInWorkId, applicationInWorkId); - - // var createdObj = await jobAutoControlService.Get() - // .Include(t => t.ApplicationsInWork).ThenInclude(t => t.WorkGroups).ThenInclude(t => t.WorkGroup) - // .Include(t => t.JobEkMasks) - // .Include(t => t.JobAutoControlInEkStatuses).ThenInclude(t => t.EkStatus) - // .FirstOrDefaultAsync(t => t.ApplicationInWorkId == applicationInWorkId); - - // return Created(locationUri, new Response(mapper.Map(createdObj), true)); - //} - - - ///// - ///// Обновить настройки автоматического управления РР - ///// - ///// - ///// - ///// - //[HttpPut(ApiRoutes.JobAutoControl.Update)] - //public async Task Update([FromRoute] Guid applicationInWorkId, [FromBody] JobAutoControlRequest request) - //{ - // var resultValidate = await validator.ValidateAsync(request); - // if (!resultValidate.IsValid) - // return BadRequest(new Response(resultValidate.Errors)); - - // var orig = await jobAutoControlService.Get() - // .Include(t => t.JobEkMasks) - // .Include(t => t.JobAutoControlInEkStatuses) - // .FirstOrDefaultAsync(t => t.ApplicationInWorkId == applicationInWorkId); - - // if (orig == null) - // return BadRequest(new Response(false, new List { - // new ErrorModel { FieldName = nameof(applicationInWorkId), Message = $"Запись с {nameof(applicationInWorkId)}: {applicationInWorkId} не существует." } - // })); - - - // orig.DateModified = DateTimeOffset.UtcNow; - // orig.CreateNew = request.CreateNew; - // orig.Deactivate = request.Deactivate; - // orig.Activate = request.Activate; - // orig.OnTemplateIsActive = request.OnTemplateIsActive; - // orig.OnScheduleIsActive = request.OnScheduleIsActive; - // orig.OffTemplateIsActive = request.OffTemplateIsActive; - // orig.OffScheduleIsActive = request.OffScheduleIsActive; - - // orig.JobEkMasks.Clear(); - // // убираем пробелы и дубли - // request.EkMasks.Where(t => t.Trim() != string.Empty).Distinct().ToList().ForEach(mask => orig.JobEkMasks.Add(new JobEkMask - // { - // //Id = Guid.NewGuid(), // не пойму почему не нужно это поле. с ним ошибка. - // DateCreated = DateTimeOffset.UtcNow, - // Name = mask.Trim(), - // JobAutoControlId = orig.Id - // })); - - // orig.JobAutoControlInEkStatuses.Clear(); - // request.EnabledEkStatuses.ForEach(status => orig.JobAutoControlInEkStatuses.Add(new JobAutoControlInEkStatus - // { - // EkStatusCode = (int)status, - // JobAutoControlId = orig.Id - // })); - - // if (!await jobAutoControlService.CommitAsync()) - // return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при изменении записи." } })); - - - // var updatedObj = await jobAutoControlService.Get() - // .Include(t => t.ApplicationsInWork).ThenInclude(t => t.WorkGroups).ThenInclude(t => t.WorkGroup) - // .Include(t => t.JobEkMasks) - // .Include(t => t.JobAutoControlInEkStatuses).ThenInclude(t => t.EkStatus) - // .FirstOrDefaultAsync(t => t.ApplicationInWorkId == applicationInWorkId); - - // return Ok(new Response(mapper.Map(updatedObj), true)); - //} - - - ///// - ///// Удалить настройки автоматического управления РР - ///// - ///// - ///// - //[HttpDelete(ApiRoutes.JobAutoControl.Delete)] - //public async Task Delete([FromRoute] Guid applicationInWorkId) - //{ - // var obj = await jobAutoControlService.Get().FirstOrDefaultAsync(t => t.ApplicationInWorkId == applicationInWorkId); - - // if (obj == null) - // return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при удалении настроект. Не найден настройки с {nameof(applicationInWorkId)}: {applicationInWorkId}" } })); - - // if (!jobAutoControlService.Delete(obj) || !await jobAutoControlService.CommitAsync()) - // return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при удалении настроек." } })); - - // return NoContent(); - //} - - - } -} diff --git a/PARR.API/Controllers/V1/ShortcodeApplyController.cs b/PARR.API/Controllers/V1/ShortcodeApplyController.cs index d80060d7..205eda63 100644 --- a/PARR.API/Controllers/V1/ShortcodeApplyController.cs +++ b/PARR.API/Controllers/V1/ShortcodeApplyController.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; @@ -7,9 +8,11 @@ using PARR.API.Contracts.V1.Responses.Base; using PARR.API.Controllers.V1.Base; using PARR.Core.Repositories.Interfaces; using PARR.Core.Services.Shortcodes; +using PARR.Domain.Common.Roles; namespace PARR.API.Controllers.V1 { + [Authorize(Roles = ParrRoles.Administrator.Role)] public class ShortcodeApplyController : BaseApiController { private readonly ILogger logger; diff --git a/PARR.API/Controllers/V1/Statistics/StatWorkloadController.cs b/PARR.API/Controllers/V1/Statistics/StatWorkloadController.cs index 0da2dea9..0bb953e4 100644 --- a/PARR.API/Controllers/V1/Statistics/StatWorkloadController.cs +++ b/PARR.API/Controllers/V1/Statistics/StatWorkloadController.cs @@ -1,7 +1,6 @@ using AutoMapper; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using NodaTime; using PARR.API.Contracts.V1; using PARR.API.Contracts.V1.Requests.BaseRequests; using PARR.API.Contracts.V1.Requests.Queries; diff --git a/PARR.API/Controllers/V1/SyncTaskController.cs b/PARR.API/Controllers/V1/SyncTaskController.cs index 9b7425b9..2ff7528a 100644 --- a/PARR.API/Controllers/V1/SyncTaskController.cs +++ b/PARR.API/Controllers/V1/SyncTaskController.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; using PARR.API.Contracts.V1; using PARR.API.Contracts.V1.Requests; using PARR.API.Contracts.V1.Responses.Base; @@ -8,11 +9,13 @@ using PARR.API.Settings; using PARR.Core.Common.Interfaces.RabbitServices; using PARR.Core.Services.MatchingStatusService; using PARR.Domain.Common.Rabbit.Messages; +using PARR.Domain.Common.Roles; using PARR.Domain.Entities.Base.History; using PARR.Domain.Enums; namespace PARR.API.Controllers.V1 { + [Authorize(Roles = ParrRoles.Administrator.Role)] public class SyncTaskController : BaseApiController { private readonly ILogger logger; diff --git a/PARR.API/Controllers/V1/TaskController.cs b/PARR.API/Controllers/V1/TaskController.cs new file mode 100644 index 00000000..70a60e4f --- /dev/null +++ b/PARR.API/Controllers/V1/TaskController.cs @@ -0,0 +1,51 @@ +using AutoMapper; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using PARR.API.Contracts.V1; +using PARR.API.Contracts.V1.Responses; +using PARR.API.Contracts.V1.Responses.Base; +using PARR.API.Controllers.V1.Base; +using PARR.Core.Services.TaskServices.Interfaces; +using PARR.Domain.Common.Roles; +using PARR.Domain.Enums; + +namespace PARR.API.Controllers.V1 +{ + /// + /// Контроллер по управлению Task (задания, очереди) + /// + [Authorize(Roles = ParrRoles.Administrator.Role)] + public class TaskController : BaseApiController + { + private readonly ITaskManagementService taskManagementService; + private readonly IMapper mapper; + + public TaskController( + ITaskManagementService taskManagementService, + IMapper mapper + ) + { + this.taskManagementService = taskManagementService; + this.mapper = mapper; + } + + + /// + /// Получить список активных задач (задания, очереди) + /// + /// + [HttpGet(ApiRoutes.Tasks.GetActiveTasks)] + public async Task GetActiveTasks([FromQuery] TaskTypeEnum? typeCode) + { + var activeTasks = await taskManagementService.GetActiveTasksAsync(typeCode); + + if (activeTasks.Count == 0) + return NoContent(); + + var response = mapper.Map>(activeTasks); + + return Ok(new Response>(response, true)); + } + + } +} diff --git a/PARR.API/Controllers/V1/TaskStatusController.cs b/PARR.API/Controllers/V1/TaskStatusController.cs index d7cb6293..e31764fb 100644 --- a/PARR.API/Controllers/V1/TaskStatusController.cs +++ b/PARR.API/Controllers/V1/TaskStatusController.cs @@ -11,6 +11,9 @@ using PARR.Domain.Common.Roles; namespace PARR.API.Controllers.V1 { + /// + /// Статусы заданий роботам + /// [Authorize(Roles = ParrRoles.EsppRobot.RoleOrAdmin)] public class TaskStatusController : BaseApiController { @@ -27,7 +30,7 @@ namespace PARR.API.Controllers.V1 } /// - /// Получить список статусов заданий + /// Получить список статусов заданий роботам /// /// [HttpGet(ApiRoutes.TaskStatus.GetAll)] diff --git a/PARR.API/MappingProfiles/DomainToResponseProfile.cs b/PARR.API/MappingProfiles/DomainToResponseProfile.cs index e6847037..f156da2d 100644 --- a/PARR.API/MappingProfiles/DomainToResponseProfile.cs +++ b/PARR.API/MappingProfiles/DomainToResponseProfile.cs @@ -6,6 +6,7 @@ using PARR.API.MappingProfiles.Resolvers; using PARR.Core.Repositories.Interfaces.Schedule; using PARR.Domain.DTOs.Matching; using PARR.Domain.DTOs.Shortcode; +using PARR.Domain.DTOs.TaskDTO; using PARR.Domain.DTOs.Workload; using PARR.Domain.Entities; using PARR.Domain.Entities.Base.History; @@ -445,6 +446,25 @@ namespace PARR.API.MappingProfiles #endregion + + #region Task + + //CreateMap() + // .Include() + // .Include(); + + //CreateMap(); + + CreateMap(); + + // --- + + CreateMap(); + + CreateMap(); + + #endregion + } } diff --git a/PARR.Core/Infrastructure/Mapping/TaskMappingProfile.cs b/PARR.Core/Infrastructure/Mapping/TaskMappingProfile.cs new file mode 100644 index 00000000..53b96ab0 --- /dev/null +++ b/PARR.Core/Infrastructure/Mapping/TaskMappingProfile.cs @@ -0,0 +1,36 @@ +using AutoMapper; +using PARR.Domain.DTOs.TaskDTO; + +namespace PARR.Core.Infrastructure.Mapping +{ + /// + /// Маппинг все что касается Task + /// + internal class TaskMappingProfile : Profile + { + public TaskMappingProfile() + { + #region TaskItem + CreateMap() + .Include() + .Include(); + + CreateMap(); + #endregion + + #region ActiveTask + CreateMap() + .ForMember(d => d.Type, o => o.MapFrom(s => s.TaskType)) + .ForMember(d => d.Status, o => o.MapFrom(s => s.TaskStatus)); + #endregion + + #region TaskType + CreateMap(); + #endregion + + #region TaskStatus + CreateMap(); + #endregion + } + } +} diff --git a/PARR.Core/PARR.Core.csproj b/PARR.Core/PARR.Core.csproj index ede68966..6b9219aa 100644 --- a/PARR.Core/PARR.Core.csproj +++ b/PARR.Core/PARR.Core.csproj @@ -6,10 +6,6 @@ enable - - - - diff --git a/PARR.Core/Services/TaskServices/Implementations/TaskManagementService.cs b/PARR.Core/Services/TaskServices/Implementations/TaskManagementService.cs index 51c0496c..5db6eb93 100644 --- a/PARR.Core/Services/TaskServices/Implementations/TaskManagementService.cs +++ b/PARR.Core/Services/TaskServices/Implementations/TaskManagementService.cs @@ -1,11 +1,12 @@ -using Microsoft.EntityFrameworkCore; +using AutoMapper; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using PARR.Core.Common.Interfaces.RabbitServices; using PARR.Core.Repositories.Interfaces.TaskRepositories; using PARR.Core.Services.TaskServices.Interfaces; using PARR.Domain.Common.Rabbit.Messages; +using PARR.Domain.DTOs.TaskDTO; using PARR.Domain.Entities.Base.History; -using PARR.Domain.Entities.TaskEntities; using PARR.Domain.Enums; using PARR.Domain.Settings; using System.Text.Encodings.Web; @@ -19,24 +20,29 @@ namespace PARR.Core.Services.TaskServices.Implementations private readonly ITaskTypeRepository taskTypeRepository; private readonly ITaskRepository taskRepository; private readonly IRabbitService rabbitService; + private readonly IMapper mapper; public TaskManagementService( ILogger logger, ITaskTypeRepository taskTypeRepository, ITaskRepository taskRepository, - IRabbitService rabbitService + IRabbitService rabbitService, + IMapper mapper ) { this.logger = logger; this.taskTypeRepository = taskTypeRepository; this.taskRepository = taskRepository; this.rabbitService = rabbitService; + this.mapper = mapper; } public async Task CreateTaskAsync(TaskTypeEnum typeCode, T payload, IHistoryInitiator initiator, IMqSettings mqSettings) { - var taskType = await taskTypeRepository.Get().AsNoTracking().FirstOrDefaultAsync(t => t.Code == typeCode); + var taskType = await taskTypeRepository.Get() + .AsNoTracking() + .FirstOrDefaultAsync(t => t.Code == typeCode); if (taskType == null) { @@ -59,7 +65,7 @@ namespace PARR.Core.Services.TaskServices.Implementations var payloadStr = PayloadToString(payload); // Создаем новую запись задачи - var task = new TaskItem + var task = new Domain.Entities.TaskEntities.TaskItem { Id = Guid.NewGuid(), TypeCode = typeCode, @@ -99,12 +105,29 @@ namespace PARR.Core.Services.TaskServices.Implementations } + public async Task> GetActiveTasksAsync(TaskTypeEnum? typeCode = null) + { + var query = taskRepository.Get() + .AsNoTracking() + .Include(t => t.TaskStatus) + .Include(t => t.TaskType) + .Where(t => t.StatusCode == TaskItemStatusEnum.Pending || t.StatusCode == TaskItemStatusEnum.Processing); + + if (typeCode.HasValue) + query = query.Where(t => t.TypeCode == typeCode.Value); + + var tasks = await query.OrderByDescending(t => t.DateCreated).ToListAsync(); + + return mapper.Map>(tasks); + } + + /// /// Получить активную singleton задачу указанного типа /// /// /// - private async Task GetActiveSingletonTaskAsync(TaskTypeEnum typeCode) + private async Task GetActiveSingletonTaskAsync(TaskTypeEnum typeCode) { return await taskRepository.Get().AsNoTracking() .FirstOrDefaultAsync(t => t.TypeCode == typeCode && ( diff --git a/PARR.Core/Services/TaskServices/Interfaces/ITaskManagementService.cs b/PARR.Core/Services/TaskServices/Interfaces/ITaskManagementService.cs index e558d175..24370534 100644 --- a/PARR.Core/Services/TaskServices/Interfaces/ITaskManagementService.cs +++ b/PARR.Core/Services/TaskServices/Interfaces/ITaskManagementService.cs @@ -1,4 +1,5 @@ -using PARR.Domain.Entities.Base.History; +using PARR.Domain.DTOs.TaskDTO; +using PARR.Domain.Entities.Base.History; using PARR.Domain.Enums; using PARR.Domain.Settings; @@ -19,5 +20,13 @@ namespace PARR.Core.Services.TaskServices.Interfaces /// Настройки очереди /// Task CreateTaskAsync(TaskTypeEnum typeCode, T payload, IHistoryInitiator initiator, IMqSettings mqSettings); + + + /// + /// Получить список активных задач + /// + /// + /// + Task> GetActiveTasksAsync(TaskTypeEnum? typeCode = null); } } diff --git a/PARR.DAL/Context/DataContext.cs b/PARR.DAL/Context/DataContext.cs index ab342b67..36b68b74 100644 --- a/PARR.DAL/Context/DataContext.cs +++ b/PARR.DAL/Context/DataContext.cs @@ -579,7 +579,7 @@ namespace PARR.DAL.Context { f.HasData(new Domain.Entities.TaskEntities.TaskType[] { - new() { Code = TaskTypeEnum.Workload, Name = TaskTypeEnum.Workload.ToString(), Description = "Формирование данных для отчетности - Загруженность", MaxRetries = 2, IsSingleton=true, MaxExecutionTimeMinutes=60, RetentionDays=90 } + new() { Code = TaskTypeEnum.Workload, Name = TaskTypeEnum.Workload.ToString(), Description = "Формирование данных для отчетности - Загруженность", MaxRetries = 2, IsSingleton=true, MaxExecutionTimeMinutes=90, RetentionDays=90 } } ); }); diff --git a/PARR.Domain/DTOs/TaskDTO/ActiveTask.cs b/PARR.Domain/DTOs/TaskDTO/ActiveTask.cs new file mode 100644 index 00000000..057e54a7 --- /dev/null +++ b/PARR.Domain/DTOs/TaskDTO/ActiveTask.cs @@ -0,0 +1,12 @@ +namespace PARR.Domain.DTOs.TaskDTO +{ + /// + /// DTO + /// + public record ActiveTask : TaskItemBase + { + public required TaskType Type { get; init; } + + public required TaskStatus Status { get; init; } + } +} diff --git a/PARR.Domain/DTOs/TaskDTO/TaskItem.cs b/PARR.Domain/DTOs/TaskDTO/TaskItem.cs new file mode 100644 index 00000000..4095ca3c --- /dev/null +++ b/PARR.Domain/DTOs/TaskDTO/TaskItem.cs @@ -0,0 +1,26 @@ +namespace PARR.Domain.DTOs.TaskDTO +{ + /// + /// DTO + /// + public record TaskItemBase + { + public Guid Id { get; init; } + + public DateTimeOffset DateCreated { get; init; } + + public DateTimeOffset? DateModified { get; init; } + + public int RetryCount { get; init; } + + public DateTimeOffset? ProcessedAt { get; init; } + } + + /// + /// DTO + /// + public record TaskItem : TaskItemBase + { + //todo: + } +} diff --git a/PARR.Domain/DTOs/TaskDTO/TaskStatus.cs b/PARR.Domain/DTOs/TaskDTO/TaskStatus.cs new file mode 100644 index 00000000..6313aaee --- /dev/null +++ b/PARR.Domain/DTOs/TaskDTO/TaskStatus.cs @@ -0,0 +1,16 @@ +using PARR.Domain.Enums; + +namespace PARR.Domain.DTOs.TaskDTO +{ + /// + /// DTO + /// + public record TaskStatus + { + public TaskItemStatusEnum Code { get; init; } + + public required string Name { get; init; } + + public required string Description { get; init; } + } +} diff --git a/PARR.Domain/DTOs/TaskDTO/TaskType.cs b/PARR.Domain/DTOs/TaskDTO/TaskType.cs new file mode 100644 index 00000000..696d7a03 --- /dev/null +++ b/PARR.Domain/DTOs/TaskDTO/TaskType.cs @@ -0,0 +1,36 @@ +using PARR.Domain.Enums; + +namespace PARR.Domain.DTOs.TaskDTO +{ + /// + /// DTO + /// + public record TaskType + { + public TaskTypeEnum Code { get; init; } + + public required string Name { get; init; } + + public required string Description { get; init; } + + /// + /// Максимальное кол-во попыток + /// + public int MaxRetries { get; init; } + + /// + /// Максимальное время выполнения + /// + public int MaxExecutionTimeMinutes { get; init; } + + /// + /// Одновременно может быть только одна задача или несколько + /// + public bool IsSingleton { get; init; } + + /// + /// Сколько дней хранить в БД + /// + public int RetentionDays { get; init; } + } +}