|
|
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<typeof generateM2Shipments>): Promise<Buffer> {
|
|
|
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();
|
|
|
});
|
|
|
});
|