переход на fluentValidation
This commit is contained in:
@@ -6,6 +6,8 @@
|
|||||||
<PackageVersion Include="EFCore.NamingConventions" Version="10.0.1" />
|
<PackageVersion Include="EFCore.NamingConventions" Version="10.0.1" />
|
||||||
<PackageVersion Include="Extensions.Hosting.AsyncInitialization" Version="3.0.1" />
|
<PackageVersion Include="Extensions.Hosting.AsyncInitialization" Version="3.0.1" />
|
||||||
<PackageVersion Include="FluentResults" Version="4.0.0" />
|
<PackageVersion Include="FluentResults" Version="4.0.0" />
|
||||||
|
<PackageVersion Include="FluentValidation" Version="12.1.1" />
|
||||||
|
<PackageVersion Include="FluentValidation.DependencyInjectionExtensions" Version="12.1.1" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.10" />
|
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.10" />
|
||||||
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.10" />
|
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.10" />
|
||||||
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.10" />
|
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="10.0.10" />
|
||||||
|
|||||||
@@ -20,11 +20,11 @@ public sealed class RequestTypesController(RequestTypeService service) : Control
|
|||||||
|
|
||||||
[HttpPost]
|
[HttpPost]
|
||||||
public async Task<IActionResult> Create(CreateRequestTypeRequest body, CancellationToken ct) =>
|
public async Task<IActionResult> Create(CreateRequestTypeRequest body, CancellationToken ct) =>
|
||||||
(await service.CreateAsync(body.Name, body.PageUrl, ct)).ToActionResult();
|
(await service.CreateAsync(body, ct)).ToActionResult();
|
||||||
|
|
||||||
[HttpPut("{id:guid}")]
|
[HttpPut("{id:guid}")]
|
||||||
public async Task<IActionResult> Update(Guid id, UpdateRequestTypeRequest body, CancellationToken ct) =>
|
public async Task<IActionResult> Update(Guid id, UpdateRequestTypeRequest body, CancellationToken ct) =>
|
||||||
(await service.UpdateAsync(id, body.Name, body.PageUrl, ct)).ToActionResult();
|
(await service.UpdateAsync(id, body, ct)).ToActionResult();
|
||||||
|
|
||||||
[HttpDelete("{id:guid}")]
|
[HttpDelete("{id:guid}")]
|
||||||
public async Task<IActionResult> Delete(Guid id, CancellationToken ct) =>
|
public async Task<IActionResult> Delete(Guid id, CancellationToken ct) =>
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ using Microsoft.AspNetCore.Authorization;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Tnsu.ApprovalRoute.Api.Common;
|
using Tnsu.ApprovalRoute.Api.Common;
|
||||||
using Tnsu.ApprovalRoute.Application.TemplateRoutes;
|
using Tnsu.ApprovalRoute.Application.TemplateRoutes;
|
||||||
using Tnsu.ApprovalRoute.Domain.Enums;
|
|
||||||
|
|
||||||
namespace Tnsu.ApprovalRoute.Api.Controllers;
|
namespace Tnsu.ApprovalRoute.Api.Controllers;
|
||||||
|
|
||||||
@@ -12,13 +11,8 @@ namespace Tnsu.ApprovalRoute.Api.Controllers;
|
|||||||
public sealed class TemplateRoutesController(TemplateRouteService service) : ControllerBase
|
public sealed class TemplateRoutesController(TemplateRouteService service) : ControllerBase
|
||||||
{
|
{
|
||||||
[HttpGet("resolve")]
|
[HttpGet("resolve")]
|
||||||
public async Task<IActionResult> Resolve(
|
public async Task<IActionResult> Resolve([FromQuery] ResolveTemplateRouteRequest request, CancellationToken ct) =>
|
||||||
[FromQuery] Guid requestTypeId,
|
(await service.ResolveAsync(request, ct)).ToActionResult();
|
||||||
[FromQuery] string? userEmail,
|
|
||||||
[FromQuery] Guid? projectId,
|
|
||||||
[FromQuery] RouteStepStatus status,
|
|
||||||
CancellationToken ct) =>
|
|
||||||
(await service.ResolveAsync(requestTypeId, userEmail, projectId, status, ct)).ToActionResult();
|
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<IActionResult> List([FromQuery] Guid? requestTypeId, CancellationToken ct) =>
|
public async Task<IActionResult> List([FromQuery] Guid? requestTypeId, CancellationToken ct) =>
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
|
||||||
|
namespace Tnsu.ApprovalRoute.Api.Filters;
|
||||||
|
|
||||||
|
// Валидирует аргументы action'ов, для которых зарегистрирован IValidator<T> — без валидации внутри сервисов.
|
||||||
|
public sealed class ValidationActionFilter(IServiceProvider serviceProvider) : IAsyncActionFilter
|
||||||
|
{
|
||||||
|
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
|
||||||
|
{
|
||||||
|
foreach (var argument in context.ActionArguments.Values)
|
||||||
|
{
|
||||||
|
if (argument is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var validatorType = typeof(IValidator<>).MakeGenericType(argument.GetType());
|
||||||
|
if (serviceProvider.GetService(validatorType) is not IValidator validator)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var validationContext = new ValidationContext<object>(argument);
|
||||||
|
var result = await validator.ValidateAsync(validationContext, context.HttpContext.RequestAborted);
|
||||||
|
if (result.IsValid)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
context.Result = new BadRequestObjectResult(new
|
||||||
|
{
|
||||||
|
message = string.Join(" ", result.Errors.Select(e => e.ErrorMessage))
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await next();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection;
|
|||||||
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Hosting;
|
||||||
using Microsoft.OpenApi;
|
using Microsoft.OpenApi;
|
||||||
using Tnsu.ApprovalRoute.Api.Auth;
|
using Tnsu.ApprovalRoute.Api.Auth;
|
||||||
|
using Tnsu.ApprovalRoute.Api.Filters;
|
||||||
using Tnsu.ApprovalRoute.Application;
|
using Tnsu.ApprovalRoute.Application;
|
||||||
using Tnsu.ApprovalRoute.Application.Common.Interfaces;
|
using Tnsu.ApprovalRoute.Application.Common.Interfaces;
|
||||||
using Tnsu.ApprovalRoute.Infrastructure;
|
using Tnsu.ApprovalRoute.Infrastructure;
|
||||||
@@ -44,7 +45,7 @@ builder.Services.AddOpenApi(options =>
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
builder.Services.AddHealthChecks();
|
builder.Services.AddHealthChecks();
|
||||||
builder.Services.AddControllers()
|
builder.Services.AddControllers(options => options.Filters.Add<ValidationActionFilter>())
|
||||||
.AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
|
.AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()));
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using FluentValidation;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Tnsu.ApprovalRoute.Application.RequestTypes;
|
using Tnsu.ApprovalRoute.Application.RequestTypes;
|
||||||
using Tnsu.ApprovalRoute.Application.TemplateRoutes;
|
using Tnsu.ApprovalRoute.Application.TemplateRoutes;
|
||||||
@@ -13,6 +15,8 @@ public static class ApplicationServiceCollectionExtensions
|
|||||||
services.AddScoped<TemplateRouteService>();
|
services.AddScoped<TemplateRouteService>();
|
||||||
services.AddScoped<TemplateRouteStepService>();
|
services.AddScoped<TemplateRouteStepService>();
|
||||||
|
|
||||||
|
services.AddValidatorsFromAssembly(Assembly.GetExecutingAssembly());
|
||||||
|
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,33 +27,23 @@ public sealed class RequestTypeService(ApprovalRouteDbContext db)
|
|||||||
: Result.Ok(ToDto(entity));
|
: Result.Ok(ToDto(entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<RequestTypeDto>> CreateAsync(string name, string pageUrl, CancellationToken cancellationToken)
|
public async Task<Result<RequestTypeDto>> CreateAsync(CreateRequestTypeRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
var entity = new RequestType { Name = request.Name, PageUrl = request.PageUrl };
|
||||||
return Result.Fail<RequestTypeDto>(new ValidationError("Имя типа заявки обязательно."));
|
|
||||||
if (string.IsNullOrWhiteSpace(pageUrl))
|
|
||||||
return Result.Fail<RequestTypeDto>(new ValidationError("Url страницы обязателен."));
|
|
||||||
|
|
||||||
var entity = new RequestType { Name = name, PageUrl = pageUrl };
|
|
||||||
db.RequestTypes.Add(entity);
|
db.RequestTypes.Add(entity);
|
||||||
await db.SaveChangesAsync(cancellationToken);
|
await db.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
return Result.Ok(ToDto(entity));
|
return Result.Ok(ToDto(entity));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<RequestTypeDto>> UpdateAsync(Guid id, string name, string pageUrl, CancellationToken cancellationToken)
|
public async Task<Result<RequestTypeDto>> UpdateAsync(Guid id, UpdateRequestTypeRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var entity = await db.RequestTypes.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
var entity = await db.RequestTypes.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||||
if (entity is null)
|
if (entity is null)
|
||||||
return Result.Fail<RequestTypeDto>(new NotFoundError("Тип заявки", id));
|
return Result.Fail<RequestTypeDto>(new NotFoundError("Тип заявки", id));
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(name))
|
entity.Name = request.Name;
|
||||||
return Result.Fail<RequestTypeDto>(new ValidationError("Имя типа заявки обязательно."));
|
entity.PageUrl = request.PageUrl;
|
||||||
if (string.IsNullOrWhiteSpace(pageUrl))
|
|
||||||
return Result.Fail<RequestTypeDto>(new ValidationError("Url страницы обязателен."));
|
|
||||||
|
|
||||||
entity.Name = name;
|
|
||||||
entity.PageUrl = pageUrl;
|
|
||||||
await db.SaveChangesAsync(cancellationToken);
|
await db.SaveChangesAsync(cancellationToken);
|
||||||
|
|
||||||
return Result.Ok(ToDto(entity));
|
return Result.Ok(ToDto(entity));
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
|
||||||
|
namespace Tnsu.ApprovalRoute.Application.RequestTypes;
|
||||||
|
|
||||||
|
public sealed class CreateRequestTypeRequestValidator : AbstractValidator<CreateRequestTypeRequest>
|
||||||
|
{
|
||||||
|
public CreateRequestTypeRequestValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Name).NotEmpty().WithMessage("Имя типа заявки обязательно.");
|
||||||
|
RuleFor(x => x.PageUrl).NotEmpty().WithMessage("Url страницы обязателен.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class UpdateRequestTypeRequestValidator : AbstractValidator<UpdateRequestTypeRequest>
|
||||||
|
{
|
||||||
|
public UpdateRequestTypeRequestValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Name).NotEmpty().WithMessage("Имя типа заявки обязательно.");
|
||||||
|
RuleFor(x => x.PageUrl).NotEmpty().WithMessage("Url страницы обязателен.");
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-17
@@ -37,9 +37,9 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
|
|||||||
if (route is null)
|
if (route is null)
|
||||||
return Result.Fail<TemplateRouteStepDto>(new NotFoundError("Маршрут", routeId));
|
return Result.Fail<TemplateRouteStepDto>(new NotFoundError("Маршрут", routeId));
|
||||||
|
|
||||||
var fieldsError = ValidateFields(r.ExecutorEmail, r.Position, r.ProjectRole, route.ByProject);
|
if (!route.ByProject && r.ProjectRole != ProjectRole.None)
|
||||||
if (fieldsError is not null)
|
return Result.Fail<TemplateRouteStepDto>(new ValidationError(
|
||||||
return Result.Fail<TemplateRouteStepDto>(fieldsError);
|
"Роль в проекте недопустима для шага маршрута, который не учитывает проект (by_project = false)."));
|
||||||
|
|
||||||
var entity = new TemplateRouteStep
|
var entity = new TemplateRouteStep
|
||||||
{
|
{
|
||||||
@@ -70,9 +70,9 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
|
|||||||
if (entity is null)
|
if (entity is null)
|
||||||
return Result.Fail<TemplateRouteStepDto>(new NotFoundError("Шаг маршрута", id));
|
return Result.Fail<TemplateRouteStepDto>(new NotFoundError("Шаг маршрута", id));
|
||||||
|
|
||||||
var fieldsError = ValidateFields(r.ExecutorEmail, r.Position, r.ProjectRole, route.ByProject);
|
if (!route.ByProject && r.ProjectRole != ProjectRole.None)
|
||||||
if (fieldsError is not null)
|
return Result.Fail<TemplateRouteStepDto>(new ValidationError(
|
||||||
return Result.Fail<TemplateRouteStepDto>(fieldsError);
|
"Роль в проекте недопустима для шага маршрута, который не учитывает проект (by_project = false)."));
|
||||||
|
|
||||||
entity.OrderNo = r.OrderNo;
|
entity.OrderNo = r.OrderNo;
|
||||||
entity.ExecutorEmail = r.ExecutorEmail;
|
entity.ExecutorEmail = r.ExecutorEmail;
|
||||||
@@ -99,17 +99,6 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
|
|||||||
return Result.Ok();
|
return Result.Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IError? ValidateFields(string executorEmail, string position, ProjectRole projectRole, bool routeByProject)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(executorEmail) && string.IsNullOrWhiteSpace(position) && projectRole == ProjectRole.None)
|
|
||||||
return new ValidationError("Исполнитель, должность и роль в проекте не могут быть пустыми одновременно.");
|
|
||||||
|
|
||||||
if (!routeByProject && projectRole != ProjectRole.None)
|
|
||||||
return new ValidationError("Роль в проекте недопустима для шага маршрута, который не учитывает проект (by_project = false).");
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<Result<TemplateRouteStepDto>> AppendMissingProjectRoleWarningAsync(
|
private async Task<Result<TemplateRouteStepDto>> AppendMissingProjectRoleWarningAsync(
|
||||||
TemplateRoute route, Result<TemplateRouteStepDto> result, CancellationToken cancellationToken)
|
TemplateRoute route, Result<TemplateRouteStepDto> result, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Tnsu.ApprovalRoute.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Tnsu.ApprovalRoute.Application.TemplateRouteSteps;
|
||||||
|
|
||||||
|
public sealed class CreateTemplateRouteStepRequestValidator : AbstractValidator<CreateTemplateRouteStepRequest>
|
||||||
|
{
|
||||||
|
public CreateTemplateRouteStepRequestValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x)
|
||||||
|
.Must(x => !(string.IsNullOrWhiteSpace(x.ExecutorEmail) && string.IsNullOrWhiteSpace(x.Position) && x.ProjectRole == ProjectRole.None))
|
||||||
|
.WithMessage("Исполнитель, должность и роль в проекте не могут быть пустыми одновременно.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class UpdateTemplateRouteStepRequestValidator : AbstractValidator<UpdateTemplateRouteStepRequest>
|
||||||
|
{
|
||||||
|
public UpdateTemplateRouteStepRequestValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x)
|
||||||
|
.Must(x => !(string.IsNullOrWhiteSpace(x.ExecutorEmail) && string.IsNullOrWhiteSpace(x.Position) && x.ProjectRole == ProjectRole.None))
|
||||||
|
.WithMessage("Исполнитель, должность и роль в проекте не могут быть пустыми одновременно.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,3 +17,9 @@ public sealed record ResolveTemplateRouteResultDto(
|
|||||||
RouteType RouteType,
|
RouteType RouteType,
|
||||||
bool RequiresControl,
|
bool RequiresControl,
|
||||||
IReadOnlyList<ResolvedRouteStepDto> Steps);
|
IReadOnlyList<ResolvedRouteStepDto> Steps);
|
||||||
|
|
||||||
|
public sealed record ResolveTemplateRouteRequest(
|
||||||
|
Guid RequestTypeId,
|
||||||
|
string? UserEmail,
|
||||||
|
Guid? ProjectId,
|
||||||
|
RouteStepStatus Status);
|
||||||
|
|||||||
@@ -39,9 +39,6 @@ public sealed class TemplateRouteService(
|
|||||||
|
|
||||||
public async Task<Result<TemplateRouteDto>> CreateAsync(CreateTemplateRouteRequest r, CancellationToken cancellationToken)
|
public async Task<Result<TemplateRouteDto>> CreateAsync(CreateTemplateRouteRequest r, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (r.CompanyId == Guid.Empty)
|
|
||||||
return Result.Fail<TemplateRouteDto>(new ValidationError("Компания обязательна."));
|
|
||||||
|
|
||||||
var requestTypeExists = await db.RequestTypes.AnyAsync(x => x.Id == r.RequestTypeId, cancellationToken);
|
var requestTypeExists = await db.RequestTypes.AnyAsync(x => x.Id == r.RequestTypeId, cancellationToken);
|
||||||
if (!requestTypeExists)
|
if (!requestTypeExists)
|
||||||
return Result.Fail<TemplateRouteDto>(new NotFoundError("Тип заявки", r.RequestTypeId));
|
return Result.Fail<TemplateRouteDto>(new NotFoundError("Тип заявки", r.RequestTypeId));
|
||||||
@@ -72,9 +69,6 @@ public sealed class TemplateRouteService(
|
|||||||
if (entity is null)
|
if (entity is null)
|
||||||
return Result.Fail<TemplateRouteDto>(new NotFoundError("Маршрут", id));
|
return Result.Fail<TemplateRouteDto>(new NotFoundError("Маршрут", id));
|
||||||
|
|
||||||
if (r.CompanyId == Guid.Empty)
|
|
||||||
return Result.Fail<TemplateRouteDto>(new ValidationError("Компания обязательна."));
|
|
||||||
|
|
||||||
var duplicate = await HasDuplicateAsync(entity.RequestTypeId, r.DepartmentId, r.CompanyId, r.ByProject, excludeId: entity.Id, cancellationToken);
|
var duplicate = await HasDuplicateAsync(entity.RequestTypeId, r.DepartmentId, r.CompanyId, r.ByProject, excludeId: entity.Id, cancellationToken);
|
||||||
if (duplicate)
|
if (duplicate)
|
||||||
return Result.Fail<TemplateRouteDto>(new ConflictError(
|
return Result.Fail<TemplateRouteDto>(new ConflictError(
|
||||||
@@ -103,28 +97,24 @@ public sealed class TemplateRouteService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Result<ResolveTemplateRouteResultDto>> ResolveAsync(
|
public async Task<Result<ResolveTemplateRouteResultDto>> ResolveAsync(
|
||||||
Guid requestTypeId, string? userEmail, Guid? projectId, RouteStepStatus status, CancellationToken cancellationToken)
|
ResolveTemplateRouteRequest request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (status == RouteStepStatus.None)
|
var email = string.IsNullOrWhiteSpace(request.UserEmail) ? currentUser.Email : request.UserEmail;
|
||||||
return Result.Fail<ResolveTemplateRouteResultDto>(
|
|
||||||
new ValidationError("Статус обязателен: Approval или Execution."));
|
|
||||||
|
|
||||||
var email = string.IsNullOrWhiteSpace(userEmail) ? currentUser.Email : userEmail;
|
|
||||||
if (string.IsNullOrWhiteSpace(email))
|
if (string.IsNullOrWhiteSpace(email))
|
||||||
return Result.Fail<ResolveTemplateRouteResultDto>(
|
return Result.Fail<ResolveTemplateRouteResultDto>(
|
||||||
new ValidationError("Не удалось определить email инициатора: не передан явно и отсутствует в токене."));
|
new ValidationError("Не удалось определить email инициатора: не передан явно и отсутствует в токене."));
|
||||||
|
|
||||||
var requestTypeExists = await db.RequestTypes.AnyAsync(x => x.Id == requestTypeId, cancellationToken);
|
var requestTypeExists = await db.RequestTypes.AnyAsync(x => x.Id == request.RequestTypeId, cancellationToken);
|
||||||
if (!requestTypeExists)
|
if (!requestTypeExists)
|
||||||
return Result.Fail<ResolveTemplateRouteResultDto>(new NotFoundError("Тип заявки", requestTypeId));
|
return Result.Fail<ResolveTemplateRouteResultDto>(new NotFoundError("Тип заявки", request.RequestTypeId));
|
||||||
|
|
||||||
var employee = await portalClient.GetEmployeeByEmailAsync(email, cancellationToken);
|
var employee = await portalClient.GetEmployeeByEmailAsync(email, cancellationToken);
|
||||||
var byProject = projectId is not null;
|
var byProject = request.ProjectId is not null;
|
||||||
|
|
||||||
var route = await db.TemplateRoutes.AsNoTracking()
|
var route = await db.TemplateRoutes.AsNoTracking()
|
||||||
.Include(x => x.Steps)
|
.Include(x => x.Steps)
|
||||||
.Where(x =>
|
.Where(x =>
|
||||||
x.RequestTypeId == requestTypeId &&
|
x.RequestTypeId == request.RequestTypeId &&
|
||||||
x.CompanyId == employee.CompanyId &&
|
x.CompanyId == employee.CompanyId &&
|
||||||
x.ByProject == byProject &&
|
x.ByProject == byProject &&
|
||||||
x.DepartmentId == employee.DepartmentId)
|
x.DepartmentId == employee.DepartmentId)
|
||||||
@@ -132,7 +122,7 @@ public sealed class TemplateRouteService(
|
|||||||
?? await db.TemplateRoutes.AsNoTracking()
|
?? await db.TemplateRoutes.AsNoTracking()
|
||||||
.Include(x => x.Steps)
|
.Include(x => x.Steps)
|
||||||
.Where(x =>
|
.Where(x =>
|
||||||
x.RequestTypeId == requestTypeId &&
|
x.RequestTypeId == request.RequestTypeId &&
|
||||||
x.CompanyId == employee.CompanyId &&
|
x.CompanyId == employee.CompanyId &&
|
||||||
x.ByProject == byProject &&
|
x.ByProject == byProject &&
|
||||||
x.DepartmentId == null)
|
x.DepartmentId == null)
|
||||||
@@ -141,14 +131,14 @@ public sealed class TemplateRouteService(
|
|||||||
if (route is null)
|
if (route is null)
|
||||||
return Result.Fail<ResolveTemplateRouteResultDto>(new NotFoundError(
|
return Result.Fail<ResolveTemplateRouteResultDto>(new NotFoundError(
|
||||||
"Маршрут для этого типа заявки, отдела/компании инициатора и признака «по проекту»",
|
"Маршрут для этого типа заявки, отдела/компании инициатора и признака «по проекту»",
|
||||||
requestTypeId));
|
request.RequestTypeId));
|
||||||
|
|
||||||
IReadOnlyList<ProjectExecutorInfo> projectExecutors = [];
|
IReadOnlyList<ProjectExecutorInfo> projectExecutors = [];
|
||||||
if (projectId is { } id)
|
if (request.ProjectId is { } projectId)
|
||||||
projectExecutors = await portalClient.GetProjectExecutorsAsync(id, cancellationToken);
|
projectExecutors = await portalClient.GetProjectExecutorsAsync(projectId, cancellationToken);
|
||||||
|
|
||||||
var steps = route.Steps
|
var steps = route.Steps
|
||||||
.Where(s => s.Status == status)
|
.Where(s => s.Status == request.Status)
|
||||||
.OrderBy(s => s.OrderNo)
|
.OrderBy(s => s.OrderNo)
|
||||||
.Select(s => ResolveStep(s, projectExecutors))
|
.Select(s => ResolveStep(s, projectExecutors))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
using FluentValidation;
|
||||||
|
using Tnsu.ApprovalRoute.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Tnsu.ApprovalRoute.Application.TemplateRoutes;
|
||||||
|
|
||||||
|
public sealed class CreateTemplateRouteRequestValidator : AbstractValidator<CreateTemplateRouteRequest>
|
||||||
|
{
|
||||||
|
public CreateTemplateRouteRequestValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.CompanyId).NotEqual(Guid.Empty).WithMessage("Компания обязательна.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class UpdateTemplateRouteRequestValidator : AbstractValidator<UpdateTemplateRouteRequest>
|
||||||
|
{
|
||||||
|
public UpdateTemplateRouteRequestValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.CompanyId).NotEqual(Guid.Empty).WithMessage("Компания обязательна.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed class ResolveTemplateRouteRequestValidator : AbstractValidator<ResolveTemplateRouteRequest>
|
||||||
|
{
|
||||||
|
public ResolveTemplateRouteRequestValidator()
|
||||||
|
{
|
||||||
|
RuleFor(x => x.Status).NotEqual(RouteStepStatus.None).WithMessage("Статус обязателен: Approval или Execution.");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,6 +5,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="FluentResults" />
|
<PackageReference Include="FluentResults" />
|
||||||
|
<PackageReference Include="FluentValidation" />
|
||||||
|
<PackageReference Include="FluentValidation.DependencyInjectionExtensions" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
Reference in New Issue
Block a user