fix: 优化报销制单下载与付款进度(09-08-reimbursement-payment-closeout)
这个提交包含在:
@@ -2559,7 +2559,7 @@ describe("payment batch reimbursement routes", () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it("exports a reviewed CCB batch as BIFF8 and records the first export", async () => {
|
||||
it.each([{}, { reason: "导入企业网银" }])("exports BIFF8 without requiring a reason and preserves legacy input: %j", async (payload) => {
|
||||
const payerEncrypted = encryptStringOrThrow(
|
||||
{ fieldEncryptionKey: FIELD_ENCRYPTION_KEY },
|
||||
`6217${"0".repeat(8)}1234`,
|
||||
@@ -2603,14 +2603,28 @@ describe("payment batch reimbursement routes", () => {
|
||||
method: "POST",
|
||||
url: `/api/payment-batches/${BATCH_ID}/payment-order`,
|
||||
headers: { authorization: "Bearer test" },
|
||||
payload: { reason: "导入企业网银" },
|
||||
payload,
|
||||
});
|
||||
expect(response.statusCode).toBe(200);
|
||||
expect(response.rawPayload.subarray(0, 8).toString("hex")).toBe("d0cf11e0a1b11ae1");
|
||||
expect(response.headers["cache-control"]).toBe("no-store");
|
||||
expect(decodeURIComponent(response.headers["content-disposition"])).toContain("报销制单_");
|
||||
expect(state.batch.payment_order_export_count).toBe(1);
|
||||
expect(tx.auditLog.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({ action: "PAYMENT_ORDER_EXPORTED", extra: expect.objectContaining({ reason: "导入企业网银", exportCount: 1 }) }),
|
||||
data: expect.objectContaining({ action: "PAYMENT_ORDER_EXPORTED", extra: expect.objectContaining({ purpose: "reimbursement_bank_transfer", purposeSource: "system", exportCount: 1, ...payload }) }),
|
||||
});
|
||||
const firstAudit = tx.auditLog.create.mock.calls.find(([args]) => args.data.action === "PAYMENT_ORDER_EXPORTED")[0].data.extra;
|
||||
if (!payload.reason) expect(firstAudit).not.toHaveProperty("reason");
|
||||
const repeated = await app.inject({
|
||||
method: "POST",
|
||||
url: `/api/payment-batches/${BATCH_ID}/payment-order`,
|
||||
headers: { authorization: "Bearer test" },
|
||||
payload: {},
|
||||
});
|
||||
expect(repeated.statusCode).toBe(200);
|
||||
expect(state.batch.payment_order_export_count).toBe(2);
|
||||
expect(tx.auditLog.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({ action: "PAYMENT_ORDER_REEXPORTED", extra: expect.objectContaining({ purposeSource: "system", exportCount: 2 }) }),
|
||||
});
|
||||
await app.close();
|
||||
});
|
||||
@@ -2713,6 +2727,31 @@ describe("payment batch reimbursement routes", () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it.each([{ reason: "" }, { reason: "x" }, { purposeSource: "user" }])("rejects invalid optional download input: %j", async (payload) => {
|
||||
const { app, tx } = await buildApp({
|
||||
actor: { id: ACTOR_ID, department_id: "dept-1", permissionCodes: ["payment.batch.manage", "payment.order.export"], roleCodes: ["finance"] },
|
||||
});
|
||||
const response = await app.inject({
|
||||
method: "POST", url: `/api/payment-batches/${BATCH_ID}/payment-order`,
|
||||
headers: { authorization: "Bearer test" }, payload,
|
||||
});
|
||||
expect(response.statusCode).toBe(400);
|
||||
expect(tx.paymentBatch.update).not.toHaveBeenCalled();
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it.each(["cancel", `items/${ITEM_ID}/payee-bank/reveal`])("still requires reasons for %s", async (action) => {
|
||||
const { app } = await buildApp({
|
||||
actor: { id: ACTOR_ID, department_id: "dept-1", permissionCodes: ["*"], roleCodes: ["finance"] },
|
||||
});
|
||||
const response = await app.inject({
|
||||
method: "POST", url: `/api/payment-batches/${BATCH_ID}/${action}`,
|
||||
headers: { authorization: "Bearer test" }, payload: {},
|
||||
});
|
||||
expect(response.statusCode).toBe(400);
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it.each(["draft", "failed", "cancelled", "executed"])(
|
||||
"rejects direct payment-order export while the batch is %s",
|
||||
async (status) => {
|
||||
|
||||
在新工单中引用
屏蔽一个用户