апи для работы с маршрутами согласования #2
+8
-8
@@ -5,10 +5,10 @@ namespace Tnsu.ApprovalRoute.Application.TemplateRouteSteps;
|
|||||||
public sealed record TemplateRouteStepDto(
|
public sealed record TemplateRouteStepDto(
|
||||||
int Id,
|
int Id,
|
||||||
int OrderNo,
|
int OrderNo,
|
||||||
string ExecutorEmail,
|
string? ExecutorEmail,
|
||||||
string Position,
|
string? Position,
|
||||||
ProjectRole ProjectRole,
|
ProjectRole? ProjectRole,
|
||||||
HeadType HeadType,
|
HeadType? HeadType,
|
||||||
RouteStepStatus Status,
|
RouteStepStatus Status,
|
||||||
int RouteId,
|
int RouteId,
|
||||||
bool AllowReassignToOtherApprovers,
|
bool AllowReassignToOtherApprovers,
|
||||||
@@ -18,8 +18,8 @@ public sealed record CreateTemplateRouteStepRequest(
|
|||||||
int OrderNo,
|
int OrderNo,
|
||||||
string? ExecutorEmail,
|
string? ExecutorEmail,
|
||||||
string? Position,
|
string? Position,
|
||||||
ProjectRole ProjectRole,
|
ProjectRole? ProjectRole,
|
||||||
HeadType HeadType,
|
HeadType? HeadType,
|
||||||
RouteStepStatus Status,
|
RouteStepStatus Status,
|
||||||
bool AllowReassignToOtherApprovers);
|
bool AllowReassignToOtherApprovers);
|
||||||
|
|
||||||
@@ -27,8 +27,8 @@ public sealed record UpdateTemplateRouteStepRequest(
|
|||||||
int OrderNo,
|
int OrderNo,
|
||||||
string? ExecutorEmail,
|
string? ExecutorEmail,
|
||||||
string? Position,
|
string? Position,
|
||||||
ProjectRole ProjectRole,
|
ProjectRole? ProjectRole,
|
||||||
HeadType HeadType,
|
HeadType? HeadType,
|
||||||
RouteStepStatus Status,
|
RouteStepStatus Status,
|
||||||
bool AllowReassignToOtherApprovers);
|
bool AllowReassignToOtherApprovers);
|
||||||
|
|
||||||
|
|||||||
+13
-20
@@ -37,15 +37,12 @@ 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));
|
||||||
|
|
||||||
if (!route.ByProject && r.ProjectRole != ProjectRole.None)
|
if (!route.ByProject && r.ProjectRole is not (null or ProjectRole.None))
|
||||||
return Result.Fail<TemplateRouteStepDto>(new ValidationError(
|
return Result.Fail<TemplateRouteStepDto>(new ValidationError(
|
||||||
"Роль в проекте недопустима для шага маршрута, который не учитывает проект (by_project = false)."));
|
"Роль в проекте недопустима для шага маршрута, который не учитывает проект (by_project = false)."));
|
||||||
|
|
||||||
var executorEmail = r.ExecutorEmail ?? string.Empty;
|
|
||||||
var position = r.Position ?? string.Empty;
|
|
||||||
|
|
||||||
var duplicate = await HasDuplicateAsync(
|
var duplicate = await HasDuplicateAsync(
|
||||||
route.Id, executorEmail, position, r.ProjectRole, r.HeadType, r.Status, excludeId: null, cancellationToken);
|
route.Id, r.ExecutorEmail, r.Position, r.ProjectRole, r.HeadType, r.Status, excludeId: null, cancellationToken);
|
||||||
if (duplicate)
|
if (duplicate)
|
||||||
return Result.Fail<TemplateRouteStepDto>(new ConflictError(
|
return Result.Fail<TemplateRouteStepDto>(new ConflictError(
|
||||||
"Шаг с такими исполнителем, должностью, ролью в проекте, типом руководителя и статусом уже существует в этом маршруте."));
|
"Шаг с такими исполнителем, должностью, ролью в проекте, типом руководителя и статусом уже существует в этом маршруте."));
|
||||||
@@ -54,8 +51,8 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
|
|||||||
{
|
{
|
||||||
RouteId = route.Id,
|
RouteId = route.Id,
|
||||||
OrderNo = r.OrderNo,
|
OrderNo = r.OrderNo,
|
||||||
ExecutorEmail = executorEmail,
|
ExecutorEmail = r.ExecutorEmail,
|
||||||
Position = position,
|
Position = r.Position,
|
||||||
ProjectRole = r.ProjectRole,
|
ProjectRole = r.ProjectRole,
|
||||||
HeadType = r.HeadType,
|
HeadType = r.HeadType,
|
||||||
Status = r.Status,
|
Status = r.Status,
|
||||||
@@ -78,22 +75,19 @@ 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));
|
||||||
|
|
||||||
if (!route.ByProject && r.ProjectRole != ProjectRole.None)
|
if (!route.ByProject && r.ProjectRole is not (null or ProjectRole.None))
|
||||||
return Result.Fail<TemplateRouteStepDto>(new ValidationError(
|
return Result.Fail<TemplateRouteStepDto>(new ValidationError(
|
||||||
"Роль в проекте недопустима для шага маршрута, который не учитывает проект (by_project = false)."));
|
"Роль в проекте недопустима для шага маршрута, который не учитывает проект (by_project = false)."));
|
||||||
|
|
||||||
var executorEmail = r.ExecutorEmail ?? string.Empty;
|
|
||||||
var position = r.Position ?? string.Empty;
|
|
||||||
|
|
||||||
var duplicate = await HasDuplicateAsync(
|
var duplicate = await HasDuplicateAsync(
|
||||||
route.Id, executorEmail, position, r.ProjectRole, r.HeadType, r.Status, excludeId: entity.Id, cancellationToken);
|
route.Id, r.ExecutorEmail, r.Position, r.ProjectRole, r.HeadType, r.Status, excludeId: entity.Id, cancellationToken);
|
||||||
if (duplicate)
|
if (duplicate)
|
||||||
return Result.Fail<TemplateRouteStepDto>(new ConflictError(
|
return Result.Fail<TemplateRouteStepDto>(new ConflictError(
|
||||||
"Шаг с такими исполнителем, должностью, ролью в проекте, типом руководителя и статусом уже существует в этом маршруте."));
|
"Шаг с такими исполнителем, должностью, ролью в проекте, типом руководителя и статусом уже существует в этом маршруте."));
|
||||||
|
|
||||||
entity.OrderNo = r.OrderNo;
|
entity.OrderNo = r.OrderNo;
|
||||||
entity.ExecutorEmail = executorEmail;
|
entity.ExecutorEmail = r.ExecutorEmail;
|
||||||
entity.Position = position;
|
entity.Position = r.Position;
|
||||||
entity.ProjectRole = r.ProjectRole;
|
entity.ProjectRole = r.ProjectRole;
|
||||||
entity.HeadType = r.HeadType;
|
entity.HeadType = r.HeadType;
|
||||||
entity.Status = r.Status;
|
entity.Status = r.Status;
|
||||||
@@ -120,14 +114,13 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
|
|||||||
var headType = r.HeadType ?? entity.HeadType;
|
var headType = r.HeadType ?? entity.HeadType;
|
||||||
var status = r.Status ?? entity.Status;
|
var status = r.Status ?? entity.Status;
|
||||||
|
|
||||||
if (!route.ByProject && projectRole != ProjectRole.None)
|
if (!route.ByProject && projectRole is not (null or ProjectRole.None))
|
||||||
return Result.Fail<TemplateRouteStepDto>(new ValidationError(
|
return Result.Fail<TemplateRouteStepDto>(new ValidationError(
|
||||||
"Роль в проекте недопустима для шага маршрута, который не учитывает проект (by_project = false)."));
|
"Роль в проекте недопустима для шага маршрута, который не учитывает проект (by_project = false)."));
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(executorEmail) && string.IsNullOrWhiteSpace(position) &&
|
if (TemplateRouteStepFields.CountFilled(executorEmail, position, projectRole, headType) != 1)
|
||||||
projectRole == ProjectRole.None && headType == HeadType.None)
|
|
||||||
return Result.Fail<TemplateRouteStepDto>(new ValidationError(
|
return Result.Fail<TemplateRouteStepDto>(new ValidationError(
|
||||||
"Должно быть заполнено хотя бы одно из полей: исполнитель, должность, роль в проекте или тип руководителя."));
|
"Должно быть заполнено ровно одно из полей: исполнитель, должность, роль в проекте или тип руководителя."));
|
||||||
|
|
||||||
var duplicate = await HasDuplicateAsync(
|
var duplicate = await HasDuplicateAsync(
|
||||||
route.Id, executorEmail, position, projectRole, headType, status, excludeId: entity.Id, cancellationToken);
|
route.Id, executorEmail, position, projectRole, headType, status, excludeId: entity.Id, cancellationToken);
|
||||||
@@ -160,7 +153,7 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Task<bool> HasDuplicateAsync(
|
private Task<bool> HasDuplicateAsync(
|
||||||
int routeId, string executorEmail, string position, ProjectRole projectRole, HeadType headType,
|
int routeId, string? executorEmail, string? position, ProjectRole? projectRole, HeadType? headType,
|
||||||
RouteStepStatus status, int? excludeId, CancellationToken cancellationToken) =>
|
RouteStepStatus status, int? excludeId, CancellationToken cancellationToken) =>
|
||||||
db.TemplateRouteSteps.AnyAsync(x =>
|
db.TemplateRouteSteps.AnyAsync(x =>
|
||||||
x.Id != excludeId &&
|
x.Id != excludeId &&
|
||||||
@@ -179,7 +172,7 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
|
|||||||
if (route.ByProject)
|
if (route.ByProject)
|
||||||
{
|
{
|
||||||
var hasProjectRoleStep = await db.TemplateRouteSteps
|
var hasProjectRoleStep = await db.TemplateRouteSteps
|
||||||
.AnyAsync(x => x.RouteId == route.Id && x.ProjectRole != ProjectRole.None, cancellationToken);
|
.AnyAsync(x => x.RouteId == route.Id && x.ProjectRole != null && x.ProjectRole != ProjectRole.None, cancellationToken);
|
||||||
if (!hasProjectRoleStep)
|
if (!hasProjectRoleStep)
|
||||||
warning = "Должен быть хотя бы один шаг с ролью в проекте.";
|
warning = "Должен быть хотя бы один шаг с ролью в проекте.";
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-12
@@ -8,12 +8,8 @@ public sealed class CreateTemplateRouteStepRequestValidator : AbstractValidator<
|
|||||||
public CreateTemplateRouteStepRequestValidator()
|
public CreateTemplateRouteStepRequestValidator()
|
||||||
{
|
{
|
||||||
RuleFor(x => x)
|
RuleFor(x => x)
|
||||||
.Must(x =>
|
.Must(x => TemplateRouteStepFields.CountFilled(x.ExecutorEmail, x.Position, x.ProjectRole, x.HeadType) == 1)
|
||||||
!string.IsNullOrWhiteSpace(x.ExecutorEmail) ||
|
.WithMessage("Должно быть заполнено ровно одно из полей: исполнитель, должность, роль в проекте или тип руководителя.");
|
||||||
!string.IsNullOrWhiteSpace(x.Position) ||
|
|
||||||
x.ProjectRole != ProjectRole.None ||
|
|
||||||
x.HeadType != HeadType.None)
|
|
||||||
.WithMessage("Должно быть заполнено хотя бы одно из полей: исполнитель, должность, роль в проекте или тип руководителя.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,11 +18,16 @@ public sealed class UpdateTemplateRouteStepRequestValidator : AbstractValidator<
|
|||||||
public UpdateTemplateRouteStepRequestValidator()
|
public UpdateTemplateRouteStepRequestValidator()
|
||||||
{
|
{
|
||||||
RuleFor(x => x)
|
RuleFor(x => x)
|
||||||
.Must(x =>
|
.Must(x => TemplateRouteStepFields.CountFilled(x.ExecutorEmail, x.Position, x.ProjectRole, x.HeadType) == 1)
|
||||||
!string.IsNullOrWhiteSpace(x.ExecutorEmail) ||
|
.WithMessage("Должно быть заполнено ровно одно из полей: исполнитель, должность, роль в проекте или тип руководителя.");
|
||||||
!string.IsNullOrWhiteSpace(x.Position) ||
|
|
||||||
x.ProjectRole != ProjectRole.None ||
|
|
||||||
x.HeadType != HeadType.None)
|
|
||||||
.WithMessage("Должно быть заполнено хотя бы одно из полей: исполнитель, должность, роль в проекте или тип руководителя.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static class TemplateRouteStepFields
|
||||||
|
{
|
||||||
|
public static int CountFilled(string? executorEmail, string? position, ProjectRole? projectRole, HeadType? headType) =>
|
||||||
|
(string.IsNullOrWhiteSpace(executorEmail) ? 0 : 1) +
|
||||||
|
(string.IsNullOrWhiteSpace(position) ? 0 : 1) +
|
||||||
|
(projectRole is null or ProjectRole.None ? 0 : 1) +
|
||||||
|
(headType is null or HeadType.None ? 0 : 1);
|
||||||
|
}
|
||||||
|
|||||||
+4
-4
@@ -5,10 +5,10 @@ namespace Tnsu.ApprovalRoute.Application.TemplateRoutes;
|
|||||||
public sealed record ResolvedRouteStepDto(
|
public sealed record ResolvedRouteStepDto(
|
||||||
int Id,
|
int Id,
|
||||||
int OrderNo,
|
int OrderNo,
|
||||||
string ExecutorEmail,
|
string? ExecutorEmail,
|
||||||
string Position,
|
string? Position,
|
||||||
ProjectRole ProjectRole,
|
ProjectRole? ProjectRole,
|
||||||
HeadType HeadType,
|
HeadType? HeadType,
|
||||||
RouteStepStatus Status,
|
RouteStepStatus Status,
|
||||||
bool AllowReassignToOtherApprovers);
|
bool AllowReassignToOtherApprovers);
|
||||||
|
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ public sealed class TemplateRouteService(
|
|||||||
|
|
||||||
private static ResolvedRouteStepDto ResolveStep(TemplateRouteStep step, IReadOnlyList<ProjectExecutorInfo> projectExecutors)
|
private static ResolvedRouteStepDto ResolveStep(TemplateRouteStep step, IReadOnlyList<ProjectExecutorInfo> projectExecutors)
|
||||||
{
|
{
|
||||||
if (step.ProjectRole != ProjectRole.None)
|
if (step.ProjectRole is not (null or ProjectRole.None))
|
||||||
{
|
{
|
||||||
var projectExecutor = projectExecutors.FirstOrDefault(e => e.ProjectRole == step.ProjectRole);
|
var projectExecutor = projectExecutors.FirstOrDefault(e => e.ProjectRole == step.ProjectRole);
|
||||||
if (projectExecutor is not null)
|
if (projectExecutor is not null)
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ public class TemplateRouteStep
|
|||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int OrderNo { get; set; }
|
public int OrderNo { get; set; }
|
||||||
|
|
||||||
public string ExecutorEmail { get; set; } = string.Empty;
|
public string? ExecutorEmail { get; set; }
|
||||||
public string Position { get; set; } = string.Empty;
|
public string? Position { get; set; }
|
||||||
public ProjectRole ProjectRole { get; set; } = ProjectRole.None;
|
public ProjectRole? ProjectRole { get; set; }
|
||||||
public HeadType HeadType { get; set; } = HeadType.None;
|
public HeadType? HeadType { get; set; }
|
||||||
|
|
||||||
public RouteStepStatus Status { get; set; } = RouteStepStatus.Approval;
|
public RouteStepStatus Status { get; set; } = RouteStepStatus.Approval;
|
||||||
public int RouteId { get; set; }
|
public int RouteId { get; set; }
|
||||||
|
|||||||
@@ -29,10 +29,10 @@ public class ApprovalRouteDbContext(DbContextOptions<ApprovalRouteDbContext> opt
|
|||||||
modelBuilder.Entity<TemplateRouteStep>(e =>
|
modelBuilder.Entity<TemplateRouteStep>(e =>
|
||||||
{
|
{
|
||||||
e.HasKey(x => x.Id);
|
e.HasKey(x => x.Id);
|
||||||
e.Property(x => x.ExecutorEmail).HasMaxLength(256).IsRequired();
|
e.Property(x => x.ExecutorEmail).HasMaxLength(256);
|
||||||
e.Property(x => x.Position).HasMaxLength(128).IsRequired();
|
e.Property(x => x.Position).HasMaxLength(128);
|
||||||
e.Property(x => x.ProjectRole).HasConversion<string>().HasMaxLength(32).IsRequired();
|
e.Property(x => x.ProjectRole).HasConversion<string>().HasMaxLength(32);
|
||||||
e.Property(x => x.HeadType).HasConversion<string>().HasMaxLength(32).IsRequired();
|
e.Property(x => x.HeadType).HasConversion<string>().HasMaxLength(32);
|
||||||
e.Property(x => x.Status).HasConversion<string>().HasMaxLength(32).IsRequired();
|
e.Property(x => x.Status).HasConversion<string>().HasMaxLength(32).IsRequired();
|
||||||
e.HasOne(x => x.Route).WithMany(x => x.Steps).HasForeignKey(x => x.RouteId);
|
e.HasOne(x => x.Route).WithMany(x => x.Steps).HasForeignKey(x => x.RouteId);
|
||||||
e.HasIndex(x => new { x.RouteId, x.OrderNo });
|
e.HasIndex(x => new { x.RouteId, x.OrderNo });
|
||||||
|
|||||||
+1
-5
@@ -12,7 +12,7 @@ using Tnsu.ApprovalRoute.Infrastructure.Persistence;
|
|||||||
namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
|
namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(ApprovalRouteDbContext))]
|
[DbContext(typeof(ApprovalRouteDbContext))]
|
||||||
[Migration("20260724114550_InitialCreate")]
|
[Migration("20260727103117_InitialCreate")]
|
||||||
partial class InitialCreate
|
partial class InitialCreate
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -111,13 +111,11 @@ namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
|
|||||||
.HasColumnName("allow_reassign_to_other_approvers");
|
.HasColumnName("allow_reassign_to_other_approvers");
|
||||||
|
|
||||||
b.Property<string>("ExecutorEmail")
|
b.Property<string>("ExecutorEmail")
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
.HasMaxLength(256)
|
||||||
.HasColumnType("character varying(256)")
|
.HasColumnType("character varying(256)")
|
||||||
.HasColumnName("executor_email");
|
.HasColumnName("executor_email");
|
||||||
|
|
||||||
b.Property<string>("HeadType")
|
b.Property<string>("HeadType")
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(32)
|
.HasMaxLength(32)
|
||||||
.HasColumnType("character varying(32)")
|
.HasColumnType("character varying(32)")
|
||||||
.HasColumnName("head_type");
|
.HasColumnName("head_type");
|
||||||
@@ -127,13 +125,11 @@ namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
|
|||||||
.HasColumnName("order_no");
|
.HasColumnName("order_no");
|
||||||
|
|
||||||
b.Property<string>("Position")
|
b.Property<string>("Position")
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(128)
|
.HasMaxLength(128)
|
||||||
.HasColumnType("character varying(128)")
|
.HasColumnType("character varying(128)")
|
||||||
.HasColumnName("position");
|
.HasColumnName("position");
|
||||||
|
|
||||||
b.Property<string>("ProjectRole")
|
b.Property<string>("ProjectRole")
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(32)
|
.HasMaxLength(32)
|
||||||
.HasColumnType("character varying(32)")
|
.HasColumnType("character varying(32)")
|
||||||
.HasColumnName("project_role");
|
.HasColumnName("project_role");
|
||||||
+4
-4
@@ -57,10 +57,10 @@ namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
|
|||||||
id = table.Column<int>(type: "integer", nullable: false)
|
id = table.Column<int>(type: "integer", nullable: false)
|
||||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||||
order_no = table.Column<int>(type: "integer", nullable: false),
|
order_no = table.Column<int>(type: "integer", nullable: false),
|
||||||
executor_email = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: false),
|
executor_email = table.Column<string>(type: "character varying(256)", maxLength: 256, nullable: true),
|
||||||
position = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: false),
|
position = table.Column<string>(type: "character varying(128)", maxLength: 128, nullable: true),
|
||||||
project_role = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
project_role = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||||
head_type = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
head_type = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: true),
|
||||||
status = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
status = table.Column<string>(type: "character varying(32)", maxLength: 32, nullable: false),
|
||||||
route_id = table.Column<int>(type: "integer", nullable: false),
|
route_id = table.Column<int>(type: "integer", nullable: false),
|
||||||
allow_reassign_to_other_approvers = table.Column<bool>(type: "boolean", nullable: false)
|
allow_reassign_to_other_approvers = table.Column<bool>(type: "boolean", nullable: false)
|
||||||
-4
@@ -108,13 +108,11 @@ namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
|
|||||||
.HasColumnName("allow_reassign_to_other_approvers");
|
.HasColumnName("allow_reassign_to_other_approvers");
|
||||||
|
|
||||||
b.Property<string>("ExecutorEmail")
|
b.Property<string>("ExecutorEmail")
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(256)
|
.HasMaxLength(256)
|
||||||
.HasColumnType("character varying(256)")
|
.HasColumnType("character varying(256)")
|
||||||
.HasColumnName("executor_email");
|
.HasColumnName("executor_email");
|
||||||
|
|
||||||
b.Property<string>("HeadType")
|
b.Property<string>("HeadType")
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(32)
|
.HasMaxLength(32)
|
||||||
.HasColumnType("character varying(32)")
|
.HasColumnType("character varying(32)")
|
||||||
.HasColumnName("head_type");
|
.HasColumnName("head_type");
|
||||||
@@ -124,13 +122,11 @@ namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
|
|||||||
.HasColumnName("order_no");
|
.HasColumnName("order_no");
|
||||||
|
|
||||||
b.Property<string>("Position")
|
b.Property<string>("Position")
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(128)
|
.HasMaxLength(128)
|
||||||
.HasColumnType("character varying(128)")
|
.HasColumnType("character varying(128)")
|
||||||
.HasColumnName("position");
|
.HasColumnName("position");
|
||||||
|
|
||||||
b.Property<string>("ProjectRole")
|
b.Property<string>("ProjectRole")
|
||||||
.IsRequired()
|
|
||||||
.HasMaxLength(32)
|
.HasMaxLength(32)
|
||||||
.HasColumnType("character varying(32)")
|
.HasColumnType("character varying(32)")
|
||||||
.HasColumnName("project_role");
|
.HasColumnName("project_role");
|
||||||
|
|||||||
+2
-2
@@ -160,7 +160,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
|||||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||||
await stepService.CreateAsync(
|
await stepService.CreateAsync(
|
||||||
route.Value.Id,
|
route.Value.Id,
|
||||||
new CreateTemplateRouteStepRequest(1, "approver@tnsu.kz", "Lead", ProjectRole.None, HeadType.Chief, RouteStepStatus.Approval, false),
|
new CreateTemplateRouteStepRequest(1, "approver@tnsu.kz", "Lead", null, HeadType.Chief, RouteStepStatus.Approval, false),
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
// Action
|
// Action
|
||||||
@@ -187,7 +187,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
|||||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||||
await stepService.CreateAsync(
|
await stepService.CreateAsync(
|
||||||
route.Value.Id,
|
route.Value.Id,
|
||||||
new CreateTemplateRouteStepRequest(1, "", "Lead", ProjectRole.ProjectManager, HeadType.None, RouteStepStatus.Approval, false),
|
new CreateTemplateRouteStepRequest(1, "", "Lead", ProjectRole.ProjectManager, null, RouteStepStatus.Approval, false),
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
// Action
|
// Action
|
||||||
|
|||||||
+59
-16
@@ -42,7 +42,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
|||||||
// Action
|
// Action
|
||||||
var result = await _service.CreateAsync(
|
var result = await _service.CreateAsync(
|
||||||
999999,
|
999999,
|
||||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false),
|
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false),
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
@@ -58,7 +58,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
|||||||
// Action
|
// Action
|
||||||
var result = await _service.CreateAsync(
|
var result = await _service.CreateAsync(
|
||||||
routeId,
|
routeId,
|
||||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.ProjectManager, HeadType.None, RouteStepStatus.Approval, false),
|
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.ProjectManager, null, RouteStepStatus.Approval, false),
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
@@ -70,14 +70,14 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
|||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var routeId = await CreateRouteAsync("Route with duplicate step", byProject: false);
|
var routeId = await CreateRouteAsync("Route with duplicate step", byProject: false);
|
||||||
var request = new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.Chief, RouteStepStatus.Approval, false);
|
var request = new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, HeadType.Chief, RouteStepStatus.Approval, false);
|
||||||
var first = await _service.CreateAsync(routeId, request, CancellationToken.None);
|
var first = await _service.CreateAsync(routeId, request, CancellationToken.None);
|
||||||
first.IsSuccess.ShouldBeTrue();
|
first.IsSuccess.ShouldBeTrue();
|
||||||
|
|
||||||
// Action
|
// Action
|
||||||
var duplicate = await _service.CreateAsync(
|
var duplicate = await _service.CreateAsync(
|
||||||
routeId,
|
routeId,
|
||||||
new CreateTemplateRouteStepRequest(2, "a@b.kz", "Engineer", ProjectRole.None, HeadType.Chief, RouteStepStatus.Approval, true),
|
new CreateTemplateRouteStepRequest(2, "a@b.kz", "Engineer", null, HeadType.Chief, RouteStepStatus.Approval, true),
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
@@ -93,13 +93,13 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
|||||||
// Action
|
// Action
|
||||||
var created = await _service.CreateAsync(
|
var created = await _service.CreateAsync(
|
||||||
routeId,
|
routeId,
|
||||||
new CreateTemplateRouteStepRequest(1, null, null, ProjectRole.None, HeadType.Chief, RouteStepStatus.Approval, false),
|
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false),
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
created.IsSuccess.ShouldBeTrue();
|
created.IsSuccess.ShouldBeTrue();
|
||||||
created.Value.HeadType.ShouldBe(HeadType.Chief);
|
created.Value.HeadType.ShouldBe(HeadType.Chief);
|
||||||
created.Value.ExecutorEmail.ShouldBe(string.Empty);
|
created.Value.ExecutorEmail.ShouldBeNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact(DisplayName = "Создание шага маршрута и получение его по Id возвращает сохранённые данные")]
|
[Fact(DisplayName = "Создание шага маршрута и получение его по Id возвращает сохранённые данные")]
|
||||||
@@ -111,7 +111,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
|||||||
// Action
|
// Action
|
||||||
var created = await _service.CreateAsync(
|
var created = await _service.CreateAsync(
|
||||||
routeId,
|
routeId,
|
||||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false),
|
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false),
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
@@ -131,7 +131,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
|||||||
// Action
|
// Action
|
||||||
var created = await _service.CreateAsync(
|
var created = await _service.CreateAsync(
|
||||||
routeId,
|
routeId,
|
||||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false),
|
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false),
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
@@ -149,7 +149,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
|||||||
// Action
|
// Action
|
||||||
var created = await _service.CreateAsync(
|
var created = await _service.CreateAsync(
|
||||||
routeId,
|
routeId,
|
||||||
new CreateTemplateRouteStepRequest(1, "", "", ProjectRole.ProjectManager, HeadType.None, RouteStepStatus.Approval, false),
|
new CreateTemplateRouteStepRequest(1, "", "", ProjectRole.ProjectManager, null, RouteStepStatus.Approval, false),
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
@@ -164,14 +164,14 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
|||||||
var routeId = await CreateRouteAsync("Route to update step", byProject: false);
|
var routeId = await CreateRouteAsync("Route to update step", byProject: false);
|
||||||
var created = await _service.CreateAsync(
|
var created = await _service.CreateAsync(
|
||||||
routeId,
|
routeId,
|
||||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false),
|
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false),
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
// Action
|
// Action
|
||||||
var updated = await _service.UpdateAsync(
|
var updated = await _service.UpdateAsync(
|
||||||
routeId,
|
routeId,
|
||||||
created.Value.Id,
|
created.Value.Id,
|
||||||
new UpdateTemplateRouteStepRequest(2, "b@b.kz", "Senior Engineer", ProjectRole.None, HeadType.Chief, RouteStepStatus.Execution, true),
|
new UpdateTemplateRouteStepRequest(2, "b@b.kz", "Senior Engineer", null, HeadType.Chief, RouteStepStatus.Execution, true),
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
@@ -188,23 +188,66 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
|||||||
var routeId = await CreateRouteAsync("Route to patch step", byProject: false);
|
var routeId = await CreateRouteAsync("Route to patch step", byProject: false);
|
||||||
var created = await _service.CreateAsync(
|
var created = await _service.CreateAsync(
|
||||||
routeId,
|
routeId,
|
||||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false),
|
new CreateTemplateRouteStepRequest(1, "a@b.kz", null, null, null, RouteStepStatus.Approval, false),
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
// Action
|
// Action
|
||||||
var patched = await _service.PatchAsync(
|
var patched = await _service.PatchAsync(
|
||||||
routeId,
|
routeId,
|
||||||
created.Value.Id,
|
created.Value.Id,
|
||||||
new PatchTemplateRouteStepRequest(OrderNo: null, ExecutorEmail: "c@b.kz", Position: null, ProjectRole: null, HeadType: null, Status: RouteStepStatus.Execution, AllowReassignToOtherApprovers: null),
|
new PatchTemplateRouteStepRequest(OrderNo: 2, ExecutorEmail: null, Position: null, ProjectRole: null, HeadType: null, Status: RouteStepStatus.Execution, AllowReassignToOtherApprovers: null),
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
patched.IsSuccess.ShouldBeTrue();
|
patched.IsSuccess.ShouldBeTrue();
|
||||||
patched.Value.ExecutorEmail.ShouldBe("c@b.kz");
|
patched.Value.ExecutorEmail.ShouldBe("a@b.kz");
|
||||||
patched.Value.Position.ShouldBe("Engineer");
|
patched.Value.OrderNo.ShouldBe(2);
|
||||||
patched.Value.Status.ShouldBe(RouteStepStatus.Execution);
|
patched.Value.Status.ShouldBe(RouteStepStatus.Execution);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact(DisplayName = "Патч, после которого заполнено больше одного из полей исполнителя, возвращает неуспешный результат")]
|
||||||
|
public async Task PatchAsync_ResultsInMoreThanOneFilledField_ReturnsFailedResult()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var routeId = await CreateRouteAsync("Route to patch step into conflict", byProject: false);
|
||||||
|
var created = await _service.CreateAsync(
|
||||||
|
routeId,
|
||||||
|
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
// Action
|
||||||
|
var patched = await _service.PatchAsync(
|
||||||
|
routeId,
|
||||||
|
created.Value.Id,
|
||||||
|
new PatchTemplateRouteStepRequest(OrderNo: null, ExecutorEmail: "a@b.kz", Position: null, ProjectRole: null, HeadType: null, Status: null, AllowReassignToOtherApprovers: null),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
patched.IsFailed.ShouldBeTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact(DisplayName = "Явные ProjectRole.None и HeadType.None при патче считаются незаполненными, как и null")]
|
||||||
|
public async Task PatchAsync_ExplicitNoneValues_TreatedAsUnfilled()
|
||||||
|
{
|
||||||
|
// Arrange
|
||||||
|
var routeId = await CreateRouteAsync("Route with explicit None values on patch", byProject: false);
|
||||||
|
var created = await _service.CreateAsync(
|
||||||
|
routeId,
|
||||||
|
new CreateTemplateRouteStepRequest(1, "a@b.kz", null, null, null, RouteStepStatus.Approval, false),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
// Action
|
||||||
|
var patched = await _service.PatchAsync(
|
||||||
|
routeId,
|
||||||
|
created.Value.Id,
|
||||||
|
new PatchTemplateRouteStepRequest(OrderNo: null, ExecutorEmail: null, Position: null, ProjectRole: ProjectRole.None, HeadType: HeadType.None, Status: null, AllowReassignToOtherApprovers: null),
|
||||||
|
CancellationToken.None);
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
patched.IsSuccess.ShouldBeTrue();
|
||||||
|
patched.Value.ExecutorEmail.ShouldBe("a@b.kz");
|
||||||
|
}
|
||||||
|
|
||||||
[Fact(DisplayName = "Удаление шага маршрута убирает его из базы")]
|
[Fact(DisplayName = "Удаление шага маршрута убирает его из базы")]
|
||||||
public async Task DeleteAsync_RemovesStep()
|
public async Task DeleteAsync_RemovesStep()
|
||||||
{
|
{
|
||||||
@@ -212,7 +255,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
|||||||
var routeId = await CreateRouteAsync("Route to delete step", byProject: false);
|
var routeId = await CreateRouteAsync("Route to delete step", byProject: false);
|
||||||
var created = await _service.CreateAsync(
|
var created = await _service.CreateAsync(
|
||||||
routeId,
|
routeId,
|
||||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.None, HeadType.None, RouteStepStatus.Approval, false),
|
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false),
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
// Action
|
// Action
|
||||||
|
|||||||
Reference in New Issue
Block a user