diff --git a/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderDto.cs b/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderDto.cs
index 739d177..73f808a 100644
--- a/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderDto.cs
+++ b/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderDto.cs
@@ -11,6 +11,10 @@ namespace Tnsu.ApprovalRoute.Application.Reminders;
/// наследованием (шаг перекрывает тип заявки).
///
/// Статус просроченного шага (ApprovalStatus строки списка «… — согласование»).
+///
+/// Уникальный код заявки (колонка RequestUniqueCode списка данных) — null, если у
+/// заявки его нет или строка данных не найдена.
+///
public sealed record ReminderDto(
string Email,
int HoursOverdue,
@@ -19,4 +23,5 @@ public sealed record ReminderDto(
int RequestTypeId,
string RequestTypeName,
int RequestId,
- string Status);
+ string Status,
+ string? RequestUniqueCode);
diff --git a/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderService.cs b/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderService.cs
index bc2d28d..9fea9c6 100644
--- a/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderService.cs
+++ b/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderService.cs
@@ -96,6 +96,9 @@ public sealed class ReminderService(
var active = await ReadRowsAsync(siteUrl, approvalListTitle, ActiveStatusFilter, cancellationToken);
if (active.Count == 0) return [];
+ var uniqueCodes = await ReadUniqueCodesAsync(
+ siteUrl, type.ListId!, active.Select(r => r.TicketId).Distinct(), cancellationToken);
+
var reminders = new List();
foreach (var row in active)
{
@@ -127,7 +130,8 @@ public sealed class ReminderService(
type.Id,
type.Name,
row.TicketId,
- row.Status));
+ row.Status,
+ uniqueCodes.GetValueOrDefault(row.TicketId)));
}
return reminders;
@@ -172,4 +176,30 @@ public sealed class ReminderService(
private static List Parse(IEnumerable items) =>
items.Select(ApprovalRow.TryParse).Where(r => r is not null).Select(r => r!).ToList();
+
+ ///
+ /// RequestUniqueCode заявок из списка данных — только по заявкам, у которых
+ /// сейчас есть просроченный шаг (а не по всему списку: он может быть большим).
+ ///
+ private async Task> ReadUniqueCodesAsync(
+ string siteUrl, string dataListUrl, IEnumerable ticketIds, CancellationToken cancellationToken)
+ {
+ var filter = string.Join(" or ", ticketIds.Select(id => $"Id eq {id}"));
+ if (filter.Length == 0) return new Dictionary();
+
+ var items = await sharePoint.GetItemsByListUrlAsync(
+ siteUrl, dataListUrl, $"$select=Id,RequestUniqueCode&$filter={filter}", cancellationToken);
+
+ var result = new Dictionary();
+ foreach (var item in items)
+ {
+ if (!item.TryGetProperty("Id", out var idProp) || !idProp.TryGetInt32(out var id)) continue;
+
+ result[id] = item.TryGetProperty("RequestUniqueCode", out var code) && code.ValueKind == JsonValueKind.String
+ ? code.GetString()
+ : null;
+ }
+
+ return result;
+ }
}
diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs
index 9d60089..57d4861 100644
--- a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs
+++ b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs
@@ -92,6 +92,13 @@ public sealed class ReminderServiceTests : IClassFixture(q => q.Contains("ApprovalStatus eq")), It.IsAny()))
.ReturnsAsync(activeRows.Select(Json).ToList());
+ // RequestUniqueCode запрашивается по списку данных сразу для всех активных
+ // заявок — по умолчанию тесты его не проверяют, поэтому мок отдаёт пусто.
+ sharePoint
+ .Setup(x => x.GetItemsByListUrlAsync(
+ SiteUrl, listUrl, It.Is(q => q.Contains("RequestUniqueCode")), It.IsAny()))
+ .ReturnsAsync([]);
+
return new ReminderService(
_services.GetRequiredService(),
sharePoint.Object,