|
|
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",
|
|
|
hasContainerConflict: false,
|
|
|
containerSource: null as "body" | "attachment" | null,
|
|
|
validRowCount: 1,
|
|
|
selectedValidCount: 1,
|
|
|
hasUnmappedSelected: false,
|
|
|
ackUnmapped: false,
|
|
|
};
|
|
|
|
|
|
it("does not block header-only import when no valid rows", () => {
|
|
|
const issues = collectForecastImportIssues({
|
|
|
...base,
|
|
|
validRowCount: 0,
|
|
|
selectedValidCount: 0,
|
|
|
shipmentCount: 0,
|
|
|
});
|
|
|
expect(issues.blockers).toEqual([]);
|
|
|
expect(issues.notes.some((n) => n.includes("只提交柜头"))).toBe(true);
|
|
|
});
|
|
|
|
|
|
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("summarizes invalid packing rows without blocking header-only", () => {
|
|
|
const issues = collectForecastImportIssues({
|
|
|
...base,
|
|
|
validRowCount: 0,
|
|
|
selectedValidCount: 0,
|
|
|
invalidRows: [
|
|
|
{ row_index: 1, invalid_reasons: ["渠道缺失", "件数无效"] },
|
|
|
{ row_index: 2, invalid_reasons: ["渠道缺失"] },
|
|
|
],
|
|
|
});
|
|
|
expect(issues.blockers).toEqual([]);
|
|
|
expect(issues.notes[0]).toContain("渠道缺失(2行)");
|
|
|
expect(issues.notes[0]).toContain("件数无效");
|
|
|
});
|
|
|
|
|
|
it("does not block client portal on the first tab", () => {
|
|
|
const issues = collectForecastImportIssues({
|
|
|
...base,
|
|
|
portal: "client",
|
|
|
step: 0,
|
|
|
});
|
|
|
expect(issues.blockers).toEqual([]);
|
|
|
});
|
|
|
});
|