feat(intake): validate sku import skuCode spu prefix
standard sku workbook import now rejects rows whose skuCode does not start with the product spu in uppercase (lowercase codes are reported as needing uppercase); rows with an empty skuCode still pass and get auto-generated on save; the whole import is rejected with per-row line numbers and reasons. barcodes are skipped since they derive from the sku code.
This commit is contained in:
parent
e27e4fe67e
commit
fccf9485b7
@ -4,7 +4,10 @@ import {
|
||||
type SetStateAction,
|
||||
} from "react";
|
||||
|
||||
import { parseNonIntegratedSkuWorkbook } from "@/lib/non-integrated-sku-export";
|
||||
import {
|
||||
findSkuImportSpuPrefixErrors,
|
||||
parseNonIntegratedSkuWorkbook,
|
||||
} from "@/lib/non-integrated-sku-export";
|
||||
|
||||
import type { NonIntegratedSkuItem } from "../../nonIntegratedStorage";
|
||||
import type {
|
||||
@ -154,6 +157,17 @@ export function useSkuWorkbookController({
|
||||
if (rows.length === 0) {
|
||||
throw new Error("上传资料中没有可导入的 SKU 行");
|
||||
}
|
||||
if (mode === "standard") {
|
||||
const spuPrefixErrors = findSkuImportSpuPrefixErrors(
|
||||
rows,
|
||||
formValues.spu,
|
||||
);
|
||||
if (spuPrefixErrors.length > 0) {
|
||||
throw new Error(
|
||||
`导入已取消:货号/条码必须以 ${formValues.spu} 开头。${spuPrefixErrors.join(" ")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (mode === "fuzzy") {
|
||||
const supplierErrors = getFuzzyImportSupplierErrors(
|
||||
rows,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import * as XLSX from "xlsx";
|
||||
|
||||
import {
|
||||
findSkuImportSpuPrefixErrors,
|
||||
NON_INTEGRATED_SKU_TEMPLATE_COLUMNS,
|
||||
parseNonIntegratedSkuWorkbook,
|
||||
} from "@/lib/non-integrated-sku-export";
|
||||
@ -109,3 +110,61 @@ describe("parseNonIntegratedSkuWorkbook", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("findSkuImportSpuPrefixErrors", () => {
|
||||
it("rejects non-empty sku codes that miss the spu prefix", () => {
|
||||
const errors = findSkuImportSpuPrefixErrors(
|
||||
[
|
||||
{
|
||||
skuCode: "LRSF009601",
|
||||
barcode: "LRSF009601V01",
|
||||
rowNumber: 2,
|
||||
},
|
||||
],
|
||||
"LRSF0095",
|
||||
);
|
||||
|
||||
expect(errors).toEqual([
|
||||
"第2行:货号「LRSF009601」未以 LRSF0095 开头",
|
||||
]);
|
||||
});
|
||||
|
||||
it("ignores barcodes entirely since they are auto-derived from the sku code", () => {
|
||||
const errors = findSkuImportSpuPrefixErrors(
|
||||
[{ skuCode: "LRSF009501", barcode: "WHATEVER-BARCODE", rowNumber: 2 }],
|
||||
"LRSF0095",
|
||||
);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it("requires uppercase and rejects lowercase sku codes", () => {
|
||||
const errors = findSkuImportSpuPrefixErrors(
|
||||
[{ skuCode: "lrsf009501", barcode: "LRSF009501V01", rowNumber: 2 }],
|
||||
"LRSF0095",
|
||||
);
|
||||
|
||||
expect(errors).toEqual(["第2行:货号「lrsf009501」需为大写"]);
|
||||
});
|
||||
|
||||
it("skips validation when the spu is empty", () => {
|
||||
const errors = findSkuImportSpuPrefixErrors(
|
||||
[{ skuCode: "ANY", barcode: "ANY01", rowNumber: 2 }],
|
||||
"",
|
||||
);
|
||||
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
|
||||
it("falls back to positional row labels when rowNumber is missing", () => {
|
||||
const errors = findSkuImportSpuPrefixErrors(
|
||||
[
|
||||
{ skuCode: "", barcode: "" },
|
||||
{ skuCode: "OTHER01", barcode: "" },
|
||||
],
|
||||
"LRSF0095",
|
||||
);
|
||||
|
||||
expect(errors).toEqual(["第3行:货号「OTHER01」未以 LRSF0095 开头"]);
|
||||
});
|
||||
});
|
||||
|
||||
@ -24,6 +24,33 @@ export type NonIntegratedSkuExportRow = {
|
||||
batchNo: string
|
||||
batchChangeType: string
|
||||
dingTalkFlowNo: string
|
||||
rowNumber?: number
|
||||
}
|
||||
|
||||
export function findSkuImportSpuPrefixErrors(
|
||||
rows: ReadonlyArray<
|
||||
Pick<NonIntegratedSkuExportRow, 'skuCode'> & { rowNumber?: number }
|
||||
>,
|
||||
spu: string,
|
||||
) {
|
||||
const normalizedSpu = spu.trim().toUpperCase()
|
||||
if (!normalizedSpu) return []
|
||||
|
||||
const errors: string[] = []
|
||||
rows.forEach((row, index) => {
|
||||
const rowLabel = row.rowNumber ?? index + 2
|
||||
const skuCode = row.skuCode.trim()
|
||||
if (!skuCode || skuCode.startsWith(normalizedSpu)) return
|
||||
|
||||
if (skuCode.toUpperCase().startsWith(normalizedSpu)) {
|
||||
errors.push(`第${rowLabel}行:货号「${skuCode}」需为大写`)
|
||||
} else {
|
||||
errors.push(
|
||||
`第${rowLabel}行:货号「${skuCode}」未以 ${normalizedSpu} 开头`,
|
||||
)
|
||||
}
|
||||
})
|
||||
return errors
|
||||
}
|
||||
|
||||
export type NonIntegratedSkuFuzzyImportRow = {
|
||||
@ -340,7 +367,7 @@ export async function parseNonIntegratedSkuWorkbook(
|
||||
if (missingLabels.length === 0) {
|
||||
const rows = sheetRows
|
||||
.slice(headerRowIndex + 1)
|
||||
.map((row) => {
|
||||
.map((row, index) => {
|
||||
const importRow = createEmptyImportRow()
|
||||
for (const column of NON_INTEGRATED_SKU_TEMPLATE_COLUMNS) {
|
||||
const cellIndex = headerIndexByLabel.get(column.label)
|
||||
@ -349,6 +376,7 @@ export async function parseNonIntegratedSkuWorkbook(
|
||||
? ''
|
||||
: normalizeWorkbookCellValue(row[cellIndex])
|
||||
}
|
||||
importRow.rowNumber = headerRowIndex + index + 2
|
||||
return importRow
|
||||
})
|
||||
.filter((row) =>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user