На одну заявку на подбор приходится несколько заявок на приём («Разнорабочий,
4 вакансии» → 4 кандидата), и раньше они схлопывались в одну строку: ФИО не
было вовсе, а «Дата выхода» показывала только САМУЮ ПОЗДНЮЮ из четырёх. Кто
именно вышел и когда — из отчёта было не узнать.
Теперь вакансия занимает столько строк, сколько по ней приёмов. Построчные
колонки — «Дата выхода», новая «Ф.И.О. нанимаемого сотрудника» и «Вместо
сотрудника Ф.И.О.» (у каждого приёма своё «взамен кого»); все остальные общие
и объединяются по вертикали.
Модель: VacancyReportRow.Lines — именно СТРОКИ ЛИСТА, а не «приёмы». У вакансии
без приёмов строка всё равно одна (иначе она пропала бы из отчёта), с пустыми
ФИО и датой, но с «Вместо сотрудника» из причины открытия вакансии — прежнее
поведение сохранено. Реальное число приёмов осталось в ClosedCount.
Отклонённые приёмы теперь отбрасываются целиком, а не только из счётчика:
отдельная строка с ФИО отклонённого кандидата утверждала бы «вот кого приняли».
Объединение заводится только при lineCount > 1 — область 1×1 Excel считает
некорректной и портит файл. Значение общих колонок пишется ТОЛЬКО в первую
строку: у объединённой области Excel хранит его в верхней левой ячейке, а дубли
сломали бы SUM в итогах (посчитал бы вакансию по разу на каждый приём) — на это
есть отдельный тест.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Одноимённые колонки в РАЗНЫХ списках — разные поля со своими GUID, значит и со
своими crawled property (ows_Position, ows_Position1, …); автосозданное managed
property привязано только к одному из них. Отсюда PositionOWSTEXT, который
отдаёт значения в «Заявка на подбор — данные» и молчит в «Прием на работу —
данные» при одном и том же имени.
Проверять надо не «работает ли свойство», а «работает ли оно для ЭТОГО списка» —
этим занимается новая команда `sp-write props`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Разбирались, можно ли перевести чтение на Search: один KQL сразу по всем
партициям, отставание индекса отчёту не критично. На тенанте проверено
поштучным сравнением Search и REST — часть нужных полей индекс не отдаёт
вообще, хотя значения в списках есть:
• Заявка на подбор: OpenReason, OpenReasonDetails
• Прием на работу: CandidateName, HireDate, ReplacedEmployee
• — согласование: Ticket (Lookup — managed property не нашлась вовсе)
Префикс группы колонок сайта (TansuSite…) не помогает, задержка crawl тоже
ни при чём: у тех же свежих элементов рабочие поля Search отдаёт сразу.
Значит эти поля пришлось бы дочитывать построчно батчами по 100, и на 1000
заявок Search+дочитка выходит ~46 HTTP-запросов против ~5 у нынешнего REST.
В list view threshold REST не упирается by design: партиции режутся на 4900
(ListPartitioning:ItemCountThreshold), ни один $filter не сканирует больше
предела.
Кода не меняем — фиксируем разбор в doc-комментарии, чтобы вопрос не
поднимался заново. Вернуться к Search осмысленно только после того, как в
Search Schema заведут работающие managed properties для этих полей.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
H1/I1/K1 («Дата подачи заявки», «Дата закрытия вакансии», «Дата
выхода») выходили с форматом даты вместо синей заливки шапки — хотя
код применял ко всей строке 1 один и тот же стиль Header.
Причина — SetDefaultColumnStyle(col, DataDate), вызванный ПОСЛЕ записи
строк: в отличие от чистого OOXML, где стиль колонки уступает явному
стилю ячейки, NPOI этим вызовом переписывает стиль уже существующих
ячеек колонки целиком, включая шапку. Формат даты и так стоит на каждой
конкретной ячейке с датой (SetDate) — вызов просто убран.
Добавлен регрессионный тест (сравнение Index стиля, а не ссылки на
объект — NPOI отдаёт новую обёртку ICellStyle на каждый вызов
CellStyle, ссылки не совпадают даже у идентичного стиля).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Якорь второй диаграммы стоял на строке заголовка второй вертикальной
таблицы — при коротком списке рекрутёров (2-4 человека) это оказывалось
внутри вертикального диапазона первой диаграммы (фиксированные 14
строк), и они наезжали друг на друга. Теперь обе диаграммы позиционируются
независимо от строк таблиц: вторая всегда сразу под первой (ChartHeightRows).
Заливка шапки «Общий объем работ»/«Общий объем в работе» теперь на всю
ширину блока (по числу рекрутёров), а не только под текстом — иначе она
выглядела как две узкие цветные ячейки на белом фоне.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Второй лист xlsx по образцу ручного отчёта HR: горизонтальная таблица
по рекрутёрам (всего/в работе %/закрыто %) и две вертикальные таблицы
с долей каждого рекрутёра в общем объёме и в объёме "в работе" — плюс
по круговой диаграмме на каждую (VacancyReportSummary,
VacancyReportWorkbook.BuildSummarySheet).
Библиотека для xlsx сменена с ClosedXML на NPOI: у ClosedXML нет
поддержки Excel-диаграмм вовсе, только у NPOI из бесплатных вариантов
она есть (XSSFChart/DataSources из NPOI.OOXML). Переписан весь
генератор, тесты читают файл обратно тем же NPOI. Заодно запинена
System.Security.Cryptography.Xml до 10.0.11 — версия, которую тянет
NPOI транзитивно (8.0.2), с известной уязвимостью.
Вакансии без рекрутёра сведены в «Без рекрутёра» и всегда идут в
таблицах последними — не участник рейтинга, а технический хвост.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
GET api/reports/vacancies?from=&to= отдаёт лист «Список вакансий (все)»
по образцу ручного отчёта HR: те же шестнадцать колонок, разделители по
объектам, двойная нумерация, итоги формулами.
Данные — из двух форм портала: «Заявка на подбор» даёт вакансию,
«Приём на работу» — её закрытие (связь по SelectionRequestId +
SelectionRequestListId). Рекрутёр и дата закрытия берутся из фазы
«Исполнение» парного списка «— согласование»: в самой заявке их нет.
Период отбирает заявки по «Дате подачи заявки» с откатом на Created —
поле заполняется руками и часто пустое. Заявки на приём периодом не
ограничены: приём по вакансии из начала интервала мог оформляться уже
после его конца.
Колонка «Комментарии» остаётся пустой — в ручном отчёте это пометки
рекрутёра, которым в заявках пока негде храниться.
Генерация — ClosedXML (MIT): нужны стили, формулы и autofilter, а EPPlus
с v5 под Polyform Noncommercial.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Канву SharePoint мы и раньше разжимали (тот же :global хак + supportsFullBleed
в манифесте), но содержимое упиралось в max-width: 1080px у .content и
центрировалось — визуально это читалось как «не открывается на всю ширину».
Переношу раскладку портала (RequestPortal.module.scss):
- корень .approvalAdmin — width: 100%, padding: 0;
- .shell больше не карточка: без рамки, тени, скруглений и overflow: hidden
(последний ломает sticky), align-items: flex-start, min-height: 78vh;
- .side — заливка $navy-800 на колонке (align-self: stretch), сам Sidebar
внутри sticky на 100vh: градиент не плывёт от высоты соседа, и под рейлом
на длинных таблицах нет белого провала, если sticky не включился;
- .content — сняты max-width и margin: auto, остались только отступы;
- мобильный брейкпоинт обновлён под новую схему: десктопные правила рейла
снимаются явно, иначе sticky+100vh спорил бы с position: fixed, который
ui-kit ставит drawer'у, при равной специфичности.
Разметка AdminApp.tsx не менялась — структура уже совпадала с портальной.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
---------
Co-authored-by: gorbenko.pavel <gorbenko.pavel@tansu.kz>
Reviewed-on: http://git.tnsu.kz/gorbenko.pavel/tnsu-approval-route/pulls/17
Правила из frontend/.gitignore перенесены в корневой .gitignore,
дублирующий файл удалён. README дополнен описанием SPFx frontend
(ApprovalAdmin), который ранее был ошибочно указан как отсутствующий.
4-слойная архитектура (Api/Application/Domain/Infrastructure) на .NET 10
с MediatR и EF Core + PostgreSQL, без бизнес-логики. Добавлены
docker-compose, .env.example и README для локального запуска.