diff --git a/.claude/WAL.md b/.claude/WAL.md index 983e356..c467608 100644 --- a/.claude/WAL.md +++ b/.claude/WAL.md @@ -23,9 +23,13 @@ chain: non (be2 phủ anchor 9/9 — khai, không relaunch) · be2 FIXES-15 (style-list tự đánh nhau spec v2.1 → LẬT; BS-3 số thật 12/13 CanDelete=0; T-matrix 13 ca; ghost-wire AuditBehavior). → STYLE-GATE-V2 land (style-gate-v2.md, delta-thắng-nền) + spec v2.2. -[!] B5 /fable-real reviewer — tổng hợp cuối → plan wave FINAL. wf: wf_bc763d7d-aca · - artifact sub-b5-plan-final.md (mâu-thuẫn-chéo 3 tầng + plan per-wave + Đ-dừng) -[ ] B6 hmw fan-out Opus 5 MAX execute toàn plan (per-wave gate + cicd 2 app + prod verify) +[x] B5 GO_WITH_NOTES — 9 mâu thuẫn (6 CHỐT 0 treo; X-5 comment stale file-2 · KHKK :1763 + thiếu nấc-0 null-guard "chép nguyên khuôn cũng là bẫy") · plan 31.9KB sub-b5-plan-final.md + (5 wave block + Đ-1..Đ-10) · spec v2.3 (W5(i) → T-matrix + STATUS 2-ô) +[!] B6 fan-out Opus 5 MAX — TUẦN TỰ: ✅W4 commit 1cc16a2d (4/4 ô máy · 7 dòng · 0 Đ · + lead-gate PASS — nhánh đúng khối + comment khuôn; 3 ô hành-vi DEFERRED-TO-LOCAL-VERIFY + gộp đo ④ cuối) → 🔄 W1 wf_d3466c9d-0fc (bug menu A+B + slice HD-*, 2 file fe-user) + → W2 → W3 → W5. Commit per-wave, PUSH GOM CHỜ (CI runner P0). W2/W5 + reviewer-gate. [!] TỒN CŨ — CỬA NGOÀI-GIỜ (anh gật giờ): dọn workspace runner + dispatch verify CI · (3B) Gitea cron + compact Vietreport_Master + báo VIETREPORT · đo lại A/059 [ ] chờ anh (10+3 tồn): (76)(77)(78)(79) · (89) · (74) · value_protect · lật ô YC-008↔YC-010 diff --git a/.claude/workflows/runs/2026-08-12-S190-soltuon-dieu-chinh-hd/spec-soltuon-dieu-chinh-hd-12-08-2026.md b/.claude/workflows/runs/2026-08-12-S190-soltuon-dieu-chinh-hd/spec-soltuon-dieu-chinh-hd-12-08-2026.md index f19eaa8..c2f2ab0 100644 --- a/.claude/workflows/runs/2026-08-12-S190-soltuon-dieu-chinh-hd/spec-soltuon-dieu-chinh-hd-12-08-2026.md +++ b/.claude/workflows/runs/2026-08-12-S190-soltuon-dieu-chinh-hd/spec-soltuon-dieu-chinh-hd-12-08-2026.md @@ -118,11 +118,11 @@ nào phụ thuộc layout mới; W5 cuối — BE + test-before). bật `pendingMe=1` ⇒ list **ÍT HƠN** list thường + HĐ không-đến-lượt **VẮNG**; control-dương: user ngoài luồng ⇒ **0 dòng** (không phải cả list); ✅(ii) leaf "Đang duyệt" ở lại `/my-contracts`; ✅(iii) build fe-user PASS. -- **W5:** ✅(i) `dotnet test SolutionErp.slnx` PASS **680 → 680+N** (pin số; cập nhật ĐÚNG 1 row test - `docs/STATUS.md`), test-before liệt đủ ca: TraLai(98)/TuChoi(99) Drafter xoá được · DangSoanThao - Drafter xoá được (như cũ) · **ChoDuyet(10) CẤM kể cả Drafter** (khuôn KHKK — NGƯỢC v1) · - **DaPhatHanh(9) CẤM mọi đường** · user thường xoá HĐ người khác ⇒ **403** (sửa lỗ cũ — khai rõ) · - V1 nháp (2) vẫn xoá được (chống vá quá tay); ✅(ii) changelog: xoá xong đọc-ngược DB ra **đúng 1 +- **W5:** ✅(i) 🔄 v2.3 (B5 X-1/X-2): `dotnet test SolutionErp.slnx` PASS **680 → 680+N** — ca test = + **T-matrix T-1..T-13 + phản-ca T-14 trong `style-gate-v2.md §C` (NGUỒN DUY NHẤT — danh-sách ca + cũ của v2 đã RETIRED tại đây, đừng đọc bản git-history)**; STATUS lật **2 ô**: dòng session + DELTA `680→680+N` tại commit + row canonical `:476` = DEFERRED-TO-DEPLOY (chỉ chép từ + cicd-verify — deploy đang CHỜ); ✅(ii) changelog: xoá xong đọc-ngược DB ra **đúng 1 dòng** Action=Delete (0 dòng = dẫm T26); ✅(iii) 🔄 v2.1: owner-check trong handler — test stranger-403 (khuôn `ContractSigningPlanCrudTests` stranger-case) + KHÔNG thêm `[Authorize(Policy` mới vào DELETE (`grep` DELETE-action giữ nguyên shape — policy = W8); diff --git a/.claude/workflows/runs/2026-08-12-S190-soltuon-dieu-chinh-hd/sub-b5-plan-final.md b/.claude/workflows/runs/2026-08-12-S190-soltuon-dieu-chinh-hd/sub-b5-plan-final.md index 341c9c6..e419ab4 100644 --- a/.claude/workflows/runs/2026-08-12-S190-soltuon-dieu-chinh-hd/sub-b5-plan-final.md +++ b/.claude/workflows/runs/2026-08-12-S190-soltuon-dieu-chinh-hd/sub-b5-plan-final.md @@ -1,20 +1,130 @@ # B5 — PLAN FINAL "SOLTUON-12-08-2026" (reviewer /fable-real, tổng hợp cuối YC-029) -> Nguồn: spec v2.2 (`spec-soltuon-dieu-chinh-hd-12-08-2026.md`) ⟂ `style-gate-v2.md` ⟂ `anh-mo-ta.md` + bối cảnh B1/B2/B3. -> Trạng thái: ĐANG GHI — append từng phần (chống #53). +> Nguồn: spec v2.2 (`spec-soltuon-dieu-chinh-hd-12-08-2026.md`) ⟂ `style-gate-v2.md` ⟂ `anh-mo-ta.md` + bối cảnh B1 §PROPOSE / B2-synthesis; đối chiếu nền `sub-style-{fe,be}-b3.md` chỗ delta chạm. Mọi neo `file:line` trong file này B5 ĐÃ MỞ ĐĨA ĐO TƯƠI 2026-08-12 (không chép từ artifact). ## Verdict tổng -(đang ghi) +**B5-PLAN: GO_WITH_NOTES — 9 mâu thuẫn/lỗ soi ra (6 CHỐT tại chỗ, 3 ghi-nhận, 0 treo 50/50), plan 5 wave đủ worker-cầm-làm.** + +| Wave | Verdict | Ghi chú 1 dòng | +|---|---|---| +| W4 | **GO** | Neo :176/:188/:189 đo tươi ĐÚNG; 1 file ~6 dòng; C-13 RANGE có răng | +| W1 | **GO** | Gate `ct` phải ĐỦ 3 vế (X-4); +1 việc mới comment stale thứ 2 (X-5) | +| W2 | **GO** | Q1/Q2 nhánh-đã-chọn; bổ quyết định `bodyMaxHClass` (X-3) + cơ chế lọc cây `stage3TypeFilter` opt-in | +| W3 | **GO** | Đổi-endpoint `/contracts/inbox` (0-param, đo đĩa :41-43); CẤM gửi param `pendingMe` lên server | +| W5 | **GO** (test-before) | T-matrix nguồn duy nhất (X-1); T-13 Fact riêng (X-9); STATUS 2-ô, canonical treo cicd-verify (X-2) | + +Điểm mấu chốt cho lead: (i) spec §③ W5(i) danh-sách ca + câu "ĐÚNG 1 row STATUS" là **dư-lượng v2.1** — plan này thay bằng T-matrix + 2-ô; đề nghị lead đánh dấu 2 chỗ đó trong spec khi rảnh (B5 không sửa spec — ranh). (ii) Toàn bộ Q3/Q4/Q5 + W8/W9 nằm NGOÀI plan — Đ-8 chặn worker chạm. (iii) Deploy CHỜ ⇒ mọi ô prod-only = DEFERRED-TO-DEPLOY, wave dừng ở local-verify + commit, push gom sau. ## Mâu thuẫn còn sót (X-1..) -(đang ghi) +> Soi CHÉO 3 tầng {spec v2.2 ⟂ style-gate-v2 ⟂ T-matrix} + đối chiếu ĐĨA (mọi neo dưới đây B5 đã mở file đo lại, không chép từ artifact). 9 mục: **6 CHỐT** (đổi hành-động, đã nhập vào PLAN) · **3 ghi-nhận** (không đổi hành-động). + +- **X-1 [CHỐT — T-matrix thắng] spec §③ W5(i) còn giữ danh-sách ca CŨ đã bị §C thay.** Spec :121-125 vẫn liệt "V1 nháp (2) vẫn xoá được…" trong khi chính spec §② :87-88 tuyên *"Ca test W5 = T-matrix T-1..T-13 + phản-ca T-14 (thay danh sách ca ở ✅W5(i) cũ — ca 'V1 nháp 2' đã bị chứng TRÙNG)"* và be2-R7 (style-gate §B.5) chứng phase-2 TRÙNG T-1 → thay bằng **T-4 DangGopY(3)**. Chốt: worker W5 CHỈ dùng T-1..T-14; câu-chữ §③ W5(i) coi là dư-lượng v2.1. *(Trả lời câu hỏi giao việc: T-4 phase 3 KHÔNG đá allow-list — đo đĩa `ContractPhase.cs:17-27`: DangGopY=**3** < DangInKy=**5** ⇒ nằm TRONG ` **Thứ tự BẮT BUỘC: W4 → W1 → W2 → W3 → W5** (X-8: không delta nào đổi tiền-đề). Mỗi wave: local-verify + commit riêng; **PUSH GOM SAU** (deploy CHỜ — CI runner P0). Q1/Q2 = nhánh-đã-chọn (làm theo, anh bác thì đổi); **Q3/Q4/Q5 CẤM đụng** (chờ anh). Ô hành-vi đo theo spec §④ nấc-đo-local; ô không dựng được dữ liệu ⇒ `DEFERRED-TO-DEPLOY` ghi `run.md`, CẤM tick. Ô máy: nối `;` không `&&`, `grep -c … || true`, exit qua `${PIPESTATUS[0]}` (§D đầu bảng). + +### W4 — Admin Designer 7 leaf (fe-admin, ≈6 dòng) + +- **INPUT đọc:** spec §② W4 + style-gate §D ô C-13, C-1a, C-2, C-5. Không cần file B3. +- **File đích (DUY NHẤT):** `fe-admin/src/components/Layout.tsx`. +- **Việc theo thứ tự:** + 1. Import named `findContractWfByTypeCode` từ `@/lib/contractWorkflowFamily` (hàm SẴN `fe-admin/src/lib/contractWorkflowFamily.ts:77-80`; family const `:45`). + 2. TRONG khối `if (awV2Match) {` (baseline `:176`), SAU nhánh KhkkN `:188` và TRƯỚC `}` `:189`, thêm: ``if (findContractWfByTypeCode(code)) return `/system/approval-workflows-v2/${code}` `` — `code` = phần sau `AwV2_` (vd `HdTP`), GIỮ NGUYÊN casing. + 3. Kèm comment 2-3 dòng kiểu Pattern 16-bis như khối `:181-187` sẵn có (BE seed leaf mà resolvePath thiếu nhánh ⇒ MenuLeaf DROP im lặng — gotcha #50). + 4. KHÔNG đụng: nhánh `code === 'Contract'` `:178` (nhánh chết, 0 seed) · `DbInitializer` · họ `Khkk_*` (fe-admin cố ý không có WorkflowMatrixViewPage — `:185-187`). +- **Ô acceptance:** C-13 RANGE (`open < hit < close` và `hit > dòng KhkkN`; mốc 176/188/189) · spec ✅W4(i) sidebar đủ **17 leaf** (2 NCC + 8 KHKK + 7 "QT Duyệt HĐ — …" Order 11..17, đo local ④) · (ii) click "QT Duyệt HĐ — Thầu phụ" → Designer khoá mã `HD-TP`, history CHỈ HD-TP · (iii) control-âm: `KhkkN1` + `DuyetNcc` mở đúng trang cũ · (iv) C-2 build fe-admin PASS + C-1a eslint fe-admin ≤42 (37E/5W) KHÔNG TĂNG. +- **Bẫy né:** chèn NGOÀI khối awV2Match = leaf chết câm (#50) · đừng "tiện tay" xoá nhánh chết `:178` (ngoài lệnh). +- **DoD:** build + eslint delta 0 + commit `[CLAUDE] FE-Admin: YC-029 W4 — sidebar Designer 7 leaf QT Duyệt HĐ (resolvePath AwV2_Hd*)`. + +### W1 — Danh-tính menu + slice per-loại (fe-user) + +- **INPUT đọc:** spec §② W1 (a-d) + style-gate §A.2 (fe1-R2), §A.7 (fe1-R9) + §D ô C-1a/b, C-2, C-6, C-7, C-8, C-9 + X-4/X-5 trên đây. +- **File đích (2):** `fe-user/src/components/Layout.tsx` · `fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx`. +- **Việc theo thứ tự:** + 1. **(Layout — accordion `:470-485`)** GIỮ nhánh Pe (`startsWith('/purchase-evaluations')`, `:476-478`) NGUYÊN VỊ TRÍ ĐẦU. THÊM ưu tiên `ct`: đọc `params.get('ct')`, nếu khớp `/^[1-7]$/` → `INT_TO_TYPE_CODE[Number(ct)]` (map SẴN `:27-29`) → `setExpandedCtCode` rồi return. Nhánh `?type=` cũ (`:479-484`) CHỈ còn chạy khi `['/my-contracts','/contracts/new','/inbox'].includes(location.pathname)` — allowlist EXACT (diệt bug A `?type=3` của workflow-matrix + cross-talk `Hdc_*` `/hard-copies?type=1..7`). + 2. **(Layout `:152`)** Approved: `?type=${typeInt}&phase=9` → thêm `&view=approved` (khuôn PE; trang bỏ qua param lạ — H-4 HELD). + 3. **(Layout `:141-142`)** viết lại comment stale: "cả 7 loại đều ?type=3 (HĐ chung 1 bộ workflow V2)" → nêu 7 bộ per-loại `HD-*` @S188, `ct` = danh-tính leaf (ô dương C-6 cần chứa ≥1 token `7 bộ|per-loại|S188`). + 4. **(Layout)** 🔴 KHÔNG thêm `ct`/`view`/`pendingMe` vào `TRANSIENT_QUERY_KEYS` `:350` — 3 param là DANH-TÍNH leaf, cho vào set là leaf mất phân biệt active (C-7 canh =0). Đây chính là cơ chế diệt bug B: `view` nằm NGOÀI set ⇒ leaf List tắt khi URL có `view=approved`. + 5. **(WfViewPage)** const cục-bộ `INT_TO_HD_CODE: Record` 7-entry — **MỞ `src/Backend/SolutionErp.Domain/Contracts/ContractApprovalWorkflowFamily.cs:33-42` CHÉP ĐÚNG** mapping ContractType→mã (⚠️ bản-sao-thứ-3 của họ; token gạch-nối `HD-NT-NCC`/`HD-NT-DV`; comment ghi nguồn + "drift-check = C-9"). KHÔNG import từ fe-admin (`contractWorkflowFamily.ts:16-18` tự khai fe-admin-ONLY). + 6. **(WfViewPage)** nhánh `ct` mirror khuôn `group` `:84-99`: `const rawCt = searchParams.get('ct')`; gate **ĐỦ 3 VẾ (X-4)**: `typeInt === 3 && rawCt !== null && /^[1-7]$/.test(rawCt)` → `hdCode = INT_TO_HD_CODE[Number(rawCt)]`, else `null`; `workflows = khkkGroup !== null ? …(giữ nguyên)… : hdCode !== null ? allWorkflows.filter(wf => wf.code === hdCode) : allWorkflows`. `ct` rác/thiếu ⇒ KHÔNG lọc (giữ hành vi cũ). 🔴 KHÔNG thêm `ct` vào `queryKey` (lý-do ghi sẵn `:88-91` — client-side slice của cùng payload). + 7. **(WfViewPage `:58-60` — X-5)** sửa comment stale thứ 2 "HĐ dùng CHUNG 1 bộ workflow V2…" → 7 bộ per-loại, `ct` slice. +- **Ô acceptance:** spec ✅W1(i) tái-hiện A local ④ (mở nhóm 01 → "Luồng duyệt" → accordion GIỮ 01; control-âm `ct=5` → nhóm 05) · (ii) tái-hiện B (`?type=1&phase=9&view=approved` → CHỈ leaf Đã-duyệt sáng) · (iii) vòng `ct ∈ 1..7` ≥1 quy trình ĐÚNG HỌ + 0 dòng khác họ + `ct=1` VẮNG `QT-HD-V2-001` + control `ct=99` KHÔNG lọc *(mapping int→code SAI thì C-9 vẫn xanh — chính vòng (iii) này mới khoá mapping)* · C-8 vế-1 `get('ct')` ≥1 (mới) + vế-2 `workflow-matrix?type=3&ct=` ≥1 (canh-thoái-lui, đã xanh baseline) · C-9 = 7 mã distinct · C-6 2 vế · ô X-5: `grep -ci "chung 1 bộ" fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx` = 0 · C-7 = 0 · C-1a/b eslint fe-user ≤47 (41E/6W), `Layout.tsx` ≤4E **0 ruleId mới** (lỗi sẵn :478 nằm đúng khối accordion — đổi hình thì lỗi phải ≤ cũ) · C-2 build fe-user. +- **Bẫy né:** allowlist pathname phải EXACT — `startsWith('/contracts')` sẽ nuốt luôn `/contracts/workflow-matrix` (tái sinh bug A) · KHÔNG sửa `:151` (WfView ĐÃ đúng `type=3&ct=` — v2 GIỮ type=3, chỉ THÊM logic đọc) · grep có `?` phải `-F` (B2 cụm 5). +- **DoD:** build + eslint + 3 ô hành-vi local + commit `[CLAUDE] FE-User: YC-029 W1 — danh-tính menu 7 loại HĐ (accordion ct + view=approved + WfView slice HD-*)`. + +### W2 — HOÁN ĐỔI 2 khung: cây trái 400px + list phải 360px (host fe-user; cặp mirror ×2 app) + +- **INPUT đọc:** spec §② W2 + Q1/Q2 (nhánh-đã-chọn) + style-gate §A.1 (SF-26), §A.3 (fe1-R3 JSDoc), §A.4 (fe1-R4 Select native), §A.5 (fe1-R5 header) + §D ô C-2, C-3, C-5, C-10, C-11 + X-3 trên đây. +- **File đích (3 + 2 copy):** host `fe-user/src/pages/contracts/MyContractsPage.tsx` (CHỈ fe-user — fe-admin không có trang này) · cặp mirror `fe-user/src/hooks/usePipelineStages.ts` + `fe-user/src/components/pipeline/PipelineTreePanel.tsx`, sửa xong **COPY NGUYÊN BYTE** sang `fe-admin/src/...` cùng đường dẫn. +- **Việc theo thứ tự:** + 1. **(hook — cặp mirror)** `BuildStagesOptions` thêm 2 opt-in **FLAT trong opts** (CẤM bịa tầng `stage3Options` — F-4 lens-1): + - `onOpenContract?: (id: string) => void` — JSDoc theo khuôn `:185-192` sẵn, BẮT BUỘC có mệnh đề *"hông truyền ⇒ giữ nguyên `navigate('/contracts/…')` — 6 host còn lại 0 đổi hành vi"* (fe1-R3, gate `grep -cE "hông truyền"` ≥1 per prop mới); + - `stage3TypeFilter?: number` — lọc leaf GĐ3 theo ContractType; JSDoc cùng chuẩn ("hông truyền ⇒ đủ mọi loại"). + Site leaf GĐ3 `:368-378`: `onOpen: opts.onOpenContract ? () => opts.onOpenContract!(contractId) : () => navigate(\`/contracts/${contractId}\`)` (khuôn `:228-230`/`:244`); điều kiện push leaf thêm vế `(!opts.stage3TypeFilter || !c || c.type === opts.stage3TypeFilter)` — **`c` vắng (HĐ ngoài chỉ mục 200, `:371-373`) thì leaf VẪN HIỆN khi filter bật** (không đủ dữ liệu phân loại — thà thừa; ghi comment quyết định này). + 2. **(panel — cặp mirror)** `PipelineTreePanel.tsx:111` mở rộng Pick: `'stage2Content' | 'stage2Count' | 'onOpenKhkk' | 'onOpenPe' | 'onOpenContract' | 'stage3TypeFilter'` (GIỮ tên prop `stage2Options`). + 3. **(host — grid)** `MyContractsPage.tsx:153`: `lg:grid-cols-[320px_1fr_360px]` → `lg:grid-cols-[400px_1fr_360px]` (khuôn host `KhkkListPage.tsx:171`). **Panel-1 TRÁI = khối MỚI:** thanh đầu = Select loại HĐ (wrapper `ui/select.tsx` NATIVE `:4-19` — fe1-R4, CẤM kéo radix; option đầu "Tất cả loại HĐ" cho ca URL trần; default = `?type=N` của URL, sync khi URL đổi; đổi Select CHỈ đổi state lọc cây — KHÔNG navigate/không đổi list) + ` setSelectedId qua ?id=, stage3TypeFilter: selectLoại || undefined }} />`; cột wrapper TỰ CUỘN (X-3/SF-26 — mặc định panel = 2 cuộn lồng). Nếu panel-1 cần header bar riêng: dùng đúng chuỗi class `KhkkListPage.tsx:157` (fe1-R5). + 4. **(host — panel-3 PHẢI 360px)** = khối DANH-SÁCH HĐ hiện tại CHUYỂN CHỖ NGUYÊN CHỨC NĂNG (search + empty-state + lọc `phase=9`/`deleted=1` hiện có — Q1: KHÔNG xoá khối UI anh không bảo xoá; HĐ tạo-trực-tiếp-ngoài-gói luôn thấy được ở đây). **Search ĐỔI client→server**: nối ô tìm vào param `search` của `GET /contracts` (SẴN `ContractsController.cs:19` — nói thẳng: đây là ĐỔI hành-vi, F-10 lens-2). + 5. **(host — panel-2 GIỮA)** giữ nguyên detail/placeholder; click leaf cây → chọn vào panel-2 qua `?id=` — URL Ở LẠI `/my-contracts`. Xoá render cây cũ `:242` (`{!selectedId && `) + lọc `type` CLIENT-SIDE (`c.type === Number(typeParam)`), ép shape về dạng list thường (khuôn PE `:98`: `{ items, total: length, page: 1, pageSize: length }`); ẩn/khoá phân trang + search-server ở chế độ này (inbox không phân trang — payload nhỏ, đúng hiện trạng `InboxPage.tsx:103`). Comment khai lệch-W7 CHẤP NHẬN + `[carry:w7b-inbox-param]` (nâng BE là việc khác đợt). +- **Ô acceptance:** spec ✅W3(i) so-số 2 chiều local ④ (user có ≥1 HĐ đến-lượt + ≥1 không-đến-lượt: bật `pendingMe=1` ⇒ list ÍT HƠN + HĐ không-đến-lượt VẮNG; control-dương: user ngoài luồng ⇒ **0 dòng**, không phải cả list) · (ii) leaf "Đang duyệt" ở lại `/my-contracts` · C-7 = 0 (pendingMe KHÔNG vào TRANSIENT — danh-tính leaf) · C-2 build fe-user + eslint không tăng. +- **Bẫy né:** 🔴 CẤM gửi `?pendingMe=1` lên `GET /contracts` — BE KHÔNG có param này, ASP.NET NUỐT im lặng ⇒ list y hệt list thường (B2 BLOCKER ❷; đổi-endpoint là khuôn đúng) · queryKey phải tách trục `pendingMe` (2 nguồn dữ liệu khác nhau — đừng dùng chung key gây cache lẫn). +- **DoD:** build + đo 2 chiều + commit `[CLAUDE] FE-User: YC-029 W3 — leaf Đang duyệt ở lại /my-contracts (pendingMe đổi-endpoint /contracts/inbox)`. + +### W5 — Đường XOÁ đồng-khuôn KHKK (BE nhỏ + FE, 0-mig, TEST-BEFORE) + +- **INPUT đọc:** spec §② W5 (a)-(e) + style-gate §B TRỌN (be2-R1/R2/R7/R13/R14) + §C T-matrix + §D khối BE (**MỞ `sub-style-review-b4-be2.md` mục "Ô đo phải sửa" `:198` chạy đúng bảng C1-C21 đã sửa**) + §D ô FE C-12, C-MIRROR-KHAI, C-3, C-5 + X-1/X-2/X-9 trên đây. +- **File đích (4):** `src/Backend/SolutionErp.Application/Contracts/ContractFeatures.cs` (khối DELETE `:885-902`) · `src/Backend/SolutionErp.Api/Controllers/ContractsController.cs` (`:78-81`) · test MỚI `tests/SolutionErp.Infrastructure.Tests/Application/ContractDeleteGuardTests.cs` (tên CỤ THỂ — CẤM glob) · cặp FE `fe-{user,admin}/src/components/contracts/ContractDetailContent.tsx` (sửa fe-user → copy-byte fe-admin). +- **Hiện trạng đã đo (B5, đừng đo lại):** `DeleteContractCommand(Guid Id)` KHÔNG Reason/owner-check/changelog; guard cũ `:896` `Phase >= DangInKy`; controller DELETE `:78` KHÔNG policy (class `[Authorize]` trần `:13`); `Remove` = SOFT-delete qua `AuditingInterceptor.cs:54-62` (H-6 — chỉ assert); FE DTO SẴN `drafterUserId` (`fe-user/src/types/contracts.ts:154`); EF `ContractChangelogConfiguration.cs:20` ContextNote HasMaxLength(2000). +- **Việc theo thứ tự (test-before):** + 1. **Viết `ContractDeleteGuardTests.cs` TRƯỚC** — fixture private-per-file theo nếp Contract-family (`ContractSigningPlanCrudTests.cs:77` `KhkkFixture` / `ContractCatalogTests.cs:83` — be2-R14, cái thứ 6 cùng nếp là ĐÚNG khuôn), fixture **PHẢI wire `AuditingInterceptor`** (thiếu = assert soft-delete/changelog RỖNG-NGHĨA). Ca = **T-1..T-13 ĐÚNG BẢNG §C, mỗi ca 1 `[Fact]` — T-13 viết RIÊNG (X-9: gộp vào T-1 là vỡ ô `[Fact] ≥13`)**. Chi tiết bẫy per-ca: T-2/T-3 Dev 0-row TraLai/TuChoi ⇒ fixture seed THẲNG `Phase` (§D dữ-liệu đo tay) · T-9 fake `ICurrentUser` UserId=null ⇒ expect `UnauthorizedException` (nấc-0) · T-10/T-11 khoá THỨ TỰ ném exception 404→403→409 · T-1 assert 3-trong-1 (khuất query thường · `IgnoreQueryFilters` thấy IsDeleted/DeletedBy · changelog đúng 1 dòng ContextNote/PhaseAtChange/UserId). Chạy chứng ĐỎ (guard chưa có) — KHÔNG commit lúc đỏ. **T-14 phản-ca: KHÔNG VIẾT test reflection-authz cho DELETE** (2 chiều đều cấm — chiều phủ-định pin lỗ, ĐỎ oan khi W8 mở). + 2. **BE handler viết lại (`ContractFeatures.cs:889-902`):** thứ tự trong handler = + (0) nấc-0 actor-guard khuôn `PeSoftDeleteFeatures.cs:56-58`: `!currentUser.IsAuthenticated || currentUser.UserId is null → UnauthorizedException` — 🔴 khuôn KHKK `:1763` KHÔNG có nấc này (`DrafterUserId == UserId` để null==null lọt), ĐỪNG chép nguyên hình; + (1) load + `NotFoundException` (giữ `:893-894`); + (2) owner-check khuôn KHKK `:1763-1766`: `isOwner = entity.DrafterUserId == currentUser.UserId` (an toàn vì nấc-0 đã chặn UserId null) · `isAdmin = currentUser.Roles.Contains(AppRoles.Admin)` · `!isOwner && !isAdmin → ForbiddenException` (T-7/T-8; KHAI trong comment: sửa lỗ sẵn có #82 — trước đây user thường xoá được HĐ người khác); + (3) phase guard THAY `:896-897`: allow `Phase < DangInKy(5)` **∪** `{TraLai(98), TuChoi(99)}`, còn lại `ConflictException` message liệt rõ trạng thái cấm (chặn ChoDuyet=10 kể cả Drafter — khuôn KHKK, approver-delete = Q4; chặn DaPhatHanh=9 mọi đường — đã gen mã; T-4/T-5/T-6; 🔴 bẫy vá-quá-tay: ĐỪNG chép pattern `is not (DangSoanThao or TuChoi or TraLai)` của KHKK — allow-list HĐ RỘNG HƠN, gồm cả 1/3/4 legacy `, UserId, UserName, ContextNote = reason })` **SAU** (bẫy T26 — đảo là cascade DETACH ⇒ 0 row im lặng, nguyên lý ghi tại KHKK `:1787-1790`; field-set mirror 2 site ghi-tay `CreateContractFromSigningPlanFeatures.cs:247` / `CreateContractFromEvaluationFeatures.cs:121`). 🔴 **GHOST-WIRE be2-R13:** comment `ContractChangelog.cs:10-12` nói "AuditBehavior tự populate" — **file KHÔNG TỒN TẠI**, changelog là GHI TAY; nhân tiện sửa/chú thích comment đó (cùng lượt chạm, hết nói dối). + 3. **Controller `:78-81`:** thêm `[FromQuery] string? reason = null` → `new DeleteContractCommand(id, reason)` (khuôn KHKK controller `:153-159`) — 🔴 **KHÔNG chép `[Authorize(Policy = "….Delete")]`** từ khuôn (be2-R1 LẬT: GIỮ shape class-`[Authorize]`-trần, C10 kỳ vọng **= 0**; SB-14 → comment khai VÌ SAO CHƯA gắn: chốt (82) W8 HOÃN + BS-3/be2-R2 đo LocalDB key `Contracts` = 12/13 role CanDelete=0, Drafter=0 ⇒ gắn = khoá oan mất nút xoá nháp; đánh `[carry:w8-delete-policy]`; điều kiện mở W8 = policy + grant Drafter CÙNG commit + đo lại prod). + 4. **FE `ContractDetailContent.tsx` (fe-user rồi copy-byte fe-admin):** port khuôn THANH NÚT ĐÁY `fe-user/src/components/khkk/KhkkDetailContent.tsx:1076-1097` (sticky bottom-0 + `window.confirm` message CÓ ĐÍCH nhắc chuyển sang mục "Đã xóa" + `Trash2` + `isPending` disable) — 🔴 KHÔNG lấy `KhkkWorkflowPanel` làm khuôn (phần xoá trong đó = CODE CHẾT S175). Điều kiện render nút = `phase ∈ allow-list` (mirror BE: `<5 || 98 || 99`) **VÀ** (`isDrafter` khuôn null-safe `PeDetailTabs.tsx:141`: `currentUser?.id != null && contract.drafterUserId === currentUser.id` **||** `isAdmin`). Mutation `DELETE /contracts/{id}?reason=…`; xong: toast + rời detail về list. GIỮ `47 · fe-admin >42) hoặc file-wave `Layout.tsx` >4E hoặc xuất hiện **ruleId MỚI** ⇒ DỪNG. CẤM `eslint-disable` để ép xanh (rời tập-đo). +- **Đ-2 — SHA-pair không khớp được:** copy-byte rồi mà 2 bản mirror vẫn lệch SHA (nghi BOM/CRLF/format-on-save) ⇒ DỪNG báo kèm `sha256sum` 2 bên. CẤM sửa `.gitattributes`/editor-config để ép. +- **Đ-3 — T-matrix FAIL không rõ nguyên nhân:** đặc biệt T-1 changelog 0-row DÙ đã Remove-trước-Add-sau (nghi cascade timing khác KHKK) hoặc T-9 không ném đúng exception ⇒ DỪNG kèm log. CẤM nới assert cho xanh. +- **Đ-4 — đụng file NGOÀI danh sách wave:** kể cả "tiện tay" refactor/format file lân cận ⇒ DỪNG hỏi trước khi commit (reviewer gate đánh scope-drift = FAIL). +- **Đ-5 — spec/plan LỆCH so đĩa:** neo `file:line` lệch >±15 dòng, hàm/khuôn không tồn tại, hình khuôn khác mô tả ⇒ DỪNG báo (mọi neo trong plan này B5 đã đo tươi 2026-08-12 — lệch nghĩa là cây đã trôi giữa chừng). +- **Đ-6 — mất control W1(iii):** Dev VẮNG `QT-HD-V2-001` (ô "vắng-vì-lọc" mất nghĩa) ⇒ đánh dấu ô mất-control trong `run.md` + DỪNG hỏi (dữ liệu này dính Q5 đang chờ anh — CẤM tự tạo/xoá quy trình tay). +- **Đ-7 — ô hành-vi không dựng được dữ liệu local:** áp đúng spec §④.4: đánh `DEFERRED-TO-DEPLOY` công khai trong `run.md`, CẤM tick, CẤM lặng lẽ bỏ ô. (Không phải leo thang — nhưng là đường thoát HỢP LỆ duy nhất.) +- **Đ-8 — chạm ranh CHỜ-ANH:** mọi việc dính Q3 (port 3 món KHKK/DTO nở) · Q4 (approver-delete `AllowApproverDelete`) · Q5 (QT-HD-V2-001) · W8 (policy DELETE/authz diện rộng) · W9 (đã HỦY) ⇒ DỪNG ngay — spec đã tách các món này KHỎI wave. +- **Đ-9 — baseline test lệch:** `dotnet test` TRƯỚC khi sửa ra số ≠ 680 ⇒ DỪNG báo (số pin từ B4 có thể trôi bởi commit khác chen giữa; cần lead chốt lại mốc trước khi tính 680+N). +- **Đ-10 — build/test đỏ vì nguyên nhân NGOÀI wave** (localdb/seed hỏng, package restore fail) ⇒ DỪNG báo, CẤM `--no-verify` mọi tình huống. diff --git a/.claude/workflows/runs/2026-08-12-S190-soltuon-dieu-chinh-hd/sub-b6-w1-worker.md b/.claude/workflows/runs/2026-08-12-S190-soltuon-dieu-chinh-hd/sub-b6-w1-worker.md new file mode 100644 index 0000000..36be60a --- /dev/null +++ b/.claude/workflows/runs/2026-08-12-S190-soltuon-dieu-chinh-hd/sub-b6-w1-worker.md @@ -0,0 +1,16 @@ +# sub-b6-w1-worker — implementer-frontend (B6, wave W1, YC-029) + +Run: 2026-08-12-S190-soltuon-dieu-chinh-hd · plan SOLTUON-12-08-2026 +Ranh: Write/Edit CHỈ {fe-user/src/components/Layout.tsx, fe-user/src/pages/pe/WorkflowMatrixViewPage.tsx, file này} + +## Việc đã làm (7 mục) + +(đang chạy) + +## Ô acceptance (kết quả từng ô) + +(đang chạy) + +## DEFERRED/DỪNG + +(đang chạy) diff --git a/.claude/workflows/runs/2026-08-12-S190-soltuon-dieu-chinh-hd/sub-b6-w4-worker.md b/.claude/workflows/runs/2026-08-12-S190-soltuon-dieu-chinh-hd/sub-b6-w4-worker.md new file mode 100644 index 0000000..984155f --- /dev/null +++ b/.claude/workflows/runs/2026-08-12-S190-soltuon-dieu-chinh-hd/sub-b6-w4-worker.md @@ -0,0 +1,59 @@ +# sub-b6-w4-worker — W4 (YC-029, plan SOLTUON-12-08-2026) + +Vai: implementer-frontend · File đích DUY NHẤT: `fe-admin/src/components/Layout.tsx` +Nguồn lệnh: `sub-b5-plan-final.md` block `### W4` (:37-48) + `## Điều kiện dừng` (:117-131). +INPUT phụ đã đọc: spec `:28-34` (§② W4) + `:98-100` (§③ W4) · style-gate §D `:83-87` (C-1a/C-2), `:101-103` (C-13), `:106-107` (C-5). + +## Việc đã làm + +**Tiền-đề đo tươi TRƯỚC khi sửa (Đ-5 — 0 lệch):** +- `fe-admin/src/components/Layout.tsx` — `if (awV2Match) {` = **:176** · nhánh KhkkN = **:188** · `}` đóng khối = **:189** ⇒ khớp mốc plan `:43` + C-13 `:103`. +- `fe-admin/src/lib/contractWorkflowFamily.ts` — `findContractWfByTypeCode` = **:77-80** (named export) · `CONTRACT_WF_FAMILY` = **:45** (7 member `HD-TP…HD-NT-DV`) · `contractWfTypeCode` = **:69** ⇒ khớp plan `:42`. Không lệch dòng nào ⇒ KHÔNG kích Đ-5. +- Baseline eslint fe-admin đo TRƯỚC sửa: **42 problems (37 errors, 5 warnings)**, exit 1 — đúng số pin của C-1a. + +**Việc 1 — import named** (`Layout.tsx:9`, ngay sau `import { cn } from '@/lib/cn'`): +``` +import { findContractWfByTypeCode } from '@/lib/contractWorkflowFamily' +``` + +**Việc 2 — nhánh TRONG khối `awV2Match`** (`Layout.tsx:195`, SAU nhánh KhkkN `:189` và TRƯỚC `}` `:196` — số dòng dịch +1 do import): +``` +if (findContractWfByTypeCode(code)) return `/system/approval-workflows-v2/${code}` +``` +`code` = phần sau `AwV2_` (vd `HdTP`, `HdNTNCC`), GIỮ NGUYÊN casing — không đụng chuỗi. + +**Việc 3 — comment Pattern 16-bis** (`Layout.tsx:190-194`, 5 dòng, khuôn khối KhkkN `:182-188` cũ): nêu họ `AwV2_Hd*` 7 leaf seed @S188 Order 11..17 · "chỗ thứ 4" Pattern 16-bis · thiếu nhánh ⇒ MenuLeaf DROP IM LẶNG (gotcha #50) · tra bằng BẢNG (nhà mã `lib/contractWorkflowFamily.ts` mirror BE `ContractApprovalWorkflowFamily.cs`), KHÔNG regex đoán ngược (`HdNTNCC` → `NT-NCC` hay `N-TNCC`). + +**Việc 4 — 4 thứ CẤM: KHÔNG đụng** (xác nhận bằng diff 1-file): +- nhánh `code === 'Contract'` (`:178` cũ → `:179` nay) — nguyên trạng, không xoá "tiện tay"; +- `DbInitializer` — 0 file BE trong diff; +- họ `Khkk_*` — 0 thay đổi (staticMap `Khkk_WfView:40` nguyên trạng); +- không thêm/sửa file nào khác (C-5 union = đúng 1 file). + +## Ô acceptance (kết quả từng ô) + +| Ô | Lệnh/nguồn | Số đo THẬT | Verdict | +|---|---|---|---| +| **C-13 RANGE** | style-gate `:101-103` | `open=177` · `khkk=189` · `hit=195` · `close=196` · `import=1` | **PASS** — `177 < 195 < 196` ✔ và `195 > 189` ✔ (mốc baseline 176/188/189 dịch +1 do dòng import, quan hệ thứ tự giữ nguyên) | +| **C-2 build** | `npm run build` fe-admin | `✓ built in 20.33s`, **BUILD_EXIT=0**, `dist/assets/index-DUBars48.js 1,776.57 kB` | **PASS** (2 warning chunk>500kB + INEFFECTIVE_DYNAMIC_IMPORT = pre-existing, không phải lỗi) | +| **C-1a eslint** | `npx eslint .` fe-admin, exit qua `${PIPESTATUS[0]}` | sau sửa **42 problems (37 errors, 5 warnings)** — baseline trước sửa **42 (37E/5W)** ⇒ **delta = 0** | **PASS** (≤42, KHÔNG TĂNG; exit 1 = baseline-đỏ-sẵn, không phải hồi quy) | +| C-1a file-wave | `npx eslint src/components/Layout.tsx` | **2 errors** — `290:10` + `364:8`, ruleId **`react-hooks/static-components`** (cả 2 CÁCH XA vùng sửa `:9` / `:190-195`) | **PASS** — ≤4E, **0 ruleId mới** (tổng app không đổi ⇒ không đẻ lỗi ở đâu) | +| **C-5 file list** | `{ git diff --name-only HEAD; git ls-files -o --exclude-standard; } \| sort -u \| grep -E '^fe-'` | `fe-admin/src/components/Layout.tsx` — **đúng 1 dòng** | **PASS** — 0 file lạ (Đ-4 sạch) | +| spec ✅W4(i) 17 leaf sidebar | cần dev-server + BE seed | — | **DEFERRED-TO-LOCAL-VERIFY** | +| spec ✅W4(ii) click "QT Duyệt HĐ — Thầu phụ" → Designer khoá `HD-TP` | cần dev-server | — | **DEFERRED-TO-LOCAL-VERIFY** | +| spec ✅W4(iii) control-âm `KhkkN1` + `DuyetNcc` | cần dev-server | — | **DEFERRED-TO-LOCAL-VERIFY** | + +**Ô máy: 4/4 PASS** (C-13 · C-2 · C-1a app + file-wave · C-5). Ô hành-vi: 0/3 đo — DEFERRED (xem dưới). + +## DEFERRED/DỪNG (nếu có) + +- **DEFERRED-TO-LOCAL-VERIFY — 3 ô hành-vi W4 (i)(ii)(iii)** (spec §③ `:98-100`): worker KHÔNG tự dựng dev-server (tránh chiếm port, theo lệnh giao). Lead đo ở nấc ④ (`npm run dev` fe-admin + BE LocalDB seed 7 `HD-*`). Đây là đường thoát HỢP LỆ theo Đ-7/spec §④.4, **CẤM tick** cho tới khi đo thật. + - Cần đo: (i) nhánh "Quy trình duyệt (Mới)" đủ **17 leaf** = 2 NCC + 8 KHKK + 7 "QT Duyệt HĐ — …" Order 11..17; (ii) leaf "QT Duyệt HĐ — Thầu phụ" → `/system/approval-workflows-v2/HdTP`, panel khoá mã `HD-TP`, history CHỈ HD-TP; (iii) control-âm: `KhkkN1` + `DuyetNcc` vẫn mở đúng trang cũ. +- **Đ-1..Đ-10: 0 điều kiện dừng kích hoạt.** (Đ-5 neo khớp tuyệt đối · Đ-1 eslint delta 0 · Đ-4 diff đúng 1 file · Đ-10 build xanh.) +- **DEPLOY CHỜ** — dừng ở local-verify + commit, KHÔNG push (theo lệnh giao + plan `:35`). + +## Commit + +`1cc16a2d` — `[CLAUDE] FE-Admin: YC-029 W4 — sidebar Designer 7 leaf QT Duyệt HĐ (resolvePath AwV2_Hd*)` +(subject ĐÚNG DoD từng ký tự; + trailer `Co-Authored-By` theo nếp repo — 18/30 commit gần nhất có). +`1 file changed, 7 insertions(+)` — `git add` đích danh `fe-admin/src/components/Layout.tsx`. Staged set đo `git diff --cached --name-only` = **đúng 1 dòng**; 8 file dirty khác (`.claude/WAL.md`, `docs/HANDOFF.md`, spec/plan trong run-folder…) là của em main, **KHÔNG stage**. **KHÔNG push** (deploy CHỜ).