Files
solution-erp/.claude/workflows/runs/2026-07-31-S164-4gd-khkk-fanout/sub-implfe-k4a.md
2026-08-01 09:21:16 +07:00

11 KiB
Raw Blame History

IMPLFE-K4A: DONE

Lane FE K4a — route regex 42 leaf + single-source nhãn nhóm (S166, 2026-08-01)

SPEC PIN: spec-cum2-chi-tiet-31-07-2026.md vá 1/8/15 + sub-invest-fable-b2-cum2.md:37 (K4c-10 regex ship CÙNG K4a) + :58 (acceptance-5 single-source) + :94 (fe-admin KHÔNG nới regex).

§0 Kế hoạch đo trước khi gõ (bài S164: đo đĩa TRƯỚC)

  • Đọc route THẬT fe-user/src/App.tsx (grep KhkkListPage / KhkkCreatePage / WorkflowMatrixViewPage)
  • Đọc query-param THẬT trong KhkkListPage (filter / view)
  • Đọc khuôn Pe-regex trong fe-user/src/components/Layout.tsx
  • Đọc nhãn nhóm gốc DbInitializer.cs dict khkkGroupLabels (Read-only, KHÔNG sửa)
  • Đọc types/khkk.ts ×2 app (nền F-1 7a903cf)

§1 ĐO ĐĨA (ground truth) — TRƯỚC khi gõ 1 dòng nào

1.1 Route THẬT (KHÔNG tin literal spec)

fe-user/src/App.tsx — 3 route KHỚP spec, 0 lệch:

  • :81 /khkk/listKhkkListPage · :82 /khkk/createKhkkCreatePage · :83 /khkk/workflow-matrixWorkflowMatrixViewPage (import :21 từ @/pages/pe/)
  • fe-admin/src/App.tsx :107-110: có /khkk, /khkk/list, /khkk/create, /khkk/:idKHÔNG có /khkk/workflow-matrix, 0 import WorkflowMatrixViewPage ⇒ xác nhận spec :94 (fe-admin cố ý lệch).

1.2 Query-param THẬT (KhkkListPage.tsx)

:61 searchParams.get('filter')FILTER_TO_PHASE (:40-46, key hợp lệ: DangSoanThao|ChoDuyet|DaDuyet|TraLai|TuChoi) · :62 searchParams.get('view') === 'deleted'. ⇒ filter=ChoDuyet / filter=DaDuyet / view=deleted ĐÚNG từng ký tự. 🔴 group CHƯA có consumer ở KhkkListPage lẫn KhkkCreatePage (K4b/K5 lane) — leaf trỏ đúng URL nhưng trang chưa lọc. Không phải lỗi K4a, là bàn giao (ghi ở §4 FLAG-2).

1.3 Active-state (Layout queryMatches :315-336)

TRANSIENT_QUERY_KEYS = {id,q,editHeader,page,phase,awId} :311group/filter/view KHÔNG transient ⇒ 6 URL/nhóm phân biệt được, không sáng nhầm (bài S155 view=approved). KhkkListPage KHÔNG ghi filter ngược lên URL (chỉ đọc lúc init) ⇒ highlight ổn định khi user đổi dropdown.

1.4 Bộ key BE (MenuKeys.cs — Read-only)

KhkkGroupNumbers = [2..8] :72 · KhkkLeafActions = [WfView,List,Create,Pending,Approved,Deleted] :73-74 · KhkkLeaf(n,a) => Khkk_G{n}_{a} :77. 🔴 42 leaf infix = nhóm 2..8 (7×6). Nhóm 1 GIỮ 7 key CŨ KHÔNG-infix (Khkk_G1 + Khkk_WfViewKhkk_Deleted) — đã nằm sẵn trong staticMap ×2 app. ⇒ regex ([1-8]) của spec rộng hơn tập seed 1 nấc (Khkk_G1_List không tồn tại) — giữ nguyên [1-8] theo spec, vô hại + phòng nếu sau này nhóm 1 đổi sang khuôn infix.

1.5 Nhãn nhóm BE (DbInitializer.cs — Read-only)

  • :1960 Khkk_G1 = "1. Thiết bị, máy móc, vật tư phụ (A1)" (+ labelBackfill :2235 cùng chuỗi)
  • :2127-2136 dict khkkGroupLabels nhóm 2..8: 2. Kết cấu, hạ tầng, phá dỡ & DV hỗ trợ thi công (A2-A3) · 3. Hoạt động văn phòng (A4) · 4. Vật tư (B1) · 5. Thầu phụ xây dựng (B2) · 6. Thầu phụ MEP (B3) · 7. BCH công trường (C1-C3) · 8. HĐ chưa ủy quyền — CEO (5.2)
  • Workflow code: ^KHKK-N([1-8])$ContractSigningPlanFeatures.cs:420 (regex BE thật) ⇒ khkkGroupWorkflowCode khớp.

