# sub-invest-fe-fable — S182 KHKK AWF-v2 mirror NCC (lens FE UI, "ko lệch")

> Fable deep-pass đơn lẻ, /fable-real investigator-codebase. Đề: 8 mục sidebar "Duyệt KHKK — Nhóm 1..8" phải ra ĐÚNG trang `system/approval-workflows-v2/DuyetNcc` khuôn ảnh anh gửi. 0 sửa code.

## §1 Trang DuyetNcc render từ đâu (route + page + KIND_CONFIG + dialog)

**Route:** `fe-admin/src/App.tsx:79-80` — `/system/approval-workflows-v2` + `/system/approval-workflows-v2/:typeCode` → cùng 1 component `ApprovalWorkflowsV2Page` (named export, `fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx` 1355 dòng).

**KIND map (KHÔNG phải KIND_CONFIG object đầy đủ kiểu S29/S35 — chỉ 2 Record thưa):**
- `TYPE_CODE_TO_INT` `:164-172` = `{ DuyetNcc:1, DuyetNccPhuongAn:2, Contract:3, ContractSigningPlan:10 }` — **0 entry `KhkkN*`**.
- `DEFAULT_CODE_BY_TYPE` `:173-180` = `{1:'QT-DN-V2-001', 2:'QT-DN-PA-V2-001', 3:'QT-HD-V2-001', 10:'QT-KHKK-V2-001'}` (chỉ gợi-ý ô Mã).
- Nhãn hiển thị KHÔNG có bảng FE — lấy từ BE `applicableTypeLabel` (`AwLabels.Type`, `ApprovalWorkflowV2AdminFeatures.cs:97` có entry type-10 "Kế hoạch ký kết HĐ").

**Cơ chế render `:286-353`:** `selectedTypeInt = TYPE_CODE_TO_INT[typeCode]`; unknown typeCode ⇒ `undefined` ⇒ `currentType=null` ⇒ render **LANDING GRID** mọi type (`:323-343`) thay vì panel. Panel thật = `TypePanel` `:357-459` (active card + "Lịch sử versions" + nút "Tạo quy trình mới") — mỗi panel khoá vào ĐÚNG 1 `applicableType`, active = `type.active` (BE tính), history render **filter `!d.isActive`** (`:419,:426`).

**Cột nút phải (khuôn ảnh):** `DefinitionCard:577-612` — Sửa tại chỗ `:587` / Tạo phiên bản mới `:596` / Ghim-Bỏ ghim `:606` / Xoá version `:610`. Badge: Đang áp dụng `:490` · Cho user chọn `:502` · Ngưỡng CEO `:506-513`. Badge NS PRO/CCM per NV `:560-565` (display-only suy từ role).

**Dialog Sửa tại chỗ / Tạo mới:** cùng component `Designer:620-1354`, `isEdit = editTarget !== null` `:638`; title "Sửa tại chỗ — {code} v{NN} (giữ nguyên phiên bản)" `:868`. Khối cam "QUYỀN DUYỆT {TÊN}" `:1228-1329` = **9 checkbox** map field TS (`EditLevelEntry:100-123` / API `LevelDto:37-62`):
| # | Label UI | Field |
|---|---|---|
|1| Trả về 1 Cấp trước | `allowReturnOneLevel` |
|2| Trả về 1 Bước trước | `allowReturnOneStep` |
|3| Trả về Người chỉ định | `allowReturnToAssignee` |
|4| Trả về Drafter (mặc định) — tick sẵn | `allowReturnToDrafter` (default true `:231`) |
|5| Chỉnh sửa **Section 2 (Hạng mục/NCC/Báo giá)** | `allowApproverEditDetails` `:1275` |
|6| Chỉnh sửa Section ngân sách | `allowApproverEditBudget` `:1284` |
|7| Duyệt thẳng Cấp cuối | `allowApproverSkipToFinal` — 🔴 **DISABLED khi type=10** `:1292-1302` (K3: service KHKK chưa port SkipToFinal, BE ép-false) |
|8| Duyệt là KẾT THÚC quy trình (xanh emerald) | `allowApproverFinalize` `:1307-1315` |
|9| Cho phép NV này XÓA phiếu (đỏ rose) | `allowApproverDelete` `:1320-1328` |
9 cờ gửi qua 1 hàm chung `levelFlagsPayload:255-267` (2 call-site POST `:820` + PUT `:788` — chống silent-drop F6/F-7 S155). Rule-box xanh cuối dialog `:1343-1350`. Ô "Ngưỡng CEO" `:944-961` render cho MỌI type (kể cả 10).

## §2 8 mục KHKK sidebar hiện map đi đâu (4-chỗ pattern 16-bis)

