Заявка на услуги #18

Merged
gorbenko.pavel merged 15 commits from service-request into main 2026-09-01 12:48:41 +00:00

15 Commits

Author SHA1 Message Date
gorbenko.pavel 63c674ed99 Merge branch 'claude/heuristic-jones-cb2a26' into service-request
Tests / frontend-test (pull_request) Successful in 1m18s
2026-09-01 17:25:21 +05:00
gorbenko.pavel da5c3807e8 fix(print): убрать полосы прокрутки из печати таблиц
На печати заявки под строками сметы и объёмов работ выходила серая полоса
прокрутки со стрелками (видно в PDF): контейнеры с overflow-x: auto
печатаются вместе со своим скроллбаром, а уехавшее вбок на бумаге просто
теряется — прокрутить лист нельзя.

В @media print отпускаем ширину: overflow: visible и min-width: 0, тогда
таблица укладывается в лист целиком. Помимо новых блоков «Заявки на
услуги» это же исправлено у общей ItemsTable (печать СЗ на перемещение
ТМЦ) и у матрицы согласования — там ровно тот же контейнер.

Блоки @media print стоят В КОНЦЕ файлов намеренно: медиазапрос не
повышает специфичность, и базовое `.row { min-width: 640px }`, объявленное
ниже, перебивало бы правило печати (поймано на скомпилированном CSS).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 17:19:30 +05:00
gorbenko.pavel c5a443b5d1 Включить react-hooks/exhaustive-deps и разобрать устаревшие замыкания
Правило было не задано вовсе: плагин eslint-plugin-react-hooks подключает
профиль @microsoft/eslint-config-spfx/lib/profiles/react, но правил из него
не включает. Теперь в .eslintrc.js явно заданы rules-of-hooks (error) и
exhaustive-deps (warn); сборка проходит с нулём предупреждений.

Что реально чинилось, а не затыкалось:

- shared/useStableCallback.ts — обёртка с неизменной ссылкой над колбэком-пропом
  (аналог будущего useEffectEvent). Ссылка стабильна, внутри всегда актуальная
  версия колбэка, поэтому её можно честно класть в зависимости.
