|
|
|
@@ -47,19 +47,10 @@ import { StatusBadge } from "../components/common/StatusBadge";
|
|
|
|
|
import { DateTextInput } from "../components/common/DateTextInput";
|
|
|
|
|
import { downloadBlob } from "../lib/download";
|
|
|
|
|
import { formatDisplayDate, formatDisplayDateTime } from "../lib/displayFormat";
|
|
|
|
|
import { paymentBatchStatusLabel } from "../lib/paymentBatchDisplay";
|
|
|
|
|
import { toast } from "sonner";
|
|
|
|
|
|
|
|
|
|
const PAYMENT_CHANNEL_OPTIONS: PaymentChannel[] = ["bank", "alipay", "wechat", "cash", "other"];
|
|
|
|
|
const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
|
|
|
|
|
|
|
|
|
function batchStatusLabel(value: string) {
|
|
|
|
|
if (value === "draft") return "草稿";
|
|
|
|
|
if (value === "reviewed") return "已复核";
|
|
|
|
|
if (value === "executed") return "已执行";
|
|
|
|
|
if (value === "failed") return "执行失败";
|
|
|
|
|
if (value === "cancelled") return "已取消";
|
|
|
|
|
return value || "-";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function entityTypeLabel(value: string) {
|
|
|
|
|
if (value === "reimbursement") return "报销";
|
|
|
|
@@ -79,23 +70,13 @@ function paymentChannelLabel(value: PaymentChannel | string | null | undefined)
|
|
|
|
|
function paymentItemStatusLabel(value: string) {
|
|
|
|
|
if (value === "pending") return "待付款";
|
|
|
|
|
if (value === "paid") return "已支付";
|
|
|
|
|
if (value === "executed") return "已执行";
|
|
|
|
|
if (value === "executed") return "已付款";
|
|
|
|
|
if (value === "failed") return "付款失败";
|
|
|
|
|
if (value === "cancelled") return "已取消";
|
|
|
|
|
if (value === "skipped") return "已跳过";
|
|
|
|
|
return value || "-";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function reviewNextStepLabel(value: PaymentBatch["reviewRequirement"]["nextStep"]) {
|
|
|
|
|
if (value === "first_review") return "等待独立复核";
|
|
|
|
|
return "可执行付款标记";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function reviewerDisplayName(value: string | null | undefined) {
|
|
|
|
|
if (!value) return "";
|
|
|
|
|
return UUID_PATTERN.test(value) ? "已记录" : value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatFileSize(value: number) {
|
|
|
|
|
if (!Number.isFinite(value) || value <= 0) return "-";
|
|
|
|
|
if (value < 1024) return `${value} B`;
|
|
|
|
@@ -119,14 +100,9 @@ export function PaymentBatchesPage() {
|
|
|
|
|
const canReviewBatch =
|
|
|
|
|
hasPermission(permissions, "payment.batch.review") &&
|
|
|
|
|
(me?.roleCodes.includes("manager") || me?.roleCodes.includes("ceo"));
|
|
|
|
|
const [name, setName] = useState("");
|
|
|
|
|
const [candidateSearch, setCandidateSearch] = useState("");
|
|
|
|
|
const [selectedCandidateIds, setSelectedCandidateIds] = useState<string[]>([]);
|
|
|
|
|
const [defaultPaymentDate, setDefaultPaymentDate] = useState("");
|
|
|
|
|
const [defaultPaymentChannel, setDefaultPaymentChannel] = useState<PaymentChannel | "">("");
|
|
|
|
|
const [voucherPrefix, setVoucherPrefix] = useState("");
|
|
|
|
|
const receiptInputRef = useRef<HTMLInputElement | null>(null);
|
|
|
|
|
const [selectedReceiptFiles, setSelectedReceiptFiles] = useState<File[]>([]);
|
|
|
|
|
const [error, setError] = useState("");
|
|
|
|
|
const [showCreatePanel, setShowCreatePanel] = useState(false);
|
|
|
|
|
const [confirmPaymentAction, setConfirmPaymentAction] = useState<{
|
|
|
|
@@ -172,9 +148,11 @@ export function PaymentBatchesPage() {
|
|
|
|
|
canManageBatch &&
|
|
|
|
|
!!selectedBatch &&
|
|
|
|
|
selectedBatch.status !== "cancelled";
|
|
|
|
|
const canUploadReceipt = canEditReceiptAttachments && receiptAttachmentsQuery.isSuccess;
|
|
|
|
|
const canReviewSelectedBatch =
|
|
|
|
|
canReviewBatch &&
|
|
|
|
|
!!selectedBatch &&
|
|
|
|
|
selectedBatch.createdBy !== me?.id &&
|
|
|
|
|
(
|
|
|
|
|
selectedBatch.status === "draft" && selectedBatch.reviewRequirement.nextStep === "first_review"
|
|
|
|
|
);
|
|
|
|
@@ -183,6 +161,16 @@ export function PaymentBatchesPage() {
|
|
|
|
|
!!selectedBatch &&
|
|
|
|
|
["reviewed", "failed"].includes(selectedBatch.status) &&
|
|
|
|
|
selectedBatch.reviewRequirement.nextStep === "ready_for_execute";
|
|
|
|
|
const requiresBankReceipt = Boolean(selectedBatch?.items.some((item) =>
|
|
|
|
|
["pending", "failed"].includes(item.status) &&
|
|
|
|
|
(item.paymentChannelOverride ?? selectedBatch.defaultPaymentChannel) === "bank",
|
|
|
|
|
));
|
|
|
|
|
const activeReceiptCount = (receiptAttachmentsQuery.data?.items ?? []).filter((item) => !item.archivedAt).length;
|
|
|
|
|
const needsBankReceipt =
|
|
|
|
|
canExecuteSelectedBatch && requiresBankReceipt && receiptAttachmentsQuery.isSuccess && activeReceiptCount === 0;
|
|
|
|
|
const canRegisterPaymentComplete =
|
|
|
|
|
canExecuteSelectedBatch &&
|
|
|
|
|
(!requiresBankReceipt || (receiptAttachmentsQuery.isSuccess && activeReceiptCount > 0));
|
|
|
|
|
const canCancelSelectedBatch =
|
|
|
|
|
canManageBatch &&
|
|
|
|
|
!!selectedBatch &&
|
|
|
|
@@ -205,11 +193,7 @@ export function PaymentBatchesPage() {
|
|
|
|
|
mutationFn: async () =>
|
|
|
|
|
createPaymentBatch({
|
|
|
|
|
entityType: "reimbursement",
|
|
|
|
|
name: name.trim() || "报销付款批次",
|
|
|
|
|
entityIds: selectedCandidateIds,
|
|
|
|
|
defaultPaymentDate: defaultPaymentDate || undefined,
|
|
|
|
|
defaultPaymentChannel: defaultPaymentChannel || undefined,
|
|
|
|
|
voucherPrefix: voucherPrefix || undefined,
|
|
|
|
|
}),
|
|
|
|
|
onSuccess: async ({ item }) => {
|
|
|
|
|
setError("");
|
|
|
|
@@ -219,7 +203,7 @@ export function PaymentBatchesPage() {
|
|
|
|
|
next.set("id", item.id);
|
|
|
|
|
setSearchParams(next, { replace: true });
|
|
|
|
|
await refresh();
|
|
|
|
|
toast.success("付款批次已创建");
|
|
|
|
|
toast.success("已提交付款复核,等待负责人处理");
|
|
|
|
|
},
|
|
|
|
|
onError: (err) => {
|
|
|
|
|
const message = resolveError(err);
|
|
|
|
@@ -251,7 +235,7 @@ export function PaymentBatchesPage() {
|
|
|
|
|
onSuccess: async () => {
|
|
|
|
|
setError("");
|
|
|
|
|
await refresh();
|
|
|
|
|
toast.success("付款批次已执行");
|
|
|
|
|
toast.success("付款已登记完成");
|
|
|
|
|
},
|
|
|
|
|
onError: (err) => {
|
|
|
|
|
const message = resolveError(err);
|
|
|
|
@@ -296,18 +280,17 @@ export function PaymentBatchesPage() {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const uploadReceiptMutation = useMutation({
|
|
|
|
|
mutationFn: async () => {
|
|
|
|
|
mutationFn: async (files: File[]) => {
|
|
|
|
|
if (!selectedBatch) {
|
|
|
|
|
throw new Error("请先选择付款批次");
|
|
|
|
|
}
|
|
|
|
|
if (!selectedReceiptFiles.length) {
|
|
|
|
|
if (!files.length) {
|
|
|
|
|
throw new Error("请选择银行回单附件");
|
|
|
|
|
}
|
|
|
|
|
return uploadPaymentBatchReceiptAttachments(selectedBatch.id, selectedReceiptFiles);
|
|
|
|
|
return uploadPaymentBatchReceiptAttachments(selectedBatch.id, files);
|
|
|
|
|
},
|
|
|
|
|
onSuccess: async () => {
|
|
|
|
|
setError("");
|
|
|
|
|
setSelectedReceiptFiles([]);
|
|
|
|
|
if (receiptInputRef.current) {
|
|
|
|
|
receiptInputRef.current.value = "";
|
|
|
|
|
}
|
|
|
|
@@ -400,16 +383,6 @@ export function PaymentBatchesPage() {
|
|
|
|
|
当前账号仅可查看批次并执行复核,创建与编辑入口仅对付款批次管理员开放。
|
|
|
|
|
</div>
|
|
|
|
|
) : null}
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label>批次名称</Label>
|
|
|
|
|
<Input
|
|
|
|
|
data-testid="payment-batch-create-name"
|
|
|
|
|
value={name}
|
|
|
|
|
disabled={!canManageBatch}
|
|
|
|
|
onChange={(event) => setName(event.target.value)}
|
|
|
|
|
placeholder="例如:2026Q2-财务付款批次-01"
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label>选择待付款报销</Label>
|
|
|
|
|
<Input
|
|
|
|
@@ -457,44 +430,12 @@ export function PaymentBatchesPage() {
|
|
|
|
|
<div className="px-2 py-3 text-sm text-zinc-500">没有可加入批次的已审批报销。</div>
|
|
|
|
|
) : null}
|
|
|
|
|
</div>
|
|
|
|
|
<div className="text-xs text-zinc-500">已选择 {selectedCandidateIds.length} 项;创建时服务端会再次核对审批和付款状态。</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="grid gap-3 md:grid-cols-3">
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label>默认付款日期</Label>
|
|
|
|
|
<DateTextInput
|
|
|
|
|
data-testid="payment-batch-create-payment-date"
|
|
|
|
|
value={defaultPaymentDate}
|
|
|
|
|
disabled={!canManageBatch}
|
|
|
|
|
onChange={(event) => setDefaultPaymentDate(event.target.value)}
|
|
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label>默认渠道</Label>
|
|
|
|
|
<select
|
|
|
|
|
className="select-std"
|
|
|
|
|
data-testid="payment-batch-create-payment-channel"
|
|
|
|
|
value={defaultPaymentChannel}
|
|
|
|
|
disabled={!canManageBatch}
|
|
|
|
|
onChange={(event) => setDefaultPaymentChannel((event.target.value as PaymentChannel | "") || "")}
|
|
|
|
|
>
|
|
|
|
|
<option value="">不设置</option>
|
|
|
|
|
{PAYMENT_CHANNEL_OPTIONS.map((item) => <option key={item} value={item}>{paymentChannelLabel(item)}</option>)}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
|
<Label>凭证前缀</Label>
|
|
|
|
|
<Input
|
|
|
|
|
data-testid="payment-batch-create-voucher-prefix"
|
|
|
|
|
value={voucherPrefix}
|
|
|
|
|
disabled={!canManageBatch}
|
|
|
|
|
onChange={(event) => setVoucherPrefix(event.target.value)}
|
|
|
|
|
placeholder="例如 PBX"
|
|
|
|
|
/>
|
|
|
|
|
<div className="text-xs text-zinc-500">
|
|
|
|
|
已选择 {selectedCandidateIds.length} 项;系统自动生成编号和标题,默认按银行转账提交负责人复核。
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<Button type="button" disabled={createMutation.isPending || !canManageBatch || selectedCandidateIds.length === 0} onClick={() => createMutation.mutate()}>
|
|
|
|
|
{createMutation.isPending ? "创建中..." : "创建批次"}
|
|
|
|
|
{createMutation.isPending ? "提交中..." : "创建并提交复核"}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
) : null}
|
|
|
|
@@ -514,11 +455,10 @@ export function PaymentBatchesPage() {
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div className="flex flex-wrap gap-2">
|
|
|
|
|
{canReviewBatch ? (
|
|
|
|
|
{canReviewSelectedBatch ? (
|
|
|
|
|
<Button
|
|
|
|
|
type="button"
|
|
|
|
|
variant="secondary"
|
|
|
|
|
disabled={reviewMutation.isPending || !canReviewSelectedBatch}
|
|
|
|
|
disabled={reviewMutation.isPending}
|
|
|
|
|
onClick={() => setConfirmPaymentAction({
|
|
|
|
|
kind: "financeReview",
|
|
|
|
|
batchId: selectedBatch.id,
|
|
|
|
@@ -527,13 +467,22 @@ export function PaymentBatchesPage() {
|
|
|
|
|
totalAmount: selectedBatch.totalAmount,
|
|
|
|
|
})}
|
|
|
|
|
>
|
|
|
|
|
复核
|
|
|
|
|
复核通过
|
|
|
|
|
</Button>
|
|
|
|
|
) : null}
|
|
|
|
|
{canManageBatch ? (
|
|
|
|
|
{needsBankReceipt ? (
|
|
|
|
|
<Button
|
|
|
|
|
type="button"
|
|
|
|
|
disabled={executeMutation.isPending || !canExecuteSelectedBatch}
|
|
|
|
|
disabled={uploadReceiptMutation.isPending}
|
|
|
|
|
onClick={() => receiptInputRef.current?.click()}
|
|
|
|
|
>
|
|
|
|
|
{uploadReceiptMutation.isPending ? "上传中..." : "上传银行回单"}
|
|
|
|
|
</Button>
|
|
|
|
|
) : null}
|
|
|
|
|
{canRegisterPaymentComplete ? (
|
|
|
|
|
<Button
|
|
|
|
|
type="button"
|
|
|
|
|
disabled={executeMutation.isPending}
|
|
|
|
|
onClick={() => setConfirmPaymentAction({
|
|
|
|
|
kind: "financePayment",
|
|
|
|
|
batchId: selectedBatch.id,
|
|
|
|
@@ -542,7 +491,7 @@ export function PaymentBatchesPage() {
|
|
|
|
|
totalAmount: selectedBatch.totalAmount,
|
|
|
|
|
})}
|
|
|
|
|
>
|
|
|
|
|
执行
|
|
|
|
|
登记付款完成
|
|
|
|
|
</Button>
|
|
|
|
|
) : null}
|
|
|
|
|
{canManageBatch ? (
|
|
|
|
@@ -565,7 +514,7 @@ export function PaymentBatchesPage() {
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<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">{batchStatusLabel(selectedBatch.status)}</div></div>
|
|
|
|
|
<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">{formatDisplayDate(selectedBatch.defaultPaymentDate)}</div></div>
|
|
|
|
@@ -573,10 +522,20 @@ export function PaymentBatchesPage() {
|
|
|
|
|
<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 className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-3 text-sm text-zinc-600">
|
|
|
|
|
当前要求:单次独立复核后可执行。
|
|
|
|
|
{selectedBatch.highRisk ? ` 批次金额达到 ${selectedBatch.reviewRequirement.thresholdAmount},执行前需再次核对付款对象、金额和回单,但不增加审批层级。` : ""}
|
|
|
|
|
下一步:{reviewNextStepLabel(selectedBatch.reviewRequirement.nextStep)}。
|
|
|
|
|
{selectedBatch.reviewedBy ? ` 复核人:${reviewerDisplayName(selectedBatch.reviewedBy)}` : ""}
|
|
|
|
|
{selectedBatch.status === "draft"
|
|
|
|
|
? "已提交付款复核,等待负责人处理;制单人不能复核自己的批次。"
|
|
|
|
|
: selectedBatch.status === "reviewed" && requiresBankReceipt && !receiptAttachmentsQuery.isSuccess
|
|
|
|
|
? "付款复核已通过,正在核对银行回单状态。"
|
|
|
|
|
: selectedBatch.status === "reviewed" && needsBankReceipt
|
|
|
|
|
? "付款复核已通过。请财务先在线下银行完成付款,再上传银行回单。"
|
|
|
|
|
: selectedBatch.status === "reviewed"
|
|
|
|
|
? "付款复核已通过。请财务核对付款结果并登记付款完成。"
|
|
|
|
|
: selectedBatch.status === "executed"
|
|
|
|
|
? "付款已登记完成。"
|
|
|
|
|
: selectedBatch.status === "failed"
|
|
|
|
|
? "付款登记失败,请财务核对失败原因后重试。"
|
|
|
|
|
: "该付款批次已取消。"}
|
|
|
|
|
{selectedBatch.highRisk ? ` 批次金额达到 ${selectedBatch.reviewRequirement.thresholdAmount},处理前需再次核对付款对象、金额和回单,但不增加审批层级。` : ""}
|
|
|
|
|
{selectedBatch.cancelledReason ? ` 取消原因:${selectedBatch.cancelledReason}` : ""}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
@@ -585,9 +544,9 @@ export function PaymentBatchesPage() {
|
|
|
|
|
<div className="flex flex-wrap items-start justify-between gap-3">
|
|
|
|
|
<div>
|
|
|
|
|
<h3 className="text-base font-semibold text-zinc-900">银行回单附件</h3>
|
|
|
|
|
<p className="mt-1 text-sm text-zinc-500">先在网银完成对公付款,再上传银行回单并执行 OA 已付款标记。</p>
|
|
|
|
|
<p className="mt-1 text-sm text-zinc-500">先在线下银行完成付款,再上传回单;系统不会连接银行自动转账。</p>
|
|
|
|
|
</div>
|
|
|
|
|
{canManageBatch ? (
|
|
|
|
|
{canUploadReceipt ? (
|
|
|
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
|
|
|
<input
|
|
|
|
|
ref={receiptInputRef}
|
|
|
|
@@ -596,35 +555,35 @@ export function PaymentBatchesPage() {
|
|
|
|
|
className="hidden"
|
|
|
|
|
accept=".pdf,image/jpeg,image/png,image/webp"
|
|
|
|
|
multiple
|
|
|
|
|
disabled={!canEditReceiptAttachments || uploadReceiptMutation.isPending}
|
|
|
|
|
onChange={(event) => setSelectedReceiptFiles(Array.from(event.target.files ?? []))}
|
|
|
|
|
disabled={uploadReceiptMutation.isPending}
|
|
|
|
|
onChange={(event) => {
|
|
|
|
|
const files = Array.from(event.target.files ?? []);
|
|
|
|
|
if (files.length) uploadReceiptMutation.mutate(files);
|
|
|
|
|
}}
|
|
|
|
|
/>
|
|
|
|
|
<Button
|
|
|
|
|
type="button"
|
|
|
|
|
variant="secondary"
|
|
|
|
|
disabled={!canEditReceiptAttachments || uploadReceiptMutation.isPending}
|
|
|
|
|
disabled={uploadReceiptMutation.isPending}
|
|
|
|
|
onClick={() => receiptInputRef.current?.click()}
|
|
|
|
|
>
|
|
|
|
|
选择回单
|
|
|
|
|
</Button>
|
|
|
|
|
<Button
|
|
|
|
|
type="button"
|
|
|
|
|
disabled={!canEditReceiptAttachments || uploadReceiptMutation.isPending || selectedReceiptFiles.length === 0}
|
|
|
|
|
onClick={() => uploadReceiptMutation.mutate()}
|
|
|
|
|
>
|
|
|
|
|
{uploadReceiptMutation.isPending ? "上传中..." : "上传"}
|
|
|
|
|
{uploadReceiptMutation.isPending ? "上传中..." : "上传银行回单"}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
) : null}
|
|
|
|
|
</div>
|
|
|
|
|
{selectedReceiptFiles.length ? (
|
|
|
|
|
<div className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-sm text-zinc-600">
|
|
|
|
|
待上传:{selectedReceiptFiles.map((file) => file.name).join("、")}
|
|
|
|
|
</div>
|
|
|
|
|
) : null}
|
|
|
|
|
{receiptAttachmentsQuery.isError ? (
|
|
|
|
|
<div className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
|
|
|
|
|
{resolveError(receiptAttachmentsQuery.error)}
|
|
|
|
|
<div className="flex flex-wrap items-center justify-between gap-2 rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">
|
|
|
|
|
<span>{resolveError(receiptAttachmentsQuery.error)}</span>
|
|
|
|
|
<Button
|
|
|
|
|
type="button"
|
|
|
|
|
size="sm"
|
|
|
|
|
variant="secondary"
|
|
|
|
|
disabled={receiptAttachmentsQuery.isFetching}
|
|
|
|
|
onClick={() => void receiptAttachmentsQuery.refetch()}
|
|
|
|
|
>
|
|
|
|
|
{receiptAttachmentsQuery.isFetching ? "重新加载中..." : "重新加载"}
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
) : null}
|
|
|
|
|
<div className="space-y-2">
|
|
|
|
@@ -671,9 +630,9 @@ export function PaymentBatchesPage() {
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
))}
|
|
|
|
|
{!receiptAttachmentsQuery.isLoading && !(receiptAttachmentsQuery.data?.items ?? []).length ? (
|
|
|
|
|
{receiptAttachmentsQuery.isSuccess && !(receiptAttachmentsQuery.data?.items ?? []).length ? (
|
|
|
|
|
<div className="rounded-lg border border-zinc-200 px-3 py-3 text-sm text-zinc-500">
|
|
|
|
|
暂无银行回单附件;默认银行渠道执行前需要上传至少一份回单。
|
|
|
|
|
暂无银行回单;银行转账必须先上传回单,才能登记付款完成。
|
|
|
|
|
</div>
|
|
|
|
|
) : null}
|
|
|
|
|
</div>
|
|
|
|
@@ -752,7 +711,7 @@ export function PaymentBatchesPage() {
|
|
|
|
|
>
|
|
|
|
|
<div className="flex items-center justify-between gap-3">
|
|
|
|
|
<div className="font-medium text-zinc-900">{item.batchNo}</div>
|
|
|
|
|
<StatusBadge status={item.status} label={batchStatusLabel(item.status)} />
|
|
|
|
|
<StatusBadge status={item.status} label={paymentBatchStatusLabel(item.status)} />
|
|
|
|
|
</div>
|
|
|
|
|
<div className="mt-1 text-sm text-zinc-600">{item.name}</div>
|
|
|
|
|
<div className="mt-1 text-xs text-zinc-500">
|
|
|
|
@@ -790,7 +749,7 @@ export function PaymentBatchesPage() {
|
|
|
|
|
</td>
|
|
|
|
|
<td className="table-td">
|
|
|
|
|
<div className="flex flex-wrap items-center gap-2">
|
|
|
|
|
<StatusBadge status={item.status} label={batchStatusLabel(item.status)} />
|
|
|
|
|
<StatusBadge status={item.status} label={paymentBatchStatusLabel(item.status)} />
|
|
|
|
|
{item.highRisk ? <span className="badge-warning">高风险</span> : null}
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
@@ -834,8 +793,8 @@ export function PaymentBatchesPage() {
|
|
|
|
|
? `该批次金额为 ${confirmPaymentAction.totalAmount},请核对付款对象和金额。确认后完成一次独立复核,不增加第二审批层级。`
|
|
|
|
|
: "确认后将完成一次独立复核并写入付款批次审计记录。"
|
|
|
|
|
: confirmPaymentAction?.highRisk
|
|
|
|
|
? `这是高额付款批次,金额 ${confirmPaymentAction.totalAmount}。请再次核对付款对象、金额、渠道和回单;确认后将更新源单付款状态。`
|
|
|
|
|
: "确认后将执行付款批次并影响付款状态,请确认批次、金额和回单均已核对。"
|
|
|
|
|
? `这是高额付款批次,金额 ${confirmPaymentAction.totalAmount}。请再次核对付款对象、金额、渠道和回单;确认后将登记付款完成并更新源单状态。`
|
|
|
|
|
: "请确认线下付款和银行回单均已核对。确认后将登记付款完成并更新源单状态。"
|
|
|
|
|
}
|
|
|
|
|
onConfirm={async () => {
|
|
|
|
|
if (!confirmPaymentAction) return;
|
|
|
|
|