Files
ct-forms-portal/docs/OID-PORT.md
T
Тимур Городецкий 109053e3af объединение CT, KPS, OID и дашборда в один портал
Перенос, декомпозиция и перекраска форм CT/KPS/OID на @tnsu/ui-kit,
config-driven дашборд-реестр по трём типам документов. Из отслеживания
исключены артефакты сборки (lib-commonjs/jest-output/release) — забыты
в .gitignore ранее.
2026-08-28 16:21:34 +05:00

20 KiB
Raw Blame History

OID — перенос в ct-forms-portal (Фаза 4)

Порядок (тот же, что и CT): Этап 1 перенос 1:1 → Этап 2 декомпозиция под структуру KPS → Этап 3 перекраска на ui-kit. Правило: сохранить 100% поведения; build после каждого этапа. Эталон структуры — forms/kps/, карта резки — docs/CT-PORT.md (Step2/3/4 у OID структурно близки к CT).

Источник: oid-create-form/src/webparts/oidCreateForm/. 10 файлов, самый большой — Step3ComparisonTable.tsx (2533 стр, ещё крупнее, чем был у CT).

Разведка (перед переносом)

  • Step3ComparisonTable.tsx (2533), Step4Approval.tsx (844), Step1WorksInput.tsx (646), Step2CommercialOffers.tsx (533), hooks/useOidForm.ts (299), OidService.ts (1470).
  • Шаг 1 у OID устроен ИНАЧЕ, чем у CT: Step1WorksInput — это ручной ввод сгруппированных работ (PositionGroup → OIDPosition[], поля parentGroup/priceSmeta/summaSmeta, ComboBox для группы), а не выборка из SP-списка потребностей (как RequestsTable+useRequests у CT). Ближе к паттерну ItemsTable из «СЗ на перемещение ТМЦ». Для Шага 1 нужен свой чертёж декомпозиции на Этапе 2 — карта CT/KPS сюда не подходит.
  • Step2/Step3/Step4 структурно близки к CT (коммерческие предложения / матрица сравнения / согласование) — карта резки CT применима с поправкой на домен (parentGroup/priceSmeta вместо disciplineCode/SMR/agskCode).
  • 0% ui-kit — в отличие от CT (который уже частично сидел на @tnsu/ui-kit@0.4.0), OID полностью на raw Fluent. На Этапе 3 конвертировать всё, форы нет.

Этап 1 — перенос 1:1 — ✅ ЗАВЕРШЁН

  1. ✅ Скопированы components/ и services/ → forms/oid/{components,services}/ (17 файлов, 1:1, без правок логики).
  2. ✅ OidService.ts НЕ ТРЕБУЕТ параметризации siteUrl (в отличие от Ct/KpsCtFormService!): модуль-константа SITE_URL = "https://tnsukz.sharepoint.com/sites/sed" уже прописана как раз на целевой сайт портала — OID изначально жил на /sites/sed, миграции сайта (в отличие от CT/KPS) не было. Все ~15 обращений к SP-листам используют эту константу напрямую (не this.baseUrl), so context.pageContext.web.absoluteUrl тоже будет верно резолвиться при работе внутри портала. Решение: оставить OidService.ts полностью нетронутым — любая правка (даже добавление неиспользуемого siteUrl?) была бы риском без пользы.
  3. ✅ IOidCreateFormProps.ts: hasTeamsContext был объявлен, но нигде не читался (мёртвый пропс) — сделан опциональным (hasTeamsContext?: boolean), чтобы порталу не нужно было его прокидывать. siteUrl? НЕ добавлен (сервису он не нужен — см. п.2, не добавляем неиспользуемый пропс).
  4. ✅ Обвязка портала:
    • oidDocumentConfig.ts: добавлены inPortal: true + buildInPortalParams (условие Черновик/formed → oidId, иначе → oidid — сверено с useOidForm.ts, который читает именно так; условие шире, чем у CT, т.к. oidDocumentConfig уже поддерживал статус formed в buildLinkUrl).
    • CtFormsPortal.tsx: добавлен импорт OidCreateForm + ветка route.form === 'oid' → <OidCreateForm> + «← К реестру» (по образцу CT/KPS-веток). FORM_ID_PARAMS уже содержал oidId/oidid (задел на будущее).
    • Dashboard/config/index.ts — уже были готовы (createOidDocumentConfig() зарегистрирован, Dashboard.tsx полностью generic по DOCUMENT_TYPES, без хардкода typeKey) — правок не потребовалось.
  5. ✅ heft build — зелёный (после фикса ниже), запущено и открывается в workbench.

