66 lines
1.9 KiB
TypeScript
66 lines
1.9 KiB
TypeScript
import type { FulfillmentChannel } from './channel-types.js'
|
|
|
|
type JsonObject = Record<string, unknown>
|
|
|
|
export type InternalSkuResolverInput = {
|
|
manualSkuKey?: unknown
|
|
skuCode?: unknown
|
|
skuName?: unknown
|
|
spec?: unknown
|
|
snapshot?: unknown
|
|
}
|
|
|
|
function asObject(value: unknown): JsonObject {
|
|
return value && typeof value === 'object' && !Array.isArray(value) ? (value as JsonObject) : {}
|
|
}
|
|
|
|
/**
|
|
* 内部 SKU 的唯一文本归一:NFKC、去全部空白、转小写。
|
|
* 与数据库函数 canonicalize_internal_sku() 必须保持同口径。
|
|
*/
|
|
export function canonicalizeInternalSku(value: unknown): string {
|
|
return String(value ?? '')
|
|
.normalize('NFKC')
|
|
.replace(/\s+/gu, '')
|
|
.trim()
|
|
.toLowerCase()
|
|
}
|
|
|
|
export function containsCjk(value: string): boolean {
|
|
return /\p{Script=Han}/u.test(value)
|
|
}
|
|
|
|
/**
|
|
* 内部 SKU 的唯一解析口径。
|
|
*
|
|
* 只接受包含中文的内部 SKU。平台码、英文码和空值都不能成为渠道判定键;
|
|
* 它们必须先经过 internal_sku_mappings / 中文商品名归一。
|
|
*/
|
|
export function resolveInternalSkuKey(input: InternalSkuResolverInput): string {
|
|
const snapshot = asObject(input.snapshot)
|
|
const spec = asObject(input.spec)
|
|
const candidates = [
|
|
snapshot.internalSku,
|
|
input.manualSkuKey,
|
|
spec.skuNick,
|
|
spec.outSkuSn,
|
|
input.skuCode,
|
|
input.skuName,
|
|
].map(canonicalizeInternalSku)
|
|
|
|
return candidates.find((value) => value && containsCjk(value)) || ''
|
|
}
|
|
|
|
/** 渠道选择里使用的稳定顺序,仅用于同优先级时生成诊断,不用于静默改渠道。 */
|
|
export const INTERNAL_SKU_CHANNEL_ORDER: readonly FulfillmentChannel[] = [
|
|
'affiliate_dash',
|
|
'work_order',
|
|
'third_party_voucher',
|
|
'manual',
|
|
'on_account',
|
|
]
|
|
|
|
export function compareFulfillmentChannel(a: FulfillmentChannel, b: FulfillmentChannel): number {
|
|
return INTERNAL_SKU_CHANNEL_ORDER.indexOf(a) - INTERNAL_SKU_CHANNEL_ORDER.indexOf(b)
|
|
}
|