fix: 优化报销制单下载与付款进度(09-08-reimbursement-payment-closeout)
这个提交包含在:
@@ -165,6 +165,15 @@ function parseFilenameFromContentDisposition(headerValue: string | null): string
|
||||
}
|
||||
|
||||
const parts = headerValue.split(";").map((part) => part.trim());
|
||||
const encodedPart = parts.find((part) => part.toLowerCase().startsWith("filename*="));
|
||||
const encodedValue = encodedPart?.slice("filename*=".length).trim().replace(/^"|"$/g, "");
|
||||
const utf8Value = encodedValue?.match(/^UTF-8'[^']*'(.*)$/i);
|
||||
if (utf8Value) {
|
||||
try {
|
||||
const decoded = decodeURIComponent(utf8Value[1]);
|
||||
if (decoded) return decoded;
|
||||
} catch {}
|
||||
}
|
||||
const filenamePart = parts.find((part) => part.toLowerCase().startsWith("filename="));
|
||||
if (!filenamePart) {
|
||||
return null;
|
||||
|
||||
@@ -215,11 +215,11 @@ export async function cancelPaymentBatch(batchId: string, reason: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export async function downloadPaymentOrder(batchId: string, reason: string): Promise<ApiBlobResponse> {
|
||||
export async function downloadPaymentOrder(batchId: string): Promise<ApiBlobResponse> {
|
||||
return apiRequestBlob(`/api/payment-batches/${batchId}/payment-order`, {
|
||||
method: "POST",
|
||||
headers: { "content-type": "application/json" },
|
||||
body: JSON.stringify({ reason }),
|
||||
body: JSON.stringify({}),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -561,10 +561,10 @@ export function PaymentBatchesPage() {
|
||||
});
|
||||
|
||||
const paymentOrderMutation = useMutation({
|
||||
mutationFn: ({ batchId, reason }: { batchId: string; reason: string }) =>
|
||||
downloadPaymentOrder(batchId, reason),
|
||||
onSuccess: async (response) => {
|
||||
downloadBlob(response.blob, response.filename ?? "payment-order.xls");
|
||||
mutationFn: ({ batchId }: { batchId: string; batchNo: string }) =>
|
||||
downloadPaymentOrder(batchId),
|
||||
onSuccess: async (response, variables) => {
|
||||
downloadBlob(response.blob, response.filename ?? `报销制单_${variables.batchNo.replace(/[^A-Za-z0-9-]/g, "_")}.xls`);
|
||||
setReasonAction(null);
|
||||
setActionReason("");
|
||||
await refresh();
|
||||
@@ -864,15 +864,6 @@ export function PaymentBatchesPage() {
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div className="finance-command-panel space-y-3" data-testid="payment-batch-processing-panel">
|
||||
<h3 className="text-base font-semibold text-zinc-900">处理进度</h3>
|
||||
<ProcessingProgress
|
||||
progress={selectedBatch.processingProgress}
|
||||
ariaLabel="付款批次处理进度"
|
||||
testId="payment-batch-processing-progress"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="finance-command-panel space-y-4" data-testid="payment-batch-items-panel">
|
||||
<h3 className="text-base font-semibold text-zinc-900">
|
||||
{selectedBatch.entityType === "reimbursement" ? "本批次报销单" : "本批次差旅订单"}
|
||||
@@ -1080,8 +1071,9 @@ export function PaymentBatchesPage() {
|
||||
</div>
|
||||
</BusinessDetailMain>
|
||||
|
||||
<BusinessDetailAside className="hidden lg:block">
|
||||
<BusinessDetailAside>
|
||||
<BusinessDetailActionPanel
|
||||
className="lg:static"
|
||||
status={<StatusBadge status={selectedBatch.status} label={paymentBatchStatusLabel(selectedBatch.status)} />}
|
||||
primaryAction={paymentActionModel.primary ? renderPaymentActionButton(paymentActionModel.primary) : undefined}
|
||||
moreActions={paymentActionModel.more.length ? (
|
||||
@@ -1102,6 +1094,14 @@ export function PaymentBatchesPage() {
|
||||
)}
|
||||
testId="payment-batch-action-panel"
|
||||
/>
|
||||
<div className="finance-command-panel space-y-3" data-testid="payment-batch-processing-panel">
|
||||
<h3 className="text-base font-semibold text-zinc-900">处理进度</h3>
|
||||
<ProcessingProgress
|
||||
progress={selectedBatch.processingProgress}
|
||||
ariaLabel="付款批次处理进度"
|
||||
testId="payment-batch-processing-progress"
|
||||
/>
|
||||
</div>
|
||||
</BusinessDetailAside>
|
||||
</BusinessDetailWorkspace>
|
||||
|
||||
@@ -1316,20 +1316,20 @@ export function PaymentBatchesPage() {
|
||||
</DialogHeader>
|
||||
{reasonAction?.kind === "payeeBank" && revealedPayeeAccount ? (
|
||||
<div className="mt-4 rounded-lg border border-amber-200 bg-amber-50 p-3 font-mono text-sm text-amber-900 break-all">{revealedPayeeAccount}</div>
|
||||
) : <div className="mt-4 space-y-2">
|
||||
) : reasonAction?.kind !== "paymentOrder" ? <div className="mt-4 space-y-2">
|
||||
<Textarea
|
||||
value={actionReason}
|
||||
onChange={(event) => setActionReason(event.target.value)}
|
||||
aria-label={reasonAction?.kind === "voidReceipt" ? "回单作废原因" : reasonAction?.kind === "paymentOrder" ? "制单下载原因" : reasonAction?.kind === "payeeBank" ? "查看收款账号原因" : "批次取消原因"}
|
||||
aria-label={reasonAction?.kind === "voidReceipt" ? "回单作废原因" : reasonAction?.kind === "payeeBank" ? "查看收款账号原因" : "批次取消原因"}
|
||||
rows={4}
|
||||
maxLength={500}
|
||||
placeholder={reasonAction?.kind === "voidReceipt" ? "说明回单为何作废" : reasonAction?.kind === "paymentOrder" ? "说明本次制单用途" : reasonAction?.kind === "payeeBank" ? "说明查看完整账号的业务用途" : "说明批次为何不再继续"}
|
||||
placeholder={reasonAction?.kind === "voidReceipt" ? "说明回单为何作废" : reasonAction?.kind === "payeeBank" ? "说明查看完整账号的业务用途" : "说明批次为何不再继续"}
|
||||
/>
|
||||
<div className="flex items-center justify-between text-xs text-zinc-500">
|
||||
<span>原因需填写 2–500 个字符,并写入审计记录。</span>
|
||||
<span>{actionReason.trim().length}/500</span>
|
||||
</div>
|
||||
</div>}
|
||||
</div> : null}
|
||||
<DialogFooter>
|
||||
<Button type="button" variant="secondary" onClick={() => {
|
||||
setReasonAction(null);
|
||||
@@ -1341,7 +1341,7 @@ export function PaymentBatchesPage() {
|
||||
type="button"
|
||||
variant={reasonAction?.kind === "paymentOrder" ? "default" : "destructive"}
|
||||
disabled={
|
||||
actionReason.trim().length < 2 ||
|
||||
(reasonAction?.kind !== "paymentOrder" && actionReason.trim().length < 2) ||
|
||||
cancelMutation.isPending ||
|
||||
voidReceiptMutation.isPending ||
|
||||
paymentOrderMutation.isPending ||
|
||||
@@ -1353,7 +1353,7 @@ export function PaymentBatchesPage() {
|
||||
if (reasonAction.kind === "cancel") {
|
||||
cancelMutation.mutate({ batchId: reasonAction.batchId, reason });
|
||||
} else if (reasonAction.kind === "paymentOrder") {
|
||||
paymentOrderMutation.mutate({ batchId: reasonAction.batchId, reason });
|
||||
paymentOrderMutation.mutate({ batchId: reasonAction.batchId, batchNo: reasonAction.batchNo });
|
||||
} else if (reasonAction.kind === "payeeBank") {
|
||||
revealPayeeBankMutation.mutate({ batchId: reasonAction.batchId, itemId: reasonAction.itemId, reason });
|
||||
} else {
|
||||
|
||||
在新工单中引用
屏蔽一个用户