import { describe, expect, it } from "vitest"; import ExcelJS from "exceljs"; import { parsePackingListBuffer } from "@/services/parse/packing-list"; import { generateM2Shipments } from "../fixtures/m2-shipments"; async function buildXlsx(rows: ReturnType): Promise { const wb = new ExcelJS.Workbook(); const ws = wb.addWorksheet("卡派资料"); ws.addRow([ "仓库ID", "渠道", "件数", "总体积", "毛重", "FBA ID", "Amazon reference ID", "分货标识", "派送地址", "备注", "柜号", ]); for (const r of rows) { ws.addRow([ r.F_FBACode || "", r.channel_raw || r.F_Transporter || "", r.F_CTNS ?? 0, r.F_CBM, r.F_Weight, r.F_FBAID, r.F_ReferenceId, r.F_ShipmentID, r.F_Address, r.F_Remark, "MATU2745683", ]); } const buf = await wb.xlsx.writeBuffer(); return Buffer.from(buf); } describe("packing-list", () => { it("parses 327 rows with gold sample first two", async () => { const seed = generateM2Shipments(); const buf = await buildXlsx(seed); const parsed = await parsePackingListBuffer(buf, { enableIsoCheck: false }); expect(parsed.errorCode).toBeUndefined(); expect(parsed.shipments.length).toBe(327); expect(parsed.shipments[0].F_FBACode).toBe("ABQ2"); expect(parsed.shipments[0].F_Transporter).toBe("TRUCK"); expect(parsed.shipments[0].F_CTNS).toBe(1); expect(parsed.shipments[1].F_FBACode).toBe("FTW1"); expect(parsed.shipments[1].F_CTNS).toBe(4); }); it("reports missing columns", async () => { const wb = new ExcelJS.Workbook(); const ws = wb.addWorksheet("Sheet1"); ws.addRow(["foo", "bar"]); ws.addRow([1, 2]); const buf = Buffer.from(await wb.xlsx.writeBuffer()); const parsed = await parsePackingListBuffer(buf); expect(parsed.errorCode).toBe("PARSE_FAILED"); expect(parsed.lineage.missing_columns).toBeTruthy(); }); it("parses 新辰泽官方模板(多行表头 + UPS空仓库ID)", async () => { const fs = await import("fs/promises"); const path = await import("path"); const file = path.join( process.cwd(), "docx", "邮件", "模板", "数据模版-新辰泽-WHSU8127240.xlsx", ); const buf = await fs.readFile(file); const parsed = await parsePackingListBuffer(buf, { enableIsoCheck: false }); expect(parsed.errorCode).toBeUndefined(); expect(parsed.container_header.F_ContainerNo).toBe("WHSU8127240"); expect(parsed.shipments.length).toBeGreaterThan(10); const ups = parsed.shipments.filter((s) => s.F_Transporter === "UPS"); expect(ups.length).toBeGreaterThan(0); expect(ups.every((s) => s.F_FBACode === "UPS")).toBe(true); const fedex = parsed.shipments.filter((s) => s.F_Transporter === "FEDEX"); expect(fedex.length).toBeGreaterThan(0); expect(fedex.every((s) => s.F_FBACode === "FEDEX")).toBe(true); const amazon = parsed.shipments.find((s) => s.F_FBAID === "FBA16SQQ7PTF"); expect(amazon?.F_FBACode).toBe("LAS1"); const priv = parsed.shipments.find((s) => (s.F_Address || "").includes("12924 Pierce"), ); expect(priv).toBeTruthy(); expect(priv!.F_Transporter).toBe("TRUCK"); expect(priv!.F_FBACode).toBeFalsy(); expect(parsed.shipments.every((s) => s.F_ShipmentID)).toBe(true); }); it("skips 注意 instruction footer in 智鸿 gold xlsx", async () => { const fs = await import("fs/promises"); const path = await import("path"); const file = path.join( process.cwd(), "docx", "邮件", "模板", "附件下载_邮件识别", "智鸿2+WHLC027G597465+WHSU8127240+洛杉矶+40HQ+EDT2026.04-25 ETA2026.05-15船名航次HMM EMERALD 013E+提拆派.xlsx", ); const buf = await fs.readFile(file); const parsed = await parsePackingListBuffer(buf, { enableIsoCheck: false }); expect(parsed.errorCode).toBeUndefined(); expect(parsed.shipments.length).toBe(1); expect(parsed.shipments[0].row_status).toBe("VALID"); expect(parsed.shipments[0].F_Transporter).toBe("留仓"); expect(parsed.shipments[0].F_FBACode).toBe("HOLD"); expect(parsed.shipments[0].F_CTNS).toBe(1041); expect( parsed.shipments.every((s) => !String(s.F_FBACode || "").includes("注意")), ).toBe(true); }); it("parses FormOrder extra columns (PO/PCS/City/送仓/SKU)", async () => { const wb = new ExcelJS.Workbook(); const ws = wb.addWorksheet("Sheet1"); ws.addRow([ "Move Type", "Custmer Ref No.", "FBAId(Amazon)", "PO(Amazon)", "FBACode", "Address", "Qty.(CTNS)", "Vol(CBM)", "G.W.(KGS)", "PCS", "City", "State", "ZipCode", "Contact", "Mobile", "Email", "行备注", "ReferenceId", "最早送仓日期", "最晚送仓日期", "SKU", "品名", "英文名称", ]); ws.addRow([ "卡派", "REF001", "FBA16SQQ7PTF", "PO123", "LAS1", "12924 Pierce St", 2, 0.5, 10, 20, "Los Angeles", "CA", "90001", "Tom", "123456", "a@b.com", "note1", "REFID1", "2026-05-01", "2026-05-10", "SKU-1", "玩具", "Toy", ]); const buf = Buffer.from(await wb.xlsx.writeBuffer()); const parsed = await parsePackingListBuffer(buf, { enableIsoCheck: false }); expect(parsed.errorCode).toBeUndefined(); expect(parsed.shipments).toHaveLength(1); const s = parsed.shipments[0]; expect(s.F_Transporter).toBe("TRUCK"); expect(s.F_PO).toBe("PO123"); expect(s.F_Pcs).toBe(20); expect(s.F_City).toBe("Los Angeles"); expect(s.F_State).toBe("CA"); expect(s.F_ZipCode).toBe("90001"); expect(s.F_Contact).toBe("Tom"); expect(s.F_Remark).toBe("note1"); expect(s.F_SKU).toBe("SKU-1"); expect(s.F_Description).toBe("玩具"); expect(s.F_EnglishName).toBe("Toy"); expect(s.F_Expected_DeliveryDateB).toBeTruthy(); expect(s.F_Expected_DeliveryDateE).toBeTruthy(); }); it("maps 中文模板列 仓库代码/收件人/公司/省市区/收货地址/备注", async () => { const wb = new ExcelJS.Workbook(); const ws = wb.addWorksheet("Sheet1"); ws.addRow([ "仓库代码", "渠道", "件数", "收件人", "公司名称", "联系电话", "省/市/区", "邮政编码", "收货地址", "备注", ]); ws.addRow([ "LAS1", "卡派", 3, "LAS1", "AMAZON COM SERVICES INC", "0123456789", "NV / HENDERSON /", "89044-8746", "12300 Bermuda Road", "三票分开打托", ]); const buf = Buffer.from(await wb.xlsx.writeBuffer()); const parsed = await parsePackingListBuffer(buf, { enableIsoCheck: false }); expect(parsed.errorCode).toBeUndefined(); expect(parsed.shipments).toHaveLength(1); const s = parsed.shipments[0]; expect(s.F_FBACode).toBe("LAS1"); expect(s.F_Contact).toBe("LAS1"); expect(s.F_EnglishName).toBe("AMAZON COM SERVICES INC"); expect(s.F_Mobile).toBe("0123456789"); expect(s.F_State).toBe("NV"); expect(s.F_City).toBe("HENDERSON"); expect(s.F_ZipCode).toBe("89044-8746"); expect(s.F_Address).toBe("12300 Bermuda Road"); expect(s.F_Remark).toBe("三票分开打托"); }); it("splits Address 列里的仓库代码/收件人/收货地址键值块", async () => { const wb = new ExcelJS.Workbook(); const ws = wb.addWorksheet("Sheet1"); ws.addRow(["FBACode", "渠道", "件数", "Address", "行备注"]); ws.addRow([ "", "卡派", 3, [ "仓库代码: LAS1", "收件人: LAS1", "公司名称: AMAZON COM SERVICES INC", "联系电话: 0123456789", "省/市/区: NV / HENDERSON /", "邮政编码: 89044-8746", "收货地址: 12300 Bermuda Road", ].join("\n"), "三票分开打托", ]); const buf = Buffer.from(await wb.xlsx.writeBuffer()); const parsed = await parsePackingListBuffer(buf, { enableIsoCheck: false }); expect(parsed.errorCode).toBeUndefined(); const s = parsed.shipments[0]; expect(s.F_FBACode).toBe("LAS1"); expect(s.F_Contact).toBe("LAS1"); expect(s.F_EnglishName).toBe("AMAZON COM SERVICES INC"); expect(s.F_Mobile).toBe("0123456789"); expect(s.F_State).toBe("NV"); expect(s.F_City).toBe("HENDERSON"); expect(s.F_ZipCode).toBe("89044-8746"); expect(s.F_Address).toBe("12300 Bermuda Road"); expect(s.F_Remark).toBe("三票分开打托"); }); it("splits 键值块 in FBACode 列 and unused extra column", async () => { const blob = [ "仓库代码: LAS1", "收件人: LAS1", "公司名称: AMAZON COM SERVICES INC", "联系电话: 0123456789", "省/市/区: NV / HENDERSON /", "邮政编码: 89044-8746", "收货地址: 12300 Bermuda Road", "备注:三票分开打托", ].join("\n"); const wb = new ExcelJS.Workbook(); const ws = wb.addWorksheet("Sheet1"); ws.addRow(["FBACode", "渠道", "件数", "Address", "其它"]); ws.addRow(["", "卡派", 3, "", blob]); const buf = Buffer.from(await wb.xlsx.writeBuffer()); const parsed = await parsePackingListBuffer(buf, { enableIsoCheck: false }); const s = parsed.shipments[0]; expect(s.F_FBACode).toBe("LAS1"); expect(s.F_Address).toBe("12300 Bermuda Road"); expect(s.F_Contact).toBe("LAS1"); expect(s.F_Remark).toBe("三票分开打托"); }); it("keeps dedicated Contact column over Address 块里的收件人", async () => { const wb = new ExcelJS.Workbook(); const ws = wb.addWorksheet("Sheet1"); ws.addRow(["FBACode", "渠道", "件数", "Address", "Contact"]); ws.addRow([ "", "卡派", 1, "仓库代码: ONT8\n收件人: FROMADDR\n收货地址: 1 Main St", "Tom", ]); const buf = Buffer.from(await wb.xlsx.writeBuffer()); const parsed = await parsePackingListBuffer(buf, { enableIsoCheck: false }); const s = parsed.shipments[0]; expect(s.F_FBACode).toBe("ONT8"); expect(s.F_Contact).toBe("Tom"); expect(s.F_Address).toBe("1 Main St"); }); });