Build-фикс (tsconfig): первый прогон дал ошибки TS2550 flatMap/Object.entries does not exist + каскадные TS7006 implicit any. Причина: у исходного oid-create-form/tsconfig.json был явный "lib": ["es2019", "dom", "dom.iterable"], а у ct-forms-portal/tsconfig.json — только базовый rig-конфиг (dom, es5, es2015.core/collection/iterable/promise/proxy, без ES2017/ES2019). Код OID использует Array.flatMap/Object.entries, которых там нет. Решение: добавлен тот же lib-оверрайд в ct-forms-portal/tsconfig.json (а не переписывание кода OID под ES5-совместимые аналоги) — так проще, ничего не меняет в поведении, и это чистая надстройка типов (не влияет на CT/KPS, которые эти методы не используют). Файлы OID остались нетронутыми.

Этап 2 — декомпозиция

Статус: [x] Step2CommercialOffers ✅ | [x] Step4Approval ✅ | [ ] Step1WorksInput | [ ] Step3ComparisonTable

✅ Step2CommercialOffers (533) — СДЕЛАНО (build+workbench ок)

533 → контейнер Step2CommercialOffers.tsx (165) + useKpProcessing.ts (248, конвейер Syntex/Flow + стейт uploadedFiles/suppliers/kpFiles) + step2Persist.ts (54, handleSave→функция, возвращает patch)

  • вьюхи SupplierList.tsx (43) / UploadedKpList.tsx (45) / AddNoOfferDialog.tsx (53, диалог «Добавить без КП» — держит своё UI-состояние формы). Отличие от CT: список поставщиков ведётся прямо на Шаге 2 (не как у CT), диалог вместо инлайн-формы добавления. Сохранён silent-guard if (!formData.oid) return; перед вызовом persist (было тихим no-op, не стало видимой ошибкой).

✅ Step4Approval (844) — СДЕЛАНО (build+workbench ок)

844 → useStep4Approval.ts (478: цепочка-операции, add/delete/replace, handleActionConfirm, send/export/restart, производные флаги + удобные методы selectAddSuggestion/cancelAddRow/openReplace/…)

  • view Step4Approval.tsx (426, только разметка + helpers STATUS_LABELS/getStatusClass/formatDate; Fluent). Структурно почти идентично CT (кроме handleExport≈CT handleSendToIncore, методы сервиса называются иначе: generateOidReport/updateOIDStatus/resetApprovalChainToDraft). Финальное дробление вьюхи (add-member/replace-dialog поиск в под-компоненты) — отложено на Этап 3, как у CT.

✅ Step1WorksInput (646) — СДЕЛАНО (build ожидается)

646 → useStep1WorksInput.ts (445, перенос ВЕРБАТИМ: весь стейт/рефы(projectSearchTimer/groupsRef/ isMounted/dirtyRef/syncTimer/pendingOidRef/persistRef/scheduleSyncRef)/эффекты(sessionStorage-sync, гидратация из formData с dirty-guard, cleanup-flush на unmount)/хендлеры(loadProjects/persistPositions/ scheduleSync/markDirtyAndSync/handleWorkNameBlur/handleProjectChange/updateGroupName/updatePosition/ addGroup/removeGroup/addPosition/removePosition/validate/handleSave)) + view Step1WorksInput.tsx (239, только разметка). Риск: клубок рефов/дебаунс-синхронизации позиций в SP + защита от потери правок при F5/гидратации (dirtyRef) — того же класса, что CP4 у CT, только меньшего масштаба. dirtyRef экспортирован из хука как есть (вьюха дёргает dirtyRef.current = true инлайн в одном месте — сохранено дословно, не оборачивал в сеттер). ✅ build+workbench-тест пройден пользователем (создание группы/позиции, debounce-запись, F5, blur наименования работ → авто-создание ОИД) — без регрессий.

✅ Step3ComparisonTable (2533) — СДЕЛАНО по чекпоинтам (build ожидается)

