diff --git a/.claude/WAL.md b/.claude/WAL.md index 009c287..e9e3cdb 100644 --- a/.claude/WAL.md +++ b/.claude/WAL.md @@ -29,7 +29,8 @@ chain: - [x] commit W3 `df52fa0` (11 file +2.370/−6) PUSHED - [x] **W3 ĐÓNG — cicd VERDICT=PASS 5/5** (§1 CI 580 + §3 marker [né PASS-GIẢ "Đến lượt bạn duyệt"] = cicd · §2 0-mig/96/142 + §4 smoke transitions THẬT 409-đúng-lời + 404-ProblemDetails + §5 CỜ 13/13/2 = lead 2-nguồn). Synthesis `runs/2026-07-29-S161-khkk-w3-duyet/w3-synthesis.md`. **GĐ2 KHKK trọn vòng đời phiếu trên prod.** Tồn: mark-read chuông cũ (chờ anh) · UAT tay cần WF-type-10 + non-Admin - [x] gate 5-anchor W6 PASS 5/5 (content-exact 0-drift) -- [!] wf: W6 đợt-1 `wf_e3b6a2bc-91d` ĐANG CHẠY — 1 lane BE {view per-HĐ 3-site :301/:381/:486 · inbox v2-set + nhánh ADMIN DR-4 · guard trình +PMH; CẤM đụng Reject :49-66 O-A + vùng W3 :217-394} — run-folder `runs/2026-07-29-S161-khkk-w6-duongong/`. Sau: đợt-2 test 5-PIN → reviewer (security-wave) → ship +- [x] W6 đợt-1 `wf_e3b6a2bc-91d` XONG (#53, sub-md 3.7KB §0 + code trọn): 3 fix land (+142 ContractFeatures [3-site + inbox v2 + ADMIN tường minh] · +15 CWS guard **kèm chặn null==null lọt guard** `actorUserId is Guid`); build 0E + **suite 580/0 GIỮ** (lead đo); 0 test cũ bám 3 handler (lane verify trước) +- [!] wf: W6 đợt-2 `wf_131c8f87-356` test-specialist 5-PIN (+2 bonus: admin-inbox DR-4 + CreatedBy-null-guard) ĐANG CHẠY, đích ≥585 → reviewer security-wave → ship - [ ] W5 (cầu KHKK→HĐ — cần setup WF tay TRƯỚC: type-3 + type-10 3-trạm Designer) → W7 (bản cứng 1-mốc @S161) - [ ] 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 diff --git a/.claude/workflows/runs/2026-07-29-S161-khkk-w6-duongong/sub-implementer-backend-0.md b/.claude/workflows/runs/2026-07-29-S161-khkk-w6-duongong/sub-implementer-backend-0.md new file mode 100644 index 0000000..52932db --- /dev/null +++ b/.claude/workflows/runs/2026-07-29-S161-khkk-w6-duongong/sub-implementer-backend-0.md @@ -0,0 +1,40 @@ +# sub-implementer-backend-0 — W6 Đường ống HĐ V2 (view + inbox + vai-trình) + +> Run: `2026-07-29-S161-khkk-w6-duongong` · Spec: `.claude/workflows/runs/2026-07-29-S160-khkk-dryrun-plan/spec-wave-w6-duong-ong-hd-v2-29-07-2026.md` +> Luật lượt này: BE-only · 0 mig · KHÔNG commit/push/slnx · KHÔNG viết test (đợt-2) · KHÔNG đụng `fe-*/` · KHÔNG đụng Reject `:49-66` (O-A) · KHÔNG đụng `ContractSigningPlan*` (KHKK W1-W3 đã ship). +> Ghi TỪNG MỤC trong lúc làm (chống #53). + +--- + +## §0 — ĐO TRƯỚC KHI SỬA (gate 5-anchor, đọc tươi 2026-07-29) + +| Anchor (spec §③-A) | Đo thật | Khớp? | Nội dung | +|---|---|---|---| +| `ContractFeatures.cs:334-351` | `:334` `internal static List GetEligiblePhases(IReadOnlyList userRoles)` … `:351` `return phases.ToList();` | ✅ | chữ ký CHỈ nhận role ⇒ không biết HĐ nào | +| `ContractFeatures.cs:421-430` | `:421` `private static readonly Dictionary PhaseActorRoles = new()` … `:430` `[DangDongDau] = [HrAdmin],` | ✅ | 7 key, **0 key `ChoDuyet`** | +| `ContractFeatures.cs:488-489` | `:488` `if (!isDrafter && !isEligibleByRole)` `:489` `throw new ForbiddenException("Bạn không có quyền xem HĐ này.")` | ✅ | 403 detail | +| `ContractWorkflowService.cs:73-78` | `:73` `if (!isAdmin && !isSystem` `:74` `&& !actorRoles.Contains(AppRoles.Drafter)` `:75` `&& !actorRoles.Contains(AppRoles.DeptManager))` `:77-78` throw Forbidden "không đủ quyền trình duyệt HĐ." | ✅ | guard trình | +| `ContractsController.cs:38-40` | `:38` `[HttpGet("inbox")]` `:39` `Inbox(CancellationToken ct)` `:40` `=> Ok(await mediator.Send(new GetMyInboxQuery(), ct));` | ✅ | endpoint inbox | + +**Gate 5-anchor: PASS 5/5 content-exact.** + +3 call-site `GetEligiblePhases` (grep `src/Backend`, đủ 3 — risk C "sửa chỉ 1/3"): +- `ContractFeatures.cs:301` — `ListContractsQueryHandler` (List) +- `ContractFeatures.cs:381` — `ListDeletedContractsQueryHandler` (ListDeleted, `IgnoreQueryFilters().Where(IsDeleted)`) +- `ContractFeatures.cs:486` — `GetContractQueryHandler` (Detail) +- *(`:441-442` là `PhaseActorRoles` của inbox — call-site THỨ 4 của nhánh phase, không phải của `GetEligiblePhases`)* + +Khuôn nguồn để mirror (đọc tươi): +- PE detail V2-approver: `PurchaseEvaluationFeatures.cs:886-898` (`AnyAsync(l => l.Step!.ApprovalWorkflowId == awId && l.ApproverUserId == uid)` + `isDraftPhase` S89) +- PE inbox admin phase liệt tường minh: `PurchaseEvaluationFeatures.cs:731-742` (+ `ChoDuyet` `:740`) +- PE `v2InboxIds` rỗng cho admin: `PurchaseEvaluationFeatures.cs:746-748` +- PE `ResolveV2InboxIdsAsync` in-memory join: `PurchaseEvaluationFeatures.cs:817-852` +- KHKK (chính tao viết W2): `ContractSigningPlanFeatures.cs:229-264` (Resolve) + `:275-283` (`ResolveUserApprovalWorkflowIdsAsync`) + +Nền tảng đã verify TRƯỚC khi code (chống bẫy "quên lọc IsDeleted" — risk C): +- `ContractConfiguration.cs:44` `b.HasQueryFilter(x => !x.IsDeleted)` ⇒ `db.Contracts` mặc-định ĐÃ loại HĐ xóa mềm ⇒ inbox + list + `ResolveV2InboxIdsAsync` tự an-toàn, KHÔNG cần `.Where(!IsDeleted)` tay (khác HRM). +- `Contract.cs:23/40/46` có đủ `ApprovalWorkflowId` / `CurrentWorkflowStepIndex` / `CurrentApprovalLevelOrder`; `:16` `DrafterUserId`. +- `BaseEntity.CreatedBy` kiểu **`Guid?`** (không phải string) ⇒ so trực tiếp với `actorUserId`. +- `grep tests/` cho `ContractV2Pipeline|GetMyInboxQuery|ListContractsQuery|GetContractQuery` = **0 hit** ⇒ 3 handler này hiện KHÔNG có test cũ nào bám ⇒ sửa không thể phá test cũ qua đường assert; rủi ro duy nhất là compile + suite tổng. + +---