优化快手发码接单数据
This commit is contained in:
@@ -1054,7 +1054,7 @@ export async function getAdminWorkerPlatformSummary() {
|
|||||||
export async function syncWorkerOrdersForSourceOrder(
|
export async function syncWorkerOrdersForSourceOrder(
|
||||||
order: OrderRow,
|
order: OrderRow,
|
||||||
orderItems: OrderItemRow[],
|
orderItems: OrderItemRow[],
|
||||||
options: { source?: string; autoOnly?: boolean } = {},
|
options: { source?: string; autoOnly?: boolean; sourceMetadata?: JsonObject } = {},
|
||||||
) {
|
) {
|
||||||
const rules = await listWorkProductRules({ enabled: true })
|
const rules = await listWorkProductRules({ enabled: true })
|
||||||
const created: WorkOrderRow[] = []
|
const created: WorkOrderRow[] = []
|
||||||
@@ -1109,10 +1109,14 @@ export async function syncWorkerOrdersForSourceOrder(
|
|||||||
provider: order.provider,
|
provider: order.provider,
|
||||||
platform: order.platform,
|
platform: order.platform,
|
||||||
shopId: order.shop_id,
|
shopId: order.shop_id,
|
||||||
|
shopName: order.shop_name,
|
||||||
skuCode: item.sku_code,
|
skuCode: item.sku_code,
|
||||||
skuName: item.sku_name,
|
skuName: item.sku_name,
|
||||||
quantity: Number(item.quantity || 1),
|
quantity: Number(item.quantity || 1),
|
||||||
|
orderAmountFen: Number(order.total_amount || 0),
|
||||||
|
currency: order.currency || 'CNY',
|
||||||
syncSource: options.source || 'source_order',
|
syncSource: options.source || 'source_order',
|
||||||
|
...options.sourceMetadata,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
requirementJson: JSON.stringify({ fields }),
|
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 { syncWorkerOrdersForSourceOrder } from './admin-service.js'
|
||||||
import { createWorkOrderEvent } from '../../repositories/worker-platform/work-order-repo.js'
|
import { createWorkOrderEvent } from '../../repositories/worker-platform/work-order-repo.js'
|
||||||
import { OPEN_91_PLATFORM, OPEN_91_PROVIDER } from '../open-91/config.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 { asJsonObject, type JsonObject } from '../../types/json.js'
|
||||||
import type { OrderUpdateInput } from '../../types/repository/inputs.js'
|
import type { OrderUpdateInput } from '../../types/repository/inputs.js'
|
||||||
import type {
|
import type {
|
||||||
KuaishouIndustryVoucherRow,
|
KuaishouIndustryVoucherRow,
|
||||||
|
OrderItemRow,
|
||||||
OrderRow,
|
OrderRow,
|
||||||
} from '../../types/repository/rows.js'
|
} from '../../types/repository/rows.js'
|
||||||
|
|
||||||
@@ -59,11 +64,21 @@ export type SyncWorkOrdersFromSendCodeResult = {
|
|||||||
skipped: string[]
|
skipped: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type KuaishouSendCodeWorkOrderData = {
|
||||||
|
sellerId: string
|
||||||
|
shopName: string
|
||||||
|
itemId: string
|
||||||
|
skuId: string
|
||||||
|
skuName: string
|
||||||
|
paymentFen: number
|
||||||
|
}
|
||||||
|
|
||||||
export async function syncWorkOrdersFromKuaishouSendCode(
|
export async function syncWorkOrdersFromKuaishouSendCode(
|
||||||
input: SyncWorkOrdersFromSendCodeInput,
|
input: SyncWorkOrdersFromSendCodeInput,
|
||||||
): Promise<SyncWorkOrdersFromSendCodeResult> {
|
): Promise<SyncWorkOrdersFromSendCodeResult> {
|
||||||
const normalizedOid = String(input.oid || '').trim()
|
const normalizedOid = String(input.oid || '').trim()
|
||||||
const skipped: string[] = []
|
const skipped: string[] = []
|
||||||
|
const sourceData = resolveKuaishouSendCodeWorkOrderData(input)
|
||||||
|
|
||||||
if (!normalizedOid) {
|
if (!normalizedOid) {
|
||||||
return {
|
return {
|
||||||
@@ -83,13 +98,13 @@ export async function syncWorkOrdersFromKuaishouSendCode(
|
|||||||
})
|
})
|
||||||
|
|
||||||
if (order) {
|
if (order) {
|
||||||
const orderResult = await backfillOrderShopId(order, input)
|
const orderResult = await backfillOrderShop(order, input, sourceData)
|
||||||
order = orderResult.order
|
order = orderResult.order
|
||||||
if (orderResult.skipped) {
|
if (orderResult.skipped) {
|
||||||
skipped.push(orderResult.skipped)
|
skipped.push(orderResult.skipped)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const created = await createFallbackOrder(input)
|
const created = await createFallbackOrder(input, sourceData)
|
||||||
if (created.order) {
|
if (created.order) {
|
||||||
order = created.order
|
order = created.order
|
||||||
} else {
|
} else {
|
||||||
@@ -101,11 +116,17 @@ export async function syncWorkOrdersFromKuaishouSendCode(
|
|||||||
let createdWorkOrderCount = 0
|
let createdWorkOrderCount = 0
|
||||||
|
|
||||||
if (order) {
|
if (order) {
|
||||||
const orderItems = await listOrderItemsByOrderId(order.id)
|
const orderItems = applyKuaishouSendCodeItemData(
|
||||||
|
await listOrderItemsByOrderId(order.id),
|
||||||
|
sourceData,
|
||||||
|
)
|
||||||
|
|
||||||
const syncResult = await syncWorkerOrdersForSourceOrder(order, orderItems, {
|
const syncResult = await syncWorkerOrdersForSourceOrder(order, orderItems, {
|
||||||
source: KUAISHOU_SEND_CODE_SOURCE,
|
source: KUAISHOU_SEND_CODE_SOURCE,
|
||||||
autoOnly: true,
|
autoOnly: true,
|
||||||
|
sourceMetadata: {
|
||||||
|
kuaishouSendCode: sourceData,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
createdWorkOrderCount = syncResult.createdCount
|
createdWorkOrderCount = syncResult.createdCount
|
||||||
for (const item of syncResult.skipped) {
|
for (const item of syncResult.skipped) {
|
||||||
@@ -131,26 +152,35 @@ export async function syncWorkOrdersFromKuaishouSendCode(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function backfillOrderShopId(
|
async function backfillOrderShop(
|
||||||
order: OrderRow,
|
order: OrderRow,
|
||||||
input: SyncWorkOrdersFromSendCodeInput,
|
input: SyncWorkOrdersFromSendCodeInput,
|
||||||
|
sourceData: KuaishouSendCodeWorkOrderData,
|
||||||
): Promise<{ order: OrderRow; skipped?: string }> {
|
): Promise<{ order: OrderRow; skipped?: string }> {
|
||||||
const sellerId = String(input.sellerId || '').trim()
|
const sellerId = sourceData.sellerId
|
||||||
const currentShopId = String(order.shop_id || '').trim()
|
const currentShopId = String(order.shop_id || '').trim()
|
||||||
|
const currentShopName = String(order.shop_name || '').trim()
|
||||||
|
|
||||||
if (!sellerId || currentShopId === sellerId) {
|
if (!sellerId) {
|
||||||
return { order }
|
return { order }
|
||||||
}
|
}
|
||||||
|
|
||||||
const isPlaceholderShopId = !currentShopId || currentShopId === OPEN_91_PROVIDER
|
const isPlaceholderShopId = !currentShopId || currentShopId === OPEN_91_PROVIDER
|
||||||
|
const hasShopIdConflict = !isPlaceholderShopId && currentShopId !== sellerId
|
||||||
|
|
||||||
if (!isPlaceholderShopId) {
|
if (hasShopIdConflict) {
|
||||||
return { order, skipped: `shop_id_conflict:${currentShopId}` }
|
return { order, skipped: `shop_id_conflict:${currentShopId}` }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const shouldUpdateShopName = isPlaceholderShopName(currentShopName, sellerId)
|
||||||
|
if (currentShopId === sellerId && !shouldUpdateShopName) {
|
||||||
|
return { order }
|
||||||
|
}
|
||||||
|
|
||||||
const updated = await updateOrder(order.id, {
|
const updated = await updateOrder(order.id, {
|
||||||
...buildOrderUpdateBase(order),
|
...buildOrderUpdateBase(order),
|
||||||
shopId: sellerId,
|
shopId: sellerId,
|
||||||
|
shopName: shouldUpdateShopName ? sourceData.shopName : currentShopName,
|
||||||
updatedAt: input.now,
|
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
|
order: OrderRow | null
|
||||||
reason?: string
|
reason?: string
|
||||||
}> {
|
}> {
|
||||||
const now = input.now
|
const now = input.now
|
||||||
const sellerId = String(input.sellerId || '').trim()
|
const { sellerId, itemId, skuName, paymentFen } = sourceData
|
||||||
const itemId = String(input.itemId || '').trim()
|
|
||||||
const itemTitle = String(input.itemTitle || '').trim()
|
|
||||||
const num = Math.max(1, Number(input.num) || 1)
|
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' }
|
return { order: null, reason: 'order_item_missing' }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +227,7 @@ async function createFallbackOrder(input: SyncWorkOrdersFromSendCodeInput): Prom
|
|||||||
provider: OPEN_91_PROVIDER,
|
provider: OPEN_91_PROVIDER,
|
||||||
platform: OPEN_91_PLATFORM,
|
platform: OPEN_91_PLATFORM,
|
||||||
shopId: sellerId,
|
shopId: sellerId,
|
||||||
shopName: sellerId ? `快手店铺-${sellerId}` : '',
|
shopName: sourceData.shopName,
|
||||||
platformOrderId: input.oid,
|
platformOrderId: input.oid,
|
||||||
orderStatus: KUAISHOU_SEND_CODE_ORDER_STATUS,
|
orderStatus: KUAISHOU_SEND_CODE_ORDER_STATUS,
|
||||||
payStatus: 'paid',
|
payStatus: 'paid',
|
||||||
@@ -223,11 +253,12 @@ async function createFallbackOrder(input: SyncWorkOrdersFromSendCodeInput): Prom
|
|||||||
await replaceOrderItems(order.id, [
|
await replaceOrderItems(order.id, [
|
||||||
{
|
{
|
||||||
skuCode: itemId,
|
skuCode: itemId,
|
||||||
skuName: itemTitle,
|
skuName,
|
||||||
quantity: num,
|
quantity: num,
|
||||||
specJson: JSON.stringify({
|
specJson: JSON.stringify({
|
||||||
sellerId,
|
sellerId,
|
||||||
skuId: String(input.skuId || '').trim(),
|
skuId: sourceData.skuId,
|
||||||
|
skuNick: skuName,
|
||||||
paymentFen,
|
paymentFen,
|
||||||
source: KUAISHOU_SEND_CODE_SOURCE,
|
source: KUAISHOU_SEND_CODE_SOURCE,
|
||||||
}),
|
}),
|
||||||
@@ -243,6 +274,113 @@ async function createFallbackOrder(input: SyncWorkOrdersFromSendCodeInput): Prom
|
|||||||
return { order }
|
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(
|
async function backfillVouchersToWorkOrders(
|
||||||
oid: string,
|
oid: string,
|
||||||
vouchers: KuaishouIndustryVoucherRow[],
|
vouchers: KuaishouIndustryVoucherRow[],
|
||||||
|
|||||||
Reference in New Issue
Block a user