[CLAUDE] PurchaseEvaluation: luy ke TAM TINH vs CHINH XAC — ComputePendingAsync + 5-field DTO + FE 2-app sub-dong amber/banner man duyet + D4 audit-note (UAT anh Kiet 15-07, 0 mig, test 520→528)
All checks were successful
Deploy SOLUTION_ERP / build-deploy (push) Successful in 5m27s

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
pqhuy1987
2026-07-17 14:27:19 +07:00
parent e53a69307f
commit 6664a6136a
22 changed files with 1324 additions and 56 deletions

View File

@ -130,6 +130,17 @@ export function PeWorkflowPanel({
isStepAfterFinalize(stepOrder)
|| (finalizeFlowStep != null && stepOrder === finalizeFlowStep.order
&& finalizeFlowLevelOrder != null && levelOrder > finalizeFlowLevelOrder)
// [S134 anh Kiệt FDC] Phiếu trước cùng gói thầu CHƯA duyệt (informational nudge — KHÔNG
// hard-block). M2: banner "cần duyệt phiếu trước" chỉ có nghĩa khi ĐANG chờ duyệt (phase
// ChoDuyet); phiếu Nháp/TraLai/TuChoi cũng có pending fill nhưng banner vô nghĩa ở đó.
const pendingPriors = evaluation.budgetSummary?.pendingPriorPes ?? []
const showPendingBanner = pendingPriors.length > 0
&& !evaluation.budgetSummary?.budgetFrozen
&& !readOnly
&& evaluation.phase === PurchaseEvaluationPhase.ChoDuyet
const pendingPriorsText = pendingPriors
.map(p => `${p.maPhieu ?? '(chưa có mã)'} (${PurchaseEvaluationPhaseLabel[p.phase] ?? `Phase ${p.phase}`})`)
.join(', ')
// Ứng viên giá chốt — chỉ giá nào đã có giá trị (≠ null).
// [S117 anh Kiệt FDC] Khi PRO có CẢ Min & Max → thêm ứng viên "duyệt theo khoảng"
// (source=ProMinMax, amount=Min, amountMax=Max). Chọn nó = chốt dải [Min, Max].
@ -436,6 +447,14 @@ export function PeWorkflowPanel({
</div>
)}
{/* [S134] Banner cảnh báo phiếu trước cùng gói thầu CHƯA duyệt (M2: chỉ khi phase
ChoDuyet). Informational — KHÔNG disable nút duyệt. */}
{showPendingBanner && (
<div className="rounded border border-amber-300 bg-amber-50 px-3 py-2 text-[11px] text-amber-800">
{pendingPriors.length} phiếu trước cùng gói thầu CHƯA duyệt: {pendingPriorsText} cần duyệt phiếu trước trước phiếu này.
</div>
)}
{next.length > 0 && !readOnly && (
<div>
<Label className="text-xs">Hành đng:</Label>
@ -522,6 +541,13 @@ export function PeWorkflowPanel({
<Button onClick={() => transition.mutate()} disabled={transition.isPending || priceMissing}>Xác nhận</Button>
</>}
>
{/* [S134] NHẮC LẠI phiếu trước chưa duyệt TRONG dialog duyệt (trên bộ chọn giá)
— người duyệt cuối chắc chắn thấy. KHÔNG đụng disable nút Xác nhận (B3). */}
{isApproveAction && showPendingBanner && (
<div className="mb-3 rounded border border-amber-300 bg-amber-50 px-3 py-2 text-[11px] text-amber-800">
{pendingPriors.length} phiếu trước cùng gói thầu CHƯA duyệt: {pendingPriorsText} cần duyệt phiếu trước trước phiếu này.
</div>
)}
{isCancel && (
<div className="mb-3 rounded border border-red-200 bg-red-50 px-3 py-2 text-[11px] text-red-800">
Phiếu sẽ bị khoá hoàn toàn (không edit/transition đưc nữa). Drafter cần tạo phiếu mới nếu muốn làm lại.