|
|
import { describe, expect, it } from "vitest";
|
|
|
import {
|
|
|
canEnterForecastConfirm,
|
|
|
collectForecastImportIssues,
|
|
|
} from "@/services/cc/forecast-confirm-gate";
|
|
|
|
|
|
describe("canEnterForecastConfirm", () => {
|
|
|
it("allows forecast data regardless of mail main type", () => {
|
|
|
expect(
|
|
|
canEnterForecastConfirm({
|
|
|
status: "PENDING_CONFIRM",
|
|
|
validShipmentCount: 1,
|
|
|
}),
|
|
|
).toBe(true);
|
|
|
});
|
|
|
|
|
|
it("blocks empty forecast without container header", () => {
|
|
|
expect(
|
|
|
canEnterForecastConfirm({
|
|
|
status: "PENDING_CONFIRM",
|
|
|
validShipmentCount: 0,
|
|
|
}),
|
|
|
).toBe(false);
|
|
|
});
|
|
|
|
|
|
it("allows header-only forecast (no shipment rows)", () => {
|
|
|
expect(
|
|
|
canEnterForecastConfirm({
|
|
|
status: "PENDING_CONFIRM",
|
|
|
validShipmentCount: 0,
|
|
|
hasContainerHeader: true,
|
|
|
}),
|
|
|
).toBe(true);
|
|
|
});
|
|
|
|
|
|
it("allows PARTIAL_SUCCESS so remaining instructions stay confirmable", () => {
|
|
|
expect(
|
|
|
canEnterForecastConfirm({
|
|
|
status: "PARTIAL_SUCCESS",
|
|
|
validShipmentCount: 1,
|
|
|
}),
|
|
|
).toBe(true);
|
|
|
});
|
|
|
});
|
|
|
|
|
|
describe("collectForecastImportIssues", () => {
|
|
|
const base = {
|
|
|
transMode: 0 as const,
|
|
|
operationType: 0 as const,
|
|
|
containerNo: "TRHU8524770",
|
|
|
cabinetType: "40HQ",
|
|
|
hasContainerConflict: false,
|
|
|
containerSource: null as "body" | "attachment" | null,
|
|
|
validRowCount: 1,
|
|
|
selectedValidCount: 1,
|
|
|
hasUnmappedSelected: false,
|
|
|
ackUnmapped: false,
|
|
|
};
|
|
|
|
|
|
it("blocks when no shipment info (API requires shipment fields)", () => {
|
|
|
const issues = collectForecastImportIssues({
|
|
|
...base,
|
|
|
validRowCount: 0,
|
|
|
selectedValidCount: 0,
|
|
|
shipmentCount: 0,
|
|
|
});
|
|
|
expect(issues.blockers.some((b) => /货件/.test(b))).toBe(true);
|
|
|
expect(issues.errors.rows).toBeTruthy();
|
|
|
});
|
|
|
|
|
|
it("blocks when valid rows exist but none selected", () => {
|
|
|
const issues = collectForecastImportIssues({
|
|
|
...base,
|
|
|
selectedValidCount: 0,
|
|
|
});
|
|
|
expect(issues.blockers[0]).toContain("至少勾选一行有效货件");
|
|
|
expect(issues.errors.rows).toBeTruthy();
|
|
|
});
|
|
|
|
|
|
it("explains empty and invalid container numbers", () => {
|
|
|
expect(
|
|
|
collectForecastImportIssues({ ...base, containerNo: "" }).blockers[0],
|
|
|
).toContain("柜号为空");
|
|
|
expect(
|
|
|
collectForecastImportIssues({ ...base, containerNo: "ABCD1234567" })
|
|
|
.blockers[0],
|
|
|
).toContain("ISO 6346");
|
|
|
});
|
|
|
|
|
|
it("blocks invalid packing rows with no valid shipment", () => {
|
|
|
const issues = collectForecastImportIssues({
|
|
|
...base,
|
|
|
validRowCount: 0,
|
|
|
selectedValidCount: 0,
|
|
|
invalidRows: [
|
|
|
{ row_index: 1, invalid_reasons: ["渠道缺失", "件数无效"] },
|
|
|
{ row_index: 2, invalid_reasons: ["渠道缺失"] },
|
|
|
],
|
|
|
});
|
|
|
expect(issues.blockers.some((b) => /有效货件/.test(b))).toBe(true);
|
|
|
expect(issues.notes[0]).toContain("渠道缺失(2行)");
|
|
|
expect(issues.notes[0]).toContain("件数无效");
|
|
|
});
|
|
|
|
|
|
it("still blocks missing required fields on client first tab", () => {
|
|
|
const issues = collectForecastImportIssues({
|
|
|
...base,
|
|
|
portal: "client",
|
|
|
step: 0,
|
|
|
operationType: undefined,
|
|
|
validRowCount: 0,
|
|
|
selectedValidCount: 0,
|
|
|
shipmentCount: 0,
|
|
|
});
|
|
|
expect(issues.errors.operationType).toMatch(/业务类型/);
|
|
|
expect(issues.blockers.length).toBeGreaterThanOrEqual(2);
|
|
|
});
|
|
|
|
|
|
it("blocks missing transport / operation type with field errors", () => {
|
|
|
const issues = collectForecastImportIssues({
|
|
|
...base,
|
|
|
transMode: undefined,
|
|
|
operationType: undefined,
|
|
|
});
|
|
|
expect(issues.errors.transMode).toMatch(/运输方式/);
|
|
|
expect(issues.errors.operationType).toMatch(/业务类型/);
|
|
|
expect(issues.blockers.length).toBeGreaterThanOrEqual(2);
|
|
|
});
|
|
|
|
|
|
it("blocks empty cabinet type", () => {
|
|
|
const issues = collectForecastImportIssues({
|
|
|
...base,
|
|
|
cabinetType: "",
|
|
|
});
|
|
|
expect(issues.errors.cabinetType).toMatch(/柜型/);
|
|
|
expect(issues.blockers.some((b) => /柜型/.test(b))).toBe(true);
|
|
|
});
|
|
|
});
|