Разведка подтвердила: структура похожа на CT (Step3+Step4-согласование смешаны), но домен другой (группы позиций с колонкой «Группа» в шапке, нет валют/тумблера ед.изм., смета price/summa как read-only референс) — и два независимых debounce-автосейва (офферы + критерии, оба по 1500мс) поверх unmount-persist и beforeunload-persist — риск выше, чем был у CT.

  • ✅ CP1 согласование → useStep3Approval.ts + InlineApprovalChain.tsx (голосование/делегат, 1:1, NormalPeoplePicker/IPersonaProps оставлены как есть — Fluent, трогать на Этапе 3).
  • ✅ CP2 AI/отчёт → useAiComment.ts (комментарий ИИ + отдельная генерация Excel-отчёта handleGenerateReport/generateOidReport — своя фича, отсутствует в CT в таком виде) + AiCommentBlock.tsx (проще, чем у CT — нет удаления комментария).
  • ✅ CP3 хелперы/типы → matrixTypes.ts (CriteriaEntry/CellEdit/EditableOffers/ExtraSupplier/ ExtraPosition/PositionRow) + matrixHelpers.ts (FIXED_CRITERIA/FIXED_COLS/genId/fmt/fmtKzt/ buildOfferKey).
  • ✅ CP4 ядро матрицы → useStep3Matrix.ts (1186 строк, перенос ВЕРБАТИМ: весь стейт/рефы/эффекты ОБОИХ автосейвов(офферы+критерии)/unmount-persist/beforeunload-persist/мемо/хендлеры/handleSave). extraIdCounter — модульная переменная в этом файле (как у CT). getPosRowSpan (для rowSpan группы в шапке таблицы) тоже переехал в хук и возвращается наружу.
  • Контейнер: 2533 → 877 строк. Финальное дробление render (таблица цен как отдельные под-компоненты) сознательно НЕ делали — как и у CT/OID Step1/2/4, откладывается на Этап 3 вместе с перекраской (не трогать вьюху дважды).
  • ✅ Build+workbench-тест подтверждён пользователем — без регрессий (матрица, оба автосейва, F5, unmount-persist, добавление/удаление поставщика/позиции, согласование, отчёт, AI).

✅✅✅ ЭТАП 2 ДЛЯ OID ПОЛНОСТЬЮ ЗАВЕРШЁН

Все 4 god-файла (Step1WorksInput/Step2CommercialOffers/Step3ComparisonTable/Step4Approval) разобраны, build+workbench-тест пройден по каждому. Самый рискованный кусок всего OID (Step3, два автосейва) — подтверждён рабочим. Дальше — Этап 3: перекраска на @tnsu/ui-kit (полный объём, 0% ui-kit форы нет, как у KPS изначально; не забыть полный проход по цветовым токенам + проверить локальные компоненты типа Breadcrumbs на скрытые Fluent-цвета вне @fluentui-импортов — урок с CT).

Доменные отличия OID (НЕ терять при декомпозиции!)

  • Шаг 1 — ручной ввод групп работ (PositionGroup/OIDPosition), не SP-список потребностей.
  • Позиции: parentGroup, positionType ('Услуга' | 'Дополнительные затраты'), priceSmeta/summaSmeta (сметная цена — своё, у CT такого нет).
  • Списки SP: суффикс «ОИД» уже в названиях (Сравнительная таблица ОИД, Позиции СТ ОИД, ...) — коллизий с CT/KPS нет.
  • SupplierOffer в OID проще, чем у CT: нет currencyCode/мульти-валюты в базовых полях (нет deliveryDistanceKm тоже) — проверить при декомпозиции, не тащить из CT лишнюю доменку.

✅✅ Этап 3 — перекраска (механика) ЗАВЕРШЕНА

