diff --git a/fe-admin/src/components/pe/PeFinalizeChip.tsx b/fe-admin/src/components/pe/PeFinalizeChip.tsx
new file mode 100644
index 0000000..9b4db25
--- /dev/null
+++ b/fe-admin/src/components/pe/PeFinalizeChip.tsx
@@ -0,0 +1,23 @@
+// PE — chip "Quy trình kết thúc tại cấp này" (không trình CEO). Hiện khi quy
+// trình của phiếu có cấp bật AllowApproverFinalize (Mig 58, anh Kiệt FDC S89).
+// SINGLE SOURCE OF TRUTH cho pill này dùng ở MỌI danh sách phiếu (cây danh sách
+// + picker workspace/chờ-duyệt + inbox) để mọi người NẮM ngay "phiếu này duyệt
+// đến đây là xong, không lên CEO" mà KHÔNG cần mở phiếu. Shape khớp PeUrgentChips.
+
+export function PeFinalizeChip({
+ endsBeforeCeo,
+ finalizeStepName,
+}: {
+ endsBeforeCeo: boolean
+ finalizeStepName: string | null
+}) {
+ if (!endsBeforeCeo) return null
+ return (
+
+ {finalizeStepName ? `⚑ Kết thúc · ${finalizeStepName}` : '⚑ Không lên CEO'}
+
+ )
+}
diff --git a/fe-admin/src/components/pe/PeListPanel.tsx b/fe-admin/src/components/pe/PeListPanel.tsx
index c63972d..39f3ce2 100644
--- a/fe-admin/src/components/pe/PeListPanel.tsx
+++ b/fe-admin/src/components/pe/PeListPanel.tsx
@@ -26,6 +26,7 @@ import {
type PeListItem,
} from '@/types/purchaseEvaluation'
import { PeUrgentChips } from '@/components/pe/PeUrgentChips'
+import { PeFinalizeChip } from '@/components/pe/PeFinalizeChip'
export function PeListPanel({
typeFilter,
@@ -158,6 +159,7 @@ export function PeListPanel({
+ {p.tenGoiThau}
diff --git a/fe-admin/src/components/pe/PeWorkflowPanel.tsx b/fe-admin/src/components/pe/PeWorkflowPanel.tsx
index 5c91e8d..763e56e 100644
--- a/fe-admin/src/components/pe/PeWorkflowPanel.tsx
+++ b/fe-admin/src/components/pe/PeWorkflowPanel.tsx
@@ -251,6 +251,29 @@ export function PeWorkflowPanel({
)}
+ {/* [Mig 58 AllowApproverFinalize — anh Kiệt FDC, S89] NOTE cho MỌI người NẮM:
+ quy trình của phiếu này có cấp được cấu hình "Duyệt là KẾT THÚC" (không
+ trình CEO). Branch theo phase: đang chạy = callout xanh-lá báo trước /
+ Đã duyệt = xác nhận đã kết thúc tại đâu. */}
+ {evaluation.endsBeforeCeo && (
+
+ {evaluation.phase === PurchaseEvaluationPhase.DaDuyet ? (
+
+ ✅ Phiếu đã kết thúc tại{' '}
+ {evaluation.finalizeStepName ?? 'cấp duyệt'}
+ {evaluation.finalizeLevelName ? <> · {evaluation.finalizeLevelName}> : null} — không qua CEO.
+
+ ) : (
+
+ ⚑ Quy trình rút gọn: duyệt đến{' '}
+ {evaluation.finalizeStepName ?? 'cấp này'}
+ {evaluation.finalizeLevelName ? <> · {evaluation.finalizeLevelName}> : null} là KẾT THÚC,
+ không trình CEO. Các cấp trước vẫn duyệt như thường.
+
+ )}
+
+ )}
+
{/* Mig 24 V2 — Flow render Bước → Cấp → NV thay phase cards.
Status: Done (✓ emerald) / Current (● brand) / Pending (○ slate) */}
{flow && flow.steps.length > 0 && (
diff --git a/fe-admin/src/pages/pe/PurchaseEvaluationsListPage.tsx b/fe-admin/src/pages/pe/PurchaseEvaluationsListPage.tsx
index 7fecabd..39167b0 100644
--- a/fe-admin/src/pages/pe/PurchaseEvaluationsListPage.tsx
+++ b/fe-admin/src/pages/pe/PurchaseEvaluationsListPage.tsx
@@ -27,6 +27,7 @@ import {
} from '@/types/purchaseEvaluation'
import { PeDetailTabs } from '@/components/pe/PeDetailTabs'
import { PeUrgentChips } from '@/components/pe/PeUrgentChips'
+import { PeFinalizeChip } from '@/components/pe/PeFinalizeChip'
import { PeWorkflowPanel } from '@/components/pe/PeWorkflowPanel'
export function PurchaseEvaluationsListPage() {
@@ -487,6 +488,7 @@ export function PurchaseEvaluationsListPage() {
+ {finalizeStepName ? `⚑ Kết thúc · ${finalizeStepName}` : '⚑ Không lên CEO'}
+
+ )
+}
diff --git a/fe-user/src/components/pe/PeListPanel.tsx b/fe-user/src/components/pe/PeListPanel.tsx
index c63972d..39f3ce2 100644
--- a/fe-user/src/components/pe/PeListPanel.tsx
+++ b/fe-user/src/components/pe/PeListPanel.tsx
@@ -26,6 +26,7 @@ import {
type PeListItem,
} from '@/types/purchaseEvaluation'
import { PeUrgentChips } from '@/components/pe/PeUrgentChips'
+import { PeFinalizeChip } from '@/components/pe/PeFinalizeChip'
export function PeListPanel({
typeFilter,
@@ -158,6 +159,7 @@ export function PeListPanel({
+ {p.tenGoiThau}
diff --git a/fe-user/src/components/pe/PeWorkflowPanel.tsx b/fe-user/src/components/pe/PeWorkflowPanel.tsx
index 5c91e8d..763e56e 100644
--- a/fe-user/src/components/pe/PeWorkflowPanel.tsx
+++ b/fe-user/src/components/pe/PeWorkflowPanel.tsx
@@ -251,6 +251,29 @@ export function PeWorkflowPanel({
)}
+ {/* [Mig 58 AllowApproverFinalize — anh Kiệt FDC, S89] NOTE cho MỌI người NẮM:
+ quy trình của phiếu này có cấp được cấu hình "Duyệt là KẾT THÚC" (không
+ trình CEO). Branch theo phase: đang chạy = callout xanh-lá báo trước /
+ Đã duyệt = xác nhận đã kết thúc tại đâu. */}
+ {evaluation.endsBeforeCeo && (
+
+ {evaluation.phase === PurchaseEvaluationPhase.DaDuyet ? (
+
+ ✅ Phiếu đã kết thúc tại{' '}
+ {evaluation.finalizeStepName ?? 'cấp duyệt'}
+ {evaluation.finalizeLevelName ? <> · {evaluation.finalizeLevelName}> : null} — không qua CEO.
+
+ ) : (
+
+ ⚑ Quy trình rút gọn: duyệt đến{' '}
+ {evaluation.finalizeStepName ?? 'cấp này'}
+ {evaluation.finalizeLevelName ? <> · {evaluation.finalizeLevelName}> : null} là KẾT THÚC,
+ không trình CEO. Các cấp trước vẫn duyệt như thường.
+
+ )}
+
+ )}
+
{/* Mig 24 V2 — Flow render Bước → Cấp → NV thay phase cards.
Status: Done (✓ emerald) / Current (● brand) / Pending (○ slate) */}
{flow && flow.steps.length > 0 && (
diff --git a/fe-user/src/pages/InboxPage.tsx b/fe-user/src/pages/InboxPage.tsx
index ebaf876..3337a07 100644
--- a/fe-user/src/pages/InboxPage.tsx
+++ b/fe-user/src/pages/InboxPage.tsx
@@ -26,6 +26,7 @@ import {
type PeListItem,
} from '@/types/purchaseEvaluation'
import { PeUrgentChips } from '@/components/pe/PeUrgentChips'
+import { PeFinalizeChip } from '@/components/pe/PeFinalizeChip'
const fmtMoney = (v: number) => v.toLocaleString('vi-VN')
@@ -288,6 +289,7 @@ export function InboxPage() {
+ {p.tenGoiThau}
diff --git a/fe-user/src/pages/UserDashboardPage.tsx b/fe-user/src/pages/UserDashboardPage.tsx
index d01943f..9cb3c0b 100644
--- a/fe-user/src/pages/UserDashboardPage.tsx
+++ b/fe-user/src/pages/UserDashboardPage.tsx
@@ -10,9 +10,17 @@ import { EmptyState } from '@/components/EmptyState'
import { Button } from '@/components/ui/Button'
import { useAuth } from '@/contexts/AuthContext'
import { api } from '@/lib/api'
+import { cn } from '@/lib/cn'
import type { Paged } from '@/types/master'
import type { ContractListItem } from '@/types/contracts'
import { ContractTypeLabel } from '@/types/forms'
+import { PeFinalizeChip } from '@/components/pe/PeFinalizeChip'
+import {
+ getPeDisplayStatus,
+ PeDisplayStatusColor,
+ PeDisplayStatusLabel,
+ type PeListItem,
+} from '@/types/purchaseEvaluation'
type MyDashboard = {
draftsInProgress: number
@@ -97,6 +105,14 @@ export function UserDashboardPage() {
(await api.get>('/contracts', { params: { page: 1, pageSize: 5 } })).data,
})
+ // [S89 — anh Kiệt FDC] 5 Phiếu Duyệt NCC gần đây, kèm chip "Quy trình kết thúc"
+ // (PeFinalizeChip) để user nhìn nhanh phiếu nào duyệt-đến-đây-là-xong không lên CEO.
+ const recentPe = useQuery({
+ queryKey: ['my-pe-recent'],
+ queryFn: async () =>
+ (await api.get>('/purchase-evaluations', { params: { page: 1, pageSize: 5 } })).data,
+ })
+
const s = stats.data
return (
@@ -220,6 +236,74 @@ export function UserDashboardPage() {
))}
+
+ {/* [S89 — anh Kiệt FDC] Phiếu Duyệt NCC gần đây — mỗi row có PhaseBadge (PE
+ display-status) + PeFinalizeChip ("quy trình kết thúc, không lên CEO") +
+ tên gói + mã phiếu. PE phase ≠ Contract phase nên dùng PE display-status pill. */}
+
+
+