LP6 完善请假采购体验与审批说明
这个提交包含在:
@@ -47,6 +47,14 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from ".
|
||||
import { Textarea } from "../components/ui/textarea";
|
||||
import { SectionPanel } from "../components/patterns/SectionPanel";
|
||||
import { buildPrecheckDialogMessage } from "../lib/precheck";
|
||||
import {
|
||||
formatApprovalHistoryTime,
|
||||
resolvePurchaseBudgetHint,
|
||||
resolvePurchaseDisplayCode,
|
||||
resolvePurchaseStatusHint,
|
||||
resolvePurchaseSubmitWarning,
|
||||
workflowActionLabel,
|
||||
} from "../lib/approvalDisplay";
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
@@ -496,9 +504,7 @@ export function PurchaseRequestsPage() {
|
||||
toast.success(
|
||||
data.precheckDecision === "manual_review"
|
||||
? `采购申请已提交,已生成 ${data.openedRiskFlagCount} 条待复核风险`
|
||||
: data.warnings.includes("missing_budget_pool")
|
||||
? "采购申请已提交(未匹配预算池)"
|
||||
: "采购申请已提交",
|
||||
: resolvePurchaseSubmitWarning(data.warnings),
|
||||
);
|
||||
},
|
||||
onError: (error) => {
|
||||
@@ -884,7 +890,7 @@ export function PurchaseRequestsPage() {
|
||||
<div className="flex items-start justify-between gap-3">
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-sm font-semibold text-zinc-900">
|
||||
{item.requestNo || item.title || item.id}
|
||||
{resolvePurchaseDisplayCode(item)}
|
||||
</div>
|
||||
<div className="mt-1 truncate text-xs text-zinc-500">{item.title || "未命名采购申请"}</div>
|
||||
</div>
|
||||
@@ -928,7 +934,7 @@ export function PurchaseRequestsPage() {
|
||||
}}
|
||||
>
|
||||
<TableCell className="cursor-pointer">
|
||||
<div className="font-medium text-zinc-900">{item.requestNo || item.title || item.id}</div>
|
||||
<div className="font-medium text-zinc-900">{resolvePurchaseDisplayCode(item)}</div>
|
||||
<div className="text-xs text-zinc-500">{item.title || "未命名采购申请"}</div>
|
||||
</TableCell>
|
||||
<TableCell>{item.applicant?.name ?? "-"}</TableCell>
|
||||
@@ -976,8 +982,11 @@ export function PurchaseRequestsPage() {
|
||||
<>
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<h3 className="text-lg font-semibold text-zinc-900">{detail.requestNo ?? "采购草稿"}</h3>
|
||||
<h3 className="text-lg font-semibold text-zinc-900">{resolvePurchaseDisplayCode(detail)}</h3>
|
||||
<p className="text-sm text-zinc-500">申请人:{detail.applicant?.name ?? "-"}</p>
|
||||
<p className="mt-1 text-sm text-zinc-500" data-testid="purchase-status-hint">
|
||||
{resolvePurchaseStatusHint(detail)}
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<StatusBadge data-testid="purchase-detail-status" status={detail.status} />
|
||||
@@ -1042,6 +1051,26 @@ export function PurchaseRequestsPage() {
|
||||
|
||||
<RiskFlagsCard items={detail.riskFlags} title="待处理风险项" />
|
||||
|
||||
<SectionPanel title="审批记录" description="展示最近审批动作、退回或驳回说明,便于申请人按意见修改。">
|
||||
{detail.approvalHistory.length ? (
|
||||
<div className="space-y-2" data-testid="purchase-approval-history">
|
||||
{detail.approvalHistory.map((item) => (
|
||||
<div key={item.id} className="rounded-lg border border-zinc-200 px-3 py-2 text-sm">
|
||||
<div className="flex flex-wrap items-center justify-between gap-2">
|
||||
<span className="font-medium text-zinc-900">
|
||||
{workflowActionLabel(item.action)} · {item.actorName ?? item.actorId}
|
||||
</span>
|
||||
<span className="text-xs text-zinc-500">{formatApprovalHistoryTime(item.createdAt)}</span>
|
||||
</div>
|
||||
<div className="mt-1 text-zinc-600">{item.comment || "无补充说明"}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-sm text-zinc-500">暂无审批记录。</div>
|
||||
)}
|
||||
</SectionPanel>
|
||||
|
||||
<div className="grid gap-4 md:grid-cols-2">
|
||||
<div className="space-y-2">
|
||||
<label className="text-sm font-medium text-zinc-700">标题</label>
|
||||
@@ -1119,14 +1148,19 @@ export function PurchaseRequestsPage() {
|
||||
</div>
|
||||
|
||||
<div className="rounded-lg border border-zinc-200 bg-zinc-50 p-4 text-sm text-zinc-600">
|
||||
<div>预算池:{detail.budgetPool?.name ?? "待提交时自动解析"}</div>
|
||||
<div>预算池:{detail.budgetPool?.name ?? "未匹配"}</div>
|
||||
<div>待办节点:{detail.pendingWorkflow?.task?.stepName ?? "-"}</div>
|
||||
<div>总金额:<span className="font-semibold text-zinc-900">{totalAmountPreview}</span></div>
|
||||
{!detail.budgetPool ? (
|
||||
<div className="mt-2 rounded-md border border-amber-200 bg-amber-50 px-2 py-1 text-amber-700">
|
||||
未匹配预算池时仍会按当前规则提交审批;如需强预算校验,请由管理员补充预算配置。
|
||||
</div>
|
||||
) : null}
|
||||
<div
|
||||
className={`mt-2 rounded-md border px-2 py-1 ${
|
||||
detail.budgetPool
|
||||
? "border-emerald-200 bg-emerald-50 text-emerald-700"
|
||||
: "border-amber-200 bg-amber-50 text-amber-700"
|
||||
}`}
|
||||
data-testid="purchase-budget-hint"
|
||||
>
|
||||
{resolvePurchaseBudgetHint(detail)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-3">
|
||||
@@ -1355,7 +1389,10 @@ export function PurchaseRequestsPage() {
|
||||
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<h4 className="text-base font-semibold text-zinc-900">附件</h4>
|
||||
<div>
|
||||
<h4 className="text-base font-semibold text-zinc-900">附件</h4>
|
||||
<p className="text-sm text-zinc-500">用于订单、报价、验收或情况说明;草稿状态可增删,下载会留痕。</p>
|
||||
</div>
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<input
|
||||
data-testid="purchase-attachment-file-input"
|
||||
|
||||
在新工单中引用
屏蔽一个用户