feat(api): JobAutoControlController - управление автоматическим созданием/деактивацией/активацией РР

This commit is contained in:
Mikhail Trubnikov
2024-09-09 15:48:49 +10:00
parent c74086412b
commit 60c525da54
13 changed files with 3505 additions and 8 deletions

View File

@@ -263,7 +263,7 @@
public static class StatWorkGroupWorkLoad public static class StatWorkGroupWorkLoad
{ {
public const string Get = BaseStat + "/work-group-workloads/{responseAreaCode}"; public const string Get = BaseStat + "/work-group-workloads/{responseAreaCode}";
public const string GetByWorkGroups= BaseStat + "/work-group-workloads/"; public const string GetByWorkGroups = BaseStat + "/work-group-workloads/";
} }
public static class StatWorkWorkLoad public static class StatWorkWorkLoad
@@ -361,6 +361,17 @@
} }
public static class JobAutoControl
{
public const string Get = "/jobs/" + appInWorkId + "/auto-control";
public const string Create = "/jobs/" + appInWorkId + "/auto-control";
public const string Update = "/jobs/" + appInWorkId + "/auto-control";
public const string Delete = "/jobs/" + appInWorkId + "/auto-control";
public const string appInWorkId = "{applicationInWorkId}";
}
/// <summary> /// <summary>
/// Активатор/деактиватор шаблонов/расписаний /// Активатор/деактиватор шаблонов/расписаний
/// </summary> /// </summary>

View File

@@ -0,0 +1,52 @@
using PARR.DAL.Contracts;
namespace PARR.API.Contracts.V1.Requests
{
public class JobAutoControlRequest
{
/// <summary>
/// Создавать новые РР (из настроке РР)
/// </summary>
public bool CreateNew { get; set; }
/// <summary>
/// Деактивировать не актуальные согласно ЭК (обратные статусы ЭК)
/// </summary>
public bool Deactivate { get; set; }
/// <summary>
/// Активировать РР после смены ЭК (согласно настройкам статусов ЭК)
/// </summary>
public bool Activate { get; set; }
/// <summary>
/// Состояние для активированных РР, шаблоны
/// </summary>
public bool OnTemplateIsActive { get; set; }
/// <summary>
/// Состояние для активированных РР, расписания
/// </summary>
public bool OnScheduleIsActive { get; set; }
/// <summary>
/// Состояние для деактивированных РР, шаблоны
/// </summary>
public bool OffTemplateIsActive { get; set; }
/// <summary>
/// Состояние для деактивированных РР, расписания
/// </summary>
public bool OffScheduleIsActive { get; set; }
/// <summary>
/// Маски ЭК
/// </summary>
public required List<string> EkMasks { get; set; }
/// <summary>
/// Статусы ЭК для которых включена генерация РР и активация
/// </summary>
public required List<EkStatusEnum> EnabledEkStatuses { get; set; }
}
}

View File

@@ -0,0 +1,64 @@
namespace PARR.API.Contracts.V1.Responses
{
public class JobAutoControlResponse
{
public Guid Id { get; set; }
public Guid ApplicationInWorkId { get; set; }
/// <summary>
/// Создавать новые РР (из настроке РР)
/// </summary>
public bool CreateNew { get; set; }
/// <summary>
/// Деактивировать не актуальные согласно ЭК (обратные статусы ЭК)
/// </summary>
public bool Deactivate { get; set; }
/// <summary>
/// Активировать РР после смены ЭК (согласно настройкам статусов ЭК)
/// </summary>
public bool Activate { get; set; }
/// <summary>
/// Состояние для активированных РР, шаблоны
/// </summary>
public bool OnTemplateIsActive { get; set; }
/// <summary>
/// Состояние для активированных РР, расписания
/// </summary>
public bool OnScheduleIsActive { get; set; }
/// <summary>
/// Состояние для деактивированных РР, шаблоны
/// </summary>
public bool OffTemplateIsActive { get; set; }
/// <summary>
/// Состояние для деактивированных РР, расписания
/// </summary>
public bool OffScheduleIsActive { get; set; }
/// <summary>
/// Маски ЭК
/// </summary>
public List<JobEkMaskResponse>? EkMasks { get; set; }
/// <summary>
/// Статусы ЭК для которых включена генерация РР и активация
/// </summary>
public List<EkStatusResponse>? EnabledEkStatuses{ get; set; }
/// <summary>
/// Краткое описание
/// </summary>
public required string ShortDescription { get; set; }
/// <summary>
/// Рабочие группы
/// </summary>
public List<WorkGroupResponse>? WorkGroups { get; set; }
}
}

