# CT — перенос в ct-forms-portal (Фаза 3)
Порядок (утверждён): **Этап 1 перенос 1:1 → Этап 2 декомпозиция под структуру KPS → Этап 3 перекраска на ui-kit.**
Правило: сохранить 100% поведения; build после каждого этапа. Эталон структуры — `forms/kps/`.
Связано: `docs/REPAINT-MAP.md` (перекраска), `docs/BEHAVIORS.md`.
Источник: `ct-create-form/src/webparts/ctCreateForm/`. CT = 6953 стр, god-компоненты.
## Этап 1 — перенос 1:1 — ✅ ЗАВЕРШЁН (heft build зелёный)
1. ✅ Скопированы `components/` и `services/` → `forms/ct/{components,services}/` (17 файлов, 1:1).
2. ✅ Правки для сборки в портале (минимум, поведение не менялось):
- `CtService`: конструктор `(context, siteUrl?)`, `baseUrl = siteUrl ?? absoluteUrl` (как KPS).
`kpFolder` тоже параметризован через `siteUrl` (было жёстко на `context.pageContext.web.serverRelativeUrl`).
- `ICtCreateFormProps` + `siteUrl?`; `CtCreateForm` → `new CtService(context, siteUrl)`.
- Web-part-специфичных зависимостей (loc strings) в components/services не было — ничего убирать не пришлось.
3. ✅ Обвязка портала:
- `ctDocumentConfig.ts`: `inPortal: true` + `buildInPortalParams: (row) => ({ [row.status==='Черновик'?'ctId':'ctid']: row.id })`
(сверено с `useCtForm.ts` — читает именно `ctId`/`ctid`, совпало 1:1). siteUrl уже `/sites/sed`.
- `CtFormsPortal.tsx`: ветка `form==='ct'` → `` + «← К реестру» (по образцу KPS-ветки).
4. ✅ `heft build` зелёный.
**Открытие при переносе:** CT уже частично зависел от `@tnsu/ui-kit@^0.4.0` — 3 файла
(`Step1PositionSelection.tsx`, `Step2CommercialOffers.tsx`, `CurrencyRatesForm.tsx`) уже используют
`Button`/`Alert` из кита. API совпадает 1:1 с текущим `0.11.0` (variant="secondary"/"accent", Alert
variant="err") — конфликтов не было, работает как есть. Ускорит Этап 3.
## Этап 2 — декомпозиция (карта резки god-компонентов)
**Статус:** [x] Step2CommercialOffers ✅ | [x] Step4Approval ✅ | [~] Step3ComparisonTable (в работе)
### [~] Step3ComparisonTable (2217) — В РАБОТЕ, по чекпоинтам
- ✅ **CP1 согласование** (build ок): `useStep3Approval.ts` (голосование/делегат, 1:1) + `InlineApprovalChain.tsx`
(вью цепочки + диалоги решение/делегат + баннер notifyWarning; хелперы APPROVAL_STATUS_*). Контейнер 2217→1918.
- ✅ **CP2 AI/PDF** (build ок): `useAiComment.ts` (aiComment/isAiLoading/aiError/isPdfLoading/showAiPromptDialog +
handleAiComment/handleGeneratePdfReport/handleDeleteAiComment + loadCtComment-effect + decodeHtmlEntities,
buildPayload передаётся аргументом из контейнера — матричные данные не копируются в хук) + `AiCommentBlock.tsx`
(только блок отображения, диалог-промпт остался в контейнере — как у KPS). PDF-ошибка идёт через колбэк
`onPdfError` в общий `saveError` (сохранили родной баннер).
- ✅ **CP3 хелперы/типы** (build ок): типы (ExtraSupplier/ExtraPosition/PositionRow/CellEdit/EditableOffers) →
`matrixTypes.ts`; константы (SUPPLIER_STATUS_OPTIONS/DELIVERY_TYPE_OPTIONS) + чистые ф-ции
(formatKzt/Num/Qty/round2Str/getSubCols/computeOffer/buildOfferKey) → `matrixHelpers.ts`.
`extraIdCounter` (мутабельный) оставлен в контейнере — уедет вместе с CP4.
- **Контейнер после CP1-3: 2217 → 1735 строк (−22%).**
- ✅ **CP4 ядро матрицы** (build ок): весь стейт/рефы(~15)/эффекты(loadCriteria/loadOffers/refs-sync/beforeunload/
unmount-persist)/мемо(suppliersWithOffer/allPositions/supplierCurrencies/computedOffers/minTotalByPosition/
recommendedSupplierId)/все update*/add*/delete*-хендлеры/handleSave → `useStep3Matrix.ts` (856, перенос
ВЕРБАТИМ). В контейнере — **деструктуризация** результата, поэтому render (984) НЕ менялся. `CriteriaEntry`
переехал в matrixTypes (общий). approval+ai хуки остались в контейнере (берут значения из деструктуризации).
- **Контейнер: 2217 → 984 строки (−56%). Логика матрицы вся в хуке. Этап 2 для CT ЗАВЕРШЁН.**
- ✅ **CP4 happy-path проверен в workbench**: полный проход по всем 4 шагам (создание СТ → загрузка КП → матрица →
согласование) прошёл без ошибок. Основной риск (грубый регресс/краш) снят.
⚠️ Не проверены детально: bundle-строки (комплект), перевод ед. изм. (тумблер), доп.расходы/логистика,
F5 (beforeunload-persist), критерии/AI-комментарий, делегат/замена в согласовании. Смотреть при случае,
не блокирует дальнейшую работу.
- ⏭️ **CP4 матрица-ядро** (САМЫЙ РИСК): ~15 рефов + эффекты (loadCriteria/loadOffers/beforeunload/unmount-persist) +
мемо (suppliersWithOffer/allPositions/supplierCurrencies/computedOffers/minTotalByPosition/recommendedSupplierId) +
все update*/add*/delete*-хендлеры + handleSave → `useStep3Matrix.ts` (перенос ВЕРБАТИМ, не переписывать!).
⚠️ Клубок рефов; компилятор не ловит регресс поведения. ЖЕЛАТЕЛЬНО делать при возможности workbench-теста
(после провижининга списков). Рендер таблицы (thead/tbody/итоги/критерии) дробить на ComparisonMatrix/
MatrixRowView/OfferCells/CriteriaTable/AddExtraRow — на Этапе 3 вместе с перекраской.
Метод: чисто механический перенос блоков (логику не трогать), Fluent оставляем (перекраска — Этап 3),
build после каждого god-компонента.
**Уточнённая граница Этап2/Этап3:** Этап 2 = вынос ЛОГИКИ (хуки/persist), «раз-god-ивание». Финальное
дробление оставшейся ВЬЮХИ на под-компоненты (диалоги/списки) + размеры <200 — на Этапе 3 вместе с
перекраской (диалоги всё равно → ui-kit Dialog), чтобы не трогать файлы дважды.
### ✅ Step4Approval (886) — СДЕЛАНО (build ок)
886 → `useStep4Approval.ts` (вся логика: chain-ops, add/delete/replace, handleActionConfirm, send/incore/restart,
производные флаги + удобные методы selectAddSuggestion/cancelAddRow/openReplace/…) + view `Step4Approval.tsx`
(~370, только разметка + helpers STATUS_LABELS/getStatusClass/formatDate; Fluent). Вьюху добьём на Этапе 3.
### ✅ Step2CommercialOffers (597) — СДЕЛАНО (build ок, 2 чекпоинта)
597 → контейнер `Step2CommercialOffers.tsx` (~155) + `kpTypes.ts` + `useKpProcessing.ts` (конвейер Syntex/Flow)
+ `step2Persist.ts` (handleSave→функция, возвращает patch) + вьюхи `SuppliersWithoutKp.tsx` (владеет add-стейтом)
/ `UploadedKpList.tsx`. Одно семантич. уточнение: в persist `formDataRef.current`→`formData` (в момент save совпадают).
### Step3ComparisonTable.tsx (2217) → по образцу `Step3BuildCt/*` + отдать approval в Step4
| Секции CT (строки) | Куда (аналог KPS) |
|---|---|
| 25–179 типы/хелперы/формат | `types/domain.ts`, `utils/format.ts`, `useStep3Matrix.ts` |
| 196–435 стейт матрицы | `useStep3Matrix.ts` |
| 285–394 approval-хендлеры | → Step4 `useStep4Approval.ts` |
| 396–405, 947–1017 AI/PDF | `useAiComment.ts` + `AiCommentBlock.tsx` |
| 439–604 useEffect загрузка/persist | `step3Persist.ts` + `useStep3Matrix.ts` |
| 606–706 useMemo производные | `useStep3Matrix.ts` |
| 708–789 редактируемые ячейки | `OfferCells.tsx` |
| 791–945 хендлеры строк | `AddExtraRow.tsx` + `useStep3Matrix.ts` |
| 1019–1193 handleSave | `step3Persist.ts` |
| 1197–1239 тулбар + тумблер ед.изм. | `Step3BuildCt.tsx` (+ доменное `unitConversion`) |
| 1241–1337 thead | `ComparisonMatrix.tsx` |
| 1339–1806 tbody + итоги | `MatrixRowView.tsx` + `OfferCells.tsx` + `ComparisonMatrix.tsx` |
| 1807–1864 инлайн-добавление | `AddExtraRow.tsx` |
| 1865–2029 критерии + AI + PDF | `CriteriaTable.tsx` + `AiCommentBlock.tsx` |
| 2030–2114 инлайн-цепочка (Teams) | → Step4 `InlineApprovalChain.tsx` |
| 2115–2211 диалоги (делегирование/решение) | ui-kit `Dialog` в `Step4Approval.tsx` |
### Step4Approval.tsx (886) → `useStep4Approval.ts` + `Step4Approval.tsx` + `InlineApprovalChain.tsx`
(стейт+манипуляции цепочкой 94–352, handleActionConfirm/Send/Incore/Restart 239–474 → хук; render 476–763; диалоги 764–885 → ui-kit Dialog)
### Step2CommercialOffers.tsx (597) → `useKpProcessing.ts`/`kpPipeline.ts`/`kpOffers.ts`/`step2Persist.ts` + view (`Step2LoadKp`,`KpFileList`,`CurrencyRatesForm`,`SuppliersWithoutKp`,`UploadedKpList`)
### CtService.ts (1532) — ввести `siteUrl?` (крит.) + при желании обобщённые CRUD-примитивы как в KPS. Создать `common/*` (StepShell/FormHeader/Pill) + единый `StepProps`.
## Доменные отличия CT (НЕ терять при декомпозиции!)
- Шаг 1 — из **SP-списка** (`fetchRequests(days)`), не из TNSU API (KPS).
- Колонки позиций: **disciplineCode (марка), smrType (SMR), agskCode**, uniqueCode, organization.
- `CT.number` — строковый, `generateCtNumber(projectName)` (per-project).
- `utils/unitConversion.ts` — пересчёт в ед. изм. заявки (тумблер) — нет в KPS.
- `SUPPLIER_STATUS_OPTIONS` (Завод/Дистрибьютор…) — нет в KPS.
- CurrencyRate/SupplierOffer — иные поля (rateToKzt; deliveryCostKzt/additionalCostsKzt/totalWithExtraKzt/pricePerUnitKzt).
- `getCurrentUserCompany()==='astel-k'` → `isRestrictedView` (скрытие цен).
## Этап 3 — перекраска (карта A/B/C из REPAINT-MAP)
Fluent в CT: PrimaryButton/DefaultButton→Button, TextField→TextInput/TextArea, MessageBar→Alert,
IconButton/Icon, Spinner→Spinner, Dialog→ui-kit Dialog, Checkbox→Checkbox, статусы→Pill/Badge.
Особые: **Toggle** (пересчёт ед.изм.) и **NormalPeoplePicker** (делегирование) — прямых аналогов в ките нет:
Toggle → ui-kit Toggle (есть!); people picker → TextInput + searchUsers()+suggestions (как KPS Step4).
**✅✅ Этап 3 ЗАВЕРШЁН (механическая перекраска) — Fluent полностью удалён из CT (0 импортов @fluentui
во всей `forms/ct/`), heft build ожидается пользователем.**
- Все 12 файлов с Fluent переведены на `@tnsu/ui-kit`: RequestsTable/FileUploadArea/UploadedKpList
(Checkbox/IconButton), SuppliersWithoutKp/CurrencyRatesForm/Step1PositionSelection/
Step2CommercialOffers/AiCommentBlock (TextInput/Spinner/Alert/IconButton/FormField),
useStep3Approval/InlineApprovalChain/Step4Approval (делегат/замена — NormalPeoplePicker заменён
на паттерн KPS: query+debounce(300ms)+suggestions+select, `useStep4Approval`/`useStep3Approval`
получили `handleAddQueryChange(val)`/`setDelegateQuery(val)` вместо Fluent-сигнатуры (event, val)),
и главный контейнер `Step3ComparisonTable.tsx` (984 строки, вся матрица/критерии/футер/AI-диалог).
- Правило замен применено: `TextField`→`TextInput`/`TextArea` (multiline→TextArea rows=1, автовысоты
нет — принято как в KPS `CriteriaTable`); без `className`→обёрнуто в `
`; `Toggle` без `label`→
обёрнут в `