Files
order_site/apps/backend/src/services/fulfillment/internal-sku-resolver.ts
T

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)
}