Files
tnsu-approval-route/backend/Tnsu.ApprovalRoute.Application/TemplateRouteSteps/TemplateRouteStepValidators.cs
T
gorbenko.pavel ba48ed1a0e
Tests / frontend-test (push) Successful in 47s
Tests / backend-test (push) Successful in 2m12s
Несколько условий шага, объединяемых через И/ИЛИ (#16)
<html>
<body>
<!--StartFragment-->
GetEmployeeByEmailAsync возвращает массив; новый HeadType.CompanyDirectors
--

<!--EndFragment-->
</body>
</html>[GetEmployeeByEmailAsync возвращает массив; новый HeadType.CompanyDirectors](http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/commit/572749b4d7cc11bc8e1ef4184a93f18a14b710ab)

---------

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/16
2026-08-25 07:04:44 +00:00

65 lines
4.0 KiB
C#

using FluentValidation;
using Tnsu.ApprovalRoute.Domain.Enums;
namespace Tnsu.ApprovalRoute.Application.TemplateRouteSteps;
public sealed class CreateTemplateRouteStepRequestValidator : AbstractValidator<CreateTemplateRouteStepRequest>
{
public CreateTemplateRouteStepRequestValidator()
{
RuleFor(x => x.Status).NotEqual(RouteStepStatus.None).WithMessage("Статус обязателен: Approval или Execution.");
RuleFor(x => x)
.Must(x => TemplateRouteStepFields.CountFilled(x.ExecutorEmail, x.Position, x.ProjectRole, x.HeadType) == 1)
.WithMessage("Должно быть заполнено ровно одно из полей: исполнитель, должность, роль в проекте или тип руководителя.");
RuleFor(x => x)
.Must(x => TemplateRouteStepFields.AreConditionsValid(x.Conditions))
.WithMessage("В каждом условии (field_name, operation, value) все три поля обязательны.");
// Отрицательный SLA сделал бы шаг просроченным уже в момент активации;
// ноль допустим — это осознанное «напоминать сразу».
RuleFor(x => x.SlaHours)
.GreaterThanOrEqualTo(0)
.When(x => x.SlaHours is not null)
.WithMessage("SLA в часах не может быть отрицательным.");
}
}
public sealed class UpdateTemplateRouteStepRequestValidator : AbstractValidator<UpdateTemplateRouteStepRequest>
{
public UpdateTemplateRouteStepRequestValidator()
{
RuleFor(x => x.Status).NotEqual(RouteStepStatus.None).WithMessage("Статус обязателен: Approval или Execution.");
RuleFor(x => x)
.Must(x => TemplateRouteStepFields.CountFilled(x.ExecutorEmail, x.Position, x.ProjectRole, x.HeadType) == 1)
.WithMessage("Должно быть заполнено ровно одно из полей: исполнитель, должность, роль в проекте или тип руководителя.");
RuleFor(x => x)
.Must(x => TemplateRouteStepFields.AreConditionsValid(x.Conditions))
.WithMessage("В каждом условии (field_name, operation, value) все три поля обязательны.");
// Отрицательный SLA сделал бы шаг просроченным уже в момент активации;
// ноль допустим — это осознанное «напоминать сразу».
RuleFor(x => x.SlaHours)
.GreaterThanOrEqualTo(0)
.When(x => x.SlaHours is not null)
.WithMessage("SLA в часах не может быть отрицательным.");
}
}
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);
/// <summary>
/// Условие с пустым field_name/value или operation=None — половинчатое, а не
/// «выключенное» (выключается вовсе не добавляя условие в список), поэтому
/// такое отдельное условие недопустимо, даже если остальные условия шага корректны.
/// </summary>
public static bool AreConditionsValid(IReadOnlyList<RouteStepConditionDto>? conditions) =>
conditions is null || conditions.All(c =>
!string.IsNullOrWhiteSpace(c.FieldName) &&
c.Operation != ComparisonOperator.None &&
!string.IsNullOrWhiteSpace(c.Value));
}