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.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 { /// /// Управление работами /// [Authorize(Roles = ParrRoles.Administrator.Role)] public class JobController : BaseApiController { private readonly ILogger logger; private readonly IMapper mapper; private readonly IUriService uriService; private readonly IJobService jobService; private readonly ITemplateService templateService; private readonly IValidator jobValidator; public JobController( ILogger logger, IMapper mapper, IUriService uriService, IJobService jobService, ITemplateService templateService, IJobGroupService jobGroupService, IValidator jobValidator, IUnitFilterService unitFilterService, IUnitService unitService ) { this.logger = logger; this.mapper = mapper; this.uriService = uriService; this.jobService = jobService; this.templateService = templateService; this.jobValidator = jobValidator; } /// /// Получить список заданий на выполнение работ(Job) постранично /// /// [HttpGet(ApiRoutes.Job.GetAll)] public async Task GetAll([FromQuery] PaginationQuery paginationQuery, [FromQuery] JobQuery filter) { var paginationFilter = mapper.Map(paginationQuery); IQueryable query = jobService.Get(); query = query.OrderBy(t => t.Name); if (!string.IsNullOrEmpty(filter.Name)) query = query.Where(t => t.Name.ToLower().Contains(filter.Name.ToLower())); if (filter.GroupId.HasValue) query = query.Where(t => t.GroupId == filter.GroupId.Value); if (filter.IsFull) { query = query .Include(t => t.Tnk) .Include(t => t.Group).ThenInclude(t => t.GroupType); query = query .Include(t => t.UnitFilters) .ThenInclude(t => t.FieldFilters) .ThenInclude(t => t.UnitField) .Include(t => t.UnitFilters) .ThenInclude(t => t.RelationshipFilters) .ThenInclude(t => t.UnitField); } var jobs = await jobService.GetPage(query, paginationFilter).ToListAsync(); if (!jobs.Any()) return NoContent(); var response = mapper.Map>(jobs);//TODO Migration to job foreach (var jobResponse in response) { jobResponse.TemplatesCount = await templateService.Get().CountAsync(t => t.JobId == jobResponse.Id); } var paginationResponse = new PagedResponse(response, true).GetPaginatedProps(paginationFilter, query); return Ok(paginationResponse); } /// /// Получить задание на выполнение работ по id /// /// /// [HttpGet(ApiRoutes.Job.Get)] public async Task GetById([FromRoute] Guid id) { var job = await jobService.Get() .Include(t => t.Tnk) .Include(t => t.Group).ThenInclude(t => t.GroupType) .Include(t => t.UnitFilters) .ThenInclude(t => t.FieldFilters) .ThenInclude(t => t.UnitField) .Include(t => t.UnitFilters) .ThenInclude(t => t.RelationshipFilters) .ThenInclude(t => t.UnitField) .FirstOrDefaultAsync(t => t.Id == id); if (job == null) return NotFound(); var response = mapper.Map(job); 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)); } /// /// Создать задание на выполнение работ (Job) /// /// /// [HttpPost(ApiRoutes.Job.Create)] public async Task Create([FromBody] JobRequest request) { #region Валидация var jobValidateResult = await jobValidator.ValidateAsync(request);//Валидация параметров самого задания if (!jobValidateResult.IsValid) return BadRequest(new Response(jobValidateResult.Errors)); #endregion #region Проверка существования работы с такими же параметрами var isExistTheSameLinks = await jobService.Get() .Include(t => t.Group).ThenInclude(t => t.GroupType) .CountAsync(t => //t.Group!.IsUmbrella == true && t.Group!.GroupType!.Code == JobGroupTypesEnum.Umbrella && 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()) 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).ThenInclude(t => t.GroupType) .Include(t => t.UnitFilters) .ThenInclude(t => t.FieldFilters) .ThenInclude(t => t.UnitField) .Include(t => t.UnitFilters) .ThenInclude(t => t.RelationshipFilters) .FirstOrDefaultAsync(t => t.Id == job.Id); var locationUri = uriService.GetUri(ApiRoutes.Job.Get, ApiRoutes.Job.getParam, createdJob!.Id); var response = mapper.Map(createdJob); // так как мы только что создали Job, то у него нет шаблонов, смело ставим = 0 (ускоряем запрос) response.TemplatesCount = 0; return Created(locationUri, new Response(response, true)); } /// /// Обновить задание на выполнение работ (Job) /// /// /// /// [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() .Include(t => t.Tnk) .Include(t => t.Group).ThenInclude(t => t.GroupType) .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}" } })); #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 = "Ошибка при изменении задания на выполнение работ." } })); 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() .Include(t => t.Tnk) .Include(t => t.Group).ThenInclude(t => t.GroupType) .Include(t => t.UnitFilters) .ThenInclude(t => t.FieldFilters) .ThenInclude(t => t.UnitField) .Include(t => t.UnitFilters) .ThenInclude(t => t.RelationshipFilters) .FirstAsync(t => t.Id == orig.Id); 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) { orig.UnitFilters.Clear(); foreach (var mappedUnitFilter in mappedRequest.UnitFilters) { 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); } /* Писал полноценный апдейт, но Миша сказал что нахрен это - просто все удаляем, а потом создаём заново //Сразу удаляем 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 JobFieldFilter CreateFieldFilter(Guid unitFilterId, Guid fieldId, string valueMask) { return new JobFieldFilter { UnitFilterId = unitFilterId, DateCreated = DateTimeOffset.UtcNow, FieldId = fieldId, ValueMask = valueMask }; } /// /// Удалить задание на выполнение работ (только если нет связанных шаблонов) /// /// /// [HttpDelete(ApiRoutes.Job.Delete)] public async Task Delete([FromRoute] Guid id) { var job = await jobService.Get().Include(t => t.Tnk) .FirstOrDefaultAsync(t => t.Id == id); if (job == null) return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при удалении задания на выполнение работ. Не найдено задание на выполнение работ Id: {id}" } })); var templateCount = await templateService.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()) return BadRequest(new Response(false, new List { new ErrorModel { Message = $"Ошибка при удалении задания на выполнение работ" } })); logger.LogInformation($"Пользователь {User.Identity?.Name} удалил задание на выполнение работ: {job.Id},{job.Name}," + $" {job.WorkName}, {job.MinValueRelationships}, {job.MaxValueRelationships}," + $" {job.TemplateNameMask}, {job.TnkId}, {job.GroupId}"); return NoContent(); } /// /// Загрузка статистики /// /// /// private async Task GetStatisticsAsync(Guid jobId) { var statResult = await jobService.Get() .Include(t => t.Templates) .ThenInclude(t => t.RobotConfigurations) .Where(x => x.Id == jobId) .Select(t => new { TemplateActivated = t.Templates.Count(x => x.IsActiveTemplate), TemplateSynchronized = t.Templates.Count(x => x.RobotConfigurations.Any(c => c.TaskStatusCode == (int)TaskStatusEnum.Ok && c.RobotCode == (int)RobotsEnum.TemplateOrder)), TemplateErrors = t.Templates.Count(x => x.RobotConfigurations.Any(c => c.RobotStatusCode == (int)RobotStatusEnum.Error && c.RobotCode == (int)RobotsEnum.TemplateOrder)), ScheduleActivated = t.Templates.Count(x => x.IsActiveSchedule), ScheduleSynchronized = t.Templates.Count(x => x.RobotConfigurations.Any(c => c.TaskStatusCode == (int)TaskStatusEnum.Ok && c.RobotCode == (int)RobotsEnum.ScheduleOrder)), ScheduleErrors = t.Templates.Count(x => x.RobotConfigurations.Any(c => c.RobotStatusCode == (int)RobotStatusEnum.Error && c.RobotCode == (int)RobotsEnum.ScheduleOrder)) }).FirstOrDefaultAsync(); return new JobStatModel { ScheduleStatistics = new ScheduleStats { Activated = statResult?.ScheduleActivated ?? 0, Errors = statResult?.ScheduleErrors ?? 0, Synchronized = statResult?.ScheduleSynchronized ?? 0 }, TemplateStatistics = new TemplateStats { Activated = statResult?.TemplateActivated ?? 0, Errors = statResult?.TemplateErrors ?? 0, Synchronized = statResult?.TemplateSynchronized ?? 0 } }; } private void BindStatistics(JobResponse job, JobStatModel statistics) { //job.TemplateStatistics = new TemplateStats { Activated = statistics.TemplateStatistics.Activated, Errors = statistics.TemplateStatistics.Errors, Synchronized = statistics.TemplateStatistics.Synchronized }; //job.ScheduleStatistics = new ScheduleStats { Activated = statistics.ScheduleStatistics.Activated, Errors = statistics.ScheduleStatistics.Errors, Synchronized = statistics.ScheduleStatistics.Synchronized }; } } public class JobStatModel { public required TemplateStats TemplateStatistics { get; set; } public required ScheduleStats ScheduleStatistics { get; set; } } }