View File

@@ -0,0 +1,9 @@
namespace PARR.API.Contracts.V1.Responses
{
public class JobEkMaskResponse
{
public Guid Id { get; set; }
public required string Name { get; set; }
}
}

View File

@@ -0,0 +1,213 @@
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.Responses;
using PARR.API.Contracts.V1.Responses.Base;
using PARR.API.Controllers.V1.Base;
using PARR.API.Services.Interfaces;
using PARR.Constants;
using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces;
namespace PARR.API.Controllers.V1
{
/// <summary>
/// Управление автоматическим созданием/деактивацией/активацией РР
/// </summary>
[Authorize(Roles = ParrRoles.Administrator.Role)]
public class JobAutoControlController : BaseApiController
{
private readonly IJobAutoControlService jobAutoControlService;
private readonly IMapper mapper;
private readonly IValidator<JobAutoControlRequest> validator;
private readonly IUriService uriService;
public JobAutoControlController(
IJobAutoControlService jobAutoControlService,
IMapper mapper,
IValidator<JobAutoControlRequest> validator,
IUriService uriService
)
{
this.jobAutoControlService = jobAutoControlService;
this.mapper = mapper;
this.validator = validator;
this.uriService = uriService;
}
/// <summary>
/// Получить настройки автоматического управления РР
/// </summary>
/// <param name="applicationInWorkId"></param>
/// <returns></returns>
[HttpGet(ApiRoutes.JobAutoControl.Get)]
public async Task<IActionResult> Get([FromRoute] Guid applicationInWorkId)
{
var obj = await jobAutoControlService.Get()
.Include(t => t.ApplicationsInWork).ThenInclude(t => t.WorkGroups).ThenInclude(t => t.WorkGroup)
.Include(t => t.JobEkMasks)
.Include(t => t.JobAutoControlInEkStatuses).ThenInclude(t => t.EkStatus)
.FirstOrDefaultAsync(t => t.ApplicationInWorkId == applicationInWorkId);
if (obj == null)
return NotFound();
var response = mapper.Map<JobAutoControlResponse>(obj);
return Ok(new Response<JobAutoControlResponse>(response, true));
}
/// <summary>
/// Создать настройки автоматического управления РР
/// </summary>
/// <param name="applicationInWorkId"></param>
/// <param name="request"></param>
/// <returns></returns>
[HttpPost(ApiRoutes.JobAutoControl.Create)]
public async Task<IActionResult> Create([FromRoute] Guid applicationInWorkId, [FromBody] JobAutoControlRequest request)
{
var exist = await jobAutoControlService.Get().FirstOrDefaultAsync(t => t.ApplicationInWorkId == applicationInWorkId);
if (exist != null)
return BadRequest(new Response(false, new List<ErrorModel> {
new ErrorModel { FieldName = nameof(applicationInWorkId), Message = $"Запись с {nameof(applicationInWorkId)}: {applicationInWorkId} уже существует." }
}));
var resultValidate = await validator.ValidateAsync(request);
if (!resultValidate.IsValid)
return BadRequest(new Response(resultValidate.Errors));
var newObj = new DAL.Models.JobAutoControl
{
Id = Guid.NewGuid(),
ApplicationInWorkId = applicationInWorkId,
CreateNew = request.CreateNew,
Deactivate = request.Deactivate,
Activate = request.Activate,
OnTemplateIsActive = request.OnTemplateIsActive,
OnScheduleIsActive = request.OnScheduleIsActive,
OffTemplateIsActive = request.OffTemplateIsActive,
OffScheduleIsActive = request.OffScheduleIsActive
};
// убираем пробелы и дубли
request.EkMasks.Where(t => t.Trim() != string.Empty).Distinct().ToList().ForEach(mask => newObj.JobEkMasks.Add(new JobEkMask
{
Id = Guid.NewGuid(),
DateCreated = DateTimeOffset.UtcNow,
Name = mask.Trim(),
JobAutoControlId = newObj.Id
}));
request.EnabledEkStatuses.ForEach(status => newObj.JobAutoControlInEkStatuses.Add(new JobAutoControlInEkStatus
{
EkStatusCode = (int)status,
JobAutoControlId = newObj.Id
}));
if (!await jobAutoControlService.CreateAsync(newObj) || !await jobAutoControlService.CommitAsync())
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при создани записи." } }));
var locationUri = uriService.GetUri(ApiRoutes.JobAutoControl.Get, ApiRoutes.JobAutoControl.appInWorkId, applicationInWorkId);
var createdObj = await jobAutoControlService.Get()
.Include(t => t.ApplicationsInWork).ThenInclude(t => t.WorkGroups).ThenInclude(t => t.WorkGroup)
.Include(t => t.JobEkMasks)
.Include(t => t.JobAutoControlInEkStatuses).ThenInclude(t => t.EkStatus)
.FirstOrDefaultAsync(t => t.ApplicationInWorkId == applicationInWorkId);
return Created(locationUri, new Response<JobAutoControlResponse>(mapper.Map<JobAutoControlResponse>(createdObj), true));
}
/// <summary>
/// Обновить настройки автоматического управления РР
/// </summary>
/// <param name="applicationInWorkId"></param>
/// <param name="request"></param>
/// <returns></returns>
[HttpPut(ApiRoutes.JobAutoControl.Update)]
public async Task<IActionResult> Update([FromRoute] Guid applicationInWorkId, [FromBody] JobAutoControlRequest request)
{
var resultValidate = await validator.ValidateAsync(request);
if (!resultValidate.IsValid)
return BadRequest(new Response(resultValidate.Errors));
var orig = await jobAutoControlService.Get()
.Include(t => t.JobEkMasks)
.Include(t => t.JobAutoControlInEkStatuses)
.FirstOrDefaultAsync(t => t.ApplicationInWorkId == applicationInWorkId);
if (orig == null)
return BadRequest(new Response(false, new List<ErrorModel> {
new ErrorModel { FieldName = nameof(applicationInWorkId), Message = $"Запись с {nameof(applicationInWorkId)}: {applicationInWorkId} не существует." }
}));
orig.DateModified = DateTimeOffset.UtcNow;
orig.CreateNew = request.CreateNew;
orig.Deactivate = request.Deactivate;
orig.Activate = request.Activate;
orig.OnTemplateIsActive = request.OnTemplateIsActive;
orig.OnScheduleIsActive = request.OnScheduleIsActive;
orig.OffTemplateIsActive = request.OffTemplateIsActive;
orig.OffScheduleIsActive = request.OffScheduleIsActive;
orig.JobEkMasks.Clear();
// убираем пробелы и дубли
request.EkMasks.Where(t => t.Trim() != string.Empty).Distinct().ToList().ForEach(mask => orig.JobEkMasks.Add(new JobEkMask
{
//Id = Guid.NewGuid(), // не пойму почему не нужно это поле. с ним ошибка.
DateCreated = DateTimeOffset.UtcNow,
Name = mask.Trim(),
JobAutoControlId = orig.Id
}));
orig.JobAutoControlInEkStatuses.Clear();
request.EnabledEkStatuses.ForEach(status => orig.JobAutoControlInEkStatuses.Add(new JobAutoControlInEkStatus
{
EkStatusCode = (int)status,
JobAutoControlId = orig.Id
}));
if (!await jobAutoControlService.CommitAsync())
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = "Ошибка при изменении записи." } }));
var updatedObj = await jobAutoControlService.Get()
.Include(t => t.ApplicationsInWork).ThenInclude(t => t.WorkGroups).ThenInclude(t => t.WorkGroup)
.Include(t => t.JobEkMasks)
.Include(t => t.JobAutoControlInEkStatuses).ThenInclude(t => t.EkStatus)
.FirstOrDefaultAsync(t => t.ApplicationInWorkId == applicationInWorkId);
return Ok(new Response<JobAutoControlResponse>(mapper.Map<JobAutoControlResponse>(updatedObj), true));
}
/// <summary>
/// Удалить настройки автоматического управления РР
/// </summary>
/// <param name="applicationInWorkId"></param>
/// <returns></returns>
[HttpDelete(ApiRoutes.JobAutoControl.Delete)]
public async Task<IActionResult> Delete([FromRoute] Guid applicationInWorkId)
{
var obj = await jobAutoControlService.Get().FirstOrDefaultAsync(t => t.ApplicationInWorkId == applicationInWorkId);
if (obj == null)
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = $"Ошибка при удалении настроект. Не найден настройки с {nameof(applicationInWorkId)}: {applicationInWorkId}" } }));
if (!jobAutoControlService.Delete(obj) || !await jobAutoControlService.CommitAsync())
return BadRequest(new Response(false, new List<ErrorModel> { new ErrorModel { Message = $"Ошибка при удалении настроек." } }));
return NoContent();
}
}
}

