заработало

This commit is contained in:
gorbenko.pavel
2026-08-12 17:07:21 +05:00
parent 0b2a05cd94
commit 6daecd2e18
6 changed files with 17 additions and 277 deletions
@@ -27,10 +27,10 @@ public sealed class ReminderService(
// на большом списке получится неподъёмной длины URL.
private const int IdChunkSize = 50;
// Верхняя граница на число одновременно активных шагов по типу заявки —
// запас с большим избытком (это не общий объём заявок, а только те, что
// прямо сейчас на согласовании/исполнении).
private const int SearchRowLimit = 5000;
// $filter по Choice-колонке — сравнение на равенство, а не текстовый поиск,
// поэтому кавычки вокруг значения достаточно (в самих статусах нет апострофов).
private const string ActiveStatusFilter =
$"$filter=ApprovalStatus eq '{ApprovalStatuses.OnApproval}' or ApprovalStatus eq '{ApprovalStatuses.OnExecution}'";
public async Task<Result<IReadOnlyList<ReminderDto>>> GetOverdueAsync(
DateTimeOffset now, CancellationToken cancellationToken)
@@ -81,37 +81,17 @@ public sealed class ReminderService(
return [];
}
// Шаг 1. Активные строки — через Search, а не через $filter: дело не в
// кириллице конкретно, а в том, что $filter по Choice-колонке в
// SharePoint REST в принципе ненадёжен (молча возвращает 0 строк вместо
// ошибки). Managed property ApprovalStatusOWSCHCS для этой же колонки
// этого же списка уже проверена и используется в request-portal
// (см. MP_STATUS в myRequests.service.ts) — тот же приём годится и тут.
// Шаг 1. Активные строки — прямым $filter по полю ApprovalStatus.
//
// Так же решается вопрос объёма: без Search пришлось бы читать ВСЕ
// нерешённые строки списка (queued + active), чтобы отобрать активные
// на клиенте — при большом архиве заявок это росло бы неограниченно.
// Search сужает набор на стороне SharePoint до тех, что реально нужны.
var relativeUrl = await sharePoint.GetListServerRelativeUrlAsync(siteUrl, approvalListTitle, cancellationToken);
if (string.IsNullOrWhiteSpace(relativeUrl))
{
logger.LogWarning("Список согласования «{List}» не найден", approvalListTitle);
return [];
}
var pathClause = $"Path:\"{siteUrl.TrimEnd('/')}{relativeUrl}*\"";
var statusClause = $"({SlaStatus.OnApprovalProperty} OR {SlaStatus.OnExecutionProperty})";
var activeIds = await sharePoint.SearchListItemIdsAsync(
siteUrl, $"{pathClause} AND {statusClause}", SearchRowLimit, cancellationToken);
if (activeIds.Count == 0) return [];
// Search отдаёт только Id — реальные поля (email, SlaHours, TicketId и
// т.д.) дочитываем REST'ом, как и везде в этом сервисе (см. ReadCreatedAsync).
// Заодно и статус здесь берётся из свежих данных, а не из индекса, который
// мог успеть отстать (та же оговорка, что в request-portal).
var active = (await ReadRowsByIdsAsync(siteUrl, approvalListTitle, activeIds, cancellationToken))
.Where(r => r.Status is ApprovalStatuses.OnApproval or ApprovalStatuses.OnExecution)
.ToList();
// Ранее здесь стоял Search по managed property (ApprovalStatusOWSCHCS) —
// тот же приём, что в request-portal (см. MP_STATUS). На практике он
// строки не нашёл: индекс мог не подхватить именно эту managed property
// для этого списка (она формируется автоматически, и после провижининга
// колонок SLA переиндексация могла ещё не пройти) — сама причина не
// подтверждена, а вот результат — нет. Прямой $filter по полю сработал:
// он не отстаёт от реального состояния списка (не зависит от индексации
// вовсе), и подтверждённой на практике проблемы с ним на этом списке нет.
var active = await ReadRowsAsync(siteUrl, approvalListTitle, ActiveStatusFilter, cancellationToken);
if (active.Count == 0) return [];
// Шаг 2. По заявкам с активными строками нужны ВСЕ строки маршрута —
@@ -239,20 +219,6 @@ public sealed class ReminderService(
return Parse(items);
}
/// <summary>Дочитать полные поля строк по Id, найденным через Search (см. GetOverdueForTypeAsync).</summary>
private async Task<IReadOnlyList<ApprovalRow>> ReadRowsByIdsAsync(
string siteUrl, string listTitle, IReadOnlyList<int> ids, CancellationToken cancellationToken)
{
var rows = new List<ApprovalRow>();
foreach (var chunk in ids.Chunk(IdChunkSize))
{
var filter = "$filter=" + string.Join(" or ", chunk.Select(id => $"Id eq {id}"));
rows.AddRange(await ReadRowsAsync(siteUrl, listTitle, filter, cancellationToken));
}
return rows;
}
private async Task<IReadOnlyList<ApprovalRow>> ReadRowsByTicketsAsync(
string siteUrl, string listTitle, IReadOnlyList<int> ticketIds, CancellationToken cancellationToken)
{