| Chỗ | File:line | Trạng thái |
|---|---|---|
| 1. BE const + `All` | `MenuKeys.cs:127-134` + `:248-249` | ✅ 8 key `AwV2_KhkkN1..8` VÀO `All` (⇒ 32 policy) |
| 2. BE menu-row seed | `DbInitializer.cs:1988-1995` | ✅ label "Duyệt KHKK — Nhóm {n}", parent `ApprovalWorkflowsV2` (root :1981 dưới System), order 3..10, icon FileSignature; Admin auto-grant qua `SeedAdminPermissionsAsync` (comment :1984-1987) |
| 3. FE `lib/menuKeys.ts` mirror | `fe-admin:31-32` / `fe-user:31-32` | ❌ chỉ có `AwV2_DuyetNcc(+PhuongAn)` — **0 KhkkN**. VÔ HẠI runtime: sidebar render BE-tree + `resolvePath` string thô; 0 call-site `can('AwV2_*')`; route `/system/approval-workflows-v2` **KHÔNG PermissionGuard** (`App.tsx:79-80` route trần). Optional mirror-completeness |
| 4. FE `resolvePath` | `fe-admin/src/components/Layout.tsx:188` | ✅ `/^KhkkN[1-8]$/` → `/system/approval-workflows-v2/KhkkN{n}` (K3 S164/S165, CHỈ nới họ `AwV2_*`) |

**Bấm vào hôm nay ra gì:** route khớp `App.tsx:80` → `ApprovalWorkflowsV2Page`, nhưng `TYPE_CODE_TO_INT['KhkkN3'] = undefined` ⇒ `currentType=null` ⇒ **LANDING GRID mọi type** (`:323-343`, header "Quy trình duyệt (Mới)" + hint "chọn loại từ menu"). **CẢ 8 mục ra CÙNG landing — KHÔNG ra khuôn DuyetNcc panel. Đây chính là "lệch".** (Không phải ComingSoon, không 404.)

**Sibling:** root AwV2 có leaf 1-2 = DuyetNcc/PhuongAn (`:1982-1983`); KHÔNG có leaf `AwV2_Contract` seed (Layout `:178` chỉ phục vụ deep-link).

## §3 Delta "ko lệch" — cần thêm gì + hardcode-PE-label sites

**Danh tính nhóm = `Code`, KHÔNG phải ApplicableType.** 8 workflow seed `SeedKhkkGroupWorkflowsAsync` (`DbInitializer.cs:549-625`): `Code="KHKK-N{n}"`, **CÙNG `ApplicableType=10`**, Version=1, `IsActive=true` cả 8, `IsUserSelectable=true`, 3 trạm PRO→CCM→BGĐ sao roster Duyệt NCC prod. BE guard pin: regex `^KHKK-N([1-8])$` phải khớp `plan.ApprovalGroup` (`ContractSigningPlanFeatures.cs:479-486`). FE helper sẵn **2 app identical**: `khkkGroupWorkflowCode(n)='KHKK-N{n}'` + `khkkGroupMenuLabel(n)` (`fe-{admin,user}/src/types/khkk.ts:159-168`). Precedent slice-by-code đã chạy ở fe-user `WorkflowMatrixViewPage.tsx:84-99` (K4c: client-side filter `wf.code === khkkGroupWorkflowCode(g)`, không thêm trục group vào queryKey).

**Delta CHÍNH — 1 file `fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx`** (page đã generic theo `:typeCode`; KHÔNG thêm route/menu/BE-seed):
1. `:164-172` — thêm chiều parse: `const khkkKindMatch = typeCode?.match(/^KhkkN([1-8])$/)` → `khkkGroup: 1..8 | null`; `selectedTypeInt = khkkGroup ? 10 : TYPE_CODE_TO_INT[typeCode]` (overview query giữ nguyên `applicableType=10`).
2. `:299-301` — `currentType` cho kind nhóm = **synthetic slice**: từ `types.find(t=>t.applicableType===10)` lấy `versions = history.filter(d => d.code === khkkGroupWorkflowCode(g))`; `active = versions.find(v=>v.isActive) ?? null`; `applicableTypeLabel = 'Duyệt KHKK — Nhóm {g}'` (menu-label BE `:1988`) — **D4** anh chọn nhãn này hay nhãn nghiệp vụ `khkkGroupMenuLabel(g)` = "{g}. Thiết bị, máy móc…" (đề xuất: title menu-label + phụ đề nghiệp vụ).
3. `TypePanel:357` + `Designer:620` — prop mới `lockedCode?: string`: Designer `defaultCode → lockedCode ?? DEFAULT_CODE_BY_TYPE[...]` (`:650-651`), ô Mã `disabled={isEdit || !!lockedCode}` (`:927`), POST gửi `lockedCode ?? code` (`:798`). **Thiếu khoá này thì "Tạo quy trình mới" từ panel nhóm 3 với mã tự gõ sẽ đẻ version rơi khỏi slice → biến mất khỏi panel** (và không được guard `:482` nhận là workflow nhóm).
4. Không đổi gì khác của khuôn: 4 nút phải, badge, Lịch sử versions, dialog 9-checkbox, rule-box — dùng nguyên `TypePanel`/`DefinitionCard`/`Designer` (đúng "ko lệch"). SkipToFinal đã tự disable cho type-10 (`:1292-1302`) — 8 kind thừa hưởng vì cùng applicableType=10.