View File

@@ -15,7 +15,6 @@ using PARR.Constants;
using PARR.DAL.DomainModels; using PARR.DAL.DomainModels;
using PARR.DAL.Models; using PARR.DAL.Models;
using PARR.DAL.Services.Interfaces; using PARR.DAL.Services.Interfaces;
using Serilog.Core;
namespace PARR.API.Controllers.V1 namespace PARR.API.Controllers.V1
{ {

View File

@@ -2,7 +2,6 @@
using PARR.API.Authentication.Models; using PARR.API.Authentication.Models;
using PARR.API.Contracts.V1.Responses; using PARR.API.Contracts.V1.Responses;
using PARR.API.MappingProfiles.Resolvers; using PARR.API.MappingProfiles.Resolvers;
using PARR.BLL.Helpers;
using PARR.Constants.Shortcode; using PARR.Constants.Shortcode;
using PARR.DAL.DomainModels; using PARR.DAL.DomainModels;
using PARR.DAL.Extensions; using PARR.DAL.Extensions;
@@ -276,10 +275,23 @@ namespace PARR.API.MappingProfiles
#endregion #endregion
#region JobAutoControl
CreateMap<JobAutoControl, JobAutoControlResponse>()
.ForMember(d => d.EkMasks, o => o.MapFrom(s => s.JobEkMasks.OrderBy(t => t.Name)))
.ForMember(d => d.EnabledEkStatuses, o => o.MapFrom(s => s.JobAutoControlInEkStatuses.Select(t => t.EkStatus).OrderBy(t => t.Name)))
.ForMember(d => d.ShortDescription, o => o.MapFrom(s => s.ApplicationsInWork.ShortDescription))
.ForMember(d => d.WorkGroups, o => o.MapFrom(s => s.ApplicationsInWork.WorkGroups.Select(t => t.WorkGroup).OrderBy(t => t.Name)));
#endregion
#region ResponseArea #region ResponseArea
CreateMap<ResponseArea, ResponseAreaResponse>(); CreateMap<ResponseArea, ResponseAreaResponse>();
#endregion #endregion
CreateMap<JobEkMask, JobEkMaskResponse>();
CreateMap<EkStatus, EkStatusResponse>(); CreateMap<EkStatus, EkStatusResponse>();
} }

