统一付款详情与批次短编号(08-01-reimbursement-notification-progress-clarity)
这个提交包含在:
@@ -46,7 +46,7 @@ import { RiskFlagsCard } from "../components/common/RiskFlagsCard";
|
||||
import { StatusBadge } from "../components/common/StatusBadge";
|
||||
import { DateTextInput } from "../components/common/DateTextInput";
|
||||
import { downloadBlob } from "../lib/download";
|
||||
import { formatDisplayDate, formatDisplayDateTime } from "../lib/displayFormat";
|
||||
import { formatDisplayDate, formatDisplayDateTime, formatMoney } from "../lib/displayFormat";
|
||||
import { paymentBatchStatusLabel } from "../lib/paymentBatchDisplay";
|
||||
import { toast } from "sonner";
|
||||
|
||||
@@ -416,7 +416,7 @@ export function PaymentBatchesPage() {
|
||||
<span className="min-w-0 flex-1">
|
||||
<span className="flex flex-wrap items-center justify-between gap-2 text-sm font-medium text-zinc-900">
|
||||
<span>{candidate.businessNo} · {candidate.employee.name}</span>
|
||||
<span>{candidate.amount} {candidate.currencyCode}</span>
|
||||
<span>{formatMoney(candidate.amount)}</span>
|
||||
</span>
|
||||
<span className="mt-1 block text-xs text-zinc-500">
|
||||
{formatDisplayDate(candidate.businessDate)}
|
||||
@@ -516,11 +516,45 @@ export function PaymentBatchesPage() {
|
||||
<div className="detail-fact-grid text-sm xl:grid-cols-6">
|
||||
<div className="detail-fact-card"><div className="detail-fact-card__label">状态</div><div className="detail-fact-card__value">{paymentBatchStatusLabel(selectedBatch.status)}</div></div>
|
||||
<div className="detail-fact-card"><div className="detail-fact-card__label">实体</div><div className="detail-fact-card__value">{entityTypeLabel(selectedBatch.entityType)}</div></div>
|
||||
<div className="detail-fact-card"><div className="detail-fact-card__label">总金额</div><div className="detail-fact-card__value">{selectedBatch.totalAmount}</div></div>
|
||||
<div className="detail-fact-card"><div className="detail-fact-card__label">总金额</div><div className="detail-fact-card__value">{formatMoney(selectedBatch.totalAmount)}</div></div>
|
||||
<div className="detail-fact-card"><div className="detail-fact-card__label">默认日期</div><div className="detail-fact-card__value">{formatDisplayDate(selectedBatch.defaultPaymentDate)}</div></div>
|
||||
<div className="detail-fact-card"><div className="detail-fact-card__label">默认渠道</div><div className="detail-fact-card__value">{paymentChannelLabel(selectedBatch.defaultPaymentChannel)}</div></div>
|
||||
<div className="detail-fact-card"><div className="detail-fact-card__label">复核进度</div><div className="detail-fact-card__value">{selectedBatch.reviewRequirement.completedReviewCount}/{selectedBatch.reviewRequirement.requiredReviewCount}</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="finance-command-panel space-y-3" data-testid="payment-batch-current-processing">
|
||||
<h3 className="text-base font-semibold text-zinc-900">当前处理</h3>
|
||||
<div className="detail-fact-grid text-sm md:grid-cols-2">
|
||||
<div className="detail-fact-card">
|
||||
<div className="detail-fact-card__label">当前责任</div>
|
||||
<div className="detail-fact-card__value">
|
||||
{selectedBatch.status === "draft"
|
||||
? "负责人(非制单人)"
|
||||
: ["reviewed", "failed"].includes(selectedBatch.status)
|
||||
? "财务"
|
||||
: selectedBatch.status === "executed"
|
||||
? "已完成"
|
||||
: "已结束"}
|
||||
</div>
|
||||
</div>
|
||||
<div className="detail-fact-card">
|
||||
<div className="detail-fact-card__label">下一步</div>
|
||||
<div className="detail-fact-card__value">
|
||||
{selectedBatch.status === "draft"
|
||||
? "复核通过"
|
||||
: selectedBatch.status === "reviewed" && requiresBankReceipt && !receiptAttachmentsQuery.isSuccess
|
||||
? "核对银行回单状态"
|
||||
: selectedBatch.status === "reviewed" && needsBankReceipt
|
||||
? "线下付款并上传银行回单"
|
||||
: selectedBatch.status === "reviewed"
|
||||
? "登记付款完成"
|
||||
: selectedBatch.status === "failed"
|
||||
? "核对失败原因并重试"
|
||||
: "-"}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-3 text-sm text-zinc-600">
|
||||
{selectedBatch.status === "draft"
|
||||
? "已提交付款复核,等待负责人处理;制单人不能复核自己的批次。"
|
||||
@@ -535,15 +569,58 @@ export function PaymentBatchesPage() {
|
||||
: selectedBatch.status === "failed"
|
||||
? "付款登记失败,请财务核对失败原因后重试。"
|
||||
: "该付款批次已取消。"}
|
||||
{selectedBatch.highRisk ? ` 批次金额达到 ${selectedBatch.reviewRequirement.thresholdAmount},处理前需再次核对付款对象、金额和回单,但不增加审批层级。` : ""}
|
||||
{selectedBatch.highRisk ? ` 批次金额达到 ${formatMoney(selectedBatch.reviewRequirement.thresholdAmount)},处理前需再次核对付款对象、金额和回单,但不增加审批层级。` : ""}
|
||||
{selectedBatch.cancelledReason ? ` 取消原因:${selectedBatch.cancelledReason}` : ""}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="finance-command-panel space-y-4" data-testid="payment-batch-items-panel">
|
||||
<h3 className="text-base font-semibold text-zinc-900">报销明细</h3>
|
||||
<div className="space-y-4">
|
||||
{selectedBatch.items.map((item) => (
|
||||
<div key={item.id} className="detail-evidence-card">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<div className="font-medium text-zinc-900">{item.entityCode}</div>
|
||||
<div className="text-sm text-zinc-600">{item.entityTitle}</div>
|
||||
</div>
|
||||
<div className="text-sm text-zinc-600">{formatMoney(item.amount)} · {paymentItemStatusLabel(item.status)}</div>
|
||||
</div>
|
||||
<div className="mt-3 grid gap-3 md:grid-cols-4">
|
||||
<Input
|
||||
defaultValue={item.paymentVoucherNoOverride ?? ""}
|
||||
placeholder="凭证号"
|
||||
disabled={!canEditSelectedBatch || !["pending", "failed"].includes(item.status)}
|
||||
onBlur={(event) => updateItemMutation.mutate({ batchId: selectedBatch.id, itemId: item.id, paymentVoucherNoOverride: event.target.value || null })}
|
||||
/>
|
||||
<DateTextInput
|
||||
defaultValue={item.paymentDateOverride ?? ""}
|
||||
disabled={!canEditSelectedBatch || !["pending", "failed"].includes(item.status)}
|
||||
onBlur={(event) => updateItemMutation.mutate({ batchId: selectedBatch.id, itemId: item.id, paymentDateOverride: event.target.value || null })}
|
||||
/>
|
||||
<select
|
||||
className="select-std"
|
||||
defaultValue={item.paymentChannelOverride ?? ""}
|
||||
disabled={!canEditSelectedBatch || !["pending", "failed"].includes(item.status)}
|
||||
onChange={(event) => updateItemMutation.mutate({ batchId: selectedBatch.id, itemId: item.id, paymentChannelOverride: (event.target.value as PaymentChannel | "") || null })}
|
||||
>
|
||||
<option value="">默认渠道</option>
|
||||
{PAYMENT_CHANNEL_OPTIONS.map((option) => <option key={option} value={option}>{paymentChannelLabel(option)}</option>)}
|
||||
</select>
|
||||
<div className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-sm text-zinc-600">
|
||||
{item.failureMessage ? `失败原因:${item.failureMessage}` : "付款异常:无"}
|
||||
</div>
|
||||
</div>
|
||||
<RiskFlagsCard items={item.openRiskFlags} title="关联开放风险" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="finance-command-panel space-y-4" data-testid="payment-batch-receipt-panel">
|
||||
<div className="flex flex-wrap items-start justify-between gap-3">
|
||||
<div>
|
||||
<h3 className="text-base font-semibold text-zinc-900">银行回单附件</h3>
|
||||
<h3 className="text-base font-semibold text-zinc-900">银行回单</h3>
|
||||
<p className="mt-1 text-sm text-zinc-500">先在线下银行完成付款,再上传回单;系统不会连接银行自动转账。</p>
|
||||
</div>
|
||||
{canUploadReceipt ? (
|
||||
@@ -561,14 +638,16 @@ export function PaymentBatchesPage() {
|
||||
if (files.length) uploadReceiptMutation.mutate(files);
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
disabled={uploadReceiptMutation.isPending}
|
||||
onClick={() => receiptInputRef.current?.click()}
|
||||
>
|
||||
{uploadReceiptMutation.isPending ? "上传中..." : "上传银行回单"}
|
||||
</Button>
|
||||
{!needsBankReceipt ? (
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
disabled={uploadReceiptMutation.isPending}
|
||||
onClick={() => receiptInputRef.current?.click()}
|
||||
>
|
||||
{uploadReceiptMutation.isPending ? "上传中..." : "上传银行回单"}
|
||||
</Button>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
@@ -637,49 +716,6 @@ export function PaymentBatchesPage() {
|
||||
) : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="finance-command-panel space-y-4">
|
||||
<h3 className="text-base font-semibold text-zinc-900">批次项</h3>
|
||||
<div className="space-y-4">
|
||||
{selectedBatch.items.map((item) => (
|
||||
<div key={item.id} className="detail-evidence-card">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<div className="font-medium text-zinc-900">{item.entityCode}</div>
|
||||
<div className="text-sm text-zinc-600">{item.entityTitle}</div>
|
||||
</div>
|
||||
<div className="text-sm text-zinc-600">{item.amount} {item.currencyCode} · {paymentItemStatusLabel(item.status)}</div>
|
||||
</div>
|
||||
<div className="mt-3 grid gap-3 md:grid-cols-4">
|
||||
<Input
|
||||
defaultValue={item.paymentVoucherNoOverride ?? ""}
|
||||
placeholder="凭证号"
|
||||
disabled={!canEditSelectedBatch || !["pending", "failed"].includes(item.status)}
|
||||
onBlur={(event) => updateItemMutation.mutate({ batchId: selectedBatch.id, itemId: item.id, paymentVoucherNoOverride: event.target.value || null })}
|
||||
/>
|
||||
<DateTextInput
|
||||
defaultValue={item.paymentDateOverride ?? ""}
|
||||
disabled={!canEditSelectedBatch || !["pending", "failed"].includes(item.status)}
|
||||
onBlur={(event) => updateItemMutation.mutate({ batchId: selectedBatch.id, itemId: item.id, paymentDateOverride: event.target.value || null })}
|
||||
/>
|
||||
<select
|
||||
className="select-std"
|
||||
defaultValue={item.paymentChannelOverride ?? ""}
|
||||
disabled={!canEditSelectedBatch || !["pending", "failed"].includes(item.status)}
|
||||
onChange={(event) => updateItemMutation.mutate({ batchId: selectedBatch.id, itemId: item.id, paymentChannelOverride: (event.target.value as PaymentChannel | "") || null })}
|
||||
>
|
||||
<option value="">默认渠道</option>
|
||||
{PAYMENT_CHANNEL_OPTIONS.map((option) => <option key={option} value={option}>{paymentChannelLabel(option)}</option>)}
|
||||
</select>
|
||||
<div className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-sm text-zinc-600">
|
||||
{item.failureMessage ? `失败原因:${item.failureMessage}` : "付款异常:无"}
|
||||
</div>
|
||||
</div>
|
||||
<RiskFlagsCard items={item.openRiskFlags} title="关联开放风险" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="finance-command-panel text-sm text-zinc-500">未找到付款批次</div>
|
||||
@@ -715,7 +751,7 @@ export function PaymentBatchesPage() {
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-zinc-600">{item.name}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">
|
||||
金额 {item.totalAmount}
|
||||
金额 {formatMoney(item.totalAmount)}
|
||||
{item.highRisk ? " · 高风险" : ""}
|
||||
{` · 复核 ${item.reviewRequirement.completedReviewCount}/${item.reviewRequirement.requiredReviewCount}`}
|
||||
</div>
|
||||
@@ -743,7 +779,7 @@ export function PaymentBatchesPage() {
|
||||
<div className="text-xs text-zinc-500">{item.name}</div>
|
||||
</td>
|
||||
<td className="table-td">{entityTypeLabel(item.entityType)}</td>
|
||||
<td className="table-td-strong">{item.totalAmount}</td>
|
||||
<td className="table-td-strong">{formatMoney(item.totalAmount)}</td>
|
||||
<td className="table-td">
|
||||
{item.reviewRequirement.completedReviewCount}/{item.reviewRequirement.requiredReviewCount}
|
||||
</td>
|
||||
@@ -790,10 +826,10 @@ export function PaymentBatchesPage() {
|
||||
description={
|
||||
confirmPaymentAction?.kind === "financeReview"
|
||||
? confirmPaymentAction.highRisk
|
||||
? `该批次金额为 ${confirmPaymentAction.totalAmount},请核对付款对象和金额。确认后完成一次独立复核,不增加第二审批层级。`
|
||||
? `该批次金额为 ${formatMoney(confirmPaymentAction.totalAmount)},请核对付款对象和金额。确认后完成一次独立复核,不增加第二审批层级。`
|
||||
: "确认后将完成一次独立复核并写入付款批次审计记录。"
|
||||
: confirmPaymentAction?.highRisk
|
||||
? `这是高额付款批次,金额 ${confirmPaymentAction.totalAmount}。请再次核对付款对象、金额、渠道和回单;确认后将登记付款完成并更新源单状态。`
|
||||
? `这是高额付款批次,金额 ${formatMoney(confirmPaymentAction.totalAmount)}。请再次核对付款对象、金额、渠道和回单;确认后将登记付款完成并更新源单状态。`
|
||||
: "请确认线下付款和银行回单均已核对。确认后将登记付款完成并更新源单状态。"
|
||||
}
|
||||
onConfirm={async () => {
|
||||
|
||||
在新工单中引用
屏蔽一个用户