Архивация списков (#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
@@ -0,0 +1,47 @@
namespace Tnsu.ApprovalRoute.Domain.Entities;
/// <summary>
/// Один SharePoint-список данных из нумерованной серии по типу заявок.
///
/// Списки заявок нумерованные («&lt;Форма&gt; &lt;N&gt; — данные»): когда в текущем
/// списке набирается порог элементов, рядом создаётся следующая пара (партиция), а
/// прежняя остаётся архивом только для чтения. Эта таблица — реестр всей серии: без
/// неё поиск по заявкам типа не знает, по каким спискам искать.
///
/// Текущий (тот, куда идёт запись) список продолжает лежать в
/// <see cref="RequestType.ListId"/> — реестр его дублирует строкой с наибольшим
/// <see cref="Number"/>, но источником правды для записи остаётся RequestType.
///
/// Сайта у партиции нет намеренно: вся серия живёт на одном сайте — том, что указан
/// в <see cref="RequestType.SiteUrl"/> на момент создания партиции. Хранить его
/// отдельно на каждой партиции — заводить возможность архивам разъехаться по разным
/// сайтам, а такой сценарий не поддерживается ни провижинером (см.
/// SharePointListProvisioner — всегда пишет на siteUrl запрашивающего типа заявок),
/// ни поиском. Если сайт типа заявок когда-нибудь придётся сменить, старые партиции
/// нужно будет переносить осознанно, а не молча продолжать читать их со старого адреса.
/// </summary>
public class RequestListPartition
{
public int Id { get; set; }
public int RequestTypeId { get; set; }
public RequestType? RequestType { get; set; }
/// <summary>Номер партиции в серии, с 1. Уникален в пределах типа заявок.</summary>
public int Number { get; set; }
/// <summary>
/// Серверный относительный url списка — та же семантика, что у
/// <see cref="RequestType.ListId"/> (несмотря на имя, это url, а не GUID).
/// </summary>
public string ListId { get; set; } = string.Empty;
/// <summary>
/// Title списка на момент создания — для админки, чтобы показать серию без
/// похода в SharePoint за каждым заголовком. Может разойтись с фактическим,
/// если список переименовали вручную.
/// </summary>
public string? Title { get; set; }
public DateTimeOffset CreatedAt { get; set; }
}
@@ -25,5 +25,23 @@ public class RequestType
/// </summary>
public bool? AskOverdueReason { get; set; }
/// <summary>
/// Момент, когда фоновая задача создания следующей партиции списка взяла эту
/// строку в работу. null — задача не выполняется.
///
/// Одновременно и признак «идёт провижининг», и БЛОКИРОВКА: задача стартует
/// только если сумела перевести поле из null в now() одним условным UPDATE
/// (см. ListPartitioningLock) — тогда две параллельные задачи по одному типу
/// заявок невозможны.
///
/// Метка времени, а не bool, намеренно: упавший процесс не успевает снять
/// флаг, и bool заблокировал бы тип навсегда, а по метке видно, что блокировка
/// просрочена, и её можно перехватить.
/// </summary>
public DateTimeOffset? ListPartitioningStartedAt { get; set; }
public ICollection<TemplateRoute> Routes { get; set; } = new List<TemplateRoute>();
/// <summary>Нумерованная серия партиций списка этого типа заявок (архив + текущая).</summary>
public ICollection<RequestListPartition> ListPartitions { get; set; } = new List<RequestListPartition>();
}