Fix/by approver silent failures (#25)
Tests / frontend-test (push) Successful in 1m34s
Tests / backend-test (push) Successful in 1m45s

Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/25
This commit was merged in pull request #25.
This commit is contained in:
2026-10-05 04:38:37 +00:00
parent dcdfcdd396
commit b959feedbf
2 changed files with 228 additions and 58 deletions
@@ -54,6 +54,15 @@ public interface ISharePointClient
Task<IReadOnlyList<JsonElement>> GetItemsByListTitleAsync(
string siteUrl, string listTitle, string odataQuery, CancellationToken cancellationToken);
/// <summary>
/// Запрос в SharePoint Search (postquery) от имени приложения — видит ВСЕ
/// элементы, а не только доступные пользователю. Возвращает строки результата
/// как «имя managed property → значение» (RowLimit — не больше 500).
/// Индекс отстаёт от списков, поэтому найденное нужно перепроверять по REST.
/// </summary>
Task<IReadOnlyList<IReadOnlyDictionary<string, string?>>> SearchAsync(
string siteUrl, string queryText, IReadOnlyList<string> selectProperties, CancellationToken cancellationToken);
/// <summary>
/// Обновить поля элемента списка, найденного по заголовку. fields — анонимный
/// объект «внутреннее имя колонки → значение»; перечисленные колонки
@@ -220,6 +229,57 @@ public sealed class SharePointClient(
$"{siteUrl.TrimEnd('/')}/_api/web/lists/getByTitle('{Escape(listTitle)}')/items?{odataQuery}",
cancellationToken);
public async Task<IReadOnlyList<IReadOnlyDictionary<string, string?>>> SearchAsync(
string siteUrl, string queryText, IReadOnlyList<string> selectProperties, CancellationToken cancellationToken)
{
var body = JsonSerializer.Serialize(new
{
request = new
{
Querytext = queryText,
RowLimit = 500,
SelectProperties = selectProperties,
TrimDuplicates = false
}
});
var json = await SendAsync(
HttpMethod.Post, siteUrl, $"{siteUrl.TrimEnd('/')}/_api/search/postquery", body,
allowNotFound: false, cancellationToken);
var result = new List<IReadOnlyDictionary<string, string?>>();
if (json is null
|| !json.Value.TryGetProperty("PrimaryQueryResult", out var primary)
|| !primary.TryGetProperty("RelevantResults", out var relevant)
|| !relevant.TryGetProperty("Table", out var table)
|| !table.TryGetProperty("Rows", out var rows))
return result;
// nometadata отдаёт Rows массивом, verbose — обёрнутым в results; терпим оба.
if (rows.ValueKind == JsonValueKind.Object && rows.TryGetProperty("results", out var wrapped))
rows = wrapped;
if (rows.ValueKind != JsonValueKind.Array) return result;
foreach (var row in rows.EnumerateArray())
{
if (!row.TryGetProperty("Cells", out var cells)) continue;
if (cells.ValueKind == JsonValueKind.Object && cells.TryGetProperty("results", out var wrappedCells))
cells = wrappedCells;
var dict = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase);
foreach (var cell in cells.EnumerateArray())
{
var key = cell.TryGetProperty("Key", out var k) ? k.GetString() : null;
if (key is null) continue;
dict[key] = cell.TryGetProperty("Value", out var v) && v.ValueKind == JsonValueKind.String ? v.GetString() : null;
}
result.Add(dict);
}
return result;
}
public Task UpdateItemByListTitleAsync(
string siteUrl, string listTitle, int itemId, object fields, CancellationToken cancellationToken) =>
MergeAsync(