Архивация списков (#13)
Tests / frontend-test (push) Successful in 44s
Tests / backend-test (push) Failing after 17m4s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/13
This commit was merged in pull request #13.
This commit is contained in:
2026-08-19 09:08:36 +00:00
parent a2672042d7
commit 82512eb1db
56 changed files with 2587 additions and 51 deletions
@@ -3,6 +3,7 @@ using FluentResults;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Tnsu.ApprovalRoute.Application.ListPartitioning;
using Tnsu.ApprovalRoute.Domain.Entities;
using Tnsu.ApprovalRoute.Infrastructure.Persistence;
using Tnsu.ApprovalRoute.Infrastructure.SharePoint;
@@ -20,6 +21,7 @@ namespace Tnsu.ApprovalRoute.Application.Reminders;
public sealed class ReminderService(
ApprovalRouteDbContext db,
ISharePointClient sharePoint,
ListPartitionRegistry partitionRegistry,
IOptions<RemindersOptions> options,
ILogger<ReminderService> logger)
{
@@ -62,14 +64,60 @@ public sealed class ReminderService(
}
}
/// <summary>
/// Обходит ВСЕ партиции типа заявки (архив + текущая) — заявка, уехавшая в
/// архив, продолжает ждать чьего-то решения точно так же, как в текущем
/// списке, и не должна пропадать из напоминаний только из-за того, что для
/// новых заявок завели следующий список.
///
/// request_list_partitions хранит только архив (см. ListPartitionRegistry) —
/// текущая партиция синтезируется отдельно и добавляется к архиву здесь же,
/// а не читается из БД.
/// </summary>
private async Task<IReadOnlyList<ReminderDto>> GetOverdueForTypeAsync(
RequestType type, DateTimeOffset now, CancellationToken cancellationToken)
{
var archived = await partitionRegistry.GetArchivedAsync(type, cancellationToken);
var current = await partitionRegistry.BuildCurrentAsync(type, archived, cancellationToken);
var partitions = archived
.Select(x => (x.ListId, x.Number))
.Append((current.ListId, current.Number));
var reminders = new List<ReminderDto>();
foreach (var (listId, number) in partitions)
{
try
{
reminders.AddRange(await GetOverdueForPartitionAsync(type, listId, number, now, cancellationToken));
}
catch (Exception e)
{
// Недоступная партиция (например, архивный список переименовали
// или удалили вручную) не должна обнулять напоминания по
// остальным партициям того же типа.
logger.LogError(
e, "Напоминания по списку {ListId} (партиция №{Number}) типа «{Type}» не собраны",
listId, number, type.Name);
}
}
return reminders;
}
private async Task<IReadOnlyList<ReminderDto>> GetOverdueForPartitionAsync(
RequestType type, string listId, int number, DateTimeOffset now, CancellationToken cancellationToken)
{
var siteUrl = type.SiteUrl!;
var dataListTitle = await sharePoint.GetListTitleAsync(siteUrl, type.ListId!, cancellationToken);
// Читаем Title ЖИВЬЁМ — заголовок могли переименовать вручную, а по url
// список найдётся всегда.
var dataListTitle = await sharePoint.GetListTitleAsync(siteUrl, listId, cancellationToken);
if (string.IsNullOrWhiteSpace(dataListTitle))
{
logger.LogWarning("Список данных типа «{Type}» не найден по url {Url}", type.Name, type.ListId);
logger.LogWarning(
"Список данных партиции №{Number} типа «{Type}» не найден по url {Url}",
number, type.Name, listId);
return [];
}