/** * SKU 图文件名 → SKU 编号 / 产品条码 自动判别 * * 依据生产数据的稳定结构(配合 lib/spu-parser.ts 的品类字典): * - SKU 编号: [前缀-?]品类码 + 纯数字,如 DNZ038521、AD033601、GBSFY-AD0360、MKHDNZ0409260600987 * - 组合 SKU: ZH- 开头,或以 + 连接多个有效产品编码;整串都属于 SKU 编号 * - 产品条码: SKU 编号 + N数字(V数字)? 段,如 DNZ038521N1280V01、CY001306N35V01 * - 其余(纯序号、02-1、复合串、含中文等)判别不出,交给人工填写 * * 前缀连字符可选:MKH(模块化)等业务前缀直接无连字符拼接品类码; * MKH 不在品类字典中,词典层面无歧义。判定的其余部分整串锚定,绝不猜。 */ import { getCategoryCodes, getShopPrefixes } from '@/lib/spu-parser' /** 直接拼接在品类码前的业务前缀(无连字符形态),与店铺前缀一并剥离判定。 */ const EXTRA_NAKED_PREFIXES = ['MKH'] // ZH 是明确的“组合套装”业务前缀。组合段内即使出现 N/V 也不能按条码解释; // 每段至少同时包含字母码和数字,避免把 ZH-任意文本误判为 SKU。 const COMBINATION_SKU_PATTERN = /^ZH-[A-Z]+\d[A-Z0-9]*(?:-[A-Z]+\d[A-Z0-9]*)+$/i export type SkuOrBarcodeDetection = | { type: 'barcode'; value: string } | { type: 'skuCode'; value: string } function escapeRegExp(value: string): string { return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') } function buildPattern(kind: 'barcode' | 'skuCode'): RegExp { // 长前缀/长品类码放在 alternation 前面,避免短码抢先匹配(如 AD 抢掉 ADX 前缀的场景)。 const shops = [...getShopPrefixes(), ...EXTRA_NAKED_PREFIXES] .map(escapeRegExp) .sort((a, b) => b.length - a.length) .join('|') const categories = getCategoryCodes() .map(escapeRegExp) .sort((a, b) => b.length - a.length) .join('|') const shopPart = shops ? `(?:(?:${shops})-?)?` : '' // N 段必有,V 段可省(生产数据存在 cy001301N35 这种无 V 段的真实条码)。 const tail = kind === 'barcode' ? 'N\\d+(?:V\\d+)?' : '' return new RegExp(`^${shopPart}(?:${categories})\\d+${tail}$`, 'i') } const BARCODE_PATTERN = buildPattern('barcode') const SKU_CODE_PATTERN = buildPattern('skuCode') /** * 判别一个 SKU 图文件名(应先剥掉图片后缀)。 * 返回 null 表示命名不规范,不自动填,由上传人员人工填写。 */ export function detectSkuOrBarcodeName(name: string): SkuOrBarcodeDetection | null { const trimmed = String(name || '').trim() if (!trimmed) return null // 组合 SKU 的业务语义优先于普通 N/V 条码规则。 const parts = trimmed.split('+') const isJoinedSku = parts.length > 1 && parts.every(part => SKU_CODE_PATTERN.test(part) || BARCODE_PATTERN.test(part) ) if (COMBINATION_SKU_PATTERN.test(trimmed) || isJoinedSku) { return { type: 'skuCode', value: trimmed } } // 整串锚定:两条规则天然互斥(skuCode 规则的 \d+$ 不容 N/V 段),顺序无关。 if (BARCODE_PATTERN.test(trimmed)) { return { type: 'barcode', value: trimmed } } if (SKU_CODE_PATTERN.test(trimmed)) { return { type: 'skuCode', value: trimmed } } return null }