gongxiangp2/lib/sku-barcode-detector.ts

76 lines
3.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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
}