文件
hyb-oa/client/tests/e2e/travel-reconciliation-flow.spec.ts
T

123 行
5.3 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { apiGetData, createAuthenticatedApiClient, FINANCE_MGR, MANAGER, login } from "./helpers/auth";
import {
approveTravelRequestViaApi,
createSubmittedTravelRequestViaApi,
importOrphanTravelTransaction,
importTravelPaymentSeed,
markTravelOrderPaidViaApi,
} from "./helpers/travel";
type ReconciliationBatchDetailResponse = {
summary: {
pendingItems: number;
};
};
test("E2E 差旅对账批次:创建→确认异常→收口", async ({ page }) => {
const managerApi = await createAuthenticatedApiClient(MANAGER);
const financeMgrApi = await createAuthenticatedApiClient(FINANCE_MGR);
const isolatedStartOffsetDays = 45 + Math.floor(Math.random() * 120);
const created = await createSubmittedTravelRequestViaApi({
purposePrefix: "Playwright差旅对账",
amount: "1666.00",
startOffsetDays: isolatedStartOffsetDays,
endOffsetDays: isolatedStartOffsetDays + 1,
});
try {
await approveTravelRequestViaApi(managerApi, created.travelRequest.requestId);
const paymentSeed = await importTravelPaymentSeed(financeMgrApi.api, {
requestId: created.travelRequest.requestId,
employeeId: created.travelRequest.employeeId,
amount: "1666.00",
occurredAtDate: created.travelRequest.startDate,
});
await markTravelOrderPaidViaApi(financeMgrApi.api, {
orderId: paymentSeed.orderId,
voucherNo: `RECON-${Date.now()}`,
paymentDate: created.travelRequest.startDate,
});
const orphanTxnNo = `PW-ORPHAN-${Date.now()}`;
await importOrphanTravelTransaction(financeMgrApi.api, {
requestId: created.travelRequest.requestId,
employeeId: created.travelRequest.employeeId,
externalTxnId: orphanTxnNo,
amount: "88.00",
occurredAtDate: created.travelRequest.startDate,
});
await login(page, FINANCE_MGR);
await page.goto("/travel-requests?tab=reconciliation");
await expect(page.getByRole("heading", { name: "差旅管理" })).toBeVisible();
await page.locator("#travel-recon-create-legal-entity").selectOption(created.travelRequest.legalEntityId);
await page.locator("#travel-recon-create-cost-center").selectOption(created.travelRequest.costCenterId);
await page.locator("#travel-recon-create-period-start").fill(created.travelRequest.startDate);
await page.locator("#travel-recon-create-period-end").fill(created.travelRequest.endDate);
const createBatchResponsePromise = page.waitForResponse(
(response) =>
response.request().method() === "POST" &&
new URL(response.url()).pathname === "/api/travel/reconciliation/batches" &&
response.ok(),
);
await page.getByRole("button", { name: "创建批次" }).click();
const createBatchResponse = await createBatchResponsePromise;
const createBatchPayload = (await createBatchResponse.json()) as { data?: { id?: string } };
const batchId = createBatchPayload.data?.id;
if (!batchId) {
throw new Error(`创建批次后未返回 batchId: ${JSON.stringify(createBatchPayload)}`);
}
await expect(page).toHaveURL(new RegExp(`batchId=${batchId}`), { timeout: 30_000 });
const detailSection = () => page.locator(".card-std").filter({ hasText: "异常与明细" }).first();
const orphanItemCard = () => page.locator(".rounded-xl.border.border-zinc-200").filter({ hasText: orphanTxnNo }).first();
const fetchBatchDetail = () =>
apiGetData<ReconciliationBatchDetailResponse>(
financeMgrApi.api,
`/api/travel/reconciliation/batches/${batchId}`,
);
await expect(page.getByText("ERP 摘要桥")).toBeVisible();
await expect(page.getByText("异常与明细")).toBeVisible();
await expect(orphanItemCard()).toBeVisible({ timeout: 30_000 });
await expect(orphanItemCard()).toContainText("待确认");
let pendingConfirmCount = (await fetchBatchDetail()).summary.pendingItems;
while (pendingConfirmCount > 0) {
page.once("dialog", async (dialog) => {
await dialog.accept("Playwright 已确认孤儿交易");
});
await Promise.all([
page.waitForResponse(
(response) =>
response.request().method() === "POST" &&
/\/api\/travel\/reconciliation\/items\/[^/]+\/confirm$/.test(new URL(response.url()).pathname) &&
response.ok(),
),
detailSection().getByRole("button", { name: "确认差异" }).first().click(),
]);
await expect
.poll(async () => (await fetchBatchDetail()).summary.pendingItems, {
timeout: 30_000,
})
.toBe(pendingConfirmCount - 1);
pendingConfirmCount -= 1;
await page.reload();
await expect(page).toHaveURL(new RegExp(`batchId=${batchId}`));
await expect(page.getByText("异常与明细")).toBeVisible();
}
await expect(orphanItemCard()).toContainText("已确认", { timeout: 30_000 });
await page.getByRole("button", { name: "Finalize" }).click();
await expect(page.getByText("阶段状态").locator("..")).toContainText(/已入队|已发送|已跳过|未触发/, {
timeout: 30_000,
});
await expect(page.getByText("阶段状态")).toBeVisible();
await expect(page.getByText("应付摘要")).toBeVisible();
} finally {
await Promise.all([created.client.dispose(), managerApi.dispose(), financeMgrApi.dispose()]);
}
});