переход на реальное api

This commit is contained in:
gorbenko.pavel
2026-07-24 12:27:42 +05:00
parent 2759c1ef7d
commit fa13e93190
24 changed files with 271 additions and 99 deletions
@@ -6,22 +6,22 @@ public sealed record TemplateRouteDto(
Guid Id,
RouteType RouteType,
Guid RequestTypeId,
Guid? DepartmentId,
Guid CompanyId,
string? Department,
string Company,
bool ByProject,
bool RequiresControl);
public sealed record CreateTemplateRouteRequest(
RouteType RouteType,
Guid RequestTypeId,
Guid? DepartmentId,
Guid CompanyId,
string? Department,
string Company,
bool ByProject,
bool RequiresControl);
public sealed record UpdateTemplateRouteRequest(
RouteType RouteType,
Guid? DepartmentId,
Guid CompanyId,
string? Department,
string Company,
bool ByProject,
bool RequiresControl);
@@ -11,7 +11,7 @@ namespace Tnsu.ApprovalRoute.Application.TemplateRoutes;
public sealed class TemplateRouteService(
ApprovalRouteDbContext db,
HttpTnsuPortalClient portalClient,
ITnsuPortalClient portalClient,
ICurrentUser currentUser)
{
public async Task<Result<IReadOnlyList<TemplateRouteDto>>> ListAsync(Guid? requestTypeId, CancellationToken cancellationToken)
@@ -22,7 +22,7 @@ public sealed class TemplateRouteService(
var items = await query
.Select(x => new TemplateRouteDto(
x.Id, x.RouteType, x.RequestTypeId, x.DepartmentId, x.CompanyId, x.ByProject, x.RequiresControl))
x.Id, x.RouteType, x.RequestTypeId, x.Department, x.Company, x.ByProject, x.RequiresControl))
.ToListAsync(cancellationToken);
return Result.Ok<IReadOnlyList<TemplateRouteDto>>(items);
@@ -43,7 +43,8 @@ public sealed class TemplateRouteService(
if (!requestTypeExists)
return Result.Fail<TemplateRouteDto>(new NotFoundError("Тип заявки", r.RequestTypeId));
var duplicate = await HasDuplicateAsync(r.RequestTypeId, r.DepartmentId, r.CompanyId, r.ByProject, excludeId: null, cancellationToken);
var department = Normalize(r.Department);
var duplicate = await HasDuplicateAsync(r.RequestTypeId, department, r.Company, r.ByProject, excludeId: null, cancellationToken);
if (duplicate)
return Result.Fail<TemplateRouteDto>(new ConflictError(
"Маршрут с таким типом заявки, отделом, компанией и признаком «по проекту» уже существует."));
@@ -52,8 +53,8 @@ public sealed class TemplateRouteService(
{
RouteType = r.RouteType,
RequestTypeId = r.RequestTypeId,
DepartmentId = r.DepartmentId,
CompanyId = r.CompanyId,
Department = department,
Company = r.Company,
ByProject = r.ByProject,
RequiresControl = r.RequiresControl
};
@@ -69,14 +70,15 @@ public sealed class TemplateRouteService(
if (entity is null)
return Result.Fail<TemplateRouteDto>(new NotFoundError("Маршрут", id));
var duplicate = await HasDuplicateAsync(entity.RequestTypeId, r.DepartmentId, r.CompanyId, r.ByProject, excludeId: entity.Id, cancellationToken);
var department = Normalize(r.Department);
var duplicate = await HasDuplicateAsync(entity.RequestTypeId, department, r.Company, r.ByProject, excludeId: entity.Id, cancellationToken);
if (duplicate)
return Result.Fail<TemplateRouteDto>(new ConflictError(
"Маршрут с таким типом заявки, отделом, компанией и признаком «по проекту» уже существует."));
entity.RouteType = r.RouteType;
entity.DepartmentId = r.DepartmentId;
entity.CompanyId = r.CompanyId;
entity.Department = department;
entity.Company = r.Company;
entity.ByProject = r.ByProject;
entity.RequiresControl = r.RequiresControl;
await db.SaveChangesAsync(cancellationToken);
@@ -109,23 +111,28 @@ public sealed class TemplateRouteService(
return Result.Fail<ResolveTemplateRouteResultDto>(new NotFoundError("Тип заявки", request.RequestTypeId));
var employee = await portalClient.GetEmployeeByEmailAsync(email, cancellationToken);
if (employee is null)
return Result.Fail<ResolveTemplateRouteResultDto>(
new NotFoundError("Сотрудник по email в справочнике api.tnsu.kz не найден", Guid.Empty));
var byProject = request.ProjectId is not null;
var department = Normalize(employee.Department);
var route = await db.TemplateRoutes.AsNoTracking()
.Include(x => x.Steps)
.Where(x =>
x.RequestTypeId == request.RequestTypeId &&
x.CompanyId == employee.CompanyId &&
x.Company == employee.Company &&
x.ByProject == byProject &&
x.DepartmentId == employee.DepartmentId)
x.Department == department)
.FirstOrDefaultAsync(cancellationToken)
?? await db.TemplateRoutes.AsNoTracking()
.Include(x => x.Steps)
.Where(x =>
x.RequestTypeId == request.RequestTypeId &&
x.CompanyId == employee.CompanyId &&
x.Company == employee.Company &&
x.ByProject == byProject &&
x.DepartmentId == null)
x.Department == null)
.FirstOrDefaultAsync(cancellationToken);
if (route is null)
@@ -147,25 +154,25 @@ public sealed class TemplateRouteService(
}
private Task<bool> HasDuplicateAsync(
Guid requestTypeId, Guid? departmentId, Guid companyId, bool byProject, Guid? excludeId, CancellationToken cancellationToken) =>
Guid requestTypeId, string? department, string company, bool byProject, Guid? excludeId, CancellationToken cancellationToken) =>
db.TemplateRoutes.AnyAsync(x =>
x.Id != excludeId &&
x.RequestTypeId == requestTypeId &&
x.DepartmentId == departmentId &&
x.CompanyId == companyId &&
x.Department == department &&
x.Company == company &&
x.ByProject == byProject,
cancellationToken);
private static ResolvedRouteStepDto ResolveStep(TemplateRouteStep step, IReadOnlyList<ProjectExecutorInfo> projectExecutors)
{
// Для шагов с ролью в проекте фактический исполнитель приходит из данных проекта (api.tnsu.kz),
// а не из статичного ExecutorEmail шаблона — тот используется только для шагов без привязки к проекту.
// Для шагов с ролью в проекте фактический исполнитель (email) приходит из данных проекта (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.Id, step.OrderNo, projectExecutor.Email, step.Position,
step.ProjectRole, step.HeadType, step.Status, step.AllowReassignToOtherApprovers);
}
@@ -174,6 +181,8 @@ public sealed class TemplateRouteService(
step.ProjectRole, step.HeadType, step.Status, step.AllowReassignToOtherApprovers);
}
private static string? Normalize(string? value) => string.IsNullOrWhiteSpace(value) ? null : value;
private static TemplateRouteDto ToDto(TemplateRoute e) => new(
e.Id, e.RouteType, e.RequestTypeId, e.DepartmentId, e.CompanyId, e.ByProject, e.RequiresControl);
e.Id, e.RouteType, e.RequestTypeId, e.Department, e.Company, e.ByProject, e.RequiresControl);
}
@@ -7,7 +7,7 @@ public sealed class CreateTemplateRouteRequestValidator : AbstractValidator<Crea
{
public CreateTemplateRouteRequestValidator()
{
RuleFor(x => x.CompanyId).NotEqual(Guid.Empty).WithMessage("Компания обязательна.");
RuleFor(x => x.Company).NotEmpty().WithMessage("Компания обязательна.");
}
}
@@ -15,7 +15,7 @@ public sealed class UpdateTemplateRouteRequestValidator : AbstractValidator<Upda
{
public UpdateTemplateRouteRequestValidator()
{
RuleFor(x => x.CompanyId).NotEqual(Guid.Empty).WithMessage("Компания обязательна.");
RuleFor(x => x.Company).NotEmpty().WithMessage("Компания обязательна.");
}
}