关闭旧商品匹配默认回退

This commit is contained in:
yml2213
2026-08-18 13:50:56 +08:00
parent 79990066f0
commit c2305d419e
10 changed files with 186 additions and 25 deletions
@@ -72,6 +72,10 @@ import {
getWorkerPlatformNotificationConfig,
saveWorkerPlatformNotificationConfig,
} from './worker-platform-notification-config-service.js'
import {
getWorkerProductMatchConfig,
saveWorkerProductMatchConfig,
} from './worker-product-match-config-service.js'
import { refreshUploadedFileUrls } from '../file-storage/file-storage-service.js'
import {
createWorkOrderMaterialAdminNotification,
@@ -465,7 +469,9 @@ export async function testAdminKuaishouProductMatch(payload: JsonObject = {}) {
listWorkProductRules({ enabled: true }),
listWorkProductRuleMappings({ enabled: true }),
])
const decision = resolveMatchingProductRuleDecision(order, item, rules, mappings)
const decision = resolveMatchingProductRuleDecision(order, item, rules, mappings, {
legacyFallbackEnabled: getWorkerProductMatchConfig().legacyFallbackEnabled,
})
const context = resolveKuaishouWorkProductMatchContext(item)
return {
context,
@@ -972,6 +978,14 @@ export async function saveAdminWorkerPlatformNotificationConfig(payload: JsonObj
return saveWorkerPlatformNotificationConfig(payload)
}
export async function getAdminWorkerProductMatchConfig() {
return getWorkerProductMatchConfig()
}
export async function saveAdminWorkerProductMatchConfig(payload: JsonObject = {}) {
return saveWorkerProductMatchConfig(payload)
}
export async function listAdminWorkerFinanceRequests(query: JsonObject = {}) {
const page = normalizePage(query.page)
const pageSize = normalizePageSize(query.pageSize)
@@ -2002,11 +2016,14 @@ export async function syncWorkerOrdersForSourceOrder(
listWorkProductRules({ enabled: true }),
listWorkProductRuleMappings({ enabled: true }),
])
const productMatchConfig = getWorkerProductMatchConfig()
const created: WorkOrderRow[] = []
const skipped: Array<{ orderItemId: number; reason: string }> = []
for (const item of orderItems) {
const match = resolveMatchingProductRuleDecision(order, item, rules, mappings)
const match = resolveMatchingProductRuleDecision(order, item, rules, mappings, {
legacyFallbackEnabled: productMatchConfig.legacyFallbackEnabled,
})
const matchContext = resolveKuaishouWorkProductMatchContext(item)
if (matchContext.sellerId) {
const snapshot = safeParseJson(item.item_snapshot_json)
@@ -1,3 +1,4 @@
export * from './mappers.js'
export * from './worker-service.js'
export * from './admin-service.js'
export * from './worker-product-match-config-service.js'
@@ -611,8 +611,9 @@ export function resolveMatchingProductRule(
item: OrderItemRow,
rules: WorkProductRuleRow[],
mappings: WorkProductRuleMappingRow[] = [],
options: { legacyFallbackEnabled?: boolean } = {},
) {
return resolveMatchingProductRuleDecision(order, item, rules, mappings).rule
return resolveMatchingProductRuleDecision(order, item, rules, mappings, options).rule
}
export type WorkProductRuleMatchDecision = {
@@ -638,12 +639,15 @@ export function resolveMatchingProductRuleDecision(
item: OrderItemRow,
rules: WorkProductRuleRow[],
mappings: WorkProductRuleMappingRow[] = [],
options: { legacyFallbackEnabled?: boolean } = {},
): WorkProductRuleMatchDecision {
const context = resolveKuaishouWorkProductMatchContext(item)
const rulesById = new Map(rules.map((rule) => [Number(rule.id), rule]))
// 快手发码订单默认只允许确定性商品映射;非快手订单保留原有规则,避免影响其他来源。
const useLegacyFallback = !context.sellerId || options.legacyFallbackEnabled === true
const candidates = [
...mappings.map((mapping) => scoreWorkProductRuleMapping(context, mapping, rulesById)),
...rules.map((rule) => scoreWorkProductRule(order, item, rule)),
...(useLegacyFallback ? rules.map((rule) => scoreWorkProductRule(order, item, rule)) : []),
]
.filter(
(candidate): candidate is { rule: WorkProductRuleRow; score: number; mappingId?: number } =>
@@ -158,41 +158,63 @@ test('排行榜打手名称仅展示首字符', () => {
assert.equal(maskWorkerLeaderboardDisplayName(''), '****')
})
test('快手具体 SKU 精确规则优先于大标题包含规则', () => {
const decision = resolveMatchingProductRuleDecision(buildOrderRow(), buildOrderItemRow(), [
buildProductRule(),
buildProductRule({
id: 2,
rule_key: 'gift-box',
product_name: '精英尊尚专属礼盒',
match_json: {
sellerId: '3676797936',
itemTitle: '和平精英密钥指挥官特种兵侦察兵密钥',
skuNick: '1个精英尊尚专属礼盒',
itemTitleMatchType: 'exact',
skuNickMatchType: 'exact',
},
}),
])
test('开启旧规则兼容匹配后,快手具体 SKU 精确规则优先于大标题包含规则', () => {
const decision = resolveMatchingProductRuleDecision(
buildOrderRow(),
buildOrderItemRow(),
[
buildProductRule(),
buildProductRule({
id: 2,
rule_key: 'gift-box',
product_name: '精英尊尚专属礼盒',
match_json: {
sellerId: '3676797936',
itemTitle: '和平精英密钥指挥官特种兵侦察兵密钥',
skuNick: '1个精英尊尚专属礼盒',
itemTitleMatchType: 'exact',
skuNickMatchType: 'exact',
},
}),
],
[],
{ legacyFallbackEnabled: true },
)
assert.equal(decision.reason, 'matched')
assert.equal(decision.rule?.rule_key, 'gift-box')
})
test('快手同优先级精确规则进入冲突状态', () => {
test('开启旧规则兼容匹配后,快手同优先级精确规则进入冲突状态', () => {
const match = {
sellerId: '3676797936',
skuId: '189872452606936',
}
const decision = resolveMatchingProductRuleDecision(buildOrderRow(), buildOrderItemRow(), [
buildProductRule({ rule_key: 'gift-box-a', match_json: match }),
buildProductRule({ id: 2, rule_key: 'gift-box-b', match_json: match }),
])
const decision = resolveMatchingProductRuleDecision(
buildOrderRow(),
buildOrderItemRow(),
[
buildProductRule({ rule_key: 'gift-box-a', match_json: match }),
buildProductRule({ id: 2, rule_key: 'gift-box-b', match_json: match }),
],
[],
{ legacyFallbackEnabled: true },
)
assert.equal(decision.reason, 'ambiguous')
assert.equal(decision.rule, null)
})
test('快手订单默认不使用旧模板大标题匹配', () => {
const decision = resolveMatchingProductRuleDecision(buildOrderRow(), buildOrderItemRow(), [
buildProductRule({ rule_key: 'special-soldier', product_name: '特种兵' }),
])
assert.equal(decision.reason, 'unmatched')
assert.equal(decision.rule, null)
assert.deepEqual(decision.candidates, [])
})
test('快手 SKU 覆盖映射优先于商品默认映射', () => {
const defaultRule = buildProductRule({ rule_key: 'commander-default', product_name: '' })
const skuRule = buildProductRule({ id: 2, rule_key: 'gift-box-sku', product_name: '' })
@@ -0,0 +1,42 @@
import { APP_CONFIG_KEYS } from '../../config/app-config-keys.js'
import type { JsonObject } from '../../types/json.js'
import { readAppConfigEntry, saveAppConfigEntry } from '../config/app-config-store.js'
export type WorkerProductMatchConfig = {
legacyFallbackEnabled: boolean
}
export function getWorkerProductMatchConfig(): WorkerProductMatchConfig {
return readAppConfigEntry({
configKey: APP_CONFIG_KEYS.workerProductMatch,
fallback: createDefaultWorkerProductMatchConfig,
normalize: normalizeWorkerProductMatchConfig,
})
}
export async function saveWorkerProductMatchConfig(
rawValue: unknown,
): Promise<WorkerProductMatchConfig> {
return saveAppConfigEntry({
configKey: APP_CONFIG_KEYS.workerProductMatch,
value: rawValue,
normalize: normalizeWorkerProductMatchConfig,
})
}
export function createDefaultWorkerProductMatchConfig(): WorkerProductMatchConfig {
return { legacyFallbackEnabled: false }
}
export function normalizeWorkerProductMatchConfig(rawValue: unknown): WorkerProductMatchConfig {
const source = isPlainObject(rawValue) ? rawValue : {}
return {
// 默认关闭旧规则,避免大标题中的公共词触发错误接单。
legacyFallbackEnabled:
typeof source.legacyFallbackEnabled === 'boolean' ? source.legacyFallbackEnabled : false,
}
}
function isPlainObject(value: unknown): value is JsonObject {
return Object.prototype.toString.call(value) === '[object Object]'
}