优化快手发码接单数据

This commit is contained in:
yml2213
2026-08-11 14:30:19 +08:00
parent e097ddad3d
commit 40be8103fc
3 changed files with 202 additions and 17 deletions
@@ -1054,7 +1054,7 @@ export async function getAdminWorkerPlatformSummary() {
export async function syncWorkerOrdersForSourceOrder(
order: OrderRow,
orderItems: OrderItemRow[],
options: { source?: string; autoOnly?: boolean } = {},
options: { source?: string; autoOnly?: boolean; sourceMetadata?: JsonObject } = {},
) {
const rules = await listWorkProductRules({ enabled: true })
const created: WorkOrderRow[] = []
@@ -1109,10 +1109,14 @@ export async function syncWorkerOrdersForSourceOrder(
provider: order.provider,
platform: order.platform,
shopId: order.shop_id,
shopName: order.shop_name,
skuCode: item.sku_code,
skuName: item.sku_name,
quantity: Number(item.quantity || 1),
orderAmountFen: Number(order.total_amount || 0),
currency: order.currency || 'CNY',
syncSource: options.source || 'source_order',
...options.sourceMetadata,
},
}),
requirementJson: JSON.stringify({ fields }),
@@ -0,0 +1,43 @@
import assert from 'node:assert/strict'
import test from 'node:test'
import {
resolveKuaishouSendCodeShopName,
resolveKuaishouSendCodeWorkOrderData,
} from './sync-work-orders-from-send-code.js'
test('快手发码工单优先使用 ext.skuNick 与 payment', () => {
const result = resolveKuaishouSendCodeWorkOrderData(
{
sellerId: '141242642',
itemId: '25873410102642',
itemTitle: '和平精英指挥官密钥秘钥隐藏款密钥暮色之下灰烬之翎珍宝礼盒礼卡',
skuId: '185469355612642',
paymentFen: 1400,
rawParams: {
ext: '{"skuNick":"指挥官密钥1个","payment":1400}',
},
},
[
{
sellerId: '141242642',
shopId: '141242642',
shopName: '授权店铺名',
customShopName: '快手密钥店',
},
],
)
assert.deepEqual(result, {
sellerId: '141242642',
shopName: '快手密钥店',
itemId: '25873410102642',
skuId: '185469355612642',
skuName: '指挥官密钥1个',
paymentFen: 1400,
})
})
test('未配置授权店铺名时保留 sellerId 兜底名称', () => {
assert.equal(resolveKuaishouSendCodeShopName('141242642'), '快手店铺-141242642')
})
@@ -27,10 +27,15 @@ import {
import { syncWorkerOrdersForSourceOrder } from './admin-service.js'
import { createWorkOrderEvent } from '../../repositories/worker-platform/work-order-repo.js'
import { OPEN_91_PLATFORM, OPEN_91_PROVIDER } from '../open-91/config.js'
import {
findKuaishouIndustryShopConfig,
type KuaishouIndustryShopConfig,
} from '../platforms/kuaishou-industry/source-config-service.js'
import { asJsonObject, type JsonObject } from '../../types/json.js'
import type { OrderUpdateInput } from '../../types/repository/inputs.js'
import type {
KuaishouIndustryVoucherRow,
OrderItemRow,
OrderRow,
} from '../../types/repository/rows.js'
@@ -59,11 +64,21 @@ export type SyncWorkOrdersFromSendCodeResult = {
skipped: string[]
}
export type KuaishouSendCodeWorkOrderData = {
sellerId: string
shopName: string
itemId: string
skuId: string
skuName: string
paymentFen: number
}
export async function syncWorkOrdersFromKuaishouSendCode(
input: SyncWorkOrdersFromSendCodeInput,
): Promise<SyncWorkOrdersFromSendCodeResult> {
const normalizedOid = String(input.oid || '').trim()
const skipped: string[] = []
const sourceData = resolveKuaishouSendCodeWorkOrderData(input)
if (!normalizedOid) {
return {
@@ -83,13 +98,13 @@ export async function syncWorkOrdersFromKuaishouSendCode(
})
if (order) {
const orderResult = await backfillOrderShopId(order, input)
const orderResult = await backfillOrderShop(order, input, sourceData)
order = orderResult.order
if (orderResult.skipped) {
skipped.push(orderResult.skipped)
}
} else {
const created = await createFallbackOrder(input)
const created = await createFallbackOrder(input, sourceData)
if (created.order) {
order = created.order
} else {
@@ -101,11 +116,17 @@ export async function syncWorkOrdersFromKuaishouSendCode(
let createdWorkOrderCount = 0
if (order) {
const orderItems = await listOrderItemsByOrderId(order.id)
const orderItems = applyKuaishouSendCodeItemData(
await listOrderItemsByOrderId(order.id),
sourceData,
)
const syncResult = await syncWorkerOrdersForSourceOrder(order, orderItems, {
source: KUAISHOU_SEND_CODE_SOURCE,
autoOnly: true,
sourceMetadata: {
kuaishouSendCode: sourceData,
},
})
createdWorkOrderCount = syncResult.createdCount
for (const item of syncResult.skipped) {
@@ -131,26 +152,35 @@ export async function syncWorkOrdersFromKuaishouSendCode(
}
}
async function backfillOrderShopId(
async function backfillOrderShop(
order: OrderRow,
input: SyncWorkOrdersFromSendCodeInput,
sourceData: KuaishouSendCodeWorkOrderData,
): Promise<{ order: OrderRow; skipped?: string }> {
const sellerId = String(input.sellerId || '').trim()
const sellerId = sourceData.sellerId
const currentShopId = String(order.shop_id || '').trim()
const currentShopName = String(order.shop_name || '').trim()
if (!sellerId || currentShopId === sellerId) {
if (!sellerId) {
return { order }
}
const isPlaceholderShopId = !currentShopId || currentShopId === OPEN_91_PROVIDER
const hasShopIdConflict = !isPlaceholderShopId && currentShopId !== sellerId
if (!isPlaceholderShopId) {
if (hasShopIdConflict) {
return { order, skipped: `shop_id_conflict:${currentShopId}` }
}
const shouldUpdateShopName = isPlaceholderShopName(currentShopName, sellerId)
if (currentShopId === sellerId && !shouldUpdateShopName) {
return { order }
}
const updated = await updateOrder(order.id, {
...buildOrderUpdateBase(order),
shopId: sellerId,
shopName: shouldUpdateShopName ? sourceData.shopName : currentShopName,
updatedAt: input.now,
})
@@ -178,18 +208,18 @@ function buildOrderUpdateBase(order: OrderRow): Omit<OrderUpdateInput, 'updatedA
}
}
async function createFallbackOrder(input: SyncWorkOrdersFromSendCodeInput): Promise<{
async function createFallbackOrder(
input: SyncWorkOrdersFromSendCodeInput,
sourceData: KuaishouSendCodeWorkOrderData,
): Promise<{
order: OrderRow | null
reason?: string
}> {
const now = input.now
const sellerId = String(input.sellerId || '').trim()
const itemId = String(input.itemId || '').trim()
const itemTitle = String(input.itemTitle || '').trim()
const { sellerId, itemId, skuName, paymentFen } = sourceData
const num = Math.max(1, Number(input.num) || 1)
const paymentFen = Math.max(0, Number(input.paymentFen) || 0)
if (!itemId && !itemTitle) {
if (!itemId && !skuName) {
return { order: null, reason: 'order_item_missing' }
}
@@ -197,7 +227,7 @@ async function createFallbackOrder(input: SyncWorkOrdersFromSendCodeInput): Prom
provider: OPEN_91_PROVIDER,
platform: OPEN_91_PLATFORM,
shopId: sellerId,
shopName: sellerId ? `快手店铺-${sellerId}` : '',
shopName: sourceData.shopName,
platformOrderId: input.oid,
orderStatus: KUAISHOU_SEND_CODE_ORDER_STATUS,
payStatus: 'paid',
@@ -223,11 +253,12 @@ async function createFallbackOrder(input: SyncWorkOrdersFromSendCodeInput): Prom
await replaceOrderItems(order.id, [
{
skuCode: itemId,
skuName: itemTitle,
skuName,
quantity: num,
specJson: JSON.stringify({
sellerId,
skuId: String(input.skuId || '').trim(),
skuId: sourceData.skuId,
skuNick: skuName,
paymentFen,
source: KUAISHOU_SEND_CODE_SOURCE,
}),
@@ -243,6 +274,113 @@ async function createFallbackOrder(input: SyncWorkOrdersFromSendCodeInput): Prom
return { order }
}
export function resolveKuaishouSendCodeWorkOrderData(
input: Pick<
SyncWorkOrdersFromSendCodeInput,
'sellerId' | 'itemId' | 'itemTitle' | 'skuId' | 'paymentFen' | 'rawParams'
>,
shops: Array<
Pick<KuaishouIndustryShopConfig, 'sellerId' | 'shopId' | 'customShopName' | 'shopName'>
> | null = null,
): KuaishouSendCodeWorkOrderData {
const sellerId = String(input.sellerId || '').trim()
const ext = safeParseJson(input.rawParams?.ext)
const skuName = String(ext.skuNick || input.itemTitle || '').trim()
const paymentFen =
normalizePaymentFen(input.paymentFen) ||
normalizePaymentFen(ext.payment ?? ext.totalGoodsValue ?? ext.goodsValue)
const matchedShop = shops
? shops.find((shop) => String(shop.sellerId || shop.shopId || '').trim() === sellerId) || null
: findKuaishouIndustryShopConfig(sellerId)
return {
sellerId,
shopName: resolveKuaishouSendCodeShopName(sellerId, matchedShop),
itemId: String(input.itemId || '').trim(),
skuId: String(input.skuId || '').trim(),
skuName,
paymentFen,
}
}
function normalizePaymentFen(value: unknown): number {
const amount = Number(value)
return Number.isFinite(amount) && amount > 0 ? Math.trunc(amount) : 0
}
export function resolveKuaishouSendCodeShopName(
sellerId: unknown,
shop: Pick<KuaishouIndustryShopConfig, 'customShopName' | 'shopName'> | null = null,
): string {
const normalizedSellerId = String(sellerId || '').trim()
return (
String(shop?.customShopName || shop?.shopName || '').trim() ||
(normalizedSellerId ? `快手店铺-${normalizedSellerId}` : '')
)
}
function applyKuaishouSendCodeItemData(
orderItems: OrderItemRow[],
sourceData: KuaishouSendCodeWorkOrderData,
): OrderItemRow[] {
if (!sourceData.skuName) {
return orderItems
}
const matchedIds = findKuaishouSendCodeItemIds(orderItems, sourceData)
if (matchedIds.size === 0) {
return orderItems
}
return orderItems.map((item) => {
if (!matchedIds.has(Number(item.id))) {
return item
}
const snapshot = safeParseJson(item.item_snapshot_json)
return {
...item,
sku_name: sourceData.skuName,
item_snapshot_json: {
...snapshot,
externalSkuCode: sourceData.skuId || snapshot.externalSkuCode || '',
externalItemId: sourceData.itemId || snapshot.externalItemId || '',
externalSkuName: sourceData.skuName,
externalSkuNameNormalized: sourceData.skuName,
kuaishouSendCode: sourceData,
},
}
})
}
function findKuaishouSendCodeItemIds(
orderItems: OrderItemRow[],
sourceData: KuaishouSendCodeWorkOrderData,
): Set<number> {
const identifiers = new Set([sourceData.itemId, sourceData.skuId].filter(Boolean))
const matched = orderItems.filter((item) => {
const snapshot = safeParseJson(item.item_snapshot_json)
return [
item.sku_code,
snapshot.externalSkuCode,
snapshot.externalItemId,
snapshot.skuId,
snapshot.itemId,
].some((value) => identifiers.has(String(value || '').trim()))
})
if (matched.length > 0) {
return new Set(matched.map((item) => Number(item.id)))
}
return orderItems.length === 1 ? new Set([Number(orderItems[0]?.id || 0)]) : new Set()
}
function isPlaceholderShopName(shopName: string, sellerId: string): boolean {
const normalized = String(shopName || '').trim()
return !normalized || normalized === '91卡券' || normalized === `快手店铺-${sellerId}`
}
async function backfillVouchersToWorkOrders(
oid: string,
vouchers: KuaishouIndustryVoucherRow[],