fix(pdd): 匹配诊断对齐 out_sku_sn,并加速拼多多订单号搜索

匹配测试/结构化规则补读商家编码,诊断日志标出缺失字段;订单/任务/工单关键词支持带连字符的 PDD order_sn 走索引快路径。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
yml2213
2026-09-15 10:45:53 +08:00
co-authored by Cursor
parent 4a3b4ec14d
commit 5cbb977b96
11 changed files with 237 additions and 43 deletions
@@ -56,6 +56,38 @@ test('订单列表保留订单号和精确 SKU 旧筛选参数兼容', async ()
assert.deepEqual(calls[0]?.params, ['%26245%', 'SKU-1'])
})
test('订单列表关键词为拼多多 order_sn 时走平台订单号索引快路径', async () => {
const calls: Array<{ sql: string; params: unknown[] }> = []
const orderSn = '260915-091435834962130'
await listOrdersWithExecutor(createExecutor(calls, 0), {
keyword: orderSn,
})
assert.match(
calls[0]?.sql || '',
/o\.platform_order_id = \$1 OR o\.platform_order_id LIKE \$1 \|\| '%'/s,
)
assert.doesNotMatch(calls[0]?.sql || '', /oi\.sku_code/i)
assert.doesNotMatch(calls[0]?.sql || '', /item_snapshot_json/i)
assert.deepEqual(calls[0]?.params, [orderSn])
})
test('订单列表 platformOrderId 为拼多多 order_sn 时走索引快路径', async () => {
const calls: Array<{ sql: string; params: unknown[] }> = []
const orderSn = '260915-091435834962130'
await listOrdersWithExecutor(createExecutor(calls, 0), {
platformOrderId: orderSn,
})
assert.match(
calls[0]?.sql || '',
/o\.platform_order_id = \$1 OR o\.platform_order_id LIKE \$1 \|\| '%'/s,
)
assert.deepEqual(calls[0]?.params, [orderSn])
})
function createExecutor(
calls: Array<{ sql: string; params: unknown[] }>,
total: number,
+14 -4
View File
@@ -7,6 +7,7 @@ import type {
OrderUpdateInput,
} from '../types/repository/inputs.js'
import type { OrderListQueryResult, OrderListRow, OrderRow } from '../types/repository/rows.js'
import { isPlatformOrderIdLookupKeyword } from '../utils/platform-order-id.js'
export type OrderQueryExecutor = <T extends QueryResultRow = QueryResultRow>(
text: string,
@@ -251,9 +252,9 @@ export async function listOrdersWithExecutor(
if (keyword) {
const trimmedKeyword = String(keyword).trim()
const isPureOrderNo = /^\d{6,}$/.test(trimmedKeyword)
const isOrderNoLookup = isPlatformOrderIdLookupKeyword(trimmedKeyword)
if (isPureOrderNo) {
if (isOrderNoLookup) {
params.push(trimmedKeyword)
const paramIndex = `$${params.length}`
filters.push(
@@ -299,8 +300,17 @@ export async function listOrdersWithExecutor(
}
if (platformOrderId) {
params.push(`%${platformOrderId}%`)
filters.push(`o.platform_order_id ILIKE $${params.length}`)
const trimmedPlatformOrderId = String(platformOrderId).trim()
if (isPlatformOrderIdLookupKeyword(trimmedPlatformOrderId)) {
params.push(trimmedPlatformOrderId)
const paramIndex = `$${params.length}`
filters.push(
`(o.platform_order_id = ${paramIndex} OR o.platform_order_id LIKE ${paramIndex} || '%')`,
)
} else {
params.push(`%${trimmedPlatformOrderId}%`)
filters.push(`o.platform_order_id ILIKE $${params.length}`)
}
}
if (payStatus) {
@@ -62,6 +62,20 @@ test('任务列表关键词为纯数字时智能路由到平台订单号索引
assert.deepEqual(result.params, ['20247230034021572'])
})
test('任务列表关键词为拼多多 order_sn 时智能路由到平台订单号索引条件', () => {
const result = buildTaskListFilters({
keyword: '260915-091435834962130',
})
assert.match(
result.whereClause,
/ft\.platform_order_id = \$1 OR ft\.platform_order_id LIKE \$1 \|\| '%'/s,
)
assert.doesNotMatch(result.whereClause, /oi\.sku_code/i)
assert.doesNotMatch(result.whereClause, /item_snapshot_json/i)
assert.deepEqual(result.params, ['260915-091435834962130'])
})
test('任务列表关键词为 DT 任务号时智能路由到任务号索引条件', () => {
const result = buildTaskListFilters({
keyword: 'DT881aa66294cc',
+14 -4
View File
@@ -1,6 +1,7 @@
import { query, withTransaction } from '../db/client.js'
import type { PoolClient } from 'pg'
import { isPlatformOrderIdLookupKeyword } from '../utils/platform-order-id.js'
import { normalizeTimestampIso } from '../utils/time.js'
import type {
TaskCreateInput,
@@ -471,10 +472,10 @@ export function buildTaskListFilters({
if (keyword) {
const trimmedKeyword = String(keyword).trim()
const isPureOrderNo = /^\d{6,}$/.test(trimmedKeyword)
const isOrderNoLookup = isPlatformOrderIdLookupKeyword(trimmedKeyword)
const isTaskNo = /^DT[a-zA-Z0-9_-]+$/i.test(trimmedKeyword)
if (isPureOrderNo) {
if (isOrderNoLookup) {
params.push(trimmedKeyword)
const paramIndex = `$${params.length}`
filters.push(
@@ -510,8 +511,17 @@ export function buildTaskListFilters({
}
if (platformOrderId) {
params.push(`%${platformOrderId}%`)
filters.push(`ft.platform_order_id ILIKE $${params.length}`)
const trimmedPlatformOrderId = String(platformOrderId).trim()
if (isPlatformOrderIdLookupKeyword(trimmedPlatformOrderId)) {
params.push(trimmedPlatformOrderId)
const paramIndex = `$${params.length}`
filters.push(
`(ft.platform_order_id = ${paramIndex} OR ft.platform_order_id LIKE ${paramIndex} || '%')`,
)
} else {
params.push(`%${trimmedPlatformOrderId}%`)
filters.push(`ft.platform_order_id ILIKE $${params.length}`)
}
}
if (taskNo) {
@@ -5,6 +5,7 @@ import {
normalizeWorkOrderAcceptanceMode,
WORK_ORDER_GIFT_PHASE,
} from '../../domain/work-order-acceptance-mode.js'
import { isPlatformOrderIdLookupKeyword } from '../../utils/platform-order-id.js'
export const WORK_ORDER_SELECT = `
SELECT
wo.*,
@@ -819,8 +820,7 @@ export function buildWorkOrderWhere({
filters.push(`wo.id = $${params.length}`)
}
if (normalizedKeyword) {
const isPureOrderNo = /^\d{6,}$/.test(normalizedKeyword)
if (isPureOrderNo) {
if (isPlatformOrderIdLookupKeyword(normalizedKeyword)) {
params.push(normalizedKeyword)
const paramIndex = `$${params.length}`
filters.push(
@@ -16,6 +16,10 @@ import type { JsonObject } from '../../types/json.js'
import type { OrderItemRow, OrderRow } from '../../types/repository/rows.js'
import { createHttpError } from '../../utils/http.js'
import { nowIso } from '../../utils/time.js'
import {
extractPddOutSkuSn,
type PddRawOrderItem,
} from '../platforms/pdd/pdd-order-mapper.js'
import {
normalizeDateString,
normalizePage,
@@ -251,15 +255,21 @@ export async function listAdminKuaishouMatchSources(payload: JsonObject = {}) {
for (const row of recentOrderItemsResult.rows) {
const snapshot = safeParseJson(row.item_snapshot_json)
const ksc = safeParseJson(snapshot.kuaishouSendCode)
const outSkuSn = String(snapshot.out_sku_sn || snapshot.outSkuSn || '').trim()
const sellerId = normalizeKuaishouMatchId(ksc.sellerId || row.shop_id || row.platform)
const relItemId = normalizeKuaishouMatchId(
ksc.relItemId || row.sku_code || snapshot.goodsId || snapshot.goods_id,
ksc.relItemId || snapshot.goodsId || snapshot.goods_id || row.sku_code,
)
const itemTitle = String(
ksc.itemTitle || snapshot.goodsName || snapshot.goods_name || row.sku_name || '',
).trim()
const relSkuId = normalizeKuaishouMatchId(ksc.relSkuId || snapshot.spec || row.sku_code)
const skuNick = String(ksc.skuNick || snapshot.spec || row.sku_name || '').trim()
// 拼多多优先展示/分组 out_sku_sn,与生产匹配字段对齐
const relSkuId = normalizeKuaishouMatchId(
ksc.relSkuId || outSkuSn || snapshot.spec || row.sku_code,
)
const skuNick = String(
ksc.skuNick || outSkuSn || snapshot.spec || row.sku_name || '',
).trim()
if (!sellerId || (!relItemId && !itemTitle)) continue
@@ -296,30 +306,45 @@ export async function listAdminKuaishouMatchSources(payload: JsonObject = {}) {
export async function testAdminKuaishouProductMatch(payload: JsonObject = {}) {
const body = resolveKuaishouMatchPayload(payload.rawPayload ?? payload.raw_payload ?? payload)
const ext = safeParseJson(body.ext)
const outSkuSn = extractPddOutSkuSn(body as PddRawOrderItem)
const goodsId = String(body.goodsId || body.goods_id || '').trim()
const goodsName = String(body.goodsName || body.goods_name || body.itemTitle || '').trim()
const spec = String(body.spec || '').trim()
const isPdd =
body.platform === 'pdd' ||
String(body.provider || '').trim() === 'pdd' ||
String(body.sellerId || '').includes('pdd') ||
Boolean(body.goodsName || body.goods_name || body.goodsId || body.goods_id)
Boolean(outSkuSn) ||
Boolean(body.order_sn && (goodsName || goodsId))
// 与生产 mapper 对齐:匹配字段优先 out_sku_sn
const skuCode = String(body.sku_code || outSkuSn || body.itemId || goodsId || '').trim()
const skuName = String(body.sku_name || outSkuSn || spec || goodsName || '').trim()
const order = {
provider: String(body.provider || (isPdd ? 'pdd' : '91kaquan')).trim(),
platform: String(body.platform || (isPdd ? 'pdd' : 'kuaishou')).trim(),
shop_id: String(body.sellerId || body.shop_id || '').trim(),
} as OrderRow
const relItemId = normalizeKuaishouMatchId(ext.relItemId || body.goodsId || body.goods_id)
const relSkuId = normalizeKuaishouMatchId(ext.relSkuId || body.spec)
const itemTitle = String(body.itemTitle || body.goodsName || body.goods_name || '').trim()
const skuNick = String(ext.skuNick || body.skuNick || body.spec || '').trim()
const relItemId = normalizeKuaishouMatchId(ext.relItemId || goodsId)
const relSkuId = normalizeKuaishouMatchId(ext.relSkuId || outSkuSn || body.spec)
const itemTitle = String(body.itemTitle || goodsName || '').trim()
const skuNick = String(ext.skuNick || body.skuNick || outSkuSn || body.spec || '').trim()
const item = {
id: 0,
order_id: 0,
sku_code: String(body.sku_code || body.itemId || relItemId || '').trim(),
sku_name: String(body.sku_name || skuNick || itemTitle || '').trim(),
quantity: Math.max(1, Number(body.num || body.quantity) || 1),
sku_code: skuCode,
sku_name: skuName,
quantity: Math.max(1, Number(body.num || body.quantity || body.goods_number) || 1),
spec_json: {},
item_snapshot_json: {
goodsId: body.goodsId || body.goods_id,
goodsName: body.goodsName || body.goods_name,
goods_id: body.goods_id || body.goodsId,
goodsName,
goods_name: body.goods_name || body.goodsName || goodsName,
spec: body.spec,
out_sku_sn: outSkuSn,
outSkuSn,
externalSkuCode: skuCode,
externalSkuName: skuName,
kuaishouSendCode: {
sellerId: String(body.sellerId || order.shop_id || '').trim(),
itemId: String(body.itemId || '').trim(),
@@ -241,14 +241,20 @@ function scoreWorkProductRule(order: OrderRow, item: OrderItemRow, rule: WorkPro
const snapshot = safeParseJson(item.item_snapshot_json)
const source = safeParseJson(snapshot.kuaishouSendCode)
const outSkuSn = String(snapshot.out_sku_sn || snapshot.outSkuSn || '').trim()
const context = {
sellerId: normalizeMatchId(source.sellerId),
itemId: normalizeMatchId(source.itemId),
relItemId: normalizeMatchId(source.relItemId),
relItemId: normalizeMatchId(source.relItemId || snapshot.goodsId || snapshot.goods_id),
skuId: normalizeMatchId(source.skuId),
relSkuId: normalizeMatchId(source.relSkuId),
itemTitle: normalizeMatchText(source.itemTitle),
skuNick: normalizeMatchText(source.skuNick || source.skuName || snapshot.externalSkuName),
relSkuId: normalizeMatchId(source.relSkuId || outSkuSn || snapshot.spec),
itemTitle: normalizeMatchText(
source.itemTitle || snapshot.goodsName || snapshot.goods_name || '',
),
// 拼多多等平台:结构化匹配也要读 out_sku_sn,避免有编码却未命中
skuNick: normalizeMatchText(
source.skuNick || source.skuName || outSkuSn || snapshot.externalSkuName || snapshot.spec,
),
}
const ruleShopId = normalizeMatchId(rule.shop_id)
if (
@@ -292,7 +298,6 @@ function scoreWorkProductRule(order: OrderRow, item: OrderItemRow, rule: WorkPro
return { rule, score }
}
const outSkuSn = String(snapshot.out_sku_sn || snapshot.outSkuSn || '').trim()
const skuCodes = [item.sku_code, snapshot.externalSkuCode, outSkuSn, snapshot.externalItemId]
.map(normalizeMatchId)
.filter(Boolean)
@@ -602,6 +602,58 @@ test('拼多多订单使用 out_sku_sn 字段精准匹配接单规则', () => {
assert.equal(decision.rule?.id, rule.id)
})
test('拼多多订单在结构化 match_json 下仍可用 out_sku_sn 命中', () => {
const rule = buildProductRule({
provider: 'pdd',
platform: 'pdd',
product_name: '安卓怒嚎3钻',
match_type: 'contains',
match_json: {
skuNick: '安卓怒嚎3钻',
skuNickMatchType: 'contains',
},
})
const order = { ...buildOrderRow(), provider: 'pdd', platform: 'pdd', shop_id: 'pdd_shop' }
const item = {
...buildOrderItemRow(),
sku_code: '安卓怒嚎3钻',
sku_name: '安卓怒嚎3钻',
item_snapshot_json: {
goods_id: 961856378217,
goods_name: '和平精英地铁逃生苏尔南暗潮怒嚎血宴苏尔南1-6钻手枪传世武器',
spec: '安卓区(qv互通),苏尔南-怒嚎3钻',
out_sku_sn: '安卓怒嚎3钻',
},
}
const decision = resolveMatchingProductRuleDecision(order, item, [rule], [])
assert.equal(decision.reason, 'matched')
assert.equal(decision.rule?.id, rule.id)
})
test('拼多多订单缺少 out_sku_sn 且 spec 不含规则名时不命中', () => {
const rule = buildProductRule({
provider: 'pdd',
platform: 'pdd',
product_name: '安卓怒嚎3钻',
match_type: 'contains',
})
const order = { ...buildOrderRow(), provider: 'pdd', platform: 'pdd' }
const item = {
...buildOrderItemRow(),
sku_code: '961856378217',
sku_name: '安卓区(qv互通),苏尔南-怒嚎3钻',
item_snapshot_json: {
goods_id: 961856378217,
goods_name: '和平精英地铁逃生苏尔南暗潮怒嚎血宴苏尔南1-6钻手枪传世武器',
spec: '安卓区(qv互通),苏尔南-怒嚎3钻',
},
}
const decision = resolveMatchingProductRuleDecision(order, item, [rule], [])
assert.equal(decision.reason, 'unmatched')
})
test('assertWorkerLoginAllowed blocks rejected (frozen) workers with worker_rejected', () => {
assert.throws(
() => assertWorkerLoginAllowed(buildWorkerUserRow({ status: 'rejected' })),
@@ -0,0 +1,22 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import { isPlatformOrderIdLookupKeyword } from './platform-order-id.js'
test('识别纯数字平台订单号', () => {
assert.equal(isPlatformOrderIdLookupKeyword('20247230034021572'), true)
assert.equal(isPlatformOrderIdLookupKeyword('123456'), true)
assert.equal(isPlatformOrderIdLookupKeyword('12345'), false)
})
test('识别拼多多带连字符的 order_sn', () => {
assert.equal(isPlatformOrderIdLookupKeyword('260915-091435834962130'), true)
assert.equal(isPlatformOrderIdLookupKeyword('260912-123456789012345'), true)
})
test('拒绝普通关键词', () => {
assert.equal(isPlatformOrderIdLookupKeyword('皮肤店'), false)
assert.equal(isPlatformOrderIdLookupKeyword('安卓怒嚎3钻'), false)
assert.equal(isPlatformOrderIdLookupKeyword('DT881aa66294cc'), false)
assert.equal(isPlatformOrderIdLookupKeyword(''), false)
})
@@ -0,0 +1,8 @@
/**
* Detect platform order IDs that should use indexed equality/prefix lookup.
* Covers pure numeric IDs (快手等) and PDD style `digits-digits` (e.g. 260915-091435834962130).
*/
export function isPlatformOrderIdLookupKeyword(value: unknown): boolean {
const trimmed = String(value || '').trim()
return /^\d{6,}(-\d{6,})?$/.test(trimmed)
}