From e094db168f2609bf8a09d4f4b26beda46257b381 Mon Sep 17 00:00:00 2001 From: Mikhail Kuznetsov Date: Tue, 16 Sep 2025 15:27:26 +1000 Subject: [PATCH] =?UTF-8?q?feat(api):=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D0=B0=D0=BD=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=20Upd?= =?UTF-8?q?ate=20=D0=B4=D0=BB=D1=8F=20JobController?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PARR.API/Contracts/V1/Requests/JobRequest.cs | 7 + PARR.API/Controllers/V1/JobController.cs | 250 +++++++++++++++++- .../MappingProfiles/RequestToDomainProfile.cs | 4 +- PARR.API/Validators/JobRequestValidator.cs | 27 +- 4 files changed, 250 insertions(+), 38 deletions(-) diff --git a/PARR.API/Contracts/V1/Requests/JobRequest.cs b/PARR.API/Contracts/V1/Requests/JobRequest.cs index ce741c01..c688e996 100644 --- a/PARR.API/Contracts/V1/Requests/JobRequest.cs +++ b/PARR.API/Contracts/V1/Requests/JobRequest.cs @@ -2,6 +2,7 @@ { public class JobRequest { + public required Guid TnkId { get; set; } public required Guid GroupId { get; set; } @@ -26,6 +27,11 @@ public class UnitFilterRequest { + /// + /// Id = null в методе Create, в Update обязателен + /// + public Guid? Id { get; set; } + public required string UnitFilterMask { get; set; } public List? FieldFilters { get; set; } @@ -36,6 +42,7 @@ public class FieldFilterRequest { + public Guid FieldId { get; set; } public string? ValueMask { get; set; } diff --git a/PARR.API/Controllers/V1/JobController.cs b/PARR.API/Controllers/V1/JobController.cs index 67be7b86..58447fc6 100644 --- a/PARR.API/Controllers/V1/JobController.cs +++ b/PARR.API/Controllers/V1/JobController.cs @@ -160,6 +160,20 @@ namespace PARR.API.Controllers.V1 return BadRequest(new Response(jobValidateResult.Errors)); #endregion + + #region Проверка существования работы с такими же параметрами + var isExistTheSameLinks = await jobService.Get() + .CountAsync(t => + t.Group!.IsUmbrella == true && + t.GroupId == request.GroupId && + (t.MinValueRelationships == request.MinValueRelationships || + t.MaxValueRelationships == request.MaxValueRelationships) + ); + if (isExistTheSameLinks > 0) + return BadRequest(new Response(false, new List { new ErrorModel { FieldName = nameof(request.Name), Message = $"Работа с указанным диапазоном связей пересекается с уже имеющейся в базе данных({request.MinValueRelationships}-{request.MaxValueRelationships})" } })); + #endregion + + var job = mapper.Map(request); if (!await jobService.CreateAsync(job) || !await jobService.CommitAsync()) @@ -200,21 +214,84 @@ namespace PARR.API.Controllers.V1 if (!resultValidate.IsValid) return BadRequest(new Response(resultValidate.Errors)); - var orig = await jobService.Get().Include(t => t.Tnk) + var orig = await jobService.Get() + .Include(t => t.Tnk) + .Include(t => t.Group) + .Include(t => t.UnitFilters) + .ThenInclude(t => t.FieldFilters) + .Include(t => t.UnitFilters) + .ThenInclude(t => t.RelationshipFilters) + .AsSingleQuery() .FirstOrDefaultAsync(t => t.Id == id); if (orig == null) return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при изменении задания на выполнение работ. Не найдено задание с Id: {id}" } })); - orig.Name = request.Name.Trim(); - orig.WorkName = request.WorkName.Trim(); - orig.MinValueRelationships = request.MinValueRelationships; - orig.MaxValueRelationships = request.MaxValueRelationships; - orig.isParentRelationships = request.isParentRelationships; - orig.TemplateNameMask = request.TemplateNameMask.Trim(); - orig.WorkGroupMask = request.WorkGroupMask.Trim(); - orig.TnkId = request.TnkId; - orig.GroupId = request.GroupId; + #region обновление полей задания на работу + var changed = false; + + if (orig.Name != request.Name.Trim()) + { + orig.Name = request.Name.Trim(); + changed = true; + } + + if (orig.WorkName != request.WorkName.Trim()) + { + orig.WorkName = request.WorkName.Trim(); + changed = true; + } + + if (orig.MinValueRelationships != request.MinValueRelationships) + { + orig.MinValueRelationships = request.MinValueRelationships; + changed = true; + } + + if (orig.MaxValueRelationships != request.MaxValueRelationships) + { + orig.MaxValueRelationships = request.MaxValueRelationships; + changed = true; + } + + if (orig.isParentRelationships != request.isParentRelationships) + { + orig.isParentRelationships = request.isParentRelationships; + changed = true; + } + + if (orig.TemplateNameMask != request.TemplateNameMask.Trim()) + { + orig.TemplateNameMask = request.TemplateNameMask.Trim(); + changed = true; + } + + if (orig.WorkGroupMask != request.WorkGroupMask.Trim()) + { + orig.WorkGroupMask = request.WorkGroupMask.Trim(); + changed = true; + } + + if (orig.TnkId != request.TnkId) + { + orig.TnkId = request.TnkId; + changed = true; + } + + if (orig.GroupId != request.GroupId) + { + orig.GroupId = request.GroupId; + changed = true; + } + + if (changed) + orig.DateModified = DateTimeOffset.UtcNow; + #endregion + + var job = mapper.Map(request); + job.Id = id;//На всякий. Пусть будет для чистоты + + UpdateUnitFilters(orig, job);//Обновление вложенных дочерних элементов-фильтров if (!await jobService.CommitAsync()) return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при изменении задания на выполнение работ." } })); @@ -224,17 +301,164 @@ namespace PARR.API.Controllers.V1 $" {orig.TemplateNameMask}, {orig.TnkId}, {nameof(orig.GroupId)}"); - var updatedApplicationInWork = await jobService.Get().Include(t => t.Tnk) + var updatedJob = 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) .FirstAsync(t => t.Id == orig.Id); - var response = mapper.Map(updatedApplicationInWork); + var response = mapper.Map(updatedJob); response.TemplatesCount = await templateService.Get().CountAsync(t => t.JobId == id); var statistics = await GetStatisticsAsync(response.Id); BindStatistics(response, statistics); return Ok(new Response(response, true)); + } + + private void UpdateUnitFilters(Job orig, Job mappedRequest) + { + //Сразу удаляем UnitFilter которых нет + var toDelete = orig.UnitFilters.Where(t => !mappedRequest.UnitFilters.Any(e => e.Id == t.Id)); + foreach (var item in toDelete) + orig.UnitFilters.Remove(item); + + foreach (var mappedUnitFilter in mappedRequest.UnitFilters) + { + var origUnitFilter = orig.UnitFilters.FirstOrDefault(t => t.Id == mappedUnitFilter.Id); + + if (origUnitFilter == null) + { + var newUnitFilter = new JobUnitFilter + { + UnitFilter = mappedUnitFilter.UnitFilter, + DateCreated = DateTimeOffset.UtcNow, + JobId = orig.Id + }; + + foreach (var newRequestFieldFilter in mappedUnitFilter.FieldFilters) + { + var newFieldFilter = CreateFieldFilter(newUnitFilter.Id, newRequestFieldFilter.FieldId, newRequestFieldFilter.ValueMask); + + newUnitFilter.FieldFilters.Add(newFieldFilter); + } + + foreach (var newRequestRelationshipFilter in mappedUnitFilter.RelationshipFilters) + { + var newRelationshipFilter = CreateRelationshipFilter(newRequestRelationshipFilter.FieldId, + newRequestRelationshipFilter.IsParent, + newRequestRelationshipFilter.IsFullMatch, + newRequestRelationshipFilter.IsInverse, + newRequestRelationshipFilter.ValueMask); + newUnitFilter.RelationshipFilters.Add(newRelationshipFilter); + } + + orig.UnitFilters.Add(newUnitFilter); + } + else + { + #region Непосредственно UnitFilter + if (origUnitFilter.UnitFilter != mappedUnitFilter.UnitFilter) + { + origUnitFilter.UnitFilter = mappedUnitFilter.UnitFilter; + origUnitFilter.DateModified = DateTimeOffset.UtcNow; + } + #endregion + + #region FieldFilters + //Сразу удаляем неактуальные + var fieldFiltersToDelete = origUnitFilter.FieldFilters.Where(t => !mappedUnitFilter.FieldFilters.Any(m => m.FieldId == t.FieldId)); + foreach (var item in fieldFiltersToDelete) + origUnitFilter.FieldFilters.Remove(item); + + //Перебираем FieldFilter + foreach (var mappedFieldFilter in mappedUnitFilter.FieldFilters) + { + var origFieldFilter = origUnitFilter.FieldFilters.FirstOrDefault(t => t.FieldId == mappedFieldFilter.FieldId); + + if (origFieldFilter == null)//Нет в БД? не проблема - создадим + { + var newFieldFilter = CreateFieldFilter(origUnitFilter.Id, mappedFieldFilter.FieldId, mappedFieldFilter.ValueMask); + origUnitFilter.FieldFilters.Add(newFieldFilter); + } + else + { + if (origFieldFilter.ValueMask != mappedFieldFilter.ValueMask) + { + origFieldFilter.ValueMask = mappedFieldFilter.ValueMask; + origFieldFilter.DateModified = DateTimeOffset.UtcNow; + } + } + } + #endregion + + #region RelationshipFilters + //Сразу удаляем неактуальные + var relationshipFiltersToDelete = origUnitFilter.RelationshipFilters.Where(t => !mappedUnitFilter.RelationshipFilters.Any(m => m.FieldId == t.FieldId)); + foreach (var item in relationshipFiltersToDelete) + origUnitFilter.RelationshipFilters.Remove(item); + + //Перебираем RelationshipFilters + foreach (var mappedRelationshipFilter in mappedUnitFilter.RelationshipFilters) + { + var origRelationshipFilter = origUnitFilter.RelationshipFilters.FirstOrDefault(t => t.FieldId == mappedRelationshipFilter.FieldId); + + if (origRelationshipFilter == null)//Нет в БД? не проблема - создадим + { + var newRelationshipFilter = CreateRelationshipFilter(mappedRelationshipFilter.FieldId, + mappedRelationshipFilter.IsParent, + mappedRelationshipFilter.IsFullMatch, + mappedRelationshipFilter.IsInverse, + mappedRelationshipFilter.ValueMask); + + origUnitFilter.RelationshipFilters.Add(newRelationshipFilter); + } + else + { + if (origRelationshipFilter.IsParent != mappedRelationshipFilter.IsParent) + origRelationshipFilter.IsParent = mappedRelationshipFilter.IsParent; + + if (origRelationshipFilter.ValueMask != mappedRelationshipFilter.ValueMask) + origRelationshipFilter.ValueMask = mappedRelationshipFilter.ValueMask; + + if (origRelationshipFilter.IsFullMatch != mappedRelationshipFilter.IsFullMatch) + origRelationshipFilter.IsFullMatch = mappedRelationshipFilter.IsFullMatch; + + if (origRelationshipFilter.IsInverse != mappedRelationshipFilter.IsInverse) + origRelationshipFilter.IsInverse = mappedRelationshipFilter.IsInverse; + } + } + #endregion + } + } + } + + private static JobRelationshipFilter CreateRelationshipFilter(Guid fieldId, bool isParent, bool isFullMatch, bool isInverse, string valueMask) + { + return new JobRelationshipFilter + { + FieldId = fieldId, + IsParent = isParent, + IsFullMatch = isFullMatch, + IsInverse = isInverse, + ValueMask = valueMask + }; + } + + private static FieldFilter CreateFieldFilter(Guid unitFilterId, Guid fieldId, string valueMask) + { + return new FieldFilter + { + UnitFilterId = unitFilterId, + DateCreated = DateTimeOffset.UtcNow, + FieldId = fieldId, + ValueMask = valueMask + }; } @@ -350,4 +574,6 @@ namespace PARR.API.Controllers.V1 public required ScheduleStats ScheduleStatistics { get; set; } } + + } diff --git a/PARR.API/MappingProfiles/RequestToDomainProfile.cs b/PARR.API/MappingProfiles/RequestToDomainProfile.cs index 4a699077..cb31f956 100644 --- a/PARR.API/MappingProfiles/RequestToDomainProfile.cs +++ b/PARR.API/MappingProfiles/RequestToDomainProfile.cs @@ -12,6 +12,7 @@ namespace PARR.API.MappingProfiles { CreateMap(); + #region Job CreateMap() .ForMember(d => d.Id, o => o.MapFrom(s => Guid.NewGuid())) .ForMember(d => d.DateCreated, o => o.MapFrom(s => DateTimeOffset.UtcNow)) @@ -23,7 +24,7 @@ namespace PARR.API.MappingProfiles }); CreateMap() - .ForMember(d => d.Id, o => o.MapFrom(s => Guid.NewGuid())) + .ForMember(d => d.Id, o => o.MapFrom(s => (s.Id == null) ? Guid.NewGuid() : s.Id)) .ForMember(d => d.DateCreated, o => o.MapFrom(s => DateTimeOffset.UtcNow)) .ForMember(d => d.UnitFilter, o => o.MapFrom(s => s.UnitFilterMask)) .AfterMap((s, d) => @@ -42,6 +43,7 @@ namespace PARR.API.MappingProfiles .ForMember(d => d.DateCreated, o => o.MapFrom(s => DateTimeOffset.UtcNow)); CreateMap(); + #endregion } } } diff --git a/PARR.API/Validators/JobRequestValidator.cs b/PARR.API/Validators/JobRequestValidator.cs index f206ed31..b8d3915d 100644 --- a/PARR.API/Validators/JobRequestValidator.cs +++ b/PARR.API/Validators/JobRequestValidator.cs @@ -1,5 +1,4 @@ using FluentValidation; -using Microsoft.EntityFrameworkCore; using PARR.API.Contracts.V1.Requests; using PARR.DAL.Models.Job; using PARR.DAL.Services.Interfaces; @@ -27,6 +26,7 @@ namespace PARR.API.Validators this.jobGroupService = jobGroupService; this.jobService = jobService; this.unitFieldService = unitFieldService; + RuleFor(t => t.Name) .NotNull().NotEmpty(); @@ -49,17 +49,10 @@ namespace PARR.API.Validators .WithMessage("Неверно заданы параметры фильтров. Внимательнее, пожалуйста!"); RuleFor(t => t.MinValueRelationships) - .MustAsync(async (entity, value, c) => await IsMinValueRelationShipsExist(entity)) - .WithMessage("Работа с таким минимальным количеством связей уже существует"); + .GreaterThanOrEqualTo(0); 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) @@ -103,21 +96,5 @@ namespace PARR.API.Validators { 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