收敛报销明细与材料交互(07-27-yang-jun-reimbursement-readiness)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-07-31 22:01:25 +08:00
父节点 665dcabdc7
当前提交 0e44aa72f3
共修改 7 个文件,包含 407 行新增和 238 行删除
@@ -10,6 +10,7 @@ const WECOM_UA =
const reimbursementId = "11111111-1111-4111-8111-111111111111";
const ownerId = "employee-r111";
const pendingInvoiceId = "22222222-2222-4222-8222-222222222222";
const unlinkedInvoiceId = "66666666-6666-4666-8666-666666666666";
const attachmentId = "33333333-3333-4333-8333-333333333333";
const availableInvoiceId = "44444444-4444-4444-8444-444444444444";
const failedInvoiceId = "55555555-5555-4555-8555-555555555555";
@@ -80,7 +81,18 @@ function buildInvoice() {
originalName: "北京悦海堂发票.pdf",
mimeType: "application/pdf",
fileSize: 4096,
linkedItemId: "item-r111",
linkedItemId: "item-r111" as string | null,
};
}
function buildUnlinkedInvoice() {
return {
...buildInvoice(),
id: unlinkedInvoiceId,
invoiceNumber: "12345679",
sellerName: "未关联材料供应方",
originalName: "未关联发票.pdf",
linkedItemId: null,
};
}
@@ -432,7 +444,7 @@ async function mockApis(page: Page, state: ReturnType<typeof buildReimbursementS
}
if (pathName === `/api/invoice-pool/${pendingInvoiceId}/bind`) {
state.invoices = [buildInvoice()];
state.invoices = [buildInvoice(), buildUnlinkedInvoice()];
await fulfillJson(route, {
id: pendingInvoiceId,
status: "bound",
@@ -596,9 +608,15 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
expect(state.statusPollCount).toBeGreaterThanOrEqual(1);
await page.keyboard.press("Escape");
await expect(page.getByText("北京悦海堂餐饮管理有限公司")).toBeVisible();
await expect(page.getByText("北京悦海堂餐饮管理有限公司").first()).toBeVisible();
await expect(page.getByText("明细:业务招待 · 客户招待")).toBeVisible();
await page.getByRole("button", { name: "查看原件" }).first().click();
await expect(page.getByTestId("reimbursement-materials")).toContainText("发票 2 · 补充附件 0");
const itemRow = page.getByTestId("reimbursement-item-row");
await expect(itemRow.getByRole("button", { name: /查看关联发票/ })).toHaveCount(1);
await expect(itemRow).not.toContainText("未关联材料供应方");
await expect(page.getByRole("button", { name: /查看发票原件/ })).toHaveCount(2);
await expect(page.getByRole("button", { name: "查看原件", exact: true })).toHaveCount(0);
await itemRow.getByRole("button", { name: /查看关联发票/ }).click();
await expect.poll(() => page.evaluate(() => (window as typeof window & { __r111OpenCalls: unknown[][] }).__r111OpenCalls.length)).toBe(1);
const invoiceOpenUrl = await page.evaluate(
() => String((window as typeof window & { __r111OpenCalls: unknown[][] }).__r111OpenCalls[0]?.[0] ?? ""),
@@ -612,7 +630,8 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
});
await page.getByRole("button", { name: "上传附件" }).click();
await expect(page.getByText("付款截图.png")).toBeVisible();
await page.getByTestId("reimbursement-attachments").getByRole("button", { name: "查看原件" }).click();
await expect(page.getByTestId("reimbursement-materials")).toContainText("发票 2 · 补充附件 1");
await page.getByTestId("reimbursement-attachments").getByRole("button", { name: /查看补充附件/ }).click();
await expect.poll(() => page.evaluate(() => (window as typeof window & { __r111OpenCalls: unknown[][] }).__r111OpenCalls.length)).toBe(2);
const bodyText = await page.locator("body").innerText();
@@ -656,7 +675,7 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
await login(mobile.page);
await mobile.page.goto(`/mobile/reimbursements/${reimbursementId}`);
await expect(mobile.page.getByTestId("mobile-reimbursement-detail")).toBeVisible();
await expect(mobile.page.getByRole("heading", { name: "附件材料" })).toBeVisible();
await expect(mobile.page.getByRole("heading", { name: "报销材料" })).toBeVisible();
const beforeDialogMetrics = await collectVisualMetrics(mobile.page);
expect(beforeDialogMetrics.scrollWidth).toBeLessThanOrEqual(beforeDialogMetrics.width + 2);
@@ -712,21 +731,29 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
mobile.page.getByTestId("reimbursement-invoice-dialog").getByText("已添加 1 张发票"),
).toBeVisible({ timeout: 15_000 });
await mobile.page.keyboard.press("Escape");
await expect(mobile.page.getByText("北京悦海堂餐饮管理有限公司")).toBeVisible();
await mobile.page.getByRole("button", { name: "查看原件" }).first().click();
await expect(mobile.page.getByText("北京悦海堂餐饮管理有限公司").first()).toBeVisible();
await expect(mobile.page.getByTestId("mobile-reimbursement-materials")).toContainText("发票 2 · 补充附件 0");
const mobileItem = mobile.page.locator("article.mobile-list-card").filter({ hasText: "客户招待" }).first();
await expect(mobileItem.getByRole("button", { name: /查看关联发票/ })).toHaveCount(1);
await expect(mobileItem).not.toContainText("未关联材料供应方");
await expect(mobile.page.getByRole("button", { name: "查看原件", exact: true })).toHaveCount(0);
const linkedInvoiceTarget = await mobileItem.getByRole("button", { name: /查看关联发票/ }).boundingBox();
expect(linkedInvoiceTarget?.height ?? 0).toBeGreaterThanOrEqual(44);
await mobile.page.getByRole("button", { name: /查看发票原件:044002600111-12345678/ }).click();
await expect.poll(() => mobile.page.evaluate(() => (
window as typeof window & { __r111BridgeCalls: Array<{ name: string }> }
).__r111BridgeCalls.filter((call) => call.name === "previewFile").length)).toBe(1);
await mobile.page.getByTestId("mobile-reimbursement-attachments").getByRole("button", { name: "选择" }).click();
await mobile.page.locator("[data-testid='mobile-reimbursement-attachments'] input[type='file']").setInputFiles({
await mobile.page.getByRole("button", { name: "添加附件" }).click();
await mobile.page.getByTestId("mobile-reimbursement-materials").locator("input[type='file']").setInputFiles({
name: "付款截图.png",
mimeType: "image/png",
buffer: Buffer.from("r111 mobile attachment"),
});
await mobile.page.getByTestId("mobile-reimbursement-attachments").getByRole("button", { name: "上传附件" }).click();
await mobile.page.getByTestId("mobile-reimbursement-materials").getByRole("button", { name: "上传附件" }).click();
await expect(mobile.page.getByText("付款截图.png")).toBeVisible();
await mobile.page.getByTestId("mobile-reimbursement-attachments").getByRole("button", { name: "查看原件" }).click();
await expect(mobile.page.getByTestId("mobile-reimbursement-materials")).toContainText("发票 2 · 补充附件 1");
await mobile.page.getByTestId("mobile-reimbursement-attachments").getByRole("button", { name: /查看补充附件/ }).click();
await expect.poll(() => mobile.page.evaluate(() => (
window as typeof window & { __r111BridgeCalls: Array<{ name: string }> }
).__r111BridgeCalls.filter((call) => call.name === "previewFile").length)).toBe(2);
@@ -753,6 +780,11 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
window as typeof window & { __r111OpenCalls: unknown[][] }
).__r111OpenCalls.length)).toBe(0);
await mobile.page.setViewportSize({ width: 667, height: 375 });
const landscapeMetrics = await collectVisualMetrics(mobile.page);
expect(landscapeMetrics.scrollWidth).toBeLessThanOrEqual(landscapeMetrics.width + 2);
await mobile.page.setViewportSize({ width: 390, height: 844 });
const bodyText = await mobile.page.locator("body").innerText();
expect(bodyText).not.toContain("绑定整单");
expect(bodyText).not.toContain("不绑定明细");