From c9dcdedeca6915fd78da9702e01769dd79264d5d Mon Sep 17 00:00:00 2001 From: Mikhail Kuznetsov Date: Thu, 4 Sep 2025 14:51:51 +1000 Subject: [PATCH] =?UTF-8?q?feat(api):=20JobGroupController=20=D0=B4=D0=BE?= =?UTF-8?q?=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=B2=D1=8B=D0=B4?= =?UTF-8?q?=D0=B0=D1=87=D0=B0=20=D1=80=D0=B0=D1=81=D0=BF=D0=B8=D1=81=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D0=BA=D0=BE=D0=BB=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D1=81=D1=82=D0=B2=D0=BE=20=D1=81=D0=B2=D1=8F=D0=B7=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D1=85=20Job?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../V1/Requests/ApplicationInWorkRequest.cs | 12 +- .../Contracts/V1/Requests/JobGroupRequest.cs | 9 ++ .../V1/Requests/Queries/JobGroupQuery.cs | 5 - .../V1/Responses/JobGroupResponse.cs | 24 ++- .../V1/ApplicationInWorkController.cs | 12 -- PARR.API/Controllers/V1/JobGroupController.cs | 143 ++++++++++++++---- .../DomainToResponseProfile.cs | 10 +- .../Resolvers/JobGroupScheduleResolver.cs | 50 ++++++ .../Validators/ApplicationInWorkValidator.cs | 28 ++-- 9 files changed, 214 insertions(+), 79 deletions(-) create mode 100644 PARR.API/MappingProfiles/Resolvers/JobGroupScheduleResolver.cs diff --git a/PARR.API/Contracts/V1/Requests/ApplicationInWorkRequest.cs b/PARR.API/Contracts/V1/Requests/ApplicationInWorkRequest.cs index eb6df60b..004a97d2 100644 --- a/PARR.API/Contracts/V1/Requests/ApplicationInWorkRequest.cs +++ b/PARR.API/Contracts/V1/Requests/ApplicationInWorkRequest.cs @@ -37,7 +37,7 @@ //public required ScheduleRequest Schedule { get; set; } - public List Schedule { get; set; } = new List(); + //public List Schedule { get; set; } = new List(); public List WorkGroups { get; set; } = new List(); } @@ -51,10 +51,10 @@ //} - public class EsppSchValueRequest - { - public Guid TypeValueId { get; set; } + //public class EsppSchValueRequest + //{ + // public Guid TypeValueId { get; set; } - public Guid TypeConfigId { get; set; } - } + // public Guid TypeConfigId { get; set; } + //} } diff --git a/PARR.API/Contracts/V1/Requests/JobGroupRequest.cs b/PARR.API/Contracts/V1/Requests/JobGroupRequest.cs index 87fac01a..bebfdafc 100644 --- a/PARR.API/Contracts/V1/Requests/JobGroupRequest.cs +++ b/PARR.API/Contracts/V1/Requests/JobGroupRequest.cs @@ -25,5 +25,14 @@ public int? AgentTimeOutSec { get; set; } public string? AgentScript { get; set; } + + public List Schedule { get; set; } = new List(); + } + + public class EsppSchValueRequest + { + public Guid TypeValueId { get; set; } + + public Guid TypeConfigId { get; set; } } } diff --git a/PARR.API/Contracts/V1/Requests/Queries/JobGroupQuery.cs b/PARR.API/Contracts/V1/Requests/Queries/JobGroupQuery.cs index 03e3f547..118efc3e 100644 --- a/PARR.API/Contracts/V1/Requests/Queries/JobGroupQuery.cs +++ b/PARR.API/Contracts/V1/Requests/Queries/JobGroupQuery.cs @@ -8,10 +8,5 @@ namespace PARR.API.Contracts.V1.Requests.Queries /// Поиск по имени /// public string? Name { get; set; } - - /// - /// Поиск по короткому описанию - /// - public string? ShortDescription { get; set; } } } diff --git a/PARR.API/Contracts/V1/Responses/JobGroupResponse.cs b/PARR.API/Contracts/V1/Responses/JobGroupResponse.cs index 454994ca..916d0eb8 100644 --- a/PARR.API/Contracts/V1/Responses/JobGroupResponse.cs +++ b/PARR.API/Contracts/V1/Responses/JobGroupResponse.cs @@ -20,19 +20,31 @@ namespace PARR.API.Contracts.V1.Responses public DateTimeOffset ReferenceDate { get; set; } - public bool IsAutoDistributionEnabled { get; set; } + // public bool IsAutoDistributionEnabled { get; set; } - public bool IsAgent { get; set; } + // public bool IsAgent { get; set; } - public string? AgentName { get; set; } + // public string? AgentName { get; set; } - public int? AgentTimeOutSec { get; set; } + // public int? AgentTimeOutSec { get; set; } - public string? AgentScript { get; set; } + // public string? AgentScript { get; set; } } public class JobGroupResponse : JobGroupBaseResponse { - public List? Jobs { get; set; } + //public List? Jobs { get; set; } + public int JobsCount { get; set; } + public JobGroupScheduleResponse? Schedule { get; set; } + + } + + public class JobGroupScheduleResponse + { + public string Timezone { get; set; } = string.Empty; + + public EsppScheduleTypeScheduleResponse? TypeSchedule { get; set; } + + public List? Values { get; set; } } } diff --git a/PARR.API/Controllers/V1/ApplicationInWorkController.cs b/PARR.API/Controllers/V1/ApplicationInWorkController.cs index 99afbee6..0332139c 100644 --- a/PARR.API/Controllers/V1/ApplicationInWorkController.cs +++ b/PARR.API/Controllers/V1/ApplicationInWorkController.cs @@ -1,26 +1,14 @@ using AutoMapper; using FluentValidation; using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using Microsoft.EntityFrameworkCore; -using PARR.API.Contracts.V1; using PARR.API.Contracts.V1.Requests; -using PARR.API.Contracts.V1.Requests.Queries; using PARR.API.Contracts.V1.Responses; -using PARR.API.Contracts.V1.Responses.Base; using PARR.API.Controllers.V1.Base; -using PARR.API.Extensions; using PARR.API.Services.Interfaces; using PARR.API.Settings; -using PARR.BLL.Domain.Mq; using PARR.BLL.Services.Interfaces; using PARR.Constants; -using PARR.DAL.Contracts; -using PARR.DAL.DomainModels; -using PARR.DAL.Models; using PARR.DAL.Services.Interfaces; -using System.Text.Json; -using static PARR.API.Contracts.V1.ApiRoutes; namespace PARR.API.Controllers.V1 { diff --git a/PARR.API/Controllers/V1/JobGroupController.cs b/PARR.API/Controllers/V1/JobGroupController.cs index f834afe3..d0c66f03 100644 --- a/PARR.API/Controllers/V1/JobGroupController.cs +++ b/PARR.API/Controllers/V1/JobGroupController.cs @@ -1,6 +1,5 @@ using AutoMapper; using FluentValidation; -using InfluxDB.Client.Api.Domain; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -14,9 +13,9 @@ using PARR.API.Extensions; using PARR.API.Services.Interfaces; using PARR.Constants; using PARR.DAL.DomainModels; +using PARR.DAL.Models; using PARR.DAL.Models.Job; using PARR.DAL.Services.Interfaces.Job; -using System.ComponentModel.DataAnnotations; namespace PARR.API.Controllers.V1 { @@ -64,26 +63,23 @@ namespace PARR.API.Controllers.V1 if (!string.IsNullOrEmpty(filter.Name)) query = query.Where(t => t.GroupName.ToLower().Contains(filter.Name.ToLower())); - if (!string.IsNullOrEmpty(filter.ShortDescription)) - query = query.Where(t => t.ShortDescription.ToLower().Contains(filter.ShortDescription.ToLower())); - if (filter.IsFull) query = query.Include(t => t.Jobs).ThenInclude(t => t.Tnk); - var jobs = await groupService.GetPage(query, paginationFilter).ToListAsync(); + var jobGroups = await groupService.GetPage(query, paginationFilter).ToListAsync(); - if (!jobs.Any()) + if (!jobGroups.Any()) return NoContent(); if (filter.IsFull == true) { - var responseFull = mapper.Map>(jobs); + var responseFull = mapper.Map>(jobGroups); var paginationResponseFull = new PagedResponse(responseFull, true).GetPaginatedProps(paginationFilter, query); return Ok(paginationResponseFull); } - var response = mapper.Map>(jobs); + var response = mapper.Map>(jobGroups); var paginationResponse = new PagedResponse(response, true).GetPaginatedProps(paginationFilter, query); @@ -101,6 +97,7 @@ namespace PARR.API.Controllers.V1 { var jobGroup = await groupService.Get() .Include(t => t.Jobs).ThenInclude(t => t.Tnk) + .Include(t => t.EsppSchValues) .FirstOrDefaultAsync(t => t.Id == id); if (jobGroup == null) @@ -142,6 +139,16 @@ namespace PARR.API.Controllers.V1 AgentScript = request.AgentScript }; + //Добавляем настройки планировщика + request.Schedule.ForEach(item => + { + jobGroup.EsppSchValues.Add(new EsppSchValue + { + JobGroupId = jobGroup.Id, + TypeConfigId = item.TypeConfigId, + TypeValueId = item.TypeValueId + }); + }); if (!await groupService.CreateAsync(jobGroup) || !await groupService.CommitAsync()) return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при создании группы заданий на выполнение работ" } })); @@ -149,7 +156,7 @@ namespace PARR.API.Controllers.V1 logger.LogInformation($"Пользователь {User.Identity?.Name} добавил группу заданий на выполнение работ: {jobGroup.Id}, {jobGroup.GroupName}, {jobGroup.ShortDescription}"); - var createdJobGroup = await groupService.Get().Include(t=>t.Jobs).ThenInclude(t => t.Tnk) + var createdJobGroup = await groupService.Get().Include(t => t.Jobs).ThenInclude(t => t.Tnk) .FirstAsync(t => t.Id == jobGroup.Id); var locationUri = uriService.GetUri(ApiRoutes.JobGroup.Get, ApiRoutes.JobGroup.getParam, createdJobGroup.Id); @@ -175,13 +182,17 @@ namespace PARR.API.Controllers.V1 return BadRequest(new Response(resultValidate.Errors)); var orig = await groupService.Get() - .Include(t=>t.Jobs) + .Include(t => t.Jobs) .ThenInclude(t => t.Tnk) + .Include(t => t.EsppSchValues) .FirstOrDefaultAsync(t => t.Id == id); if (orig == null) return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при изменении группы заданий на выполнение работ. Не найдена группа с Id: {id}" } })); + // //Расписание было изменено, ниже добавим задание в очередь на обновление расписаний у связанных шаблонов + var isScheduleChanged = IsScheduleChanged(orig, request); + orig.GroupName = request.Name.Trim(); orig.IsUmbrella = request.IsUmbrella; orig.ShortDescription = request.ShortDescription.Trim(); @@ -194,6 +205,19 @@ namespace PARR.API.Controllers.V1 orig.AgentName = request.AgentName; orig.AgentTimeOutSec = request.AgentTimeOutSec; orig.AgentScript = request.AgentScript; + orig.DateModified = DateTimeOffset.UtcNow; + + //обновляем планировщик + orig.EsppSchValues.Clear(); + request.Schedule.ForEach(item => + { + orig.EsppSchValues.Add(new EsppSchValue + { + JobGroupId = orig.Id, + TypeConfigId = item.TypeConfigId, + TypeValueId = item.TypeValueId + }); + }); if (!await groupService.CommitAsync()) return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при изменении группы заданий на выполнение работ." } })); @@ -203,15 +227,35 @@ namespace PARR.API.Controllers.V1 $" {orig.Solution}, {orig.TemplateDuration}, {orig.ReferenceDate}, {orig.IsAutoDistributionEnabled}" + $", {orig.IsAgent}, {orig.AgentName}, {orig.AgentTimeOutSec}, {orig.AgentScript}"); + //TODO: Восстановить после перехода на JobGroup + //if (isScheduleChanged) + //{ + // //расписание было обновлено, отправим задание в очередь на перерасчет NextRun + // var requestToMq = new TemplateDistributorMq + // { + // ApplicationInWorkId = id + // }; + + // var msg = JsonSerializer.Serialize(requestToMq); + + // logger.LogDebug($"Расписание в РР applicationInWorkId: {id} было изменено. Отправляем задание в очередь на перерасчет NextRun"); + + // var sendResult = mqService.Send(mqSettings.TemplateDistributor, new[] { msg }); + + // if (sendResult.IsSuccess) + // logger.LogInformation($"Задание на перерасчет NextRun успешно отправлено в очередь MQ {mqSettings.TemplateDistributor.QueueName}"); + // else + // logger.LogError($"Ошибка при отправке задания на перерасчет NextRun в очередь MQ {mqSettings.TemplateDistributor.QueueName}"); + //} var updatedJobGroup = await groupService.Get() .Include(t => t.Jobs) .ThenInclude(t => t.Tnk) .FirstAsync(t => t.Id == orig.Id); - var response = mapper.Map(updatedJobGroup); + var response = mapper.Map(updatedJobGroup); - return Ok(new Response(response, true)); + return Ok(new Response(response, true)); } @@ -220,31 +264,64 @@ namespace PARR.API.Controllers.V1 /// /// /// - [HttpDelete(ApiRoutes.JobGroup.Delete)] - public async Task Delete([FromRoute] Guid id) + //[HttpDelete(ApiRoutes.JobGroup.Delete)] + //public async Task Delete([FromRoute] Guid id) + //{ + // var jobGroup = await groupService.Get() + // .Include(t => t.Jobs) + // .ThenInclude(t => t.Tnk) + // .FirstOrDefaultAsync(t => t.Id == id); + + // if (jobGroup == null) + // return BadRequest(new Response(false, new List { new ErrorModel { + // Message = $"Ошибка при удалении группы заданий на выполнение работ. Не найдена группа заданий на выполнение работ Id: {id}" + // } })); + + // if (!groupService.Delete(jobGroup) || !await groupService.CommitAsync()) + // return BadRequest(new Response(false, new List { new ErrorModel { + // Message = $"Ошибка при удалении группы заданий на выполнение работ" + // } })); + + // logger.LogInformation($"Пользователь {User.Identity?.Name} удалил задание на выполнение работ: {jobGroup.Id},{jobGroup.GroupName}," + + // $" {jobGroup.IsUmbrella}, {jobGroup.ShortDescription}, {jobGroup.FullDescription}," + + // $" {jobGroup.Solution}, {jobGroup.TemplateDuration}, {jobGroup.ReferenceDate}" + + // $"{jobGroup.IsAutoDistributionEnabled}, {jobGroup.IsAgent}, {jobGroup.AgentName}" + + // $"{jobGroup.AgentTimeOutSec}, {jobGroup.AgentScript}"); + + // return NoContent(); + //} + + + /// + /// Проверка, были ли изменения в расписании + /// + /// + /// + /// + private bool IsScheduleChanged(JobGroup orig, JobGroupRequest request) { - var jobGroup = await groupService.Get() - .Include(t => t.Jobs) - .ThenInclude(t => t.Tnk) - .FirstOrDefaultAsync(t => t.Id == id); + var isScheduleChanged = false; - if (jobGroup == null) - return BadRequest(new Response(false, new List { new ErrorModel { - Message = $"Ошибка при удалении группы заданий на выполнение работ. Не найдена группа заданий на выполнение работ Id: {id}" - } })); + if (orig.ReferenceDate != request.ReferenceDate) + isScheduleChanged = true; - if (!groupService.Delete(jobGroup) || !await groupService.CommitAsync()) - return BadRequest(new Response(false, new List { new ErrorModel { - Message = $"Ошибка при удалении группы заданий на выполнение работ" - } })); + if (request.Schedule.Count() != orig.EsppSchValues.Count()) + isScheduleChanged = true; - logger.LogInformation($"Пользователь {User.Identity?.Name} удалил задание на выполнение работ: {jobGroup.Id},{jobGroup.GroupName}," + - $" {jobGroup.IsUmbrella}, {jobGroup.ShortDescription}, {jobGroup.FullDescription}," + - $" {jobGroup.Solution}, {jobGroup.TemplateDuration}, {jobGroup.ReferenceDate}" + - $"{jobGroup.IsAutoDistributionEnabled}, {jobGroup.IsAgent}, {jobGroup.AgentName}" + - $"{jobGroup.AgentTimeOutSec}, {jobGroup.AgentScript}"); + if (request.IsAutoDistributionEnabled != orig.IsAutoDistributionEnabled) + isScheduleChanged = true; - return NoContent(); + request.Schedule.ForEach(requestSchedule => + { + var schExist = orig.EsppSchValues.FirstOrDefault(t => t.JobGroupId == orig.Id + && t.TypeValueId == requestSchedule.TypeValueId + && t.TypeConfigId == requestSchedule.TypeConfigId); + + if (schExist == null) + isScheduleChanged = true; + }); + + return isScheduleChanged; } } } diff --git a/PARR.API/MappingProfiles/DomainToResponseProfile.cs b/PARR.API/MappingProfiles/DomainToResponseProfile.cs index 1d44ab90..65df1825 100644 --- a/PARR.API/MappingProfiles/DomainToResponseProfile.cs +++ b/PARR.API/MappingProfiles/DomainToResponseProfile.cs @@ -308,11 +308,15 @@ namespace PARR.API.MappingProfiles #region JobGroup CreateMap() - .Include() - .ForMember(d => d.Name, o => o.MapFrom(s => s.GroupName)); + .Include(); + + CreateMap() + .ForMember(d => d.Name, o => o.MapFrom(s => s.GroupName)); CreateMap() - .ForMember(d => d.Jobs, o => o.MapFrom(s => s.Jobs)); + .ForMember(d => d.Schedule, o => o.MapFrom()) + .ForMember(d => d.JobsCount, o => o.MapFrom(s => s.Jobs.Count())); + #endregion #region JobAutoControl diff --git a/PARR.API/MappingProfiles/Resolvers/JobGroupScheduleResolver.cs b/PARR.API/MappingProfiles/Resolvers/JobGroupScheduleResolver.cs new file mode 100644 index 00000000..797c4fdb --- /dev/null +++ b/PARR.API/MappingProfiles/Resolvers/JobGroupScheduleResolver.cs @@ -0,0 +1,50 @@ +using AutoMapper; +using PARR.API.Contracts.V1.Responses; +using PARR.DAL.Contracts; +using PARR.DAL.Models.Job; +using PARR.DAL.Services.Interfaces; + +namespace PARR.API.MappingProfiles.Resolvers +{ + public class JobGroupScheduleResolver : IValueResolver + { + private readonly IEsppSchTypeConfigService esppConfigService; + private readonly ILogger logger; + private readonly IMapper mapper; + private readonly SettingsFromDb settingsFromDb; + + public JobGroupScheduleResolver( + IEsppSchTypeConfigService esppConfigService, + ILogger logger, + IMapper mapper, + SettingsFromDb settingsFromDb + ) + { + this.esppConfigService = esppConfigService; + this.logger = logger; + this.mapper = mapper; + this.settingsFromDb = settingsFromDb; + } + + + public JobGroupScheduleResponse? Resolve(JobGroup source, JobGroupResponse destination, JobGroupScheduleResponse? destMember, ResolutionContext context) + { + var schedule = esppConfigService.GetEsppScheduleDto(source.Id); + + if (schedule == null) + { + logger.LogError($"Не смог замапить расписание, так как оно null. JobGroupId: {source.Id}"); + return null; + } + + var response = new JobGroupScheduleResponse + { + Timezone = settingsFromDb.ScheduleTimezone, + TypeSchedule = mapper.Map(schedule.TypeSchedule), + Values = mapper.Map>(schedule.Values).OrderBy(t => t.Order).ToList() + }; + + return response; + } + } +} diff --git a/PARR.API/Validators/ApplicationInWorkValidator.cs b/PARR.API/Validators/ApplicationInWorkValidator.cs index f953c81d..58e6efda 100644 --- a/PARR.API/Validators/ApplicationInWorkValidator.cs +++ b/PARR.API/Validators/ApplicationInWorkValidator.cs @@ -70,12 +70,12 @@ namespace PARR.API.Validators // .WithMessage("Не удалось найти подходящую конфигруцию планировщика задания на выполнение работ"); #endregion - RuleFor(t => t.Schedule) - .NotNull().NotEmpty() - .When(t => t.Schedule.Count() > 0) - .WithMessage("Настройки планировщика не могут быть пустыми") - .Must((entity, value, c) => IsEsppSchValuesExist(entity.Schedule)) - .WithMessage("Не удалось найти подходящую конфигруцию планировщика задания"); + //RuleFor(t => t.Schedule) + // .NotNull().NotEmpty() + // .When(t => t.Schedule.Count() > 0) + // .WithMessage("Настройки планировщика не могут быть пустыми") + // .Must((entity, value, c) => IsEsppSchValuesExist(entity.Schedule)) + // .WithMessage("Не удалось найти подходящую конфигруцию планировщика задания"); #region comment @@ -109,7 +109,7 @@ namespace PARR.API.Validators } - private async Task IsAllowAutoDistributionEnabledAsync(ApplicationInWorkRequest entity, bool value) + private async Task IsAllowAutoDistributionEnabledAsync(ApplicationInWorkRequest entity, bool value)//TODO JobGroupRequest { //Автораспределение может быть включено, только если у EsppSchTypeValues не пустое поле DistributionPeriodId @@ -118,13 +118,13 @@ namespace PARR.API.Validators //Распределение включено, смотрим, разрешено ли оно в расписании //по идее, это расписание только с одним значением в EsppSchValues, но мы проверим у всех, но такого быть не может по хорошему - foreach (var item in entity.Schedule) - { - var schVal = await esppSchTypeValueService.GetAsync(item.TypeValueId); - if (schVal != null) - if (schVal.DistributionPeriodId == null) - return false; - } + //foreach (var item in entity.Schedule) + //{ + // var schVal = await esppSchTypeValueService.GetAsync(item.TypeValueId); + // if (schVal != null) + // if (schVal.DistributionPeriodId == null) + // return false; + //} return true; }