OAFRONT4 清理PC付款和工作台文案
这个提交包含在:
@@ -27,7 +27,9 @@ import { Button } from "../components/ui/button";
|
||||
import { Input } from "../components/ui/input";
|
||||
import { Label } from "../components/ui/label";
|
||||
import { RiskFlagsCard } from "../components/common/RiskFlagsCard";
|
||||
import { StatusBadge } from "../components/common/StatusBadge";
|
||||
import { downloadBlob } from "../lib/download";
|
||||
import { formatDisplayDate, formatDisplayDateTime } from "../lib/displayFormat";
|
||||
import { toast } from "sonner";
|
||||
|
||||
const PAYMENT_CHANNEL_OPTIONS: PaymentChannel[] = ["bank", "alipay", "wechat", "cash", "other"];
|
||||
@@ -47,6 +49,31 @@ function entityTypeLabel(value: string) {
|
||||
return value || "-";
|
||||
}
|
||||
|
||||
function paymentChannelLabel(value: PaymentChannel | string | null | undefined) {
|
||||
if (value === "bank") return "银行转账";
|
||||
if (value === "alipay") return "支付宝";
|
||||
if (value === "wechat") return "微信";
|
||||
if (value === "cash") return "现金";
|
||||
if (value === "other") return "其他";
|
||||
return value || "-";
|
||||
}
|
||||
|
||||
function paymentItemStatusLabel(value: string) {
|
||||
if (value === "pending") return "待付款";
|
||||
if (value === "paid") 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 "等待首轮复核";
|
||||
if (value === "second_review") return "等待二次复核";
|
||||
return "可执行付款标记";
|
||||
}
|
||||
|
||||
function formatFileSize(value: number) {
|
||||
if (!Number.isFinite(value) || value <= 0) return "-";
|
||||
if (value < 1024) return `${value} B`;
|
||||
@@ -54,11 +81,6 @@ function formatFileSize(value: number) {
|
||||
return `${(value / 1024 / 1024).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
function formatDateTime(value: string | null | undefined) {
|
||||
if (!value) return "-";
|
||||
return new Date(value).toLocaleString();
|
||||
}
|
||||
|
||||
function resolveError(error: unknown) {
|
||||
return isApiError(error)
|
||||
? `${error.code}: ${mapApiErrorMessage(error.code, error.details, error.message)}`
|
||||
@@ -303,7 +325,7 @@ export function PaymentBatchesPage() {
|
||||
|
||||
return (
|
||||
<ListWorkspace>
|
||||
<WorkspaceHeader title="支付批次" subtitle="财务批量付款、复核与执行;高风险批次达到 5 万元后需完成二次复核。" />
|
||||
<WorkspaceHeader title="付款批次" subtitle="线下银行付款后的 OA 复核、回单留存和已付款标记;高风险批次达到 5 万元后需完成二次复核。" />
|
||||
|
||||
{error ? <div className="rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-sm text-red-600">{error}</div> : null}
|
||||
|
||||
@@ -337,13 +359,13 @@ export function PaymentBatchesPage() {
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label>实体 ID</Label>
|
||||
<Label>待付款单据 ID</Label>
|
||||
<textarea
|
||||
className="min-h-28 w-full rounded-lg border border-zinc-300 px-3 py-2 text-sm disabled:bg-zinc-50"
|
||||
value={entityIds}
|
||||
disabled={!canManageBatch}
|
||||
onChange={(event) => setEntityIds(event.target.value)}
|
||||
placeholder="用逗号或换行分隔 reimbursement / travel_order ID"
|
||||
placeholder="输入报销或差旅待付款单据 ID,多个用逗号或换行分隔"
|
||||
/>
|
||||
</div>
|
||||
<div className="grid gap-3 md:grid-cols-2">
|
||||
@@ -365,7 +387,7 @@ export function PaymentBatchesPage() {
|
||||
onChange={(event) => setDefaultPaymentChannel((event.target.value as PaymentChannel | "") || "") }
|
||||
>
|
||||
<option value="">不设置</option>
|
||||
{PAYMENT_CHANNEL_OPTIONS.map((item) => <option key={item} value={item}>{item}</option>)}
|
||||
{PAYMENT_CHANNEL_OPTIONS.map((item) => <option key={item} value={item}>{paymentChannelLabel(item)}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -400,7 +422,7 @@ export function PaymentBatchesPage() {
|
||||
>
|
||||
<div className="flex items-center justify-between gap-3">
|
||||
<div className="font-medium text-zinc-900">{item.batchNo}</div>
|
||||
<div className="text-xs uppercase text-zinc-500">{item.status}</div>
|
||||
<StatusBadge status={item.status} label={batchStatusLabel(item.status)} />
|
||||
</div>
|
||||
<div className="mt-1 text-sm text-zinc-600">{item.name}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">
|
||||
@@ -410,7 +432,7 @@ export function PaymentBatchesPage() {
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
{!listQuery.data?.items?.length ? <div className="text-sm text-zinc-500">暂无支付批次</div> : null}
|
||||
{!listQuery.data?.items?.length ? <div className="text-sm text-zinc-500">暂无付款批次</div> : null}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -475,13 +497,13 @@ export function PaymentBatchesPage() {
|
||||
<div>状态:{batchStatusLabel(selectedBatch.status)}</div>
|
||||
<div>实体:{entityTypeLabel(selectedBatch.entityType)}</div>
|
||||
<div>总金额:{selectedBatch.totalAmount}</div>
|
||||
<div>默认日期:{selectedBatch.defaultPaymentDate ?? "-"}</div>
|
||||
<div>默认渠道:{selectedBatch.defaultPaymentChannel ?? "-"}</div>
|
||||
<div>默认日期:{formatDisplayDate(selectedBatch.defaultPaymentDate)}</div>
|
||||
<div>默认渠道:{paymentChannelLabel(selectedBatch.defaultPaymentChannel)}</div>
|
||||
<div>复核进度:{selectedBatch.reviewRequirement.completedReviewCount}/{selectedBatch.reviewRequirement.requiredReviewCount}</div>
|
||||
</div>
|
||||
<div className="rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-3 text-sm text-zinc-600">
|
||||
当前要求:{selectedBatch.reviewRequirement.requiredReviewCount === 2 ? `批次金额达到 ${selectedBatch.reviewRequirement.thresholdAmount},需两轮复核` : "单轮复核后可执行"}。
|
||||
下一步:{selectedBatch.reviewRequirement.nextStep === "first_review" ? "等待首轮复核" : selectedBatch.reviewRequirement.nextStep === "second_review" ? "等待二次复核" : "可执行"}。
|
||||
下一步:{reviewNextStepLabel(selectedBatch.reviewRequirement.nextStep)}。
|
||||
{selectedBatch.reviewedBy ? ` 首轮复核人:${selectedBatch.reviewedBy}` : ""}
|
||||
{selectedBatch.secondaryReviewedBy ? ` 二次复核人:${selectedBatch.secondaryReviewedBy}` : ""}
|
||||
</div>
|
||||
@@ -538,7 +560,7 @@ export function PaymentBatchesPage() {
|
||||
<div className="min-w-0">
|
||||
<div className="truncate text-sm font-medium text-zinc-900">{attachment.originalName}</div>
|
||||
<div className="mt-1 text-xs text-zinc-500">
|
||||
{attachment.mimeType} · {formatFileSize(attachment.fileSize)} · {formatDateTime(attachment.createdAt)}
|
||||
{attachment.mimeType} · {formatFileSize(attachment.fileSize)} · {formatDisplayDateTime(attachment.createdAt)}
|
||||
{attachment.uploadedBy ? ` · ${attachment.uploadedBy.name}` : ""}
|
||||
</div>
|
||||
</div>
|
||||
@@ -584,7 +606,7 @@ export function PaymentBatchesPage() {
|
||||
<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} · {item.status}</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
|
||||
@@ -606,9 +628,11 @@ export function PaymentBatchesPage() {
|
||||
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}>{option}</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 ?? "-"}</div>
|
||||
<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>
|
||||
|
||||
在新工单中引用
屏蔽一个用户