раздел «Отчёты» в боковом меню — скачивание отчёта по вакансиям (#17)
Tests / frontend-test (push) Successful in 1m15s
Tests / frontend-test (push) Successful in 1m15s
Пункт меню виден только заданному списку почт (Тришина, Городецкий, Горбенко) — группы HR в SharePoint пока нет, добавили TODO завести проверку по ней отдельно (reportsAccess.ts). Скрыт из меню совсем, а не показан неактивным, и прямой переход по ?rpnav=reports у остальных редиректит на «Новую заявку». Раздел — форма периода (С/По) и кнопка «Скачать xlsx», дергает GET api/reports/vacancies через тот же AadHttpClient, что и остальные вызовы approval-route API. Имя файла собирается на фронте по формату бэкенда (VacancyReportWorkbook.FileName), а не читается из Content-Disposition — approval-route не объявляет заголовок в Access-Control-Expose-Headers, браузер бы его не отдал. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz> Reviewed-on: http://git.tnsu.kz/timur.gorodetskiy/request-portal/pulls/17
This commit was merged in pull request #17.
This commit is contained in:
+56
-4
@@ -13,10 +13,14 @@
|
||||
пользователь вводит его в браузере сам, дальше CLI for Microsoft 365 держит токен у
|
||||
себя. Ни пароля, ни client secret в репозитории и в командах нет.
|
||||
|
||||
**Работаем от имени пользователя, а не приложения.** Это не удобство, а требование:
|
||||
SharePoint Search REST (`/_api/search/query`) под app-only-токеном обычно не отдаёт
|
||||
ничего, ему нужен пользовательский контекст. А Search — главное, что здесь измеряют.
|
||||
Побочный плюс: видно ровно то же, что видит пользователь в портале.
|
||||
**Работаем от имени пользователя, а не приложения.** Главное, что это даёт: видно
|
||||
ровно то же, что видит пользователь в портале, — а у заявок портала это принципиально,
|
||||
потому что права разорваны НА КАЖДОМ элементе (см. «Грабли» ниже).
|
||||
|
||||
Раньше здесь было написано, что Search под app-only-токеном «обычно не отдаёт ничего».
|
||||
Проверено 2026-08-28 на сертификате `CN=PnP-SharePoint-Sync` (тот же, что у бэкенда
|
||||
`tnsu-approval-route`): **отдаёт** — и `postquery`, и кастомные managed properties.
|
||||
Так что app-only Search как таковой рабочий; ограничения у него другие, см. «Грабли».
|
||||
|
||||
## Разовая подготовка
|
||||
|
||||
@@ -96,3 +100,51 @@ claims-логине частей будет четыре. Разбирать с
|
||||
партиций и в query-строке упирается в предел длины URL. Форма тела взята один в один
|
||||
из PnPjs (`@pnp/sp/search/query.js`): `{ request: { ...query } }`, массивы плоские,
|
||||
`odata=nometadata`. Иначе инструмент мерил бы не то, что портал.
|
||||
|
||||
**Часть полей индекс не отдаёт ВООБЩЕ, хотя значения в списках есть** (проверено
|
||||
2026-08-28 поштучным сравнением Search и REST на одних и тех же элементах):
|
||||
|
||||
| Список | Поле | Пробовали имена |
|
||||
| --- | --- | --- |
|
||||
| Заявка на подбор | `OpenReason`, `OpenReasonDetails` | `…OWSCHCS`, `…OWSMTXT`, `…OWSTEXT`, с префиксом `TansuSite` |
|
||||
| Прием на работу | `CandidateName`, `HireDate`, `ReplacedEmployee` | `…OWSTEXT`, `…OWSDATE`, с префиксом `TansuSite` |
|
||||
| — согласование | `Ticket` (Lookup) | `TicketOWSTEXT`, `TicketOWSLKID`, `TicketId`, `Ticket` |
|
||||
|
||||
Отличить «managed property нет вовсе» от «есть, но у строк пусто» через
|
||||
`SelectProperties` НЕЛЬЗЯ — на несуществующее имя ответ молча приходит с `null`.
|
||||
**И `SortList` для этого не годится тоже**, хотя соблазнительно: `Sortable` — флаг,
|
||||
независимый от `Retrievable`, и у автосозданных `ows_*` свойств он обычно выключен.
|
||||
`SortList` падает 400/500 и на заведомо живом `PositionOWSTEXT`, то есть меряет не то.
|
||||
Рабочий способ — сверять Search с REST на одних и тех же элементах, это делает
|
||||
`sp-write props` (репозиторий `C:\work\git\tools`).
|
||||
|
||||
**Корневая причина (нашлась 2026-08-28): одноимённые колонки в разных списках — это
|
||||
РАЗНЫЕ поля.** У каждого свой GUID, значит и своё crawled property (`ows_Position`,
|
||||
`ows_Position1`, …), а автосозданное managed property привязано только к одному из них.
|
||||
Отсюда картина, которая иначе выглядит бессмыслицей:
|
||||
|
||||
| Managed property | «Заявка на подбор 1 — данные» | «Прием на работу 1 — данные» |
|
||||
| --- | --- | --- |
|
||||
| `PositionOWSTEXT` | отдаёт | **молчит** |
|
||||
| `CandidateNameOWSTEXT` | (колонки нет) | **молчит**, но свойство рабочее в других списках |
|
||||
| `RequestStatusOWSCHCS` | отдаёт | отдаёт |
|
||||
|
||||
То есть проверять надо не «работает ли свойство», а «работает ли оно **для этого
|
||||
списка**». `sp-write props` в конце как раз и говорит, рабочее ли свойство где-то ещё
|
||||
на сайте: если да — колонка этого списка отдельное поле, и в Search Schema нужно
|
||||
домапить её crawled property к тому же managed property; если нигде — свойства нет и
|
||||
его надо заводить.
|
||||
|
||||
Полностью отсутствуют в схеме (проверено по всему сайту): `OpenReason`,
|
||||
`OpenReasonDetails`, `HireDate`.
|
||||
|
||||
Из-за всего этого отчёт по вакансиям в `tnsu-approval-route` остался на items REST по
|
||||
партициям, а не на Search — подробный расчёт в doc-комментарии `VacancyReportService`.
|
||||
|
||||
**Права разорваны на КАЖДОМ элементе.** У заявок портала
|
||||
`HasUniqueRoleAssignments = true` поштучно: доступ выдан только участникам заявки плюс
|
||||
общему админу. Поэтому под своей учёткой в списке видно 2 элемента из 19 — и это не
|
||||
баг и не кэш браузера, а модель доступа. App-only токен (сертификат) это не трогает и
|
||||
читает всё. Если сравниваете «сколько элементов в списке», обязательно фиксируйте, под
|
||||
кем смотрите: `ItemCount` в метаданных списка считает ВСЁ, а `/items` — только
|
||||
доступное вам.
|
||||
|
||||
Reference in New Issue
Block a user