From 60946b5f6404e0e3cbc8441b74e514a3178009c9 Mon Sep 17 00:00:00 2001 From: yeuimu <2197651308@qq.com> Date: Fri, 28 Aug 2026 12:23:35 +0800 Subject: [PATCH] feat(api): add origin good name parser --- .../origin-name.parser.spec.ts | 96 +++++++++++++++++++ .../product-families/origin-name.parser.ts | 69 +++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 apps/api/src/product-families/origin-name.parser.spec.ts create mode 100644 apps/api/src/product-families/origin-name.parser.ts diff --git a/apps/api/src/product-families/origin-name.parser.spec.ts b/apps/api/src/product-families/origin-name.parser.spec.ts new file mode 100644 index 0000000..52d016b --- /dev/null +++ b/apps/api/src/product-families/origin-name.parser.spec.ts @@ -0,0 +1,96 @@ +import { parseOriginName, originGroupKey } from './origin-name.parser'; + +describe('parseOriginName', () => { + it('解析完整四段名(含仓库)', () => { + expect( + parseOriginName('美国(不包邮)240g涤纶休闲短裤-DG206-单面印花-美西洛杉矶一仓'), + ).toEqual({ + country: '美国', + logisticsLabel: '不包邮', + productName: '240g涤纶休闲短裤', + skuCode: 'DG206', + craftLabel: '单面印花', + warehouseLabel: '美西洛杉矶一仓', + }); + }); + + it('解析三段名(无仓库)', () => { + expect( + parseOriginName('墨西哥(不包邮)180g纯棉女装修身T恤-METP001-单面印花'), + ).toEqual({ + country: '墨西哥', + logisticsLabel: '不包邮', + productName: '180g纯棉女装修身T恤', + skuCode: 'METP001', + craftLabel: '单面印花', + warehouseLabel: null, + }); + }); + + it('物流备注含星号等符号原样保留', () => { + const r = parseOriginName( + '波兰(包邮*运费订单结算时支付)250g男女同款抓毛圆领卫衣-PLHM002-双面印花', + ); + expect(r.logisticsLabel).toBe('包邮*运费订单结算时支付'); + expect(r.skuCode).toBe('PLHM002'); + }); + + it('半角括号也能解析', () => { + const r = parseOriginName('美国(包邮)T恤-DG001-单面印花'); + expect(r.country).toBe('美国'); + expect(r.logisticsLabel).toBe('包邮'); + expect(r.productName).toBe('T恤'); + }); + + it('段1无括号时 country=整段、物流为空', () => { + const r = parseOriginName('美国T恤-DG001-单面印花'); + expect(r.country).toBe('美国T恤'); + expect(r.logisticsLabel).toBeNull(); + expect(r.productName).toBeNull(); + }); + + it('两段名:只解析国家/物流/品名/SKU', () => { + const r = parseOriginName('美国(包邮)T恤-DG001'); + expect(r.skuCode).toBe('DG001'); + expect(r.craftLabel).toBeNull(); + expect(r.warehouseLabel).toBeNull(); + }); + + it('一段名/空值/null 安全', () => { + expect(parseOriginName('随便一个名字').skuCode).toBeNull(); + expect(parseOriginName('随便一个名字').country).toBe('随便一个名字'); + expect(parseOriginName(null).country).toBeNull(); + expect(parseOriginName('').country).toBeNull(); + }); + + it('段前后空格被 trim', () => { + const r = parseOriginName('美国(包邮) T恤 - DG001 - 单面印花'); + expect(r.skuCode).toBe('DG001'); + expect(r.craftLabel).toBe('单面印花'); + expect(r.productName).toBe('T恤'); + }); + + it('仓库段含连字符时整体保留', () => { + const r = parseOriginName('美国(包邮)T恤-DG001-单面印花-某仓-二期'); + expect(r.warehouseLabel).toBe('某仓-二期'); + }); +}); + +describe('originGroupKey', () => { + it('与 admin truncateToProcess 语义一致:前3段', () => { + expect(originGroupKey('美国(包邮)T恤-DG001-单面印花-美西一仓')).toBe( + '美国(包邮)T恤-DG001-单面印花', + ); + }); + + it('物流差异导致不同组', () => { + expect(originGroupKey('美国(不包邮)T恤-DG001-单面印花')).not.toBe( + originGroupKey('美国(包邮)T恤-DG001-单面印花'), + ); + }); + + it('空名返回空串', () => { + expect(originGroupKey(null)).toBe(''); + expect(originGroupKey('')).toBe(''); + }); +}); diff --git a/apps/api/src/product-families/origin-name.parser.ts b/apps/api/src/product-families/origin-name.parser.ts new file mode 100644 index 0000000..7527ec2 --- /dev/null +++ b/apps/api/src/product-families/origin-name.parser.ts @@ -0,0 +1,69 @@ +/** + * 原产品链接名结构化解析。 + * + * 名称格式(与 admin 端 utils/origin-name.ts 的调查结论一致): + * `国家(物流备注)品名-SKU-工艺位置[-仓库名]` + * + * 仓库段为可选;段与段之间以 `-` 分隔,段 1 内国家与物流备注以全角(或半角)括号分隔。 + * 解析失败的字段置 null,不抛异常 —— 上游名称是自由文本,解析器必须容错。 + */ +export interface ParsedOriginName { + country: string | null; + logisticsLabel: string | null; + productName: string | null; + skuCode: string | null; + craftLabel: string | null; + warehouseLabel: string | null; +} + +const EMPTY: ParsedOriginName = { + country: null, + logisticsLabel: null, + productName: null, + skuCode: null, + craftLabel: null, + warehouseLabel: null, +}; + +function splitSegment1(seg: string): Pick { + const pairs: Array<[string, string]> = [ + ['(', ')'], + ['(', ')'], + ]; + for (const [open, close] of pairs) { + const openAt = seg.indexOf(open); + if (openAt >= 0) { + const closeAt = seg.indexOf(close, openAt); + if (closeAt > openAt) { + return { + country: seg.slice(0, openAt).trim() || null, + logisticsLabel: seg.slice(openAt + 1, closeAt).trim() || null, + productName: seg.slice(closeAt + 1).trim() || null, + }; + } + } + } + return { country: seg.trim() || null, logisticsLabel: null, productName: null }; +} + +export function parseOriginName(name: string | null | undefined): ParsedOriginName { + if (!name) return EMPTY; + const segs = name.split('-').map((s) => s.trim()); + if (segs.length === 0 || segs[0] === '') return EMPTY; + return { + ...splitSegment1(segs[0]), + skuCode: segs[1] || null, + craftLabel: segs[2] || null, + // 仓库名本身可能含连字符,第 4 段起整体保留 + warehouseLabel: segs.length > 3 ? segs.slice(3).join('-') : null, + }; +} + +/** + * 分组键:与 admin 端 truncateToProcess(KEEP_SEGMENTS = 3)语义一致, + * 保留按 `-` 分段的前 3 段,丢弃可选的仓库名段。 + */ +export function originGroupKey(name: string | null | undefined): string { + if (!name) return ''; + return name.split('-').slice(0, 3).join('-'); +}