diff --git a/fe-admin/src/components/pe/PeDetailTabs.tsx b/fe-admin/src/components/pe/PeDetailTabs.tsx index 216aed1..1a75a92 100644 --- a/fe-admin/src/components/pe/PeDetailTabs.tsx +++ b/fe-admin/src/components/pe/PeDetailTabs.tsx @@ -2066,6 +2066,39 @@ function ApprovalsTab({ ev }: { ev: PeDetailBundle }) { queryFn: async () => (await api.get(`/purchase-evaluations/${ev.id}/changelogs`)).data, }) + // Plan AF S25 — userMap fallback cho historical entries pre-Plan AE + // (userName="" empty/null). Cover real approvals + synthetic reject rows. + const userMap = useMemo(() => { + const m = new Map() + if (ev.drafterUserId && ev.drafterName) m.set(ev.drafterUserId, ev.drafterName) + ev.approvals.forEach(a => { + if (a.approverUserId && a.approverName) m.set(a.approverUserId, a.approverName) + }) + ev.approvalFlow?.steps?.forEach(s => + s.levels?.forEach(l => + l.approvers?.forEach(ap => { + if (ap.userId && ap.fullName) m.set(ap.userId, ap.fullName) + }), + ), + ) + ev.levelOpinions?.forEach(o => { + if (o.signedByUserId && o.signedByFullName) m.set(o.signedByUserId, o.signedByFullName) + }) + ev.departmentOpinions?.forEach(o => { + if (o.userId && o.userName) m.set(o.userId, o.userName) + }) + return m + }, [ev]) + + const resolveActorName = (a: PeApproval): string => { + if (a.approverName && a.approverName.trim() !== '') return a.approverName + if (a.approverUserId) { + const name = userMap.get(a.approverUserId) + if (name) return name + } + return 'Hệ thống' + } + const merged = useMemo(() => { const phaseEnumMap: Record = { DangSoanThao: 1, ChoDuyet: 10, DaDuyet: 20, TraLai: 98, TuChoi: 99, @@ -2126,7 +2159,7 @@ function ApprovalsTab({ ev }: { ev: PeDetailBundle }) { {new Date(a.approvedAt).toLocaleString('vi-VN')}
- {a.approverName ?? 'Hệ thống'}{a.comment && ` · ${a.comment}`} + {resolveActorName(a)}{a.comment && ` · ${a.comment}`}
) @@ -2141,6 +2174,39 @@ function HistoryTab({ ev }: { ev: PeDetailBundle }) { queryKey: ['pe-changelog', ev.id], queryFn: async () => (await api.get(`/purchase-evaluations/${ev.id}/changelogs`)).data, }) + + // Plan AF S25 — userMap fallback cho historical entries pre-Plan AE + const userMap = useMemo(() => { + const m = new Map() + if (ev.drafterUserId && ev.drafterName) m.set(ev.drafterUserId, ev.drafterName) + ev.approvals.forEach(a => { + if (a.approverUserId && a.approverName) m.set(a.approverUserId, a.approverName) + }) + ev.approvalFlow?.steps?.forEach(s => + s.levels?.forEach(l => + l.approvers?.forEach(ap => { + if (ap.userId && ap.fullName) m.set(ap.userId, ap.fullName) + }), + ), + ) + ev.levelOpinions?.forEach(o => { + if (o.signedByUserId && o.signedByFullName) m.set(o.signedByUserId, o.signedByFullName) + }) + ev.departmentOpinions?.forEach(o => { + if (o.userId && o.userName) m.set(o.userId, o.userName) + }) + return m + }, [ev]) + + const resolveUserName = (l: PeChangelog): string => { + if (l.userName && l.userName.trim() !== '') return l.userName + if (l.userId) { + const name = userMap.get(l.userId) + if (name) return name + } + return 'Hệ thống' + } + if (logs.isLoading) return

Đang tải…

