|
|
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 === "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");
|
|
|
});
|
|
|
});
|