fix: 优化报销制单下载与付款进度(09-08-reimbursement-payment-closeout)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-09-10 16:39:01 +08:00
父节点 73fcd5a65a
当前提交 fe025e65cf
共修改 15 个文件,包含 364 行新增和 42 行删除
+11 -4
查看文件
@@ -87,6 +87,8 @@ const REASON_SCHEMA = z.object({
reason: z.string().trim().min(2).max(500),
}).strict();
const PAYMENT_ORDER_SCHEMA = REASON_SCHEMA.partial();
const RECEIPT_FILE_QUERY_SCHEMA = z.object({
mode: z.enum(["download", "preview"]).default("download"),
reason: z.string().max(500).optional(),
@@ -1642,7 +1644,12 @@ async function paymentBatchesRoutes(fastify) {
await ensureFeatureEnabled(fastify);
ensurePaymentOrderExportPermission(fastify, request);
const batchId = parseId(request.params?.id);
const { reason } = parseBody(REASON_SCHEMA, request.body);
const { reason } = parseBody(PAYMENT_ORDER_SCHEMA, request.body ?? {});
const downloadContext = {
purpose: "reimbursement_bank_transfer",
purposeSource: "system",
...(reason === undefined ? {} : { reason }),
};
let result;
try {
@@ -1675,7 +1682,7 @@ async function paymentBatchesRoutes(fastify) {
batchNo: batch.batch_no,
rowCount: generated.rowCount,
totalAmount: generated.totalAmount,
reason,
...downloadContext,
exportCount,
},
}),
@@ -1698,7 +1705,7 @@ async function paymentBatchesRoutes(fastify) {
action: "PAYMENT_ORDER_EXPORT_BLOCKED",
targetType: "payment_batch",
targetId: batchId,
extra: { reason, resultCode },
extra: { ...downloadContext, resultCode },
}),
}).catch((auditError) => {
fastify.log.error({ err: auditError, requestId: request.id }, "payment order block audit failed");
@@ -1716,7 +1723,7 @@ async function paymentBatchesRoutes(fastify) {
const safeBatchNo = String(result.batchNo).replace(/[^A-Za-z0-9-]/g, "_");
reply.header("content-type", "application/vnd.ms-excel");
reply.header("content-disposition", `attachment; filename="payment-order-${safeBatchNo}.xls"`);
reply.header("content-disposition", buildAttachmentContentDisposition(`报销制单_${safeBatchNo}.xls`));
reply.header("cache-control", "no-store");
reply.header("x-content-type-options", "nosniff");
return reply.send(result.buffer);