добавлены новые поля

This commit is contained in:
gorbenko.pavel
2026-07-31 11:54:16 +05:00
parent ea2599732c
commit fdd3787e43
16 changed files with 556 additions and 62 deletions
@@ -1,9 +1,9 @@
namespace Tnsu.ApprovalRoute.Application.RequestTypes; namespace Tnsu.ApprovalRoute.Application.RequestTypes;
public sealed record RequestTypeDto(int Id, string Name, string PageUrl); public sealed record RequestTypeDto(int Id, string Name, string PageUrl, string? ListName, string Section);
public sealed record CreateRequestTypeRequest(string Name, string PageUrl); public sealed record CreateRequestTypeRequest(string Name, string PageUrl, string? ListName, string Section);
public sealed record UpdateRequestTypeRequest(string Name, string PageUrl); public sealed record UpdateRequestTypeRequest(string Name, string PageUrl, string? ListName, string Section);
public sealed record PatchRequestTypeRequest(string? Name, string? PageUrl); public sealed record PatchRequestTypeRequest(string? Name, string? PageUrl, string? ListName, string? Section);
@@ -12,7 +12,7 @@ public sealed class RequestTypeService(ApprovalRouteDbContext db)
{ {
var items = await db.RequestTypes.AsNoTracking() var items = await db.RequestTypes.AsNoTracking()
.OrderBy(x => x.Name) .OrderBy(x => x.Name)
.Select(x => new RequestTypeDto(x.Id, x.Name, x.PageUrl)) .Select(x => new RequestTypeDto(x.Id, x.Name, x.PageUrl, x.ListName, x.Section))
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
return Result.Ok<IReadOnlyList<RequestTypeDto>>(items); return Result.Ok<IReadOnlyList<RequestTypeDto>>(items);
@@ -29,7 +29,13 @@ public sealed class RequestTypeService(ApprovalRouteDbContext db)
public async Task<Result<RequestTypeDto>> CreateAsync(CreateRequestTypeRequest request, CancellationToken cancellationToken) public async Task<Result<RequestTypeDto>> CreateAsync(CreateRequestTypeRequest request, CancellationToken cancellationToken)
{ {
var entity = new RequestType { Name = request.Name, PageUrl = request.PageUrl }; var entity = new RequestType
{
Name = request.Name,
PageUrl = request.PageUrl,
ListName = request.ListName,
Section = request.Section
};
db.RequestTypes.Add(entity); db.RequestTypes.Add(entity);
await db.SaveChangesAsync(cancellationToken); await db.SaveChangesAsync(cancellationToken);
@@ -44,6 +50,8 @@ public sealed class RequestTypeService(ApprovalRouteDbContext db)
entity.Name = request.Name; entity.Name = request.Name;
entity.PageUrl = request.PageUrl; entity.PageUrl = request.PageUrl;
entity.ListName = request.ListName;
entity.Section = request.Section;
await db.SaveChangesAsync(cancellationToken); await db.SaveChangesAsync(cancellationToken);
return Result.Ok(ToDto(entity)); return Result.Ok(ToDto(entity));
@@ -57,6 +65,8 @@ public sealed class RequestTypeService(ApprovalRouteDbContext db)
entity.Name = request.Name ?? entity.Name; entity.Name = request.Name ?? entity.Name;
entity.PageUrl = request.PageUrl ?? entity.PageUrl; entity.PageUrl = request.PageUrl ?? entity.PageUrl;
entity.ListName = request.ListName ?? entity.ListName;
entity.Section = request.Section ?? entity.Section;
await db.SaveChangesAsync(cancellationToken); await db.SaveChangesAsync(cancellationToken);
return Result.Ok(ToDto(entity)); return Result.Ok(ToDto(entity));
@@ -74,5 +84,5 @@ public sealed class RequestTypeService(ApprovalRouteDbContext db)
return Result.Ok(); return Result.Ok();
} }
private static RequestTypeDto ToDto(RequestType e) => new(e.Id, e.Name, e.PageUrl); private static RequestTypeDto ToDto(RequestType e) => new(e.Id, e.Name, e.PageUrl, e.ListName, e.Section);
} }
@@ -8,6 +8,7 @@ public sealed class CreateRequestTypeRequestValidator : AbstractValidator<Create
{ {
RuleFor(x => x.Name).NotEmpty().WithMessage("Имя типа заявки обязательно."); RuleFor(x => x.Name).NotEmpty().WithMessage("Имя типа заявки обязательно.");
RuleFor(x => x.PageUrl).NotEmpty().WithMessage("Url страницы обязателен."); RuleFor(x => x.PageUrl).NotEmpty().WithMessage("Url страницы обязателен.");
RuleFor(x => x.Section).NotEmpty().WithMessage("Раздел обязателен.");
} }
} }
@@ -17,6 +18,7 @@ public sealed class UpdateRequestTypeRequestValidator : AbstractValidator<Update
{ {
RuleFor(x => x.Name).NotEmpty().WithMessage("Имя типа заявки обязательно."); RuleFor(x => x.Name).NotEmpty().WithMessage("Имя типа заявки обязательно.");
RuleFor(x => x.PageUrl).NotEmpty().WithMessage("Url страницы обязателен."); RuleFor(x => x.PageUrl).NotEmpty().WithMessage("Url страницы обязателен.");
RuleFor(x => x.Section).NotEmpty().WithMessage("Раздел обязателен.");
} }
} }
@@ -28,5 +30,7 @@ public sealed class PatchRequestTypeRequestValidator : AbstractValidator<PatchRe
RuleFor(x => x.Name).NotEmpty().WithMessage("Имя типа заявки не может быть пустым.")); RuleFor(x => x.Name).NotEmpty().WithMessage("Имя типа заявки не может быть пустым."));
When(x => x.PageUrl is not null, () => When(x => x.PageUrl is not null, () =>
RuleFor(x => x.PageUrl).NotEmpty().WithMessage("Url страницы не может быть пустым.")); RuleFor(x => x.PageUrl).NotEmpty().WithMessage("Url страницы не может быть пустым."));
When(x => x.Section is not null, () =>
RuleFor(x => x.Section).NotEmpty().WithMessage("Раздел не может быть пустым."));
} }
} }
@@ -12,6 +12,9 @@ public sealed record TemplateRouteStepDto(
RouteStepStatus Status, RouteStepStatus Status,
int RouteId, int RouteId,
bool AllowReassignToOtherApprovers, bool AllowReassignToOtherApprovers,
string? FieldName,
ComparisonOperator? Operation,
string? Value,
string? Warning = null); string? Warning = null);
public sealed record CreateTemplateRouteStepRequest( public sealed record CreateTemplateRouteStepRequest(
@@ -21,7 +24,10 @@ public sealed record CreateTemplateRouteStepRequest(
ProjectRole? ProjectRole, ProjectRole? ProjectRole,
HeadType? HeadType, HeadType? HeadType,
RouteStepStatus Status, RouteStepStatus Status,
bool AllowReassignToOtherApprovers); bool AllowReassignToOtherApprovers,
string? FieldName,
ComparisonOperator? Operation,
string? Value);
public sealed record UpdateTemplateRouteStepRequest( public sealed record UpdateTemplateRouteStepRequest(
int OrderNo, int OrderNo,
@@ -30,7 +36,10 @@ public sealed record UpdateTemplateRouteStepRequest(
ProjectRole? ProjectRole, ProjectRole? ProjectRole,
HeadType? HeadType, HeadType? HeadType,
RouteStepStatus Status, RouteStepStatus Status,
bool AllowReassignToOtherApprovers); bool AllowReassignToOtherApprovers,
string? FieldName,
ComparisonOperator? Operation,
string? Value);
public sealed record PatchTemplateRouteStepRequest( public sealed record PatchTemplateRouteStepRequest(
int? OrderNo, int? OrderNo,
@@ -39,4 +48,7 @@ public sealed record PatchTemplateRouteStepRequest(
ProjectRole? ProjectRole, ProjectRole? ProjectRole,
HeadType? HeadType, HeadType? HeadType,
RouteStepStatus? Status, RouteStepStatus? Status,
bool? AllowReassignToOtherApprovers); bool? AllowReassignToOtherApprovers,
string? FieldName,
ComparisonOperator? Operation,
string? Value);
@@ -15,7 +15,8 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
.Where(x => x.RouteId == routeId) .Where(x => x.RouteId == routeId)
.OrderBy(x => x.OrderNo) .OrderBy(x => x.OrderNo)
.Select(x => new TemplateRouteStepDto( .Select(x => new TemplateRouteStepDto(
x.Id, x.OrderNo, x.ExecutorEmail, x.Position, x.ProjectRole, x.HeadType, x.Status, x.RouteId, x.AllowReassignToOtherApprovers)) x.Id, x.OrderNo, x.ExecutorEmail, x.Position, x.ProjectRole, x.HeadType, x.Status, x.RouteId, x.AllowReassignToOtherApprovers,
x.FieldName, x.Operation, x.Value))
.ToListAsync(cancellationToken); .ToListAsync(cancellationToken);
return Result.Ok<IReadOnlyList<TemplateRouteStepDto>>(items); return Result.Ok<IReadOnlyList<TemplateRouteStepDto>>(items);
@@ -33,7 +34,7 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
public async Task<Result<TemplateRouteStepDto>> CreateAsync(int routeId, CreateTemplateRouteStepRequest r, CancellationToken cancellationToken) public async Task<Result<TemplateRouteStepDto>> CreateAsync(int routeId, CreateTemplateRouteStepRequest r, CancellationToken cancellationToken)
{ {
var route = await db.TemplateRoutes.FirstOrDefaultAsync(x => x.Id == routeId, cancellationToken); var route = await db.TemplateRoutes.Include(x => x.RequestType).FirstOrDefaultAsync(x => x.Id == routeId, cancellationToken);
if (route is null) if (route is null)
return Result.Fail<TemplateRouteStepDto>(new NotFoundError("Маршрут", routeId)); return Result.Fail<TemplateRouteStepDto>(new NotFoundError("Маршрут", routeId));
@@ -41,6 +42,14 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
return Result.Fail<TemplateRouteStepDto>(new ValidationError( return Result.Fail<TemplateRouteStepDto>(new ValidationError(
"Роль в проекте недопустима для шага маршрута, который не учитывает проект (by_project = false).")); "Роль в проекте недопустима для шага маршрута, который не учитывает проект (by_project = false)."));
if (!TemplateRouteStepFields.IsConditionValid(r.FieldName, r.Operation, r.Value))
return Result.Fail<TemplateRouteStepDto>(new ValidationError(
"Поля условия (field_name, operation, value) должны быть заполнены все вместе или не заполнены вовсе."));
if (RequiresListName(r.FieldName, r.Operation, r.Value) && string.IsNullOrWhiteSpace(route.RequestType?.ListName))
return Result.Fail<TemplateRouteStepDto>(new ValidationError(
"Поля условия (field_name, operation, value) можно указывать только если у типа заявки указано имя списка (list_name)."));
var duplicate = await HasDuplicateAsync( var duplicate = await HasDuplicateAsync(
route.Id, r.ExecutorEmail, r.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)
@@ -56,7 +65,10 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
ProjectRole = r.ProjectRole, ProjectRole = r.ProjectRole,
HeadType = r.HeadType, HeadType = r.HeadType,
Status = r.Status, Status = r.Status,
AllowReassignToOtherApprovers = r.AllowReassignToOtherApprovers AllowReassignToOtherApprovers = r.AllowReassignToOtherApprovers,
FieldName = r.FieldName,
Operation = r.Operation,
Value = r.Value
}; };
db.TemplateRouteSteps.Add(entity); db.TemplateRouteSteps.Add(entity);
await db.SaveChangesAsync(cancellationToken); await db.SaveChangesAsync(cancellationToken);
@@ -67,7 +79,7 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
public async Task<Result<TemplateRouteStepDto>> UpdateAsync( public async Task<Result<TemplateRouteStepDto>> UpdateAsync(
int routeId, int id, UpdateTemplateRouteStepRequest r, CancellationToken cancellationToken) int routeId, int id, UpdateTemplateRouteStepRequest r, CancellationToken cancellationToken)
{ {
var route = await db.TemplateRoutes.FirstOrDefaultAsync(x => x.Id == routeId, cancellationToken); var route = await db.TemplateRoutes.Include(x => x.RequestType).FirstOrDefaultAsync(x => x.Id == routeId, cancellationToken);
if (route is null) if (route is null)
return Result.Fail<TemplateRouteStepDto>(new NotFoundError("Маршрут", routeId)); return Result.Fail<TemplateRouteStepDto>(new NotFoundError("Маршрут", routeId));
@@ -79,6 +91,14 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
return Result.Fail<TemplateRouteStepDto>(new ValidationError( return Result.Fail<TemplateRouteStepDto>(new ValidationError(
"Роль в проекте недопустима для шага маршрута, который не учитывает проект (by_project = false).")); "Роль в проекте недопустима для шага маршрута, который не учитывает проект (by_project = false)."));
if (!TemplateRouteStepFields.IsConditionValid(r.FieldName, r.Operation, r.Value))
return Result.Fail<TemplateRouteStepDto>(new ValidationError(
"Поля условия (field_name, operation, value) должны быть заполнены все вместе или не заполнены вовсе."));
if (RequiresListName(r.FieldName, r.Operation, r.Value) && string.IsNullOrWhiteSpace(route.RequestType?.ListName))
return Result.Fail<TemplateRouteStepDto>(new ValidationError(
"Поля условия (field_name, operation, value) можно указывать только если у типа заявки указано имя списка (list_name)."));
var duplicate = await HasDuplicateAsync( var duplicate = await HasDuplicateAsync(
route.Id, r.ExecutorEmail, r.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)
@@ -92,6 +112,9 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
entity.HeadType = r.HeadType; entity.HeadType = r.HeadType;
entity.Status = r.Status; entity.Status = r.Status;
entity.AllowReassignToOtherApprovers = r.AllowReassignToOtherApprovers; entity.AllowReassignToOtherApprovers = r.AllowReassignToOtherApprovers;
entity.FieldName = r.FieldName;
entity.Operation = r.Operation;
entity.Value = r.Value;
await db.SaveChangesAsync(cancellationToken); await db.SaveChangesAsync(cancellationToken);
return Result.Ok(await ToDtoWithWarningAsync(entity, route, cancellationToken)); return Result.Ok(await ToDtoWithWarningAsync(entity, route, cancellationToken));
@@ -100,7 +123,7 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
public async Task<Result<TemplateRouteStepDto>> PatchAsync( public async Task<Result<TemplateRouteStepDto>> PatchAsync(
int routeId, int id, PatchTemplateRouteStepRequest r, CancellationToken cancellationToken) int routeId, int id, PatchTemplateRouteStepRequest r, CancellationToken cancellationToken)
{ {
var route = await db.TemplateRoutes.FirstOrDefaultAsync(x => x.Id == routeId, cancellationToken); var route = await db.TemplateRoutes.Include(x => x.RequestType).FirstOrDefaultAsync(x => x.Id == routeId, cancellationToken);
if (route is null) if (route is null)
return Result.Fail<TemplateRouteStepDto>(new NotFoundError("Маршрут", routeId)); return Result.Fail<TemplateRouteStepDto>(new NotFoundError("Маршрут", routeId));
@@ -113,6 +136,9 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
var projectRole = r.ProjectRole ?? entity.ProjectRole; var projectRole = r.ProjectRole ?? entity.ProjectRole;
var headType = r.HeadType ?? entity.HeadType; var headType = r.HeadType ?? entity.HeadType;
var status = r.Status ?? entity.Status; var status = r.Status ?? entity.Status;
var fieldName = r.FieldName ?? entity.FieldName;
var operation = r.Operation ?? entity.Operation;
var value = r.Value ?? entity.Value;
if (status == RouteStepStatus.None) if (status == RouteStepStatus.None)
return Result.Fail<TemplateRouteStepDto>(new ValidationError("Статус обязателен: Approval или Execution.")); return Result.Fail<TemplateRouteStepDto>(new ValidationError("Статус обязателен: Approval или Execution."));
@@ -125,6 +151,14 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
return Result.Fail<TemplateRouteStepDto>(new ValidationError( return Result.Fail<TemplateRouteStepDto>(new ValidationError(
"Должно быть заполнено ровно одно из полей: исполнитель, должность, роль в проекте или тип руководителя.")); "Должно быть заполнено ровно одно из полей: исполнитель, должность, роль в проекте или тип руководителя."));
if (!TemplateRouteStepFields.IsConditionValid(fieldName, operation, value))
return Result.Fail<TemplateRouteStepDto>(new ValidationError(
"Поля условия (field_name, operation, value) должны быть заполнены все вместе или не заполнены вовсе."));
if (RequiresListName(fieldName, operation, value) && string.IsNullOrWhiteSpace(route.RequestType?.ListName))
return Result.Fail<TemplateRouteStepDto>(new ValidationError(
"Поля условия (field_name, operation, value) можно указывать только если у типа заявки указано имя списка (list_name)."));
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);
if (duplicate) if (duplicate)
@@ -138,6 +172,9 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
entity.HeadType = headType; entity.HeadType = headType;
entity.Status = status; entity.Status = status;
entity.AllowReassignToOtherApprovers = r.AllowReassignToOtherApprovers ?? entity.AllowReassignToOtherApprovers; entity.AllowReassignToOtherApprovers = r.AllowReassignToOtherApprovers ?? entity.AllowReassignToOtherApprovers;
entity.FieldName = fieldName;
entity.Operation = operation;
entity.Value = value;
await db.SaveChangesAsync(cancellationToken); await db.SaveChangesAsync(cancellationToken);
return Result.Ok(await ToDtoWithWarningAsync(entity, route, cancellationToken)); return Result.Ok(await ToDtoWithWarningAsync(entity, route, cancellationToken));
@@ -155,6 +192,9 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
return Result.Ok(); return Result.Ok();
} }
private static bool RequiresListName(string? fieldName, ComparisonOperator? operation, string? value) =>
TemplateRouteStepFields.CountConditionFilled(fieldName, operation, value) > 0;
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) =>
@@ -184,5 +224,6 @@ public sealed class TemplateRouteStepService(ApprovalRouteDbContext db)
} }
private static TemplateRouteStepDto ToDto(TemplateRouteStep e) => new( private static TemplateRouteStepDto ToDto(TemplateRouteStep e) => new(
e.Id, e.OrderNo, e.ExecutorEmail, e.Position, e.ProjectRole, e.HeadType, e.Status, e.RouteId, e.AllowReassignToOtherApprovers); e.Id, e.OrderNo, e.ExecutorEmail, e.Position, e.ProjectRole, e.HeadType, e.Status, e.RouteId, e.AllowReassignToOtherApprovers,
e.FieldName, e.Operation, e.Value);
} }
@@ -11,6 +11,9 @@ public sealed class CreateTemplateRouteStepRequestValidator : AbstractValidator<
RuleFor(x => x) RuleFor(x => x)
.Must(x => TemplateRouteStepFields.CountFilled(x.ExecutorEmail, x.Position, x.ProjectRole, x.HeadType) == 1) .Must(x => TemplateRouteStepFields.CountFilled(x.ExecutorEmail, x.Position, x.ProjectRole, x.HeadType) == 1)
.WithMessage("Должно быть заполнено ровно одно из полей: исполнитель, должность, роль в проекте или тип руководителя."); .WithMessage("Должно быть заполнено ровно одно из полей: исполнитель, должность, роль в проекте или тип руководителя.");
RuleFor(x => x)
.Must(x => TemplateRouteStepFields.IsConditionValid(x.FieldName, x.Operation, x.Value))
.WithMessage("Поля условия (field_name, operation, value) должны быть заполнены все вместе или не заполнены вовсе.");
} }
} }
@@ -22,6 +25,9 @@ public sealed class UpdateTemplateRouteStepRequestValidator : AbstractValidator<
RuleFor(x => x) RuleFor(x => x)
.Must(x => TemplateRouteStepFields.CountFilled(x.ExecutorEmail, x.Position, x.ProjectRole, x.HeadType) == 1) .Must(x => TemplateRouteStepFields.CountFilled(x.ExecutorEmail, x.Position, x.ProjectRole, x.HeadType) == 1)
.WithMessage("Должно быть заполнено ровно одно из полей: исполнитель, должность, роль в проекте или тип руководителя."); .WithMessage("Должно быть заполнено ровно одно из полей: исполнитель, должность, роль в проекте или тип руководителя.");
RuleFor(x => x)
.Must(x => TemplateRouteStepFields.IsConditionValid(x.FieldName, x.Operation, x.Value))
.WithMessage("Поля условия (field_name, operation, value) должны быть заполнены все вместе или не заполнены вовсе.");
} }
} }
@@ -32,4 +38,15 @@ internal static class TemplateRouteStepFields
(string.IsNullOrWhiteSpace(position) ? 0 : 1) + (string.IsNullOrWhiteSpace(position) ? 0 : 1) +
(projectRole is null or ProjectRole.None ? 0 : 1) + (projectRole is null or ProjectRole.None ? 0 : 1) +
(headType is null or HeadType.None ? 0 : 1); (headType is null or HeadType.None ? 0 : 1);
public static int CountConditionFilled(string? fieldName, ComparisonOperator? operation, string? value) =>
(string.IsNullOrWhiteSpace(fieldName) ? 0 : 1) +
(operation is null or ComparisonOperator.None ? 0 : 1) +
(string.IsNullOrWhiteSpace(value) ? 0 : 1);
public static bool IsConditionValid(string? fieldName, ComparisonOperator? operation, string? value)
{
var count = CountConditionFilled(fieldName, operation, value);
return count == 0 || count == 3;
}
} }
@@ -5,6 +5,8 @@ public class RequestType
public int Id { get; set; } public int Id { get; set; }
public string Name { get; set; } = string.Empty; public string Name { get; set; } = string.Empty;
public string PageUrl { get; set; } = string.Empty; public string PageUrl { get; set; } = string.Empty;
public string? ListName { get; set; }
public string Section { get; set; } = string.Empty;
public ICollection<TemplateRoute> Routes { get; set; } = new List<TemplateRoute>(); public ICollection<TemplateRoute> Routes { get; set; } = new List<TemplateRoute>();
} }
@@ -16,5 +16,9 @@ public class TemplateRouteStep
public int RouteId { get; set; } public int RouteId { get; set; }
public bool AllowReassignToOtherApprovers { get; set; } public bool AllowReassignToOtherApprovers { get; set; }
public string? FieldName { get; set; }
public ComparisonOperator? Operation { get; set; }
public string? Value { get; set; }
public TemplateRoute? Route { get; set; } public TemplateRoute? Route { get; set; }
} }
@@ -0,0 +1,9 @@
namespace Tnsu.ApprovalRoute.Domain.Enums;
public enum ComparisonOperator
{
None,
GreaterThan,
LessThan,
Equal
}
@@ -16,6 +16,8 @@ public class ApprovalRouteDbContext(DbContextOptions<ApprovalRouteDbContext> opt
e.HasKey(x => x.Id); e.HasKey(x => x.Id);
e.Property(x => x.Name).HasMaxLength(256).IsRequired(); e.Property(x => x.Name).HasMaxLength(256).IsRequired();
e.Property(x => x.PageUrl).HasMaxLength(512).IsRequired(); e.Property(x => x.PageUrl).HasMaxLength(512).IsRequired();
e.Property(x => x.ListName).HasMaxLength(256);
e.Property(x => x.Section).HasMaxLength(256).IsRequired();
}); });
modelBuilder.Entity<TemplateRoute>(e => modelBuilder.Entity<TemplateRoute>(e =>
@@ -34,6 +36,9 @@ public class ApprovalRouteDbContext(DbContextOptions<ApprovalRouteDbContext> opt
e.Property(x => x.ProjectRole).HasConversion<string>().HasMaxLength(32); e.Property(x => x.ProjectRole).HasConversion<string>().HasMaxLength(32);
e.Property(x => x.HeadType).HasConversion<string>().HasMaxLength(32); 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.Property(x => x.FieldName).HasMaxLength(128);
e.Property(x => x.Operation).HasConversion<string>().HasMaxLength(32);
e.Property(x => x.Value).HasMaxLength(256);
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 });
}); });
@@ -0,0 +1,218 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using Tnsu.ApprovalRoute.Infrastructure.Persistence;
#nullable disable
namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
{
[DbContext(typeof(ApprovalRouteDbContext))]
[Migration("20260731062407_AddRequestTypeListNameSectionAndStepConditionFields")]
partial class AddRequestTypeListNameSectionAndStepConditionFields
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "10.0.10")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.RequestType", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ListName")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("list_name");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("name");
b.Property<string>("PageUrl")
.IsRequired()
.HasMaxLength(512)
.HasColumnType("character varying(512)")
.HasColumnName("page_url");
b.Property<string>("Section")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("section");
b.HasKey("Id")
.HasName("pk_request_types");
b.ToTable("request_types", (string)null);
});
modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.TemplateRoute", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("ByProject")
.HasColumnType("boolean")
.HasColumnName("by_project");
b.Property<Guid>("CompanyId")
.HasColumnType("uuid")
.HasColumnName("company_id");
b.Property<Guid?>("DepartmentId")
.HasColumnType("uuid")
.HasColumnName("department_id");
b.Property<int>("RequestTypeId")
.HasColumnType("integer")
.HasColumnName("request_type_id");
b.Property<bool>("RequiresControl")
.HasColumnType("boolean")
.HasColumnName("requires_control");
b.Property<string>("RouteType")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("route_type");
b.HasKey("Id")
.HasName("pk_template_routes");
b.HasIndex("RequestTypeId", "DepartmentId", "CompanyId", "ByProject")
.IsUnique()
.HasDatabaseName("ix_template_routes_request_type_id_department_id_company_id_by");
b.ToTable("template_routes", (string)null);
});
modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.TemplateRouteStep", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("integer")
.HasColumnName("id");
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<bool>("AllowReassignToOtherApprovers")
.HasColumnType("boolean")
.HasColumnName("allow_reassign_to_other_approvers");
b.Property<string>("ExecutorEmail")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("executor_email");
b.Property<string>("FieldName")
.HasMaxLength(128)
.HasColumnType("character varying(128)")
.HasColumnName("field_name");
b.Property<string>("HeadType")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("head_type");
b.Property<string>("Operation")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("operation");
b.Property<int>("OrderNo")
.HasColumnType("integer")
.HasColumnName("order_no");
b.Property<string>("Position")
.HasMaxLength(128)
.HasColumnType("character varying(128)")
.HasColumnName("position");
b.Property<string>("ProjectRole")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("project_role");
b.Property<int>("RouteId")
.HasColumnType("integer")
.HasColumnName("route_id");
b.Property<string>("Status")
.IsRequired()
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("status");
b.Property<string>("Value")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("value");
b.HasKey("Id")
.HasName("pk_template_route_steps");
b.HasIndex("RouteId", "OrderNo")
.HasDatabaseName("ix_template_route_steps_route_id_order_no");
b.ToTable("template_route_steps", (string)null);
});
modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.TemplateRoute", b =>
{
b.HasOne("Tnsu.ApprovalRoute.Domain.Entities.RequestType", "RequestType")
.WithMany("Routes")
.HasForeignKey("RequestTypeId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_template_routes_request_types_request_type_id");
b.Navigation("RequestType");
});
modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.TemplateRouteStep", b =>
{
b.HasOne("Tnsu.ApprovalRoute.Domain.Entities.TemplateRoute", "Route")
.WithMany("Steps")
.HasForeignKey("RouteId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired()
.HasConstraintName("fk_template_route_steps_template_routes_route_id");
b.Navigation("Route");
});
modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.RequestType", b =>
{
b.Navigation("Routes");
});
modelBuilder.Entity("Tnsu.ApprovalRoute.Domain.Entities.TemplateRoute", b =>
{
b.Navigation("Steps");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,74 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddRequestTypeListNameSectionAndStepConditionFields : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "field_name",
table: "template_route_steps",
type: "character varying(128)",
maxLength: 128,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "operation",
table: "template_route_steps",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "value",
table: "template_route_steps",
type: "character varying(256)",
maxLength: 256,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "list_name",
table: "request_types",
type: "character varying(256)",
maxLength: 256,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "section",
table: "request_types",
type: "character varying(256)",
maxLength: 256,
nullable: false,
defaultValue: "");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "field_name",
table: "template_route_steps");
migrationBuilder.DropColumn(
name: "operation",
table: "template_route_steps");
migrationBuilder.DropColumn(
name: "value",
table: "template_route_steps");
migrationBuilder.DropColumn(
name: "list_name",
table: "request_types");
migrationBuilder.DropColumn(
name: "section",
table: "request_types");
}
}
}
@@ -31,6 +31,11 @@ namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id")); NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
b.Property<string>("ListName")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("list_name");
b.Property<string>("Name") b.Property<string>("Name")
.IsRequired() .IsRequired()
.HasMaxLength(256) .HasMaxLength(256)
@@ -43,6 +48,12 @@ namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
.HasColumnType("character varying(512)") .HasColumnType("character varying(512)")
.HasColumnName("page_url"); .HasColumnName("page_url");
b.Property<string>("Section")
.IsRequired()
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("section");
b.HasKey("Id") b.HasKey("Id")
.HasName("pk_request_types"); .HasName("pk_request_types");
@@ -112,11 +123,21 @@ namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
.HasColumnType("character varying(256)") .HasColumnType("character varying(256)")
.HasColumnName("executor_email"); .HasColumnName("executor_email");
b.Property<string>("FieldName")
.HasMaxLength(128)
.HasColumnType("character varying(128)")
.HasColumnName("field_name");
b.Property<string>("HeadType") b.Property<string>("HeadType")
.HasMaxLength(32) .HasMaxLength(32)
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("head_type"); .HasColumnName("head_type");
b.Property<string>("Operation")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("operation");
b.Property<int>("OrderNo") b.Property<int>("OrderNo")
.HasColumnType("integer") .HasColumnType("integer")
.HasColumnName("order_no"); .HasColumnName("order_no");
@@ -141,6 +162,11 @@ namespace Tnsu.ApprovalRoute.Infrastructure.Persistence.Migrations
.HasColumnType("character varying(32)") .HasColumnType("character varying(32)")
.HasColumnName("status"); .HasColumnName("status");
b.Property<string>("Value")
.HasMaxLength(256)
.HasColumnType("character varying(256)")
.HasColumnName("value");
b.HasKey("Id") b.HasKey("Id")
.HasName("pk_template_route_steps"); .HasName("pk_template_route_steps");
@@ -13,7 +13,7 @@ public sealed class RequestTypeServiceTests(PostgresDatabaseFixture fixture) : I
public async Task CreateAsync_Then_GetAsync_ReturnsCreatedEntity() public async Task CreateAsync_Then_GetAsync_ReturnsCreatedEntity()
{ {
// Action // Action
var created = await _service.CreateAsync(new CreateRequestTypeRequest("Vacation", "/vacation"), CancellationToken.None); var created = await _service.CreateAsync(new CreateRequestTypeRequest("Vacation", "/vacation", null, "General"), CancellationToken.None);
// Assert // Assert
created.IsSuccess.ShouldBeTrue(); created.IsSuccess.ShouldBeTrue();
@@ -43,11 +43,11 @@ public sealed class RequestTypeServiceTests(PostgresDatabaseFixture fixture) : I
public async Task UpdateAsync_ChangesFields() public async Task UpdateAsync_ChangesFields()
{ {
// Arrange // Arrange
var created = await _service.CreateAsync(new CreateRequestTypeRequest("Old name", "/old"), CancellationToken.None); var created = await _service.CreateAsync(new CreateRequestTypeRequest("Old name", "/old", null, "General"), CancellationToken.None);
// Action // Action
var updated = await _service.UpdateAsync( var updated = await _service.UpdateAsync(
created.Value.Id, new UpdateRequestTypeRequest("New name", "/new"), CancellationToken.None); created.Value.Id, new UpdateRequestTypeRequest("New name", "/new", null, "General"), CancellationToken.None);
// Assert // Assert
updated.IsSuccess.ShouldBeTrue(); updated.IsSuccess.ShouldBeTrue();
@@ -60,7 +60,7 @@ public sealed class RequestTypeServiceTests(PostgresDatabaseFixture fixture) : I
{ {
// Action // Action
var result = await _service.UpdateAsync( var result = await _service.UpdateAsync(
999999, new UpdateRequestTypeRequest("X", "/x"), CancellationToken.None); 999999, new UpdateRequestTypeRequest("X", "/x", null, "General"), CancellationToken.None);
// Assert // Assert
result.IsFailed.ShouldBeTrue(); result.IsFailed.ShouldBeTrue();
@@ -70,11 +70,11 @@ public sealed class RequestTypeServiceTests(PostgresDatabaseFixture fixture) : I
public async Task PatchAsync_UpdatesOnlyProvidedFields() public async Task PatchAsync_UpdatesOnlyProvidedFields()
{ {
// Arrange // Arrange
var created = await _service.CreateAsync(new CreateRequestTypeRequest("Original name", "/original"), CancellationToken.None); var created = await _service.CreateAsync(new CreateRequestTypeRequest("Original name", "/original", null, "General"), CancellationToken.None);
// Action // Action
var patched = await _service.PatchAsync( var patched = await _service.PatchAsync(
created.Value.Id, new PatchRequestTypeRequest(Name: "Patched name", PageUrl: null), CancellationToken.None); created.Value.Id, new PatchRequestTypeRequest(Name: "Patched name", PageUrl: null, ListName: null, Section: null), CancellationToken.None);
// Assert // Assert
patched.IsSuccess.ShouldBeTrue(); patched.IsSuccess.ShouldBeTrue();
@@ -87,7 +87,7 @@ public sealed class RequestTypeServiceTests(PostgresDatabaseFixture fixture) : I
{ {
// Action // Action
var result = await _service.PatchAsync( var result = await _service.PatchAsync(
999999, new PatchRequestTypeRequest(Name: "X", PageUrl: null), CancellationToken.None); 999999, new PatchRequestTypeRequest(Name: "X", PageUrl: null, ListName: null, Section: null), CancellationToken.None);
// Assert // Assert
result.IsFailed.ShouldBeTrue(); result.IsFailed.ShouldBeTrue();
@@ -97,7 +97,7 @@ public sealed class RequestTypeServiceTests(PostgresDatabaseFixture fixture) : I
public async Task DeleteAsync_RemovesEntity() public async Task DeleteAsync_RemovesEntity()
{ {
// Arrange // Arrange
var created = await _service.CreateAsync(new CreateRequestTypeRequest("Temp", "/temp"), CancellationToken.None); var created = await _service.CreateAsync(new CreateRequestTypeRequest("Temp", "/temp", null, "General"), CancellationToken.None);
// Action // Action
var deleted = await _service.DeleteAsync(created.Value.Id, CancellationToken.None); var deleted = await _service.DeleteAsync(created.Value.Id, CancellationToken.None);
@@ -113,7 +113,7 @@ public sealed class RequestTypeServiceTests(PostgresDatabaseFixture fixture) : I
public async Task ListAsync_IncludesCreatedEntity() public async Task ListAsync_IncludesCreatedEntity()
{ {
// Arrange // Arrange
var created = await _service.CreateAsync(new CreateRequestTypeRequest("Listed type", "/listed"), CancellationToken.None); var created = await _service.CreateAsync(new CreateRequestTypeRequest("Listed type", "/listed", null, "General"), CancellationToken.None);
// Action // Action
var list = await _service.ListAsync(CancellationToken.None); var list = await _service.ListAsync(CancellationToken.None);
@@ -38,7 +38,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
} }
private async Task<int> CreateRequestTypeAsync(string name) => private async Task<int> CreateRequestTypeAsync(string name) =>
(await _requestTypeService.CreateAsync(new CreateRequestTypeRequest(name, $"/{name}"), CancellationToken.None)).Value.Id; (await _requestTypeService.CreateAsync(new CreateRequestTypeRequest(name, $"/{name}", null, "General"), CancellationToken.None)).Value.Id;
[Fact(DisplayName = "Создание маршрута и получение его по Id возвращает сохранённые данные")] [Fact(DisplayName = "Создание маршрута и получение его по Id возвращает сохранённые данные")]
public async Task CreateAsync_ThenGetAsync_ReturnsCreatedEntity() public async Task CreateAsync_ThenGetAsync_ReturnsCreatedEntity()
@@ -163,7 +163,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", null, null, null, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "approver@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, null, null, null),
CancellationToken.None); CancellationToken.None);
// Action // Action
@@ -196,22 +196,22 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
var stepService = services.GetRequiredService<TemplateRouteStepService>(); var stepService = services.GetRequiredService<TemplateRouteStepService>();
var requestTypeId = (await requestTypeService.CreateAsync( var requestTypeId = (await requestTypeService.CreateAsync(
new CreateRequestTypeRequest("Collapse duplicates request", "/collapse-duplicates"), CancellationToken.None)).Value.Id; new CreateRequestTypeRequest("Collapse duplicates request", "/collapse-duplicates", null, "General"), CancellationToken.None)).Value.Id;
var route = await routeService.CreateAsync( var route = await routeService.CreateAsync(
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false), new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false),
CancellationToken.None); CancellationToken.None);
await stepService.CreateAsync( await stepService.CreateAsync(
route.Value.Id, route.Value.Id,
new CreateTemplateRouteStepRequest(1, "dup@tnsu.kz", null, null, null, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "dup@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, null, null, null),
CancellationToken.None); CancellationToken.None);
await stepService.CreateAsync( await stepService.CreateAsync(
route.Value.Id, route.Value.Id,
new CreateTemplateRouteStepRequest(2, "other@tnsu.kz", null, null, null, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(2, "other@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, null, null, null),
CancellationToken.None); CancellationToken.None);
await stepService.CreateAsync( await stepService.CreateAsync(
route.Value.Id, route.Value.Id,
new CreateTemplateRouteStepRequest(3, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(3, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false, null, null, null),
CancellationToken.None); CancellationToken.None);
// Action // Action
@@ -240,7 +240,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, null, null, ProjectRole.ProjectManager, null, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, null, null, ProjectRole.ProjectManager, null, RouteStepStatus.Approval, false, null, null, null),
CancellationToken.None); CancellationToken.None);
// Action // Action
@@ -272,13 +272,13 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
var stepService = services.GetRequiredService<TemplateRouteStepService>(); var stepService = services.GetRequiredService<TemplateRouteStepService>();
var requestTypeId = (await requestTypeService.CreateAsync( var requestTypeId = (await requestTypeService.CreateAsync(
new CreateRequestTypeRequest("Position request", "/position-request"), CancellationToken.None)).Value.Id; new CreateRequestTypeRequest("Position request", "/position-request", null, "General"), CancellationToken.None)).Value.Id;
var route = await routeService.CreateAsync( var route = await routeService.CreateAsync(
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false), new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false),
CancellationToken.None); CancellationToken.None);
await stepService.CreateAsync( await stepService.CreateAsync(
route.Value.Id, route.Value.Id,
new CreateTemplateRouteStepRequest(1, null, "Lead", null, null, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, null, "Lead", null, null, RouteStepStatus.Approval, false, null, null, null),
CancellationToken.None); CancellationToken.None);
// Action // Action
@@ -311,13 +311,13 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
var stepService = services.GetRequiredService<TemplateRouteStepService>(); var stepService = services.GetRequiredService<TemplateRouteStepService>();
var requestTypeId = (await requestTypeService.CreateAsync( var requestTypeId = (await requestTypeService.CreateAsync(
new CreateRequestTypeRequest("Chief request", "/chief-request"), CancellationToken.None)).Value.Id; new CreateRequestTypeRequest("Chief request", "/chief-request", null, "General"), CancellationToken.None)).Value.Id;
var route = await routeService.CreateAsync( var route = await routeService.CreateAsync(
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false), new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false),
CancellationToken.None); CancellationToken.None);
await stepService.CreateAsync( await stepService.CreateAsync(
route.Value.Id, route.Value.Id,
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false, null, null, null),
CancellationToken.None); CancellationToken.None);
// Action // Action
@@ -346,13 +346,13 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
var stepService = services.GetRequiredService<TemplateRouteStepService>(); var stepService = services.GetRequiredService<TemplateRouteStepService>();
var requestTypeId = (await requestTypeService.CreateAsync( var requestTypeId = (await requestTypeService.CreateAsync(
new CreateRequestTypeRequest("DirectorTansu request", "/director-tansu-request"), CancellationToken.None)).Value.Id; new CreateRequestTypeRequest("DirectorTansu request", "/director-tansu-request", null, "General"), CancellationToken.None)).Value.Id;
var route = await routeService.CreateAsync( var route = await routeService.CreateAsync(
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false), new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, DepartmentId, CompanyId, ByProject: false, RequiresControl: false),
CancellationToken.None); CancellationToken.None);
await stepService.CreateAsync( await stepService.CreateAsync(
route.Value.Id, route.Value.Id,
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.DirectorTansu, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.DirectorTansu, RouteStepStatus.Approval, false, null, null, null),
CancellationToken.None); CancellationToken.None);
// Action // Action
@@ -379,7 +379,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, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false, null, null, null),
CancellationToken.None); CancellationToken.None);
// Action // Action
@@ -407,7 +407,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, null, "Несуществующая должность", null, null, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, null, "Несуществующая должность", null, null, RouteStepStatus.Approval, false, null, null, null),
CancellationToken.None); CancellationToken.None);
// Action // Action
@@ -435,7 +435,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, null, null, ProjectRole.Security, null, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, null, null, ProjectRole.Security, null, RouteStepStatus.Approval, false, null, null, null),
CancellationToken.None); CancellationToken.None);
// Action // Action
@@ -24,10 +24,10 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
_requestTypeService = services.GetRequiredService<RequestTypeService>(); _requestTypeService = services.GetRequiredService<RequestTypeService>();
} }
private async Task<int> CreateRouteAsync(string requestTypeName, bool byProject) private async Task<int> CreateRouteAsync(string requestTypeName, bool byProject, string? listName = null)
{ {
var requestTypeId = (await _requestTypeService.CreateAsync( var requestTypeId = (await _requestTypeService.CreateAsync(
new CreateRequestTypeRequest(requestTypeName, $"/{requestTypeName}"), CancellationToken.None)).Value.Id; new CreateRequestTypeRequest(requestTypeName, $"/{requestTypeName}", listName, "General"), CancellationToken.None)).Value.Id;
var route = await _routeService.CreateAsync( var route = await _routeService.CreateAsync(
new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, null, CompanyId, byProject, RequiresControl: false), new CreateTemplateRouteRequest(RouteType.Sequential, requestTypeId, null, CompanyId, byProject, RequiresControl: false),
@@ -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", null, null, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null, null, null),
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, null, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.ProjectManager, null, RouteStepStatus.Approval, false, null, null, null),
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", null, HeadType.Chief, RouteStepStatus.Approval, false); var request = new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, HeadType.Chief, RouteStepStatus.Approval, false, null, null, null);
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", null, HeadType.Chief, RouteStepStatus.Approval, true), new CreateTemplateRouteStepRequest(2, "a@b.kz", "Engineer", null, HeadType.Chief, RouteStepStatus.Approval, true, null, null, null),
CancellationToken.None); CancellationToken.None);
// Assert // Assert
@@ -93,7 +93,7 @@ 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, null, HeadType.Chief, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false, null, null, null),
CancellationToken.None); CancellationToken.None);
// Assert // Assert
@@ -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", null, null, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null, null, null),
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", null, null, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null, null, null),
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, null, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "", "", ProjectRole.ProjectManager, null, RouteStepStatus.Approval, false, null, null, null),
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", null, null, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null, null, null),
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", null, HeadType.Chief, RouteStepStatus.Execution, true), new UpdateTemplateRouteStepRequest(2, "b@b.kz", "Senior Engineer", null, HeadType.Chief, RouteStepStatus.Execution, true, null, null, null),
CancellationToken.None); CancellationToken.None);
// Assert // Assert
@@ -188,14 +188,14 @@ 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", null, null, null, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "a@b.kz", null, null, null, RouteStepStatus.Approval, false, null, null, null),
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: 2, ExecutorEmail: null, 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, FieldName: null, Operation: null, Value: null),
CancellationToken.None); CancellationToken.None);
// Assert // Assert
@@ -212,14 +212,14 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
var routeId = await CreateRouteAsync("Route to patch step into conflict", byProject: false); var routeId = await CreateRouteAsync("Route to patch step into conflict", byProject: false);
var created = await _service.CreateAsync( var created = await _service.CreateAsync(
routeId, routeId,
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false, null, null, null),
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: "a@b.kz", Position: null, ProjectRole: null, HeadType: null, Status: null, AllowReassignToOtherApprovers: null), new PatchTemplateRouteStepRequest(OrderNo: null, ExecutorEmail: "a@b.kz", Position: null, ProjectRole: null, HeadType: null, Status: null, AllowReassignToOtherApprovers: null, FieldName: null, Operation: null, Value: null),
CancellationToken.None); CancellationToken.None);
// Assert // Assert
@@ -233,14 +233,14 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
var routeId = await CreateRouteAsync("Route to patch step status none", byProject: false); var routeId = await CreateRouteAsync("Route to patch step status none", byProject: false);
var created = await _service.CreateAsync( var created = await _service.CreateAsync(
routeId, routeId,
new CreateTemplateRouteStepRequest(1, "a@b.kz", null, null, null, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "a@b.kz", null, null, null, RouteStepStatus.Approval, false, null, null, null),
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: null, Position: null, ProjectRole: null, HeadType: null, Status: RouteStepStatus.None, AllowReassignToOtherApprovers: null), new PatchTemplateRouteStepRequest(OrderNo: null, ExecutorEmail: null, Position: null, ProjectRole: null, HeadType: null, Status: RouteStepStatus.None, AllowReassignToOtherApprovers: null, FieldName: null, Operation: null, Value: null),
CancellationToken.None); CancellationToken.None);
// Assert // Assert
@@ -254,14 +254,14 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
var routeId = await CreateRouteAsync("Route with explicit None values on patch", byProject: false); var routeId = await CreateRouteAsync("Route with explicit None values on patch", byProject: false);
var created = await _service.CreateAsync( var created = await _service.CreateAsync(
routeId, routeId,
new CreateTemplateRouteStepRequest(1, "a@b.kz", null, null, null, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "a@b.kz", null, null, null, RouteStepStatus.Approval, false, null, null, null),
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: null, Position: null, ProjectRole: ProjectRole.None, HeadType: HeadType.None, Status: null, AllowReassignToOtherApprovers: null), new PatchTemplateRouteStepRequest(OrderNo: null, ExecutorEmail: null, Position: null, ProjectRole: ProjectRole.None, HeadType: HeadType.None, Status: null, AllowReassignToOtherApprovers: null, FieldName: null, Operation: null, Value: null),
CancellationToken.None); CancellationToken.None);
// Assert // Assert
@@ -269,6 +269,78 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
patched.Value.ExecutorEmail.ShouldBe("a@b.kz"); patched.Value.ExecutorEmail.ShouldBe("a@b.kz");
} }
[Fact(DisplayName = "Создание шага с частично заполненными полями условия возвращает неуспешный результат")]
public async Task CreateAsync_ConditionFieldsPartiallyFilled_ReturnsFailedResult()
{
// Arrange
var routeId = await CreateRouteAsync("Route with partial condition", byProject: false, listName: "Requests");
// Action
var result = await _service.CreateAsync(
routeId,
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, "Amount", null, null),
CancellationToken.None);
// Assert
result.IsFailed.ShouldBeTrue();
}
[Fact(DisplayName = "Создание шага с условием на типе заявки без list_name возвращает неуспешный результат")]
public async Task CreateAsync_ConditionFieldsWithoutListName_ReturnsFailedResult()
{
// Arrange
var routeId = await CreateRouteAsync("Route without list name", byProject: false);
// Action
var result = await _service.CreateAsync(
routeId,
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, "Amount", ComparisonOperator.GreaterThan, "1000"),
CancellationToken.None);
// Assert
result.IsFailed.ShouldBeTrue();
}
[Fact(DisplayName = "Создание шага с условием на типе заявки со list_name успешно")]
public async Task CreateAsync_ConditionFieldsWithListName_ReturnsCreatedStep()
{
// Arrange
var routeId = await CreateRouteAsync("Route with list name", byProject: false, listName: "Requests");
// Action
var created = await _service.CreateAsync(
routeId,
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, "Amount", ComparisonOperator.GreaterThan, "1000"),
CancellationToken.None);
// Assert
created.IsSuccess.ShouldBeTrue();
created.Value.FieldName.ShouldBe("Amount");
created.Value.Operation.ShouldBe(ComparisonOperator.GreaterThan);
created.Value.Value.ShouldBe("1000");
}
[Fact(DisplayName = "Патч, после которого поля условия заполнены частично, возвращает неуспешный результат")]
public async Task PatchAsync_ConditionFieldsPartiallyFilled_ReturnsFailedResult()
{
// Arrange
var routeId = await CreateRouteAsync("Route to patch condition into conflict", byProject: false, listName: "Requests");
var created = await _service.CreateAsync(
routeId,
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null, null, null),
CancellationToken.None);
// Action
var patched = await _service.PatchAsync(
routeId,
created.Value.Id,
new PatchTemplateRouteStepRequest(OrderNo: null, ExecutorEmail: null, Position: null, ProjectRole: null, HeadType: null, Status: null, AllowReassignToOtherApprovers: null, FieldName: "Amount", Operation: null, Value: null),
CancellationToken.None);
// Assert
patched.IsFailed.ShouldBeTrue();
}
[Fact(DisplayName = "Удаление шага маршрута убирает его из базы")] [Fact(DisplayName = "Удаление шага маршрута убирает его из базы")]
public async Task DeleteAsync_RemovesStep() public async Task DeleteAsync_RemovesStep()
{ {
@@ -276,7 +348,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", null, null, RouteStepStatus.Approval, false), new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null, null, null),
CancellationToken.None); CancellationToken.None);
// Action // Action