[CLAUDE] PurchaseEvaluation · Tests: phiếu nháp riêng tư người tạo (chặn leak role + V2-approver)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m56s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 4m56s
Màn hình CEO ở "chỗ thao tác" hiện bản nháp / phiếu chưa xong của NGƯỜI KHÁC (anh Kiệt FDC). Phiếu nháp (DangSoanThao) lộ qua 2 đường: cond #2 role-eligible (Drafter/DeptManager → DangSoanThao, mọi Drafter thấy nháp nhau) + cond #3 V2-approver (workflow pin lúc TẠO phiếu nên approver như CEO thấy mọi nháp trước khi gửi duyệt = leak chính). Luật mới: nháp = RIÊNG TƯ người tạo (DrafterUserId) + Admin thấy hết. 4 chỗ: - GetEligiblePhases: bỏ DangSoanThao khỏi role-eligible (cond #2). - Inbox (mảng admin): bỏ DangSoanThao — nháp khỏi Inbox hẳn kể cả admin (anh Kiệt chốt). - List query cond #3: guard != DangSoanThao (approver không thấy nháp). - Detail authz: guard != DangSoanThao cho V2-approver (không mở nháp người khác bằng link). Phiếu ĐÃ gửi duyệt giữ nguyên (approver vẫn thấy). Phần admin-screen tinh chỉnh sâu để sau. +PeDraftVisibilityTests (2 test, test-before security): owner thấy nháp mình · drafter khác không · CEO/V2-approver không · admin thấy hết · phiếu đã gửi vẫn hiện (no regression). Full suite 419→421 PASS. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -582,10 +582,15 @@ public class ListPurchaseEvaluationsQueryHandler(
|
||||
.Select(l => l.Step!.ApprovalWorkflowId)
|
||||
.Distinct()
|
||||
.ToListAsync(ct);
|
||||
// [S89] Nháp = owner-only (cond1 DrafterUserId). cond2 role: eligiblePhases đã bỏ
|
||||
// DangSoanThao. cond3 V2-approver: guard !=DangSoanThao → approver KHÔNG thấy phiếu
|
||||
// còn nháp (workflow đã pin lúc tạo nhưng chưa gửi duyệt), chỉ thấy khi đã gửi.
|
||||
q = q.Where(x =>
|
||||
x.e.DrafterUserId == userId
|
||||
|| eligiblePhases.Contains(x.e.Phase)
|
||||
|| (x.e.ApprovalWorkflowId != null && userApprovalWfIds.Contains(x.e.ApprovalWorkflowId.Value)));
|
||||
|| (x.e.Phase != PurchaseEvaluationPhase.DangSoanThao
|
||||
&& x.e.ApprovalWorkflowId != null
|
||||
&& userApprovalWfIds.Contains(x.e.ApprovalWorkflowId.Value)));
|
||||
}
|
||||
|
||||
if (request.Type is not null) q = q.Where(x => x.e.Type == request.Type);
|
||||
@ -642,7 +647,11 @@ public class ListPurchaseEvaluationsQueryHandler(
|
||||
if (userRoles.Any(r => required.Contains(r)))
|
||||
foreach (var p in toAdd) phases.Add(p);
|
||||
}
|
||||
AddIfAny([AppRoles.Drafter, AppRoles.DeptManager], PurchaseEvaluationPhase.DangSoanThao);
|
||||
// [S89 anh Kiệt FDC] DangSoanThao KHÔNG còn là role-eligible phase: phiếu NHÁP =
|
||||
// RIÊNG TƯ người tạo (thấy qua DrafterUserId ownership + Admin), KHÔNG lộ qua role
|
||||
// Drafter/DeptManager. Trước đây map DangSoanThao → mọi Drafter/DeptManager thấy
|
||||
// nháp của nhau (rối — màn hình CEO đầy nháp người khác). Approver chỉ thấy phiếu khi
|
||||
// ĐÃ gửi duyệt (V2-approver cond cũng guard !=DangSoanThao ở list + detail authz).
|
||||
AddIfAny([AppRoles.Procurement], PurchaseEvaluationPhase.ChoPurchasing);
|
||||
AddIfAny([AppRoles.ProjectManager], PurchaseEvaluationPhase.ChoDuAn);
|
||||
AddIfAny([AppRoles.CostControl], PurchaseEvaluationPhase.ChoCCM);
|
||||
@ -672,7 +681,8 @@ public class GetMyPurchaseEvaluationInboxQueryHandler(
|
||||
var isAdmin = userRoles.Contains(AppRoles.Admin);
|
||||
var eligiblePhases = isAdmin
|
||||
? [
|
||||
PurchaseEvaluationPhase.DangSoanThao,
|
||||
// [S89] Nháp (DangSoanThao) BỎ khỏi Inbox kể cả Admin: Inbox = "việc chờ duyệt",
|
||||
// nháp xem ở danh sách/workspace (anh Kiệt: bỏ nháp khỏi Inbox hẳn).
|
||||
PurchaseEvaluationPhase.ChoPurchasing,
|
||||
PurchaseEvaluationPhase.ChoDuAn,
|
||||
PurchaseEvaluationPhase.ChoCCM,
|
||||
@ -808,7 +818,11 @@ public class GetPurchaseEvaluationQueryHandler(
|
||||
.AnyAsync(l => l.Step!.ApprovalWorkflowId == awIdForCheck
|
||||
&& l.ApproverUserId == uidForCheck, ct);
|
||||
}
|
||||
if (!isDrafter && !eligiblePhases.Contains(e.Phase) && !isV2Approver)
|
||||
// [S89] Nháp = RIÊNG TƯ: V2-approver KHÔNG mở được phiếu CÒN nháp (chỉ thấy khi đã
|
||||
// gửi duyệt). eligiblePhases đã bỏ DangSoanThao → cond role cũng loại; chỉ Drafter
|
||||
// (+ Admin bypass trên) mở được nháp.
|
||||
var isDraftPhase = e.Phase == PurchaseEvaluationPhase.DangSoanThao;
|
||||
if (!isDrafter && !eligiblePhases.Contains(e.Phase) && (isDraftPhase || !isV2Approver))
|
||||
throw new ForbiddenException("Bạn không có quyền xem phiếu này.");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user