5.3 KiB
5.3 KiB
SPEC WAVE-6 — Đường ống HĐ V2: view + inbox + vai-trình (GĐ3 sống)
Nguồn: lane-2 §1 (bảng SẴN/GAP/PARTIAL re-verify 07-29) + §6-W6. Điều kiện tiên quyết VẬN HÀNH của GĐ3 — không có nó, HĐ pin V2 xong vẫn "vô hình" với approver ("cơ chế đúng, đường đi không có"). Build độc lập W1-W3 (0 đụng file KHKK) — chạy sớm được.
① TÍNH NĂNG / ĐỀ BÀI
3 lỗ đường ống làm HĐ V2 không vận hành được (đo 07-29):
- Approver V2 KHÔNG XEM được HĐ
ChoDuyet— view-guardGetEligiblePhases(ContractFeatures.cs:334-351) 0 entry ChoDuyet cho approver (:488-489ném Forbidden). - HĐ ChoDuyet KHÔNG vào inbox —
PhaseActorRoles(:421-430) 100% legacy phase. - PMH role
Procurementbị 403 khi trình — guard trình đòiDrafter∨DeptManager(ContractWorkflowService.cs:73-78). Wave này = 0 UI mới, 0 mig; sau deploy: approver thấy + mở + duyệt được HĐ đang chờ mình.
② CÁCH IMPLEMENT
- Gate 5-anchor (§③-A).
- Test-before 3 test (§③-B) RED.
- 🔴 View — ĐÚNG CÁCH per-HĐ, KHÔNG role-blanket (review F-S3/F-C2/F-01):
GetEligiblePhases(userRoles)(ContractFeatures.cs:334-350) chữ ký chỉ nhận role ⇒ nếu chỉ nhétChoDuyet/DaPhatHanhvào tập role thì mở toang cho MỌI user mang role đó (kể cả HĐ xóa mềm ở:381). Fix: thêm nhánh V2 membership-theo-HĐ ở CẢ 3 call-site củaGetEligiblePhases(:301List ·:381ListDeleted ·:486Detail — review F-S3 chỉ ra gate cũ thiếu 2 site) — user thấy HĐChoDuyetnếu làApproverUserIdcủa Level trong workflow pin CỦA CHÍNH HĐ đó, và HĐDaPhatHanhnếu thuộc workflow HĐ đó HOẶC mang vai bản-cứng (Director/AuthorizedSigner/HrAdmin/CostControl — actor b.19/20/21, cho GĐ4/W7). Admin giữ thấy-hết. KHÔNG mở toang: user ngoài cả hai điều kiện → 403 y cũ. (Đây là điều kiện W7 phụ thuộc — F-C2.) - Inbox: mirror khuôn PE
ResolveV2InboxIdsAsync(precompute id-set các HĐ mà con-trỏ Step/Level hiện tại chứaApproverUserId==actor) — GHÉP vào inbox query hiện có thay vì chỉPhaseActorRoleslegacy. Giữ nhánh legacy cho HĐ V1 (nếu prod xuất hiện lại data V1). 🔴 Nhánh ADMIN (F-B13, DR-4 chốt @S161): admin hiện lấyeligiblePhases = PhaseActorRoles.Keys(ContractFeatures.cs:440-441) mà dict:421-430KHÔNG cóChoDuyet⇒ ghép v2InboxIds xong admin VẪN mù HĐ ChoDuyet. Mirror TRỌN khuôn PEPurchaseEvaluationFeatures.cs:731-748: admin dùng danh-sách phase liệt TƯỜNG MINH +ChoDuyet(admin thấy mọi HĐ chờ duyệt qua nhánh PHASE;v2InboxIdsđể rỗng cho admin đúng như PE:746-748). - Guard trình (
ContractWorkflowService.cs:73-78): nới thànhDrafter ∨ DeptManager ∨ CreatedBy==actor ∨ Procurement(dòng tối thiểu đóng lớp 403-PMH; giữ 403 cho role ngoài). - (Nếu DTO cần) thêm
currentApprovalinfo vào detail DTO cho FE panel — chỉ khi FE thiếu dữ liệu hiển thị. - Build + test GREEN + commit
[CLAUDE] Contract: W6 duong ong V2 view+inbox+trinh. cicd verify.
③ CHECKLIST
A. Gate 5-anchor: ContractFeatures.cs:334-351 · :421-430 · :488-489 · ContractWorkflowService.cs:73-78 · ContractsController.cs:38-40 (khu inbox/list endpoint).
B. Acceptance (lane-2 §7-W6):
- 5 test ĐÚNG TÊN (khuôn repo) trong
ContractV2PipelineTests:Inbox_PendingLevelApprover_SeesChoDuyetContract·Inbox_OtherLevel_DoesNotSeeContract(ca ÂM) ·View_V2Approver_NotDrafter_CanSeeChoDuyetContract·List_HardCopyActor_NotAdmin_SeesDaPhatHanhContract(🔴 review F-01: LIST test cho actor bản-cứng — không wave nào đo LIST trước) ·View_UserOutsideWorkflow_WithUnrelatedRole_Gets403(🔴 review F-S4: ca ÂM pin ROLE cụ thể như CostControl/Procurement, KHÔNG chọn HrAdmin cho pass dễ). dotnet testPASS 0 fail, số-sau ≥ baseline + 5.- Prod ③: login approver-V2 KHÔNG-admin →
GET /api/contracts/inbox≥1 HĐ ChoDuyet mà mình là Level hiện tại +GET /{id}200; login admin →/api/contracts/inbox≥1 HĐ ChoDuyet (🔴 F-B13 — trước W6: admin 0 dòng vìPhaseActorRoles.Keyskhông có ChoDuyet); login actor bản-cứng non-admin (vd Director) →GET /api/contracts?type=1&phase=9≥1 dòng (trước W6: 0 dòng + 403). - Ca ÂM giữ (pin role): user role KHÔNG liên quan + không thuộc workflow HĐ nào →
GET /{id}vẫn 403 (chứng KHÔNG mở toang qua role-blanket). - 🔴 Kiểm hở xóa-mềm: HĐ
ChoDuyetđã soft-delete → user V2 KHÔNG thấy ở:381ListDeleted (fix membership không được rò sang màn Đã-xóa). - CI run PASS; 0 mig (diff
*Migrations*= 0 — khuôn M2 đợt-5). C. Rủi ro: [ ] đổi view-guard làm hở HĐ cho MỌI user (thiếu điều kiện per-HĐ) — ca ÂM pin-role + kiểm-xóa-mềm canh · [ ] inbox V2 quên lọcIsDeleted· [ ] SỬA CHỈ 1/3 call-siteGetEligiblePhases(F-S3: gate cũ thiếu:301+:381) — grep 3 site trước khi đóng · [ ] đụng nhánh Reject-trước-guard (:49-66) = O-A anh chốt ĐỂ MỞ, ĐỪNG "tiện tay sửa".