[CLAUDE] PurchaseEvaluation: Plan AC — fix Lịch sử duyệt panel show Trả lại + Duyệt vượt cấp
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m27s
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 3m27s
Bro UAT 2026-05-19 screenshot: panel "Lịch sử duyệt" KHÔNG show Return mode events (Bro Trả lại từ Phan Văn Chương → Trà missing) + KHÔNG distinct event Duyệt vượt cấp (skipToFinal F2). Root cause: - PurchaseEvaluationApprovals.Add() chỉ ở Approve branch (line 472 V2 + 660 V1) - Reject branch line 75-103 NEVER adds Approval row — chỉ log Changelog - skipToFinal advance branch line 532-572 dùng existing line 472 row nhưng comment KHÔNG distinct "vượt cấp" semantic vs approve thường Fix Plan AC: 1. BE Service.cs Reject branch (line 75-103): capture pre-call Step/Level trước ApplyReturnModeAsync mutate pointer, add Approval row sau khi mutate: Decision=Reject + FromPhase + ToPhase=evaluation.Phase + Comment carry from-position + mode summary. Cover cả Trả lại (TraLai+pointer-mode) + Từ chối (TuChoi terminal). 2. BE Service.cs line 472 Approve branch: enrich Comment với prefix "[Duyệt vượt cấp tới Cấp cuối]" khi skipToFinal=true để Lịch sử duyệt distinguish vượt cấp với approve thường. 3. FE PeDetailTabs.tsx × 2 app ApprovalsTab: add Decision badge phân biệt Approve (emerald) / Trả lại (amber) / Từ chối (rose). Vì 3/4 mode Trả lại (OneLevel/OneStep/Assignee) giữ Phase=ChoDuyet → fromPhase→toPhase badge giống Approve. Decision badge bù visual phân biệt. Verify: - dotnet build clean 0 err 2 warn (pre-existing DocxRenderer) - dotnet test 111/111 PASS - npm build × fe-user + fe-admin PASS 0 TS err Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@ -1999,29 +1999,48 @@ function QuoteDialog({
|
||||
}
|
||||
|
||||
// ===== Tab: Duyệt =====
|
||||
// Plan AC S25 Bug 3 — Decision badge phân biệt Approve / Trả lại / Từ chối.
|
||||
// 3/4 mode Trả lại (OneLevel/OneStep/Assignee) giữ Phase=ChoDuyet → fromPhase
|
||||
// + toPhase badge giống hệt Approve. Decision badge bù visual phân biệt.
|
||||
const PE_DECISION_REJECT = 2
|
||||
function decisionBadge(decision: number, toPhase: number): { label: string; cls: string } {
|
||||
if (decision === PE_DECISION_REJECT) {
|
||||
// Reject phân biệt: TuChoi(99) = "Từ chối" / TraLai(98) hoặc ChoDuyet(10) = "Trả lại"
|
||||
if (toPhase === 99) return { label: 'Từ chối', cls: 'bg-rose-100 text-rose-700 border border-rose-200' }
|
||||
return { label: 'Trả lại', cls: 'bg-amber-100 text-amber-700 border border-amber-200' }
|
||||
}
|
||||
return { label: 'Duyệt', cls: 'bg-emerald-100 text-emerald-700 border border-emerald-200' }
|
||||
}
|
||||
|
||||
function ApprovalsTab({ ev }: { ev: PeDetailBundle }) {
|
||||
if (ev.approvals.length === 0) return <p className="text-sm text-slate-500">Chưa có bước duyệt nào.</p>
|
||||
return (
|
||||
<ol className="space-y-2">
|
||||
{ev.approvals.map(a => (
|
||||
<li key={a.id} className="rounded border border-slate-200 bg-white p-3 text-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<span className={cn('rounded px-1.5 py-0.5 text-[11px]', PurchaseEvaluationPhaseColor[a.fromPhase])}>
|
||||
{PurchaseEvaluationPhaseLabel[a.fromPhase]}
|
||||
</span>
|
||||
<span className="mx-2">→</span>
|
||||
<span className={cn('rounded px-1.5 py-0.5 text-[11px]', PurchaseEvaluationPhaseColor[a.toPhase])}>
|
||||
{PurchaseEvaluationPhaseLabel[a.toPhase]}
|
||||
</span>
|
||||
{ev.approvals.map(a => {
|
||||
const dec = decisionBadge(a.decision, a.toPhase)
|
||||
return (
|
||||
<li key={a.id} className="rounded border border-slate-200 bg-white p-3 text-sm">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className={cn('rounded px-1.5 py-0.5 text-[11px] font-medium', dec.cls)}>
|
||||
{dec.label}
|
||||
</span>
|
||||
<span className={cn('rounded px-1.5 py-0.5 text-[11px]', PurchaseEvaluationPhaseColor[a.fromPhase])}>
|
||||
{PurchaseEvaluationPhaseLabel[a.fromPhase]}
|
||||
</span>
|
||||
<span className="text-slate-400">→</span>
|
||||
<span className={cn('rounded px-1.5 py-0.5 text-[11px]', PurchaseEvaluationPhaseColor[a.toPhase])}>
|
||||
{PurchaseEvaluationPhaseLabel[a.toPhase]}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-xs text-slate-500">{new Date(a.approvedAt).toLocaleString('vi-VN')}</span>
|
||||
</div>
|
||||
<span className="text-xs text-slate-500">{new Date(a.approvedAt).toLocaleString('vi-VN')}</span>
|
||||
</div>
|
||||
<div className="mt-1 text-xs text-slate-500">
|
||||
{a.approverName ?? 'Hệ thống'}{a.comment && ` · ${a.comment}`}
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
<div className="mt-1 text-xs text-slate-500">
|
||||
{a.approverName ?? 'Hệ thống'}{a.comment && ` · ${a.comment}`}
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ol>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user