1.6 🔴 TIỀN-ĐỀ LEAD SAI ×2 (đo ra, không suy)

(a) KHKK_GROUP_LABELS — map nhãn ĐÃ TỒN TẠI. types/khkk.ts:129 KHKK_APPROVAL_GROUP_LABELS: Record<number,string> (K2 S164 land), 5 call-site đang dùng: KhkkListPage:243 · KhkkDetailPage:301 · KhkkCreatePage:232,285. Đẻ thêm map tên KHKK_GROUP_LABELS = PHÁ acceptance-5 (thành 2 nguồn). Hơn nữa comment K2 :125-127 đã uỷ quyền sẵn cho K4a: "nếu K4a land nhãn khác thì SỬA Ở ĐÂY (1 const, 2 app), đừng vá rải trong page". Quan hệ đo được: nhãn BE ≡ `${n}. ${KHKK_APPROVAL_GROUP_LABELS[n]}` cho 8/8 nhóm (đối chiếu từng ký tự §1.5 ↔ :130-137). ⇒ giải bằng DERIVE, không hardcode lần 2.

(b) isAdminHidden KHÔNG hề ẩn KeHoachKyKet. fe-admin/Layout.tsx:194-196 = return key.startsWith('Ct_') — chỉ ẩn Ct_*. ⇒ mệnh đề "gỡ KeHoachKyKet khỏi ẩn-admin ⇒ phải nới regex trước" mô tả một điều kiện tương lai KHÔNG tồn tại; ràng buộc thật đã ĐANG hiệu lực NGAY (fe-admin đang hiện KHKK). Comment phải viết theo đĩa, không theo tiền-đề.

§2 THỰC THI

F1 — fe-user/src/components/Layout.tsx (+34 / 0)

Nhánh khkkMatch đặt SAU khối peMatch, TRƯỚC return null cuối resolvePath. Regex ĐÚNG literal spec /^Khkk_G([1-8])_(WfView|List|Create|Pending|Approved|Deleted)$/. 6 route (đọc từ App.tsx thật, không tin literal spec): WfView → /khkk/workflow-matrix?type=10&group={n} · List → /khkk/list?group={n} · Create → /khkk/create?group={n} · Pending → …&filter=ChoDuyet · Approved → …&filter=DaDuyet · Deleted → …&view=deleted. +comment khai lệch fe-admin (không có WorkflowMatrixViewPage, lead chốt 2026-07-27) + khai [1-8] rộng hơn tập seed [2-8] là cố ý + khai group không transient nên active-state phân biệt được.

F2 — fe-admin/src/components/Layout.tsx (+13 / 0, COMMENT-ONLY)

Ràng buộc ngược đặt ngay trên isAdminHidden :194. KHÔNG nới regex (diff 0 dòng xoá, 0 dòng code — chỉ comment). Nội dung viết theo ĐĨA chứ không theo tiền-đề lead (xem §1.6b).

F3 — types/khkk.ts ×2 app (+25 / +25, SHA-pair)

KHÔNG đẻ KHKK_GROUP_LABELS (sẽ thành nguồn thứ 2 — xem §1.6a). Thay bằng 2 export DERIVE trên map sẵn có:

  • khkkGroupMenuLabel(n)`${n}. ${KHKK_APPROVAL_GROUP_LABELS[n]}` , fallback Nhóm {n}. Comment khai TO: không phải nguồn sidebar (sidebar đọc /api/menus/meMenuItems.Label, admin còn override displayLabel Mig 27); dùng cho cây K5 / leaf-title / WfView.
  • khkkGroupWorkflowCode(n) => \KHKK-N${n}`(O-2) — khớp regex BE^KHKK-N([1-8])$ Features.cs:420. 0 call-site cũ bị đụng ⇒ 5 chỗ đang dùng KHKK_APPROVAL_GROUP_LABELSgiữ nguyên hành vi, ô selectN{g} - {label}` không bị lặp số.

§3 CHỨNG (đo bằng lệnh, kèm control-âm)

A. Nhãn byte-exact 8/8 — script trích chuỗi từ DbInitializer.cs (Khkk_G1 tuple :1960 + dict :2127-2136) và từ types/khkk.ts, so "{n}. "+short với BE:

OK N1..N8  ->  --- byte-exact 8/8 khop, 0 lech
CONTROL(âm giả, thêm 'x' vào N4): OK-phep-do-bat-duoc-lech

N2 có "phá dỡ" . Control-âm chứng phép so có RĂNG (không phải happy-path mù).

B. Regex ăn đủ 42 leafnew Function() chạy CHÍNH khối text đã ship trong Layout.tsx (không chép tay), key sinh từ KhkkGroupNumbers/KhkkLeafActions đọc thẳng MenuKeys.cs:

