123 行
5.3 KiB
TypeScript
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()]);
|
|
}
|
|
});
|