feat: config-driven реестр на реальных данных SP

This commit is contained in:
Тимур Городецкий
2026-08-17 11:23:57 +05:00
commit 31ef6fdbb9
99 changed files with 33739 additions and 0 deletions
+98
View File
@@ -0,0 +1,98 @@
# Инвентарь поведения — ct / oid / kps → ct-forms-portal
Спецификация для нового объединённого проекта. Источники (РЕФЕРЕНС, не трогаем):
- `ct-create-form` (ctCreateForm) — закупка по позициям из заявок TNSU
- `oid-create-form` (oidCreateForm) — ручной ввод работ (ОИД)
- `kps-ct-form` (kpsCtForm) — эталон организации (decomposed, common/)
Правило: поведение КАЖДОЙ формы переносим точь-в-точь. Общее → shared; различия → per-form.
Заполняется по областям по мере чтения.
---
## Область 1. Машина состояний мастера (навигация по шагам)
**Вывод: логика ОДИНАКОВА во всех трёх → общий хук `useWizard` (в common/), различия — конфигом.**
### Общее (переносим как shared `useWizard`)
Состояние: `currentStep` (1..N), `completedSteps: number[]`, `highestStep`, `formData`.
- `goToStep(step)` → `currentStep = step`; `highestStep = max(highestStep, step)`.
- `goNext()` → `currentStep = min(LAST, s+1)`; `highestStep = max(highestStep, next)`.
- `goPrev()` → `currentStep = max(1, s-1)`. **highestStep НЕ меняет.**
- `markStepComplete(step)` → добавить в completedSteps, если ещё нет (без дублей).
- `ensureHighestStep(step)` → `highestStep = max(highestStep, step)`.
- `updateFormData(partial)` → merge в formData.
- **Кликабельность шага в степпере: `step <= highestStep`** (защита от прыжка вперёд) — уже реализовано в `@tnsu/ui-kit` Stepper.
Это ЧИСТАЯ логика — обязательно покрыть юнит-тестами (clamp 1..LAST, max для highestStep, отсутствие дублей в completed).
### Сохранение состояния (sessionStorage) — общее, ключ per-form
- Персистим `{ formData, currentStep, completedSteps, highestStep }` в sessionStorage.
- Ключ: ct — `ct_form_{ctId}`; kps — свой `sessionKey(ctId)`. → параметр `sessionKeyPrefix` в конфиге формы.
- Пишем, когда известен id сущности (ct.id / ctId). Читаем при инициализации (ленивый init useState).
### Вход по URL + ссылка из уведомления — общее поведение, детали per-form
- id сущности из URL: ct — `ctId`|`ctid`; oid/kps — свои. Признак «ссылка из уведомления» = наличие
ПАРАМЕТРА в нижнем регистре (ct: `ctid`). → параметры `urlIdParam` / `notificationParam` в конфиге.
- Логика приземления при входе по URL:
- **ссылка из уведомления** (согласующий): загрузить из SP → `goToStep(3)` (Шаг 3 главный) +
пометить шаги 1..3 завершёнными (⇒ highestStep=4, доступны все). [ct: markComplete 3 и 4; kps: 1,2,3 — СВЕРИТЬ при переносе, привести к единому].
- **обычный вход по URL** (редактирование): загрузить → приземлиться на «самый дальний
заполненный» шаг (`furthestFilledStep`), пометить завершёнными до него; если статус
approved/rejected → пометить и шаг 4.
- `furthestFilledStep(data)` (kps, эталон): approvers>0 или статус on_approval/approved/rejected → 4;
offers>0 → 3; suppliers>0 → 2; иначе → 1.
### Различия per-form (СОХРАНИТЬ)
- **ct:** хук `useCtForm`; SP-загрузка — большим useEffect в контейнере (~130 строк): восстанавливает
offers, currencyRates, uploadedKpFiles (по сессии + дочитка из SP), `step3DeliveryKm`, suppliers из
офферов. `getCurrentUserProfile → initiatorDepartment`; `getCurrentUserCompany → isRestrictedView`
(company === 'astel-k'). `hidePrices = isRestrictedView || (approvalSent && !userInApprovalChain)`.
Статусы CTStatus: draft/kp_loading/kp_loaded/formed/on_approval/approved/rejected.
- **oid:** хук `useOidForm(context)` (грузит внутри). Шаг 1 — РУЧНОЙ ВВОД работ (Step1WorksInput,
ComboBox) — принципиально другой. `isReadOnly = formData.approvalSent`. Профиль → department+company.
Статусы OIDStatus: draft/formed/on_approval/approved/rejected (нет kp_*). CurrencyRates — inline в Step2.
- **kps (эталон):** хук `useKpsCtForm(ctId)` + отдельный `useSharePointLoad`. Методы `next`/`prev`
(не goNext/goPrev). `replaceFormData`. `deriveCtStatus` — пока заглушка (=formData.status), TODO
реальная логика. `CT_STATUS_META[status] = {label, kind}` (kind → Pill/Badge). `READ_ONLY_STATUSES`.
Единый контракт `StepProps` для всех шагов. Самый чистый — берём за каркас.
### Как ляжет в новый проект
- shared `useWizard(config)` — состояние + переходы + сессия (config: lastStep, sessionKeyPrefix,
urlIdParam, notificationParam).
- per-form: свои шаги, статусы (STATUS_META), SP-загрузка (furthestFilled + восстановление данных),
Step1 (ct/kps — выбор позиций; oid — ручной ввод).
- Степпер — `@tnsu/ui-kit` Stepper; шапка — `@tnsu/ui-kit` FormHeader (kps так уже и делает).
---
## Область 2. Оболочка + реестр (config-driven) — ИСТОЧНИК: ct-forms-list / myDocuments
Готовый каркас для портала. myDocuments — единый дашборд по всем 3 типам, управляется массивом
конфигов DocumentTypeConfig (по одному на CT/OID/KPS в config/*DocumentConfig.ts). Умеет ПРОСМОТР
(реестр+очереди) и СОЗДАНИЕ (кнопка «Создать» → открывает форму-мастер).
### Что уже есть (берём за основу каркаса)
- Сайдбар: пункты из documentTypes (typeLabel + icon), активный тип, бейдж-счётчик «на согласовании
у меня» на пункте. → заменить на @tnsu/ui-kit Sidebar.
- Контент по активному типу: заголовок, тулбар (поиск + фильтр статуса + кнопка «Создать» по
createNewUrl) и 3 вкладки-очереди: «На согласовании у меня» / «Инициализированные мной» /
«Согласованные мной» (Fluent Pivot).
- DocumentTypeConfig (per-type): typeKey, typeLabel, icon, myDocsListName, buildMyDocsFilter,
myDocsSelect, pendingApproval{...}, approvedByMe{...}, buildLinkUrl(row) (ctId для черновика /
ctid иначе), createNewUrl/Label, mapItem(raw→DashboardRow), columns[].
- useDocumentDashboard(context, documentTypes) — грузит myDocs/approvedDocs/pendingDocs.
- StatusBadge (variant neutral/info/success/error) → @tnsu/ui-kit Badge; маппинг статусов — в конфиге типа.
- DocumentTable → @tnsu/ui-kit DataTable (колонки уже описаны в конфиге).
### Fluent → ui-kit при переносе
Pivot → НУЖЕН Tabs (в ките пока НЕТ); SearchBox → TextInput; Dropdown → Select; Spinner/MessageBar →
Alert; Default/PrimaryButton → Button; свой сайдбар → Sidebar; DocumentTable → DataTable.
### Как это меняет каркас портала
Портал = config-driven: массив конфигов типов (CT/OID/KPS), каждый описывает и РЕЕСТР (списки/очереди
из myDocuments), и СОЗДАНИЕ/РЕДАКТИРОВАНИЕ (мастер из ct/oid/kps). Оболочка (Sidebar + вкладки) — из
myDocuments. Мастера — из kps-каркаса. Эталоны: shell/реестр = myDocuments, мастер = kps.
### Пробел ui-kit
Tabs (вкладки-очереди, замена Pivot) — добавить в @tnsu/ui-kit (был в списке: из request-portal CategoryTabs).