diff --git a/fe-admin/src/components/pe/PeDetailTabs.tsx b/fe-admin/src/components/pe/PeDetailTabs.tsx index 2b72382..92875ec 100644 --- a/fe-admin/src/components/pe/PeDetailTabs.tsx +++ b/fe-admin/src/components/pe/PeDetailTabs.tsx @@ -117,19 +117,17 @@ export function PeDetailTabs({ // "Lưu & Gửi Duyệt" workspace mode (user 2026-05-07): trigger transition // sang phase tiếp theo (= Đã gửi duyệt). nextPhases[0] thường là ChoPurchasing // (skip TuChoi). Sau success → toast + invalidate + onBack đóng workspace. - // Mig 29 (S21 t5) — F2: per-Drafter user flag (User Management page). - const [skipToFinal, setSkipToFinal] = useState(false) - const allowSkipToFinal = evaluation.drafterAllowSkipToFinal ?? false + // Mig 31 (S23 t1) — F2 Drafter-from-Nháp semantic deprecated. skipToFinal moved + // sang Approver scope ChoDuyet (per-Level slot — xem PeWorkflowPanel). const submitForApproval = useMutation({ - mutationFn: async (opts: { skipToFinal: boolean }) => { + mutationFn: async () => { const next = evaluation.workflow.nextPhases.find(p => p !== PurchaseEvaluationPhase.TuChoi && p !== PurchaseEvaluationPhase.TraLai) if (!next) throw new Error('Không có phase tiếp theo để gửi duyệt') return api.post(`/purchase-evaluations/${evaluation.id}/transitions`, { targetPhase: next, decision: 1, comment: null, - skipToFinal: opts.skipToFinal, }) }, onSuccess: () => { @@ -283,33 +281,19 @@ export function PeDetailTabs({ > Lưu - {/* Mig 28 (S21 t4) — F2: Drafter skip checkbox */} - {allowSkipToFinal && canSubmitForApproval && ( - - )} diff --git a/fe-admin/src/components/pe/PeWorkflowPanel.tsx b/fe-admin/src/components/pe/PeWorkflowPanel.tsx index 5f67745..ea82b29 100644 --- a/fe-admin/src/components/pe/PeWorkflowPanel.tsx +++ b/fe-admin/src/components/pe/PeWorkflowPanel.tsx @@ -37,6 +37,9 @@ export function PeWorkflowPanel({ // Mig 28 (S21 t4) — F1 mode Trả lại. Default Drafter (backward compat S17). const [returnMode, setReturnMode] = useState(WorkflowReturnMode.Drafter) const [returnTargetUserId, setReturnTargetUserId] = useState(null) + // Mig 31 (S23 t1) — F2 Approver duyệt thẳng Cấp cuối. Default false (admin opt-in + // per slot tick → checkbox visible trong dialog Approve, default unchecked). + const [skipToFinalApprover, setSkipToFinalApprover] = useState(false) const qc = useQueryClient() const { user: currentUser } = useAuth() const isAdmin = currentUser?.roles?.includes('Admin') ?? false @@ -97,6 +100,9 @@ export function PeWorkflowPanel({ returnMode: isTraLaiAction ? returnMode : null, returnTargetUserId: isTraLaiAction && returnMode === WorkflowReturnMode.Assignee ? returnTargetUserId : null, + // Mig 31 (S23 t1) — F2 Approver scope ChoDuyet duyệt thẳng Cấp cuối. + // BE check matchingLevel.AllowApproverSkipToFinal (admin opt-in per slot). + skipToFinal: !isReject && skipToFinalApprover, }) }, onSuccess: () => { @@ -108,6 +114,7 @@ export function PeWorkflowPanel({ setComment('') setReturnMode(WorkflowReturnMode.Drafter) setReturnTargetUserId(null) + setSkipToFinalApprover(false) }, onError: e => toast.error(getErrorMessage(e)), }) @@ -397,6 +404,32 @@ export function PeWorkflowPanel({ )} + {/* Mig 31 (S23 t1) — F2 Approver toggle: chỉ visible khi Approve forward + + admin tick AllowApproverSkipToFinal cho slot Cấp hiện tại. */} + {!isCancel && !isSendBack && levelOptions?.allowApproverSkipToFinal && ( +
+ +
+ )} + {!isCancel && !isSendBack && skipToFinalApprover && ( +
+ ⚠ Hành động KHÔNG quay lại được (trừ khi Drafter reset toàn bộ). Phiếu sẽ + skip qua tất cả Cấp/Bước còn lại và chuyển thẳng "Đã duyệt". +
+ )}