|
|
|
@@ -80,21 +80,50 @@ function reimbursementListItem() {
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function reimbursementDetail() {
|
|
|
|
|
function reimbursementDetail(status: "draft" | "reviewing" | "paid" = "draft") {
|
|
|
|
|
const workflowTaskId = "workflow-task-action-confirm";
|
|
|
|
|
return {
|
|
|
|
|
...reimbursementListItem(),
|
|
|
|
|
status,
|
|
|
|
|
submitterId: me.id,
|
|
|
|
|
ownerId: me.id,
|
|
|
|
|
departmentId: "department-action-confirm",
|
|
|
|
|
department: { id: "department-action-confirm", name: "合成部门" },
|
|
|
|
|
submittedAt: null,
|
|
|
|
|
travelSummary: { isTravel: false, purpose: null, location: null, startDate: null, endDate: null },
|
|
|
|
|
projectId: null,
|
|
|
|
|
linkedTravelRequestId: null,
|
|
|
|
|
linkedTravelRequest: null,
|
|
|
|
|
owner: { id: me.id, employee_no: me.employeeNo, name: me.name },
|
|
|
|
|
submitter: { id: me.id, employee_no: me.employeeNo, name: me.name },
|
|
|
|
|
items: [],
|
|
|
|
|
invoices: [],
|
|
|
|
|
items: [
|
|
|
|
|
{
|
|
|
|
|
id: "reimbursement-item-action-confirm",
|
|
|
|
|
expenseTypeId: "expense-type-action-confirm",
|
|
|
|
|
expenseDate: "2026-05-15",
|
|
|
|
|
amount: "88.00",
|
|
|
|
|
description: "合成报销动作回归",
|
|
|
|
|
expenseType: { id: "expense-type-action-confirm", code: "office", name: "办公费", category: "office", enabled: true },
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
invoices: [
|
|
|
|
|
{
|
|
|
|
|
id: "invoice-action-confirm",
|
|
|
|
|
status: "bound",
|
|
|
|
|
invoiceCode: "SYNTHETIC",
|
|
|
|
|
invoiceNumber: "ACTION001",
|
|
|
|
|
invoiceDate: "2026-05-15",
|
|
|
|
|
sellerName: "合成供应方",
|
|
|
|
|
buyerName: "合成购买方",
|
|
|
|
|
documentCategory: "vat_invoice",
|
|
|
|
|
documentSubtype: null,
|
|
|
|
|
totalAmount: "88.00",
|
|
|
|
|
originalName: "合成动作发票.pdf",
|
|
|
|
|
mimeType: "application/pdf",
|
|
|
|
|
fileSize: 1024,
|
|
|
|
|
linkedItemId: "reimbursement-item-action-confirm",
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
attachments: [
|
|
|
|
|
{
|
|
|
|
|
id: reimbursementAttachmentId,
|
|
|
|
@@ -117,14 +146,53 @@ function reimbursementDetail() {
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
budgetHint: null,
|
|
|
|
|
pendingWorkflow: null,
|
|
|
|
|
pendingWorkflow: status === "reviewing" ? {
|
|
|
|
|
instanceId: "workflow-instance-action-confirm",
|
|
|
|
|
stepId: "workflow-step-action-confirm",
|
|
|
|
|
task: {
|
|
|
|
|
id: workflowTaskId,
|
|
|
|
|
step_name: "负责人复核",
|
|
|
|
|
assignee_id: "another-manager",
|
|
|
|
|
assignee_role: "manager",
|
|
|
|
|
},
|
|
|
|
|
} : null,
|
|
|
|
|
workflowProgress: null,
|
|
|
|
|
processingProgress: { stages: [] },
|
|
|
|
|
approvalHistory: [],
|
|
|
|
|
riskFlags: [],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function paymentBatch(status: "draft" | "reviewed" | "executed" | "cancelled" = "reviewed", highRisk = false) {
|
|
|
|
|
type SyntheticPaymentBatchStatus = "draft" | "reviewed" | "executed" | "failed" | "cancelled";
|
|
|
|
|
|
|
|
|
|
function paymentProcessingProgress(status: SyntheticPaymentBatchStatus, hasReceipt: boolean, requiresBankReceipt = true) {
|
|
|
|
|
const reviewState = status === "draft" ? "current" : status === "cancelled" ? "exception" : "completed";
|
|
|
|
|
const paymentState = status === "executed"
|
|
|
|
|
? "completed"
|
|
|
|
|
: status === "failed"
|
|
|
|
|
? "exception"
|
|
|
|
|
: status === "cancelled"
|
|
|
|
|
? "terminated"
|
|
|
|
|
: status === "reviewed"
|
|
|
|
|
? "current"
|
|
|
|
|
: "pending";
|
|
|
|
|
return {
|
|
|
|
|
stages: [
|
|
|
|
|
{ key: "payment_batch_created", label: "批次创建", state: "completed", actorName: me.name, occurredAt: "2026-05-15T08:00:00.000Z", result: "已创建", comment: null, detail: null, targetHref: null },
|
|
|
|
|
{ key: "payment_batch_review", label: "付款复核", state: reviewState, actorName: status === "draft" ? null : me.name, occurredAt: status === "draft" ? null : "2026-05-15T08:10:00.000Z", result: status === "draft" ? "待复核" : status === "cancelled" ? "批次已取消" : "通过", comment: status === "cancelled" ? "合成取消原因" : null, detail: status === "draft" ? "待非制单人复核付款" : null, targetHref: null },
|
|
|
|
|
{ key: "payment_and_receipt", label: "付款完成", state: paymentState, actorName: ["executed", "failed"].includes(status) ? me.name : null, occurredAt: ["executed", "failed"].includes(status) ? "2026-05-15T08:30:00.000Z" : null, result: status === "executed" ? "已付款" : status === "failed" ? "付款失败" : status === "cancelled" ? "已终止" : "待付款", comment: null, detail: status === "reviewed" ? (!requiresBankReceipt ? "等待财务登记付款完成,非审批环节" : hasReceipt ? "银行回单已上传,等待财务登记付款完成,非审批环节" : "等待财务线下付款并上传回单,非审批环节") : null, targetHref: null },
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function paymentBatch(
|
|
|
|
|
status: SyntheticPaymentBatchStatus = "reviewed",
|
|
|
|
|
highRisk = false,
|
|
|
|
|
hasReceipt = true,
|
|
|
|
|
paymentChannel: "bank" | "cash" = "bank",
|
|
|
|
|
canExportPaymentOrder = false,
|
|
|
|
|
paymentOrderExportCount = 0,
|
|
|
|
|
) {
|
|
|
|
|
return {
|
|
|
|
|
id: paymentBatchId,
|
|
|
|
|
batchNo: "PAY-ACTION-001",
|
|
|
|
@@ -132,10 +200,14 @@ function paymentBatch(status: "draft" | "reviewed" | "executed" | "cancelled" =
|
|
|
|
|
name: "R7E 高风险付款确认",
|
|
|
|
|
status,
|
|
|
|
|
defaultPaymentDate: "2026-05-15",
|
|
|
|
|
defaultPaymentChannel: "bank",
|
|
|
|
|
defaultPaymentChannel: paymentChannel,
|
|
|
|
|
voucherPrefix: "R7E",
|
|
|
|
|
totalAmount: "88.00",
|
|
|
|
|
highRisk,
|
|
|
|
|
paymentAccount: null,
|
|
|
|
|
paymentOrderExportCount,
|
|
|
|
|
canExportPaymentOrder,
|
|
|
|
|
paymentOrderExportBlockers: [],
|
|
|
|
|
reviewRequirement: {
|
|
|
|
|
thresholdAmount: "1000.00",
|
|
|
|
|
requiredReviewCount: 1,
|
|
|
|
@@ -158,6 +230,7 @@ function paymentBatch(status: "draft" | "reviewed" | "executed" | "cancelled" =
|
|
|
|
|
cancelledAt: null,
|
|
|
|
|
cancelledBy: null,
|
|
|
|
|
cancelledReason: null,
|
|
|
|
|
processingProgress: paymentProcessingProgress(status, hasReceipt, paymentChannel === "bank"),
|
|
|
|
|
items: [
|
|
|
|
|
{
|
|
|
|
|
id: "payment-batch-item-action-confirm",
|
|
|
|
@@ -172,7 +245,7 @@ function paymentBatch(status: "draft" | "reviewed" | "executed" | "cancelled" =
|
|
|
|
|
payeeName: "合成收款员工",
|
|
|
|
|
bankName: "合成银行",
|
|
|
|
|
bankAccountMasked: "6222********5678",
|
|
|
|
|
status: "pending",
|
|
|
|
|
status: status === "executed" ? "executed" : status === "failed" ? "failed" : status === "cancelled" ? "cancelled" : "pending",
|
|
|
|
|
failureCode: null,
|
|
|
|
|
failureMessage: null,
|
|
|
|
|
createdAt: "2026-05-15T08:00:00.000Z",
|
|
|
|
@@ -185,16 +258,21 @@ function paymentBatch(status: "draft" | "reviewed" | "executed" | "cancelled" =
|
|
|
|
|
async function mockSystemActionApis(
|
|
|
|
|
page: Page,
|
|
|
|
|
options: {
|
|
|
|
|
batchStatus?: "draft" | "reviewed" | "executed" | "cancelled";
|
|
|
|
|
batchStatus?: SyntheticPaymentBatchStatus;
|
|
|
|
|
hasReceipt?: boolean;
|
|
|
|
|
receiptQueryError?: boolean;
|
|
|
|
|
highRisk?: boolean;
|
|
|
|
|
paymentChannel?: "bank" | "cash";
|
|
|
|
|
canExportPaymentOrder?: boolean;
|
|
|
|
|
paymentOrderExportCount?: number;
|
|
|
|
|
reimbursementStatus?: "draft" | "reviewing" | "paid";
|
|
|
|
|
meOverride?: Partial<typeof me>;
|
|
|
|
|
} = {},
|
|
|
|
|
) {
|
|
|
|
|
const currentMe = { ...me, ...options.meOverride };
|
|
|
|
|
const batchStatus = options.batchStatus ?? "reviewed";
|
|
|
|
|
let hasReceipt = options.hasReceipt ?? true;
|
|
|
|
|
let currentReimbursementStatus = options.reimbursementStatus ?? "draft";
|
|
|
|
|
const calls = {
|
|
|
|
|
deleteReimbursement: 0,
|
|
|
|
|
deleteReimbursementAttachment: 0,
|
|
|
|
@@ -205,6 +283,10 @@ async function mockSystemActionApis(
|
|
|
|
|
voidPaymentReceipt: 0,
|
|
|
|
|
attachmentDeleteReasons: [] as string[],
|
|
|
|
|
receiptVoidReasons: [] as string[],
|
|
|
|
|
submitReimbursement: 0,
|
|
|
|
|
withdrawReimbursement: 0,
|
|
|
|
|
remindWorkflow: 0,
|
|
|
|
|
withdrawComments: [] as string[],
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
await page.route((url) => url.pathname.startsWith("/api/"), async (route) => {
|
|
|
|
@@ -288,7 +370,45 @@ async function mockSystemActionApis(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (path === `/api/reimbursements/${reimbursementId}` && request.method() === "GET") {
|
|
|
|
|
await fulfillJson(route, reimbursementDetail());
|
|
|
|
|
await fulfillJson(route, reimbursementDetail(currentReimbursementStatus));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (path === `/api/reimbursements/${reimbursementId}/precheck` && request.method() === "POST") {
|
|
|
|
|
await fulfillJson(route, {
|
|
|
|
|
entityType: "reimbursement",
|
|
|
|
|
entityId: reimbursementId,
|
|
|
|
|
decision: "pass",
|
|
|
|
|
matchedBudgetControlRule: null,
|
|
|
|
|
budgetHint: null,
|
|
|
|
|
standardChecks: [],
|
|
|
|
|
warnings: [],
|
|
|
|
|
blockingReasons: [],
|
|
|
|
|
riskFlags: [],
|
|
|
|
|
nextActions: ["submit"],
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (path === `/api/reimbursements/${reimbursementId}/submit` && request.method() === "POST") {
|
|
|
|
|
calls.submitReimbursement += 1;
|
|
|
|
|
currentReimbursementStatus = "reviewing";
|
|
|
|
|
await fulfillJson(route, { reimbursementId, status: "reviewing" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (path === `/api/reimbursements/${reimbursementId}/withdraw` && request.method() === "POST") {
|
|
|
|
|
calls.withdrawReimbursement += 1;
|
|
|
|
|
const body = request.postDataJSON() as { comment?: string };
|
|
|
|
|
calls.withdrawComments.push(body.comment ?? "");
|
|
|
|
|
currentReimbursementStatus = "draft";
|
|
|
|
|
await fulfillJson(route, { reimbursementId, status: "draft" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (path === "/api/workflow/tasks/workflow-task-action-confirm/remind" && request.method() === "POST") {
|
|
|
|
|
calls.remindWorkflow += 1;
|
|
|
|
|
await fulfillJson(route, { taskId: "workflow-task-action-confirm", remindedAt: "2026-05-15T09:00:00.000Z" });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -333,7 +453,14 @@ async function mockSystemActionApis(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (path === "/api/payment-batches" && request.method() === "GET") {
|
|
|
|
|
const listItem = paymentBatch(batchStatus, options.highRisk);
|
|
|
|
|
const listItem = paymentBatch(
|
|
|
|
|
batchStatus,
|
|
|
|
|
options.highRisk,
|
|
|
|
|
hasReceipt,
|
|
|
|
|
options.paymentChannel,
|
|
|
|
|
options.canExportPaymentOrder,
|
|
|
|
|
options.paymentOrderExportCount,
|
|
|
|
|
);
|
|
|
|
|
await fulfillJson(route, {
|
|
|
|
|
items: [{
|
|
|
|
|
...listItem,
|
|
|
|
@@ -350,7 +477,16 @@ async function mockSystemActionApis(
|
|
|
|
|
|
|
|
|
|
if (path === `/api/payment-batches/${paymentBatchId}` && request.method() === "GET") {
|
|
|
|
|
calls.paymentBatchDetail += 1;
|
|
|
|
|
await fulfillJson(route, { item: paymentBatch(batchStatus, options.highRisk) });
|
|
|
|
|
await fulfillJson(route, {
|
|
|
|
|
item: paymentBatch(
|
|
|
|
|
batchStatus,
|
|
|
|
|
options.highRisk,
|
|
|
|
|
hasReceipt,
|
|
|
|
|
options.paymentChannel,
|
|
|
|
|
options.canExportPaymentOrder,
|
|
|
|
|
options.paymentOrderExportCount,
|
|
|
|
|
),
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -412,7 +548,7 @@ async function mockSystemActionApis(
|
|
|
|
|
|
|
|
|
|
if (path === `/api/payment-batches/${paymentBatchId}/execute` && request.method() === "POST") {
|
|
|
|
|
calls.executePaymentBatch += 1;
|
|
|
|
|
await fulfillJson(route, { item: paymentBatch("reviewed", options.highRisk) });
|
|
|
|
|
await fulfillJson(route, { item: paymentBatch("reviewed", options.highRisk, hasReceipt, options.paymentChannel) });
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -455,6 +591,59 @@ test.describe("R7E 全站高风险操作二次确认", () => {
|
|
|
|
|
await expect.poll(() => calls.exportReimbursements).toBe(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("报销详情迁移后保留提交与删除的状态、确认和刷新合同", async ({ page }) => {
|
|
|
|
|
const calls = await mockSystemActionApis(page, {
|
|
|
|
|
reimbursementStatus: "draft",
|
|
|
|
|
meOverride: { permissions: [...me.permissions, "expense.submit"] },
|
|
|
|
|
});
|
|
|
|
|
await login(page, user);
|
|
|
|
|
await page.goto(`/reimbursements/${reimbursementId}`);
|
|
|
|
|
|
|
|
|
|
const actionPanel = page.getByTestId("reimbursement-action-panel");
|
|
|
|
|
await expect(actionPanel.getByRole("button", { name: "提交报销" })).toBeVisible();
|
|
|
|
|
await actionPanel.getByRole("button", { name: "提交报销" }).click();
|
|
|
|
|
await expect.poll(() => calls.submitReimbursement).toBe(1);
|
|
|
|
|
await expect(page.getByText("审批中", { exact: true }).first()).toBeVisible();
|
|
|
|
|
|
|
|
|
|
await page.unrouteAll({ behavior: "wait" });
|
|
|
|
|
const deleteCalls = await mockSystemActionApis(page, { reimbursementStatus: "draft" });
|
|
|
|
|
await page.goto(`/reimbursements/${reimbursementId}`);
|
|
|
|
|
await page.getByTestId("reimbursement-more-toggle").click();
|
|
|
|
|
await page.getByTestId("reimbursement-delete-draft").click();
|
|
|
|
|
await expect(page.getByTestId("action-confirm-dialog")).toContainText("删除后无法恢复");
|
|
|
|
|
expect(deleteCalls.deleteReimbursement).toBe(0);
|
|
|
|
|
await page.getByTestId("action-confirm-submit").click();
|
|
|
|
|
await expect.poll(() => deleteCalls.deleteReimbursement).toBe(1);
|
|
|
|
|
await expect(page).toHaveURL(/\/reimbursements\?status=draft/);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("报销详情迁移后保留催办与撤回理由及刷新合同", async ({ page }) => {
|
|
|
|
|
const calls = await mockSystemActionApis(page, { reimbursementStatus: "reviewing" });
|
|
|
|
|
await login(page, user);
|
|
|
|
|
await page.goto(`/reimbursements/${reimbursementId}`);
|
|
|
|
|
|
|
|
|
|
const actionPanel = page.getByTestId("reimbursement-action-panel");
|
|
|
|
|
await actionPanel.getByRole("button", { name: "催办审批" }).click();
|
|
|
|
|
await expect.poll(() => calls.remindWorkflow).toBe(1);
|
|
|
|
|
|
|
|
|
|
await page.getByTestId("reimbursement-withdraw-comment").fill("合成撤回原因");
|
|
|
|
|
await page.getByTestId("reimbursement-withdraw").click();
|
|
|
|
|
await expect.poll(() => calls.withdrawReimbursement).toBe(1);
|
|
|
|
|
expect(calls.withdrawComments).toEqual(["合成撤回原因"]);
|
|
|
|
|
await expect(page.getByText("草稿", { exact: true }).first()).toBeVisible();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("报销详情迁移后已支付状态仍可进入打印页", async ({ page }) => {
|
|
|
|
|
await mockSystemActionApis(page, { reimbursementStatus: "paid" });
|
|
|
|
|
await login(page, user);
|
|
|
|
|
await page.goto(`/reimbursements/${reimbursementId}`);
|
|
|
|
|
|
|
|
|
|
const printLink = page.getByTestId("reimbursement-action-panel").getByRole("link", { name: "打印 / PDF" });
|
|
|
|
|
await expect(printLink).toBeVisible();
|
|
|
|
|
await printLink.click();
|
|
|
|
|
await expect(page).toHaveURL(new RegExp(`/reimbursements/${reimbursementId}/print$`));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("付款批次执行必须确认后才调用接口", async ({ page }) => {
|
|
|
|
|
const calls = await mockSystemActionApis(page);
|
|
|
|
|
page.on("dialog", (dialog) => {
|
|
|
|
@@ -478,12 +667,15 @@ test.describe("R7E 全站高风险操作二次确认", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("银行回单上传前后只显示一个付款主动作", async ({ page }) => {
|
|
|
|
|
const calls = await mockSystemActionApis(page, { hasReceipt: false });
|
|
|
|
|
const calls = await mockSystemActionApis(page, { hasReceipt: false, canExportPaymentOrder: true });
|
|
|
|
|
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(1);
|
|
|
|
|
const actionPanel = page.getByTestId("payment-batch-action-panel");
|
|
|
|
|
await expect(actionPanel.getByRole("button", { name: "上传银行回单" })).toBeVisible();
|
|
|
|
|
await actionPanel.getByText("更多", { exact: true }).click();
|
|
|
|
|
await expect(actionPanel.getByRole("button", { name: "下载银行制单" })).toBeVisible();
|
|
|
|
|
await expect(page.getByTestId("payment-batch-mobile-actions")).toBeHidden();
|
|
|
|
|
await expect(page.getByTestId("payment-batch-receipt-panel").getByRole("button", { name: "上传银行回单" })).toHaveCount(0);
|
|
|
|
|
await expect(page.getByRole("button", { name: "登记付款完成" })).toHaveCount(0);
|
|
|
|
|
|
|
|
|
@@ -494,32 +686,102 @@ test.describe("R7E 全站高风险操作二次确认", () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await expect.poll(() => calls.uploadPaymentReceipt).toBe(1);
|
|
|
|
|
await expect(page.getByRole("button", { name: "登记付款完成" })).toBeVisible();
|
|
|
|
|
await expect(page.getByTestId("payment-batch-action-panel").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, { hasReceipt: false, canExportPaymentOrder: true });
|
|
|
|
|
await page.setViewportSize({ width: 390, height: 844 });
|
|
|
|
|
await login(page, user);
|
|
|
|
|
await page.goto(`/payment-batches?id=${paymentBatchId}`);
|
|
|
|
|
|
|
|
|
|
const mobileActions = page.getByTestId("payment-batch-mobile-actions");
|
|
|
|
|
await expect(mobileActions).toBeVisible();
|
|
|
|
|
await expect(page.getByTestId("payment-batch-action-panel")).toBeHidden();
|
|
|
|
|
await expect(mobileActions.getByRole("button", { name: "上传银行回单" })).toBeVisible();
|
|
|
|
|
await mobileActions.getByRole("button", { name: "更多" }).click();
|
|
|
|
|
await expect(page.getByRole("menuitem", { name: "下载银行制单" })).toBeVisible();
|
|
|
|
|
await page.keyboard.press("Escape");
|
|
|
|
|
|
|
|
|
|
await page.getByTestId("payment-batch-receipt-file-input").setInputFiles({
|
|
|
|
|
name: "移动端合成银行回单.pdf",
|
|
|
|
|
mimeType: "application/pdf",
|
|
|
|
|
buffer: Buffer.from("%PDF-1.4\nmobile synthetic receipt\n%%EOF\n", "utf8"),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await expect.poll(() => calls.uploadPaymentReceipt).toBe(1);
|
|
|
|
|
await expect(mobileActions.getByRole("button", { name: "登记付款完成" })).toBeVisible();
|
|
|
|
|
await expect(mobileActions.getByRole("button", { name: "上传银行回单" })).toHaveCount(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("付款详情按处理顺序展示并统一人民币金额格式", async ({ page }) => {
|
|
|
|
|
await mockSystemActionApis(page, { hasReceipt: false, highRisk: true });
|
|
|
|
|
await page.setViewportSize({ width: 1024, height: 768 });
|
|
|
|
|
await login(page, user);
|
|
|
|
|
await page.goto(`/payment-batches?id=${paymentBatchId}`);
|
|
|
|
|
await expect(page.getByTestId("payment-batch-detail-panel")).toBeVisible();
|
|
|
|
|
|
|
|
|
|
const sectionOrder = await page.locator([
|
|
|
|
|
'[data-testid="payment-batch-detail-panel"]',
|
|
|
|
|
'[data-testid="payment-batch-current-processing"]',
|
|
|
|
|
'[data-testid="payment-batch-processing-panel"]',
|
|
|
|
|
'[data-testid="payment-batch-items-panel"]',
|
|
|
|
|
'[data-testid="payment-batch-receipt-panel"]',
|
|
|
|
|
].join(",")).evaluateAll((nodes) => nodes.map((node) => node.getAttribute("data-testid")));
|
|
|
|
|
expect(sectionOrder).toEqual([
|
|
|
|
|
"payment-batch-detail-panel",
|
|
|
|
|
"payment-batch-current-processing",
|
|
|
|
|
"payment-batch-processing-panel",
|
|
|
|
|
"payment-batch-items-panel",
|
|
|
|
|
"payment-batch-receipt-panel",
|
|
|
|
|
]);
|
|
|
|
|
await expect(page.getByTestId("payment-batch-detail-panel")).toContainText("¥88.00");
|
|
|
|
|
await expect(page.getByTestId("payment-batch-current-processing")).toContainText("¥1,000.00");
|
|
|
|
|
await expect(page.getByTestId("payment-batch-items-panel")).toContainText("¥88.00");
|
|
|
|
|
await expect(page.getByTestId("payment-batch-current-processing")).toContainText("线下付款并上传银行回单");
|
|
|
|
|
await expect(page.getByTestId("payment-batch-processing-panel")).toContainText("等待财务线下付款并上传回单");
|
|
|
|
|
const responsiveContract = await page.locator(".detail-workspace-grid").evaluate((workspace) => {
|
|
|
|
|
const actionPanel = workspace.querySelector(".detail-action-panel");
|
|
|
|
|
return {
|
|
|
|
|
gridColumns: getComputedStyle(workspace).gridTemplateColumns,
|
|
|
|
|
actionPosition: actionPanel ? getComputedStyle(actionPanel).position : null,
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
expect(responsiveContract.gridColumns.split(" ").filter(Boolean)).toHaveLength(2);
|
|
|
|
|
expect(responsiveContract.actionPosition).toBe("sticky");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("付款三阶段时间线覆盖五种状态并正确终止", async ({ page }) => {
|
|
|
|
|
const cases: Array<[SyntheticPaymentBatchStatus, string]> = [
|
|
|
|
|
["draft", "待复核"],
|
|
|
|
|
["reviewed", "待付款"],
|
|
|
|
|
["executed", "已付款"],
|
|
|
|
|
["failed", "付款失败"],
|
|
|
|
|
["cancelled", "已终止"],
|
|
|
|
|
];
|
|
|
|
|
for (const [status, expected] of cases) {
|
|
|
|
|
await page.unrouteAll({ behavior: "wait" });
|
|
|
|
|
await mockSystemActionApis(page, { batchStatus: status, hasReceipt: status === "executed" });
|
|
|
|
|
await page.context().clearCookies();
|
|
|
|
|
await login(page, user);
|
|
|
|
|
await page.goto(`/payment-batches?id=${paymentBatchId}`);
|
|
|
|
|
const progress = page.getByTestId("payment-batch-processing-progress");
|
|
|
|
|
await expect(progress).toBeVisible();
|
|
|
|
|
await expect(progress).toContainText("批次创建");
|
|
|
|
|
await expect(progress).toContainText("付款复核");
|
|
|
|
|
await expect(progress).toContainText("付款完成");
|
|
|
|
|
await expect(progress).toContainText(expected);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("非银行渠道直接等待登记完成且不引导回单或制单", async ({ page }) => {
|
|
|
|
|
await mockSystemActionApis(page, { batchStatus: "reviewed", hasReceipt: false, paymentChannel: "cash" });
|
|
|
|
|
await login(page, user);
|
|
|
|
|
await page.goto(`/payment-batches?id=${paymentBatchId}`);
|
|
|
|
|
|
|
|
|
|
await expect(page.getByTestId("payment-batch-processing-progress")).toContainText("等待财务登记付款完成");
|
|
|
|
|
await expect(page.getByTestId("payment-batch-processing-progress")).not.toContainText("银行制单");
|
|
|
|
|
await expect(page.getByTestId("payment-batch-processing-progress")).not.toContainText("上传回单");
|
|
|
|
|
await expect(page.getByTestId("payment-batch-receipt-panel")).toContainText("当前付款渠道不要求银行回单");
|
|
|
|
|
await expect(page.getByRole("button", { name: "上传银行回单" })).toHaveCount(0);
|
|
|
|
|
await expect(page.getByTestId("payment-batch-action-panel").getByRole("button", { name: "登记付款完成" })).toBeVisible();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test("付款详情按批次状态渐进展示收款依据和财务动作", async ({ page }) => {
|
|
|
|
@@ -626,7 +888,7 @@ test.describe("R7E 全站高风险操作二次确认", () => {
|
|
|
|
|
|
|
|
|
|
await login(page, user);
|
|
|
|
|
await page.goto(`/payment-batches?id=${paymentBatchId}`);
|
|
|
|
|
await expect(page.getByText("制单人不能复核自己的批次")).toBeVisible();
|
|
|
|
|
await expect(page.getByTestId("payment-batch-processing-progress")).toContainText("待非制单人复核付款");
|
|
|
|
|
await expect(page.getByRole("button", { name: "复核通过", exact: true })).toHaveCount(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|