раздел «Отчёты» в боковом меню — скачивание отчёта по вакансиям #17

Merged
gorbenko.pavel merged 5 commits from report into main 2026-08-31 11:00:19 +00:00

5 Commits

Author SHA1 Message Date
gorbenko.pavel dd13e337f2 refactor(RecruitingHire): CandidateName/HireDate/ReplacedEmployee -> Hire* (обход коллизии Search)
Tests / frontend-test (pull_request) Successful in 1m26s
Те же три поля internalName делит форма «Реферальная программа» — Search
индексировал managed property только у одного из двух списков (см. sp-write
sitefield/field: разные Id при одинаковом имени). Тот же приём, что для
VacancyOpenReason в RecruitingSelection: переименовать в уникальное имя,
данные перенести.

  CandidateName     -> HireCandidateName
  HireDate          -> HireHireDate
  ReplacedEmployee  -> HireReplacedEmployee

На этот раз — genuine столбцы САЙТА (createsitefield + linksitefield в
sp-write), а не просто колонки списка: так задумано и в самом провижинере
(Ensure-SiteColumn — простые типы должны быть общим эталоном), просто на
практике это не всегда соблюдается (Position тоже разъехался по Id между
списками). Проверено: Id столбца на сайте и на списке совпадают.

Данные на живом сайте перенесены отдельным скриптом (migrate-hire-fields.sh,
разово, вне репозитория) — все 37 элементов, сверено на трёх.

Задело WorkplaceSetup/prefill.ts — оно читает CandidateName/HireDate ИЗ
загруженной заявки на приём для кросс-форменного предзаполнения. Referral
(та же коллизия, другая форма) НЕ трогал.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 16:41:18 +05:00
gorbenko.pavel bd7d83b9ff refactor(RecruitingSelection): OpenReason -> VacancyOpenReason (обход коллизии Search)
Колонка OpenReason/OpenReasonDetails называлась одинаково с полями в других
списках сайта, из-за чего Search индексировал managed property только у ОДНОГО
из них — не у этого (см. sp-write props/field, разбирались 2026-08-28/29 в
tnsu-approval-route). Переименование снимает коллизию: VacancyOpenReasonOWSMTXT/
VacancyOpenReasonDetailsOWSTEXT теперь индексируются нормально, проверено.

Данные из старых колонок на живом сайте уже перенесены отдельным скриптом
(migrate-openreason.sh, разово, вне репозитория). provisioning.json обновлён на
новые internalName — он только добавляет колонки, никогда не удаляет, так что
старые OpenReason/OpenReasonDetails на сайте провижининг сам не уберёт.

load.ts/submit.ts менять не пришлось — оба читают/пишут поля по ключам
IFormData обобщённо.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 16:18:48 +05:00
gorbenko.pavel 0701736e23 docs(tools/sp): корневая причина непроиндексированных полей + правка про SortList
Причина найдена: одноимённые колонки в разных списках — разные поля со своими
crawled property, автосозданное managed property привязано к одному из них.
Добавлена таблица, показывающая, что PositionOWSTEXT отдаёт значения в одном
списке и молчит в другом, — иначе картина выглядит бессмыслицей.

Исправлено неверное утверждение из предыдущего коммита: проверять существование
managed property через SortList нельзя. Sortable — независимый от Retrievable
флаг, у автосозданных ows_* обычно выключен; SortList падает 400/500 и на
заведомо живом PositionOWSTEXT. Рабочий способ — сверка Search с REST, её делает
`sp-write props`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 14:22:48 +05:00
gorbenko.pavel f7979ac534 docs(tools/sp): грабли поискового индекса и прав на элементах
Проверено на тенанте 2026-08-28, при разборе «перевести отчёт по вакансиям
на Search или нет»:

1. Часть полей индекс не отдаёт ВООБЩЕ, хотя значения в списках есть
   (OpenReason/OpenReasonDetails, CandidateName/HireDate/ReplacedEmployee,
   Ticket). Рядом в тех же списках PositionOWSTEXT, RequestStatusOWSCHCS,
   SelectionRequestIdOWSNMBR и др. отдаются нормально — то есть дело не в
   типе колонки и не в задержке crawl. Добавлена таблица с тем, какие имена
   уже перепробованы, чтобы не ходить по кругу.

2. Отличить «managed property нет» от «есть, но пусто» через SelectProperties
   нельзя — на несуществующее имя молча приходит null. Отличается сортировкой:
   SortList по несуществующему свойству падает 400/500.

3. Права у заявок портала разорваны на КАЖДОМ элементе
   (HasUniqueRoleAssignments = true), поэтому под своей учёткой в списке видно
   2 элемента из 19 — не баг и не кэш браузера. App-only это не трогает.
   Отсюда же: ItemCount списка считает всё, /items — только доступное вам.

Заодно исправлено утверждение «Search под app-only обычно ничего не отдаёт» —
на сертификате CN=PnP-SharePoint-Sync отдаёт, и postquery, и кастомные
managed properties.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 14:10:36 +05:00
gorbenko.pavel 849aa7964d feat: раздел «Отчёты» в боковом меню — скачивание отчёта по вакансиям
Tests / frontend-test (pull_request) Successful in 1m17s
Пункт меню виден только заданному списку почт (Тришина, Городецкий,
Горбенко) — группы 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>
2026-08-28 10:11:26 +05:00