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)).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_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(); }); });