diff --git a/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderService.cs b/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderService.cs index f29ff7c..7829f26 100644 --- a/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderService.cs +++ b/backend/Tnsu.ApprovalRoute.Application/Reminders/ReminderService.cs @@ -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 options, ILogger logger) { @@ -57,14 +59,63 @@ public sealed class ReminderService( return Result.Ok>(reminders); } + /// + /// Обходит ВСЕ партиции типа заявки (архив + текущая) — заявка, уехавшая в + /// архив, продолжает ждать чьего-то решения точно так же, как в текущем + /// списке, и не должна пропадать из напоминаний только из-за того, что для + /// новых заявок завели следующий список. + /// + /// EnsureCurrentRegisteredAsync — на случай, если этот тип заявок ни разу не + /// читали через ListPartitionService (сам реестр партиций мог ещё не знать + /// про текущий список): без неё серия была бы пустой и напоминания молча не + /// строились бы даже по текущему списку. + /// private async Task> GetOverdueForTypeAsync( RequestType type, DateTimeOffset now, CancellationToken cancellationToken) + { + await partitionRegistry.EnsureCurrentRegisteredAsync(type, cancellationToken); + + var partitions = await db.RequestListPartitions.AsNoTracking() + .Where(x => x.RequestTypeId == type.Id) + .OrderBy(x => x.Number) + .ToListAsync(cancellationToken); + + var reminders = new List(); + foreach (var partition in partitions) + { + try + { + reminders.AddRange(await GetOverdueForPartitionAsync(type, partition, now, cancellationToken)); + } + catch (Exception e) + { + // Недоступная партиция (например, архивный список переименовали + // или удалили вручную) не должна обнулять напоминания по + // остальным партициям того же типа. + logger.LogError( + e, "Напоминания по списку {ListId} (партиция №{Number}) типа «{Type}» не собраны", + partition.ListId, partition.Number, type.Name); + } + } + + return reminders; + } + + private async Task> GetOverdueForPartitionAsync( + RequestType type, RequestListPartition partition, DateTimeOffset now, CancellationToken cancellationToken) { var siteUrl = type.SiteUrl!; - var dataListTitle = await sharePoint.GetListTitleAsync(siteUrl, type.ListId!, cancellationToken); + + // Читаем Title ЖИВЬЁМ, а не из partition.Title (закешированного на момент + // создания партиции) — тот же принцип, что был здесь и раньше для + // единственного списка: заголовок могли переименовать вручную, и кеш + // тогда соврёт, а по url список найдётся всегда. + var dataListTitle = await sharePoint.GetListTitleAsync(siteUrl, partition.ListId, cancellationToken); if (string.IsNullOrWhiteSpace(dataListTitle)) { - logger.LogWarning("Список данных типа «{Type}» не найден по url {Url}", type.Name, type.ListId); + logger.LogWarning( + "Список данных партиции №{Number} типа «{Type}» не найден по url {Url}", + partition.Number, type.Name, partition.ListId); return []; } diff --git a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs index 9d60089..a476e9f 100644 --- a/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs +++ b/backend/tests/Tnsu.ApprovalRoute.IntegrationTests/ApplicationServices/ReminderServiceTests.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; using Shouldly; +using Tnsu.ApprovalRoute.Application.ListPartitioning; using Tnsu.ApprovalRoute.Application.Reminders; using Tnsu.ApprovalRoute.Application.RequestTypes; using Tnsu.ApprovalRoute.Infrastructure.Persistence; @@ -95,6 +96,7 @@ public sealed class ReminderServiceTests : IClassFixture(), sharePoint.Object, + _services.GetRequiredService(), Options.Create(new RemindersOptions()), NullLogger.Instance); }