R123/A1-A6 收敛移动发票反馈并补部署检查

这个提交包含在:
2026-05-08 12:32:30 +08:00
父节点 135be878f5
当前提交 052413f407
共修改 8 个文件,包含 427 行新增和 57 行删除
@@ -11,6 +11,8 @@ const reimbursementId = "11111111-1111-4111-8111-111111111111";
const ownerId = "employee-r111";
const pendingInvoiceId = "22222222-2222-4222-8222-222222222222";
const attachmentId = "33333333-3333-4333-8333-333333333333";
const availableInvoiceId = "44444444-4444-4444-8444-444444444444";
const failedInvoiceId = "55555555-5555-4555-8555-555555555555";
function envelope(data: unknown) {
return { requestId: "mock-r111-invoice-reimbursement", data };
@@ -92,10 +94,54 @@ function buildAttachment() {
};
}
function buildAvailableInvoice() {
return {
id: availableInvoiceId,
invoiceCode: "044002600111",
invoiceNumber: "87654321",
invoiceDate: "2026-05-08",
sellerName: "北京悦海堂餐饮管理有限公司",
totalAmount: "1683.00",
originalName: "已有发票.pdf",
createdAt: "2026-05-08T08:00:00.000Z",
};
}
function buildFailedInvoice() {
return {
id: failedInvoiceId,
status: "ocr_failed",
ocrType: "vat",
ocrProvider: "tencent",
ocrAttempts: 1,
ocrLastError: "识别服务未返回完整发票信息",
ocrCompletedAt: "2026-05-08T08:05:00.000Z",
invoiceCode: null,
invoiceNumber: null,
invoiceDate: null,
amount: null,
taxAmount: null,
totalAmount: null,
linkedReimbursementId: null,
linkedItemId: null,
linkedTravelOrderId: null,
originalName: "识别失败发票.pdf",
mimeType: "application/pdf",
fileSize: 4096,
verification: null,
owner: { id: ownerId, employeeNo: "E111", name: "测试员工" },
uploadedBy: { id: ownerId, employeeNo: "E111", name: "测试员工" },
createdAt: "2026-05-08T08:01:00.000Z",
updatedAt: "2026-05-08T08:05:00.000Z",
};
}
function buildReimbursementState() {
return {
invoices: [] as ReturnType<typeof buildInvoice>[],
attachments: [] as ReturnType<typeof buildAttachment>[],
availableInvoices: [buildAvailableInvoice()],
failedInvoices: [buildFailedInvoice()],
statusPollCount: 0,
};
}
@@ -283,18 +329,18 @@ async function mockApis(page: Page, state: ReturnType<typeof buildReimbursementS
if (pathName === "/api/invoice-pool/bindable") {
await fulfillJson(route, {
items: [
{
id: "44444444-4444-4444-8444-444444444444",
invoiceCode: "044002600111",
invoiceNumber: "87654321",
invoiceDate: "2026-05-08",
sellerName: "北京悦海堂餐饮管理有限公司",
totalAmount: "1683.00",
originalName: "已有发票.pdf",
createdAt: "2026-05-08T08:00:00.000Z",
},
],
items: state.availableInvoices,
});
return;
}
if (pathName === "/api/invoice-pool" && url.searchParams.get("status") === "ocr_failed") {
await fulfillJson(route, {
page: 1,
pageSize: 20,
total: state.failedInvoices.length,
ownerScope: "own",
items: state.failedInvoices,
});
return;
}
@@ -314,26 +360,27 @@ async function mockApis(page: Page, state: ReturnType<typeof buildReimbursementS
if (pathName === "/api/invoice-pool/status") {
state.statusPollCount += 1;
const stillPending = state.statusPollCount === 1;
await fulfillJson(route, {
items: [
{
id: pendingInvoiceId,
status: "available",
reason: "available",
message: "发票可添加",
invoiceCode: "044002600111",
invoiceNumber: "12345678",
invoiceDate: "2026-05-08",
sellerName: "北京悦海堂餐饮管理有限公司",
totalAmount: "1683.00",
status: stillPending ? "pending_ocr" : "available",
reason: stillPending ? "pending_ocr" : "available",
message: stillPending ? "发票正在识别" : "发票可添加",
invoiceCode: stillPending ? null : "044002600111",
invoiceNumber: stillPending ? null : "12345678",
invoiceDate: stillPending ? null : "2026-05-08",
sellerName: stillPending ? null : "北京悦海堂餐饮管理有限公司",
totalAmount: stillPending ? null : "1683.00",
originalName: "北京悦海堂发票.pdf",
linkedReimbursementId: null,
linkedItemId: null,
linkedTravelOrderId: null,
ocrAttempts: 1,
ocrAttempts: stillPending ? 0 : 1,
ocrLastError: null,
ocrCompletedAt: "2026-05-08T08:05:00.000Z",
updatedAt: "2026-05-08T08:05:00.000Z",
ocrCompletedAt: stillPending ? null : "2026-05-08T08:05:00.000Z",
updatedAt: stillPending ? "2026-05-08T08:04:00.000Z" : "2026-05-08T08:05:00.000Z",
},
],
missingIds: [],
@@ -367,6 +414,28 @@ async function mockApis(page: Page, state: ReturnType<typeof buildReimbursementS
return;
}
if (pathName === `/api/invoice-pool/${availableInvoiceId}/archive`) {
state.availableInvoices = [];
await fulfillJson(route, {
id: availableInvoiceId,
status: "archived",
archivedReason: "manual",
archivedAt: "2026-05-08T08:20:00.000Z",
});
return;
}
if (pathName === `/api/invoice-pool/${failedInvoiceId}/archive`) {
state.failedInvoices = [];
await fulfillJson(route, {
id: failedInvoiceId,
status: "archived",
archivedReason: "manual",
archivedAt: "2026-05-08T08:21:00.000Z",
});
return;
}
await fulfillJson(route, {});
});
}
@@ -494,15 +563,39 @@ test.describe("R111 国内报销发票与附件双端体验", () => {
const dialogMetrics = await collectVisualMetrics(mobile.page);
expect(dialogMetrics.activeElementTestId).not.toBe("mobile-reimbursement-invoice-search");
const invoiceDialog = mobile.page.getByTestId("reimbursement-invoice-dialog");
await invoiceDialog.getByRole("button", { name: "已有发票" }).click();
const availableInvoiceCard = invoiceDialog.locator("article").filter({ hasText: "044002600111-87654321" });
const failedInvoiceCard = invoiceDialog.locator("article").filter({ hasText: "识别失败发票.pdf" });
await expect(availableInvoiceCard).toBeVisible();
await expect(failedInvoiceCard.getByText("识别失败", { exact: true })).toBeVisible();
await expect(failedInvoiceCard.getByText("识别失败,可删除后重新上传。")).toBeVisible();
await availableInvoiceCard.getByRole("button", { name: "删除" }).click();
await expect(mobile.page.getByRole("heading", { name: "删除发票?" })).toBeVisible();
await mobile.page.getByRole("dialog").last().getByRole("button", { name: "删除" }).click();
await expect(availableInvoiceCard).toHaveCount(0);
await expect(failedInvoiceCard).toBeVisible();
await failedInvoiceCard.getByRole("button", { name: "删除" }).click();
await expect(mobile.page.getByRole("heading", { name: "删除发票?" })).toBeVisible();
await mobile.page.getByRole("dialog").last().getByRole("button", { name: "删除" }).click();
await expect(failedInvoiceCard).toHaveCount(0);
await expect(invoiceDialog.getByText("暂无可添加发票")).toBeVisible();
await invoiceDialog.getByRole("button", { name: "上传" }).click();
await mobile.page.getByTestId("reimbursement-invoice-upload-file").setInputFiles({
name: "北京悦海堂发票.pdf",
mimeType: "application/pdf",
buffer: Buffer.from("%PDF r111 mobile invoice"),
});
await mobile.page.getByTestId("reimbursement-invoice-upload-submit").click();
await expect(mobile.page.getByTestId("reimbursement-invoice-pending-hint")).toHaveCount(1);
await expect(mobile.page.getByTestId("reimbursement-invoice-dialog")).not.toContainText("识别完成后可在");
await expect(
mobile.page.getByTestId("reimbursement-invoice-dialog").getByText("已添加 1 张发票"),
).toBeVisible({ timeout: 10_000 });
).toBeVisible({ timeout: 15_000 });
await mobile.page.keyboard.press("Escape");
await expect(mobile.page.getByText("北京悦海堂餐饮管理有限公司")).toBeVisible();