From 31a0a8e9e30120bccb3472d12c1ecfda3e1f94c8 Mon Sep 17 00:00:00 2001 From: "gorbenko.pavel" Date: Thu, 27 Aug 2026 15:53:31 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B0=D0=BF=D0=BE=D0=BC=D0=B8=D0=BD?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=8F:=20=D1=83=D0=B1=D1=80=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D0=B4=D1=83=D0=B1=D0=BB=D1=8C=20ManagerEmail,=20?= =?UTF-8?q?=D1=81=D1=81=D1=8B=D0=BB=D0=BA=D0=B0=20=D0=B8=D0=B7=20=D0=BD?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B5=D0=BA;=20None=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B2=D1=8B=D0=BC=20=D0=B2=D0=BE=20=D0=B2=D1=81?= =?UTF-8?q?=D0=B5=D1=85=20enum?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Напоминания: - ProbationEndingReminderDto: ManagerEmail убран — это был унаследованный ReceiverEmail под вторым именем. - Ссылка для ProbationEnding больше не ведёт на саму заявку на приём (она уже закрыта, решать по ней нечего) — берётся из новой настройки RemindersOptions.ProbationReminderUrl и ведёт в раздел, где заводят новую заявку. Подстановок в ней нет: адрес конкретной формы в портал не передать, rptype без rpid/rpsource игнорируется (см. parseRoute в request-portal). Заодно ушли BuildProbationUrl и ненужные больше параметры type/listId. Стайлгайд (CLAUDE.md) — у enum первое значение None: - ReminderFeed: None=0 трактуется как ApprovalOverdue, чтобы вызов без явного выбора работал как раньше. - EmploymentStatus: заодно инвертирован switch в ReminderService — раньше «продолжить» было веткой по умолчанию, и новый None молча означал бы «сотрудник работает». Теперь по умолчанию пропуск с предупреждением. - ConditionsLogic: None — НЕвалидное значение, отвергается валидаторами шага (в том числе новым PatchTemplateRouteStepRequestValidator, его не было вовсе). Поэтому у ConditionsLogic в Create/Update-запросах убрано значение по умолчанию: System.Text.Json в .NET 10 уважает дефолты параметров конструктора, и пропущенное поле молча означало «И». Теперь пропущенное поле даёт None и упирается в валидатор. ЛОМАЮЩЕЕ ИЗМЕНЕНИЕ КОНТРАКТА: клиент, не присылающий conditionsLogic в POST/PUT шага, получит 400 вместо прежнего молчаливого «И». Админка не затронута — она это поле всегда отправляет. --- .../Reminders/EmployeeDirectorySnapshot.cs | 13 +++- .../Reminders/ProbationEndingReminderDto.cs | 11 +-- .../Reminders/ReminderFeed.cs | 11 ++- .../Reminders/ReminderService.cs | 36 +++++---- .../Reminders/RemindersOptions.cs | 15 ++++ .../TemplateRouteStepDto.cs | 4 +- .../TemplateRouteStepValidators.cs | 35 +++++++++ .../Enums/ConditionsLogic.cs | 16 +++- .../ReminderServiceTests.cs | 19 ++++- .../TemplateRouteServiceTests.cs | 56 ++++++------- .../TemplateRouteStepServiceTests.cs | 78 ++++++++++--------- 11 files changed, 192 insertions(+), 102 deletions(-) diff --git a/backend/Tnsu.ApprovalRoute.Application/Reminders/EmployeeDirectorySnapshot.cs b/backend/Tnsu.ApprovalRoute.Application/Reminders/EmployeeDirectorySnapshot.cs index f82fd86..9ea516d 100644 --- a/backend/Tnsu.ApprovalRoute.Application/Reminders/EmployeeDirectorySnapshot.cs +++ b/backend/Tnsu.ApprovalRoute.Application/Reminders/EmployeeDirectorySnapshot.cs @@ -4,14 +4,21 @@ namespace Tnsu.ApprovalRoute.Application.Reminders; internal enum EmploymentStatus { + /// + /// Статус не определяли. EmploymentOf такого не возвращает — значение существует + /// только как значение по умолчанию (default(EmploymentStatus)); трактуется + /// наравне с , то есть «трудоустройство не подтверждено». + /// + None = 0, + /// Сотрудника с таким ФИО в справочнике нет вовсе — трудоустройство не подтверждено. - NotFound, + NotFound = 1, /// Работает хотя бы в одной компании. - Employed, + Employed = 2, /// Есть в справочнике, но уволен из всех компаний, где числился. - Dismissed, + Dismissed = 3, } /// diff --git a/backend/Tnsu.ApprovalRoute.Application/Reminders/ProbationEndingReminderDto.cs b/backend/Tnsu.ApprovalRoute.Application/Reminders/ProbationEndingReminderDto.cs index b42fa84..3a810ab 100644 --- a/backend/Tnsu.ApprovalRoute.Application/Reminders/ProbationEndingReminderDto.cs +++ b/backend/Tnsu.ApprovalRoute.Application/Reminders/ProbationEndingReminderDto.cs @@ -1,8 +1,9 @@ namespace Tnsu.ApprovalRoute.Application.Reminders; -/// -/// То же значение, что и унаследованный ReceiverEmail, — email руководителя -/// принимаемого сотрудника, кому адресовано напоминание. +/// +/// Email руководителя принимаемого сотрудника — кому адресовано напоминание. +/// Отдельного ManagerEmail рядом нет намеренно: это было то же самое значение +/// под вторым именем. /// /// Email принимаемого сотрудника (см. ReminderService.GetProbationEndingAsync). /// ФИО принимаемого сотрудника (колонка CandidateName списка данных). @@ -11,7 +12,7 @@ namespace Tnsu.ApprovalRoute.Application.Reminders; /// Дата окончания испытательного срока (HireDate + срок). /// Сколько дней осталось до конца испытательного срока. public sealed record ProbationEndingReminderDto( - string ManagerEmail, + string ReceiverEmail, string CandidateEmail, string CandidateName, string? ProbationPeriod, @@ -20,4 +21,4 @@ public sealed record ProbationEndingReminderDto( int DaysUntilProbationEnds, string Url, string? ReceiverName = null) - : ReminderDto(ManagerEmail, ReceiverName, Url); + : ReminderDto(ReceiverEmail, ReceiverName, Url); diff --git a/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderFeed.cs b/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderFeed.cs index 1b3dd50..33085df 100644 --- a/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderFeed.cs +++ b/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderFeed.cs @@ -1,16 +1,15 @@ namespace Tnsu.ApprovalRoute.Application.Reminders; /// -/// Какой вид напоминаний запросить у . +/// Какой вид напоминаний запросить у . /// -/// GetRemindersAsync изначально (до появления ProbationEnding) отдавал только -/// просроченные шаги согласования — этот вид и остаётся значением по умолчанию -/// (0), чтобы старые потребители (Power Automate), не указывающие feed явно, -/// продолжали получать ровно то же, что и раньше. +/// («вид не указан») трактуется как : +/// до появления ProbationEnding эндпоинт отдавал только просроченные шаги, и +/// вызов без явного выбора обязан работать как раньше. /// public enum ReminderFeed { None = 0, ApprovalOverdue = 1, - ProbationEnding = 2 + ProbationEnding = 2, } diff --git a/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderService.cs b/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderService.cs index ac1d089..45cac5b 100644 --- a/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderService.cs +++ b/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderService.cs @@ -403,15 +403,20 @@ public sealed class ReminderService( var reminders = new List(); foreach (var row in rows) { - var reminder = await TryBuildProbationReminderAsync(type, row, listId, directory, now, cancellationToken); + var reminder = await TryBuildProbationReminderAsync(row, directory, now, cancellationToken); if (reminder is not null) reminders.Add(reminder); } return reminders; } + /// + /// Ни type, ни listId здесь больше не нужны: ссылка в этом виде напоминаний — + /// константа из настроек (см. ProbationReminderUrl), а поля самой заявки + /// (RequestTypeId и т.п.) в ProbationEndingReminderDto не входят. + /// private async Task TryBuildProbationReminderAsync( - RequestType type, HireRow row, string listId, EmployeeDirectorySnapshot directory, + HireRow row, EmployeeDirectorySnapshot directory, DateTimeOffset now, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(row.CandidateName) || row.HireDate is not { } hireDate) return null; @@ -425,11 +430,17 @@ public sealed class ReminderService( var candidateName = row.CandidateName.Trim(); + // Перечислены оба «продолжающих» исхода явно, а «пропустить» — это default: + // так любое НЕ подтверждённое трудоустройство (в том числе EmploymentStatus.None, + // если он когда-нибудь сюда доедет) приводит к пропуску с предупреждением, а не + // молча считается «работает». switch (directory.EmploymentOf(candidateName)) { + case EmploymentStatus.Employed: + break; case EmploymentStatus.Dismissed: return null; // уже уволен — напоминать не о чем. - case EmploymentStatus.NotFound: + default: logger.LogWarning( "Заявка на приём {RequestId}: кандидат «{Fio}» не найден в /Employee/FullBase — статус трудоустройства не подтверждён, напоминание пропущено", row.Id, candidateName); @@ -455,26 +466,17 @@ public sealed class ReminderService( } return new ProbationEndingReminderDto( - ManagerEmail: managerEmail, + ReceiverEmail: managerEmail, CandidateEmail: candidateEmail, CandidateName: candidateName, ProbationPeriod: row.ProbationPeriod, HireDate: hireDate, ProbationEndsAt: probationEndsAt, DaysUntilProbationEnds: daysLeft, - Url: BuildProbationUrl(type, row, listId), + // Не ссылка на саму заявку на приём (она уже закрыта, решать по ней + // нечего) — константный адрес раздела, где руководитель заводит новую + // заявку по итогам испытательного срока. См. ProbationReminderUrl. + Url: options.Value.ProbationReminderUrl, ReceiverName: directory.NameByEmail(managerEmail)); } - - /// - /// Ссылка на саму заявку (не на шаг согласования — этого вида напоминаний - /// понятие "фаза" не касается), в том же формате, что и BuildUrl. - /// - private string BuildProbationUrl(RequestType type, HireRow row, string listId) => - options.Value.RequestUrlTemplate - .Replace("{siteUrl}", type.SiteUrl!.TrimEnd('/')) - .Replace("{pageUrl}", type.PageUrl) - .Replace("{requestId}", row.Id.ToString()) - .Replace("{listId}", Uri.EscapeDataString(listId)) - .Replace("{nav}", "approvals"); } diff --git a/backend/Tnsu.ApprovalRoute.Application/Reminders/RemindersOptions.cs b/backend/Tnsu.ApprovalRoute.Application/Reminders/RemindersOptions.cs index 30ce775..5e83987 100644 --- a/backend/Tnsu.ApprovalRoute.Application/Reminders/RemindersOptions.cs +++ b/backend/Tnsu.ApprovalRoute.Application/Reminders/RemindersOptions.cs @@ -34,4 +34,19 @@ public sealed class RemindersOptions /// За сколько дней до конца испытательного срока присылать напоминание. public int ProbationReminderDaysBeforeEnd { get; set; } = 3; + + /// + /// Куда ведёт ссылка в напоминании об окончании испытательного срока — на + /// раздел «Подать заявку» портала, а НЕ на саму заявку на приём: от + /// руководителя по этому напоминанию требуется завести НОВУЮ заявку (решение + /// по итогам испытательного срока), а не открыть уже закрытую старую. + /// + /// Поэтому ссылка константная, без подстановок: адрес конкретной формы в + /// портал вообще не передать — параметр rptype без rpid/rpsource + /// игнорируется, и маршрут сваливается в раздел «Подать заявку» (см. + /// parseRoute в request-portal/shared/routing.ts). Отдельная настройка, а не + /// хардкод, — чтобы адрес правился конфигурацией. + /// + public string ProbationReminderUrl { get; set; } = + "https://tnsukz.sharepoint.com/sites/requests?rpnav=new"; } diff --git a/backend/Tnsu.ApprovalRoute.Application/TemplateRouteSteps/TemplateRouteStepDto.cs b/backend/Tnsu.ApprovalRoute.Application/TemplateRouteSteps/TemplateRouteStepDto.cs index 9b008b3..eb41027 100644 --- a/backend/Tnsu.ApprovalRoute.Application/TemplateRouteSteps/TemplateRouteStepDto.cs +++ b/backend/Tnsu.ApprovalRoute.Application/TemplateRouteSteps/TemplateRouteStepDto.cs @@ -35,7 +35,7 @@ public sealed record CreateTemplateRouteStepRequest( RouteStepStatus Status, bool AllowReassignToOtherApprovers, IReadOnlyList? Conditions, - ConditionsLogic ConditionsLogic = ConditionsLogic.And, + ConditionsLogic ConditionsLogic, int? SlaHours = null, bool? AskOverdueReason = null); @@ -48,7 +48,7 @@ public sealed record UpdateTemplateRouteStepRequest( RouteStepStatus Status, bool AllowReassignToOtherApprovers, IReadOnlyList? Conditions, - ConditionsLogic ConditionsLogic = ConditionsLogic.And, + ConditionsLogic ConditionsLogic, int? SlaHours = null, bool? AskOverdueReason = null); diff --git a/backend/Tnsu.ApprovalRoute.Application/TemplateRouteSteps/TemplateRouteStepValidators.cs b/backend/Tnsu.ApprovalRoute.Application/TemplateRouteSteps/TemplateRouteStepValidators.cs index 2a5d42e..e9c5ab7 100644 --- a/backend/Tnsu.ApprovalRoute.Application/TemplateRouteSteps/TemplateRouteStepValidators.cs +++ b/backend/Tnsu.ApprovalRoute.Application/TemplateRouteSteps/TemplateRouteStepValidators.cs @@ -8,6 +8,9 @@ public sealed class CreateTemplateRouteStepRequestValidator : AbstractValidator< public CreateTemplateRouteStepRequestValidator() { RuleFor(x => x.Status).NotEqual(RouteStepStatus.None).WithMessage("Статус обязателен: Approval или Execution."); + RuleFor(x => x.ConditionsLogic) + .NotEqual(ConditionsLogic.None) + .WithMessage("Логика объединения условий обязательна: And или Or."); RuleFor(x => x) .Must(x => TemplateRouteStepFields.CountFilled(x.ExecutorEmail, x.Position, x.ProjectRole, x.HeadType) == 1) .WithMessage("Должно быть заполнено ровно одно из полей: исполнитель, должность, роль в проекте или тип руководителя."); @@ -28,6 +31,9 @@ public sealed class UpdateTemplateRouteStepRequestValidator : AbstractValidator< public UpdateTemplateRouteStepRequestValidator() { RuleFor(x => x.Status).NotEqual(RouteStepStatus.None).WithMessage("Статус обязателен: Approval или Execution."); + RuleFor(x => x.ConditionsLogic) + .NotEqual(ConditionsLogic.None) + .WithMessage("Логика объединения условий обязательна: And или Or."); RuleFor(x => x) .Must(x => TemplateRouteStepFields.CountFilled(x.ExecutorEmail, x.Position, x.ProjectRole, x.HeadType) == 1) .WithMessage("Должно быть заполнено ровно одно из полей: исполнитель, должность, роль в проекте или тип руководителя."); @@ -43,6 +49,35 @@ public sealed class UpdateTemplateRouteStepRequestValidator : AbstractValidator< } } +/// +/// У PATCH-запроса шага валидатора раньше не было вовсе — здесь он ровно про одно: +/// не дать протащить невалидные значения-заглушки в поля, которые запрос ЯВНО +/// задаёт. null остаётся «не менять» и не проверяется (см. PatchTemplateRouteStepRequest). +/// +public sealed class PatchTemplateRouteStepRequestValidator : AbstractValidator +{ + public PatchTemplateRouteStepRequestValidator() + { + When(x => x.Status is not null, () => + RuleFor(x => x.Status) + .NotEqual(RouteStepStatus.None) + .WithMessage("Статус обязателен: Approval или Execution.")); + When(x => x.ConditionsLogic is not null, () => + RuleFor(x => x.ConditionsLogic) + .NotEqual(ConditionsLogic.None) + .WithMessage("Логика объединения условий обязательна: And или Or.")); + 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) => diff --git a/backend/Tnsu.ApprovalRoute.Domain/Enums/ConditionsLogic.cs b/backend/Tnsu.ApprovalRoute.Domain/Enums/ConditionsLogic.cs index 1f37402..a0ba7f3 100644 --- a/backend/Tnsu.ApprovalRoute.Domain/Enums/ConditionsLogic.cs +++ b/backend/Tnsu.ApprovalRoute.Domain/Enums/ConditionsLogic.cs @@ -6,9 +6,21 @@ namespace Tnsu.ApprovalRoute.Domain.Enums; /// public enum ConditionsLogic { + /// + /// Логика не задана — НЕвалидное значение (см. валидаторы шага), а не «как И». + /// Существует только как значение по умолчанию (default(ConditionsLogic)): + /// шаг с такой логикой не должен ни создаваться, ни сохраняться. + /// + /// Именно поэтому у самой сущности и у запросов на создание/обновление + /// значение по умолчанию задано явно — ConditionsLogic.And + /// (см. TemplateRouteStep.ConditionsLogic и *TemplateRouteStepRequest): + /// пропущенное в запросе поле по-прежнему означает «И», как и раньше. + /// + None = 0, + /// Шаг появляется, только если выполняются ВСЕ условия. - And, + And = 1, /// Шаг появляется, если выполняется ХОТЯ БЫ ОДНО условие. - Or + Or = 2 } diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs index 69eacf9..6787ab6 100644 --- a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs +++ b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs @@ -31,6 +31,11 @@ public sealed class ReminderServiceTests : IClassFixture(), portalClient.Object, Clock, - Options.Create(new RemindersOptions { RecruitingHirePageUrl = pageUrl, ProbationReminderDaysBeforeEnd = daysBeforeEnd }), + Options.Create(new RemindersOptions + { + RecruitingHirePageUrl = pageUrl, + ProbationReminderDaysBeforeEnd = daysBeforeEnd, + ProbationReminderUrl = ProbationReminderUrl + }), NullLogger.Instance); } @@ -394,10 +404,13 @@ public sealed class ReminderServiceTests : IClassFixture(); - reminder.ManagerEmail.ShouldBe("manager@tnsu.kz"); + reminder.ReceiverEmail.ShouldBe("manager@tnsu.kz"); reminder.CandidateEmail.ShouldBe("candidate@tnsu.kz"); reminder.CandidateName.ShouldBe("Иванов Иван Иванович"); reminder.DaysUntilProbationEnds.ShouldBe(3); + // Ссылка ведёт не на саму заявку на приём, а на раздел, где заводят новую + // (константа из настроек, см. RemindersOptions.ProbationReminderUrl). + reminder.Url.ShouldBe(ProbationReminderUrl); } [Fact(DisplayName = "Кандидат уже уволен (dismissalDate заполнен) — напоминания нет")] @@ -558,7 +571,7 @@ public sealed class ReminderServiceTests : IClassFixture.Instance); var result = await service.GetProbationEndingAsync(Now, CancellationToken.None); diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteServiceTests.cs b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteServiceTests.cs index 1f8b1a6..b3c3857 100644 --- a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteServiceTests.cs +++ b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteServiceTests.cs @@ -169,7 +169,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture(); await stepService.CreateAsync( route.Value.Id, - new CreateTemplateRouteStepRequest(1, "approver@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, null), + new CreateTemplateRouteStepRequest(1, "approver@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And), CancellationToken.None); // Action @@ -206,7 +206,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture(); await stepService.CreateAsync( route.Value.Id, - new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.DepartmentHead, RouteStepStatus.Approval, false, null), + new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.DepartmentHead, RouteStepStatus.Approval, false, null, ConditionsLogic.And), CancellationToken.None); // Action — departmentId в запросе не передан @@ -419,7 +419,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture(); await stepService.CreateAsync( route.Value.Id, - new CreateTemplateRouteStepRequest(1, "big-amount@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }), + new CreateTemplateRouteStepRequest(1, "big-amount@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }, ConditionsLogic.And), CancellationToken.None); // Action @@ -515,11 +515,11 @@ public sealed class TemplateRouteServiceTests : IClassFixture(); await stepService.CreateAsync( route.Value.Id, - new CreateTemplateRouteStepRequest(1, "big-amount@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }), + new CreateTemplateRouteStepRequest(1, "big-amount@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }, ConditionsLogic.And), CancellationToken.None); await stepService.CreateAsync( route.Value.Id, - new CreateTemplateRouteStepRequest(2, "always@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, null), + new CreateTemplateRouteStepRequest(2, "always@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And), CancellationToken.None); // Action @@ -546,7 +546,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture(); await stepService.CreateAsync( route.Value.Id, - new CreateTemplateRouteStepRequest(1, "big-amount@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }), + new CreateTemplateRouteStepRequest(1, "big-amount@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }, ConditionsLogic.And), CancellationToken.None); // Action — fieldValues не переданы вовсе @@ -571,7 +571,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture(); await stepService.CreateAsync( route.Value.Id, - new CreateTemplateRouteStepRequest(1, "urgent@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Priority", ComparisonOperator.Equal, "Urgent") }), + new CreateTemplateRouteStepRequest(1, "urgent@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Priority", ComparisonOperator.Equal, "Urgent") }, ConditionsLogic.And), CancellationToken.None); // Action @@ -605,7 +605,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture(); await stepService.CreateAsync( route.Value.Id, - new CreateTemplateRouteStepRequest(1, "text-condition@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Priority", operation, expected) }), + new CreateTemplateRouteStepRequest(1, "text-condition@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Priority", operation, expected) }, ConditionsLogic.And), CancellationToken.None); // Action @@ -672,7 +672,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture(); await stepService.CreateAsync( route.Value.Id, - new CreateTemplateRouteStepRequest(1, null, null, ProjectRole.ProjectManager, null, RouteStepStatus.Approval, false, null), + new CreateTemplateRouteStepRequest(1, null, null, ProjectRole.ProjectManager, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And), CancellationToken.None); // Action @@ -710,7 +710,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture(); await stepService.CreateAsync( route.Value.Id, - new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false, null), + new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false, null, ConditionsLogic.And), CancellationToken.None); // Action @@ -1065,7 +1065,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture(); await stepService.CreateAsync( route.Value.Id, - new CreateTemplateRouteStepRequest(1, null, "Несуществующая должность", null, null, RouteStepStatus.Approval, false, null), + new CreateTemplateRouteStepRequest(1, null, "Несуществующая должность", null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And), CancellationToken.None); // Action @@ -1094,7 +1094,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture(); await stepService.CreateAsync( route.Value.Id, - new CreateTemplateRouteStepRequest(1, null, null, ProjectRole.Security, null, RouteStepStatus.Approval, false, null), + new CreateTemplateRouteStepRequest(1, null, null, ProjectRole.Security, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And), CancellationToken.None); // Action diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteStepServiceTests.cs b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteStepServiceTests.cs index a6aa592..831e076 100644 --- a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteStepServiceTests.cs +++ b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/TemplateRouteStepServiceTests.cs @@ -43,7 +43,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture