diff --git a/PARR.API/Contracts/V1/Requests/JobRequest.cs b/PARR.API/Contracts/V1/Requests/JobRequest.cs index 18dc4c92..77730073 100644 --- a/PARR.API/Contracts/V1/Requests/JobRequest.cs +++ b/PARR.API/Contracts/V1/Requests/JobRequest.cs @@ -1,79 +1,91 @@ namespace PARR.API.Contracts.V1.Requests { - public class JobRequest + public record JobRequest { - public required Guid TnkId { get; set; } + public required Guid TnkId { get; init; } - public required Guid GroupId { get; set; } + public required Guid GroupId { get; init; } - public required string Name { get; set; } + public required string Name { get; init; } - public int? MinValueRelationships { get; set; } + //public int? MinValueRelationships { get; init; } - public int? MaxValueRelationships { get; set; } + //public int? MaxValueRelationships { get; init; } - public bool? IsParentRelationships { get; set; } + //public bool? IsParentRelationships { get; init; } - public required string TemplateNameMask { get; set; } + public JobRelationships? Relationships { get; init; } - public required string WorkGroupMask { get; set; } + public required string TemplateNameMask { get; init; } - public required string WorkName { get; set; } + public required string WorkGroupMask { get; init; } - public required string ResponseAreaMask { get; set; } + public required string WorkName { get; init; } - #region AutoControl + public required string ResponseAreaMask { get; init; } - public bool IsEnableAutoControl { get; set; } + //#region AutoControl - public bool InitUsedTemplateState { get; set; } + //public bool IsEnableAutoControl { get; set; } - public bool InitUsedScheduleState { get; set; } + //public bool InitUsedTemplateState { get; set; } - #endregion + //public bool InitUsedScheduleState { get; set; } - public required List UnitFilters { get; set; } + //#endregion + + public JobAutoControlRequest? AutoControl { get; init; } + + public required List UnitFilters { get; init; } } + public record JobRelationships + { + public int MinValueRelationships { get; init; } - public class UnitFilterRequest + public int MaxValueRelationships { get; init; } + + public bool IsParentRelationships { get; init; } + } + + public record UnitFilterRequest { ///// ///// Id = null в методе Create, в Update обязателен ///// //public Guid? Id { get; set; } - public required string UnitFilterMask { get; set; } + public required string UnitFilterMask { get; init; } - public List? FieldFilters { get; set; } + public List? FieldFilters { get; init; } - public List? RelationshipFilters { get; set; } + public List? RelationshipFilters { get; init; } } - public class FieldFilterRequest + public record FieldFilterRequest { - public Guid FieldId { get; set; } + public Guid FieldId { get; init; } - public string? ValueMask { get; set; } + public string? ValueMask { get; init; } - public bool IsInverse { get; set; } = false; + public bool IsInverse { get; init; } = false; } - public class RelationshipFilterRequest + public record RelationshipFilterRequest { - public Guid FieldId { get; set; } + public Guid FieldId { get; init; } - public bool? IsParent { get; set; } + public bool? IsParent { get; init; } - public string? ValueMask { get; set; } + public string? ValueMask { get; init; } - public bool? IsFullMatch { get; set; } + public bool? IsFullMatch { get; init; } - public bool? IsInverse { get; set; } + public bool? IsInverse { get; init; } } } \ No newline at end of file diff --git a/PARR.API/Contracts/V1/Responses/JobGroupResponse.cs b/PARR.API/Contracts/V1/Responses/JobGroupResponse.cs index 449c40a4..af15d2cf 100644 --- a/PARR.API/Contracts/V1/Responses/JobGroupResponse.cs +++ b/PARR.API/Contracts/V1/Responses/JobGroupResponse.cs @@ -40,6 +40,8 @@ /// public bool IsAutoDistributionEnabled { get; set; } + public JobGroupAutoControlResponse? AutoControl { get; set; } + // public bool IsAgent { get; set; } // public string? AgentName { get; set; } @@ -68,8 +70,6 @@ /// public ScheduleExcludeTypeCalendarResponse? ScheduleExcludeTypeCalendar { get; set; } - public JobGroupAutoControlResponse? AutoControl { get; set; } - ///// ///// Настройки автораспределения ///// diff --git a/PARR.API/Controllers/V1/JobController.cs b/PARR.API/Controllers/V1/JobController.cs index fd2903b6..279e2dbc 100644 --- a/PARR.API/Controllers/V1/JobController.cs +++ b/PARR.API/Controllers/V1/JobController.cs @@ -35,46 +35,43 @@ namespace PARR.API.Controllers.V1 [Authorize(Roles = ParrRoles.Administrator.Role)] public class JobController : BaseApiController { - private readonly ILogger logger; - private readonly IMapper mapper; - private readonly IUriService uriService; - private readonly IJobRepository jobService; - private readonly ITemplateRepository templateService; - //private readonly IValidator jobValidator; - private readonly IRabbitService mqService; - private readonly MqSettings mqSettings; - private readonly IClientService clientService; - private readonly IJobAutoControlRepository jobAutoControlService; - private readonly IMatchingStatusService matchingStatusService; + private readonly ILogger _logger; + private readonly IMapper _mapper; + private readonly IUriService _uriService; + private readonly IJobRepository _jobRepository; + private readonly ITemplateRepository _templateRepository; + private readonly IRabbitService _mqService; + private readonly MqSettings _mqSettings; + private readonly IClientService _clientService; + private readonly IJobAutoControlRepository _jobAutoControlRepository; + private readonly IMatchingStatusService _matchingStatusService; public JobController( ILogger logger, IMapper mapper, IUriService uriService, - IJobRepository jobService, - ITemplateRepository templateService, - IJobGroupRepository jobGroupService, - //IValidator jobValidator, - IUnitFilterService unitFilterService, - IUnitRepository unitService, + IJobRepository jobRepository, + ITemplateRepository templateRepository, + IJobGroupRepository jobGroupRepository, + IUnitFilterService unitFilterRepository, + IUnitRepository unitRepository, IRabbitService mqService, MqSettings mqSettings, IClientService clientService, - IJobAutoControlRepository jobAutoControlService, + IJobAutoControlRepository jobAutoControlRepository, IMatchingStatusService matchingStatusService ) { - this.logger = logger; - this.mapper = mapper; - this.uriService = uriService; - this.jobService = jobService; - this.templateService = templateService; - //this.jobValidator = jobValidator; - this.mqService = mqService; - this.mqSettings = mqSettings; - this.clientService = clientService; - this.jobAutoControlService = jobAutoControlService; - this.matchingStatusService = matchingStatusService; + _logger = logger; + _mapper = mapper; + _uriService = uriService; + _jobRepository = jobRepository; + _templateRepository = templateRepository; + _mqService = mqService; + _mqSettings = mqSettings; + _clientService = clientService; + _jobAutoControlRepository = jobAutoControlRepository; + _matchingStatusService = matchingStatusService; } /// @@ -84,18 +81,20 @@ namespace PARR.API.Controllers.V1 [HttpGet(ApiRoutes.Job.GetAll)] public async Task GetAll([FromQuery] PaginationQuery paginationQuery, [FromQuery] JobQuery filter) { - var paginationFilter = mapper.Map(paginationQuery); + var paginationFilter = _mapper.Map(paginationQuery); + + IQueryable query = _jobRepository.Get() + .Include(t => t.AutoControl); - IQueryable query = jobService.Get().Include(t => t.AutoControl); query = query.OrderBy(t => t.Name); if (!string.IsNullOrEmpty(filter.Name)) { //query = query.Where(t => t.Name.ToLower().Contains(filter.Name.ToLower())); - query = query.Where(t=>EF.Functions.Like(t.Name.ToLower(), SqlHelpers.RegexToLike(filter.Name))); + query = query.Where(t => EF.Functions.Like(t.Name.ToLower(), SqlHelpers.RegexToLike(filter.Name))); } - + if (filter.GroupId.HasValue) query = query.Where(t => t.GroupId == filter.GroupId.Value); @@ -107,9 +106,10 @@ namespace PARR.API.Controllers.V1 if (filter.IsFull) { query = query - .Include(t => t.Tnk) - .Include(t => t.Group).ThenInclude(t => t.GroupType) - .Include(t => t.Group).ThenInclude(t => t.GroupingUnitField); + .Include(t => t.Tnk) + .Include(t => t.Group).ThenInclude(t => t.GroupType) + .Include(t => t.Group).ThenInclude(t => t.GroupingUnitField) + .Include(t => t.Group).ThenInclude(t => t.AutoControl); query = query .Include(t => t.UnitFilters) @@ -120,12 +120,12 @@ namespace PARR.API.Controllers.V1 .ThenInclude(t => t.UnitField); } - var jobs = await jobService.GetPage(query, paginationFilter).ToListAsync(); + var jobs = await _jobRepository.GetPage(query, paginationFilter).ToListAsync(); if (!jobs.Any()) return NoContent(); - var response = mapper.Map>(jobs);//TODO Migration to job + var response = _mapper.Map>(jobs);//TODO Migration to job foreach (var jobResponse in response) { @@ -152,9 +152,10 @@ namespace PARR.API.Controllers.V1 [HttpGet(ApiRoutes.Job.Get)] public async Task GetById([FromRoute] Guid id) { - var job = await jobService.Get() + 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.UnitFilters) .ThenInclude(t => t.FieldFilters) @@ -168,7 +169,7 @@ namespace PARR.API.Controllers.V1 if (job == null) return NotFound(); - var response = mapper.Map(job); + var response = _mapper.Map(job); response.TemplatesCount = await GetCountTemplatesAsync(id); //await templateService.Get().CountAsync(t => t.JobId == id); response.MatchingStatus = await GetMatchingStatusAsync(id); @@ -188,51 +189,56 @@ namespace PARR.API.Controllers.V1 [HttpPost(ApiRoutes.Job.Create)] public async Task Create([FromBody] JobRequest request) { - //#region Валидация - //var jobValidateResult = await jobValidator.ValidateAsync(request);//Валидация параметров самого задания + #region Проверка существования работы с такими же настройками связей параметрами - //if (!jobValidateResult.IsValid) - // return BadRequest(new Response(jobValidateResult.Errors)); - //#endregion + if (request.Relationships != null) + { + //todo: не сильно правильный запрос, в нем проверяем полное совпадение, но не проверяем пересечения + var isExistTheSameLinks = await _jobRepository.Get() + //.Include(t => t.Group).ThenInclude(t => t.GroupType) + .AsNoTracking() + .CountAsync(t => + //t.Group!.GroupType!.Code == JobGroupTypesEnum.Umbrella + //(t.Group!.GroupType!.Code == JobGroupTypesEnum.Umbrella || t.Group!.GroupType!.Code == JobGroupTypesEnum.Group) && + t.Group!.GroupType!.IsRelationshipsAllowed && + t.GroupId == request.GroupId && + (t.MinValueRelationships == request.Relationships.MinValueRelationships || + t.MaxValueRelationships == request.Relationships.MaxValueRelationships) + ); - //TODO: !!!!!!! проверить, если тип ЗОНТИК или ГРУППИРОВКА, то обязательно должны быть заполнены поля min max - - #region Проверка существования работы с такими же параметрами - - var isExistTheSameLinks = await jobService.Get() - .Include(t => t.Group).ThenInclude(t => t.GroupType) - .CountAsync(t => - //t.Group!.GroupType!.Code == JobGroupTypesEnum.Umbrella - (t.Group!.GroupType!.Code == JobGroupTypesEnum.Umbrella || t.Group!.GroupType!.Code == JobGroupTypesEnum.Group) && - 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})" } })); + if (isExistTheSameLinks > 0) + return BadRequest(new Response(false, new List { new ErrorModel { + FieldName = nameof(request.Name), + Message = $"Работа с указанным диапазоном связей пересекается с уже имеющейся в базе данных({request.Relationships.MinValueRelationships}-{request.Relationships.MaxValueRelationships})" + } })); + } #endregion - var job = mapper.Map(request); + var job = _mapper.Map(request); - job.AutoControl = new JobAutoControl + if (request.AutoControl != null) { - JobId = job.Id, - IsEnable = request.IsEnableAutoControl, - InitUsedScheduleState = request.InitUsedScheduleState, - InitUsedTemplateState = request.InitUsedTemplateState - }; + // разрешен автоконтроль или нет, проверил в валидаторе + job.AutoControl = new JobAutoControl + { + JobId = job.Id, + IsEnable = request.AutoControl.IsEnable, + InitUsedScheduleState = request.AutoControl.InitUsedScheduleState, + InitUsedTemplateState = request.AutoControl.InitUsedTemplateState + }; + } - if (!await jobService.CreateAsync(job) || !await jobService.CommitAsync()) + if (!await _jobRepository.CreateAsync(job) || !await _jobRepository.CommitAsync()) return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при созании задания на выполнение работ" } })); - logger.LogInformation($"Пользователь {User.Identity?.Name} добавил задание на выполнение работ: {job.Id}, {job.Name}, {job.WorkName}"); + _logger.LogInformation($"Пользователь {User.Identity?.Name} добавил задание на выполнение работ: {job.Id}, {job.Name}, {job.WorkName}"); - var createdJob = await jobService.Get() + var createdJob = 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.UnitFilters) .ThenInclude(t => t.FieldFilters) @@ -242,9 +248,9 @@ namespace PARR.API.Controllers.V1 .Include(t => t.AutoControl) .FirstOrDefaultAsync(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); + var response = _mapper.Map(createdJob); // так как мы только что создали Job, то у него нет шаблонов, смело ставим = 0 (ускоряем запрос) response.TemplatesCount = 0; response.MatchingStatus = await GetMatchingStatusAsync(response.Id); @@ -262,11 +268,7 @@ namespace PARR.API.Controllers.V1 [HttpPut(ApiRoutes.Job.Update)] public async Task Update([FromRoute] Guid id, [FromBody] JobRequest request) { - //var resultValidate = await jobValidator.ValidateAsync(request); - //if (!resultValidate.IsValid) - // return BadRequest(new Response(resultValidate.Errors)); - - var orig = await jobService.Get() + var orig = await _jobRepository.Get() .Include(t => t.Tnk) .Include(t => t.Group).ThenInclude(t => t.GroupType) .Include(t => t.Group).ThenInclude(t => t.GroupingUnitField) @@ -287,77 +289,77 @@ namespace PARR.API.Controllers.V1 //TODO: ВОТ ЭТО ВООБЩЕ МЫ БУДЕМ ПРОВЕРЯТЬ, АААА???? - Проверка существования работы с такими же параметрами - //TODO: !!!!!!! проверить, если тип ЗОНТИК или ГРУППИРОВКА, то обязательно должны быть заполнены поля min max - #region обновление полей задания на работу orig.Name = request.Name.Trim(); orig.WorkName = request.WorkName.Trim(); - orig.MinValueRelationships = request.MinValueRelationships; - orig.MaxValueRelationships = request.MaxValueRelationships; - orig.IsParentRelationships = request.IsParentRelationships; + orig.MinValueRelationships = request.Relationships?.MinValueRelationships; + orig.MaxValueRelationships = request.Relationships?.MaxValueRelationships; + orig.IsParentRelationships = request.Relationships?.IsParentRelationships; orig.TemplateNameMask = request.TemplateNameMask.Trim(); orig.WorkGroupMask = request.WorkGroupMask.Trim(); orig.TnkId = request.TnkId; orig.GroupId = request.GroupId; orig.ResponseAreaMask = request.ResponseAreaMask.Trim(); - if (orig.AutoControl != null) + #region Настройки автоконтроля + + // валидатор проверяет корректность + if (request.AutoControl != null) { - orig.AutoControl.IsEnable = request.IsEnableAutoControl; - orig.AutoControl.InitUsedScheduleState = request.InitUsedScheduleState; - orig.AutoControl.InitUsedTemplateState = request.InitUsedTemplateState; - } - else - { - // автоконтрол не загружен, проверяем есть ли он в бд, если нет, то создадим - var existAutoControl = await jobAutoControlService.Get().FirstOrDefaultAsync(t => t.JobId == id); - if (existAutoControl != null) + // Создаем новую запись или обновляем существующую + if (orig.AutoControl != null) { - logger.LogError($"При обновлении job {id}, не загрузась связь с JobAutoControl, но она есть. Не стал обновлять Job, вернул ошибку."); - return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при изменении задания на выполнение работ." } })); + orig.AutoControl.IsEnable = request.AutoControl.IsEnable; + orig.AutoControl.InitUsedScheduleState = request.AutoControl.InitUsedScheduleState; + orig.AutoControl.InitUsedTemplateState = request.AutoControl.InitUsedTemplateState; } else { - var autoControl = new JobAutoControl + orig.AutoControl = new JobAutoControl { JobId = id, - InitUsedScheduleState = request.InitUsedScheduleState, - InitUsedTemplateState = request.InitUsedTemplateState, - IsEnable = request.IsEnableAutoControl + InitUsedScheduleState = request.AutoControl.InitUsedScheduleState, + InitUsedTemplateState = request.AutoControl.InitUsedTemplateState, + IsEnable = request.AutoControl.IsEnable }; - orig.AutoControl = autoControl; - logger.LogWarning($"При обновлении job {id}, отсутствовала запись в таблице JobAutoControl, создал ее. {autoControl.ToJson()}"); } } + else + { + // Удаляем настройки, скорей всего автоконтролем управляет группа работ + orig.AutoControl = null; + } + #endregion orig.DateModified = DateTimeOffset.UtcNow; #endregion - var job = mapper.Map(request); + var job = _mapper.Map(request); job.Id = id;//На всякий. Пусть будет для чистоты UpdateUnitFilters(orig, job);//Обновление вложенных дочерних элементов-фильтров - if (!await jobService.CommitAsync()) + if (!await _jobRepository.CommitAsync()) return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при изменении задания на выполнение работ." } })); // если изменили маску, отправим задание на переименование связанных шаблонов if (isChangedTemplateNameMask) { - var mqResult = await SendRequestToUpdateTemplates(id); + var mqResult = await SendRequestToUpdateTemplates(orig); //todo: если ошибка. пользователя не предупреждаем... возможно ему это и не нужно знать...ну не переименуются шаблоны, может они переименуются позже... } - logger.LogInformation($"Пользователь {User.Identity?.Name} обновил задание на выполнение работ: {orig.Id}," + + _logger.LogInformation($"Пользователь {User.Identity?.Name} обновил задание на выполнение работ: {orig.Id}," + $" {orig.Name}, {orig.WorkName}, {orig.MinValueRelationships}, {orig.MaxValueRelationships}," + $" {orig.TemplateNameMask}, {orig.TnkId}, {nameof(orig.GroupId)}"); - var updatedJob = await jobService.Get() + var updatedJob = 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.UnitFilters) .ThenInclude(t => t.FieldFilters) @@ -367,7 +369,7 @@ namespace PARR.API.Controllers.V1 .Include(t => t.AutoControl) .FirstAsync(t => t.Id == orig.Id); - var response = mapper.Map(updatedJob); + var response = _mapper.Map(updatedJob); response.TemplatesCount = await GetCountTemplatesAsync(id); //await templateService.Get().CountAsync(t => t.JobId == id); response.MatchingStatus = await GetMatchingStatusAsync(id); @@ -564,7 +566,7 @@ namespace PARR.API.Controllers.V1 [HttpDelete(ApiRoutes.Job.Delete)] public async Task Delete([FromRoute] Guid id) { - var job = await jobService.Get().Include(t => t.Tnk) + var job = await _jobRepository.Get().Include(t => t.Tnk) .FirstOrDefaultAsync(t => t.Id == id); if (job == null) @@ -572,19 +574,19 @@ namespace PARR.API.Controllers.V1 Message = $"Ошибка при удалении задания на выполнение работ. Не найдено задание на выполнение работ Id: {id}" } })); - var templateCount = await templateService.Get().CountAsync(t => t.JobId == id); + var templateCount = await _templateRepository.Get().CountAsync(t => t.JobId == id); if (templateCount > 0) return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при удалении задания на выполнение работ. С данным заданием связаны шаблоны: {templateCount} шт." } })); - if (!jobService.Delete(job) || !await jobService.CommitAsync()) + if (!_jobRepository.Delete(job) || !await _jobRepository.CommitAsync()) return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при удалении задания на выполнение работ" } })); - logger.LogInformation($"Пользователь {User.Identity?.Name} удалил задание на выполнение работ: {job.Id},{job.Name}," + + _logger.LogInformation($"Пользователь {User.Identity?.Name} удалил задание на выполнение работ: {job.Id},{job.Name}," + $" {job.WorkName}, {job.MinValueRelationships}, {job.MaxValueRelationships}," + $" {job.TemplateNameMask}, {job.TnkId}, {job.GroupId}"); @@ -599,7 +601,7 @@ namespace PARR.API.Controllers.V1 /// private async Task GetStatisticsAsync(Guid jobId) { - var statResult = await jobService.Get() + var statResult = await _jobRepository.Get() .Include(t => t.Templates) .ThenInclude(t => t.RobotConfigurations) .Where(x => x.Id == jobId) @@ -629,35 +631,53 @@ namespace PARR.API.Controllers.V1 } - private async Task SendRequestToUpdateTemplates(Guid jobId) + private async Task SendRequestToUpdateTemplates(Job job) { + if (job.Group?.GroupType == null) + throw new ArgumentException("Не хватает include для job.Group.GroupType", nameof(job.Group.GroupType)); + + Guid Id = default; + SyncTaskEntityTypeEnum EntityType = default; + + // Смотрим кто управляет автоконтролем, и какой объект можно синхронизировать + if (job.Group.GroupType.IsJobGroupAutoControl) + { + // Управляет JobGroup + Id = job.GroupId; + EntityType = SyncTaskEntityTypeEnum.JobGroup; + + } + else + { + // Управляет Job + Id = job.Id; + EntityType = SyncTaskEntityTypeEnum.Job; + } + var request = new TemplateMatcherMq { - Id = jobId, - EntityType = SyncTaskEntityTypeEnum.Job, + Id = Id, + EntityType = EntityType, Action = TemplateMatcherActionEnum.Update, Initiator = new HistoryInitiator { - InitiatorIp = clientService.GetClientIp()?.ToString(), + InitiatorIp = _clientService.GetClientIp()?.ToString(), InitiatorParrComponentId = ParrComponentsEnum.Api, InitiatorComment = $"В GUI изменено имя шаблона, при сохранении Job отправлен запрос на обновление связанных шаблонов" } }; - //var msg = JsonSerializer.Serialize(request); + var result = await _mqService.SendAsync(_mqSettings.TemplatesMatcher, new List { request }); - //var result = await mqService.SendAsync(mqSettings.TemplatesMatcher, new[] { msg }); - var result = await mqService.SendAsync(mqSettings.TemplatesMatcher, new List { request }); - - logger.LogDebug("Получен код отпрвки: {IsSuccess}", result.IsSuccess); + _logger.LogDebug("Получен код отпрвки: {IsSuccess}", result.IsSuccess); if (!result.IsSuccess) { - logger.LogError($"Ошибка при отправке запроса в очередь на обновление связанных шаблонов, после обновления маски шаблона. {request.ToJson()}"); + _logger.LogError($"Ошибка при отправке запроса в очередь на обновление связанных шаблонов, после обновления маски шаблона. {request.ToJson()}"); return false; } - logger.LogInformation($"После изменения маски шаблона в jobId: {jobId}, отправлен запрос в очередь на переименование связанных шаблонов: {request.ToJson()}"); + _logger.LogInformation($"После изменения маски шаблона в jobId: {job.Id}, отправлен запрос в очередь на переименование связанных шаблонов: {request.ToJson()}"); return true; } @@ -671,7 +691,7 @@ namespace PARR.API.Controllers.V1 private async Task GetCountTemplatesAsync(Guid jobId) { // Получаем только шаблоны в статусе used - return await templateService.Get().CountAsync(t => t.JobId == jobId && t.StatusTypeId == TemplateStatusTypeEnum.Used); + return await _templateRepository.Get().CountAsync(t => t.JobId == jobId && t.StatusTypeId == TemplateStatusTypeEnum.Used); } /// @@ -681,9 +701,9 @@ namespace PARR.API.Controllers.V1 /// private async Task GetMatchingStatusAsync(Guid jobId) { - var statusMatching = await matchingStatusService.GetStatusAsync(jobId, SyncTaskEntityTypeEnum.Job); + var statusMatching = await _matchingStatusService.GetStatusAsync(jobId, SyncTaskEntityTypeEnum.Job); - return mapper.Map(statusMatching); + return _mapper.Map(statusMatching); } } diff --git a/PARR.API/Controllers/V1/JobGroupController.cs b/PARR.API/Controllers/V1/JobGroupController.cs index c4aac721..30c5db67 100644 --- a/PARR.API/Controllers/V1/JobGroupController.cs +++ b/PARR.API/Controllers/V1/JobGroupController.cs @@ -33,9 +33,9 @@ namespace PARR.API.Controllers.V1 [Authorize(Roles = ParrRoles.Administrator.Role)] public class JobGroupController : BaseApiController { - private readonly ILogger logger; - private readonly IMapper mapper; - private readonly IUriService uriService; + private readonly ILogger _logger; + private readonly IMapper _mapper; + private readonly IUriService _uriService; private readonly IJobGroupRepository _groupRepository; private readonly IJobRepository _jobRepository; private readonly IEsppSchTypeConfigRepository _esppConfigRepository; @@ -43,8 +43,8 @@ namespace PARR.API.Controllers.V1 private readonly IMatchingStatusService _matchingStatusRepository; private readonly IScheduleResponseAreaTimeOffsetRepository _scheduleResponseAreaTimeOffsetRepository; private readonly IJobAutoControlRepository _jobAutoControlRepository; - private readonly IRabbitService mqService; - private readonly MqSettings mqSettings; + private readonly IRabbitService _mqService; + private readonly MqSettings _mqSettings; public JobGroupController( ILogger logger, @@ -61,9 +61,9 @@ namespace PARR.API.Controllers.V1 MqSettings mqSettings ) { - this.logger = logger; - this.mapper = mapper; - this.uriService = uriService; + this._logger = logger; + this._mapper = mapper; + this._uriService = uriService; _groupRepository = groupRepository; _jobRepository = jobRepository; _esppConfigRepository = esppConfigRepository; @@ -71,8 +71,8 @@ namespace PARR.API.Controllers.V1 _matchingStatusRepository = matchingStatusRepository; _scheduleResponseAreaTimeOffsetRepository = scheduleResponseAreaTimeOffsetRepository; _jobAutoControlRepository = jobAutoControlRepository; - this.mqService = mqService; - this.mqSettings = mqSettings; + this._mqService = mqService; + this._mqSettings = mqSettings; } @@ -83,7 +83,7 @@ namespace PARR.API.Controllers.V1 [HttpGet(ApiRoutes.JobGroup.GetAll)] public async Task GetAll([FromQuery] PaginationQuery paginationQuery, [FromQuery] JobGroupQuery filter) { - var paginationFilter = mapper.Map(paginationQuery); + var paginationFilter = _mapper.Map(paginationQuery); IQueryable query = _groupRepository.Get() .Include(t => t.GroupType) @@ -110,7 +110,7 @@ namespace PARR.API.Controllers.V1 if (!jobGroups.Any()) return NoContent(); - var response = mapper.Map>(jobGroups); + var response = _mapper.Map>(jobGroups); if (filter.IsFull) foreach (var jobGroupResponse in response) @@ -143,7 +143,7 @@ namespace PARR.API.Controllers.V1 if (jobGroup == null) return NotFound(); - var response = mapper.Map(jobGroup); + var response = _mapper.Map(jobGroup); await AppendMissingDataAsync(response); return Ok(new Response(response, true)); @@ -188,7 +188,7 @@ namespace PARR.API.Controllers.V1 // на всякий проверим, но вообще это проверяется в валидаторе if (request.DistributionConfig == null) { - logger.LogError("Ошибка при создании группы работ '{name}', отсутствуют настройки автораспределения", request.Name); + _logger.LogError("Ошибка при создании группы работ '{name}', отсутствуют настройки автораспределения", request.Name); return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при создании группы заданий на выполнение работ" } })); } @@ -226,7 +226,7 @@ namespace PARR.API.Controllers.V1 if (!await _groupRepository.CreateAsync(jobGroup) || !await _groupRepository.CommitAsync()) return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при создании группы заданий на выполнение работ" } })); - logger.LogInformation($"Пользователь {User.Identity?.Name} добавил группу заданий на выполнение работ: {jobGroup.Id}, {jobGroup.GroupName}, {jobGroup.ShortDescription}"); + _logger.LogInformation($"Пользователь {User.Identity?.Name} добавил группу заданий на выполнение работ: {jobGroup.Id}, {jobGroup.GroupName}, {jobGroup.ShortDescription}"); var createdJobGroup = await _groupRepository.Get().Include(t => t.Jobs).ThenInclude(t => t.Tnk) @@ -238,9 +238,9 @@ namespace PARR.API.Controllers.V1 .Include(t => t.AutoControl) .FirstAsync(t => t.Id == jobGroup.Id); - var locationUri = uriService.GetUri(ApiRoutes.JobGroup.Get, ApiRoutes.JobGroup.getParam, createdJobGroup.Id); + var locationUri = _uriService.GetUri(ApiRoutes.JobGroup.Get, ApiRoutes.JobGroup.getParam, createdJobGroup.Id); - var response = mapper.Map(createdJobGroup); + var response = _mapper.Map(createdJobGroup); await AppendMissingDataAsync(response); return Created(locationUri, new Response(response, true)); @@ -297,7 +297,7 @@ namespace PARR.API.Controllers.V1 { if (request.DistributionConfig == null) { - logger.LogError("Ошибка при изменении группы работ '{name}', отсутствуют настройки автораспределения", request.Name); + _logger.LogError("Ошибка при изменении группы работ '{name}', отсутствуют настройки автораспределения", request.Name); return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при изменении группы заданий на выполнение работ" } })); } @@ -378,7 +378,7 @@ namespace PARR.API.Controllers.V1 if (!await _groupRepository.CommitAsync()) return BadRequest(new Response(false, new List { new ErrorModel { Message = "Ошибка при изменении группы заданий на выполнение работ." } })); - logger.LogInformation($"Пользователь {User.Identity?.Name} обновил группу заданий на выполнение работ: {orig.Id}," + + _logger.LogInformation($"Пользователь {User.Identity?.Name} обновил группу заданий на выполнение работ: {orig.Id}," + $" {orig.GroupName}, {orig.ShortDescription}, {orig.FullDescription}," + $" {orig.Solution}, {orig.TemplateDuration}, {orig.ReferenceDate}, {orig.IsAutoDistributionEnabled}" + $", {orig.IsAgent}, {orig.AgentName}, {orig.AgentTimeOutSec}, {orig.AgentScript}"); @@ -393,12 +393,12 @@ namespace PARR.API.Controllers.V1 JobGroupId = id, Initiator = new HistoryInitiator { InitiatorComment = "Изменилось расписание группы работ в ГУИ, отправлен запрос на перерасчет nextRun", InitiatorParrComponentId = ParrComponentsEnum.Api } }; - var sendResult = await mqService.SendAsync(mqSettings.NextRun, new List { requestToMq }); + var sendResult = await _mqService.SendAsync(_mqSettings.NextRun, new List { requestToMq }); if (sendResult.IsSuccess) - logger.LogInformation("Задание на перерасчет NextRun успешно отправлено в очередь MQ {queueName}", mqSettings.NextRun.QueueName); + _logger.LogInformation("Задание на перерасчет NextRun успешно отправлено в очередь MQ {queueName}", _mqSettings.NextRun.QueueName); else - logger.LogError("Ошибка при отправке задания на перерасчет NextRun в очередь MQ {queueName}", mqSettings.NextRun.QueueName); + _logger.LogError("Ошибка при отправке задания на перерасчет NextRun в очередь MQ {queueName}", _mqSettings.NextRun.QueueName); } #endregion @@ -414,7 +414,7 @@ namespace PARR.API.Controllers.V1 .Include(t => t.AutoControl) .FirstAsync(t => t.Id == orig.Id); - var response = mapper.Map(updatedJobGroup); + var response = _mapper.Map(updatedJobGroup); await AppendMissingDataAsync(response); return Ok(new Response(response, true)); @@ -448,7 +448,7 @@ namespace PARR.API.Controllers.V1 Message = $"Ошибка при удалении группы заданий на выполнение работ" } })); - logger.LogInformation($"Пользователь {User.Identity?.Name} удалил группу заданий на выполнение работ: {jobGroup.Id},{jobGroup.GroupName}," + + _logger.LogInformation($"Пользователь {User.Identity?.Name} удалил группу заданий на выполнение работ: {jobGroup.Id},{jobGroup.GroupName}," + $" {jobGroup.ShortDescription}, {jobGroup.FullDescription}," + $" {jobGroup.Solution}, {jobGroup.TemplateDuration}, {jobGroup.ReferenceDate}," + $" {jobGroup.IsAutoDistributionEnabled}, {jobGroup.IsAgent}, {jobGroup.AgentName}," + @@ -471,6 +471,8 @@ namespace PARR.API.Controllers.V1 if (!jobAutoControlsToRemove.Any()) return; + _logger.LogInformation("У группы работ {JobGroupId}, у связанных работ {JobsCount} шт. удалены настройки автоконтроля, так как автоконтролем управляем группа работ.", jobGroupId, jobAutoControlsToRemove.Count); + _jobAutoControlRepository.RemoveRange(jobAutoControlsToRemove); } @@ -540,7 +542,7 @@ namespace PARR.API.Controllers.V1 if (schedule == null) { - logger.LogError($"Не смог замапить расписание, так как оно null. JobGroupId: {jobGroupResponse.Id}"); + _logger.LogError($"Не смог замапить расписание, так как оно null. JobGroupId: {jobGroupResponse.Id}"); return; } @@ -548,8 +550,8 @@ namespace PARR.API.Controllers.V1 { //Timezone = settingsFromDb.ScheduleTimezone, Timezone = _scheduleResponseAreaTimeOffsetRepository.GetDefault.EsppValue, - TypeSchedule = mapper.Map(schedule.TypeSchedule), - Values = mapper.Map>(schedule.Values).OrderBy(t => t.Order).ToList() + TypeSchedule = _mapper.Map(schedule.TypeSchedule), + Values = _mapper.Map>(schedule.Values).OrderBy(t => t.Order).ToList() }; jobGroupResponse.Schedule = scheduleResponse; @@ -580,7 +582,7 @@ namespace PARR.API.Controllers.V1 else { // Это не сгруппированный тип, обнуляем IsGroupByResponsible - logger.LogInformation("При сохраненни JobGroup, был передан IsGroupByResponsible: {IsGroupByResponsible}, но при этом, тип группы не сгруппированный, а GroupTypeId: {GroupTypeId}, обнулил IsGroupByResponsible", + _logger.LogInformation("При сохраненни JobGroup, был передан IsGroupByResponsible: {IsGroupByResponsible}, но при этом, тип группы не сгруппированный, а GroupTypeId: {GroupTypeId}, обнулил IsGroupByResponsible", request.IsGroupByResponsible, request.GroupTypeId); return null; } @@ -609,7 +611,7 @@ namespace PARR.API.Controllers.V1 else { // Это не сгруппированный тип, обнуляем GroupingUnitFieldId - logger.LogInformation("При сохраненни JobGroup, был передан GroupingUnitFieldId: {GroupingUnitFieldId}, но при этом, тип группы не сгруппированный, а GroupTypeId: {GroupTypeId}, обнулил GroupingUnitFieldId", + _logger.LogInformation("При сохраненни JobGroup, был передан GroupingUnitFieldId: {GroupingUnitFieldId}, но при этом, тип группы не сгруппированный, а GroupTypeId: {GroupTypeId}, обнулил GroupingUnitFieldId", request.GroupingUnitFieldId, request.GroupTypeId); return null; } @@ -625,7 +627,7 @@ namespace PARR.API.Controllers.V1 { var statusMatching = await _matchingStatusRepository.GetStatusAsync(jobGroupId, SyncTaskEntityTypeEnum.JobGroup); - return mapper.Map(statusMatching); + return _mapper.Map(statusMatching); } diff --git a/PARR.API/MappingProfiles/DomainToResponseProfile.cs b/PARR.API/MappingProfiles/DomainToResponseProfile.cs index 802340d3..b1e2b251 100644 --- a/PARR.API/MappingProfiles/DomainToResponseProfile.cs +++ b/PARR.API/MappingProfiles/DomainToResponseProfile.cs @@ -368,7 +368,8 @@ namespace PARR.API.MappingProfiles .Include() .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.IsWorkGroupTimezone, o => o.MapFrom(s => s.IsResponseAreaTimezone)) + .ForMember(d => d.AutoControl, o => o.MapFrom(s => s.AutoControl)); CreateMap() .ForMember(d => d.Name, o => o.MapFrom(s => s.GroupName)) @@ -379,8 +380,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.AutoControl, o => o.MapFrom(s => s.AutoControl)); + .ForMember(d => d.ScheduleExcludeTypeCalendar, o => o.MapFrom(s => s.ScheduleExcludeTypeCalendar)); //.ForMember(d => d.DistributionConfig, o => o.MapFrom(s => s.DistributionConfig)); CreateMap(); diff --git a/PARR.API/MappingProfiles/RequestToDomainProfile.cs b/PARR.API/MappingProfiles/RequestToDomainProfile.cs index 1278a3ed..4264fc5c 100644 --- a/PARR.API/MappingProfiles/RequestToDomainProfile.cs +++ b/PARR.API/MappingProfiles/RequestToDomainProfile.cs @@ -17,6 +17,9 @@ namespace PARR.API.MappingProfiles CreateMap() .ForMember(d => d.Id, o => o.MapFrom(s => Guid.NewGuid())) + .ForMember(d => d.IsParentRelationships, o => o.MapFrom(s => s.Relationships != null ? s.Relationships.IsParentRelationships : (bool?)null)) + .ForMember(d => d.MaxValueRelationships, o => o.MapFrom(s => s.Relationships != null ? s.Relationships.MaxValueRelationships : (int?)null)) + .ForMember(d => d.MinValueRelationships, o => o.MapFrom(s => s.Relationships != null ? s.Relationships.MinValueRelationships : (int?)null)) .ForMember(d => d.DateCreated, o => o.MapFrom(s => DateTimeOffset.UtcNow)) .AfterMap((s, d) => { diff --git a/PARR.API/Validators/JobGroupValidator.cs b/PARR.API/Validators/JobGroupValidator.cs index 2c68cda9..d885d2e1 100644 --- a/PARR.API/Validators/JobGroupValidator.cs +++ b/PARR.API/Validators/JobGroupValidator.cs @@ -135,7 +135,9 @@ namespace PARR.API.Validators .MustAsync(async (entity, value, c) => { // Автоконтроль разрешен только типам работ у которых включен IsJobGroupAutoControl - var isAllowedAutocontrol = await jobGroupTypeRepository.Get().AsNoTracking().AnyAsync(t => t.Id == entity.GroupTypeId && t.IsJobGroupAutoControl == true); + var isAllowedAutocontrol = await jobGroupTypeRepository.Get() + .AnyAsync(t => t.Id == entity.GroupTypeId && t.IsJobGroupAutoControl == true); + // Разрешен автоконтроль и есть значение if (isAllowedAutocontrol && value != null) return true; @@ -146,7 +148,7 @@ namespace PARR.API.Validators return false; }) - .WithMessage("Не верные параметры автоконтроля"); + .WithMessage("Некорректные параметры автоконтроля"); } } diff --git a/PARR.API/Validators/JobRequestValidator.cs b/PARR.API/Validators/JobRequestValidator.cs index 1ced6f2b..33ea9456 100644 --- a/PARR.API/Validators/JobRequestValidator.cs +++ b/PARR.API/Validators/JobRequestValidator.cs @@ -1,31 +1,32 @@ using FluentValidation; +using Microsoft.EntityFrameworkCore; +using Newtonsoft.Json.Linq; using PARR.API.Contracts.V1.Requests; using PARR.Core.Repositories.Interfaces; using PARR.Core.Repositories.Interfaces.Job; using PARR.Core.Repositories.Interfaces.Unit; -using PARR.Domain.Entities.JobGroupEntities; namespace PARR.API.Validators { public class JobRequestValidator : AbstractValidator { - private readonly ITnkRepository tnkService; - private readonly IJobGroupRepository jobGroupService; - private readonly IJobRepository jobRepository; - private readonly IUnitFieldRepository unitFieldService; - private JobGroup? jobGroup; + private readonly ITnkRepository _tnkRepository; + private readonly IJobGroupRepository _jobGroupRepository; + private readonly IJobRepository _jobRepository; + private readonly IUnitFieldRepository _unitFieldRepository; + //private JobGroup? jobGroup; public JobRequestValidator( - ITnkRepository tnkService, - IJobGroupRepository jobGroupService, - IJobRepository jobService, - IUnitFieldRepository unitFieldService + ITnkRepository tnkRepository, + IJobGroupRepository jobGroupRepository, + IJobRepository jobRepository, + IUnitFieldRepository unitFieldRepository ) { - this.tnkService = tnkService; - this.jobGroupService = jobGroupService; - this.jobRepository = jobService; - this.unitFieldService = unitFieldService; + _tnkRepository = tnkRepository; + _jobGroupRepository = jobGroupRepository; + _jobRepository = jobRepository; + _unitFieldRepository = unitFieldRepository; RuleFor(t => t.Name) .NotNull().NotEmpty(); @@ -48,14 +49,54 @@ namespace PARR.API.Validators .MustAsync(async (entity, value, c) => await IsUnitFiltersCorrect(entity)) .WithMessage("Неверно заданы параметры фильтров. Внимательнее, пожалуйста!"); - RuleFor(t => t.MinValueRelationships) - .GreaterThanOrEqualTo(0); + RuleFor(t => t.Relationships) + .MustAsync(async (entity, value, c) => + { + // Relationships должны быть обязательно заполнены если в типе работ IsRelationshipsAllowed==true + var relationshipsIsRequired = await jobGroupRepository.Get() + .AnyAsync(t => t.Id == entity.GroupId && t.GroupType!.IsRelationshipsAllowed, c); + + if (relationshipsIsRequired && value != null) + return true; + + if (!relationshipsIsRequired && value == null) + return true; + + return false; + }) + .WithMessage("Некорректные настройки кол-ва связей"); + + When(t => t.Relationships != null, () => + { + RuleFor(t => t.Relationships!.MinValueRelationships) + .GreaterThanOrEqualTo(0); + + RuleFor(t => t.Relationships!.MaxValueRelationships) + .GreaterThanOrEqualTo(t => t.Relationships!.MinValueRelationships); + }); - RuleFor(t => t.MaxValueRelationships) - .GreaterThanOrEqualTo(t => t.MinValueRelationships); RuleFor(t => t.ResponseAreaMask) .NotNull().NotEmpty(); + + RuleFor(t => t.AutoControl) + .MustAsync(async (entity, value, c) => + { + // Автоконтроль разрешен только типам работ, у которых выключен IsJobGroupAutoControl + var isAllowedJobAutocontrol = await jobGroupRepository.Get() + .AnyAsync(t => t.Id == entity.GroupId && t.GroupType!.IsJobGroupAutoControl == false, c); + + // Разрешен автоконтроль и есть значение + if (isAllowedJobAutocontrol && value != null) + return true; + + // Запрещен автоконтроль и нет значения + if (!isAllowedJobAutocontrol && value == null) + return true; + + return false; + }) + .WithMessage("Некорректные параметры автоконтроля"); } private async Task IsUnitFiltersCorrect(JobRequest entity) @@ -72,7 +113,7 @@ namespace PARR.API.Validators foreach (var fieldFilter in unitFilter.FieldFilters) { var fieldId = fieldFilter.FieldId; - if (await unitFieldService.GetAsync(fieldId) == null) + if (await _unitFieldRepository.GetAsync(fieldId) == null) return false; } @@ -80,7 +121,7 @@ namespace PARR.API.Validators foreach (var relationshipFilter in unitFilter.RelationshipFilters!) { var fieldId = relationshipFilter.FieldId; - if (await unitFieldService.GetAsync(fieldId) == null) + if (await _unitFieldRepository.GetAsync(fieldId) == null) return false; } } @@ -90,7 +131,7 @@ namespace PARR.API.Validators private async Task IsGroupExist(JobRequest entity) { - jobGroup = await jobGroupService.GetAsync(entity.GroupId); + var jobGroup = await _jobGroupRepository.GetAsync(entity.GroupId); return jobGroup != null; } @@ -98,7 +139,7 @@ namespace PARR.API.Validators private async Task IsTnkExist(JobRequest entity) { - return await tnkService.GetAsync(entity.TnkId) != null; + return await _tnkRepository.GetAsync(entity.TnkId) != null; } } } \ No newline at end of file diff --git a/PARR.Domain/Entities/JobGroupEntities/JobGroupType.cs b/PARR.Domain/Entities/JobGroupEntities/JobGroupType.cs index 29d0bd33..93f3a827 100644 --- a/PARR.Domain/Entities/JobGroupEntities/JobGroupType.cs +++ b/PARR.Domain/Entities/JobGroupEntities/JobGroupType.cs @@ -35,6 +35,7 @@ namespace PARR.Domain.Entities.JobGroupEntities /// /// Автоконтролем управляет JobGroup? true - да JobGroup, false - Job. /// Влияет на интерфейс и на логику работы автоконтроля. + /// Если true - то в GUI, кнопку синхронизировать можно нажать только в группах. /// [Comment("Автоконтролем управляет JobGroup? true - да JobGroup, false - Job. Влияет на интерфейс и на логику работы автоконтроля.")] public bool IsJobGroupAutoControl { get; set; } = false;