汇总已落盘的借款本金与还款、兼容历史编号的周期化编号、付款责任与回单核验、报销进度和排序、PWA/采购事务、错误原因与日期校验及ERP退役。 统一共享预算池锁序和锁后判断,恢复差旅原预算选择,补原因码固定夹具及反向完整性检查。 纳入配套迁移、测试、受管QA、分层验收合同、规约及任务记录;原始证据留在ignored artifacts。 关联任务:09-17-company-loan-principal-repayment、09-17-erp-exclusive-retirement、09-18-reimbursement-periodic-numbering、09-08-reimbursement-payment-closeout、09-18-local-concurrency-product-fixes、09-19-budget-pool-concurrency、09-05-error-explainability-payment-readiness。 预算补审由Claude定向验证2文件928项通过,原4446/28及r6结果仍仅对应各自历史输入;本轮未重跑测试。 完整新候选门禁、R1真实入口接入与12项公网义务仍开放。生产编号未启用、差旅仍冻结;不推送、不部署,不宣称发布资格。
227 行
6.5 KiB
TypeScript
227 行
6.5 KiB
TypeScript
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: "[email protected]",
|
|
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("[email protected]");
|
|
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 page.getByRole("tab", { name: /^财务台账/ }).click();
|
|
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");
|
|
});
|