From e7b51f09a67b869110f6aaaa7bcc4638c9d9c1fd Mon Sep 17 00:00:00 2001 From: yml2213 Date: Sat, 12 Sep 2026 15:42:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(worker-platform):=20=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=8B=BC=E5=A4=9A=E5=A4=9A=E8=AE=A2=E5=8D=95=E5=8C=B9=E9=85=8D?= =?UTF-8?q?=E8=AF=8A=E6=96=AD=E4=B8=8E=E9=80=9A=E7=94=A8=E5=8F=91=E5=8D=95?= =?UTF-8?q?=E8=A7=84=E5=88=99=E5=8C=B9=E9=85=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. 修复匹配诊断日志仅记录快手发单凭证的问题,支持记录拼多多等全平台订单的商品匹配结果; 2. 匹配诊断来源商品聚合查询 order_items 与 orders,拼多多商品正常展示并在测试中可用; 3. 接单模板规则平台默认值改为留空,支持跨平台通用匹配; 4. 接单模板重匹配功能支持重扫数据库中未建工单的订单并自动生成接单工单。 --- .../admin-kuaishou-match-service.ts | 112 ++++++++++++++++-- .../sync-work-orders-from-send-code.ts | 33 +++++- .../sync-worker-orders-service.ts | 32 +++-- .../pages/admin/panels/ProductRulesPanel.tsx | 8 +- 4 files changed, 161 insertions(+), 24 deletions(-) diff --git a/apps/backend/src/services/worker-platform/admin-kuaishou-match-service.ts b/apps/backend/src/services/worker-platform/admin-kuaishou-match-service.ts index 5294c2e2..0baae78e 100644 --- a/apps/backend/src/services/worker-platform/admin-kuaishou-match-service.ts +++ b/apps/backend/src/services/worker-platform/admin-kuaishou-match-service.ts @@ -1,3 +1,4 @@ +import { query } from '../../db/client.js' import { listKuaishouIndustryVoucherWorkOrderSyncSources } from '../../repositories/kuaishou-industry-voucher-repo.js' import { deleteWorkProductRuleMapping, @@ -183,7 +184,38 @@ export async function deleteAdminWorkProductRuleMapping(mappingId: number | stri export async function listAdminKuaishouMatchSources(payload: JsonObject = {}) { const limit = Math.min(500, normalizePositiveInteger(payload.limit, 100)) - const sources = await listKuaishouIndustryVoucherWorkOrderSyncSources(limit) + const [sources, recentOrderItemsResult] = await Promise.all([ + listKuaishouIndustryVoucherWorkOrderSyncSources(limit), + query<{ + platform: string + shop_id: string + platform_order_id: string + sku_code: string + sku_name: string + item_snapshot_json: unknown + order_raw_payload: unknown + created_at: string + updated_at: string + }>( + ` + SELECT + o.platform, + o.shop_id, + o.platform_order_id, + o.raw_payload_json AS order_raw_payload, + oi.sku_code, + oi.sku_name, + oi.item_snapshot_json, + oi.created_at, + oi.updated_at + FROM order_items oi + JOIN orders o ON o.id = oi.order_id + ORDER BY oi.id DESC + LIMIT $1 + `, + [limit], + ), + ]) const grouped = new Map() for (const source of sources) { const rawPayload = safeParseJson(source.raw_payload_json) @@ -215,6 +247,51 @@ export async function listAdminKuaishouMatchSources(payload: JsonObject = {}) { rawPayload, }) } + + for (const row of recentOrderItemsResult.rows) { + const snapshot = safeParseJson(row.item_snapshot_json) + const ksc = safeParseJson(snapshot.kuaishouSendCode) + const sellerId = normalizeKuaishouMatchId( + ksc.sellerId || row.shop_id || row.platform, + ) + const relItemId = normalizeKuaishouMatchId( + ksc.relItemId || row.sku_code || snapshot.goodsId || snapshot.goods_id, + ) + 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() + + if (!sellerId || (!relItemId && !itemTitle)) continue + + const key = [ + sellerId, + relItemId, + normalizeMatchTextForAdmin(itemTitle), + relSkuId, + normalizeMatchTextForAdmin(skuNick), + ].join('|') + const current = grouped.get(key) + const rawPayload = + Object.keys(snapshot).length > 0 ? snapshot : safeParseJson(row.order_raw_payload) + grouped.set(key, { + sellerId, + relItemId, + itemTitle, + relSkuId, + skuNick, + sampleOid: String(row.platform_order_id || '').trim(), + lastSeenAt: row.updated_at || row.created_at, + seenCount: Number(current?.seenCount || 0) + 1, + rawPayload, + }) + } + return { items: Array.from(grouped.values()).sort((left, right) => String(right.lastSeenAt).localeCompare(String(left.lastSeenAt)), @@ -225,27 +302,38 @@ 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 isPdd = + body.platform === 'pdd' || + String(body.sellerId || '').includes('pdd') || + Boolean(body.goodsName || body.goods_name || body.goodsId || body.goods_id) const order = { - provider: '91kaquan', - platform: 'kuaishou', - shop_id: String(body.sellerId || '').trim(), + 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 item = { id: 0, order_id: 0, - sku_code: String(body.itemId || '').trim(), - sku_name: String(ext.skuNick || '').trim(), - quantity: Math.max(1, Number(body.num) || 1), + 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), spec_json: {}, item_snapshot_json: { + goodsId: body.goodsId || body.goods_id, + goodsName: body.goodsName || body.goods_name, + spec: body.spec, kuaishouSendCode: { - sellerId: String(body.sellerId || '').trim(), + sellerId: String(body.sellerId || order.shop_id || '').trim(), itemId: String(body.itemId || '').trim(), - itemTitle: String(body.itemTitle || '').trim(), + itemTitle, skuId: String(body.skuId || '').trim(), - relItemId: normalizeKuaishouMatchId(ext.relItemId), - relSkuId: normalizeKuaishouMatchId(ext.relSkuId), - skuNick: String(ext.skuNick || '').trim(), + relItemId, + relSkuId, + skuNick, }, }, } as OrderItemRow diff --git a/apps/backend/src/services/worker-platform/sync-work-orders-from-send-code.ts b/apps/backend/src/services/worker-platform/sync-work-orders-from-send-code.ts index cf0a6303..bfcb10c7 100644 --- a/apps/backend/src/services/worker-platform/sync-work-orders-from-send-code.ts +++ b/apps/backend/src/services/worker-platform/sync-work-orders-from-send-code.ts @@ -1,3 +1,4 @@ +import { query } from '../../db/client.js' /** * 快手 send-code 数据接入发单平台(工单侧)。 * @@ -202,7 +203,37 @@ export async function reprocessKuaishouSendCodeWorkOrders(limit = 100) { skippedCount += result.skipped.length } - return { scannedCount: sources.length, createdCount, skippedCount } + // Also rescan general platform orders (e.g. PDD) that do not have work orders yet + const unfulfilledOrders = await query( + ` + SELECT o.* + FROM orders o + WHERE o.order_status IN ('paid', 'shipped') + AND NOT EXISTS ( + SELECT 1 FROM work_orders wo WHERE wo.order_id = o.id + ) + ORDER BY o.id DESC + LIMIT $1 + `, + [limit], + ) + + for (const order of unfulfilledOrders.rows) { + const items = await listOrderItemsByOrderId(order.id) + if (items.length === 0) continue + const syncResult = await syncWorkerOrdersForSourceOrder(order, items, { + source: 'reprocess_matches', + autoOnly: true, + }) + createdCount += syncResult.createdCount + skippedCount += syncResult.skippedCount + } + + return { + scannedCount: sources.length + unfulfilledOrders.rows.length, + createdCount, + skippedCount, + } } async function backfillOrderShop( diff --git a/apps/backend/src/services/worker-platform/sync-worker-orders-service.ts b/apps/backend/src/services/worker-platform/sync-worker-orders-service.ts index a69628fa..e2bd8f2a 100644 --- a/apps/backend/src/services/worker-platform/sync-worker-orders-service.ts +++ b/apps/backend/src/services/worker-platform/sync-worker-orders-service.ts @@ -52,17 +52,34 @@ export async function syncWorkerOrdersForSourceOrder( legacyFallbackEnabled: productMatchConfig.legacyFallbackEnabled, }) const matchContext = resolveKuaishouWorkProductMatchContext(item) - if (matchContext.sellerId) { - const snapshot = safeParseJson(item.item_snapshot_json) + const snapshot = safeParseJson(item.item_snapshot_json) + const sellerId = + matchContext.sellerId || + String(order.shop_id || order.provider || order.platform || '').trim() + const relItemId = + matchContext.relItemId || + String(item.sku_code || snapshot.goodsId || snapshot.goods_id || '').trim() + const itemTitle = + matchContext.itemTitle || + String(snapshot.goodsName || snapshot.goods_name || item.sku_name || '').trim() + const relSkuId = + matchContext.relSkuId || + String(snapshot.spec || item.sku_code || '').trim() + const skuNick = + matchContext.skuNick || + String(snapshot.spec || item.sku_name || '').trim() + + // Record product match diagnostics for all platform orders (including PDD) + if (sellerId || relItemId || itemTitle) { await createWorkProductMatchLog({ orderId: Number(order.id), orderItemId: Number(item.id), source: options.source || 'source_order', - sellerId: matchContext.sellerId, - relItemId: matchContext.relItemId, - itemTitle: matchContext.itemTitle, - relSkuId: matchContext.relSkuId, - skuNick: matchContext.skuNick, + sellerId, + relItemId, + itemTitle, + relSkuId, + skuNick, matchStatus: match.reason, ruleId: match.rule?.id || null, mappingId: match.mappingId, @@ -71,6 +88,7 @@ export async function syncWorkerOrdersForSourceOrder( options.sourceMetadata?.kuaishouSendCodeRawPayload || snapshot.kuaishouSendCodeRawPayload || snapshot.kuaishouSendCode || + snapshot || {}, ), now: nowIso(), diff --git a/apps/frontend/src/pages/admin/panels/ProductRulesPanel.tsx b/apps/frontend/src/pages/admin/panels/ProductRulesPanel.tsx index 4f0f0477..e40b641b 100644 --- a/apps/frontend/src/pages/admin/panels/ProductRulesPanel.tsx +++ b/apps/frontend/src/pages/admin/panels/ProductRulesPanel.tsx @@ -424,7 +424,7 @@ export default function ProductRulesPanel() { const response = await reprocessAdminKuaishouWorkOrderMatches() const result = response.data message.success( - `已扫描 ${result.scannedCount} 笔快手订单,新增 ${result.createdCount} 个接单工单`, + `已扫描 ${result.scannedCount} 笔订单,新增 ${result.createdCount} 个接单工单`, ) await Promise.all([ queryClient.invalidateQueries({ queryKey: ['admin-worker-platform-product-rules'] }), @@ -432,7 +432,7 @@ export default function ProductRulesPanel() { queryClient.invalidateQueries({ queryKey: ['admin-worker-platform-orders'] }), ]) } catch (error) { - message.error(error instanceof Error ? error.message : '快手订单重匹配失败') + message.error(error instanceof Error ? error.message : '订单重匹配失败') } } @@ -744,7 +744,7 @@ export default function ProductRulesPanel() { layout="vertical" className="product-rule-main-form" initialValues={{ - platform: 'kuaishou', + platform: '', matchType: 'contains', itemTitleMatchType: 'exact', skuNickMatchType: 'exact', @@ -914,7 +914,7 @@ export default function ProductRulesPanel() { - +