Напоминания: убрать дубль ManagerEmail, ссылка из настроек; None первым во всех enum
Напоминания: - 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 вместо прежнего молчаливого «И». Админка не затронута — она это поле всегда отправляет.
This commit is contained in:
@@ -4,14 +4,21 @@ namespace Tnsu.ApprovalRoute.Application.Reminders;
|
||||
|
||||
internal enum EmploymentStatus
|
||||
{
|
||||
/// <summary>
|
||||
/// Статус не определяли. EmploymentOf такого не возвращает — значение существует
|
||||
/// только как значение по умолчанию (default(EmploymentStatus)); трактуется
|
||||
/// наравне с <see cref="NotFound"/>, то есть «трудоустройство не подтверждено».
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>Сотрудника с таким ФИО в справочнике нет вовсе — трудоустройство не подтверждено.</summary>
|
||||
NotFound,
|
||||
NotFound = 1,
|
||||
|
||||
/// <summary>Работает хотя бы в одной компании.</summary>
|
||||
Employed,
|
||||
Employed = 2,
|
||||
|
||||
/// <summary>Есть в справочнике, но уволен из всех компаний, где числился.</summary>
|
||||
Dismissed,
|
||||
Dismissed = 3,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
namespace Tnsu.ApprovalRoute.Application.Reminders;
|
||||
|
||||
/// <param name="ManagerEmail">
|
||||
/// То же значение, что и унаследованный ReceiverEmail, — email руководителя
|
||||
/// принимаемого сотрудника, кому адресовано напоминание.
|
||||
/// <param name="ReceiverEmail">
|
||||
/// Email руководителя принимаемого сотрудника — кому адресовано напоминание.
|
||||
/// Отдельного ManagerEmail рядом нет намеренно: это было то же самое значение
|
||||
/// под вторым именем.
|
||||
/// </param>
|
||||
/// <param name="CandidateEmail">Email принимаемого сотрудника (см. ReminderService.GetProbationEndingAsync).</param>
|
||||
/// <param name="CandidateName">ФИО принимаемого сотрудника (колонка CandidateName списка данных).</param>
|
||||
@@ -11,7 +12,7 @@ namespace Tnsu.ApprovalRoute.Application.Reminders;
|
||||
/// <param name="ProbationEndsAt">Дата окончания испытательного срока (HireDate + срок).</param>
|
||||
/// <param name="DaysUntilProbationEnds">Сколько дней осталось до конца испытательного срока.</param>
|
||||
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);
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
namespace Tnsu.ApprovalRoute.Application.Reminders;
|
||||
|
||||
/// <summary>
|
||||
/// Какой вид напоминаний запросить у <see cref="ReminderService.GetRemindersAsync(DateTimeOffset, ReminderFeed, CancellationToken)"/>.
|
||||
/// Какой вид напоминаний запросить у <see cref="ReminderService.GetRemindersAsync(ReminderFeed, CancellationToken)"/>.
|
||||
///
|
||||
/// GetRemindersAsync изначально (до появления ProbationEnding) отдавал только
|
||||
/// просроченные шаги согласования — этот вид и остаётся значением по умолчанию
|
||||
/// (0), чтобы старые потребители (Power Automate), не указывающие feed явно,
|
||||
/// продолжали получать ровно то же, что и раньше.
|
||||
/// <see cref="None"/> («вид не указан») трактуется как <see cref="ApprovalOverdue"/>:
|
||||
/// до появления ProbationEnding эндпоинт отдавал только просроченные шаги, и
|
||||
/// вызов без явного выбора обязан работать как раньше.
|
||||
/// </summary>
|
||||
public enum ReminderFeed
|
||||
{
|
||||
None = 0,
|
||||
ApprovalOverdue = 1,
|
||||
ProbationEnding = 2
|
||||
ProbationEnding = 2,
|
||||
}
|
||||
|
||||
@@ -403,15 +403,20 @@ public sealed class ReminderService(
|
||||
var reminders = new List<ReminderDto>();
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ни type, ни listId здесь больше не нужны: ссылка в этом виде напоминаний —
|
||||
/// константа из настроек (см. ProbationReminderUrl), а поля самой заявки
|
||||
/// (RequestTypeId и т.п.) в ProbationEndingReminderDto не входят.
|
||||
/// </summary>
|
||||
private async Task<ReminderDto?> 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));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ссылка на саму заявку (не на шаг согласования — этого вида напоминаний
|
||||
/// понятие "фаза" не касается), в том же формате, что и BuildUrl.
|
||||
/// </summary>
|
||||
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");
|
||||
}
|
||||
|
||||
@@ -34,4 +34,19 @@ public sealed class RemindersOptions
|
||||
|
||||
/// <summary>За сколько дней до конца испытательного срока присылать напоминание.</summary>
|
||||
public int ProbationReminderDaysBeforeEnd { get; set; } = 3;
|
||||
|
||||
/// <summary>
|
||||
/// Куда ведёт ссылка в напоминании об окончании испытательного срока — на
|
||||
/// раздел «Подать заявку» портала, а НЕ на саму заявку на приём: от
|
||||
/// руководителя по этому напоминанию требуется завести НОВУЮ заявку (решение
|
||||
/// по итогам испытательного срока), а не открыть уже закрытую старую.
|
||||
///
|
||||
/// Поэтому ссылка константная, без подстановок: адрес конкретной формы в
|
||||
/// портал вообще не передать — параметр rptype без rpid/rpsource
|
||||
/// игнорируется, и маршрут сваливается в раздел «Подать заявку» (см.
|
||||
/// parseRoute в request-portal/shared/routing.ts). Отдельная настройка, а не
|
||||
/// хардкод, — чтобы адрес правился конфигурацией.
|
||||
/// </summary>
|
||||
public string ProbationReminderUrl { get; set; } =
|
||||
"https://tnsukz.sharepoint.com/sites/requests?rpnav=new";
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public sealed record CreateTemplateRouteStepRequest(
|
||||
RouteStepStatus Status,
|
||||
bool AllowReassignToOtherApprovers,
|
||||
IReadOnlyList<RouteStepConditionDto>? 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<RouteStepConditionDto>? Conditions,
|
||||
ConditionsLogic ConditionsLogic = ConditionsLogic.And,
|
||||
ConditionsLogic ConditionsLogic,
|
||||
int? SlaHours = null,
|
||||
bool? AskOverdueReason = null);
|
||||
|
||||
|
||||
+35
@@ -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<
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// У PATCH-запроса шага валидатора раньше не было вовсе — здесь он ровно про одно:
|
||||
/// не дать протащить невалидные значения-заглушки в поля, которые запрос ЯВНО
|
||||
/// задаёт. null остаётся «не менять» и не проверяется (см. PatchTemplateRouteStepRequest).
|
||||
/// </summary>
|
||||
public sealed class PatchTemplateRouteStepRequestValidator : AbstractValidator<PatchTemplateRouteStepRequest>
|
||||
{
|
||||
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) =>
|
||||
|
||||
@@ -6,9 +6,21 @@ namespace Tnsu.ApprovalRoute.Domain.Enums;
|
||||
/// </summary>
|
||||
public enum ConditionsLogic
|
||||
{
|
||||
/// <summary>
|
||||
/// Логика не задана — НЕвалидное значение (см. валидаторы шага), а не «как И».
|
||||
/// Существует только как значение по умолчанию (default(ConditionsLogic)):
|
||||
/// шаг с такой логикой не должен ни создаваться, ни сохраняться.
|
||||
///
|
||||
/// Именно поэтому у самой сущности и у запросов на создание/обновление
|
||||
/// значение по умолчанию задано явно — <c>ConditionsLogic.And</c>
|
||||
/// (см. TemplateRouteStep.ConditionsLogic и *TemplateRouteStepRequest):
|
||||
/// пропущенное в запросе поле по-прежнему означает «И», как и раньше.
|
||||
/// </summary>
|
||||
None = 0,
|
||||
|
||||
/// <summary>Шаг появляется, только если выполняются ВСЕ условия.</summary>
|
||||
And,
|
||||
And = 1,
|
||||
|
||||
/// <summary>Шаг появляется, если выполняется ХОТЯ БЫ ОДНО условие.</summary>
|
||||
Or
|
||||
Or = 2
|
||||
}
|
||||
|
||||
+16
-3
@@ -31,6 +31,11 @@ public sealed class ReminderServiceTests : IClassFixture<PostgresDatabaseFixture
|
||||
private const string DataListTitle = "Заявка тест — данные";
|
||||
private const string ApprovalListTitle = "Заявка тест — согласование";
|
||||
|
||||
// Своё значение (не то, что по умолчанию в RemindersOptions) — так тест
|
||||
// проверяет, что ссылка действительно берётся из настроек, а не совпадает
|
||||
// с ожидаемой случайно.
|
||||
private const string ProbationReminderUrl = "https://portal.test/новая-заявка";
|
||||
|
||||
private static readonly DateTimeOffset Now = new(2026, 8, 12, 12, 0, 0, TimeSpan.Zero);
|
||||
|
||||
// GetOverdueAsync/GetProbationEndingAsync по-прежнему берут now параметром — их
|
||||
@@ -355,7 +360,12 @@ public sealed class ReminderServiceTests : IClassFixture<PostgresDatabaseFixture
|
||||
_services.GetRequiredService<ListPartitionRegistry>(),
|
||||
portalClient.Object,
|
||||
Clock,
|
||||
Options.Create(new RemindersOptions { RecruitingHirePageUrl = pageUrl, ProbationReminderDaysBeforeEnd = daysBeforeEnd }),
|
||||
Options.Create(new RemindersOptions
|
||||
{
|
||||
RecruitingHirePageUrl = pageUrl,
|
||||
ProbationReminderDaysBeforeEnd = daysBeforeEnd,
|
||||
ProbationReminderUrl = ProbationReminderUrl
|
||||
}),
|
||||
NullLogger<ReminderService>.Instance);
|
||||
}
|
||||
|
||||
@@ -394,10 +404,13 @@ public sealed class ReminderServiceTests : IClassFixture<PostgresDatabaseFixture
|
||||
|
||||
result.IsSuccess.ShouldBeTrue();
|
||||
var reminder = result.Value.ShouldHaveSingleItem().ShouldBeOfType<ProbationEndingReminderDto>();
|
||||
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<PostgresDatabaseFixture
|
||||
|
||||
var service = new ReminderService(
|
||||
db, sharePoint.Object, partitionRegistry, portalClient.Object, Clock,
|
||||
Options.Create(new RemindersOptions { RecruitingHirePageUrl = pageUrl }),
|
||||
Options.Create(new RemindersOptions { RecruitingHirePageUrl = pageUrl, ProbationReminderUrl = ProbationReminderUrl }),
|
||||
NullLogger<ReminderService>.Instance);
|
||||
|
||||
var result = await service.GetProbationEndingAsync(Now, CancellationToken.None);
|
||||
|
||||
+28
-28
@@ -169,7 +169,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||
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<PostgresDatabaseFi
|
||||
await stepService.CreateAsync(
|
||||
route.Value.Id,
|
||||
new CreateTemplateRouteStepRequest(
|
||||
1, "approver@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, null,
|
||||
1, "approver@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And,
|
||||
SlaHours: stepSla, AskOverdueReason: stepAsk),
|
||||
CancellationToken.None);
|
||||
|
||||
@@ -285,15 +285,15 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
||||
|
||||
await stepService.CreateAsync(
|
||||
route.Value.Id,
|
||||
new CreateTemplateRouteStepRequest(1, "dup@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, "dup@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
await stepService.CreateAsync(
|
||||
route.Value.Id,
|
||||
new CreateTemplateRouteStepRequest(2, "other@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(2, "other@tnsu.kz", null, null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
await stepService.CreateAsync(
|
||||
route.Value.Id,
|
||||
new CreateTemplateRouteStepRequest(3, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(3, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
@@ -342,11 +342,11 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
||||
|
||||
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);
|
||||
await stepService.CreateAsync(
|
||||
route.Value.Id,
|
||||
new CreateTemplateRouteStepRequest(2, null, null, null, HeadType.DirectionHead, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(2, null, null, null, HeadType.DirectionHead, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
@@ -377,7 +377,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||
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<PostgresDatabaseFi
|
||||
CancellationToken.None);
|
||||
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
|
||||
@@ -459,7 +459,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
||||
CancellationToken.None);
|
||||
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
|
||||
@@ -488,7 +488,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||
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<PostgresDatabaseFi
|
||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||
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<PostgresDatabaseFi
|
||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||
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<PostgresDatabaseFi
|
||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||
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<PostgresDatabaseFi
|
||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||
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<PostgresDatabaseFi
|
||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||
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<PostgresDatabaseFi
|
||||
CancellationToken.None);
|
||||
await stepService.CreateAsync(
|
||||
route.Value.Id,
|
||||
new CreateTemplateRouteStepRequest(1, null, "Lead", null, null, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, null, "Lead", null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
@@ -749,7 +749,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
||||
CancellationToken.None);
|
||||
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
|
||||
@@ -785,7 +785,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
||||
CancellationToken.None);
|
||||
await stepService.CreateAsync(
|
||||
route.Value.Id,
|
||||
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.DirectorTansu, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.DirectorTansu, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
@@ -832,7 +832,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
||||
CancellationToken.None);
|
||||
await stepService.CreateAsync(
|
||||
route.Value.Id,
|
||||
new CreateTemplateRouteStepRequest(5, null, null, null, HeadType.CompanyDirectors, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(5, null, null, null, HeadType.CompanyDirectors, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
@@ -883,7 +883,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
||||
CancellationToken.None);
|
||||
await stepService.CreateAsync(
|
||||
route.Value.Id,
|
||||
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.CompanyDirectors, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.CompanyDirectors, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
@@ -949,7 +949,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
||||
CancellationToken.None);
|
||||
await stepService.CreateAsync(
|
||||
route.Value.Id,
|
||||
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.CompanyDirectors, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.CompanyDirectors, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
@@ -998,11 +998,11 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
||||
CancellationToken.None);
|
||||
await stepService.CreateAsync(
|
||||
route.Value.Id,
|
||||
new CreateTemplateRouteStepRequest(5, null, null, null, HeadType.CompanyDirectors, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(5, null, null, null, HeadType.CompanyDirectors, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
await stepService.CreateAsync(
|
||||
route.Value.Id,
|
||||
new CreateTemplateRouteStepRequest(10, null, null, null, HeadType.DirectorTansu, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(10, null, null, null, HeadType.DirectorTansu, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action — тестовая конфигурация не переопределяет TnsuPortal:DirectorTansuEmail,
|
||||
@@ -1036,7 +1036,7 @@ public sealed class TemplateRouteServiceTests : IClassFixture<PostgresDatabaseFi
|
||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||
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<PostgresDatabaseFi
|
||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||
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<PostgresDatabaseFi
|
||||
var stepService = _services.GetRequiredService<TemplateRouteStepService>();
|
||||
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
|
||||
|
||||
+42
-36
@@ -43,7 +43,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
// Action
|
||||
var result = await _service.CreateAsync(
|
||||
999999,
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -59,7 +59,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
// Action
|
||||
var result = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.ProjectManager, null, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", ProjectRole.ProjectManager, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -71,14 +71,14 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
{
|
||||
// Arrange
|
||||
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, null);
|
||||
var request = new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, HeadType.Chief, RouteStepStatus.Approval, false, null, ConditionsLogic.And);
|
||||
var first = await _service.CreateAsync(routeId, request, CancellationToken.None);
|
||||
first.IsSuccess.ShouldBeTrue();
|
||||
|
||||
// Action
|
||||
var duplicate = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(2, "a@b.kz", "Engineer", null, HeadType.Chief, RouteStepStatus.Approval, true, null),
|
||||
new CreateTemplateRouteStepRequest(2, "a@b.kz", "Engineer", null, HeadType.Chief, RouteStepStatus.Approval, true, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -153,14 +153,14 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
var routeId = await CreateRouteAsync("Route with duplicate head type", byProject: false);
|
||||
var first = await _service.CreateAsync(
|
||||
routeId,
|
||||
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);
|
||||
first.IsSuccess.ShouldBeTrue();
|
||||
|
||||
// Action — другой статус (Execution вместо Approval), но тот же HeadType.Chief
|
||||
var second = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(2, null, null, null, HeadType.Chief, RouteStepStatus.Execution, true, null),
|
||||
new CreateTemplateRouteStepRequest(2, null, null, null, HeadType.Chief, RouteStepStatus.Execution, true, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -174,14 +174,14 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
var routeId = await CreateRouteAsync("Route with duplicate department head", byProject: false);
|
||||
var first = await _service.CreateAsync(
|
||||
routeId,
|
||||
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);
|
||||
first.IsSuccess.ShouldBeTrue();
|
||||
|
||||
// Action
|
||||
var second = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(2, null, null, null, HeadType.DepartmentHead, RouteStepStatus.Execution, false, null),
|
||||
new CreateTemplateRouteStepRequest(2, null, null, null, HeadType.DepartmentHead, RouteStepStatus.Execution, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -197,11 +197,11 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
// Action
|
||||
var chief = await _service.CreateAsync(
|
||||
routeId,
|
||||
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);
|
||||
var directorTansu = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(2, null, null, null, HeadType.DirectorTansu, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(2, null, null, null, HeadType.DirectorTansu, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -219,11 +219,11 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
// Action
|
||||
var small = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.LessThan, "1000") }),
|
||||
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.LessThan, "1000") }, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
var big = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(2, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }),
|
||||
new CreateTemplateRouteStepRequest(2, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -240,11 +240,11 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
// Action
|
||||
var small = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", null, null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.LessThan, "1000") }),
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", null, null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.LessThan, "1000") }, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
var big = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(2, "a@b.kz", null, null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }),
|
||||
new CreateTemplateRouteStepRequest(2, "a@b.kz", null, null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -259,14 +259,14 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
var routeId = await CreateRouteAsync("Route with head type same condition", byProject: false, listName: "Requests");
|
||||
var first = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }),
|
||||
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
first.IsSuccess.ShouldBeTrue();
|
||||
|
||||
// Action — то же условие, другой статус
|
||||
var second = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(2, null, null, null, HeadType.Chief, RouteStepStatus.Execution, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }),
|
||||
new CreateTemplateRouteStepRequest(2, null, null, null, HeadType.Chief, RouteStepStatus.Execution, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -280,14 +280,14 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
var routeId = await CreateRouteAsync("Route to update head type on itself", byProject: false);
|
||||
var created = await _service.CreateAsync(
|
||||
routeId,
|
||||
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 — HasDuplicateHeadTypeAsync должен исключать сам обновляемый шаг (excludeId)
|
||||
var updated = await _service.UpdateAsync(
|
||||
routeId,
|
||||
created.Value.Id,
|
||||
new UpdateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Execution, true, null),
|
||||
new UpdateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Execution, true, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -303,7 +303,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
// Action
|
||||
var created = await _service.CreateAsync(
|
||||
routeId,
|
||||
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);
|
||||
|
||||
// Assert
|
||||
@@ -321,7 +321,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
// Action
|
||||
var created = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -341,7 +341,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
// Action
|
||||
var created = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -359,7 +359,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
// Action
|
||||
var created = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(1, "", "", ProjectRole.ProjectManager, null, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, "", "", ProjectRole.ProjectManager, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -374,14 +374,14 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
var routeId = await CreateRouteAsync("Route to update step", byProject: false);
|
||||
var created = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
var updated = await _service.UpdateAsync(
|
||||
routeId,
|
||||
created.Value.Id,
|
||||
new UpdateTemplateRouteStepRequest(2, "b@b.kz", "Senior Engineer", null, HeadType.Chief, RouteStepStatus.Execution, true, null),
|
||||
new UpdateTemplateRouteStepRequest(2, "b@b.kz", "Senior Engineer", null, HeadType.Chief, RouteStepStatus.Execution, true, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -398,7 +398,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
var routeId = await CreateRouteAsync("Route to patch step", byProject: false);
|
||||
var created = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", null, null, null, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", null, null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
@@ -422,7 +422,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
var routeId = await CreateRouteAsync("Route to patch step into conflict", byProject: false);
|
||||
var created = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, null, null, null, HeadType.Chief, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
@@ -443,7 +443,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
var routeId = await CreateRouteAsync("Route to patch step status none", byProject: false);
|
||||
var created = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", null, null, null, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", null, null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
@@ -457,6 +457,12 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
patched.IsFailed.ShouldBeTrue();
|
||||
}
|
||||
|
||||
// ConditionsLogic здесь всюду передаётся явно, потому что он обязателен: у
|
||||
// Create/Update-запроса значения по умолчанию нет вовсе, а пропущенное в JSON
|
||||
// поле превращается в ConditionsLogic.None и отвергается валидатором на слое
|
||||
// API (TemplateRouteStepValidators + ValidationActionFilter). Отдельным тестом
|
||||
// это не покрыто — сервис валидаторы не вызывает.
|
||||
|
||||
[Fact(DisplayName = "Явные ProjectRole.None и HeadType.None при патче считаются незаполненными, как и null")]
|
||||
public async Task PatchAsync_ExplicitNoneValues_TreatedAsUnfilled()
|
||||
{
|
||||
@@ -464,7 +470,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
var routeId = await CreateRouteAsync("Route with explicit None values on patch", byProject: false);
|
||||
var created = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", null, null, null, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", null, null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
@@ -489,7 +495,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
var result = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false,
|
||||
new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "") }),
|
||||
new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "") }, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -505,7 +511,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
// Action
|
||||
var result = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }),
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -521,7 +527,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
// Action
|
||||
var created = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }),
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, new[] { new RouteStepConditionDto("Amount", ComparisonOperator.GreaterThan, "1000") }, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Assert
|
||||
@@ -539,7 +545,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
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),
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
@@ -560,7 +566,7 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
var routeId = await CreateRouteAsync("Route to delete step", byProject: false);
|
||||
var created = await _service.CreateAsync(
|
||||
routeId,
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, "a@b.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
@@ -581,16 +587,16 @@ public sealed class TemplateRouteStepServiceTests : IClassFixture<PostgresDataba
|
||||
var routeBId = await CreateRouteAsync("Route B for executor search", byProject: false);
|
||||
await _service.CreateAsync(
|
||||
routeAId,
|
||||
new CreateTemplateRouteStepRequest(1, "Target@Example.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, "Target@Example.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
await _service.CreateAsync(
|
||||
routeBId,
|
||||
new CreateTemplateRouteStepRequest(1, "target@example.kz", "Manager", null, null, RouteStepStatus.Execution, false, null),
|
||||
new CreateTemplateRouteStepRequest(1, "target@example.kz", "Manager", null, null, RouteStepStatus.Execution, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
// Другой исполнитель — не должен попасть в выдачу.
|
||||
await _service.CreateAsync(
|
||||
routeAId,
|
||||
new CreateTemplateRouteStepRequest(2, "other@example.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null),
|
||||
new CreateTemplateRouteStepRequest(2, "other@example.kz", "Engineer", null, null, RouteStepStatus.Approval, false, null, ConditionsLogic.And),
|
||||
CancellationToken.None);
|
||||
|
||||
// Action
|
||||
|
||||
Reference in New Issue
Block a user