View File

@@ -0,0 +1,33 @@
using FluentValidation;
using PARR.API.Contracts.V1.Requests;
using PARR.DAL.Contracts;
namespace PARR.API.Validators
{
public class JobAutoControlRequestValidator : AbstractValidator<JobAutoControlRequest>
{
public JobAutoControlRequestValidator()
{
RuleFor(t => t.EkMasks).Must(items =>
items.Count(t => t.Trim().Length > 0) > 0
).WithMessage("Должна быть указана хотя бы одна маска ЭК");
RuleFor(t => t.EnabledEkStatuses).Must(
items => items.Distinct().Count() == items.Count()
).WithMessage("Статусы ЭК не должны повторяться");
RuleFor(t => t.EnabledEkStatuses).Must(items =>
{
return !items.Any(item => !Enum.IsDefined(typeof(EkStatusEnum), item)) && !items.Any(t => t == 0);
})
.WithMessage($"Допустимые значения: {(int)EkStatusEnum.New} ({EkStatusEnum.New}), " +
$"{(int)EkStatusEnum.Preapre} ({EkStatusEnum.Preapre}), " +
$"{(int)EkStatusEnum.Exploitation} ({EkStatusEnum.Exploitation}), " +
$"{(int)EkStatusEnum.Repair} ({EkStatusEnum.Repair}), " +
$"{(int)EkStatusEnum.Reserve} ({EkStatusEnum.Reserve}), " +
$"{(int)EkStatusEnum.OutOfService} ({EkStatusEnum.OutOfService}), " +
$"{(int)EkStatusEnum.Test} ({EkStatusEnum.Test}), " +
$"{(int)EkStatusEnum.Development} ({EkStatusEnum.Development})");
}
}
}

