апи для работы с маршрутами согласования #2
@@ -3,6 +3,7 @@ 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;
|
||||||
|
|
||||||
@@ -11,6 +12,15 @@ namespace Tnsu.ApprovalRoute.Api.Controllers;
|
|||||||
[Route("api/template-routes")]
|
[Route("api/template-routes")]
|
||||||
public sealed class TemplateRoutesController(ISender mediator) : ControllerBase
|
public sealed class TemplateRoutesController(ISender mediator) : ControllerBase
|
||||||
{
|
{
|
||||||
|
[HttpGet("resolve")]
|
||||||
|
public async Task<IActionResult> Resolve(
|
||||||
|
[FromQuery] Guid requestTypeId,
|
||||||
|
[FromQuery] string? userEmail,
|
||||||
|
[FromQuery] Guid? projectId,
|
||||||
|
[FromQuery] RouteStepStatus status,
|
||||||
|
CancellationToken ct) =>
|
||||||
|
(await mediator.Send(new ResolveTemplateRouteQuery(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) =>
|
||||||
(await mediator.Send(new ListTemplateRoutesQuery(requestTypeId), ct)).ToActionResult();
|
(await mediator.Send(new ListTemplateRoutesQuery(requestTypeId), ct)).ToActionResult();
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Tnsu.ApprovalRoute.Domain.Enums;
|
||||||
|
|
||||||
|
namespace Tnsu.ApprovalRoute.Application.TemplateRoutes;
|
||||||
|
|
||||||
|
public sealed record ResolvedRouteStepDto(
|
||||||
|
Guid Id,
|
||||||
|
int OrderNo,
|
||||||
|
string ExecutorEmail,
|
||||||
|
string Position,
|
||||||
|
ProjectRole ProjectRole,
|
||||||
|
HeadType HeadType,
|
||||||
|
RouteStepStatus Status,
|
||||||
|
bool AllowReassignToOtherApprovers);
|
||||||
|
|
||||||
|
public sealed record ResolveTemplateRouteResultDto(
|
||||||
|
Guid RouteId,
|
||||||
|
RouteType RouteType,
|
||||||
|
bool RequiresControl,
|
||||||
|
IReadOnlyList<ResolvedRouteStepDto> Steps);
|
||||||
+95
@@ -0,0 +1,95 @@
|
|||||||
|
using FluentResults;
|
||||||
|
using MediatR;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Tnsu.ApprovalRoute.Application.Common.Errors;
|
||||||
|
using Tnsu.ApprovalRoute.Application.Common.Interfaces;
|
||||||
|
using Tnsu.ApprovalRoute.Domain.Entities;
|
||||||
|
using Tnsu.ApprovalRoute.Domain.Enums;
|
||||||
|
using Tnsu.ApprovalRoute.Infrastructure.Persistence;
|
||||||
|
using Tnsu.ApprovalRoute.Infrastructure.TnsuPortal;
|
||||||
|
|
||||||
|
namespace Tnsu.ApprovalRoute.Application.TemplateRoutes;
|
||||||
|
|
||||||
|
public sealed record ResolveTemplateRouteQuery(
|
||||||
|
Guid RequestTypeId,
|
||||||
|
string? UserEmail,
|
||||||
|
Guid? ProjectId,
|
||||||
|
RouteStepStatus Status) : IRequest<Result<ResolveTemplateRouteResultDto>>;
|
||||||
|
|
||||||
|
public sealed class ResolveTemplateRouteHandler(
|
||||||
|
ApprovalRouteDbContext db,
|
||||||
|
HttpTnsuPortalClient portalClient,
|
||||||
|
ICurrentUser currentUser)
|
||||||
|
: IRequestHandler<ResolveTemplateRouteQuery, Result<ResolveTemplateRouteResultDto>>
|
||||||
|
{
|
||||||
|
public async Task<Result<ResolveTemplateRouteResultDto>> Handle(ResolveTemplateRouteQuery request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
if (request.Status == RouteStepStatus.None)
|
||||||
|
return Result.Fail<ResolveTemplateRouteResultDto>(
|
||||||
|
new ValidationError("Статус обязателен: Approval или Execution."));
|
||||||
|
|
||||||
|
var email = string.IsNullOrWhiteSpace(request.UserEmail) ? currentUser.Email : request.UserEmail;
|
||||||
|
if (string.IsNullOrWhiteSpace(email))
|
||||||
|
return Result.Fail<ResolveTemplateRouteResultDto>(
|
||||||
|
new ValidationError("Не удалось определить email инициатора: не передан явно и отсутствует в токене."));
|
||||||
|
|
||||||
|
var requestTypeExists = await db.RequestTypes.AnyAsync(x => x.Id == request.RequestTypeId, cancellationToken);
|
||||||
|
if (!requestTypeExists)
|
||||||
|
return Result.Fail<ResolveTemplateRouteResultDto>(new NotFoundError("Тип заявки", request.RequestTypeId));
|
||||||
|
|
||||||
|
var employee = await portalClient.GetEmployeeByEmailAsync(email, cancellationToken);
|
||||||
|
var byProject = request.ProjectId is not null;
|
||||||
|
|
||||||
|
var route = await db.TemplateRoutes.AsNoTracking()
|
||||||
|
.Include(x => x.Steps)
|
||||||
|
.Where(x =>
|
||||||
|
x.RequestTypeId == request.RequestTypeId &&
|
||||||
|
x.CompanyId == employee.CompanyId &&
|
||||||
|
x.ByProject == byProject &&
|
||||||
|
x.DepartmentId == employee.DepartmentId)
|
||||||
|
.FirstOrDefaultAsync(cancellationToken)
|
||||||
|
?? await db.TemplateRoutes.AsNoTracking()
|
||||||
|
.Include(x => x.Steps)
|
||||||
|
.Where(x =>
|
||||||
|
x.RequestTypeId == request.RequestTypeId &&
|
||||||
|
x.CompanyId == employee.CompanyId &&
|
||||||
|
x.ByProject == byProject &&
|
||||||
|
x.DepartmentId == null)
|
||||||
|
.FirstOrDefaultAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (route is null)
|
||||||
|
return Result.Fail<ResolveTemplateRouteResultDto>(new NotFoundError(
|
||||||
|
"Маршрут для этого типа заявки, отдела/компании инициатора и признака «по проекту»",
|
||||||
|
request.RequestTypeId));
|
||||||
|
|
||||||
|
IReadOnlyList<ProjectExecutorInfo> projectExecutors = [];
|
||||||
|
if (request.ProjectId is { } projectId)
|
||||||
|
projectExecutors = await portalClient.GetProjectExecutorsAsync(projectId, cancellationToken);
|
||||||
|
|
||||||
|
var steps = route.Steps
|
||||||
|
.Where(s => s.Status == request.Status)
|
||||||
|
.OrderBy(s => s.OrderNo)
|
||||||
|
.Select(s => ResolveStep(s, projectExecutors))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return Result.Ok(new ResolveTemplateRouteResultDto(route.Id, route.RouteType, route.RequiresControl, steps));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ResolvedRouteStepDto ResolveStep(TemplateRouteStep step, IReadOnlyList<ProjectExecutorInfo> projectExecutors)
|
||||||
|
{
|
||||||
|
// Для шагов с ролью в проекте фактический исполнитель приходит из данных проекта (api.tnsu.kz),
|
||||||
|
// а не из статичного ExecutorEmail шаблона — тот используется только для шагов без привязки к проекту.
|
||||||
|
if (step.ProjectRole != ProjectRole.None)
|
||||||
|
{
|
||||||
|
var projectExecutor = projectExecutors.FirstOrDefault(e => e.ProjectRole == step.ProjectRole);
|
||||||
|
if (projectExecutor is not null)
|
||||||
|
return new ResolvedRouteStepDto(
|
||||||
|
step.Id, step.OrderNo, projectExecutor.Email, projectExecutor.Position,
|
||||||
|
step.ProjectRole, step.HeadType, step.Status, step.AllowReassignToOtherApprovers);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ResolvedRouteStepDto(
|
||||||
|
step.Id, step.OrderNo, step.ExecutorEmail, step.Position,
|
||||||
|
step.ProjectRole, step.HeadType, step.Status, step.AllowReassignToOtherApprovers);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user