diff --git a/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderService.cs b/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderService.cs
index 45cac5b..f38ffd4 100644
--- a/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderService.cs
+++ b/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderService.cs
@@ -312,6 +312,19 @@ public sealed class ReminderService(
return result;
}
+ ///
+ /// Часовой пояс, в котором считаются КАЛЕНДАРНЫЕ даты испытательного срока.
+ ///
+ /// Нужен потому, что портал пишет дату приёма как местную полночь, переведённую
+ /// в UTC: в списке она лежит как 19:00Z ПРЕДЫДУЩЕГО дня. Сравнивая по UTC-датам,
+ /// сервис видел бы дату приёма на сутки раньше той, что видит кадровик в
+ /// портале, и напоминание уходило бы на день раньше срока.
+ ///
+ /// Константой, а не настройкой: смещение фиксировано для всей организации и не
+ /// меняется от окружения (перехода на летнее время нет).
+ ///
+ private static readonly TimeSpan LocalOffset = TimeSpan.FromHours(6);
+
private static readonly IReadOnlyDictionary ProbationMonthsByOption =
new Dictionary
{
@@ -424,8 +437,11 @@ public sealed class ReminderService(
if (!ProbationMonthsByOption.TryGetValue(row.ProbationPeriod ?? string.Empty, out var months))
return null; // "Без испытательного срока", пусто или значение вне справочника — отсчитывать нечего.
- var probationEndsAt = hireDate.AddMonths(months);
- var daysLeft = (probationEndsAt.Date - now.Date).Days;
+ // Обе даты — в местном поясе (см. LocalOffset): и дату приёма, и «сегодня»
+ // надо брать по тому же календарю, который видит человек в портале.
+ var hireDateLocal = hireDate.ToOffset(LocalOffset);
+ var probationEndsAt = hireDateLocal.AddMonths(months);
+ var daysLeft = (probationEndsAt.Date - now.ToOffset(LocalOffset).Date).Days;
if (daysLeft != options.Value.ProbationReminderDaysBeforeEnd) return null;
var candidateName = row.CandidateName.Trim();
@@ -470,7 +486,9 @@ public sealed class ReminderService(
CandidateEmail: candidateEmail,
CandidateName: candidateName,
ProbationPeriod: row.ProbationPeriod,
- HireDate: hireDate,
+ // Тоже в местном поясе — иначе в письме дата приёма была бы на сутки
+ // раньше той, от которой посчитан ProbationEndsAt рядом.
+ HireDate: hireDateLocal,
ProbationEndsAt: probationEndsAt,
DaysUntilProbationEnds: daysLeft,
// Не ссылка на саму заявку на приём (она уже закрыта, решать по ней
diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs
index 6787ab6..2f663a2 100644
--- a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs
+++ b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs
@@ -522,6 +522,30 @@ public sealed class ReminderServiceTests : IClassFixture();
+ reminder.DaysUntilProbationEnds.ShouldBe(3);
+ // И дата приёма, и конец срока отдаются в том же местном календаре.
+ reminder.HireDate.Date.ShouldBe(new DateTime(2026, 6, 15));
+ reminder.ProbationEndsAt.Date.ShouldBe(new DateTime(2026, 8, 15));
+ }
+
// Испытательный срок не бывает длиннее 6 месяцев (см. ProbationMonthsByOption
// в ReminderService), поэтому заявка, способная дать напоминание сегодня,
// физически не может лежать раньше предпоследней партиции. Три партиции: