Files
solution-erp/.claude/workflows/runs/2026-08-08-S182-khkk-awf-mirror-ncc/sub-implementer-frontend-1.md
2026-08-08 01:58:49 +07:00

17 KiB
Raw Blame History

sub-implementer-frontend-1 — S182 W2 (a-h) lane FE

⚠️ TÊN FILE: TASK text ghi sub-implementer-frontend-0.md, RUN-TRACE block + structured-output ghi sub-implementer-frontend-1.md. Chọn -1 (bản RUN-TRACE = bản em main đọc qua subMdPath). Khai ở đây để lead biết.

§0 — Trạng thái lượt (append liên tục, chống-#53)

  • Đọc SPEC FINAL MỤC 2 W2 (a-h) + MỤC 1 R-1..R-6
  • Đọc file đích + đo tiền-đề từ ĐĨA (line-number trong spec có thể lệch)
  • W2a parse kind
  • W2b synthetic slice
  • W2c lockedCode
  • W2d D3=C 6 ô + #4
  • W2e menuKeys ×2 app
  • W2f ngưỡng CEO
  • W2g auto-pin isActive ×2 app
  • W2h comment + subtitle
  • build fe-admin — exit 0
  • build fe-user — exit 0

Lượt 2 (vá-nốt): chẩn-đoán TS trong TASK là ảnh cũ — đo lại từ đĩa thì đã sạch ⇒ 0 dòng code sửa, lượt này là ĐO + CHỨNG. Chi tiết §3.3.

  • tsc thật ×2 app (lệnh trong spec là phép đo CÂM — §3.1)
  • control-dương chứng tsc có răng (§3.2)
  • proof-harness 27/27 + anti-Goodhart (§3.4)
  • đối chiếu code-vs-spec W2a-h (§3.5)
  • mirror proof SHA + 4-place (§3.6)

§1 — Đo tiền-đề từ đĩa (TRƯỚC khi gõ)

# Tiền-đề spec Đo trên đĩa Verdict
P1 :164-172 TYPE_CODE_TO_INT ApprovalWorkflowsV2Page.tsx:164-172 ĐÚNG (TYPE_INT_CONTRACT_SIGNING_PLAN=10 :149) HELD
P2 :299-301 currentType :299-301 overview.data?.types.find(...) ĐÚNG HELD
P3 :357/:620/:650-651/:798/:920-933 TypePanel :357 · Designer :620 · defaultCode :650-651 · POST code :798 · ô Mã :920-933 — ĐÚNG cả 5 HELD
P4 6 ô D3 :1235-1266, :1268-1276, :1277-1285, :1320-1328 #1 :1232-1240 · #2 :1241-1249 · #3 :1250-1258 · #4 :1259-1267 · #5 :1268-1276 · #6 :1277-1285 · #7 :1292-1302 · #8 :1307-1315 · #9 :1320-1328 HELD (số ô #1-#4 lệch ±3 dòng so spec, khớp nội dung)
P5 :944-961 ngưỡng CEO :943-961 ĐÚNG HELD
P6 menuKeys.ts:31-32 = 2 const AwV2_* fe-admin :31-32 + fe-user :31-32 ĐÚNG, 0 key KhkkN HELD
P7 BE const để mirror MenuKeys.cs:127-134 AwV2_KhkkN1..8 (VÀO All) HELD
P8 4-place Layout staticMap fe-admin/Layout.tsx:188 /^KhkkN[1-8]$/ ĐÃ CÓ (K3 S164/S165) ⇒ chỗ-4 KHÔNG cần chạm HELD
P9 khkkGroupWorkflowCode / khkkGroupMenuLabel types/khkk.ts:159-168, 2 app SHA identical b26bf674… HELD
P10 🔴 spec W2g "2 file KhkkCreatePage vốn khác nhau — không cần byte-equal" SAI: sha256sum 2 file = 332986ae6bf1904a4db434c796164caa2768beaaebdc7843dd2b1cdd729d17da BẰNG NHAU + diff 0 dòng LỆCH → sửa mirror BYTE-EQUAL, giữ SHA-pair
P11 WorkflowDto (local) có isActive? KhkkCreatePage.tsx:57-62 = {id, code, name, isUserSelectable}THIẾU isActive phải thêm field (BE có: DefinitionDto.isActive :79)
P12 🔴 spec W2h "thêm ~1 prop subtitle" components/PageHeader.tsx:3-11 chỉ có title/description/actions (bài S37 lặp lần 3) LỆCH → xem §4 FLAG-1
P13 build = phép thử TS thật package.json build = tsc -b && vite build, noUnusedLocals+noUnusedParameters+erasableSyntaxOnly ON HELD

§2 — Thay đổi từng task (đã gõ xong, chờ build)

File chạm (4):

  1. fe-admin/src/pages/system/ApprovalWorkflowsV2Page.tsx (a,b,c,d,f,h)
  2. fe-admin/src/lib/menuKeys.ts (e)
  3. fe-user/src/lib/menuKeys.ts (e)
  4. fe-admin/src/pages/khkk/KhkkCreatePage.tsx + fe-user/… (g, cp → SHA-pair)

W2a — parse kind

  • khkkGroupMatch = typeCode?.match(/^KhkkN([1-8])$/) ?? nullkhkkGroup: number|null
  • selectedTypeInt = khkkGroup !== null ? TYPE_INT_CONTRACT_SIGNING_PLAN : typeCode ? TYPE_CODE_TO_INT[typeCode] : null
  • queryKey GIỮ NGUYÊN (['approval-workflow-v2-overview', selectedTypeInt]) ⇒ 8 panel nhóm + panel type-10 dùng chung 1 ô cache, invalidate làm tươi cả 9. Khai trong comment.
  • Regex CHUỖI (không Number()) — KhkkN03 không lọt (bài học L1 #11).
  • +import { khkkGroupMenuLabel, khkkGroupWorkflowCode } from '@/types/khkk' (không chép chuỗi KHKK-N).

W2b — synthetic slice

  • Tách typeBucket (raw BE) ⟂ currentType (lát cắt). useMemo([khkkGroup, typeBucket]).
  • versions = typeBucket.history.filter(d => d.code === khkkGroupWorkflowCode(g)) · active = versions.find(v=>v.isActive) ?? null · KHÔNG đọc typeBucket.active (1-trong-8).
  • Giữ shape TypeSummaryDto ⇒ TypePanel/Designer không biết mình đang xem nhóm hay loại.

W2c — lockedCode xuyên suốt

  • Page: lockedCode = khkkGroup !== null ? khkkGroupWorkflowCode(khkkGroup) : null
  • TypePanel({type, lockedCode, onSaved})<Designer lockedCode={lockedCode} …>
  • defaultCode = lockedCode ?? DEFAULT_CODE_BY_TYPE[applicableType] ?? 'QT-V2-001' — ô hiện ĐÚNG mã sẽ gửi (không hiện QT-KHKK-V2-001 rồi gửi KHKK-N3).
  • Ô Mã disabled={isEdit || lockedCode !== null} + hint "Mã khoá theo nhóm — version tự tăng."
  • POST code: lockedCode ?? code (bất-biến theo CẤU TRÚC, không dựa vào input disabled).
  • +key={typeCode ?? 'all'} trên <TypePanel> — NGOÀI spec, lý do: chuyển N1→N3 khi Designer đang mở thì cloneFrom/editTarget (state trong TypePanel) còn trỏ bản N1 trong khi lockedCode đã là N3 ⇒ POST đẻ KHKK-N3 mang ruột N1. Remount xoá state. Khai ở đây.

W2d — D3=C danh-sách-chốt

Cờ chung const isKhkk = applicableType === TYPE_INT_CONTRACT_SIGNING_PLAN (1 nguồn, 8 chỗ dùng).

Ô Cờ Xử lý
#1 allowReturnOneLevel disabled={isKhkk} + title chung + checked={entry.…} GIỮ + mờ text
#2 allowReturnOneStep
#3 allowReturnToAssignee
#4 allowReturnToDrafter disabled + checked THẬT + title RIÊNG "KHKK hiện luôn trả về người soạn khi Trả lại — cờ chưa phân nhánh" + KHÔNG mờ (giữ text-slate-700)
#5 allowApproverEditDetails như #1
#6 allowApproverEditBudget như #1
#7 allowApproverSkipToFinal GIỮ hành vi (kể cả checked ép-false của K3 S165); chỉ thay 3 biểu thức trùng → isKhkk, giá trị y hệt
#8 allowApproverFinalize SỐNG — 0 chạm
#9 allowApproverDelete như #1 (rose→slate khi mờ)
  • levelFlagsPayload 0 dòng đổi (echo state thật).
  • +1 dòng giải thích trong hộp tím (chỉ khi isKhkk) — nói rõ ô mờ vẫn hiện đúng giá trị DB, và 2 ô còn tác dụng.

W2e — menuKeys ×2 app

+8 const AwV2_KhkkN1..8 mirror MenuKeys.cs:127-134, sau :32 cả 2 app. Comment viết theo app (fe-admin trỏ Layout.tsx:188; fe-user khai "cố ý không có Designer") — cố ý lệch chữ, không lệch const.

W2f — Ngưỡng CEO

disabled={isEdit || isKhkk} + helper-text "KHKK không dùng ngưỡng máy — NĐUQ quyết…" + POST ceoApprovalThreshold: isKhkk ? null : (…).

W2g — auto-pin lọc isActive ×2 app

  • WorkflowDto (local, :57-62) +isActive: boolean — BE có (AwAdminOverviewDto, đối chiếu DefinitionDto.isActive).
  • find(w => w.isActive && code-match); filter isUserSelectable ở query GIỮ.
  • Sửa 2 câu thông báo cho khớp điều-kiện MỚI (3 vế: đúng mã ∧ đang áp dụng ∧ đã ghim) — nếu không sẽ nói "chưa có quy trình" trong khi nó đang nằm đó ở bản archived (sai-thầm-lặng).
  • cp fe-admin → fe-user, SHA-pair 89dfa607… BẰNG NHAU.

W2h — comment + tiêu đề

  • Vá comment :641-644: câu cũ "2 ô duyệt sớm sống cho MỌI loại" SAI — #7 vẫn khoá cho type-10 từ K3 S165. Viết lại + trỏ tại-chỗ.
  • PageHeader: title = Quy trình duyệt (Mới): {khkkGroupMenuLabel(g)} · description = phụ đề nghiệp vụ (loại + nhóm N{n} + mã khoá + "7 nhóm còn lại không đổi"). Xem FLAG-1.

§3 — Verify (build ×2 + proof)

3.1 🔴 Lệnh acceptance trong spec/TASK là PHÉP ĐO CÂM — phải đổi

npx tsc --noEmit (nguyên văn acceptance R-2 + TASK lượt 2) chạy ra EXIT=0, 0 dòng output ở CẢ 2 app kể cả khi code sai: fe-{admin,user}/tsconfig.json là solution-style {"files": [], "references": [...]} ⇒ tsc không biên dịch file nào rồi thoát 0. Xanh vì CÂM, không phải vì đúng. Lệnh THẬT (đã dùng dưới đây): npx tsc -p tsconfig.app.json --noEmit — hoặc tsc -b (chính npm run build).

# Lệnh Exit Ghi chú
V1 fe-admin$ npx tsc -p tsconfig.app.json --noEmit 0 114 file src/ trong tập biên dịch (--listFiles), có ApprovalWorkflowsV2Page.tsx + types/khkk.ts
V2 fe-user$ npx tsc -p tsconfig.app.json --noEmit 0
V3 fe-admin$ npm run build (tsc -b && vite build) 0 ✓ 1964 modules · index-CmEoYZiH.js 1.761,63 kB
V4 fe-user$ npm run build 0 ✓ 1953 modules · index-CT5Y_xQ4.js 1.677,49 kB

V3/V4 chạy SAU khi đã hoàn tác tiêm-lỗi ⇒ đo đúng trạng thái cuối trên đĩa. Warning PRE-EXISTING (không phải nợ mới): chunk >500 kB · INEFFECTIVE_DYNAMIC_IMPORT realtime.ts · CSS @import order.

3.2 Control-DƯƠNG — chứng phép đo có RĂNG

Tiêm const __ctrl_neg: number = khkkGroupWorkflowCode(3) vào cuối chính file đã ship → tsc nổ 2 lỗi: TS2322 Type 'string' is not assignable to type 'number' + TS6133 '__ctrl_neg' is declared but its value is never read. 🔴 Ý nghĩa: TS6133 (noUnusedLocals) CÒN SỐNG — đúng class lỗi mà 3 chẩn-đoán "declared-never-read" (lockedCode :417 · isKhkk :714 · khkkNotYetHint :717) thuộc về. Nên "0 lỗi" ở V1 = thật sự hết, không phải đo hụt. Hoàn tác: SHA trước/sau dda4be1f9d4dabd…b69b7f71 BẰNG NHAU (byte-identical).

3.3 🔴 Chẩn-đoán trong TASK là ẢNH CŨ — 0 dòng code phải vá

6 lỗi TS mà TASK liệt (thiếu import khkkGroupWorkflowCode/khkkGroupMenuLabel :330/:343/:355 · lockedCode chưa khai ở signature :403/:517 · 3 biến never-read) KHÔNG còn tồn tại trên đĩa: import { khkkGroupMenuLabel, khkkGroupWorkflowCode } from '@/types/khkk':36; lockedCode khai đủ ở CẢ 2 signature (TypePanel :417/:421 · Designer :690/:699); 3 biến đều có chỗ dùng thật (isKhkk 20 call-site, khkkNotYetHint 6, lockedCode 6). ⇒ Đây là IDE-diagnostic chụp giữa lúc lane trước đang gõ (gotcha #68). Lượt này KHÔNG sửa 1 dòng code nào — chỉ đo + chứng. git diff --stat giữ đúng 5 file, 0 file untracked.

3.4 Proof-harness — chạy CHÍNH text đã ship (extract-by-anchor, neo lệch thì script tự chết)

node proof-w2.mjs27 PASS / 0 FAIL:

  • T1 (8/8) dây xuyên suốt AwV2_KhkkN{n}Layout.tsx:188/system/approval-workflows-v2/KhkkN{n} → regex page parse nhóm {n}lockedCode = KHKK-N{n}, đối chiếu đủ 3 nguồn BE+admin+user.
  • T2 (7/7) control-ÂM: KhkkN0 · KhkkN9 · KhkkN03 · KhkkN10 · khkkn1 · "KhkkN1 " · KhkkN — bị chặn ở CẢ 2 regex (regex CHUỖI, không Number()).
  • T3 (2/2) control-âm cho chính T1: key bịa AwV2_KhkkN9/N0 vắng ở cả 3 nguồn.
  • T4/T5/T6 (10/10) D3=C — xem 3.5.

Anti-Goodhart (proof-control.mjs, exit 0): áp ĐÚNG vị ngữ T4 và T5 lên ô #7 (ô cố ý khoá + ép-false từ K3 S165) ⇒ cả hai đều false. Phép đo phân biệt được ô-sống ⟂ ô-khoá và bắt được ép-false; không phải luôn-luôn-PASS.

3.5 Đối chiếu code-vs-spec W2ah

Task Verdict Bằng chứng
W2a parse kind :302-306 regex chuỗi; queryKey giữ nguyên (9 panel chung 1 ô cache — khai tại :300-301)
W2b synthetic slice và là BẮT BUỘC BE active = versions.FirstOrDefault(v => v.IsActive) (:210) — với type-10 nay có 8 bản active song song (BE lane đổi sang tắt per-CODE :336-360) ⇒ đọc bucket.active sẽ sai cho 7/8 panel. history xác nhận KHÔNG lọc IsActive (definitions :118-134 chỉ lọc Type + IsUserSelectable)
W2c lockedCode signature 2 nơi · defaultCode :733 · ô Mã disabled :1020 · POST code: lockedCode ?? code :884 (bất-biến theo CẤU TRÚC). Khớp khoá với vị ngữ w.Code == request.Code bên BE
W2d D3=C đúng danh-sách-chốt 6 ô disabled={isKhkk} + checked={entry.…} THẬT · #4 tooltip riêng + KHÔNG mờ · #7 giữ nguyên · #8 allowApproverFinalize 0 chạm (T4) · levelFlagsPayload 0 dòng đổi
W2e menuKeys ×2 8 const khớp MenuKeys.cs:127-134; khối const 2 app diff 0 dòng, chỉ comment cố ý lệch
W2f ngưỡng CEO disabled={isEdit || isKhkk} :1052 + helper-text :1055 + POST null :891
W2g auto-pin isActive isActive CÓ THẬT trong payload (ToDto :176) — không phải field bịa; 2 câu thông báo đổi sang nói ĐIỀU KIỆN (rỗng-vì-lọc ⟂ rỗng-vì-chưa-có)
W2h comment + tiêu đề subtitle gộp vào description (xem FLAG-1)

3.6 Mirror proof (Pattern 16-bis + SHA)

  • KhkkCreatePage.tsx admin ⟂ user: 89dfa607061cd3d560dcbbdb8fbad157d38cae1786be3cd41a81bd8e9c1d1390 BẰNG NHAU (cặp cp, W2g).
  • types/khkk.ts admin ⟂ user: b26bf67492821b1003c3d523585e2ce563a26b04ef728867689911263e5a33d3 BẰNG NHAU — file không sửađối chứng DƯƠNG cho phép đo hash.
  • 4-place: ① page+types · ② App.tsx:80 route :typeCode đã có (0 route mới) · ③ menuKeys.ts ×2 · ④ Layout.tsx:188 staticMap đã có sẵn từ K3 S164/S165 ⇒ chỗ-4 KHÔNG cần chạm (P8 đúng), regex Layout khớp regex page.
  • Lệch CÓ CHỦ ĐÍCH: fe-user không có ApprovalWorkflowsV2Page.tsx + 0 nhánh AwV2 trong Layout.tsx (màn cấu hình là của admin) ⇒ 8 const bên fe-user là mirror hằng-số thuần, không sinh menu.

§4 — Nợ / FLAG cho lead

  • 🔴 FLAG-A (acceptance hỏng — cần sửa spec): acceptance R-2 ghi "npm run build ×2 app + tsc 0-err". Nếu ai chạy npx tsc --noEmit đúng nguyên văn thì luôn xanh kể cả code sai (§3.1). Đề nghị sửa acceptance thành npx tsc -p tsconfig.app.json --noEmit. Ảnh hưởng mọi wave FE trước/sau, không riêng S182.
  • 🔴 FLAG-B (spec tự mâu-thuẫn — đã chọn nhánh, xin lead ratify): ô #4 allowReturnToDrafter. MỤC 2 dòng d (bảng thi-hành, có neo dòng) ghi "hiển thị state thật (thường TRUE)"; MỤC 3 bảng D3 ghi bản rút gọn "#4 forced-true-disabled". Code đang theo state thật (checked={entry.allowReturnToDrafter}), vì: (1) đo đĩa: mặc định true4 lớp độc lậpApprovalWorkflow.cs:129 = true · EF HasDefaultValue(true) · DTO :232/:465 · FE hydrate ?? true (:213) ⇒ thực tế vẫn hiện TICK, thoả acceptance A2 "ô #4 tick-disabled"; (2) hard-code checked={true} sẽ nói dối đúng ca hàng row có false — cùng class lỗi mà chính D3=C cấm ở 6 ô kia ("CẤM ép false"), chỉ đảo dấu. ⇒ Đọc "forced-true"rút gọn hao nghĩa của "state thật (thường TRUE)". Nếu lead thật sự muốn ép cứng true thì nói 1 câu, sửa 1 dòng — nhưng nên biết là đánh đổi tính trung thực với DB.
  • ⚠️ FLAG-1 (P12, đã xử lý — không còn là nợ): @/components/PageHeader không có prop subtitle (title/description/actions, cả 3 đều ReactNode). Không thêm prop — phụ đề nghiệp vụ gộp vào description. Lý do: PageHeader dùng chung toàn app, đẻ prop cho 1 trang là mở rộng API cho cả trăm call-site. (Bài S37, lặp lần 3 — mỗi lần lại tưởng có subtitle.)
  • ⚠️ Ngoài spec, đã khai: key={typeCode ?? 'all'} trên <TypePanel> (:401) — remount khi đổi nhóm; thiếu nó thì cloneFrom/editTarget còn trỏ bản N1 trong khi lockedCode đã sang N3 ⇒ POST đẻ KHKK-N3 mang ruột N1.
  • ⚠️ Nợ nhận-thức (không chặn): 8 panel nhóm + panel /ContractSigningPlan dùng CHUNG 1 ô cache (queryKey không có khkkGroup — cố ý, vì lọc client-side trên payload đã tải trọn). Invalidate sau khi lưu làm tươi cả 9. Nếu sau này BE nhận code= làm tham số request thì BẮT BUỘC thêm code vào queryKey, nếu không 9 màn dùng chung cache sai CÂM.
  • ⚠️ Chưa chạy (ngoài tay lane FE): acceptance A1/A2b/A3/A4 là UAT động (bấm thật trên 8 leaf, sửa-tại-chỗ ⇒ 204, tạo v02 N1 kiểm 7 nhóm không mất badge). Lane này chỉ chứng được phần tĩnh. A3 phụ thuộc BE lane (tắt active per-CODE) — 2 nửa phải test CÙNG nhau.
  • Không nợ build/mirror: 0 lỗi TS ×2 app · SHA-pair 2/2 · 4-place 4/4 · 27/27 proof + 3 control.