feat: config-driven реестр на реальных данных SP
This commit is contained in:
@@ -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).
|
||||
Reference in New Issue
Block a user