# sub-invest-b1-fable — YC-029 SOLTUON-12-08-2026 (5 điều chỉnh GĐ3 Duyệt HĐ)

> Investigator-codebase · Fable-5 single deep-pass · ARTIFACT-FIRST (khung ghi trước khi đọc — chống #53). Append từng mục ngay khi xong.

## Đọc-ý 5 điều chỉnh (xác nhận/bác từng cái)

| # | Điều chỉnh | Đọc-ý lead (anh-mo-ta.md) | Verdict | Căn cứ (từ ground-truth 4 mục) | Tin |
|---|---|---|---|---|---|
| 1 | Dời cây toàn trình | DỜI panel cây PHẢI→TRÁI, bố cục giống KHKK; "danh-sách đi đâu" = invest đề xuất | **ĐÚNG** | Khuôn KHKK: cây = panel TRÁI 400px làm trục điều hướng, danh-sách KHÔNG cần cột riêng — flat-list là chế-độ FALLBACK trong CÙNG panel khi search/lọc (`KhkkListPage.tsx:171` + `KhkkListPanel.tsx:21-24,366-371`), panel-3 = dashed/WorkflowHistory (QĐ-2 `:252-256`) | CAO |
| 2 | Mỗi loại HĐ 1 quy trình riêng | "đúng 7 workflow HD-* đã seed @S188 — UI/menu/trang từng loại lọc ĐÚNG, không lẫn" | **ĐÚNG** (kèm đính-chính phạm-vi) | Máy ĐÃ per-loại TRỌN: 7 mã `HD-*` (`ContractApprovalWorkflowFamily.cs:33-42`) + 4 cửa guard 409 + PA-A tự pin — dữ-liệu KHÔNG lẫn; việc còn lại CHỈ là 3 lỗ UI: WfView không đọc `ct` (`WorkflowMatrixViewPage.tsx:57,67`) + 2 bug menu A/B + comment stale `Layout.tsx:141-142` | CAO |
| 3 | Menu lẫn lộn, click nhảy | BUG điều hướng — "nghi lớp gotcha #34 / #50 / #85, lane truy bằng code" | **CẦN-CHỈNH** (đúng bệnh, sai lớp nghi) | KHÔNG phải #34/#50/#85 — 2 lỗ TRÊN thuốc-của-#34: (A) leaf WfView cả 7 loại mang `?type=3` (ApplicableType) mà accordion auto-expand đọc `type` như ContractType → mutex đóng nhóm đang mở (`Layout.tsx:151` ⟂ `:470-485`, chuỗi tái-hiện được); (B) `phase=9` transient làm danh-tính → 2 leaf cùng sáng (`:152` ⟂ `:350` — đúng ca PE đã chữa `view=approved` `:180-185`) | CAO |
| 4 | Thao tác/duyệt/xóa y khuôn KHKK | Bộ hành-vi submenu + nút trong màn duyệt theo Y KHUÔN KHKK prod (owner S176) | **ĐÚNG** (1 ranh chờ anh) | Bảng lệch 6-leaf đã đo: 3 🔴 (Luồng-duyệt slice · Danh-sách = Thứ-1 · Đã-duyệt `view=approved`) + 1 ⚠️ (Đang-duyệt → `pendingMe=1` thay trang Inbox rời) + 2 ✅; màn duyệt THIẾU nút Xóa (cột `AllowApproverDelete` Mig 68 DÙNG CHUNG V2, sẵn cho `HD-*`) — delta BE nhỏ 0-mig; **ranh:** 3 món cố-ý-không-port (skipToFinal/duyệt-thay-CEO/banner-kết-thúc-sớm, chặn bởi `ContractDetail` DTO 0 field) — mặc-định GIỮ khai-lệch, port = quyết của anh | CAO (ranh 3-món: VỪA) |
| 5 | Admin Designer per-loại HĐ | Sidebar admin cần mục RIÊNG từng loại y khuôn KHKK N1-8; "lane xác nhận đã có chưa hay đang gộp" | **ĐÚNG — và ĐÃ CÓ ~95%** | Menu-row + 28 policy + Designer family-slice + route ĐÃ land S188 (`DbInitializer.cs:2303-2310` · `ApprovalWorkflowsV2Page.tsx:314-396` · `App.tsx:80`); thiếu ĐÚNG nhánh `Hd*` trong `resolvePath` fe-admin (`Layout.tsx:175-189`, 0-hit `Hd` + control dương `KhkkN:188`) ⇒ leaf drop im lặng #50 — fix ≈ 5 dòng | CAO |

## Thứ-1 layout cây toàn trình

**Hiện trạng my-contracts (fe-user `pages/contracts/MyContractsPage.tsx`):**
- Grid 3-panel `lg:grid-cols-[320px_1fr_360px]` (`:153`).
- Panel-1 TRÁI 320px = **danh-sách HĐ** (search "Tìm theo mã / tên / NCC…" `:162` + empty-state "Không có HĐ — Loại HĐ này chưa có dữ liệu" `:180`) → khớp KHOANH ĐỎ #1 ảnh 1.
- Panel-2 GIỮA = `ContractDetailContent` / placeholder "Chọn 1 HĐ ở danh sách bên trái" (`:219-233`).
- Panel-3 PHẢI 360px = `PipelineTreePanel currentStage={3}` khi chưa chọn (`:242`, quyết định owner S168 slot 63 "trade-off D"), chọn HĐ thì `WorkflowHistoryPanel` (`:243`) → khớp KHOANH ĐỎ #2 "CÂY TOÀN TRÌNH (45 gói)".

**Khuôn KHKK (`pages/khkk/KhkkListPage.tsx` + `components/khkk/KhkkListPanel.tsx`):**
- Grid `lg:grid-cols-[400px_1fr_360px]` (`KhkkListPage.tsx:171`).
- Panel-1 TRÁI 400px = `KhkkListPanel` = **CÂY TOÀN TRÌNH** (owner QĐ-1 2026-08-04, `KhkkListPanel.tsx:9`) — `PipelineTreePanel currentStage={2} hideHeader hideSearch` (`:382-386`) + **flat-list fallback server-side** khi gõ search / chọn Select trạng-thái / `!canPe` (`flatMode`, mitigation R-11/M-1 `:21-24,366-371`), leaf-mode 4-tầng cho Đang duyệt/Đã duyệt/Đã xóa (`:395-405`).
- Panel-3 PHẢI = workflow + lịch sử; chưa chọn = hộp dashed, owner QĐ-2 **CẤM nhét cây vào panel-3** — comment tự khai "Khác GĐ3 ở điểm này là CÓ CHỦ ĐÍCH" (`KhkkListPage.tsx:252-256`) ⇒ chính chỗ "cố ý khác" này là thứ anh bây giờ bảo ĐỔI về giống KHKK.
- M-2 (`KhkkListPanel.tsx:16-17`): card phiếu trên cây gọi `onSelect` của host (chọn vào panel-2) thay vì navigate rời trang — qua opt-in `stage2Options.onOpenKhkk` (`PipelineTreePanel.tsx:111`, `usePipelineStages.ts:185,244`).

**Gap kỹ thuật khi dời cây sang trái ở my-contracts:**
1. `PipelineTreePanel` CHƯA có `stage3Options` — leaf HĐ GĐ3 hiện `onOpen: () => navigate('/contracts/${contractId}')` (`usePipelineStages.ts:377`) = bấm 1 phát văng khỏi màn 3-panel (đúng bệnh M-2 đã chữa cho KHKK). Mirror cần thêm opt-in `onOpenContract` (analog `onOpenKhkk` :185/:244) để chọn vào Panel-2 qua `?id=`.
2. Danh-sách HĐ đi đâu: theo khuôn KHKK = **flat-list là chế-độ fallback trong CÙNG panel trái** (search server-side/lọc trạng thái → flatMode), không phải cột riêng. Panel-3 khi chưa chọn = hộp dashed (khuôn QĐ-2), chọn HĐ = `WorkflowHistoryPanel` như cũ.
3. Bề rộng panel trái nên lên 400px như KHKK/PE (KhkkListPage tự khai số đo lấy của PE "giống nhất với Duyệt NCC").
4. fe-admin có `MyContractsPage` mirror? — cần check cặp SHA (KHKK khai MIRROR SHA256 identical với fe-admin counterpart; ContractsListPage admin là trang khác).

## Thứ-2 per-loại-HĐ

**VERDICT: máy ĐÃ per-loại (S188), dữ-liệu KHÔNG lẫn; cái "lẫn" anh thấy = 3 lỗ UI (WfView không slice + 2 bug menu Thứ-3) + 1 comment stale tự khai "chung 1 bộ".**

**Dòng chảy route `?type=N` (N = ContractType 1..7):**
1. Menu → `Layout.tsx:148` `Ct_<Code>_List → /my-contracts?type=N` (`TYPE_CODE_TO_INT`).
2. `MyContractsPage.tsx:34-36` parse chặn NaN (khuôn HardCopiesPage GĐ4) → `:47-62` gửi `type` LÊN SERVER cả 2 nhánh (`/contracts` + `/contracts/deleted`) — W7 S187 đã gỡ lọc client (`:74-76` tự khai lý do: >100 HĐ thì loại hiếm biến mất im lặng).
3. BE lọc TRƯỚC paging: `ListContractsQuery` filter `ContractFeatures.cs:356`, `ListDeletedContractsQuery` `:479` (số dòng `:325` trong comment FE đã trượt — khái niệm còn đúng).
4. Riêng "Đang duyệt": `/inbox?type=N` → BE `GET /contracts/inbox` **KHÔNG nhận type** (`ContractsController.cs:41-43`, `GetMyInboxQuery()` 0 arg) → FE lọc client `InboxPage.tsx:64,103`. Chạy đúng (payload inbox nhỏ) nhưng là nhánh DUY NHẤT còn lọc client — lệch khuôn W7, chấp nhận được, KHÔNG phải bug dữ-liệu.

**Workflow per-loại — máy ĐÃ khoá (đối chiếu ảnh-2 "mỗi 1 HĐ 1 quy trình khác nhau"):**
- Nhà 7 mã: `ContractApprovalWorkflowFamily.cs:33-42` — `HD-TP/GK/NCC/DV/MB/NT-NCC/NT-DV` map 1-1 ContractType 1..7.
- 4 cửa guard (`ContractWorkflowGuards.cs`): Create `ContractFeatures.cs:87` · sửa-nháp re-pin `:198` · cầu K7 `CreateContractFromSigningPlanFeatures.cs:166` · cửa TRÌNH `ContractWorkflowService.cs:143`. Pin chéo loại trong họ `HD-*` ⇒ 409 (`EnsureFamilyLock :137-160`).
- Máy TỰ PIN đúng loại khi user không chọn: PA-A `ResolveDefaultWorkflowAsync` (`ContractWorkflowGuards.cs:88-102`, gọi ở `ContractFeatures.cs:90`).
- V1 legacy fallback cũng per-loại sẵn: `ContractFeatures.cs:70` `.Where(w => w.ContractType == request.Type && w.IsActive)`.
- Van xả CÓ CHỦ ĐÍCH: mã NGOÀI họ `HD-` (vd `QT-HD-V2-001` đặt tay — bản duy nhất trên Dev đo 2026-08-10) cho qua mọi loại (`ContractWorkflowGuards.cs:141-143`) — đây KHÔNG phải lỗ, là quyền admin tự tạo quy trình; nhưng nếu anh muốn "TUYỆT ĐỐI mỗi loại 1 quy trình" thì phải gỡ ghim/thay QT-HD-V2-001 bằng 7 bản HD-* (việc DATA/Designer, 0 code).

**3 chỗ CÒN lẫn (đều FE/UI):**
1. **WfView không slice theo loại:** trang matrix (`fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx`) đọc `type` (`:57`) + fetch `applicableType=3` (`:67`) nhưng **KHÔNG đọc `ct`** (0 hit `get('ct')` toàn file) ⇒ leaf "Luồng duyệt" của HĐ Thầu phụ hiện CẢ 7 họ HD-* + QT-HD-V2-001 trộn chung. KHKK đã có khuôn slice sẵn ngay trong CÙNG file: `group` → lọc `code === khkkGroupWorkflowCode(n)` (`:84-98`, mirror `KHKK-N{n}`) ⇒ fix = thêm nhánh `ct` → lọc `code === HD-<token>` y khuôn.
2. **Bug menu A+B** (Thứ-3 đã root-cause ở trên) — `?type=3` WfView đá accordion + `phase=9` transient làm 2 leaf cùng sáng.
3. **Comment stale:** `Layout.tsx:141-142` tự khai "HĐ chung 1 bộ workflow V2" — đúng ở S159, SAI từ S188 (7 bộ per-loại). Comment này chính là hoá thạch của thời-chưa-per-loại, sửa cùng lượt với route WfView.

## Thứ-3 BUG menu nhảy lẫn (root-cause)

**ROOT-CAUSE A (chính — tái hiện được, đúng chữ "click menu này NHẢY sang menu khác"): leaf "Luồng duyệt" của CẢ 7 loại HĐ mang `?type=3` ⟂ accordion auto-expand đọc `type` mù ngữ-nghĩa.**
- `fe-user/src/components/Layout.tsx:151`: `Ct_<Code>_WfView → /contracts/workflow-matrix?type=3&ct=${typeInt}` — comment tự khai "WfView cả 7 loại đều ?type=3 (HĐ chung 1 bộ workflow V2) — thêm &ct=N để active-state riêng từng leaf". Ở URL này `type` = **ApplicableType** (3=Contract), KHÔNG phải ContractType.
- Accordion auto-expand `Layout.tsx:470-485`: useEffect đọc `params.get('type')` cho MỌI pathname ngoài `/purchase-evaluations` (nhánh else `:479-483` nuốt luôn `/contracts/workflow-matrix`) → `INT_TO_TYPE_CODE[3] = 'NhaCungCap'` (`:16-29`) → `setExpandedCtCode('NhaCungCap')`.
- **Chuỗi tái hiện:** đang mở nhóm "01. HĐ Thầu phụ" → click leaf "Luồng duyệt" → URL `?type=3&ct=1` → accordion mutex (1 Ct_ mở tối đa, `:464-467`) ĐÓNG nhóm 01 + MỞ nhóm "03. HĐ Nhà cung cấp". Leaf active thật (WfView ct=1) bị giấu trong nhóm đã đóng; nhóm 03 mở ra không leaf nào sáng (WfView nhóm 03 pin `ct=3` ≠ 1, `queryMatches:368-374` so nghiêm key pinned). Với user = "click menu này nhảy sang menu khác". Đây là ĐÚNG bug-class "2 nghĩa của type" (ApplicableType ⟂ ContractType — trục đã cảnh báo trong project-memory SP-002).
- Cùng lớp: click "Luồng duyệt" nhóm 3 thì KHÔNG nhảy (trùng số 3 tình cờ) — giải thích vì sao bug chập chờn "menu lẫn lộn".

**ROOT-CAUSE B (phụ — sáng-nhầm-2-mục): leaf "Đã duyệt" = `?type=N&phase=9` (`Layout.tsx:152`) dùng `phase` TRANSIENT làm danh tính — tái phạm đúng bug S155 mà PE đã chữa.**
- `phase` ∈ `TRANSIENT_QUERY_KEYS` (`:350`). `queryMatches:369` chỉ bỏ-qua transient khi ĐÍCH không pin → đứng ở "Đã duyệt" (`?type=1&phase=9`): leaf "Đã duyệt" (pin phase) khớp NGHIÊM ✓ sáng, leaf "Danh sách" (target `?type=1`, không pin phase → phase ignorable) CŨNG khớp ✓ sáng ⇒ **2 leaf cùng sáng 1 lúc**. PE đã chữa đúng ca này bằng khoá định danh `view=approved` (`:180-185` tự khai luật "URL trùng hệt → menu sáng nhầm — bug UAT anh báo 2026-07-27") — `Ct_*_Approved` :152 KHÔNG bắt chước.
- Đối chứng: "Đã xóa" dùng `deleted=1` (không nằm TRANSIENT) = danh tính chuẩn → không dính.

**Gotcha ứng viên — phán quyết:** #34 (NavLink pathname-only) = KHÔNG PHẢI thủ phạm trực tiếp — fe-user đã có custom `queryMatches` (:354) chính là thuốc của #34; bệnh còn lại là 2 lỗ TRÊN thuốc (A: accordion side-channel không đi qua queryMatches; B: transient-vs-identity). #50 (resolvePath drop im lặng) = không tái ở đây, 42 leaf Ct_* đều resolve. #85 (OR-gate key) = không liên quan điều-hướng.

**Fix đề xuất (FE-only, 0-mig):**
1. WfView route mang danh tính loại làm trục chính: đổi `:151` thành `/contracts/workflow-matrix?ct=N` (bỏ `type=3` khỏi URL; `WorkflowMatrixViewPage` tự suy applicableType=3 khi ở path `/contracts/workflow-matrix` — nó đã có khuôn alias-path cho khkk `?type=10`), HOẶC tối thiểu: auto-expand `:470-485` ưu tiên `ct` khi có + loại trừ pathname `/contracts/workflow-matrix` khỏi nhánh else.
2. Approved: `:152` thêm khoá định danh `&view=approved` y khuôn PE `:185` (giữ `phase=9` làm bộ lọc thật; trang `MyContractsPage` đã bỏ qua param lạ).

## Thứ-4 bộ thao-tác vs khuôn KHKK

**Bộ 6 leaf submenu GIỐNG HỆT theo seed** — cả 2 họ cùng {Luồng duyệt·Danh sách·Thao tác·Đang duyệt·Đã duyệt·Đã xóa} cùng label + icon (`DbInitializer.cs:2094-2099` Khkk G1 / `:2271-2276` Khkk G2-8 / `:2196-2201` Ct_×7). LỆCH nằm ở HÀNH VI từng leaf + màn duyệt:

| Trục | KHKK (chuẩn prod, owner S176) | HĐ GĐ3 hiện tại | Verdict |
|---|---|---|---|
| Luồng duyệt | slice đúng nhóm: `group` → lọc `code===KHKK-N{n}` (`WorkflowMatrixViewPage.tsx:84-98`) | `?type=3&ct=N` — trang KHÔNG đọc `ct`, hiện trộn cả 7 họ; `type=3` đá accordion (bug A) | 🔴 LỆCH — fix bug A + slice `ct`→`HD-*` y khuôn group |
| Danh sách | cây toàn trình đứng TRÁI 400px (`KhkkListPage.tsx:171`) | list trái 320px, cây bên PHẢI (`MyContractsPage.tsx:153,242`) | 🔴 LỆCH — chính là Thứ-1 |
| Thao tác | `/khkk/create?group=n` | `/contracts/new?type=N` | ✅ tương đương (form tạo riêng loại) |
| Đang duyệt | Ở LẠI CÙNG trang list: `filter=ChoDuyet` — `filter` KHÔNG transient (`Layout.tsx:350`) nên vừa lọc vừa là danh tính hợp lệ | văng sang trang RIÊNG `/inbox?type=N`; BE inbox KHÔNG nhận type (`ContractsController.cs:41-43`) → lọc client (`InboxPage.tsx:103`) | ⚠️ LỆCH KHUÔN — muốn y KHKK thì "Đang duyệt" về `/my-contracts?type=N&pendingMe=1` (param `pendingMe` BE ĐÃ có, khuôn PE `Layout.tsx:176`); giữ InboxPage cho vai trò hộp thư tổng |
| Đã duyệt | `filter=DaDuyet` = danh tính chuẩn | `phase=9` transient → 2 leaf cùng sáng (bug B) | 🔴 LỆCH — fix `view=approved` (đã đề xuất Thứ-3) |
| Đã xóa | `view=deleted` + title "· đã xóa" + placeholder "chỉ xem danh sách, không mở chi tiết" (`KhkkListPage.tsx:151-152,198-200`) | `deleted=1` + badge "Đã xóa — chỉ xem" + chặn select (`MyContractsPage.tsx:94,134-137`) | ✅ TƯƠNG ĐƯƠNG hành vi (khác chữ; danh tính `deleted=1` hợp lệ) |

**Màn duyệt — `ContractWorkflowPanel.tsx` (MIRROR SHA256 2 app, khuôn nguồn tự khai = `KhkkWorkflowPanel` bản owner-trim S175 `:2-5`):**
- ✅ ĐÃ GIỐNG: 2 nút Duyệt/"← Trả lại" (`:347`, Từ chối KHÔNG render đúng trim S174 `:69,:317`) · banner "✓ Đến lượt bạn duyệt" (`:291`) · banner TRẢ LẠI/TỪ CHỐI (`:307,:312`) · sơ đồ Bước→Cấp. 4 host cùng render (`:42-43`: ContractDetailPage ×2 app · MyContractsPage · ContractsListPage · InboxPage).
- 🔴 KHKK CÓ mà GĐ3 THIẾU (đáng làm — anh nhắc "xóa" trong lời Thứ-4): **nút Xóa phiếu ở màn duyệt** (KHKK panel mục 4 `KhkkWorkflowPanel.tsx:12-14,118,233`; cờ `AllowApproverDelete` per-Level Mig 68 nằm trên bảng `ApprovalWorkflowLevels` DÙNG CHUNG V2 — cột ĐÃ có sẵn cho workflow HD-*). Phía HĐ: `ContractFeatures.cs` 0 hit `AllowApproverDelete`, và **delete-guard còn so SỐ `Phase >= DangInKy` (`:896-897`)** ⇒ HĐ V2 ở ChoDuyet(10)/TraLai(98) KHÔNG xóa được (S160 finding còn sống) — muốn y KHKK bắt buộc delta BE nhỏ (nới guard V2-aware + đọc cờ), 0-mig vì cột sẵn.
- 🟡 KHKK CÓ mà GĐ3 CỐ Ý KHÔNG PORT (panel tự khai "đo ra, không phải cắt cho gọn" `:26-39`): ① ô-tích "Cấp này KẾT THÚC" (API `TransitionContractBody` 3 field, `skipToFinal` không trên surface) ② badge "⚑ Duyệt thay CEO" (`ApproveV2Async` HĐ không đọc `allowApproverFinalize`, `ContractWorkflowService.cs:230-407`) ③ **banner kết-thúc-sớm + meta `{code} v{NN}`** — chặn bởi `ContractDetail` DTO 0 field (`endedByLevelFinalize`/`workflowCode`/`workflowVersion`). Nếu anh muốn ngang KHKK tuyệt đối → cần nở DTO (BE), còn không thì giữ nguyên khai-lệch.
- 🟢 KHÁC CÓ CHỦ ĐÍCH: file đính kèm lúc duyệt — KHKK có section riêng trong panel (`KhkkWorkflowPanel.tsx:502`, đường `approval-attachments` riêng); HĐ dùng `ContractAttachmentsSection` sẵn trong detail (`ContractWorkflowPanel.tsx:44-46`) — không thiếu chức năng, chỉ khác chỗ đặt.

## Thứ-5 admin Designer per-loại

**VERDICT: ~95% ĐÃ CÓ từ S188 — BE seed 7 menu-row + policy + Designer slice + route đều ĐỦ; thiếu ĐÚNG 1 nhánh `resolvePath` fe-admin ⇒ 7 leaf DROP IM LẶNG khỏi sidebar (đúng gotcha #50). Fix ≈ 5 dòng fe-admin, 0 BE, 0 mig, 0 seed mới.**

1. **Sidebar fe-admin = DB-menu-driven, KHÔNG phải menu tĩnh** (bác giả-thuyết cũ): render từ `AuthContext.menu` → `filterForAdmin` lọc ĐỆ QUY theo `ADMIN_HIDDEN_KEYS` (`Layout.tsx:227-240` — ẩn Contracts/PE/KeHoachKyKet/HopDongCung/Workflows/PeWorkflows…; **`ApprovalWorkflowsV2` KHÔNG nằm trong tập ẩn**) + prefix `Ct_` (`:248`). Leaf sống PHẢI qua `resolvePath` — trả `null` = drop im lặng (`:251-260`).
2. **Menu-row GĐ3 ĐÃ SEED:** `DbInitializer.cs:2303-2310` sinh 7 leaf key `AwV2_Hd{TP,GK,NCC,DV,MB,NTNCC,NTDV}`, label `"QT Duyệt HĐ — {shortName}"`, parent `ApprovalWorkflowsV2`, Order 11..17 (tiếp ngay sau KHKK 3..10) — key sinh by-construction từ `MenuKeys.ApprovalWorkflowContractLeaf` (`MenuKeys.cs:151`) trên `ContractApprovalWorkflowFamily.Members` (invariant T6 `:2296-2300`: key vào `All` ⇒ 28 policy `AwV2_Hd*.{R,C,U,D}` + admin auto-grant). ⇒ leaf hiện có của mục "Quy trình duyệt (Mới)": `AwV2_DuyetNcc` · `AwV2_DuyetNccPhuongAn` · `AwV2_KhkkN1..8` render được; **7 leaf HĐ tồn tại trong DB nhưng KHÔNG hiện** — khớp ảnh-3.
3. **Lỗ duy nhất — `resolvePath` fe-admin (`Layout.tsx:175-189`)**: block `awV2Match` chỉ có 2 nhánh `DuyetNcc|DuyetNccPhuongAn|Contract` (`:178`) + `/^KhkkN[1-8]$/` (`:188`) — **0 nhánh `Hd*`** (grep `Hd|KhkkN` toàn file: chỉ `Hdc_*` GĐ4 `:49-55` + KhkkN `:188` = control dương cùng block). Trớ trêu: `lib/contractWorkflowFamily.ts:10-11` TỰ KHAI resolvePath là "chỗ đọc thứ 2 … thiếu là leaf DROP IM LẶNG — gotcha #50", lib dựng sẵn cho đúng việc này mà Layout **chưa import** (grep import: duy nhất `ApprovalWorkflowsV2Page.tsx:39`). Đây là ca "vá tệp A quên tệp B" kinh điển (S180): W6 nối trang Designer, quên nối sidebar.
4. **Designer page slice ĐÃ TRỌN (W6 S187, "type-3 family-slice"):** route path-param `/system/approval-workflows-v2/:typeCode` (`App.tsx:80`) → `findContractWfByTypeCode(typeCode)` (`ApprovalWorkflowsV2Page.tsx:314`) → `selectedTypeInt=3` (`:316-320`) → cắt `history.filter(d => d.code === familyCode)`, `active` suy TỪ lát cắt — cố ý KHÔNG đọc `ActiveOthers` (F-3 phương án ii, `:337-370`) → `lockedCode` khoá ô Mã (`:377`) → `panelLabel = ContractTypeLabel[n]` (`:385-389`) + panelDescription tự giải thích vì sao khoá (`:393-396`). Cùng MỘT đường với `KhkkN{n}` (`:298-308`, `:334-336` "Hai HỌ per-Code dùng CHUNG một đường"). 9-checkbox Sửa-tại-chỗ (ảnh-3, gồm cờ Xóa đỏ) round-trip sẵn cho MỌI workflow V2 kể cả `HD-*` (`ApprovalWorkflowV2AdminFeatures.cs:475,883,1288`).
5. **Việc CỤ THỂ ra 7 leaf "Duyệt HĐ — <loại>":** CHỈ sửa fe-admin `components/Layout.tsx` — import `findContractWfByTypeCode` từ `@/lib/contractWorkflowFamily` + thêm sau `:188`: `if (findContractWfByTypeCode(code)) return '/system/approval-workflows-v2/' + code`. **KHÔNG dùng `?ct=`/`?family=`** — khuôn họ này là path-param `:typeCode` (`HdTP`…`HdNTNCC`) y hệt `KhkkN{n}`. KHÔNG sửa DbInitializer (label "QT Duyệt HĐ — Thầu phụ…" đã khớp khuôn "Duyệt KHKK — Nhóm n"; muốn đổi label thì BẮT BUỘC đường `labelBackfill` `:2301-2302` — không cần). Ghi chú phụ: leaf `AwV2_Contract` cũ ("cả loại Hợp đồng", `:178`) không thấy trong ảnh-3 — chưa đo row DB, không thuộc scope 7-leaf, uncertain vô hại.

## PROPOSE spec 3-mục + wave + checklist

### ① Đề-bài (1 đoạn)

Đồng-khuôn GĐ3 "Duyệt Hợp đồng" với KHKK + Duyệt NCC prod (luật owner S176: đã-deploy = CHUẨN): (Đ1) cây toàn trình đứng TRÁI làm trục điều hướng thay danh-sách; (Đ2+Đ3) menu 7 loại HĐ mang danh-tính đúng trục ContractType — hết nhảy lẫn, hết 2-leaf-cùng-sáng, leaf "Luồng duyệt" chỉ hiện đúng quy trình `HD-*` của loại đó (máy per-loại ĐÃ xong @S188 — toàn bộ việc là UI); (Đ4) bộ thao-tác theo hành-vi KHKK (Đang-duyệt ở lại cùng trang, Đã-duyệt danh-tính chuẩn, nút Xóa ở màn duyệt theo cờ per-Cấp); (Đ5) admin Designer hiện 7 leaf per-loại đã seed sẵn. Tổng: **FE-only + 1 delta BE nhỏ (W5, 0-mig)**.

### ② Cách implement per-điều-chỉnh (file:line)

- **Đ1 (cây trái):** `fe-user/pages/contracts/MyContractsPage.tsx:153` grid → `[400px_1fr_360px]`; panel-1 = `PipelineTreePanel currentStage={3} hideHeader hideSearch` + flat-list fallback server-side trong CÙNG panel (khuôn `KhkkListPage.tsx:171` + `KhkkListPanel.tsx:21-24,366-371`); panel-3 = hộp dashed khi chưa chọn / `WorkflowHistoryPanel` khi chọn (QĐ-2 `KhkkListPage.tsx:252-256` — CẤM cây ở panel-3). Gap phải bịt: `usePipelineStages.ts:377` leaf GĐ3 đang `navigate('/contracts/...')` → thêm opt-in `stage3Options.onOpenContract` (analog `onOpenKhkk` `:185,:244`) + wire `PipelineTreePanel.tsx:111` để chọn vào panel-2 qua `?id=`. **Host chỉ fe-user** (glob `fe-admin/src/pages/contracts/`: 4 file, KHÔNG có MyContractsPage); `usePipelineStages` + `PipelineTreePanel` = cặp MIRROR ×2 app → sửa cả 2 bản giữ SHA-identical.
- **Đ2+Đ3 (danh-tính menu + slice):** `fe-user/components/Layout.tsx:151` WfView → `/contracts/workflow-matrix?ct=N` (BỎ `type=3` khỏi URL — trang tự suy applicableType=3 theo path, khuôn alias-path khkk sẵn) · `:152` thêm `&view=approved` (khuôn PE `:180-185`, giữ `phase=9` làm lọc thật) · sửa comment stale `:141-142` ("chung 1 bộ" → 7 bộ per-loại S188) · accordion `:470-485` ưu tiên `ct` khi có + loại trừ pathname `/contracts/workflow-matrix` khỏi nhánh else (phòng-thủ 2 lớp) · `WorkflowMatrixViewPage.tsx` thêm nhánh đọc `ct` → lọc `code === HD-<token>` y khuôn nhánh `group` `:84-98` (map ContractType→mã: const cục-bộ 7-entry khuôn `khkkGroupWorkflowCode` — fe-user KHÔNG mirror lib fe-admin, `contractWorkflowFamily.ts:16-18` tự khai fe-admin-ONLY). fe-admin KHÔNG đụng: `Ct_*` bị ẩn (`Layout.tsx:248`) + cố ý không có WorkflowMatrixViewPage (`:185-187`).
- **Đ4 (thao tác):** Đang-duyệt: `fe-user/Layout.tsx` `Ct_*_Pending` → `/my-contracts?type=N&pendingMe=1` (khuôn PE `:176`; BE param `pendingMe` sẵn) + `MyContractsPage` đọc `pendingMe` gửi server; GIỮ `/inbox` làm hộp thư tổng. Đã-duyệt/Đã-xóa: đã nằm Đ2+Đ3 (`view=approved`; `deleted=1` giữ nguyên ✅). Nút Xóa màn duyệt → W5. 3 món cố-ý-không-port: GIỮ khai-lệch (ngoài scope, chờ anh).
- **Đ5 (admin 7 leaf):** `fe-admin/components/Layout.tsx` import `findContractWfByTypeCode` từ `@/lib/contractWorkflowFamily` + thêm sau `:188`: `if (findContractWfByTypeCode(code)) return '/system/approval-workflows-v2/' + code`. Hết. (Menu-row/policy/Designer/route đã sẵn — xem mục Thứ-5.)
- **W5 BE nhỏ (nút Xóa, 0-mig):** mirror khuôn PE prod — `PeSoftDeleteFeatures.cs:110-114` (3 điều-kiện: ChoDuyet · ĐÚNG-LƯỢT actor · `matchingLevel.AllowApproverDelete`) + contract-surface `PurchaseEvaluationsController.cs:169`. Việc: nới delete-guard `ContractFeatures.cs:896-897` (đang so SỐ `Phase >= DangInKy` — chặn nhầm V2 ChoDuyet=10/TraLai=98/TuChoi=99, finding S160) thành V2-aware + nhánh xóa-bởi-approver đọc cờ (cột `ApprovalWorkflowLevels.AllowApproverDelete` Mig 68 DÙNG CHUNG V2 — Designer đã round-trip cho `HD-*` sẵn: `ApprovalWorkflowV2AdminFeatures.cs:475,883,1288`) + DTO expose cờ (khuôn `PurchaseEvaluationDtos.cs:138`) + FE `ContractWorkflowPanel.tsx` thêm mục Xóa mirror `KhkkWorkflowPanel.tsx:12-14,118,233` (panel SHA-mirror ×2 app).

### ③ WAVE (5, thứ tự phụ thuộc) + CHECKLIST acceptance đo được

Chỉnh so đề-xuất lead: **gộp slice-WfView vào W1** (route `:151` đổi sang `?ct=` và trang đọc `ct` là 2 nửa của MỘT thay đổi — tách wave thì trạng-thái trung-gian leaf trỏ `ct` mà trang chưa đọc); W3 thu về pendingMe; W4 độc lập chạy song song được bất kỳ lúc.

- **W1 — Danh-tính menu + slice per-loại (fe-user, FE nhỏ — chữa bug anh đau nhất):** Layout `:151/:152/:141-142/:470-485` + WorkflowMatrixViewPage nhánh `ct`.
  ✅ (i) tái-hiện A: mở nhóm "01. HĐ Thầu phụ" → click "Luồng duyệt" → accordion GIỮ nhóm 01, leaf sáng đúng (trước: đóng 01, mở 03); (ii) tái-hiện B: đứng `?type=1&phase=9&view=approved` → CHỈ leaf "Đã duyệt" sáng (trước: 2 leaf); (iii) WfView `ct=1` chỉ hiện họ `HD-TP` (0 dòng QT-HD-V2-001/6 loại kia); (iv) `grep -rn "workflow-matrix?type=3" fe-user/src` = 0-hit (control dương: grep `workflow-matrix?ct=` ≥1-hit `Layout.tsx`); (v) `npm run build` fe-user PASS.
- **W2 — Cây toàn trình sang trái (FE lớn):** MyContractsPage 3-panel đảo + `stage3Options.onOpenContract` + flatMode.
  ✅ (i) grep `grid-cols-\[400px` MyContractsPage.tsx ≥1-hit; (ii) hành-vi: click leaf HĐ trên cây → panel-2 mở detail, URL Ở LẠI `/my-contracts` (trước: navigate rời `/contracts/{id}`); (iii) gõ search → flatMode list; (iv) cặp mirror `usePipelineStages.ts`/`PipelineTreePanel.tsx` 2 app: `git diff --stat` chạm cả 2 bản + so SHA256 từng cặp khớp; (v) `npm run build` 2 app PASS.
- **W3 — Đang-duyệt về cùng trang (FE nhỏ, sau W1 — cùng vùng Layout `:148-152` tránh conflict):** `Ct_*_Pending` → `pendingMe=1`.
  ✅ (i) grep `pendingMe=1` fe-user Layout.tsx nhánh Ct_ ≥1-hit; (ii) hành-vi: leaf "Đang duyệt" ở lại `/my-contracts`, list = phiếu đến-lượt-mình (đối chiếu inbox cũ cùng user); (iii) build fe-user PASS.
- **W4 — Admin Designer 7 leaf (fe-admin ≈5 dòng, song song được):** resolvePath nhánh `Hd*`.
  ✅ (i) đăng nhập admin → "Quy trình duyệt (Mới)" hiện đủ 17 leaf (2 NCC + 8 KHKK + 7 "QT Duyệt HĐ — …" Order 11..17); (ii) click "QT Duyệt HĐ — Thầu phụ" → panel title "…: HĐ Thầu phụ", ô Mã khoá `HD-TP`, history CHỈ version HD-TP; (iii) `npm run build` fe-admin PASS.
- **W5 — Nút Xóa màn duyệt V2-aware (BE nhỏ + FE, 0-mig, test-before cho guard):** ContractFeatures guard + delete-by-approver + DTO + panel ×2 app.
  ✅ (i) `dotnet test SolutionErp.slnx` PASS + test mới: TraLai(98)/TuChoi(99) Drafter xóa được · ChoDuyet(10) CHỈ approver-đúng-lượt-có-cờ xóa được · cờ tắt → 403/409 · V1 `Phase>=DangInKy` VẪN chặn (regression S160); (ii) grep `AllowApproverDelete` trong `ContractFeatures.cs`/Contract-delete-path ≥1-hit (trước: 0-hit toàn Contract-side); (iii) panel 2 app SHA-identical + nút Xóa chỉ render khi DTO cờ bật; (iv) build 2 app PASS.

**Ngoài wave (chờ anh / data-only):** ① gỡ-ghim hoặc thay `QT-HD-V2-001` (bản type-3 đặt tay duy nhất trên Dev) bằng 7 bản `HD-*` nếu anh muốn "tuyệt-đối mỗi loại 1 quy trình" — việc Designer/DATA, 0 code (van xả `ContractWorkflowGuards.cs:141-143` là CỐ Ý); ② 3 món cố-ý-không-port (skipToFinal / duyệt-thay-CEO / banner-kết-thúc-sớm) cần nở `ContractDetail` DTO nếu anh muốn ngang KHKK tuyệt đối.

== HẾT — 3/3 mục B1c chốt ==
