From 39eb8ddc2cb3dec30b98b8160925b767d366f14a Mon Sep 17 00:00:00 2001 From: Mikhail Kuznetsov Date: Thu, 11 Sep 2025 16:03:51 +1000 Subject: [PATCH] =?UTF-8?q?feat(api):=20JobControl=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=BF=D0=B8=D1=81=D0=B0=D0=BD=20=D0=BC=D0=B5=D1=82=D0=BE?= =?UTF-8?q?=D0=B4=20Create+=D0=92=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PARR.API/Contracts/V1/ApiRoutes.cs | 2 + PARR.API/Contracts/V1/Requests/JobRequest.cs | 39 +++++- .../Contracts/V1/Responses/JobResponse.cs | 8 ++ PARR.API/Controllers/V1/JobController.cs | 99 ++++++++++---- PARR.API/Controllers/V1/JobGroupController.cs | 2 +- .../MappingProfiles/RequestToDomainProfile.cs | 33 +++++ PARR.API/Validators/JobRequestValidator.cs | 123 ++++++++++++++++++ PARR.API/Validators/JobValidator.cs | 48 ------- .../Implementations/UnitFilterService.cs | 13 +- .../Interfaces/IUnitFilterService.cs | 8 +- 10 files changed, 295 insertions(+), 80 deletions(-) create mode 100644 PARR.API/Validators/JobRequestValidator.cs delete mode 100644 PARR.API/Validators/JobValidator.cs diff --git a/PARR.API/Contracts/V1/ApiRoutes.cs b/PARR.API/Contracts/V1/ApiRoutes.cs index 74592acf..fff342c1 100644 --- a/PARR.API/Contracts/V1/ApiRoutes.cs +++ b/PARR.API/Contracts/V1/ApiRoutes.cs @@ -374,6 +374,8 @@ public const string Create = Base + "/jobs/"; public const string Update = Base + "/jobs/" + getParam; + public const string Preview = Base + "/jobs/units/preview"; + public const string getParam = "{id}"; } diff --git a/PARR.API/Contracts/V1/Requests/JobRequest.cs b/PARR.API/Contracts/V1/Requests/JobRequest.cs index c3463849..ce741c01 100644 --- a/PARR.API/Contracts/V1/Requests/JobRequest.cs +++ b/PARR.API/Contracts/V1/Requests/JobRequest.cs @@ -9,7 +9,7 @@ public required string Name { get; set; } public int? MinValueRelationships { get; set; } - + public int? MaxValueRelationships { get; set; } public bool? isParentRelationships { get; set; } @@ -19,5 +19,40 @@ public required string WorkGroupMask { get; set; } public required string WorkName { get; set; } + + public required List UnitFilters { get; set; } } -} + + + public class UnitFilterRequest + { + public required string UnitFilterMask { get; set; } + + public List? FieldFilters { get; set; } + + public List? RelationshipFilters { get; set; } + } + + + public class FieldFilterRequest + { + public Guid FieldId { get; set; } + + public string? ValueMask { get; set; } + + } + + + public class RelationshipFilterRequest + { + public Guid FieldId { get; set; } + + public bool? IsParent { get; set; } + + public string? ValueMask { get; set; } + + public bool? IsFullMatch { get; set; } + + public bool? IsInverse { get; set; } + } +} \ No newline at end of file diff --git a/PARR.API/Contracts/V1/Responses/JobResponse.cs b/PARR.API/Contracts/V1/Responses/JobResponse.cs index 7e09f57f..272df40d 100644 --- a/PARR.API/Contracts/V1/Responses/JobResponse.cs +++ b/PARR.API/Contracts/V1/Responses/JobResponse.cs @@ -6,9 +6,17 @@ public required string Name { get; set; } + public int? MinValueRelationships { get; set; } + + public int? MaxValueRelationships { get; set; } + + public bool? isParentRelationships { get; set; } + public required string TemplateNameMask { get; set; } public required string WorkName { get; set; } + + public required string WorkGroupMask { get; set; } } diff --git a/PARR.API/Controllers/V1/JobController.cs b/PARR.API/Controllers/V1/JobController.cs index d1f17a55..a3057fa6 100644 --- a/PARR.API/Controllers/V1/JobController.cs +++ b/PARR.API/Controllers/V1/JobController.cs @@ -14,9 +14,11 @@ using PARR.API.Services.Interfaces; using PARR.Constants; using PARR.DAL.Contracts; using PARR.DAL.DomainModels; +using PARR.DAL.DomainServices.Interfaces; using PARR.DAL.Models.Job; using PARR.DAL.Services.Interfaces; using PARR.DAL.Services.Interfaces.Job; +using PARR.DAL.Services.Interfaces.Unit; namespace PARR.API.Controllers.V1 { @@ -31,7 +33,13 @@ namespace PARR.API.Controllers.V1 private readonly IUriService uriService; private readonly IJobService jobService; private readonly ITemplateService templateService; - private readonly IValidator validator; + private readonly IJobGroupService jobGroupService; + private readonly IValidator jobValidator; + private readonly IValidator unitFilterValidator; + private readonly IValidator fieldFilterValidator; + private readonly IValidator relationshipFilterValidator; + private readonly IUnitFilterService unitFilterService; + private readonly IUnitService unitService; public JobController( ILogger logger, @@ -39,7 +47,13 @@ namespace PARR.API.Controllers.V1 IUriService uriService, IJobService jobService, ITemplateService templateService, - IValidator validator + IJobGroupService jobGroupService, + IValidator jobValidator, + IValidator unitFilterValidator, + IValidator fieldFilterValidator, + IValidator relationshipFilterValidator, + IUnitFilterService unitFilterService, + IUnitService unitService ) { this.logger = logger; @@ -47,7 +61,13 @@ namespace PARR.API.Controllers.V1 this.uriService = uriService; this.jobService = jobService; this.templateService = templateService; - this.validator = validator; + this.jobGroupService = jobGroupService; + this.jobValidator = jobValidator; + this.unitFilterValidator = unitFilterValidator; + this.fieldFilterValidator = fieldFilterValidator; + this.relationshipFilterValidator = relationshipFilterValidator; + this.unitFilterService = unitFilterService; + this.unitService = unitService; } /// @@ -142,36 +162,31 @@ namespace PARR.API.Controllers.V1 [HttpPost(ApiRoutes.Job.Create)] public async Task Create([FromBody] JobRequest request) { - var resultValidate = await validator.ValidateAsync(request); + #region Валидация + var jobValidateResult = await jobValidator.ValidateAsync(request);//Валидация параметров самого задания - if (!resultValidate.IsValid) - return BadRequest(new Response(resultValidate.Errors)); - - var job = new Job - { - Id = Guid.NewGuid(), - Name = request.Name.Trim(), - WorkName = request.WorkName.Trim(), - MinValueRelationships = request.MinValueRelationships, - MaxValueRelationships = request.MaxValueRelationships, - isParentRelationships = request.isParentRelationships, - TemplateNameMask = request.TemplateNameMask.Trim(), - WorkGroupMask = request.WorkGroupMask.Trim(), - TnkId = request.TnkId, - GroupId = request.GroupId - }; + if (!jobValidateResult.IsValid) + return BadRequest(new Response(jobValidateResult.Errors)); + #endregion + var job = mapper.Map(request); if (!await jobService.CreateAsync(job) || !await jobService.CommitAsync()) return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при созании задания на выполнение работ" } })); logger.LogInformation($"Пользователь {User.Identity?.Name} добавил задание на выполнение работ: {job.Id}, {job.Name}, {job.WorkName}"); + var createdJob = await jobService.Get() + .Include(t => t.Tnk) + .Include(t => t.Group) + .Include(t => t.UnitFilters) + .ThenInclude(t => t.FieldFilters) + .ThenInclude(t => t.Field) + .Include(t => t.UnitFilters) + .ThenInclude(t => t.RelationshipFilters) + .FirstOrDefaultAsync(t => t.Id == job.Id); - var createdJob = await jobService.Get().Include(t => t.Tnk) - .FirstAsync(t => t.Id == job.Id); - - var locationUri = uriService.GetUri(ApiRoutes.Job.Get, ApiRoutes.Job.getParam, createdJob.Id); + var locationUri = uriService.GetUri(ApiRoutes.Job.Get, ApiRoutes.Job.getParam, createdJob!.Id); var response = mapper.Map(createdJob); // так как мы только что создали Job, то у него нет шаблонов, смело ставим = 0 (ускоряем запрос) @@ -190,7 +205,7 @@ namespace PARR.API.Controllers.V1 [HttpPut(ApiRoutes.Job.Update)] public async Task Update([FromRoute] Guid id, [FromBody] JobRequest request) { - var resultValidate = await validator.ValidateAsync(request); + var resultValidate = await jobValidator.ValidateAsync(request); if (!resultValidate.IsValid) return BadRequest(new Response(resultValidate.Errors)); @@ -267,6 +282,38 @@ namespace PARR.API.Controllers.V1 return NoContent(); } + /// + /// Получить список ЭК для которых будут созданы шаблоны + /// + /// + /// + [HttpPost(ApiRoutes.Job.Preview)] + public async Task Preview([FromBody] JobRequest request) + { + #region Валидация + var jobValidateResult = await jobValidator.ValidateAsync(request);//Валидация параметров самого задания + + if (!jobValidateResult.IsValid) + return BadRequest(new Response(jobValidateResult.Errors)); + #endregion + + var job = mapper.Map(request); + + var group = await jobGroupService.GetAsync(request.GroupId); + + job.Group = group; + + var unitIds = await unitFilterService.GetUnitsIdByJobFilterAsync(job); + + if (unitIds == null) + return NotFound(); + + var units = await unitService.Get().Where(t => unitIds.Any(a => a == t.Id)).Take(1000).ToListAsync(); + + var response = mapper.Map>(units); + + return Ok(response); + } /// /// Загрузка статистики @@ -303,7 +350,6 @@ namespace PARR.API.Controllers.V1 //job.ScheduleStatistics = new ScheduleStats { Activated = statistics.ScheduleStatistics.Activated, Errors = statistics.ScheduleStatistics.Errors, Synchronized = statistics.ScheduleStatistics.Synchronized }; } - } public class JobStatModel @@ -312,4 +358,5 @@ namespace PARR.API.Controllers.V1 public required ScheduleStats ScheduleStatistics { get; set; } } + } diff --git a/PARR.API/Controllers/V1/JobGroupController.cs b/PARR.API/Controllers/V1/JobGroupController.cs index b8dfd0d2..0f22fb00 100644 --- a/PARR.API/Controllers/V1/JobGroupController.cs +++ b/PARR.API/Controllers/V1/JobGroupController.cs @@ -104,13 +104,13 @@ 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) return NotFound(); var response = mapper.Map(jobGroup); + await AppendMissingDataAsync(response); return Ok(new Response(response, true)); } diff --git a/PARR.API/MappingProfiles/RequestToDomainProfile.cs b/PARR.API/MappingProfiles/RequestToDomainProfile.cs index 74b7107d..4a699077 100644 --- a/PARR.API/MappingProfiles/RequestToDomainProfile.cs +++ b/PARR.API/MappingProfiles/RequestToDomainProfile.cs @@ -1,6 +1,8 @@ using AutoMapper; +using PARR.API.Contracts.V1.Requests; using PARR.API.Contracts.V1.Requests.Queries; using PARR.DAL.DomainModels; +using PARR.DAL.Models.Job; namespace PARR.API.MappingProfiles { @@ -9,6 +11,37 @@ namespace PARR.API.MappingProfiles public RequestToDomainProfile() { CreateMap(); + + CreateMap() + .ForMember(d => d.Id, o => o.MapFrom(s => Guid.NewGuid())) + .ForMember(d => d.DateCreated, o => o.MapFrom(s => DateTimeOffset.UtcNow)) + .AfterMap((s, d) => + { + if (d.UnitFilters != null) + foreach (var unitFilter in d.UnitFilters) + unitFilter.JobId = d.Id; + }); + + CreateMap() + .ForMember(d => d.Id, o => o.MapFrom(s => Guid.NewGuid())) + .ForMember(d => d.DateCreated, o => o.MapFrom(s => DateTimeOffset.UtcNow)) + .ForMember(d => d.UnitFilter, o => o.MapFrom(s => s.UnitFilterMask)) + .AfterMap((s, d) => + { + if (d.FieldFilters != null) + foreach (var fieldFilter in d.FieldFilters) + fieldFilter.UnitFilterId = d.Id; + + if (d.RelationshipFilters != null) + foreach (var relationshipFilter in d.RelationshipFilters) + relationshipFilter.UnitFilterId = d.Id; + }); + + CreateMap() + .ForMember(d => d.Id, o => o.MapFrom(s => Guid.NewGuid())) + .ForMember(d => d.DateCreated, o => o.MapFrom(s => DateTimeOffset.UtcNow)); + + CreateMap(); } } } diff --git a/PARR.API/Validators/JobRequestValidator.cs b/PARR.API/Validators/JobRequestValidator.cs new file mode 100644 index 00000000..f206ed31 --- /dev/null +++ b/PARR.API/Validators/JobRequestValidator.cs @@ -0,0 +1,123 @@ +using FluentValidation; +using Microsoft.EntityFrameworkCore; +using PARR.API.Contracts.V1.Requests; +using PARR.DAL.Models.Job; +using PARR.DAL.Services.Interfaces; +using PARR.DAL.Services.Interfaces.Job; +using PARR.DAL.Services.Interfaces.Unit; + +namespace PARR.API.Validators +{ + public class JobRequestValidator : AbstractValidator + { + private readonly ITnkService tnkService; + private readonly IJobGroupService jobGroupService; + private readonly IJobService jobService; + private readonly IUnitFieldService unitFieldService; + private JobGroup? jobGroup; + + public JobRequestValidator( + ITnkService tnkService, + IJobGroupService jobGroupService, + IJobService jobService, + IUnitFieldService unitFieldService + ) + { + this.tnkService = tnkService; + this.jobGroupService = jobGroupService; + this.jobService = jobService; + this.unitFieldService = unitFieldService; + RuleFor(t => t.Name) + .NotNull().NotEmpty(); + + RuleFor(t => t.WorkName) + .NotNull().NotEmpty(); + + RuleFor(t => t.TnkId) + .MustAsync(async (entity, value, c) => await IsTnkExist(entity)) + .WithMessage("Указан несуществующий Id ТНК({PropertyValue})"); + + RuleFor(t => t.GroupId) + .MustAsync(async (entity, value, c) => await IsGroupExist(entity)) + .WithMessage("Указан несуществующий Id группы работ({PropertyValue})"); + + RuleForEach(t => t.UnitFilters) + .NotNull().NotEmpty(); + + RuleForEach(t => t.UnitFilters) + .MustAsync(async (entity, value, c) => await IsUnitFiltersCorrect(entity)) + .WithMessage("Неверно заданы параметры фильтров. Внимательнее, пожалуйста!"); + + RuleFor(t => t.MinValueRelationships) + .MustAsync(async (entity, value, c) => await IsMinValueRelationShipsExist(entity)) + .WithMessage("Работа с таким минимальным количеством связей уже существует"); + + RuleFor(t => t.MaxValueRelationships) + .GreaterThanOrEqualTo(t => t.MinValueRelationships); + + RuleFor(t => t.MaxValueRelationships) + .MustAsync(async (entity, value, c) => await IsMaxValueRelationShipsExist(entity)) + .WithMessage("Работа с таким максимальным количеством связей уже существует"); + + + } + + private async Task IsUnitFiltersCorrect(JobRequest entity) + { + foreach (var unitFilter in entity.UnitFilters) + { + if (string.IsNullOrEmpty(unitFilter.UnitFilterMask)) + return false; + + if (unitFilter.FieldFilters == null || !unitFilter.FieldFilters.Any()) + return false; + + foreach (var fieldFilter in unitFilter.FieldFilters) + { + var fieldId = fieldFilter.FieldId; + if (await unitFieldService.GetAsync(fieldId) == null) + return false; + } + + if (unitFilter.RelationshipFilters != null || unitFilter.FieldFilters.Any()) + foreach (var relationshipFilter in unitFilter.RelationshipFilters!) + { + var fieldId = relationshipFilter.FieldId; + if (await unitFieldService.GetAsync(fieldId) == null) + return false; + } + } + + return true; + } + + private async Task IsGroupExist(JobRequest entity) + { + jobGroup = await jobGroupService.GetAsync(entity.GroupId); + + return jobGroup != null; + } + + + private async Task IsTnkExist(JobRequest entity) + { + return await tnkService.GetAsync(entity.TnkId) != null; + } + + private async Task IsMinValueRelationShipsExist(JobRequest entity) + { + if (!jobGroup?.IsUmbrella == true) + return true; + + return await jobService.Get().CountAsync(t => t.GroupId == entity.GroupId && t.MinValueRelationships == entity.MinValueRelationships) == 0; + } + + private async Task IsMaxValueRelationShipsExist(JobRequest entity) + { + if (!jobGroup?.IsUmbrella == true) + return true; + + return await jobService.Get().CountAsync(t => t.GroupId == entity.GroupId && t.MaxValueRelationships == entity.MaxValueRelationships) == 0; + } + } +} \ No newline at end of file diff --git a/PARR.API/Validators/JobValidator.cs b/PARR.API/Validators/JobValidator.cs deleted file mode 100644 index 376c138c..00000000 --- a/PARR.API/Validators/JobValidator.cs +++ /dev/null @@ -1,48 +0,0 @@ -using FluentValidation; -using PARR.API.Contracts.V1.Requests; -using PARR.DAL.Services.Interfaces; -using PARR.DAL.Services.Interfaces.Job; - -namespace PARR.API.Validators -{ - public class JobValidator : AbstractValidator - { - private readonly ITnkService tnkService; - private readonly IJobGroupService jobGroupService; - - public JobValidator( - ITnkService tnkService, - IJobGroupService jobGroupService - ) - { - this.tnkService = tnkService; - this.jobGroupService = jobGroupService; - - RuleFor(t => t.Name) - .NotNull().NotEmpty(); - - RuleFor(t => t.WorkName) - .NotNull().NotEmpty(); - - RuleFor(t => t.TnkId) - .MustAsync(async (entity, value, c) => await IsTnkExist(entity)) - .WithMessage("Указан несуществующий Id ТНК"); - - RuleFor(t => t.GroupId) - .MustAsync(async (entity, value, c) => await IsGroupExist(entity)) - .WithMessage("Указан несуществующий Id группы работ"); - } - - - private async Task IsGroupExist(JobRequest entity) - { - return await jobGroupService.GetAsync(entity.GroupId) != null; - } - - - private async Task IsTnkExist(JobRequest entity) - { - return await tnkService.GetAsync(entity.TnkId) != null; - } - } -} diff --git a/PARR.DAL/DomainServices/Implementations/UnitFilterService.cs b/PARR.DAL/DomainServices/Implementations/UnitFilterService.cs index 7e04fd1a..91019cb5 100644 --- a/PARR.DAL/DomainServices/Implementations/UnitFilterService.cs +++ b/PARR.DAL/DomainServices/Implementations/UnitFilterService.cs @@ -1,6 +1,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using PARR.DAL.DomainServices.Interfaces; +using PARR.DAL.Models.Job; using PARR.DAL.Services.Interfaces; using PARR.DAL.Services.Interfaces.Job; using PARR.DAL.Services.Interfaces.Unit; @@ -28,9 +29,9 @@ namespace PARR.DAL.DomainServices.Implementations this.templateService = templateService; } + public async Task?> GetUnitsIdByJobFilterAsync(Guid jobId) { - var result = new List(); var job = await jobService .Get().AsNoTracking() @@ -44,6 +45,14 @@ namespace PARR.DAL.DomainServices.Implementations if (job == null) return null; + return await GetUnitsIdByJobFilterAsync(job); + } + + + public async Task?> GetUnitsIdByJobFilterAsync(Job job) + { + var result = new List(); + var baseQuery = unitService .Get().AsNoTracking() .Include(t => t.UnitValues) @@ -110,6 +119,8 @@ namespace PARR.DAL.DomainServices.Implementations return result; } + + public async Task?> GetUnitsIdForExistTemplatesByJobFilterAsync(Guid jobId) { var unitIdsMustBeCreated = await GetUnitsIdByJobFilterAsync(jobId); diff --git a/PARR.DAL/DomainServices/Interfaces/IUnitFilterService.cs b/PARR.DAL/DomainServices/Interfaces/IUnitFilterService.cs index 05a237b8..2cb30582 100644 --- a/PARR.DAL/DomainServices/Interfaces/IUnitFilterService.cs +++ b/PARR.DAL/DomainServices/Interfaces/IUnitFilterService.cs @@ -1,4 +1,6 @@ -namespace PARR.DAL.DomainServices.Interfaces +using PARR.DAL.Models.Job; + +namespace PARR.DAL.DomainServices.Interfaces { public interface IUnitFilterService { @@ -9,6 +11,8 @@ /// Task?> GetUnitsIdByJobFilterAsync(Guid jobId); + Task?> GetUnitsIdByJobFilterAsync(Job job); + /// /// Получить все Unit.Id, для которых не были созданы Template /// @@ -22,6 +26,6 @@ /// Id работы /// Task?> GetUnitsIdForExistTemplatesByJobFilterAsync(Guid jobId); - + } }