Все 13 файлов с Fluent → @tnsu/ui-kit (0 импортов @fluentui в forms/oid/, включая .tsx и .scss — проверено grep). Порядок: тривиальные (Step2 IconButton-файлы) → простые (AiCommentBlock/ AddNoOfferDialog/Step2CommercialOffers) → Step1WorksInput → сложные (useStep3Approval+InlineApprovalChain, Step4Approval) → главный контейнер Step3ComparisonTable.tsx (877 строк) → OidCreateForm+Breadcrumbs → токены.

  • ComboBox не тянется на ui-kit (нет автокомплита с готовыми опциями) — сделан локальный Step1WorksInput/ComboField.tsx (свободный ввод + подсказки, паттерн как у поиска участника в Step4Approval: TextInput/native input + <ul> подсказок, debounce уже был в хуке). Используется для «Проект» (серверный поиск через a.loadProjects) и «Ед. изм.» (локальная фильтрация статичного списка). Эскейп-хэтч: поле «Наименование работ» тоже осталось на native <input> (не TextInput) — нужен onBlur (создаёт ОИД по потере фокуса), которого у ui-kit TextInput нет.
  • TextInput/TextArea из ui-kit не имеют пропа className — все места, где раньше вешали кастомный класс на инпут (ячейки матрицы offerInput/qtyInput/dataCellName, критерии, тулбар) переведены на обёртку <div className={styles.xxx}><TextInput .../></div> (как в CT MatrixRowView/CriteriaTable).
  • useStep3Approval/InlineApprovalChain: NormalPeoplePicker+IPersonaProps заменены на паттерн CT — delegateDialog.query/suggestions/selected + debounce 300мс через service.searchUsers, Dialog open/title/subText/footer вместо DialogType.normal/DialogFooter.
  • Иконки: Fluent→ui-kit спрайт (Add→plus, Delete/Cancel→trash/close, ChevronLeft→arrow-left, ExcelDocument→document, Forward→chevron-right, Accept→check, Up/Down→arrow-up/arrow-down, Switch→swap, AddFriend→(без иконки, нет аналога), Lightbulb→(без иконки, как у CT)).
  • Токены: создан forms/oid/components/common/_theme.scss — точное зеркало CT/KPS-моста (тот же navy/gold маппинг). Полный проход по hex-литералам (~30 уникальных кодов, ~200+ вхождений) → bulk-sed по всем *.module.scss (кроме OidCreateForm.module.scss, где 2 литерала #1b1a19/#252423 сознательно оставлены как есть в .dark-блоке — тот же паттерн, что и у CT, dark-theme не токенизируем). По ходу найдены и убраны 2 мёртвых класса (.winBtn/.winBtnActive, не использовались в разметке). OidCreateForm.module.scss переписан по образцу CtCreateForm.module.scss (root/content/dark), Fluent References.scss-импорт и мёртвые классы .welcome/.welcomeImage/.links убраны.
  • ✅ Build+workbench-тест перекраски пройден пользователем. По итогам визуальной проверки — 2 доп. фикса:
    • Билд-гоча: SPFx typings-генератор для scss выкидывает из IExportStyles пустые CSS-правила (.class {} без объявлений) — TS2339 "Property does not exist", хотя класс использован в TSX. Столкнулись дважды: (1) .addRowBtn в Step4Approval.module.scss забыт при переносе JSX-паттерна из CT (там кнопки стилизовались инлайново); (2) при удалении заливки .extraRow (см. ниже) правило стало пустым — пришлось оставить с background: transparent; вместо полного удаления класса. Написан bash/awk-скрипт для проверки styles.X (tsx) ↔ определение класса (scss) по всему дереву — полезно на будущее при переносе JSX между формами.
    • Цвет: .colSel/.thSupplierSel (подсветка выбранного поставщика) были на $warn-bg — заменены на rgba($gold-500, 0.02), как у CT. .extraRow (доп. затраты) — заливка $warn-bg убрана полностью (только background: transparent, класс сохранён пустым не может — см. гочу выше).
  • ✅✅ Финальное дробление render Step3ComparisonTable.tsx ЗАВЕРШЕНО (877→138 строк контейнер), по точному образцу CT: ComparisonMatrix.tsx(228, вся <table>)/MatrixRowView.tsx(258, обычная позиция+bundle, с доменной ячейкой «Группа» rowSpan — которой нет у CT)/ExtraPositionRow.tsx(123, доп.затраты, без bundle)/AddExtraRow.tsx(73)/CriteriaTable.tsx(125). Логика не трогалась — чистая перекладка JSX, единый проп matrix: Step3Matrix (уже был экспортирован из useStep3Matrix.ts). Порядок строк в <tbody> сохранён точно: позиции по группам → AddExtraRow → subtotal обычных → extra-позиции → subtotal доп.затрат → grand total. Проверка styles.X↔scss после дробления — чисто, @fluentui — 0 вхождений.
  • ✅ Build + беглый workbench-прогон (Шаг 1→4) подтверждён пользователем после дробления — без регрессий.

✅✅✅ ФОРМА OID ПОЛНОСТЬЮ ЗАВЕРШЕНА

Этап 1 (перенос) + Этап 2 (декомпозиция логики) + Этап 3 (перекраска на @tnsu/ui-kit) + финальное дробление render — всё сделано и подтверждено сборкой/workbench. OID теперь на одном уровне готовности с CT. Три формы портала (CT/KPS/OID) в состоянии Fluent→ui-kit переведены полностью.