Files
ct-forms-portal/docs/BEHAVIORS.md
T
2026-08-17 11:23:57 +05:00

8.6 KiB

Инвентарь поведения — 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).