diff --git a/.claude/WAL.md b/.claude/WAL.md index 65196ec..262b84e 100644 --- a/.claude/WAL.md +++ b/.claude/WAL.md @@ -22,7 +22,8 @@ chain: - [x] commit W2 `6cbc6ad` (20 file +5.020/−23) PUSHED - [x] **W2 ĐÓNG — cicd VERDICT=PASS 6/6** (PHÉP 1+4 cicd [CI #428 gate 574 · bundle 2-app rotate + marker 1/1 + control-âm 3-tầng] + PHÉP 2+3+5+6 lead 2-nguồn [0-mig MIGTOP giữ · menu 142 · smoke 200 + **picker trả PE THẬT `PE/2026/A/045`** + control 404 · CỜ 13/13/2 giữ]). Synthesis `runs/2026-07-29-S161-khkk-w2-crud/w2-synthesis.md`. Tồn: F-10 bịt-đồng-bộ-2-module (cần anh) · dựng WF tay type-3+10 (trước W5) · UAT non-Admin - [x] gate 5-anchor W3 PASS 5/5 (content-based; A1 path thật = Infrastructure/Services) -- [!] wf: W3 đợt-1 `wf_ef4d05a8-98c` ĐANG CHẠY — {BE skeleton service+endpoint transitions (test-before) · FE panel ×2 app} — HỢP-ĐỒNG transitions LEAD-định-nghĩa literal trong run.md (bài W2). Sau: đợt-2 test 6-RED → đợt-3 implement GREEN → reviewer → ship. Run-folder `runs/2026-07-29-S161-khkk-w3-duyet/` +- [x] W3 đợt-1 `wf_ef4d05a8-98c` XONG (2 lane #53, sub-md cứu trọn): BE skeleton + rào-FK-547-vắng-dep + đối-chứng-chéo-FE-5/5 + 3 quyết lead-duyệt · FE panel wire :324 ×2 + SHA-pair 3/3 + marker ×3 bundle; build 0W/0E + 574/0 giữ; harvest 2 diary xong +- [!] wf: W3 đợt-2 `wf_445f7e28-38b` test-specialist 6-test-RED ĐANG CHẠY → đợt-3 BE implement GREEN (copy ApproveV2Async:217-394 adapt 3) → reviewer → ship. Run-folder `runs/2026-07-29-S161-khkk-w3-duyet/` - [ ] W6 → W5 → W7 theo đồ thị plan-cha (sau W3) - [ ] đợt-2 SAU khi wf về: test-specialist + database-agent (song song) → lead acceptance §③-B (build·test·sqlcmd 96·Down sạch·đo CỜ) → reviewer → commit/push → cicd-monitor 3-chân-kiềng - [ ] setup thủ công TRƯỚC W5 (SAU W1 vì type=10 cần mig 69): dựng workflow type=3 + type=10 3-trạm qua Designer (seed chỉ 1 trạm) diff --git a/.claude/agent-memory/implementer-backend/MEMORY.md b/.claude/agent-memory/implementer-backend/MEMORY.md index a280c9e..62ea9a9 100644 --- a/.claude/agent-memory/implementer-backend/MEMORY.md +++ b/.claude/agent-memory/implementer-backend/MEMORY.md @@ -1,12 +1,6 @@ # Implementer-Backend Agent — Persistent Memory -- **S161-W2 (07-29) KHKK CRUD mega-file + Controller `[hmw wf_6f0aad26-229 · chết #53 SAU §7 (trước §8 build-log) NHƯNG sub-md 16.5KB ghi-từng-mục TRỌN + build lead 0W/0E 2s incremental = lane đã build xanh; entry on-behalf]`:** `ContractSigningPlanFeatures.cs` (Create 4-rào-fail-sớm + auto-Lines snapshot + UpdateDraft null-safe 3/3 #73 + Get/List/inbox/deleted + Dossier + Attachments-mọi-phase + Delete allow-list M6 + Picker) + `ContractSigningPlansController` 15 action **policy 15/15 kể cả Read tường minh** (#82). 4 QUYẾT khai-để-lead-phân-xử (lead duyệt cả 4): D1 `PeId` giữ nguyên spec (hợp-đồng 2-lane, đổi = gãy body JSON) · D2 `DrafterUserId=currentUser` KHÔNG copy pe.Drafter (Guid?→non-null = Guid.Empty hỏng quyền) · D3 IDOR mình-soạn ∪ có-chân-duyệt (khuôn Proposal 0-rào là khuôn HỞ — bài 8b S155; nới-phòng = 1 vế 1 chỗ) · D4 ProposedAmount mồi=PeReferenceAmount (W2 0 command sửa line). Bẫy né được ghi lại: **(1) codegen gọi TRƯỚC `Add(plan)`** — generator SaveChanges nội bộ trong tx riêng, Add trước = flush phiếu dở (S88 self-committing); **(2) SUM quote so `w.Id` (row PeSupplier) KHÔNG `w.SupplierId`** (nhầm = cộng tiền NCC khác); **(3) `Remove(plan)` TRƯỚC `Add(changelog)`** — CascadeDeleteTiming.Immediate detach entity Added (bug T26 S155); **(4) subquery `Any` lồng 2 tầng trong Where vỡ dịch SQL CHỈ LÚC CHẠY** → dùng hình dạng join :185-189; **(5) tự mở lại 5 anchor + grep-cùng-lớp ra 3 site comment sai khi lead giao 1** (S122). Tag `[s161-w2, khkk-crud, 4-quyet-khai-lead-phan-xu, codegen-truoc-add, wid-vs-supplierid, remove-truoc-changelog]` - -- **S161 (07-29) W1 KHKK schema Mig 69 `[hmw wf_7d6fb81b-640 + wf_f8c806d2-5a3 · CẢ 2 lượt chết #53 (×2 cùng task) NHƯNG lượt-2 sub-md 6.2KB ghi-TỪNG-MỤC-trong-lúc-làm cứu TRỌN narrative + code đĩa sống 100%; lead verify build 0W/0E + solo đuôi; entry on-behalf]`:** Module `Domain/ContractSigningPlans/` 7 entity + 3 enum (Phase 1/2/3/98/99 · DossierItemKind/Status trong DossierItem.cs, start=1) + `ApplicableType.ContractSigningPlan=10` append-only + 7 Configuration + DbSets ×2 file + **Mig 69 `20260729122015_AddContractSigningPlans`** (7 CreateTable · 0 ALTER · Down 7 DropTable · UNIQUE filtered `(PlanId,SupplierId) WHERE IsDeleted=0` #57 · MaKeHoach unique filter NOT-NULL · FK 6-con Cascade 1-hop + Plan→AWF Restrict — **0 multiple-cascade-path by design**: Attachment.DossierItemId chỉ HasIndex KHÔNG FK) · apply 2 DB **0 drift** (khác S133) · sys.tables 89→96 · MenuKeys.All+=KeHoachKyKet:179. Lead solo đuôi: seeder `SeedKeHoachKyKetAccessAsync` (khuôn S118 + MỞ RỘNG vế CanDelete Drafter/Admin, UPGRADE-if-exists F-S1, call SAU revoke) + `ContractSigningPlanCodeGenerator` (KHKK/{YYYY}/{Seq:D3}, SERIALIZABLE, `WorkflowAppCodeSequences` dùng chung ns Domain.Office) + DI + 3 site AWV2Admin (count/retained/orphan — danh sách kết ở VehicleBooking, ItTicket KHÔNG có LevelOpinions). Learned: (1) **ghi-sub-md-TỪNG-MỤC = thứ duy nhất sống khi return chết** — lượt-1 dồn cuối mất trắng narrative, lượt-2 ghi từng mục giữ đủ grep-proof; (2) audit-first §0 bảng đo trước khi làm tiếp = relaunch-cắt-gọt chuẩn; (3) Changelog/Approval = BaseEntity KHÔNG HasQueryFilter (parity PE) — schema-test phải assert theo THỰC TẾ entity không đoán. Tag `[s161, w1-khkk-mig69, sub-md-tung-muc-cuu-narrative, 53-x2-cung-task, cascade-1-hop, upgrade-if-exists-candelete]` - -- **S153 (07-26) arc ctx-adopt T1 + T3 (2 lượt hmw đợt-1) `[lead seed on-behalf @closeout M9; T1 return mất schema-fail → derive-from-body; T3 return CLEAN]`:** **T1 máy `mind-check`** — EXTEND `scripts/session_ctx.py` (+528 dòng, ADD-only: `_TS_KEY_RE` module-level canonical + subcommand + parser + dispatch; 2 subcommand cũ BẤT ĐỘNG). 7 phép + 2 phụ · 4 mức {đạt·trượt·cờ·bỏ-qua-có-khai} · draft/closed/`--root`/`--session` PIN · exit {0,1,3}. **9 quyết-định thiết-kế có lý-do đo được**, đắt nhất: **D1 config đọc LAZY** — chỉ đọc `memory-budget.json` khi CÓ tệp để đo, vì 2 dòng acceptance chỉ đồng-thời đúng theo thứ tự đó ("không có gì để đo thì không đọc trần; có thứ để đo thì TỪ CHỐI đoán trần") · D3 marker VẮNG ⇒ quét TRỌN + CỜ (hướng hỏng NGHIÊM: tệp thiếu marker = tệp sai khuôn, phải lộ) · D4 rào-3 in pattern-name + `file:line` **KHÔNG in snippet** (gate chống rò không được tự rò ra log) · D6 `_ascii_safe()` backslashreplace (console cp1252 gặp diacritic ⇒ `UnicodeEncodeError` = máy chết GIỮA gate). - **T3 persona `ctx-audit`** 11/11 acceptance (14.347B): CHỈNH-1 của lead landed đúng nấc (`tools:` = **mô-tả ý-định**, backstop thật = git-diff commit-gate) + verify 2 chiều `grep -c 'chặn-bằng-thiếu-tool'` = 0 (cụm đó sống ở `harness-eval.md:60`, KHÔNG carry sang) · M-1 hình-B **từ lúc sinh** (đọc `lead-stale-auditor.md:51-54` CHỈ để tránh, 0 chữ chép). **5 phát hiện thêm:** vai tự nhập lưới INFORM-only của detector khi file land (`:1721-1742` đọc LIVE frontmatter) · PHẦN-B #22 sẽ FIRE THẬT nếu lead scribe mà chưa seed diary · H25 parse greedy ⇒ `sub-ctx-audit-open` UNMAPPED không flag (no-name-guessing) · C11(a) `$roleNameRx` KHÔNG áp ctx-audit ⇒ đúng khi persona không bắt gắn class-tag. - Tag `[s153, t1-mind-check-extend-python, d1-config-lazy, d4-khong-in-snippet, t3-persona-11-11, tu-nhap-luoi-inform-only]` +- **S161 (07-29) ARC W1+W2+W3đ1 KHKK — 3 lượt/3 wave, chi tiết = sub-md run-folder `runs/2026-07-29-S161-khkk-{w1-schema,w2-crud,w3-duyet}/` (tổng ~47KB ghi-từng-mục; cả 3 lượt return #53 nhưng 0 mất):** **W1** Mig 69 7-bảng (UNIQUE filtered #57 · cascade-1-hop · apply 2 DB 0-drift · sys.tables 96) + seeder `SeedKeHoachKyKetAccessAsync` UPGRADE-if-exists+CanDelete + CodeGen `KHKK/{YYYY}/{Seq:D3}` WorkflowAppCodeSequences + 3 site AWV2Admin — ship `a8027c0` cicd 6/6. **W2** mega-file CRUD 15-endpoint policy-15/15 + 4 quyết D1-D4 khai-lead-duyệt; 5 bẫy né ghi sổ: codegen-TRƯỚC-Add (S88) · SUM so `w.Id` KHÔNG `w.SupplierId` · `Remove` trước `Add(changelog)` (T26) · subquery-Any-2-tầng vỡ-SQL-lúc-chạy · giao-1-grep-cùng-lớp-ra-3 (S122) — ship `6cbc6ad` cicd 6/6 (reviewer 12F: D1 "FE sẽ khớp" = giả-định-không-phải-phép-đo → 6 điểm đứt, lead vá). **W3đ1** skeleton test-before: switch-throw từng nhánh (đỏ-THẬT không đỏ-compile) + **rào-FK-547-bằng-VẮNG-MẶT-dep** (không inject IChangelogService) + **§6 tự đối-chứng chéo hợp-đồng FE 5/5 TRƯỚC bàn giao** (bài W2 → nghi thức) + §7 bàn-giao ctor-CUỐI. Tag `[s161, khkk-w1-w2-w3d1, mig69, upgrade-if-exists, rao-vang-mat-dep, doi-chung-cheo, gia-dinh-vs-phep-do]` - **S139 (07-17) Governance wave adap-3-bản 2-lane [wf_f9981ae5-903 · L1 CLEAN · L2 #53-garble → sub-1.md ON-BEHALF em-main verify-2-nguồn] `[engine: opus-worker]`:** L1 = MASTER-CHECKLIST 2-tầng in 4 cửa (ss Phase-3 · se Phase-4 "TỔNG-HỢP CHỐT" · pause ý-6 · tiep §3-bis bold-block KHÔNG heading) + C1 2-câu đóng-băng bề-mặt-đo (§L.b(c) prepend + mirror §2.1.2) + C6i do-record ×2 + luật-số sàn-5 §L.b(j)(iv) — PASS 6/6 (heading 26/28/7/14 bất-biến · 0 checklist-file mới · BƯỚC-0 intact ×8 · 10/10 config-token preserved · add-only 26+/2−). L2 = +C6 cite-2-nấc + H24-4 pending-flip vào governance-detectors.ps1 (+260/0 add-only) — fault-inject **13/13 2-chiều** (C6 7/7 lane chạy; H24-4 6/6 em-main hoàn tất — lane chết GIỮA fix harness-bug), live-run TOTAL 45 GIỮ + 2 net mới 0-flag count-riêng + exit-0 + ASCII 0-byte>127 + clean-diff 0-FOREIGN-line. Learned: (1) heading-invariant proof = block chèn toàn `>`/bullet + `grep '^+#'`=none (mạnh hơn đọc-mắt, họ insertion-only S134); (2) test-harness PS1 cần CÙNG kỷ-luật naming như production — bẫy `$h`/`$H` case-insensitive cắn chính harness dù script-đích đã né (`:813`); (3) 2 lane cùng chạm bề-mặt-đo (L1 sửa file nằm trong scan-scope L2) → số đo giữa-chừng trôi, clean-diff PHẢI cùng-snapshot. Tag `[s139, adap-3-ban, master-checklist-4-cua, c6-cite-2-nac, h24-4-pending-flip, faultinject-13-13, 53-writelane-onbehalf]` diff --git a/.claude/agent-memory/implementer-frontend/MEMORY.md b/.claude/agent-memory/implementer-frontend/MEMORY.md index ef578d8..731634f 100644 --- a/.claude/agent-memory/implementer-frontend/MEMORY.md +++ b/.claude/agent-memory/implementer-frontend/MEMORY.md @@ -1,8 +1,6 @@ # Implementer-Frontend Agent — Persistent Memory -- **S161-W2 (07-29) KHKK 3 page ×2 app `[hmw wf_6f0aad26-229 · chết #53 sau MUC-1+cp (4-place dở) → lead solo đuôi {Layout ×2 re-point + fe-admin App routes + matrix whitelist +10} · build ×2 PASS + SHA-pair 4/4 + coming-soon 0/0]`:** `types/khkk.ts` + 3 page `pages/khkk/` viết fe-user trước rồi cp (SHA-pair 4/4). Types theo ENTITY (BE lane song song chưa land Application lúc recon — luật đề bài "ưu tiên shape entity" ăn); header types khai HỢP-ĐỒNG FE↔BE 12 endpoint để BE đối chiếu 1 chỗ. Chip lọc List đổi STATE không ghi URL (bug UAT S155 menu-sáng-nhầm). Create: dropdown workflow đọc `{types:[...]}` KHÔNG phải flat array (khuôn ProposalCreatePage:59-69). Detail: Lines READ-ONLY (W2 không endpoint sửa line — KHÔNG bịa UI). **WfView 2 app 2 ĐƯỜNG (lead chốt @đuôi):** fe-user → alias route `/khkk/workflow-matrix?type=10` tái dùng `WorkflowMatrixViewPage` (whitelist :56 nay `1|2|3|10`) · fe-admin → Designer deep-link `/system/approval-workflows-v2/ContractSigningPlan` (admin CỐ Ý không có matrix — chốt 07-27; "Luồng duyệt" admin = dựng quy trình). menuKeys.ts place-3 SKIP-có-khai: pages 0 usage MenuKeys/usePermission (khớp khuôn Proposal) — entry-không-consumer = drift. Root `KeHoachKyKet` staticMap fe-user /dashboard→`/khkk/list`. Tag `[s161-w2, khkk-3-page, types-theo-entity, wfview-2-app-2-duong, alias-route-khong-de-page, place3-skip-khong-consumer]` - -- **S161 (07-29) W1 KHKK Designer type-10 fe-admin `[hmw wf_7d6fb81b-640 lane-2 · return CLEAN · lead verify: build tươi PASS + disk khớp từng site; diagnostics IDE giữa-agent = stale (#68) đĩa thắng]`:** 1 file `ApprovalWorkflowsV2Page.tsx` (+67/−9): `TYPE_INT_CONTRACT_SIGNING_PLAN=10` (:147) → `TYPE_CODE_TO_INT:162` (deep-link `/ContractSigningPlan`, route sẵn `App.tsx:76`) + `DEFAULT_CODE_BY_TYPE:170` `'QT-KHKK-V2-001'` (suy diễn khuôn — spec KHÔNG chốt, đổi 1 dòng) + rào 2 checkbox early-finalize **2 LỚP**: render (`disabled` + `checked` ép false + tooltip dùng chung `KHKK_EARLY_FINALIZE_NOTE` :1284-1330, className ternary chuỗi ĐẦY ĐỦ Pattern-14) **VÀ** payload (`levelFlagsPayload(e, lockEarlyFinalize)` :247 ép false :254-255 — 2/2 call-site PUT :783 + POST :815) — chỉ disable ô = containment GIẢ vì `copyFromDefinition()` vẫn tuồn `true` vào state→BE. Cân 409: PUT echo-so-BẰNG (`AWV2AdminFeatures:580-585`) KHÔNG nổ vì type-10 mọi bản đều POST-ép-false + cloneFrom cùng-type. **2 tiền-đề đề bài SAI, lane tự bẻ (mirror bài S160):** (i) FE KHÔNG có bảng nhãn ApplicableType — nhãn từ BE `applicableTypeLabel`; (ii) khuôn "entry type=9" không tồn tại (`TYPE_CODE_TO_INT` dừng ở 3) → mirror `Contract:3`. **Cờ đỏ báo lead:** type-10 (và cả type-3 xưa nay) KHÔNG có đường sidebar — allowlist `Layout.tsx:162-168` chỉ có 2 key `AwV2_` PE; admin vào bằng URL trực tiếp; BE seed leaf mới thì PHẢI nới `Layout.tsx:165` cùng lượt. Tag `[s161, w1-designer-type10, two-layer-containment-render-payload, fe-khong-label-map, tien-de-de-bai-sai-tu-be, stale-diag-68]` +- **S161 (07-29) ARC W1+W2+W3đ1 KHKK FE — 3 lượt, chi tiết = sub-md `runs/2026-07-29-S161-khkk-*/` (mọi lượt #53, đĩa cứu trọn):** **W1** Designer type-10: containment **2-LỚP render+payload** (`levelFlagsPayload(e, lock)` ép false 2/2 call-site — chỉ `disabled` = containment GIẢ vì `copyFromDefinition` tuồn state); 2 tiền-đề đề-bài SAI tự bẻ (FE không có bảng nhãn — nhãn từ BE `applicableTypeLabel`; khuôn type-9 không tồn tại → mirror `Contract:3`). **W2** 3 page + types ×2 SHA-pair; types-theo-ENTITY khi BE song song; header types = HỢP-ĐỒNG 12-endpoint (reviewer đối chiếu ra 6 đứt → lead vá; bài: hợp-đồng phải LEAD-định-nghĩa literal); WfView 2-app-2-đường (user matrix?type=10 whitelist+10 · admin Designer deep-link); place-3 menuKeys SKIP-có-khai (0 consumer). **W3đ1** `KhkkWorkflowPanel` mirror PE-panel 1063-dòng theo bảng **GIỮ/BỎ tường minh** (BỎ đủ 2 đường cấm + price-picker/budget/4-radio); cây workflow THÔ — **FE tự suy ✓/●/○ từ con-trỏ đôi** (StepIndex 0-based vs Level.Order giá-trị); bổ `approverUserId` opinion (W2 khai thiếu). Tag `[s161, khkk-fe-3-wave, containment-2-lop, types-theo-entity, giu-bo-tuong-minh, fe-suy-status-con-tro]` - **S153 (07-26) arc ctx-adopt T2 + T5 (2 lượt hmw) `[lead seed on-behalf @closeout M9; T2 return mất schema-fail (#53 +1) → derive-from-body, template VẪN LAND 8.819B; T5 return CLEAN]`:** **T2 template** `.claude/templates/session-mind-template.md` — 3 marker (RULES-START/END · MIND-TOP) · **0 dòng key `ts:`** (ts sống trong HEADING sau em-dash ⇒ không phạm rào-2) · 6 hit `rào-` (3 rào × tên+lý-do) · chiều chèn **NGƯỢC** `_context` khai TO trong khối luật · placeholder `` trong schema-comment chống parser false-match (mirror trick template `_context:32`) · kiểm `_SECRET_RES` để ví-dụ rào-3 không tự khớp mẫu (secrets-sweep rglob TRỌN session-dir, KHÔNG biết enclosure). **T5 `hmw.js`** 3/3 acceptance: VALID_ROLES 20→21 (sau T10/T11 = **23**) + 🔴 **writeGuard là 2 ĐIỂM không phải 1** — `:228` JS-comment doc **và** `:235` template-literal ĐI VÀO PROMPT sub; chỉ sửa `:235` ⇒ doc lệch trong CÙNG file, chỉ sửa `:228` ⇒ **prompt runtime KHÔNG đổi** ⇒ vai read-only bị hướng-dẫn như write-sub ⇒ Bash-write MD ⇒ mojibake G-009. `:235` = **điểm có RĂNG**. Vùng CẤM đo bằng diff: `WIDTH_CAP` 0 hit · `TIER2_EXPECTED_FULL_ID` 0 hit. diff --git a/.claude/workflows/runs/2026-07-29-S161-khkk-w3-duyet/run.md b/.claude/workflows/runs/2026-07-29-S161-khkk-w3-duyet/run.md index 3deffd6..abde227 100644 --- a/.claude/workflows/runs/2026-07-29-S161-khkk-w3-duyet/run.md +++ b/.claude/workflows/runs/2026-07-29-S161-khkk-w3-duyet/run.md @@ -12,9 +12,9 @@ | # | Stage | Vai | Trạng thái | |---|---|---|---| -| 1 | Đợt-1a: BE SKELETON — interface `IContractSigningPlanWorkflowService.TransitionAsync` + impl throw-stub + DI + endpoint transitions (build xanh, 0 logic) | hmw `implementer-backend` | [ ] | -| 2 | Đợt-1b: FE panel ×2 app — workflow panel ✓/●/○ + banner đến-lượt-bạn + nút Gửi/Duyệt/Trả/Từ-chối + ô ý kiến (POST theo HỢP-ĐỒNG trên) | hmw `implementer-frontend` | [ ] | -| 3 | Đợt-2: test-specialist — **6 test SÀN tên PIN** (spec §③-B) chạy **RED** (skeleton throw) + record RED output | hmw `test-specialist` | [ ] | +| 1 | Đợt-1a: BE SKELETON — interface `IContractSigningPlanWorkflowService.TransitionAsync` + impl throw-stub + DI + endpoint transitions (build xanh, 0 logic) | hmw `implementer-backend` | [x] `wf_ef4d05a8-98c` #53 nhưng sub-md 14KB TRỌN 7 mục — skeleton + rào-FK-547-bằng-vắng-dep + **tự đối-chứng chéo FE 5/5** + bàn giao ctor-CUỐI; 3 quyết lead DUYỆT cả 3, case-insensitive GIỮ. Build 0W/0E + 574/0 giữ (lane đo §4) | +| 2 | Đợt-1b: FE panel ×2 app — workflow panel ✓/●/○ + banner đến-lượt-bạn + nút Gửi/Duyệt/Trả/Từ-chối + ô ý kiến (POST theo HỢP-ĐỒNG trên) | hmw `implementer-frontend` | [x] cùng wf #53 — panel land + wiring :324 ×2 app + types cây-workflow-thô (FE tự suy status từ con-trỏ đôi) + GIỮ/BỎ tường minh (BỎ đủ 2 đường cấm); **lead verify: build ×2 + marker `transitions`×3 bundle tươi + SHA-pair 3/3** | +| 3 | Đợt-2: test-specialist — **6 test SÀN tên PIN** (spec §③-B) chạy **RED** (skeleton throw) + record RED output | hmw `test-specialist` | [!] `wf_445f7e28-38b` ĐANG CHẠY | | 4 | Đợt-3: BE IMPLEMENT — copy ApproveV2Async:217-394 adapt 3 chỗ (choke-point 2-write-path + KHÔNG port 2 đường sớm + TraLai) + LogSigningPlanTransition KHKK-riêng (CẤM IChangelogService — FK-547) + guard trình + notify Cấp-kế → **6 test GREEN** | hmw `implementer-backend` | [ ] | | 5 | Lead: suite + build ×2 + acceptance sweep | lead | [ ] | | 6 | Reviewer adversarial (transitions = financial/security) | `reviewer` | [ ] | diff --git a/.claude/workflows/runs/2026-07-29-S161-khkk-w3-duyet/sub-implementer-backend-0.md b/.claude/workflows/runs/2026-07-29-S161-khkk-w3-duyet/sub-implementer-backend-0.md new file mode 100644 index 0000000..302f43b --- /dev/null +++ b/.claude/workflows/runs/2026-07-29-S161-khkk-w3-duyet/sub-implementer-backend-0.md @@ -0,0 +1,137 @@ +# sub-implementer-backend-0 — S161 W3 ĐỢT-1 SKELETON (test-before, 0 logic) + +> Ghi TỪNG MỤC trong lúc làm (bài #53: chỉ thứ trên đĩa mới sống). +> Scope MINE: `Application/ContractSigningPlans/Services/` + `Infrastructure/Services/` + `Infrastructure/DependencyInjection.cs` + `Api/Controllers/ContractSigningPlansController.cs`. +> KHÔNG chạm: `tests/**` (test-specialist) · `fe-*/**` (implementer-frontend) · slnx · commit/push. + +--- + +## §0 — AUDIT TRƯỚC KHI GÕ (đo, không đoán) + +| Mục đo | Lệnh / anchor | Kết quả | +|---|---|---| +| Service KHKK workflow đã có chưa? | `grep -rn "ContractSigningPlanWorkflowService" --include=*.cs src/` | **0 hit** ⇒ scaffold sạch, không đụng file người khác | +| Run folder | `ls .claude/workflows/runs/2026-07-29-S161-khkk-w3-duyet/` | có `run.md` (đã scaffold) — KHÔNG tạo folder | +| Khuôn interface+DI để mirror | `IContractSigningPlanCodeGenerator.cs` (Application/ContractSigningPlans/Services/) + `DependencyInjection.cs:42` | xác nhận ĐÚNG chỗ | +| Controller đích | `Api/Controllers/ContractSigningPlansController.cs:21` | comment sẵn: "🔴 KHÔNG có endpoint transition (submit/approve/reject/return) — đó là W3" ⇒ đợt này gỡ đúng dòng đó | +| Enum phase | `Domain/ContractSigningPlans/ContractSigningPlanPhase.cs:8-15` | DangSoanThao=1 · ChoDuyet=2 · DaDuyet=3 · TraLai=98 · TuChoi=99 | +| Con-trỏ đôi (field THẬT trên entity) | `Domain/ContractSigningPlans/ContractSigningPlan.cs:35-37` | `ApprovalWorkflowId` · `CurrentWorkflowStepIndex` (0-based) · `CurrentApprovalLevelOrder` (1-based) | +| Exception có sẵn | `Application/Common/Exceptions/ApplicationException.cs:37-40` | `ConflictException(string)` — dùng cho action sai | +| Anchor nguồn copy đợt-3 | `Infrastructure/Services/ContractWorkflowService.cs:217-394` | `:217` = `private async Task ApproveV2Async(` ✔ · `:246` GroupBy Order ✔ · `:255-266` OR-of-N (vế `allowedUserIds.Contains` ở `:259-260`) ✔ | + +**Ghi chú anchor:** spec §③-A nói OR-of-N tại `:259-260` — đọc thực tế thì `:259` là `var allowedUserIds = pendingLevelGroup.Select(l => l.ApproverUserId).ToHashSet();` và `:260` là `if (!allowedUserIds.Contains(actorUserId.Value))`. Anchor KHỚP (không drift). + +--- + +## §1 — Interface + record (Application) — MỤC 1 ✔ + +**File MỚI:** `src/Backend/SolutionErp.Application/ContractSigningPlans/Services/IContractSigningPlanWorkflowService.cs` (43 dòng) + +- `IContractSigningPlanWorkflowService.TransitionAsync(Guid planId, string action, string? comment, CancellationToken ct = default)` → `Task` — ĐÚNG hợp-đồng lead. +- `record ContractSigningPlanTransitionResult(int Phase, int? CurrentWorkflowStepIndex, int? CurrentApprovalLevelOrder)` — đúng 3 field response, không thừa. +- Đặt cạnh `IContractSigningPlanCodeGenerator.cs` cùng thư mục `Services/` (khuôn đã có). + +**QUYẾT-1 (khai để lead phân xử): `Phase` để kiểu `int`, KHÔNG `ContractSigningPlanPhase`.** +Lý do: hợp-đồng chốt `"phase": int`. Đo được: `Api/Program.cs:33-34` `AddJsonOptions` CHỈ thêm `UtcDateTimeJsonConverter`, KHÔNG đăng ký `JsonStringEnumConverter` và KHÔNG đổi `PropertyNamingPolicy` (grep `JsonStringEnumConverter` toàn `SolutionErp.Api` = 0 hit) ⇒ hôm nay enum vẫn ra số. Nhưng nếu ngày nào đó ai thêm converter enum thì MỌI enum lật sang chuỗi và hợp-đồng vỡ ÂM THẦM. Ép `int` tại nguồn = rào chống hồi-quy đó. + +**Camel-case bind:** không có `PropertyNamingPolicy` override ⇒ mặc định ASP.NET Core camelCase ⇒ `Phase`→`"phase"`, `CurrentWorkflowStepIndex`→`"currentWorkflowStepIndex"`, `CurrentApprovalLevelOrder`→`"currentApprovalLevelOrder"`; body `"action"/"comment"` bind vào `Action`/`Comment`. Khớp literal 3/3 + 2/2. + +--- + +## §2 — Service skeleton (Infrastructure) — MỤC 2 ✔ + +**File MỚI:** `src/Backend/SolutionErp.Infrastructure/Services/ContractSigningPlanWorkflowService.cs` + +- Rào action: `AllowedActions` HashSet `{submit,approve,return,reject}` (`StringComparer.OrdinalIgnoreCase`) → sai ⇒ `throw new ConflictException("Hành động không hợp lệ.")` — ĐỨNG TRƯỚC mọi truy vấn DB. +- 4 nhánh `switch` TÁCH RIÊNG, mỗi nhánh `throw new NotImplementedException("W3 đợt-3: " + action)` + 1 dòng ghi rõ đợt-3 phải điền gì (submit: guard trình + đặt con-trỏ (1,1) + notify Cấp 1 · approve: OR-of-N + UPSERT + tiến con-trỏ + finalize · return: TraLai · reject: TuChoi). `default:` ném Conflict (switch đóng kín). +- **Comment đầu file = hợp-đồng đợt-3** (yêu cầu mục 2): nguồn copy `ContractWorkflowService.cs:217-394`; CẤM Proposal-flatten; 4 điểm GIỮ (con-trỏ đôi `ContractSigningPlan.cs:35-37` · GroupBy `:246` · OR-of-N `:255-266` · UPSERT `:292-316`); 3 chỗ ADAPT (a) finalize **2 write-path** kể cả admin-override `:190-193` gán Phase QUA BIẾN + khuôn RULE `ContractFeatures.cs:356-360` (KHÔNG dùng `PEWS:1008` vì dòng đó tự-đếm sai) (b) KHÔNG port 2 nhánh kết thúc sớm (c) reject → TraLai toàn phần, cờ 4 return-mode ở `ApprovalWorkflow.cs:116-125` (class `ApprovalWorkflowLevel` trong file đó từ `:95`); CẤM `IChangelogService.LogWorkflowTransitionAsync` (FK-547, `ContractChangelogConfiguration.cs:22-26`). + +**QUYẾT-2: 4 dependency `(IApplicationDbContext db, ICurrentUser currentUser, IDateTime dateTime, INotificationService notifications)` — khai từ đợt-1 dù đợt-1 chưa dùng.** +- `ICurrentUser` thay cặp `(actorUserId, actorRoles)` của khuôn Contract/PE, **bắt buộc** vì chữ ký lead chốt KHÔNG có tham số actor. Test dựng được: khuôn `FakeCurrentUser` có sẵn ≥10 file test (vd `PeCcmBudgetPeriodSetterAuthzTests.cs:30`). +- **KHÔNG** `UserManager`: role lấy từ `ICurrentUser.Roles`, phòng ban từ `db.Users` (`IApplicationDbContext.cs:29`) ⇒ test khỏi kéo `IdentityFixture`. +- **KHÔNG** `IChangelogService` — CỐ Ý: không inject thì không lỡ tay gọi hàm gây FK-547 (rào bằng thiết-kế, không bằng trí nhớ). +- **KHÔNG** codegen — mã KHKK gen ngay lúc TẠO phiếu (W2), không gen lúc trình. + +**QUYẾT-3: `#pragma warning disable CS9113` bao quanh class (2 dòng, đợt-3 xoá).** +Đo được: build lượt-1 ra **4 warning CS9113** `Parameter 'db'/'currentUser'/'dateTime'/'notifications' is unread` (dòng 71-74). 2 đường xử lý: +- (i) bỏ tham số cho sạch warning ⇒ test-specialist phải dựng `new ContractSigningPlanWorkflowService()` hôm nay rồi đợt-3 sửa lại **file tests** — mà tests KHÔNG phải phần việc lane này (sửa chéo); +- (ii) giữ chữ ký cuối + tắt đúng mã CS9113 đúng phạm vi class. +Chọn (ii) vì mục đích test-before là **khoá chữ ký**. Bằng chứng khuôn dựng thẳng: `tests/.../Services/ContractWorkflowServiceApproveV2Tests.cs:42` `new ContractWorkflowService(db, codeGen, dt, notify, changelog, um)`. + +--- + +## §3 — DI + Controller — MỤC 3, 4 ✔ + +**DI** `src/Backend/SolutionErp.Infrastructure/DependencyInjection.cs:43-45` — `services.AddScoped();` NGAY DƯỚI dòng codegen `:42` (đúng chỗ lead chỉ). + +**Controller** `src/Backend/SolutionErp.Api/Controllers/ContractSigningPlansController.cs`: +- Primary ctor `(IMediator mediator, IContractSigningPlanWorkflowService workflow)` + `using SolutionErp.Application.ContractSigningPlans.Services;`. +- `POST {id:guid}/transitions` + `[Authorize(Policy = "KeHoachKyKet.Update")]` → `workflow.TransitionAsync(id, body.Action, body.Comment, ct)` → `Ok(result)`. +- Body record `ContractSigningPlanTransitionBody(string Action, string? Comment = null)`. +- **Không try-catch** (GlobalExceptionMiddleware map → ProblemDetails). +- **KHÔNG kiểm `id != cmd.Id`** như các endpoint dossier-item: body transition KHÔNG mang id ⇒ 1 nguồn sự thật là route. + +**Sửa comment STALE (bài S116/S122):** header controller `:21` cũ ghi *"🔴 KHÔNG có endpoint transition (submit/approve/reject/return) — đó là W3"* — sau đợt này là SAI. Đã thay bằng đính chính + khai rõ "đợt-1 mới skeleton ⇒ endpoint trả 500 tới khi đợt-3 điền". Grep cùng-lớp `grep -rn "đó là W3"` phần dưới §5 để chắc không còn site nào khác. + +--- + +## §4 — BUILD + TEST (số đo thật) — MỤC 5 ✔ + +| Lượt | Lệnh | Kết quả | +|---|---|---| +| Build #1 | `dotnet build SolutionErp.slnx` | **Build succeeded · 0 Error · 4 Warning** (CS9113 ×4, dòng 71-74 `ContractSigningPlanWorkflowService.cs`) — đo được, không đoán | +| Vá | thêm `#pragma warning disable/restore CS9113` bao class (2 dòng, có comment "đợt-3 xoá") | — | +| Build #2 | `dotnet build SolutionErp.slnx` | **Build succeeded · 0 Warning(s) · 0 Error(s)** · 30.02s · 6/6 project (Domain, Application, Infrastructure, Api, Domain.Tests, Infrastructure.Tests) | +| Test | `dotnet test SolutionErp.slnx` | **Domain 45/45 pass · Infrastructure 529/529 pass · Failed 0 · Skipped 0** ⇒ tổng **574** = ĐÚNG baseline (45D + 529I). Skeleton KHÔNG phá gì. | + +--- + +## §5 — Grep verify sau khi vá (soi lại bằng lệnh KHÁC, bài S146) + +**A. Stale-comment cùng-lớp** — `grep -rn "đó là W3\|KHÔNG có endpoint transition" --include=*.{cs,ts,tsx,md} src/ fe-admin/src fe-user/src` +→ đúng **1 hit** và là dòng ĐÍNH CHÍNH mình vừa viết (`ContractSigningPlansController.cs:22`), 0 site sai còn sót. + +**B. Policy resolve được không** (không có trong `MenuKeys.All` ⇒ **500 lúc chạy**, không phải 403) — `grep -n "KeHoachKyKet" MenuKeys.cs` → `:42` const + **`:180` NẰM TRONG `All`** ⇒ 4 policy `KeHoachKyKet.{Read,Create,Update,Delete}` sinh runtime ⇒ `KeHoachKyKet.Update` của endpoint mới hợp lệ. ✔ + +**C. Code đã land** — `ContractSigningPlansController.cs:207` `[HttpPost("{id:guid}/transitions")]` · `:225` `record ContractSigningPlanTransitionBody` · `DependencyInjection.cs:45` `AddScoped`. ✔ + +**D. Containment (git status)** — file mình chạm ĐÚNG 4: +- `?? src/Backend/SolutionErp.Application/ContractSigningPlans/Services/IContractSigningPlanWorkflowService.cs` (mới) +- `?? src/Backend/SolutionErp.Infrastructure/Services/ContractSigningPlanWorkflowService.cs` (mới) +- ` M src/Backend/SolutionErp.Infrastructure/DependencyInjection.cs` +- ` M src/Backend/SolutionErp.Api/Controllers/ContractSigningPlansController.cs` ++ sub-md của mình trong run-folder. **0 file `tests/**` · 0 file `fe-*` · 0 sửa `slnx` · 0 commit/push · 0 chạm agent-memory.** +(`fe-*/khkk*`, `sub-implementer-frontend-1.md`, `.session-counter.json` trong `git status` là của **lane FE + harness**, KHÔNG phải mình.) + +--- + +## §6 — ĐỐI CHỨNG CHÉO LANE FE (đọc-only) — hợp-đồng khớp 5/5 + +Bài W2 "6 điểm đứt" nên soi ngay thay vì chờ tích hợp. Đọc `fe-admin/src/types/khkk.ts:250-276` + `KhkkWorkflowPanel.tsx:132`: + +| Điểm | FE (đĩa) | BE (mình) | Khớp | +|---|---|---|---| +| URL | `KhkkWorkflowPanel.tsx:132` `/contract-signing-plans/${plan.id}/transitions` | `[HttpPost("{id:guid}/transitions")]` trên `[Route("api/contract-signing-plans")]` | ✔ | +| action 4 literal | `khkk.ts:257-262` `'submit'|'approve'|'return'|'reject'` | `AllowedActions` cùng 4 chuỗi | ✔ | +| body field | `khkk.ts:266-269` `{ action, comment }` | `ContractSigningPlanTransitionBody(Action, Comment)` → camelCase | ✔ | +| response 3 field | `khkk.ts:272-276` `phase / currentWorkflowStepIndex / currentApprovalLevelOrder` | record cùng 3 field | ✔ | +| kiểu `phase` | `khkk.ts:273` `phase: number` (**INT**, comment FE ghi rõ) | `int Phase` (QUYẾT-1) | ✔ — 2 lane độc lập cùng chốt INT | + +⇒ QUYẾT-1 (ép `int` thay enum) được lane FE xác nhận độc lập, không phải mình tự diễn giải. + +--- + +## §7 — BÀN GIAO + +**Cho test-specialist (viết 6 test RED ngay được):** +- Dựng service: `new ContractSigningPlanWorkflowService(db, currentUser, dateTime, notifications)` — chữ ký này ĐÃ CUỐI, đợt-3 không đổi. `currentUser` dùng khuôn `FakeCurrentUser` (`PeCcmBudgetPeriodSetterAuthzTests.cs:30`). +- Gọi: `await svc.TransitionAsync(planId, "approve", "ý kiến", ct)` → hiện ném `NotImplementedException("W3 đợt-3: approve")` ⇒ **6 test sàn ĐỎ THẬT** (đúng ý test-before), KHÔNG đỏ vì lỗi biên dịch. +- Action lạ (vd `"finalize"`) → `ConflictException("Hành động không hợp lệ.")` — hành vi này ĐÃ THẬT ở đợt-1, muốn test luôn cũng được. +- `Inbox_OnlyPendingLevelApprover_SeesPlan` không chạm service này (đi qua query handler W2). + +**Cho đợt-3 (điền logic):** đọc block comment đầu `ContractSigningPlanWorkflowService.cs` — đã chép sẵn 4 điểm GIỮ + 3 chỗ ADAPT + 2 lệnh CẤM (Proposal-flatten, `IChangelogService.LogWorkflowTransitionAsync`) kèm `file:line`. Việc đầu tiên: **xoá 2 dòng `#pragma CS9113`** (4 dep sẽ được dùng thật). + +**Cần lead phân xử (3 quyết đã khai ở §1-§2):** QUYẾT-1 `phase` kiểu `int` · QUYẾT-2 bộ 4 dependency (đặc biệt `ICurrentUser` thay tham số actor, và **cố ý không inject `IChangelogService`** làm rào FK-547) · QUYẾT-3 pragma CS9113 tạm 2 dòng. + +**Ambiguity còn lại (khai, không tự quyết):** rào action nhận **không phân biệt hoa/thường** (`"Submit"` cũng qua). Hợp-đồng ghi chữ thường; FE gửi chữ thường (`khkk.ts:257-262`) nên nới này KHÔNG ảnh hưởng acceptance nào — nếu lead muốn STRICT ordinal thì đổi 1 chỗ (`AllowedActions` bỏ `OrdinalIgnoreCase` + bỏ `ToLowerInvariant`). + diff --git a/.claude/workflows/runs/2026-07-29-S161-khkk-w3-duyet/sub-implementer-frontend-1.md b/.claude/workflows/runs/2026-07-29-S161-khkk-w3-duyet/sub-implementer-frontend-1.md new file mode 100644 index 0000000..28f18dd --- /dev/null +++ b/.claude/workflows/runs/2026-07-29-S161-khkk-w3-duyet/sub-implementer-frontend-1.md @@ -0,0 +1,52 @@ +# sub-implementer-frontend-1 — W3 ĐỢT-1b: FE panel duyệt KHKK ×2 app (S161, 2026-07-29) + +> Ghi TỪNG MỤC trong lúc làm (chống #53). Task: panel `KhkkWorkflowPanel.tsx` + gắn DetailPage + types transitions, SHA-pair ×2 app, build ×2. +> BE logic W3 CHƯA có (task 1 skeleton song song) ⇒ bấm nút sẽ 404/500 — CHẤP NHẬN đợt này. + +## MỤC 0 — Baseline SHA (trước khi sửa) + +``` +340e64d0… fe-user/src/types/khkk.ts = fe-admin (PAIR OK) +9b91606e… fe-user/src/pages/khkk/KhkkDetailPage.tsx = fe-admin (PAIR OK) +``` + +## MỤC 1 — RECON: field THẬT của detail DTO (BE đĩa, KHÔNG suy) + +Nguồn: `src/Backend/SolutionErp.Application/ContractSigningPlans/ContractSigningPlanFeatures.cs` + +| Cái | Bằng chứng | Ghi chú | +|---|---|---| +| `ContractSigningPlanDetailDto` | `:119-147` | field CUỐI = `List WorkflowSteps` `:147` | +| `ContractSigningPlanWorkflowStepDto(Id, Order, Name, DepartmentId, Levels)` | `:112-117` | KHÔNG có `DepartmentName`, KHÔNG có `Status` | +| `ContractSigningPlanWorkflowLevelDto(Id, Order, Name, ApproverUserId, ApproverFullName)` | `:105-110` | KHÔNG có `Status`, KHÔNG có `allowApproverFinalize` (W1 rào type-10) | +| `ContractSigningPlanLevelOpinionDto` | `:93-103` | có `ApproverUserId` (`:99`) — FE types W2 **THIẾU** field này ⇒ bổ sung | +| con-trỏ đôi | `:136-137` `CurrentWorkflowStepIndex` + `CurrentApprovalLevelOrder` | | +| Tree build | `:537-566` | Steps sort `Order`, Levels sort `Order`, `ApproverFullName` join Users `:549-552` | + +🔴 **KHÁC PE CĂN BẢN — đây là điểm dễ đứt nhất của wave này:** PE trả `approvalFlow` đã có sẵn +`status: 'Done'|'Current'|'Pending'` per Bước/Cấp (BE precompute). **KHKK trả CÂY THÔ, KHÔNG status** +⇒ FE **phải tự suy** trạng thái từ con-trỏ đôi. Không có field `status` nào để đọc. + +🔴 **Ngữ nghĩa con-trỏ (BE tự khai, `:224-226`):** `CurrentWorkflowStepIndex` là **INDEX 0-based** vào +list Steps đã sort theo Order — **KHÔNG phải Order**. `CurrentApprovalLevelOrder` là **GIÁ TRỊ** `Level.Order`. +Luật so khớp OR-of-N canonical BE `:259-260`: +`steps[idx].Levels.Any(l => l.Order == CurrentApprovalLevelOrder && l.ApproverUserId == userId)` +⇒ FE mirror ĐÚNG vị ngữ này (nhiều Level **cùng Order** = OR-of-N; lấy `.some()`, KHÔNG `.find()` 1 người). + +Rào XEM BE `:184-195` (`EnsureCanViewAsync`): Admin ∨ drafter ∨ (phase ≠ DangSoanThao ∧ approver-của-workflow-đã-pin). +⇒ phiếu **Nháp** approver KHÔNG đọc được; **TraLai** thì đọc được. + +Endpoint transitions: **CHƯA có trong controller** (`ContractSigningPlansController.cs` grep `HttpPost` → chỉ +`/`, `/{id}/dossier-items`, `/{id}/attachments`). Đúng như đề bài: đợt-1 FE đi trước BE. + +## MỤC 2 — Khuôn PE panel: cái GIỮ / cái BỎ + +Nguồn `fe-user/src/components/pe/PeWorkflowPanel.tsx` (1063 dòng). + +GIỮ (mirror): sơ đồ Bước→Cấp ✓/●/○ `:381-478` · banner đến-lượt `:489-508` (`actorInV2Level`/`blockedByV2Level` +`:109-114`) · nút 3 màu emerald/amber/red `:546-562` · Dialog + ô ý kiến `:869-870` · `useAuth()` lấy actor `:66-67` +· `qc.invalidateQueries` + `toast.error(getErrorMessage(e))` `:288`. + +BỎ (đơn-giản-hoá theo lệnh): price-picker `:794-820` · budget/pendingPriors `:170-180` · file-đính-kèm-khi-duyệt +`:823-868` · 4 return-mode radio `:635-713` (W3 = TraLai toàn phần, spec §②-3(c)) · `skipToFinal`/`finalizeByCcm`/ +`applyLevelFinalize` `:57-63` (spec §②-3(b): **CẤM** port 2 đường kết thúc sớm) · xóa-mềm-bởi-approver `:295-312`.