收口付款批次创建与复核(08-01-reimbursement-notification-progress-clarity)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-08-02 00:29:47 +08:00
父节点 78b80f88d7
当前提交 7c39f8b0f9
共修改 17 个文件,包含 591 行新增和 227 行删除
@@ -182,15 +182,22 @@ function paymentBatch(status: "draft" | "reviewed" = "reviewed") {
async function mockSystemActionApis(
page: Page,
options: { batchStatus?: "draft" | "reviewed"; meOverride?: Partial<typeof me> } = {},
options: {
batchStatus?: "draft" | "reviewed";
hasReceipt?: boolean;
receiptQueryError?: boolean;
meOverride?: Partial<typeof me>;
} = {},
) {
const currentMe = { ...me, ...options.meOverride };
const batchStatus = options.batchStatus ?? "reviewed";
let hasReceipt = options.hasReceipt ?? true;
const calls = {
deleteReimbursement: 0,
deleteReimbursementAttachment: 0,
exportReimbursements: 0,
executePaymentBatch: 0,
uploadPaymentReceipt: 0,
voidPaymentReceipt: 0,
attachmentDeleteReasons: [] as string[],
receiptVoidReasons: [] as string[],
@@ -327,8 +334,19 @@ async function mockSystemActionApis(
}
if (path === `/api/payment-batches/${paymentBatchId}/receipt-attachments` && request.method() === "GET") {
if (options.receiptQueryError) {
await route.fulfill({
status: 503,
contentType: "application/json",
body: JSON.stringify({
requestId: "mock-system-action-confirm",
error: { code: "SYS_001", message: "回单状态暂时不可用" },
}),
});
return;
}
await fulfillJson(route, {
items: [
items: hasReceipt ? [
{
id: paymentReceiptId,
originalName: "合成银行回单.pdf",
@@ -348,11 +366,18 @@ async function mockSystemActionApis(
uploadedBy: { id: me.id, employeeNo: me.employeeNo, name: me.name },
createdAt: "2026-05-15T08:20:00.000Z",
},
],
] : [],
});
return;
}
if (path === `/api/payment-batches/${paymentBatchId}/receipt-attachments` && request.method() === "POST") {
calls.uploadPaymentReceipt += 1;
hasReceipt = true;
await fulfillJson(route, { items: [{ id: paymentReceiptId }] });
return;
}
if (
path === `/api/payment-batches/${paymentBatchId}/receipt-attachments/${paymentReceiptId}/void` &&
request.method() === "POST"
@@ -419,18 +444,37 @@ test.describe("R7E 全站高风险操作二次确认", () => {
await page.goto(`/payment-batches?id=${paymentBatchId}`);
await expect(page.getByText("PAY-ACTION-001").first()).toBeVisible();
await page.getByRole("button", { name: "执行" }).click();
await expect(page.getByTestId("action-confirm-dialog")).toContainText("确认后将执行付款批次");
await page.getByRole("button", { name: "登记付款完成" }).click();
await expect(page.getByTestId("action-confirm-dialog")).toContainText("登记付款完成");
expect(calls.executePaymentBatch).toBe(0);
await page.getByTestId("action-confirm-cancel").click();
expect(calls.executePaymentBatch).toBe(0);
await page.getByRole("button", { name: "执行" }).click();
await page.getByRole("button", { name: "登记付款完成" }).click();
await page.getByTestId("action-confirm-submit").click();
await expect.poll(() => calls.executePaymentBatch).toBe(1);
});
test("银行回单上传前后只显示一个付款主动作", async ({ page }) => {
const calls = await mockSystemActionApis(page, { hasReceipt: false });
await login(page, user);
await page.goto(`/payment-batches?id=${paymentBatchId}`);
await expect(page.getByRole("button", { name: "上传银行回单" }).first()).toBeVisible();
await expect(page.getByRole("button", { name: "登记付款完成" })).toHaveCount(0);
await page.getByTestId("payment-batch-receipt-file-input").setInputFiles({
name: "合成银行回单.pdf",
mimeType: "application/pdf",
buffer: Buffer.from("%PDF-1.4\nsynthetic receipt\n%%EOF\n", "utf8"),
});
await expect.poll(() => calls.uploadPaymentReceipt).toBe(1);
await expect(page.getByRole("button", { name: "登记付款完成" })).toBeVisible();
await expect(page.getByTestId("payment-batch-detail-panel").getByRole("button", { name: "上传银行回单" })).toHaveCount(0);
});
test("高敏附件删除要求对象、后果和理由,取消与理由不足均为零请求", async ({ page }) => {
const calls = await mockSystemActionApis(page);
await login(page, user);
@@ -493,6 +537,33 @@ test.describe("R7E 全站高风险操作二次确认", () => {
await login(page, user);
await page.goto(`/payment-batches?id=${paymentBatchId}`);
await expect(page.getByText("PAY-ACTION-001").first()).toBeVisible();
await expect(page.getByRole("button", { name: "复核", exact: true })).toHaveCount(0);
await expect(page.getByRole("button", { name: "复核通过", exact: true })).toHaveCount(0);
});
test("兼任付款复核职责的制单人也不显示自审动作", async ({ page }) => {
await mockSystemActionApis(page, {
batchStatus: "draft",
meOverride: {
roleCodes: ["manager", "finance"],
permissions: [...me.permissions, "payment.batch.review"],
},
});
await login(page, user);
await page.goto(`/payment-batches?id=${paymentBatchId}`);
await expect(page.getByText("制单人不能复核自己的批次")).toBeVisible();
await expect(page.getByRole("button", { name: "复核通过", exact: true })).toHaveCount(0);
});
test("回单查询失败时不把未知状态误报为空且不显示付款动作", async ({ page }) => {
await mockSystemActionApis(page, { receiptQueryError: true });
await login(page, user);
await page.goto(`/payment-batches?id=${paymentBatchId}`);
await expect(page.getByText(/服务暂不可用/)).toBeVisible();
await expect(page.getByRole("button", { name: "重新加载" })).toBeVisible();
await expect(page.getByText(/暂无银行回单/)).toHaveCount(0);
await expect(page.getByRole("button", { name: "上传银行回单" })).toHaveCount(0);
await expect(page.getByRole("button", { name: "登记付款完成" })).toHaveCount(0);
});
});