import { readFileSync } from "node:fs"; import path from "node:path"; import { expect, test } from "@playwright/test"; import { buildReimbursementApprovalEvidence } from "../../src/lib/reimbursementApprovalProjection"; import { buildReimbursementAmountSummary, parseMoneyToMinorUnits, } from "../../src/lib/reimbursementWorkspace"; function source(relativePath: string) { return readFileSync(path.resolve(process.cwd(), relativePath), "utf8"); } test("报销金额使用整数分判断完整性且不产生展示用第二总额", () => { expect(parseMoneyToMinorUnits("0")).toBe(0n); expect(parseMoneyToMinorUnits("0.1")).toBe(10n); expect(parseMoneyToMinorUnits("1234.56")).toBe(123456n); expect(parseMoneyToMinorUnits("1,234.56")).toBeNull(); expect(parseMoneyToMinorUnits("0.001")).toBeNull(); expect(parseMoneyToMinorUnits("NaN")).toBeNull(); expect(parseMoneyToMinorUnits(null)).toBeNull(); expect(buildReimbursementAmountSummary("0.30", [{ amount: "0.1" }, { amount: "0.20" }])).toEqual({ authoritativeTotalAmount: "0.30", integrity: "ready", }); expect(buildReimbursementAmountSummary("1000.00", [{ amount: "400" }, { amount: "600.00" }])).toEqual({ authoritativeTotalAmount: "1000.00", integrity: "ready", }); expect(buildReimbursementAmountSummary("100.00", [{ amount: "99.99" }])).toEqual({ authoritativeTotalAmount: "100.00", integrity: "mismatch", }); expect(buildReimbursementAmountSummary("100.00", [{ amount: "invalid" }])).toEqual({ authoritativeTotalAmount: "100.00", integrity: "unavailable", }); expect(buildReimbursementAmountSummary("0.00", [])).toEqual({ authoritativeTotalAmount: "0.00", integrity: "ready", }); }); test("PC 与移动明细只展示共享模型中的服务端权威合计", () => { const evidenceList = source("src/components/reimbursements/ReimbursementItemEvidenceList.tsx"); const desktopDetail = source("src/pages/ReimbursementDetailPage.tsx"); const mobileDetail = source("src/pages/MobileReimbursementDetailPage.tsx"); const approvalAdapter = source("src/pages/reimbursement-workspace/ReimbursementApprovalAdapter.tsx"); const desktopShell = source("src/components/reimbursements/ReimbursementDesktopWorkspaceShell.tsx"); const mobileShell = source("src/components/reimbursements/ReimbursementMobileWorkspaceShell.tsx"); expect(evidenceList).toContain(" { const desktopShell = source("src/components/reimbursements/ReimbursementDesktopWorkspaceShell.tsx"); const mobileShell = source("src/components/reimbursements/ReimbursementMobileWorkspaceShell.tsx"); const approvalAdapter = source("src/pages/reimbursement-workspace/ReimbursementApprovalAdapter.tsx"); expect(desktopShell).not.toContain("slots.timeline"); expect(desktopShell.indexOf("slots.actionPanel")).toBeLessThan(desktopShell.indexOf("slots.asideContext")); expect(mobileShell.indexOf("slots.afterEvidence")).toBeLessThan(mobileShell.indexOf("slots.timeline")); expect(approvalAdapter).toContain("asideContext: timeline"); expect(approvalAdapter.match(/^\s+timeline,$/gm)).toHaveLength(1); }); test("申请人进度与审批历史保持有意分工", () => { const desktopDetail = source("src/pages/ReimbursementDetailPage.tsx"); const mobileDetail = source("src/pages/MobileReimbursementDetailPage.tsx"); const approvalAdapter = source("src/pages/reimbursement-workspace/ReimbursementApprovalAdapter.tsx"); expect(desktopDetail).toContain(" { const baseDetail = { items: [{ id: "item-1", expenseTypeId: "type-1", expenseDate: "2026-09-01", amount: "100.00", description: "合成明细", assetPriorApprovalBasis: null, expenseType: { id: "type-1", code: "office_supplies", name: "办公用品", category: "office", enabled: true, requiresInvoice: true }, }], attachments: [], }; const buildDetail = (amountEvidenceStatus: "verified" | "manual_review") => ({ ...baseDetail, invoices: [{ id: "invoice-1", status: "bound", invoiceCode: "CODE-1", invoiceNumber: "NUMBER-1", invoiceDate: "2026-09-01", sellerName: "合成供应商", buyerName: null, documentCategory: "invoice", documentSubtype: "vat", totalAmount: "100.00", originalName: "invoice.pdf", mimeType: "application/pdf", fileSize: 100, linkedItemId: "item-1", amountEvidenceStatus, }], }); const snapshot = { standardViolations: [], budgetCheck: null, budgetCheckStatus: "not_applicable" as const, parseStatus: "ready" as const }; const verified = buildReimbursementApprovalEvidence(buildDetail("verified"), snapshot).items[0].invoices[0]; const manualReview = buildReimbursementApprovalEvidence(buildDetail("manual_review"), snapshot).items[0].invoices[0]; const declaredFact = ({ amountEvidenceStatus: _context, ...fact }: typeof verified) => fact; expect(declaredFact(verified)).toEqual(declaredFact(manualReview)); expect(verified.amountEvidenceStatus).toBe("verified"); expect(manualReview.amountEvidenceStatus).toBe("manual_review"); }); test("打印页保持组合归档视图且归档元数据不进入交互工作台", () => { const printPage = source("src/pages/ReimbursementPrintPage.tsx"); const desktopShell = source("src/components/reimbursements/ReimbursementDesktopWorkspaceShell.tsx"); const mobileShell = source("src/components/reimbursements/ReimbursementMobileWorkspaceShell.tsx"); expect(printPage).toContain("const totalAmount = detail.totalAmount"); expect(printPage).toContain("