Files
parr_api/PARR.API/Controllers/V1/JobController.cs

580 lines
27 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
{
/// <summary>
/// Управление работами
/// </summary>
[Authorize(Roles = ParrRoles.Administrator.Role)]
public class JobController : BaseApiController
{
private readonly ILogger<JobController> logger;
private readonly IMapper mapper;
private readonly IUriService uriService;
private readonly IJobService jobService;
private readonly ITemplateService templateService;
private readonly IJobGroupService jobGroupService;
private readonly IValidator<JobRequest> jobValidator;
private readonly IUnitFilterService unitFilterService;
private readonly IUnitService unitService;
public JobController(
ILogger<JobController> logger,
IMapper mapper,
IUriService uriService,
IJobService jobService,
ITemplateService templateService,
IJobGroupService jobGroupService,
IValidator<JobRequest> jobValidator,
IUnitFilterService unitFilterService,
IUnitService unitService
)
{
this.logger = logger;
this.mapper = mapper;
this.uriService = uriService;
this.jobService = jobService;
this.templateService = templateService;
this.jobGroupService = jobGroupService;
this.jobValidator = jobValidator;
this.unitFilterService = unitFilterService;
this.unitService = unitService;
}
/// <summary>
/// Получить список заданий на выполнение работ(Job) постранично
/// </summary>
/// <returns></returns>
[HttpGet(ApiRoutes.Job.GetAll)]
public async Task<IActionResult> GetAll([FromQuery] PaginationQuery paginationQuery, [FromQuery] JobQuery filter)
{
var paginationFilter = mapper.Map<PaginationFilter>(paginationQuery);
IQueryable<Job> 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);
query = query
.Include(t => t.UnitFilters)
.ThenInclude(t => t.FieldFilters)
.ThenInclude(t => t.Field)
.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<List<JobResponse>>(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<JobResponse>(response, true).GetPaginatedProps(paginationFilter, query);
return Ok(paginationResponse);
}
/// <summary>
/// Получить задание на выполнение работ по id
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpGet(ApiRoutes.Job.Get)]
public async Task<IActionResult> GetById([FromRoute] Guid id)
{
var job = 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 == id);
if (job == null)
return NotFound();
var response = mapper.Map<JobResponse>(job);
response.TemplatesCount = await templateService.Get().CountAsync(t => t.JobId == id);
var statistics = await GetStatisticsAsync(response.Id);
BindStatistics(response, statistics);
return Ok(new Response<JobResponse>(response, true));
}
/// <summary>
/// Создать задание на выполнение работ (Job)
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost(ApiRoutes.Job.Create)]
public async Task<IActionResult> 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()
.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<ErrorModel> { new ErrorModel { FieldName = nameof(request.Name), Message = $"Работа с указанным диапазоном связей пересекается с уже имеющейся в базе данных({request.MinValueRelationships}-{request.MaxValueRelationships})" } }));
#endregion
var job = mapper.Map<Job>(request);
if (!await jobService.CreateAsync(job) || !await jobService.CommitAsync())
return BadRequest(new Response(false, new List<ErrorModel> { 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 locationUri = uriService.GetUri(ApiRoutes.Job.Get, ApiRoutes.Job.getParam, createdJob!.Id);
var response = mapper.Map<JobResponse>(createdJob);
// так как мы только что создали Job, то у него нет шаблонов, смело ставим = 0 (ускоряем запрос)
response.TemplatesCount = 0;
return Created(locationUri, new Response<JobResponse>(response, true));
}
/// <summary>
/// Обновить задание на выполнение работ (Job)
/// </summary>
/// <param name="id"></param>
/// <param name="request"></param>
/// <returns></returns>
[HttpPut(ApiRoutes.Job.Update)]
public async Task<IActionResult> 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)
.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<ErrorModel> { 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<Job>(request);
job.Id = id;//На всякий. Пусть будет для чистоты
UpdateUnitFilters(orig, job);//Обновление вложенных дочерних элементов-фильтров
if (!await jobService.CommitAsync())
return BadRequest(new Response(false, new List<ErrorModel> { 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)
.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<JobResponse>(updatedJob);
response.TemplatesCount = await templateService.Get().CountAsync(t => t.JobId == id);
var statistics = await GetStatisticsAsync(response.Id);
BindStatistics(response, statistics);
return Ok(new Response<JobResponse>(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
};
}
/// <summary>
/// Удалить задание на выполнение работ (только если нет связанных шаблонов)
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
[HttpDelete(ApiRoutes.Job.Delete)]
public async Task<IActionResult> 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<ErrorModel> { 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<ErrorModel> { new ErrorModel {
Message = $"Ошибка при удалении задания на выполнение работ. С данным заданием связаны шаблоны: {templateCount} шт."
} }));
if (!jobService.Delete(job) || !await jobService.CommitAsync())
return BadRequest(new Response(false, new List<ErrorModel> { 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();
}
/// <summary>
/// Получить список ЭК для которых будут созданы шаблоны
/// </summary>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost(ApiRoutes.Job.Preview)]
public async Task<IActionResult> 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<Job>(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<List<UnitResponse>>(units);
return Ok(response);
}
/// <summary>
/// Загрузка статистики
/// </summary>
/// <param name="jobId"></param>
/// <returns></returns>
private async Task<JobStatModel> 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; }
}
}