заработало
This commit is contained in:
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user