**Hardcode-label sites trong khuôn chung (đánh dấu — KHÔNG tự quyết):**
- `:1275` "Cho phép chỉnh sửa **Section 2 (Hạng mục/NCC/Báo giá)** lúc đang duyệt" — từ vựng PE. KHKK service **KHÔNG đọc cờ này** (0-hit `AllowApproverEditDetails` trong `ContractSigningPlan*.cs`).
- `:1284` "Section ngân sách" — tương tự, 0-hit `AllowApproverEditBudget`.
- `:944-961` ô "**Ngưỡng giá trị gói CEO**" render mọi type — KHKK service **CỐ Ý KHÔNG port** `CeoApprovalThreshold` (`ContractSigningPlanWorkflowService.cs:46-47`: NĐUQ = human-gate vĩnh viễn, OG-3) ⇒ nhập cho kind KHKK = config-lie class #78. Khuôn disable-có-tooltip sẵn ở `:1292`.
- Badge "✎ NS PRO/NS CCM" (`:560-565`) — suy từ role PE-budget; hiển thị trên panel KHKK như đang hiển thị cho type-10 hiện nay.
- **Răng thật của 9 checkbox với KHKK hôm nay: CHỈ #8 `AllowApproverFinalize` sống** (`ContractSigningPlanWorkflowService.cs:276`); 4 cờ Trả-về không đọc — Trả lại KHKK = TOÀN PHẦN luôn-được (`:48,:354-362`); Delete/EditDetails/EditBudget/SkipToFinal: 0-hit. **D3:** giữ nguyên khuôn (đúng "ko lệch", như panel ContractSigningPlan đang render) hay disable 7 ô chết theo khuôn SkipToFinal — anh/lead chốt; mặc định nghiêng GIỮ (uniform, cờ nằm chờ port).

## §4 fe-user — IsUserSelectable tại create KHKK + designer admin-only proof

- **Tạo phiếu KHKK KHÔNG có dropdown chọn quy trình** — workflow GÁN CỨNG theo nhóm (owner S174): `fe-user/src/pages/khkk/KhkkCreatePage.tsx:96-116` query `applicableType=10` → **filter `w.isUserSelectable` `:104`** → `autoWorkflow = find(code === 'KHKK-N{group}')` `:113-116`; thiếu mã ⇒ CHẶN tạo + báo "Chưa có quy trình KHKK-N{n}" (`:128,:155`). `fe-admin/src/pages/khkk/KhkkCreatePage.tsx` mirror cùng khuôn (`:97-100`). ⇒ **Bỏ ghim (`IsUserSelectable=false`) 1 workflow nhóm = CHẶN TẠO PHIẾU nhóm đó** ở cả 2 app — nút Ghim/Bỏ ghim trên 8 panel mới có hệ quả nghiệp vụ thật.
- Consumer thứ 2 `fe-user/src/pages/khkk/KhkkDetailPage.tsx:214-227` = bridge KHKK→HĐ, query `applicableType=3` lọc `isUserSelectable && isActive` — không liên quan nhóm.
- **Designer admin-only — CHỨNG:** grep `approval-workflows-v2` toàn `fe-user/src` = chỉ API-consumer (Create/Detail/Matrix/Proposal/WorkflowApp) — **0 page designer, 0 route `/system/approval-workflows-v2`**, Layout fe-user **0 nhánh `AwV2_*`** (grep 0-hit ngoài `lib/menuKeys.ts:31-32` const chết). fe-user chỉ có view read-only `pages/pe/WorkflowMatrixViewPage.tsx` (`/khkk/workflow-matrix?type=10&group=n`, K4c slice cùng convention code).

## §5 Wave FE đề xuất + acceptance