- onCodeLoaded во всех forms/*/*View.tsx и *Form.tsx идёт в эффект загрузки
  через эту обёртку: просто дописать проп в зависимости нельзя — заявка
  перезапрашивалась бы на каждый рендер родителя.
- useRequestSubmit: run стабилизирован (useCallback + savingRef вместо чтения
  состояния), возвращаемый объект мемоизирован. Формы берут из него стабильный
  setError и указывают в зависимостях его, а не submit целиком.
- shared/usePersistedQuery.ts (useRoutedQuery) — было устаревшее замыкание:
  эффект навсегда запоминал первую версию onQueryChange, а та замкнута на route
  на момент монтирования, то есть набранный запрос мог уехать в маршрут чужого
  раздела. Теперь зовётся актуальная версия.
- propertyPane/DebugUserField.tsx — то же самое с onResolved.
- Формы: в эффект "новая заявка по умолчанию на себя" дописан context
  (единственный экземпляр на жизнь веб-части, лишних перезапусков не даёт),
  в ReferralForm ещё и мемоизированный approval.
- RequestsTableView: dataKey в зависимости (он и так выводится из view/
  statusFilter/debouncedQuery, которые там уже есть).

Осознанные отступления оставлены с объяснением ПОЧЕМУ: useMatrixEditor (момент
перечитывания матрицы задаёт вызывающий через reloadKey), companiesFieldValue
в RecruitingHire/RecruitingSelection.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 17:11:54 +05:00
gorbenko.pavel 6d92d7f9c1 fix(approval): колонки SLA в манифестах согласования + читаемый текст ошибки
Маршрут «Заявки на услуги» падал с 400 «Поле или свойство SLAhours не
существует»: общий ApprovalListService читает и пишет SLAhours/
AskOverdueReason у ЛЮБОЙ формы, а в манифесте согласования их не было.
Манифест копировался с TmcTransfer, где их тоже нет — там на тенанте
колонки почему-то есть, поэтому не проявлялось, но следующая партиция
списка создалась бы уже сломанной. Добавил обеим формам.

Текст ошибки при этом выводился как «Поле…»:
useMatrixEditor и useRequestTypeId клали в state сырой err.message, а
PnPjs кладёт туда тело ответа SharePoint целиком, где кириллица —
\u-escape-последовательности. Пропустил оба через friendlyErrorMessage,
который для этого и написан (он уже используется формами).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 17:06:06 +05:00
gorbenko.pavel 1e265875e2 fix(ServiceRequest): падение просмотра заявки — хук после раннего return
useMemo с attachmentUrls стоял ПОСЛЕ `if (loading) return` — на первом
рендере (загрузка) он не вызывался, на втором вызывался, порядок хуков
менялся, и React ронял компонент с #310 «rendered more hooks than during
the previous render». Карточка заявки на услуги не открывалась вообще.
Перенёс расчёт выше ранних выходов.

Заодно включил `react-hooks/rules-of-hooks` (error) — плагин давно в
зависимостях и в коде есть eslint-disable для exhaustive-deps, то есть
правила подразумевались, но в .eslintrc их забыли объявить. По остальному
коду правило даёт 0 нарушений, так что этот баг был единственным.
`exhaustive-deps` пока оставил выключенным: на текущем коде это 16
предупреждений, их разбор — отдельная задача, а не довесок к фиксу.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-01 16:49:44 +05:00
gorbenko.pavel a3d3983d78 fix(ServiceRequest): убрать лишний FormGrid внутри CollapseSection
CollapseSection.body у @tnsu/ui-kit уже сам является 2-колоночным грид-
контейнером (._body{grid-template-columns:1fr 1fr}) — FormGrid внутри
неё создавал ВЛОЖЕННЫЙ грид, который занимал только ОДНУ из двух колонок
родителя (как единственный grid-item без явного grid-column), а его
собственные две колонки делили уже урезанную пополам ширину. Из-за этого
«Объём работ», «Итого объёмы работ» и «Общие замечания» рендерились на
половине ширины карточки, хотя место было.

FormField кладём прямо в CollapseSection, без обёртки FormGrid — так уже
сделано во всех остальных формах (см. Clearance/RequestSection.tsx).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 16:37:58 +05:00
gorbenko.pavel 97fb71036b fix(ServiceRequest): строка сметы/объёмов работ не влезала в узкий Drawer
Форма открывается в боковом Drawer, а не на всю страницу — его ширина не
связана с viewport, поэтому брейкпоинты CollapseSection/FormGrid (следят
за шириной окна) не спасают. Фикс-колонки строки (ед.изм/кол-во/цена/
сумма) при этом не сжимались (grid-колонки по умолчанию не сжимаются
своего содержимого) и вылезали за рамку карточки.

Обернул строки в горизонтально скроллящийся контейнер (тот же приём, что
раньше был у плоской таблицы — .tableWrap в AnnexesSection) вместо попытки
сжать поля до нечитаемости.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 16:28:21 +05:00
gorbenko.pavel 0b9bb80b1f refactor(ServiceRequest): итог групп — прямо в JSON, без отдельной колонки
Итог сметы и объёмов работ теперь кладём прямо в JSON-колонку рядом с
группами ({ groups, total } — см. serializeGroups/parseGroupedJson в
cost.ts), а не отдельной числовой колонкой SharePoint. Форма total из
хранилища не доверяет, всегда считает заново; в JSON он нужен только тем,
кто читает колонку напрямую (отчёты, sp-query), не поднимая форму.

Убраны EstimateTotal/WorkVolumesTotal из провижининг-манифеста — они ещё
не были провижинены на тенанте, миграция не нужна.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 15:50:56 +05:00
gorbenko.pavel 6e2ad78e6e feat(ServiceRequest): детализация объёмов работ под «Объём работ»
Под текстовым описанием объёма работ добавлена таблица групп/позиций с
ценой и суммой — в том же формате, что и смета (группа → позиции → сумма
по группе → общий итог). Дополняет текст итоговыми цифрами, не заменяет
его.

Редактор таблицы «группы → позиции» вынесен в отдельный переиспользуемый
компонент CostGroupsEditor — CostsSection и новый блок в ScopeSection
используют один и тот же код вместо двух копий.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 15:43:56 +05:00
gorbenko.pavel 8fcba75f0d feat(ServiceRequest): прикрепление файла к строке «Приложения»
Вернул возможность прикладывать файл прямо к строке «Приложения» (fileName
на IAnnexRow, хранится как встроенное вложение элемента списка) и убрал
общий блок «Вложения» — он был избыточен: файлы теперь привязаны к своим
строкам, отдельного места под них не нужно.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 15:28:47 +05:00
gorbenko.pavel 76143e62be refactor(ServiceRequest): упростить «Приложения» — только опись документов
Убрано прикрепление реальных файлов к строке (в исходной бумажной форме
их нет — только перечень с номером и подтверждением), а заодно колонки
«Подтверждение» и «Кол-во стр.». Приложения теперь просто опись: описание
документа + номер приложения. Для файлов остаётся общий блок «Вложения».

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 14:40:40 +05:00
gorbenko.pavel 40561cbe60 feat(ServiceRequest): прикрепление файла к строке «Приложения»
Каждую строку таблицы приложений теперь можно связать с реальным файлом —
он хранится как встроенное вложение элемента списка (тот же механизм, что
у общего блока «Вложения»), новых списков/библиотек в SharePoint под это
заводить не нужно. В IAnnexRow добавлено fileName; выбранный, но ещё не
сохранённый файл держится в памяти формы и грузится вместе с общими
вложениями при save() (нужен id элемента). Удаление строки/замена файла
открепляет уже загруженное вложение, чтобы не оставалось висящих файлов.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 12:08:20 +05:00
gorbenko.pavel c73ab17cbd refactor(ServiceRequest): CostsJson/CostTotal -> EstimateJson/EstimateTotal
Понятнее по смыслу колонки: это смета (Estimate), а не «costs» вообще.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 11:40:28 +05:00
gorbenko.pavel ec53e48064 refactor(ServiceRequest): смета по группам вместо плоской таблицы
Смета теперь — список групп (разделов работ), внутри каждой свои позиции;
название группы необязательно. Сумма по позиции/группе/общий итог по-прежнему
считаются на лету, не хранятся построчно. UI — карточка на группу с полями
без рамки таблицы, по макету коллеги.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 11:40:20 +05:00
gorbenko.pavel 3a7579a5ef feat(ServiceRequest): новая форма «Заявка на услуги» (wh-service-request)
Электронный аналог бумажной Service Requisition Form ПТО: заявитель,
проект (маршрут резолвится по нему), описание требований, табличные части
«Приложения» и «Смета» (JSON-колонки, сумма считается на лету), только
фаза согласования — без исполнения. Форма подачи + режим согласующего,
провижининг-манифесты данных и согласования, регистрация в реестрах форм.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 11:40:10 +05:00