import { expect, test, type Page, type Route } from "@playwright/test"; function envelope(data: unknown) { return { requestId: "mock-biz8-ap-ledger-project", data }; } async function fulfillJson(route: Route, data: unknown, status = 200) { await route.fulfill({ status, contentType: "application/json", body: JSON.stringify(envelope(data)), }); } const financeMe = { id: "employee-biz8-finance", employeeNo: "F-BIZ8", name: "测试财务", phone: "13800000000", email: "finance@hyinda.com", departmentId: "dept-finance", directManagerId: null, status: "active", notificationPushEnabled: true, roleCodes: ["finance"], profileCompleteness: { hasEmergencyContact: true, hasBankAccount: true, hasAvatar: false, hasIdNumber: false, score: 60, }, wecomBinding: null, }; function navigationItem(key: string, href: string, label: string, icon: string) { return { key, href, label, icon, placement: "main", permissionCodes: [], featureFlagKey: null }; } function financeLedgerItem() { return { id: "reimbursement-biz8-project", formNumber: "BX20260513001", reimbursementDate: "2026-05-13", status: "approved", totalAmount: "1688.00", description: "项目差旅报销", owner: { id: "employee-biz8-owner", employeeNo: "E-BIZ8", name: "测试员工" }, submitter: { id: "employee-biz8-owner", employeeNo: "E-BIZ8", name: "测试员工" }, department: { id: "dept-rd", name: "研发部" }, project: { id: "project-biz8", projectNo: "PJ-2026-001", name: "交付项目" }, payment: { status: "unpaid", paymentVoucherNo: null, paymentDate: null, paymentChannel: null, paidAt: null, paidBy: null, }, invoice: { status: "bound", counts: { total: 2, pendingOcr: 0, ocrFailed: 0, available: 0, bound: 2, archived: 0, usedComplete: 0, }, }, attachmentCount: 1, createdAt: "2026-05-13T08:00:00.000Z", updatedAt: "2026-05-13T09:00:00.000Z", }; } async function mockApis(page: Page, ledgerQueries: string[]) { await page.route((url) => url.pathname.startsWith("/api/"), async (route) => { const request = route.request(); const url = new URL(request.url()); const path = url.pathname; if (path === "/api/auth/sso/providers") { await fulfillJson(route, []); return; } if (path === "/api/auth/wecom/config") { await fulfillJson(route, { enabled: false, entryEnabled: false, oauthEnabled: false, corpId: "", agentId: "", loginUrl: null, bindEnabled: false, defaultReturnTo: "/portal", status: "disabled", statusReasons: [], }); return; } if (path === "/api/auth/login" || path === "/api/auth/refresh") { await route.fulfill({ status: 200, contentType: "application/json", headers: { "set-cookie": "has_session=1; Path=/; SameSite=Lax" }, body: JSON.stringify(envelope({ accessToken: "mock-biz8-token", expiresIn: 900, mustChangePassword: false })), }); return; } if (path === "/api/me") { await fulfillJson(route, financeMe); return; } if (path === "/api/system/navigation") { await fulfillJson(route, { workspace: { id: "workspace-biz8", persisted: true, profile: "small_team_internal", orgMode: "single_company", primaryEntryMode: "password", notificationStrategy: "in_app", devicePriority: "desktop", moduleVisibility: {}, createdAt: null, updatedAt: null, updatedBy: null, }, sections: { main: [navigationItem("portal", "/portal", "工作台", "home_app_logo")], admin: [], backup: [navigationItem("ap", "/ap-operations", "应付账款", "account_balance_wallet")], }, }); return; } if (path === "/api/portal") { await fulfillJson(route, { me: financeMe, workspace: null, navigation: [], cards: [], pendingTasks: { count: 0, href: "/workflow/tasks" }, notifications: [], quickLinks: [], projectSummary: null, riskSummary: null, recentActivities: [], }); return; } if (path === "/api/ap-operations") { await fulfillJson(route, { summary: { pendingPaymentCount: 0, partialMatchCount: 0, exceptionCount: 0, budgetOverspendCount: 0, }, pendingPaymentBatches: [], failedPaymentBatchItems: [], exceptionRiskFlags: [], budgetOverspendRisks: [], partialMatchItems: [], }); return; } if (path === "/api/ap-operations/reimbursement-ledger") { ledgerQueries.push(url.searchParams.get("q") ?? ""); await fulfillJson(route, { page: 1, pageSize: 20, total: 1, items: [financeLedgerItem()], }); return; } if (path === "/api/payment-batches") { await fulfillJson(route, { items: [] }); return; } if (path === "/api/risk-flags") { await fulfillJson(route, { items: [] }); return; } await fulfillJson(route, { message: `unhandled mock api: ${path}` }, 404); }); } async function loginAsFinance(page: Page) { await page.goto("/login"); const accountLoginButton = page.getByRole("button", { name: "使用账号密码登录" }); if (await accountLoginButton.isVisible({ timeout: 1_000 }).catch(() => false)) { await accountLoginButton.click(); } await page.getByTestId("login-identifier").fill("finance@hyinda.com"); await page.getByTestId("login-password").fill("password"); await page.getByTestId("login-submit").click(); await page.waitForURL(/\/portal/, { timeout: 30_000 }); } test("BIZ8 AP 财务台账展示项目并支持项目关键字搜索", async ({ page }) => { const ledgerQueries: string[] = []; await mockApis(page, ledgerQueries); await loginAsFinance(page); await page.goto("/ap-operations"); await expect(page.getByTestId("finance-ledger-table")).toContainText("交付项目"); await expect(page.getByTestId("finance-ledger-table")).toContainText("PJ-2026-001"); await page.getByPlaceholder("单号、姓名、部门、项目").fill("PJ-2026-001"); await expect.poll(() => ledgerQueries).toContain("PJ-2026-001"); });