**W-FE1 (fe-admin, 1 file):** `ApprovalWorkflowsV2Page.tsx` — 4 chạm §3 (parse kind `:164-172` · synthetic slice `:299-301` · `lockedCode` xuyên `TypePanel:345-350/:357/:447-455` → `Designer:620/:650/:798/:927` · title). Import từ `types/khkk.ts` (đã có sẵn trong fe-admin). Ước ~60-90 dòng diff.
**W-FE2 (optional):** `fe-{admin,user}/src/lib/menuKeys.ts` +8 const `AwV2_KhkkN*` (mirror-completeness, 0 hệ quả runtime).
**SHA-mirror 2 app: KHÔNG áp dụng** — trang system Designer là fe-admin-only (chứng §4); `types/khkk.ts` 2 app đã identical sẵn, không chạm.
**BE:** 0 bắt buộc cho khuôn hiển thị; **D1 (khuyến nghị làm cùng wave):** `ApprovalWorkflowV2AdminFeatures.cs:339-343` deactivate-per-TYPE — sửa thành per-Code khi code khớp `^KHKK-N[1-8]$` (hoặc per-Code toàn cục — semantic đổi, anh chốt).

**Acceptance đo được:**
1. Login admin → sidebar Hệ thống › Quy trình duyệt (Mới) hiện 10 leaf; bấm "Duyệt KHKK — Nhóm 3" → URL `/system/approval-workflows-v2/KhkkN3` → header "Quy trình duyệt (Mới): Duyệt KHKK — Nhóm 3" + card `KHKK-N3 v01` badge "Đang áp dụng"+"Cho user chọn" + 3 Bước (PRO 4NV+TP·CCM 4NV+TP·BGĐ CEO) + cột 4 nút phải + "Lịch sử versions"/"Tạo quy trình mới". 8/8 leaf → 8 slice khác nhau, hết landing grid.
2. "Sửa tại chỗ" → dialog title "Sửa tại chỗ — KHKK-N3 v01 (giữ nguyên phiên bản)", per-NV khối cam "QUYỀN DUYỆT …" 9 checkbox 2 cột, "Trả về Drafter (mặc định)" tick sẵn, dòng xanh KẾT THÚC + dòng đỏ XÓA phiếu, SkipToFinal mờ + tooltip, rule-box cuối.
3. "Tạo quy trình mới" từ panel nhóm: ô Mã khoá `KHKK-N3` → lưu ra v02 nằm ĐÚNG panel nhóm 3; (nếu D1 làm) badge "Đang áp dụng" 7 nhóm khác KHÔNG mất.
4. Regression: `/DuyetNcc` `/DuyetNccPhuongAn` `/Contract` `/ContractSigningPlan` render như cũ; fe-user tạo phiếu nhóm 3 vẫn auto-pin `KHKK-N3` (không đổi API).

## §6 Surprises ngoài đề

1. 🔴 **Panel `ContractSigningPlan` (đích của `Khkk_WfView` admin, `Layout.tsx:40`) hôm nay GIẤU 7/8 workflow nhóm:** BE `Active = FirstOrDefault(IsActive)` per-type (`ApprovalWorkflowV2AdminFeatures.cs:210`, tie-break Version DESC không xác định giữa 8 bản v1) + FE history filter `!d.isActive` (`:419,:426`) ⇒ 8 bản đều active thì 1 hiện card, 7 KHÔNG hiện đâu cả. Slice-by-code của wave này vá đúng bệnh; cân nhắc re-point `Khkk_WfView` hoặc để panel aggregate liệt theo code.
2. 🔴 **Bom deactivate-per-TYPE** (`:339-343`): bấm "Tạo quy trình mới"/"Lưu + kích hoạt" cho BẤT KỲ mã nào thuộc type-10 ⇒ `IsActive=false` CẢ 8 nhóm (seeder không re-set — idempotent per-Code `:610` skip). Runtime tạo phiếu KHÔNG hỏng (pin theo `IsUserSelectable`, guard type `EnsureWorkflowTypeAsync:307-318` không đòi IsActive) nhưng badge/active-slice sai toàn bộ. = D1.
3. Seeder KHKK resolve người duyệt THEO EMAIL, thiếu user ⇒ bỏ slot/cấp/bước + log warning (`DbInitializer.cs:544-548,:597-600`) — DB dev thiếu người thật thì 8 panel có thể hiện bước rỗng/thiếu NV; không phải bug FE.
4. `AwV2_Contract` không có menu-leaf seed (chỉ deep-link `Layout.tsx:178`) — sibling lệch nhẹ, không thuộc scope.
5. Route FE `/system/approval-workflows-v2` KHÔNG PermissionGuard (App.tsx:79-80) — 2-tầng gotcha #82: hiển thị dựa menu-grant, authz thật ở BE controller.
