# 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'` → `` + «← К реестру» (по образцу 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 + `