fix(pdd): 匹配诊断对齐 out_sku_sn,并加速拼多多订单号搜索
匹配测试/结构化规则补读商家编码,诊断日志标出缺失字段;订单/任务/工单关键词支持带连字符的 PDD order_sn 走索引快路径。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -56,6 +56,38 @@ test('订单列表保留订单号和精确 SKU 旧筛选参数兼容', async ()
|
|||||||
assert.deepEqual(calls[0]?.params, ['%26245%', 'SKU-1'])
|
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(
|
function createExecutor(
|
||||||
calls: Array<{ sql: string; params: unknown[] }>,
|
calls: Array<{ sql: string; params: unknown[] }>,
|
||||||
total: number,
|
total: number,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import type {
|
|||||||
OrderUpdateInput,
|
OrderUpdateInput,
|
||||||
} from '../types/repository/inputs.js'
|
} from '../types/repository/inputs.js'
|
||||||
import type { OrderListQueryResult, OrderListRow, OrderRow } from '../types/repository/rows.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>(
|
export type OrderQueryExecutor = <T extends QueryResultRow = QueryResultRow>(
|
||||||
text: string,
|
text: string,
|
||||||
@@ -251,9 +252,9 @@ export async function listOrdersWithExecutor(
|
|||||||
|
|
||||||
if (keyword) {
|
if (keyword) {
|
||||||
const trimmedKeyword = String(keyword).trim()
|
const trimmedKeyword = String(keyword).trim()
|
||||||
const isPureOrderNo = /^\d{6,}$/.test(trimmedKeyword)
|
const isOrderNoLookup = isPlatformOrderIdLookupKeyword(trimmedKeyword)
|
||||||
|
|
||||||
if (isPureOrderNo) {
|
if (isOrderNoLookup) {
|
||||||
params.push(trimmedKeyword)
|
params.push(trimmedKeyword)
|
||||||
const paramIndex = `$${params.length}`
|
const paramIndex = `$${params.length}`
|
||||||
filters.push(
|
filters.push(
|
||||||
@@ -299,8 +300,17 @@ export async function listOrdersWithExecutor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (platformOrderId) {
|
if (platformOrderId) {
|
||||||
params.push(`%${platformOrderId}%`)
|
const trimmedPlatformOrderId = String(platformOrderId).trim()
|
||||||
filters.push(`o.platform_order_id ILIKE $${params.length}`)
|
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) {
|
if (payStatus) {
|
||||||
|
|||||||
@@ -62,6 +62,20 @@ test('任务列表关键词为纯数字时智能路由到平台订单号索引
|
|||||||
assert.deepEqual(result.params, ['20247230034021572'])
|
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 任务号时智能路由到任务号索引条件', () => {
|
test('任务列表关键词为 DT 任务号时智能路由到任务号索引条件', () => {
|
||||||
const result = buildTaskListFilters({
|
const result = buildTaskListFilters({
|
||||||
keyword: 'DT881aa66294cc',
|
keyword: 'DT881aa66294cc',
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { query, withTransaction } from '../db/client.js'
|
import { query, withTransaction } from '../db/client.js'
|
||||||
import type { PoolClient } from 'pg'
|
import type { PoolClient } from 'pg'
|
||||||
|
|
||||||
|
import { isPlatformOrderIdLookupKeyword } from '../utils/platform-order-id.js'
|
||||||
import { normalizeTimestampIso } from '../utils/time.js'
|
import { normalizeTimestampIso } from '../utils/time.js'
|
||||||
import type {
|
import type {
|
||||||
TaskCreateInput,
|
TaskCreateInput,
|
||||||
@@ -471,10 +472,10 @@ export function buildTaskListFilters({
|
|||||||
|
|
||||||
if (keyword) {
|
if (keyword) {
|
||||||
const trimmedKeyword = String(keyword).trim()
|
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)
|
const isTaskNo = /^DT[a-zA-Z0-9_-]+$/i.test(trimmedKeyword)
|
||||||
|
|
||||||
if (isPureOrderNo) {
|
if (isOrderNoLookup) {
|
||||||
params.push(trimmedKeyword)
|
params.push(trimmedKeyword)
|
||||||
const paramIndex = `$${params.length}`
|
const paramIndex = `$${params.length}`
|
||||||
filters.push(
|
filters.push(
|
||||||
@@ -510,8 +511,17 @@ export function buildTaskListFilters({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (platformOrderId) {
|
if (platformOrderId) {
|
||||||
params.push(`%${platformOrderId}%`)
|
const trimmedPlatformOrderId = String(platformOrderId).trim()
|
||||||
filters.push(`ft.platform_order_id ILIKE $${params.length}`)
|
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) {
|
if (taskNo) {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
normalizeWorkOrderAcceptanceMode,
|
normalizeWorkOrderAcceptanceMode,
|
||||||
WORK_ORDER_GIFT_PHASE,
|
WORK_ORDER_GIFT_PHASE,
|
||||||
} from '../../domain/work-order-acceptance-mode.js'
|
} from '../../domain/work-order-acceptance-mode.js'
|
||||||
|
import { isPlatformOrderIdLookupKeyword } from '../../utils/platform-order-id.js'
|
||||||
export const WORK_ORDER_SELECT = `
|
export const WORK_ORDER_SELECT = `
|
||||||
SELECT
|
SELECT
|
||||||
wo.*,
|
wo.*,
|
||||||
@@ -819,8 +820,7 @@ export function buildWorkOrderWhere({
|
|||||||
filters.push(`wo.id = $${params.length}`)
|
filters.push(`wo.id = $${params.length}`)
|
||||||
}
|
}
|
||||||
if (normalizedKeyword) {
|
if (normalizedKeyword) {
|
||||||
const isPureOrderNo = /^\d{6,}$/.test(normalizedKeyword)
|
if (isPlatformOrderIdLookupKeyword(normalizedKeyword)) {
|
||||||
if (isPureOrderNo) {
|
|
||||||
params.push(normalizedKeyword)
|
params.push(normalizedKeyword)
|
||||||
const paramIndex = `$${params.length}`
|
const paramIndex = `$${params.length}`
|
||||||
filters.push(
|
filters.push(
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ import type { JsonObject } from '../../types/json.js'
|
|||||||
import type { OrderItemRow, OrderRow } from '../../types/repository/rows.js'
|
import type { OrderItemRow, OrderRow } from '../../types/repository/rows.js'
|
||||||
import { createHttpError } from '../../utils/http.js'
|
import { createHttpError } from '../../utils/http.js'
|
||||||
import { nowIso } from '../../utils/time.js'
|
import { nowIso } from '../../utils/time.js'
|
||||||
|
import {
|
||||||
|
extractPddOutSkuSn,
|
||||||
|
type PddRawOrderItem,
|
||||||
|
} from '../platforms/pdd/pdd-order-mapper.js'
|
||||||
import {
|
import {
|
||||||
normalizeDateString,
|
normalizeDateString,
|
||||||
normalizePage,
|
normalizePage,
|
||||||
@@ -251,15 +255,21 @@ export async function listAdminKuaishouMatchSources(payload: JsonObject = {}) {
|
|||||||
for (const row of recentOrderItemsResult.rows) {
|
for (const row of recentOrderItemsResult.rows) {
|
||||||
const snapshot = safeParseJson(row.item_snapshot_json)
|
const snapshot = safeParseJson(row.item_snapshot_json)
|
||||||
const ksc = safeParseJson(snapshot.kuaishouSendCode)
|
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 sellerId = normalizeKuaishouMatchId(ksc.sellerId || row.shop_id || row.platform)
|
||||||
const relItemId = normalizeKuaishouMatchId(
|
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(
|
const itemTitle = String(
|
||||||
ksc.itemTitle || snapshot.goodsName || snapshot.goods_name || row.sku_name || '',
|
ksc.itemTitle || snapshot.goodsName || snapshot.goods_name || row.sku_name || '',
|
||||||
).trim()
|
).trim()
|
||||||
const relSkuId = normalizeKuaishouMatchId(ksc.relSkuId || snapshot.spec || row.sku_code)
|
// 拼多多优先展示/分组 out_sku_sn,与生产匹配字段对齐
|
||||||
const skuNick = String(ksc.skuNick || snapshot.spec || row.sku_name || '').trim()
|
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
|
if (!sellerId || (!relItemId && !itemTitle)) continue
|
||||||
|
|
||||||
@@ -296,30 +306,45 @@ export async function listAdminKuaishouMatchSources(payload: JsonObject = {}) {
|
|||||||
export async function testAdminKuaishouProductMatch(payload: JsonObject = {}) {
|
export async function testAdminKuaishouProductMatch(payload: JsonObject = {}) {
|
||||||
const body = resolveKuaishouMatchPayload(payload.rawPayload ?? payload.raw_payload ?? payload)
|
const body = resolveKuaishouMatchPayload(payload.rawPayload ?? payload.raw_payload ?? payload)
|
||||||
const ext = safeParseJson(body.ext)
|
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 =
|
const isPdd =
|
||||||
body.platform === 'pdd' ||
|
body.platform === 'pdd' ||
|
||||||
|
String(body.provider || '').trim() === 'pdd' ||
|
||||||
String(body.sellerId || '').includes('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 = {
|
const order = {
|
||||||
provider: String(body.provider || (isPdd ? 'pdd' : '91kaquan')).trim(),
|
provider: String(body.provider || (isPdd ? 'pdd' : '91kaquan')).trim(),
|
||||||
platform: String(body.platform || (isPdd ? 'pdd' : 'kuaishou')).trim(),
|
platform: String(body.platform || (isPdd ? 'pdd' : 'kuaishou')).trim(),
|
||||||
shop_id: String(body.sellerId || body.shop_id || '').trim(),
|
shop_id: String(body.sellerId || body.shop_id || '').trim(),
|
||||||
} as OrderRow
|
} as OrderRow
|
||||||
const relItemId = normalizeKuaishouMatchId(ext.relItemId || body.goodsId || body.goods_id)
|
const relItemId = normalizeKuaishouMatchId(ext.relItemId || goodsId)
|
||||||
const relSkuId = normalizeKuaishouMatchId(ext.relSkuId || body.spec)
|
const relSkuId = normalizeKuaishouMatchId(ext.relSkuId || outSkuSn || body.spec)
|
||||||
const itemTitle = String(body.itemTitle || body.goodsName || body.goods_name || '').trim()
|
const itemTitle = String(body.itemTitle || goodsName || '').trim()
|
||||||
const skuNick = String(ext.skuNick || body.skuNick || body.spec || '').trim()
|
const skuNick = String(ext.skuNick || body.skuNick || outSkuSn || body.spec || '').trim()
|
||||||
const item = {
|
const item = {
|
||||||
id: 0,
|
id: 0,
|
||||||
order_id: 0,
|
order_id: 0,
|
||||||
sku_code: String(body.sku_code || body.itemId || relItemId || '').trim(),
|
sku_code: skuCode,
|
||||||
sku_name: String(body.sku_name || skuNick || itemTitle || '').trim(),
|
sku_name: skuName,
|
||||||
quantity: Math.max(1, Number(body.num || body.quantity) || 1),
|
quantity: Math.max(1, Number(body.num || body.quantity || body.goods_number) || 1),
|
||||||
spec_json: {},
|
spec_json: {},
|
||||||
item_snapshot_json: {
|
item_snapshot_json: {
|
||||||
goodsId: body.goodsId || body.goods_id,
|
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,
|
spec: body.spec,
|
||||||
|
out_sku_sn: outSkuSn,
|
||||||
|
outSkuSn,
|
||||||
|
externalSkuCode: skuCode,
|
||||||
|
externalSkuName: skuName,
|
||||||
kuaishouSendCode: {
|
kuaishouSendCode: {
|
||||||
sellerId: String(body.sellerId || order.shop_id || '').trim(),
|
sellerId: String(body.sellerId || order.shop_id || '').trim(),
|
||||||
itemId: String(body.itemId || '').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 snapshot = safeParseJson(item.item_snapshot_json)
|
||||||
const source = safeParseJson(snapshot.kuaishouSendCode)
|
const source = safeParseJson(snapshot.kuaishouSendCode)
|
||||||
|
const outSkuSn = String(snapshot.out_sku_sn || snapshot.outSkuSn || '').trim()
|
||||||
const context = {
|
const context = {
|
||||||
sellerId: normalizeMatchId(source.sellerId),
|
sellerId: normalizeMatchId(source.sellerId),
|
||||||
itemId: normalizeMatchId(source.itemId),
|
itemId: normalizeMatchId(source.itemId),
|
||||||
relItemId: normalizeMatchId(source.relItemId),
|
relItemId: normalizeMatchId(source.relItemId || snapshot.goodsId || snapshot.goods_id),
|
||||||
skuId: normalizeMatchId(source.skuId),
|
skuId: normalizeMatchId(source.skuId),
|
||||||
relSkuId: normalizeMatchId(source.relSkuId),
|
relSkuId: normalizeMatchId(source.relSkuId || outSkuSn || snapshot.spec),
|
||||||
itemTitle: normalizeMatchText(source.itemTitle),
|
itemTitle: normalizeMatchText(
|
||||||
skuNick: normalizeMatchText(source.skuNick || source.skuName || snapshot.externalSkuName),
|
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)
|
const ruleShopId = normalizeMatchId(rule.shop_id)
|
||||||
if (
|
if (
|
||||||
@@ -292,7 +298,6 @@ function scoreWorkProductRule(order: OrderRow, item: OrderItemRow, rule: WorkPro
|
|||||||
return { rule, score }
|
return { rule, score }
|
||||||
}
|
}
|
||||||
|
|
||||||
const outSkuSn = String(snapshot.out_sku_sn || snapshot.outSkuSn || '').trim()
|
|
||||||
const skuCodes = [item.sku_code, snapshot.externalSkuCode, outSkuSn, snapshot.externalItemId]
|
const skuCodes = [item.sku_code, snapshot.externalSkuCode, outSkuSn, snapshot.externalItemId]
|
||||||
.map(normalizeMatchId)
|
.map(normalizeMatchId)
|
||||||
.filter(Boolean)
|
.filter(Boolean)
|
||||||
|
|||||||
@@ -602,6 +602,58 @@ test('拼多多订单使用 out_sku_sn 字段精准匹配接单规则', () => {
|
|||||||
assert.equal(decision.rule?.id, rule.id)
|
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', () => {
|
test('assertWorkerLoginAllowed blocks rejected (frozen) workers with worker_rejected', () => {
|
||||||
assert.throws(
|
assert.throws(
|
||||||
() => assertWorkerLoginAllowed(buildWorkerUserRow({ status: 'rejected' })),
|
() => 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)
|
||||||
|
}
|
||||||
@@ -169,7 +169,7 @@ export default function ProductMatchPanel() {
|
|||||||
|
|
||||||
async function runMatchTest() {
|
async function runMatchTest() {
|
||||||
if (!testPayload.trim()) {
|
if (!testPayload.trim()) {
|
||||||
message.warning('请选择或粘贴快手原始载荷')
|
message.warning('请选择或粘贴原始订单载荷')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -194,7 +194,7 @@ export default function ProductMatchPanel() {
|
|||||||
|
|
||||||
const sourceColumns: TableColumnsType<KuaishouMatchSource> = [
|
const sourceColumns: TableColumnsType<KuaishouMatchSource> = [
|
||||||
{
|
{
|
||||||
title: '快手商品 / SKU',
|
title: '商品 / SKU',
|
||||||
key: 'source',
|
key: 'source',
|
||||||
render: (_, row) => (
|
render: (_, row) => (
|
||||||
<div className="cell-stack">
|
<div className="cell-stack">
|
||||||
@@ -252,17 +252,30 @@ export default function ProductMatchPanel() {
|
|||||||
{
|
{
|
||||||
title: '店铺 / 商品 / SKU',
|
title: '店铺 / 商品 / SKU',
|
||||||
key: 'target',
|
key: 'target',
|
||||||
render: (_, row) => (
|
render: (_, row) => {
|
||||||
<div className="cell-stack">
|
const payload = (row.rawPayload || {}) as Record<string, unknown>
|
||||||
<Typography.Text ellipsis={{ tooltip: row.itemTitle }}>
|
const outSkuSn = String(payload.out_sku_sn || payload.outSkuSn || '').trim()
|
||||||
{row.itemTitle || '-'}
|
const skuLabel = outSkuSn || row.skuNick || row.relSkuId || ''
|
||||||
</Typography.Text>
|
return (
|
||||||
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
<div className="cell-stack">
|
||||||
{formatShopLabel(row.sellerId, shopNameBySellerId)} ·{' '}
|
<Typography.Text ellipsis={{ tooltip: row.itemTitle }}>
|
||||||
{row.skuNick || row.relSkuId || '商品默认'}
|
{row.itemTitle || '-'}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
<Typography.Text type="secondary" style={{ fontSize: 12 }}>
|
||||||
),
|
{formatShopLabel(row.sellerId, shopNameBySellerId)} ·{' '}
|
||||||
|
{skuLabel ? (
|
||||||
|
<Typography.Text type={outSkuSn ? undefined : 'secondary'} style={{ fontSize: 12 }}>
|
||||||
|
{outSkuSn ? `编码 ${skuLabel}` : skuLabel}
|
||||||
|
</Typography.Text>
|
||||||
|
) : (
|
||||||
|
<Typography.Text type="danger" style={{ fontSize: 12 }}>
|
||||||
|
无 out_sku_sn
|
||||||
|
</Typography.Text>
|
||||||
|
)}
|
||||||
|
</Typography.Text>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '结果',
|
title: '结果',
|
||||||
@@ -345,7 +358,7 @@ export default function ProductMatchPanel() {
|
|||||||
showSearch
|
showSearch
|
||||||
optionFilterProp="label"
|
optionFilterProp="label"
|
||||||
style={{ minWidth: 360 }}
|
style={{ minWidth: 360 }}
|
||||||
placeholder="选择已接收的快手原始载荷"
|
placeholder="选择已接收的原始载荷"
|
||||||
options={sources.map((source, index) => ({
|
options={sources.map((source, index) => ({
|
||||||
value: index,
|
value: index,
|
||||||
label: `${formatShopLabel(source.sellerId, shopNameBySellerId)} · ${source.itemTitle} · ${source.skuNick || '商品默认'}`,
|
label: `${formatShopLabel(source.sellerId, shopNameBySellerId)} · ${source.itemTitle} · ${source.skuNick || '商品默认'}`,
|
||||||
@@ -363,7 +376,7 @@ export default function ProductMatchPanel() {
|
|||||||
value={testPayload}
|
value={testPayload}
|
||||||
onChange={(event) => setTestPayload(event.target.value)}
|
onChange={(event) => setTestPayload(event.target.value)}
|
||||||
rows={16}
|
rows={16}
|
||||||
placeholder="粘贴快手 send-code 原始载荷"
|
placeholder="粘贴快手 send-code 或拼多多订单原始载荷(含 out_sku_sn)"
|
||||||
/>
|
/>
|
||||||
{testResult ? (
|
{testResult ? (
|
||||||
<div className="page-stack">
|
<div className="page-stack">
|
||||||
@@ -380,7 +393,10 @@ export default function ProductMatchPanel() {
|
|||||||
: '-'}
|
: '-'}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
<Descriptions.Item label="具体 SKU">
|
<Descriptions.Item label="具体 SKU">
|
||||||
{testResult.context.skuNick || '-'}
|
{testResult.context.skuNick || testResult.context.relSkuId || '-'}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label="商品 ID">
|
||||||
|
{testResult.context.relItemId || '-'}
|
||||||
</Descriptions.Item>
|
</Descriptions.Item>
|
||||||
</Descriptions>
|
</Descriptions>
|
||||||
<JsonPreview
|
<JsonPreview
|
||||||
|
|||||||
Reference in New Issue
Block a user