View File

@@ -8,9 +8,9 @@ namespace PARR.API.Validators
{ {
public RobotStateRequestValidator() public RobotStateRequestValidator()
{ {
RuleFor(t => t.Robot).NotNull().IsInEnum().WithMessage($"Допустимые значения: {(int)RobotsAllEnum.TemplateOrder} ({RobotsAllEnum.TemplateOrder})," + RuleFor(t => t.Robot).NotNull().IsInEnum().WithMessage($"Допустимые значения: {(int)RobotsAllEnum.TemplateOrder} ({RobotsAllEnum.TemplateOrder}), " +
$"{(int)RobotsAllEnum.ScheduleOrder} ({RobotsAllEnum.ScheduleOrder})," + $"{(int)RobotsAllEnum.ScheduleOrder} ({RobotsAllEnum.ScheduleOrder}), " +
$"{(int)RobotsAllEnum.TemplatesExport} ({RobotsAllEnum.TemplatesExport})," + $"{(int)RobotsAllEnum.TemplatesExport} ({RobotsAllEnum.TemplatesExport}), " +
$"{(int)RobotsAllEnum.ScheduleExport} ({RobotsAllEnum.ScheduleExport})"); $"{(int)RobotsAllEnum.ScheduleExport} ({RobotsAllEnum.ScheduleExport})");
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,37 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace PARR.DAL.Migrations
{
/// <inheritdoc />
public partial class tblJobEkMaskAddIndexUnique : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_JobEkMasks_JobAutoControlId",
table: "JobEkMasks");
migrationBuilder.CreateIndex(
name: "IX_JobEkMasks_JobAutoControlId_Name",
table: "JobEkMasks",
columns: new[] { "JobAutoControlId", "Name" },
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_JobEkMasks_JobAutoControlId_Name",
table: "JobEkMasks");
migrationBuilder.CreateIndex(
name: "IX_JobEkMasks_JobAutoControlId",
table: "JobEkMasks",
column: "JobAutoControlId");
}
}
}

View File

@@ -1619,7 +1619,8 @@ namespace PARR.DAL.Migrations
b.HasKey("Id"); b.HasKey("Id");
b.HasIndex("JobAutoControlId"); b.HasIndex("JobAutoControlId", "Name")
.IsUnique();
b.ToTable("JobEkMasks"); b.ToTable("JobEkMasks");
}); });

View File

@@ -1,10 +1,12 @@
using PARR.DAL.Models.Base; using Microsoft.EntityFrameworkCore;
using PARR.DAL.Models.Base;
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
namespace PARR.DAL.Models namespace PARR.DAL.Models
{ {
[Table("JobEkMasks")] [Table("JobEkMasks")]
[Index(nameof(JobAutoControlId), nameof(Name), IsUnique = true)]
public class JobEkMask : IBase public class JobEkMask : IBase
{ {
[Key] [Key]