BE nhom=[2,3,4,5,6,7,8] actions=[WfView,List,Create,Pending,Approved,Deleted]
--- 42 leaf, 0 tra null (drop im lang)
CONTROL Khkk_G9_List  -> null
CONTROL Khkk_G2_Bogus -> null
CONTROL Khkk_List     -> null  (đúng: nhóm 1 không-infix đi staticMap, chặn trước regex)

C. SHA-pair types (acceptance-5)

file sha256
baseline trước K4a (cả 2 app) eba649cc6fc7eccfeec5262f2e4d58265dbf8caaac4d53c6bc7120fc8065976d
fe-user/src/types/khkk.ts 7a99b865189480933c57a46a9d9aac9be525d14a78e7743f3a82293b706f22b6
fe-admin/src/types/khkk.ts 7a99b865189480933c57a46a9d9aac9be525d14a78e7743f3a82293b706f22b6
IDENTICAL. Layout.tsx 2 app KHÁC NHAU by-design (fe-user +34 có regex · fe-admin +13 comment-only) — không mirror, đúng chủ đích.

D. Build ×2 PASS, 0 TS error (npm run build = tsc -b && vite build, noUnusedLocals:true ⇒ build LÀ phép thử TS6133 thật, không tin IDE — gotcha #68)

app modules bundle js css time
fe-user 1950 index-B2v0kn5F.js 1,640.21 kB (gzip 414.27) index-my6T4K7V.css 92.62 kB 20.75s
fe-admin 1961 index-84zDBHE-.js 1,723.20 kB (gzip 428.44) index-Ct6-3cYU.css 86.80 kB 8.17s
Warning >500KB + INEFFECTIVE_DYNAMIC_IMPORT realtime.ts = PRE-EXISTING cả 2 app.

E. Ràogit status sau lượt: 4 file của tôi. MenuKeys.cs + DbInitializer.cs M nhưng là lane BE land trước (tôi chỉ Read/Grep, 0 Edit). KhkkListPage.tsx · KhkkCreatePage.tsx · KhkkWorkflowPanel.tsx · KhkkDetailPage.tsx KHÔNG có trong diff. KhkkGroupMenuSeedTests.cs untracked = lane test. 0 commit.

§4 FLAG cho lead (3 — cần quyết, KHÔNG tự quyết)

FLAG-1 (MAJOR, đã tự bẻ — báo để lead soát lại spec): đề bài yêu cầu export KHKK_GROUP_LABELS mới, nhưng đĩa đã có KHKK_APPROVAL_GROUP_LABELS (types/khkk.ts:129, K2 land, 5 call-site). Đẻ map thứ 2 là phá đúng acceptance-5 mà nó phục vụ. K2 còn để sẵn uỷ quyền trong comment :125-127 ("nếu K4a land nhãn khác thì SỬA Ở ĐÂY (1 const, 2 app)"). Tôi đi đường DERIVE. Nếu lead vẫn muốn đúng tên KHKK_GROUP_LABELS thì đó là đổi TÊN của const sẵn có (+ sửa 5 import), không phải thêm const — 1 lượt nhỏ, nói là làm.

FLAG-2 (MAJOR, bàn giao K4b/K5): ?group=n chưa có ai đọc. KhkkListPage:61-62 chỉ đọc filter + view; KhkkCreatePage lấy nhóm từ state default, không đọc URL. ⇒ 42 leaf điều hướng đúng + sáng đúng, nhưng trang hiện toàn bộ nhóm. Câu hỏi treo cho K4b: leaf "Danh sách" của NHÓM 1 (Khkk_List/khkk/list, KHÔNG có group=) nghĩa là "nhóm 1" hay "tất cả"? Nếu K4b coi vắng-group = nhóm 1 thì mất đường xem-tất-cả; nếu coi = tất cả thì leaf nhóm 1 nói dối tên nó. Đây là quyết định UX ⇒ tôi KHÔNG tự chọn, và cố ý KHÔNG sửa 6 dòng staticMap nhóm 1.

FLAG-3 (MINOR, hệ quả đã biết của vá-15/:94): isAdminHidden chỉ ẩn Ct_* ⇒ fe-admin ĐANG hiện root KHKK. Sau khi BE seed land, sidebar admin sẽ có 7 nhóm rỗng (Khkk_G2..G8 render header nhưng 42 leaf con trả null → thân rỗng), vì MenuGroup chỉ xét children.length > 0 chứ không xét con có resolve được không. Nhóm 1 vẫn đủ 6 mục. Nếu lead thấy 7 nhóm rỗng là nhiễu thì có 2 đường (đều 1 lượt nhỏ): (a) nới regex admin, WfView trỏ Designer AwV2_KhkkN{n}; (b) thêm Khkk_G vào isAdminHidden. Không tự chọn.

IMPLFE-K4A-END