实现报销材料连续预览(07-27-yang-jun-reimbursement-readiness)
这个提交包含在:
@@ -73,10 +73,13 @@ function buildInvoice() {
|
||||
return {
|
||||
id: pendingInvoiceId,
|
||||
status: "bound",
|
||||
invoiceCode: "044002600111",
|
||||
invoiceCode: null as string | null,
|
||||
invoiceNumber: "12345678",
|
||||
invoiceDate: "2026-05-08",
|
||||
sellerName: "北京悦海堂餐饮管理有限公司",
|
||||
sellerName: "北京悦海堂餐饮管理有限公司" as string | null,
|
||||
buyerName: null as string | null,
|
||||
documentCategory: "full_vat_invoice" as string | null,
|
||||
documentSubtype: "VatElectronicInvoiceFull" as string | null,
|
||||
totalAmount: "1683.00",
|
||||
originalName: "北京悦海堂发票.pdf",
|
||||
mimeType: "application/pdf",
|
||||
@@ -90,7 +93,10 @@ function buildUnlinkedInvoice() {
|
||||
...buildInvoice(),
|
||||
id: unlinkedInvoiceId,
|
||||
invoiceNumber: "12345679",
|
||||
sellerName: "未关联材料供应方",
|
||||
sellerName: null,
|
||||
buyerName: "测试购买方",
|
||||
documentCategory: "electronic_train_ticket",
|
||||
documentSubtype: "ElectronicTrainTicketFull",
|
||||
originalName: "未关联发票.pdf",
|
||||
linkedItemId: null,
|
||||
};
|
||||
@@ -351,6 +357,30 @@ async function mockApis(page: Page, state: ReturnType<typeof buildReimbursementS
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathName === `/api/reimbursements/${reimbursementId}/materials/preview-bundle` && request.method() === "POST") {
|
||||
const pages = [
|
||||
...state.invoices.map((_, index) => ({
|
||||
url: `https://oa.hyinda.com/api/reimbursements/${reimbursementId}/materials/preview/77777777-7777-4777-8777-777777777777/${index}?previewToken=invoice-${index}`,
|
||||
materialType: "invoice",
|
||||
materialIndex: index + 1,
|
||||
pageNumber: 1,
|
||||
})),
|
||||
...state.attachments.map((_, index) => ({
|
||||
url: `https://oa.hyinda.com/api/reimbursements/${reimbursementId}/materials/preview/77777777-7777-4777-8777-777777777777/${state.invoices.length + index}?previewToken=attachment-${index}`,
|
||||
materialType: "attachment",
|
||||
materialIndex: index + 1,
|
||||
pageNumber: 1,
|
||||
})),
|
||||
];
|
||||
await fulfillJson(route, { expiresAt: "2026-07-31T10:02:00.000Z", pages });
|
||||
return;
|
||||
}
|
||||
|
||||
if (pathName.includes(`/api/reimbursements/${reimbursementId}/materials/preview/`) && request.method() === "GET") {
|
||||
await route.fulfill({ status: 200, contentType: "image/png", body: Buffer.from("synthetic-preview-image") });
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
pathName === `/api/reimbursements/${reimbursementId}/attachments/${attachmentId}/preview-url`
|
||||
&& request.method() === "POST"
|
||||
@@ -611,17 +641,15 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
|
||||
await expect(page.getByText("北京悦海堂餐饮管理有限公司").first()).toBeVisible();
|
||||
await expect(page.getByText("明细:业务招待 · 客户招待")).toBeVisible();
|
||||
await expect(page.getByTestId("reimbursement-materials")).toContainText("发票 2 · 补充附件 0");
|
||||
await expect(page.getByTestId("reimbursement-materials")).toContainText("数电发票 · 12345678");
|
||||
await expect(page.getByTestId("reimbursement-materials")).toContainText("电子火车票 · 12345679");
|
||||
await expect(page.getByTestId("reimbursement-materials")).not.toContainText("无代码");
|
||||
await expect(page.getByTestId("reimbursement-materials")).not.toContainText("未识别销售方");
|
||||
const itemRow = page.getByTestId("reimbursement-item-row");
|
||||
await expect(itemRow.getByRole("button", { name: /查看关联发票/ })).toHaveCount(1);
|
||||
await expect(itemRow).not.toContainText("未关联材料供应方");
|
||||
await expect(itemRow.getByRole("button", { name: /查看关联发票/ })).toHaveCount(0);
|
||||
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] ?? ""),
|
||||
);
|
||||
expect(invoiceOpenUrl).toContain(`/api/invoice-pool/${pendingInvoiceId}/file?token=`);
|
||||
|
||||
await page.locator("#reimbursement-attachment-upload").setInputFiles({
|
||||
name: "付款截图.png",
|
||||
@@ -631,8 +659,13 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
|
||||
await page.getByRole("button", { name: "上传附件" }).click();
|
||||
await expect(page.getByText("付款截图.png")).toBeVisible();
|
||||
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);
|
||||
await page.getByTestId("reimbursement-materials-open-all").click();
|
||||
await page.waitForURL(new RegExp(`/reimbursements/${reimbursementId}/materials$`));
|
||||
await expect(page.getByTestId("reimbursement-material-viewer")).toContainText("共 3 页");
|
||||
await expect(page.getByRole("img", { name: /预览/ })).toHaveCount(3);
|
||||
await page.getByRole("button", { name: "返回报销单" }).click();
|
||||
await page.waitForURL(new RegExp(`/reimbursements/${reimbursementId}$`));
|
||||
expect(await page.evaluate(() => (window as typeof window & { __r111OpenCalls: unknown[][] }).__r111OpenCalls.length)).toBe(0);
|
||||
|
||||
const bodyText = await page.locator("body").innerText();
|
||||
expect(bodyText).not.toContain("绑定整单");
|
||||
@@ -733,16 +766,14 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
|
||||
await mobile.page.keyboard.press("Escape");
|
||||
await expect(mobile.page.getByText("北京悦海堂餐饮管理有限公司").first()).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-materials")).toContainText("发票 2 · 补充附件 0");
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-materials")).toContainText("数电发票 · 12345678");
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-materials")).toContainText("电子火车票 · 12345679");
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-materials")).not.toContainText("无代码");
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-materials")).not.toContainText("未识别销售方");
|
||||
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(mobileItem.getByRole("button", { name: /查看关联发票/ })).toHaveCount(0);
|
||||
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.getByRole("button", { name: "添加附件" }).click();
|
||||
await mobile.page.getByTestId("mobile-reimbursement-materials").locator("input[type='file']").setInputFiles({
|
||||
@@ -753,26 +784,25 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
|
||||
await mobile.page.getByTestId("mobile-reimbursement-materials").getByRole("button", { name: "上传附件" }).click();
|
||||
await expect(mobile.page.getByText("付款截图.png")).toBeVisible();
|
||||
await expect(mobile.page.getByTestId("mobile-reimbursement-materials")).toContainText("发票 2 · 补充附件 1");
|
||||
await mobile.page.getByTestId("mobile-reimbursement-attachments").getByRole("button", { name: /查看补充附件/ }).click();
|
||||
const openAllButton = mobile.page.getByTestId("mobile-reimbursement-materials-open-all");
|
||||
const openAllTarget = await openAllButton.boundingBox();
|
||||
expect(openAllTarget?.height ?? 0).toBeGreaterThanOrEqual(44);
|
||||
await openAllButton.click();
|
||||
await expect.poll(() => mobile.page.evaluate(() => (
|
||||
window as typeof window & { __r111BridgeCalls: Array<{ name: string }> }
|
||||
).__r111BridgeCalls.filter((call) => call.name === "previewFile").length)).toBe(2);
|
||||
).__r111BridgeCalls.filter((call) => call.name === "imagePreview").length)).toBe(1);
|
||||
const nativePreviewCalls = await mobile.page.evaluate(() => (
|
||||
window as typeof window & { __r111BridgeCalls: Array<{ name: string; params: Record<string, unknown> }> }
|
||||
).__r111BridgeCalls.filter((call) => call.name === "previewFile"));
|
||||
).__r111BridgeCalls.filter((call) => call.name === "imagePreview"));
|
||||
expect(nativePreviewCalls).toEqual([
|
||||
expect.objectContaining({
|
||||
params: expect.objectContaining({
|
||||
url: expect.stringContaining(`/api/invoice-pool/${pendingInvoiceId}/file?token=`),
|
||||
name: "北京悦海堂发票.pdf",
|
||||
size: 4096,
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
params: expect.objectContaining({
|
||||
url: expect.stringContaining(`/attachments/${attachmentId}/file?previewToken=`),
|
||||
name: "付款截图.png",
|
||||
size: 2048,
|
||||
current: expect.stringContaining(`/materials/preview/`),
|
||||
urls: expect.arrayContaining([
|
||||
expect.stringContaining("previewToken=invoice-0"),
|
||||
expect.stringContaining("previewToken=invoice-1"),
|
||||
expect.stringContaining("previewToken=attachment-0"),
|
||||
]),
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
|
||||
在新工单中引用
屏蔽一个用户