Напоминания: убрать дубль 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:
gorbenko.pavel
2026-08-27 15:53:31 +05:00
parent 81ed85d6ce
commit 31a0a8e9e3
11 changed files with 192 additions and 102 deletions
@@ -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");
}