From 3818a470ce2e3c53e90dde2d0a88b1952def5a6e Mon Sep 17 00:00:00 2001 From: Mikhail Trubnikov Date: Mon, 28 Sep 2026 12:22:30 +1000 Subject: [PATCH] =?UTF-8?q?feat(api):=20Job=20-=20=D0=B2=20=D1=80=D0=B5?= =?UTF-8?q?=D1=81=D0=BF=D0=BE=D0=BD=D1=81=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D1=8B=20=D0=B3=D1=80=D1=83=D0=BF=D0=BF=D0=BE?= =?UTF-8?q?=D0=B2=D1=8B=D0=B5=20=D1=84=D0=B8=D0=BB=D1=8C=D1=82=D1=80=D1=8B?= =?UTF-8?q?.=20=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=B2=D0=B0=D0=BB=D0=B8=D0=B4=D0=B0=D1=86=D0=B8=D1=8F=20=D1=81?= =?UTF-8?q?=20=D1=83=D1=87=D0=B5=D1=82=D0=BE=D0=BC=20IsAllowJobUnitFilter?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../V1/Responses/JobGroupResponse.cs | 13 ++- .../Contracts/V1/Responses/JobResponse.cs | 3 + PARR.API/Controllers/V1/JobController.cs | 46 ++++++++-- .../DomainToResponseProfile.cs | 6 +- PARR.API/Validators/JobRequestValidator.cs | 88 +++++-------------- 5 files changed, 75 insertions(+), 81 deletions(-) diff --git a/PARR.API/Contracts/V1/Responses/JobGroupResponse.cs b/PARR.API/Contracts/V1/Responses/JobGroupResponse.cs index 4e26b101..b57f19ec 100644 --- a/PARR.API/Contracts/V1/Responses/JobGroupResponse.cs +++ b/PARR.API/Contracts/V1/Responses/JobGroupResponse.cs @@ -50,6 +50,11 @@ // public int? AgentTimeOutSec { get; set; } // public string? AgentScript { get; set; } + + /// + /// Юнит фильтры + /// + public List? UnitFilters { get; set; } } public class JobGroupResponse : JobGroupWithDistributionConfigResponse @@ -71,10 +76,10 @@ /// public ScheduleExcludeTypeCalendarResponse? ScheduleExcludeTypeCalendar { get; set; } - /// - /// Юнит фильтры - /// - public List? UnitFilters { get; set; } + ///// + ///// Юнит фильтры + ///// + //public List? UnitFilters { get; set; } } public class JobGroupAutoControlResponse diff --git a/PARR.API/Contracts/V1/Responses/JobResponse.cs b/PARR.API/Contracts/V1/Responses/JobResponse.cs index 33590680..39a73dd4 100644 --- a/PARR.API/Contracts/V1/Responses/JobResponse.cs +++ b/PARR.API/Contracts/V1/Responses/JobResponse.cs @@ -30,6 +30,9 @@ public int? TemplatesCount { get; set; } + /// + /// Юнит фильтры Работы + /// public List? UnitFilters { get; set; } public JobAutoControlResponse? AutoControl { get; set; } diff --git a/PARR.API/Controllers/V1/JobController.cs b/PARR.API/Controllers/V1/JobController.cs index f38b0aa4..57413d9e 100644 --- a/PARR.API/Controllers/V1/JobController.cs +++ b/PARR.API/Controllers/V1/JobController.cs @@ -82,6 +82,7 @@ namespace PARR.API.Controllers.V1 var paginationFilter = _mapper.Map(paginationQuery); IQueryable query = _jobRepository.Get() + .AsNoTracking() .Include(t => t.AutoControl); @@ -152,9 +153,22 @@ namespace PARR.API.Controllers.V1 { var job = await _jobRepository.Get() .Include(t => t.Tnk) - .Include(t => t.Group).ThenInclude(t => t.GroupType) - .Include(t => t.Group).ThenInclude(t => t.AutoControl) - .Include(t => t.Group).ThenInclude(t => t.GroupingUnitField) + .Include(t => t.Group) + .ThenInclude(t => t.GroupType) + .Include(t => t.Group) + .ThenInclude(t => t.AutoControl) + .Include(t => t.Group) + .ThenInclude(t => t.GroupingUnitField) + //--- Фильтры в группе + .Include(t => t.Group) + .ThenInclude(t => t.UnitFilters) + .ThenInclude(t => t.FieldFilters) + .ThenInclude(t => t.UnitField) + .Include(t => t.Group) + .ThenInclude(t => t.UnitFilters) + .ThenInclude(t => t.RelationshipFilters) + .ThenInclude(t=>t.UnitField) + //--- .Include(t => t.UnitFilters) .ThenInclude(t => t.FieldFilters) .ThenInclude(t => t.UnitField) @@ -162,6 +176,7 @@ namespace PARR.API.Controllers.V1 .ThenInclude(t => t.RelationshipFilters) .ThenInclude(t => t.UnitField) .Include(t => t.AutoControl) + .AsNoTracking() .FirstOrDefaultAsync(t => t.Id == id); if (job == null) @@ -169,12 +184,9 @@ namespace PARR.API.Controllers.V1 var response = _mapper.Map(job); - response.TemplatesCount = await GetCountTemplatesAsync(id); //await templateService.Get().CountAsync(t => t.JobId == id); + response.TemplatesCount = await GetCountTemplatesAsync(id); response.MatchingStatus = await GetMatchingStatusAsync(id); - //var statistics = await GetStatisticsAsync(response.Id); - //BindStatistics(response, statistics); - return Ok(new Response(response, true)); } @@ -244,6 +256,16 @@ namespace PARR.API.Controllers.V1 .Include(t => t.UnitFilters) .ThenInclude(t => t.RelationshipFilters) .Include(t => t.AutoControl) + //--- Фильтры в группе + .Include(t => t.Group) + .ThenInclude(t => t.UnitFilters) + .ThenInclude(t => t.FieldFilters) + .ThenInclude(t => t.UnitField) + .Include(t => t.Group) + .ThenInclude(t => t.UnitFilters) + .ThenInclude(t => t.RelationshipFilters) + .ThenInclude(t => t.UnitField) + //--- .FirstOrDefaultAsync(t => t.Id == job.Id); var locationUri = _uriService.GetUri(ApiRoutes.Job.Get, ApiRoutes.Job.getParam, createdJob!.Id); @@ -365,6 +387,16 @@ namespace PARR.API.Controllers.V1 .Include(t => t.UnitFilters) .ThenInclude(t => t.RelationshipFilters) .Include(t => t.AutoControl) + //--- Фильтры в группе + .Include(t => t.Group) + .ThenInclude(t => t.UnitFilters) + .ThenInclude(t => t.FieldFilters) + .ThenInclude(t => t.UnitField) + .Include(t => t.Group) + .ThenInclude(t => t.UnitFilters) + .ThenInclude(t => t.RelationshipFilters) + .ThenInclude(t => t.UnitField) + //--- .FirstAsync(t => t.Id == orig.Id); var response = _mapper.Map(updatedJob); diff --git a/PARR.API/MappingProfiles/DomainToResponseProfile.cs b/PARR.API/MappingProfiles/DomainToResponseProfile.cs index 4d7c5b2f..fba83343 100644 --- a/PARR.API/MappingProfiles/DomainToResponseProfile.cs +++ b/PARR.API/MappingProfiles/DomainToResponseProfile.cs @@ -388,7 +388,8 @@ namespace PARR.API.MappingProfiles .ForMember(d => d.GroupType, o => o.MapFrom(s => s.GroupType)) .ForMember(d => d.GroupingUnitField, o => o.MapFrom(s => s.GroupingUnitField)) .ForMember(d => d.IsWorkGroupTimezone, o => o.MapFrom(s => s.IsResponseAreaTimezone)) - .ForMember(d => d.AutoControl, o => o.MapFrom(s => s.AutoControl)); + .ForMember(d => d.AutoControl, o => o.MapFrom(s => s.AutoControl)) + .ForMember(d => d.UnitFilters, o => o.MapFrom(s => s.UnitFilters)); CreateMap() .ForMember(d => d.Name, o => o.MapFrom(s => s.GroupName)) @@ -399,8 +400,7 @@ namespace PARR.API.MappingProfiles CreateMap() .ForMember(d => d.ScheduleExcludeType, o => o.MapFrom(s => s.ScheduleExcludeType)) - .ForMember(d => d.ScheduleExcludeTypeCalendar, o => o.MapFrom(s => s.ScheduleExcludeTypeCalendar)) - .ForMember(d => d.UnitFilters, o => o.MapFrom(s => s.UnitFilters)); + .ForMember(d => d.ScheduleExcludeTypeCalendar, o => o.MapFrom(s => s.ScheduleExcludeTypeCalendar)); //.ForMember(d => d.DistributionConfig, o => o.MapFrom(s => s.DistributionConfig)); //------- unit filters diff --git a/PARR.API/Validators/JobRequestValidator.cs b/PARR.API/Validators/JobRequestValidator.cs index c54f5c84..3427d2ae 100644 --- a/PARR.API/Validators/JobRequestValidator.cs +++ b/PARR.API/Validators/JobRequestValidator.cs @@ -12,7 +12,6 @@ namespace PARR.API.Validators private readonly ITnkRepository _tnkRepository; private readonly IJobGroupRepository _jobGroupRepository; private readonly IUnitFieldRepository _unitFieldRepository; - //private JobGroup? jobGroup; public JobRequestValidator( ITnkRepository tnkRepository, @@ -39,15 +38,28 @@ namespace PARR.API.Validators .MustAsync(async (entity, value, c) => await IsGroupExist(entity)) .WithMessage("Указан несуществующий Id группы работ({PropertyValue})"); - //RuleForEach(t => t.UnitFilters) - // .NotNull().NotEmpty(); + #region UnitFiters + + When(t => t.UnitFilters != null && t.UnitFilters.Any(), () => + { + RuleFor(t => t.UnitFilters) + .MustAsync(async (entity, value, c) => + { + // Проверяем флаг в БД: разрешены ли фильтры для этой группы + return await jobGroupRepository.Get() + .AnyAsync(t => t.Id == entity.GroupId && t.GroupType!.IsAllowJobUnitFilter == true, c); + }) + .WithMessage("Дополнительные фильтры запрещены") + // Stop делает так, что если флаг равен false, FluentValidation остановится + // и НЕ пойдет выполнять Include базового валидатора (сэкономим запрос к БД) + .DependentRules(() => + { + // Этот валидатор сработает ПОСЛЕ успешной проверки флага IsAllowJobUnitFilter + Include(unitFilterValidator); + }); + }); - // Валидация фильтров (они могут быть пустые, мы так договорились) - Include(unitFilterValidator); - - //RuleForEach(t => t.UnitFilters) - // .MustAsync(async (entity, value, c) => await IsUnitFiltersCorrect(entity)) - // .WithMessage("Неверно заданы параметры фильтров."); + #endregion RuleFor(t => t.Relationships) .MustAsync(async (entity, value, c) => @@ -99,64 +111,6 @@ namespace PARR.API.Validators .WithMessage("Некорректные параметры автоконтроля"); } - //private async Task IsUnitFiltersCorrect(JobRequest entity) - //{ - // // 1. Проверим маски, они не должны быть пустыми - // foreach (var unitFilter in entity.UnitFilters) - // { - // if (string.IsNullOrEmpty(unitFilter.UnitFilterMask)) - // return false; - // } - - // // 2. Собираем все уникальные FieldId из всех фильтров в памяти - // var requestedFieldIds = entity.UnitFilters - // .SelectMany(uf => - // (uf.FieldFilters ?? Enumerable.Empty()).Select(ff => ff.FieldId) - // .Concat((uf.RelationshipFilters ?? Enumerable.Empty()).Select(rf => rf.FieldId))) - // .Distinct() - // .ToList(); - - // // Если фильтров с ID нет, то и проверять в БД нечего - // if (!requestedFieldIds.Any()) - // return true; - - // // 3. Выполняем ОДИН запрос к БД. - // var existingFieldIds = await _unitFieldRepository.Get() - // .Where(field => requestedFieldIds.Contains(field.Id)) - // .Select(field => field.Id) - // .Distinct() - // .ToListAsync(); - - // // 4. Если количество найденных ID в БД меньше, чем мы запрашивали, - // // значит какого-то fieldId не существует - // return requestedFieldIds.Count == existingFieldIds.Count; - - // #region Old - // //foreach (var unitFilter in entity.UnitFilters) - // //{ - // // if (string.IsNullOrEmpty(unitFilter.UnitFilterMask)) - // // return false; - - // // if (unitFilter.FieldFilters != null && unitFilter.FieldFilters.Any()) - // // foreach (var fieldFilter in unitFilter.FieldFilters) - // // { - // // var fieldId = fieldFilter.FieldId; - // // if (await _unitFieldRepository.GetAsync(fieldId) == null) - // // return false; - // // } - - // // if (unitFilter.RelationshipFilters != null && unitFilter.RelationshipFilters.Any()) - // // foreach (var relationshipFilter in unitFilter.RelationshipFilters!) - // // { - // // var fieldId = relationshipFilter.FieldId; - // // if (await _unitFieldRepository.GetAsync(fieldId) == null) - // // return false; - // // } - // //} - - // //return true; - // #endregion - //} private async Task IsGroupExist(JobRequest entity) {