You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

305 lines
10 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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