[CLAUDE] App+Infra+Tests: W6 KHKK — duong ong HD V2 (view per-HD 3-site + inbox V2+ADMIN + guard trinh +PMH)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m52s

- View: vế isV2Approver per-HĐ ở CẢ 3 call-site GetEligiblePhases (:301/:381/:486) — không role-blanket, nháp-riêng-tư S89 giữ, deleted-view cùng vị ngữ
- Inbox: ghép v2InboxIds precompute (mirror PE ResolveV2InboxIdsAsync) + nhánh ADMIN phase-list tường minh +ChoDuyet (DR-4/F-B13) — HĐ ChoDuyet vào inbox cả approver lẫn admin
- Guard trình CWS:73-78: Drafter|DeptManager|người-tạo|Procurement + chặn null==null lọt guard (actorUserId is Guid); Reject :49-66 + vùng W3 :217-394 0-diff (O-A giữ)
- tests +7 (587/0): 5 PIN + admin-inbox DR-4 + Submit 4-vế/null-trap
- Reviewer: Trục 1-3 vai PASS 0-critical + 4-7 lead — 2 ghi-nhận owner: workflow-blanket-by-design độ rộng + Reject pre-existing AMPLIFIED (bán kính nổ tăng khi view mở)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-07-29 23:27:19 +07:00
parent d03d097886
commit 761d8183f3
3 changed files with 811 additions and 6 deletions

View File

@ -70,9 +70,22 @@ public class ContractWorkflowService(
if ((fromPhase == ContractPhase.DangSoanThao || fromPhase == ContractPhase.TraLai)
&& (targetPhase == ContractPhase.ChoDuyet || (!isAdmin && !isSystem)))
{
// [W6 S161] Nới vai được TRÌNH: `Drafter DeptManager người-tạo Procurement`.
// Lỗ đo 07-29: nhân sự Phòng Mua hàng (role `Procurement`) bị 403 khi trình HĐ do
// chính mình lập. Vẫn giữ 403 cho role NGOÀI 4 vế này.
// 🔴 `actorUserId is Guid` là BẮT BUỘC, không được viết thẳng
// `contract.CreatedBy == actorUserId`: cả hai đều `Guid?` ⇒ HĐ seed/legacy có
// `CreatedBy == null` gặp actor null sẽ ra `null == null` = TRUE và lọt guard.
// Xét cả `DrafterUserId` vì đó là field handler tạo HĐ gán tường minh
// (`ContractFeatures.cs:96`), còn `CreatedBy` do `AuditingInterceptor.cs:45` gán —
// HĐ nạp từ seed có thể trống ô sau.
var isCreator = actorUserId is Guid actorId
&& (contract.CreatedBy == actorId || contract.DrafterUserId == actorId);
if (!isAdmin && !isSystem
&& !actorRoles.Contains(AppRoles.Drafter)
&& !actorRoles.Contains(AppRoles.DeptManager))
&& !actorRoles.Contains(AppRoles.DeptManager)
&& !actorRoles.Contains(AppRoles.Procurement)
&& !isCreator)
{
throw new ForbiddenException(
$"Role ({string.Join(",", actorRoles)}) không đủ quyền trình duyệt HĐ.");