// User UAT 2026-05-08: chỉ track events Trả lại + Gửi duyệt lại. // User UAT 2026-05-19: + track Budget Adjust (Bug 1) + 4 mode Trả lại (Bug 2). @@ -2172,7 +2238,7 @@ function HistoryTab({ ev }: { ev: PeDetailBundle }) { {filtered.map(l => (
  • - {l.userName ?? 'Hệ thống'} + {resolveUserName(l)} {new Date(l.createdAt).toLocaleString('vi-VN')}
    {l.summary}
    diff --git a/fe-user/src/components/pe/PeDetailTabs.tsx b/fe-user/src/components/pe/PeDetailTabs.tsx index e87e3a8..df37ab4 100644 --- a/fe-user/src/components/pe/PeDetailTabs.tsx +++ b/fe-user/src/components/pe/PeDetailTabs.tsx @@ -2060,6 +2060,39 @@ function ApprovalsTab({ ev }: { ev: PeDetailBundle }) { queryFn: async () => (await api.get(`/purchase-evaluations/${ev.id}/changelogs`)).data, }) + // Plan AF S25 — userMap fallback cho historical entries pre-Plan AE + // (userName="" empty/null). Cover real approvals + synthetic reject rows. + const userMap = useMemo(() => { + const m = new Map() + if (ev.drafterUserId && ev.drafterName) m.set(ev.drafterUserId, ev.drafterName) + ev.approvals.forEach(a => { + if (a.approverUserId && a.approverName) m.set(a.approverUserId, a.approverName) + }) + ev.approvalFlow?.steps?.forEach(s => + s.levels?.forEach(l => + l.approvers?.forEach(ap => { + if (ap.userId && ap.fullName) m.set(ap.userId, ap.fullName) + }), + ), + ) + ev.levelOpinions?.forEach(o => { + if (o.signedByUserId && o.signedByFullName) m.set(o.signedByUserId, o.signedByFullName) + }) + ev.departmentOpinions?.forEach(o => { + if (o.userId && o.userName) m.set(o.userId, o.userName) + }) + return m + }, [ev]) + + const resolveActorName = (a: PeApproval): string => { + if (a.approverName && a.approverName.trim() !== '') return a.approverName + if (a.approverUserId) { + const name = userMap.get(a.approverUserId) + if (name) return name + } + return 'Hệ thống' + } + const merged = useMemo(() => { const phaseEnumMap: Record = { DangSoanThao: 1, ChoDuyet: 10, DaDuyet: 20, TraLai: 98, TuChoi: 99, @@ -2120,7 +2153,7 @@ function ApprovalsTab({ ev }: { ev: PeDetailBundle }) { {new Date(a.approvedAt).toLocaleString('vi-VN')}
    - {a.approverName ?? 'Hệ thống'}{a.comment && ` · ${a.comment}`} + {resolveActorName(a)}{a.comment && ` · ${a.comment}`}
  • ) @@ -2135,6 +2168,43 @@ function HistoryTab({ ev }: { ev: PeDetailBundle }) { queryKey: ['pe-changelog', ev.id], queryFn: async () => (await api.get(`/purchase-evaluations/${ev.id}/changelogs`)).data, }) + + // Plan AF S25 — userMap fallback cho historical entries pre-Plan AE deploy + // (userName="" empty hoặc null). Build map từ data có sẵn PeDetailBundle: + // drafter + approvals + approvalFlow + levelOpinions + departmentOpinions. + // KHÔNG cần extra fetch /api/users (admin permission). Cover gần hết users + // tham gia phiếu. + const userMap = useMemo(() => { + const m = new Map() + if (ev.drafterUserId && ev.drafterName) m.set(ev.drafterUserId, ev.drafterName) + ev.approvals.forEach(a => { + if (a.approverUserId && a.approverName) m.set(a.approverUserId, a.approverName) + }) + ev.approvalFlow?.steps?.forEach(s => + s.levels?.forEach(l => + l.approvers?.forEach(ap => { + if (ap.userId && ap.fullName) m.set(ap.userId, ap.fullName) + }), + ), + ) + ev.levelOpinions?.forEach(o => { + if (o.signedByUserId && o.signedByFullName) m.set(o.signedByUserId, o.signedByFullName) + }) + ev.departmentOpinions?.forEach(o => { + if (o.userId && o.userName) m.set(o.userId, o.userName) + }) + return m + }, [ev]) + + const resolveUserName = (l: PeChangelog): string => { + if (l.userName && l.userName.trim() !== '') return l.userName + if (l.userId) { + const name = userMap.get(l.userId) + if (name) return name + } + return 'Hệ thống' + } + if (logs.isLoading) return

    Đang tải…

    // User UAT 2026-05-08: chỉ track events Trả lại + Gửi duyệt lại. // User UAT 2026-05-19: + track Budget Adjust (Bug 1) + 4 mode Trả lại (Bug 2). @@ -2166,7 +2236,7 @@ function HistoryTab({ ev }: { ev: PeDetailBundle }) { {filtered.map(l => (
  • - {l.userName ?? 'Hệ thống'} + {resolveUserName(l)} {new Date(l.createdAt).toLocaleString('vi-VN')}
    {l.summary}