抽离后台履约查询结果组装逻辑

This commit is contained in:
yml
2026-05-04 15:00:31 +08:00
parent 34eb54f906
commit da6668c6ef
3 changed files with 269 additions and 99 deletions
@@ -0,0 +1,128 @@
// @ts-check
import { createHttpError } from '../../utils/http.js'
import { formatFenToAmount } from '../../utils/money.js'
import { isPlainObject, pickFirstNonEmpty } from './admin-platform-config-context.js'
import { mapAdminObservedProductItem } from './admin-platform-config-domain.js'
export function normalizeFulfillmentLookupPayload(payload = {}) {
const provider = String(payload.provider || 'agiso').trim() || 'agiso'
const platform = String(payload.platform || 'xianyu').trim() || 'xianyu'
const shopId = String(payload.shopId || '').trim()
const platformOrderId = String(payload.platformOrderId || '').trim()
if (!shopId) {
throw createHttpError('请先填写店铺 ID', {
statusCode: 400,
errorCode: 'admin_fulfillment_lookup_missing_shop_id',
})
}
if (!platformOrderId) {
throw createHttpError('请先填写平台订单号', {
statusCode: 400,
errorCode: 'admin_fulfillment_lookup_missing_platform_order_id',
})
}
if (provider !== 'agiso' || platform !== 'xianyu') {
throw createHttpError('目前仅支持 Agiso 咸鱼订单手动查询', {
statusCode: 400,
errorCode: 'admin_fulfillment_lookup_platform_not_supported',
})
}
return { provider, platform, shopId, platformOrderId }
}
export function resolveFulfillmentLookupDetail(detailResult, platformOrderId) {
const detail = isPlainObject(detailResult?.parsed) ? detailResult.parsed : {}
const items = Array.isArray(detail.items) ? detail.items : []
if (items.length > 0) {
return { detail, items }
}
const detailReason = String(detailResult?.reason || '').trim()
const detailMessage = String(detailResult?.errorMessage || '').trim()
let message = detailMessage
if (!message && detailReason === 'missing_config') {
message = '当前店铺缺少订单详情查询配置,请先检查 accessToken、appSecret 和详情接口地址'
}
if (!message) {
message = `未查询到订单 ${platformOrderId} 的商品明细`
}
throw createHttpError(message, {
statusCode: 404,
errorCode: 'admin_fulfillment_lookup_order_items_not_found',
})
}
export function buildFulfillmentLookupResult({
provider = '',
platform = '',
shopId = '',
platformOrderId = '',
detail = /** @type {Record<string, unknown>} */ ({}),
detailResult = /** @type {Record<string, unknown>} */ ({}),
bindings = [],
fallbackShopName = '',
} = {}) {
const resolvedShopName = pickFirstNonEmpty([detail.shopName, fallbackShopName])
const items = Array.isArray(detail.items) ? detail.items : []
return {
order: {
provider,
platform,
shopId,
shopName: resolvedShopName,
platformOrderId,
buyerName: String(detail.buyerName || '').trim(),
totalAmountFen: Number(detail.totalAmount || 0),
totalAmount: formatFenToAmount(detail.totalAmount),
paidAt: detail.paidAt || null,
enriched: Boolean(detailResult?.enriched),
enrichReason: String(detailResult?.reason || '').trim(),
errorMessage: String(detailResult?.errorMessage || '').trim(),
},
items: items.map((item, index) => {
const observed = {
provider,
platform,
shopId,
shopName: resolvedShopName,
externalItemId: pickFirstNonEmpty([item?.externalItemId, item?.itemId]),
externalSkuCode: pickFirstNonEmpty([
item?.externalSkuCode,
item?.skuCode,
item?.externalItemId,
item?.itemId,
]),
externalSkuName: pickFirstNonEmpty([item?.externalSkuName, item?.skuName]),
latestSeenAt: null,
orderItemCount: Math.max(1, Number(item?.quantity || 0) || 1),
}
return {
lineId: [
platformOrderId,
index + 1,
observed.externalSkuCode || 'na',
observed.externalItemId || 'na',
].join(':'),
itemTitle: pickFirstNonEmpty([
item?.skuName,
item?.externalSkuName,
item?.externalSkuCode,
item?.externalItemId,
]),
quantity: observed.orderItemCount,
...mapAdminObservedProductItem(observed, bindings),
}
}),
}
}
@@ -0,0 +1,124 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import {
buildFulfillmentLookupResult,
normalizeFulfillmentLookupPayload,
resolveFulfillmentLookupDetail,
} from './admin-platform-config-fulfillment.js'
test('normalizeFulfillmentLookupPayload validates required fields and defaults provider/platform', () => {
assert.deepEqual(
normalizeFulfillmentLookupPayload({
shopId: ' shop-1 ',
platformOrderId: ' order-1 ',
}),
{
provider: 'agiso',
platform: 'xianyu',
shopId: 'shop-1',
platformOrderId: 'order-1',
},
)
assert.throws(
() => normalizeFulfillmentLookupPayload({ platformOrderId: 'order-1' }),
/请先填写店铺 ID/,
)
})
test('resolveFulfillmentLookupDetail maps missing config into readable error', () => {
assert.throws(
() =>
resolveFulfillmentLookupDetail(
{
reason: 'missing_config',
parsed: { items: [] },
},
'order-1',
),
/当前店铺缺少订单详情查询配置/,
)
})
test('buildFulfillmentLookupResult assembles order and item matching payload', () => {
const result = buildFulfillmentLookupResult({
provider: 'agiso',
platform: 'xianyu',
shopId: 'shop-1',
platformOrderId: 'order-1',
detail: {
shopName: '店铺A',
buyerName: '买家A',
totalAmount: 12345,
paidAt: '2026-05-04T10:00:00.000Z',
items: [
{
externalItemId: 'item-1',
externalSkuCode: 'sku-1',
externalSkuName: '礼包A',
skuName: '礼包A',
quantity: 2,
},
],
},
detailResult: {
enriched: true,
reason: 'ok',
errorMessage: '',
},
bindings: [
{
provider: 'agiso',
platform: 'xianyu',
shopId: 'shop-1',
skuCode: 'internal-1',
skuName: '内部商品',
profileKey: 'profile-1',
match: {
externalSkuCode: 'sku-1',
},
},
],
fallbackShopName: '备用店铺',
})
assert.deepEqual(result, {
order: {
provider: 'agiso',
platform: 'xianyu',
shopId: 'shop-1',
shopName: '店铺A',
platformOrderId: 'order-1',
buyerName: '买家A',
totalAmountFen: 12345,
totalAmount: '123.45',
paidAt: '2026-05-04T10:00:00.000Z',
enriched: true,
enrichReason: 'ok',
errorMessage: '',
},
items: [
{
lineId: 'order-1:1:sku-1:item-1',
itemTitle: '礼包A',
quantity: 2,
provider: 'agiso',
platform: 'xianyu',
shopId: 'shop-1',
shopName: '店铺A',
externalItemId: 'item-1',
externalSkuCode: 'sku-1',
externalSkuName: '礼包A',
latestSeenAt: null,
orderItemCount: 2,
configured: true,
matchedBinding: {
skuCode: 'internal-1',
skuName: '内部商品',
profileKey: 'profile-1',
},
},
],
})
})
@@ -75,7 +75,6 @@ import {
} from '../order/kuaishou-cloud-fulfillment-config-service.js'
import { getFulfillmentProfileByKey } from '../../repositories/fulfillment-profile-repo.js'
import { createHttpError } from '../../utils/http.js'
import { formatFenToAmount } from '../../utils/money.js'
import { syncConfiguredFulfillmentBindings } from '../bootstrap/fulfillment-bootstrap-service.js'
import { normalizeProductName } from '../order/product-match-service.js'
import { resolveDisplayShopName } from './admin-read-shared-helpers.js'
@@ -86,6 +85,11 @@ import {
resolveCloudtentaclesAdminContext,
resolveKuaishouEticketAdminContext,
} from './admin-platform-config-context.js'
import {
buildFulfillmentLookupResult,
normalizeFulfillmentLookupPayload,
resolveFulfillmentLookupDetail,
} from './admin-platform-config-fulfillment.js'
import {
applyOptionalStringField,
mapAdminAgisoMessagingDefaults,
@@ -789,31 +793,7 @@ export async function updateAdminKuaishouCloudFulfillmentConfig(
/** @param {AdminFulfillmentBindingLookupInput} [payload] */
export async function lookupAdminFulfillmentBindingOrder(payload = /** @type {AdminFulfillmentBindingLookupInput} */ ({})) {
const provider = String(payload.provider || 'agiso').trim() || 'agiso'
const platform = String(payload.platform || 'xianyu').trim() || 'xianyu'
const shopId = String(payload.shopId || '').trim()
const platformOrderId = String(payload.platformOrderId || '').trim()
if (!shopId) {
throw createHttpError('请先填写店铺 ID', {
statusCode: 400,
errorCode: 'admin_fulfillment_lookup_missing_shop_id',
})
}
if (!platformOrderId) {
throw createHttpError('请先填写平台订单号', {
statusCode: 400,
errorCode: 'admin_fulfillment_lookup_missing_platform_order_id',
})
}
if (provider !== 'agiso' || platform !== 'xianyu') {
throw createHttpError('目前仅支持 Agiso 咸鱼订单手动查询', {
statusCode: 400,
errorCode: 'admin_fulfillment_lookup_platform_not_supported',
})
}
const { provider, platform, shopId, platformOrderId } = normalizeFulfillmentLookupPayload(payload)
const detailResult = await enrichAgisoXianyuTradeOrder({
provider,
@@ -835,80 +815,18 @@ export async function lookupAdminFulfillmentBindingOrder(payload = /** @type {Ad
requestId: `admin-fulfillment-lookup:${shopId}:${platformOrderId}`,
})
const detail = isPlainObject(detailResult?.parsed) ? detailResult.parsed : {}
const items = Array.isArray(detail.items) ? detail.items : []
if (items.length === 0) {
const detailReason = String(detailResult?.reason || '').trim()
const detailMessage = String(detailResult?.errorMessage || '').trim()
let message = detailMessage
if (!message && detailReason === 'missing_config') {
message = '当前店铺缺少订单详情查询配置,请先检查 accessToken、appSecret 和详情接口地址'
}
if (!message) {
message = `未查询到订单 ${platformOrderId} 的商品明细`
}
throw createHttpError(message, {
statusCode: 404,
errorCode: 'admin_fulfillment_lookup_order_items_not_found',
})
}
const { detail } = resolveFulfillmentLookupDetail(detailResult, platformOrderId)
const bindings = getOrderFulfillmentBindingConfigs()
const resolvedShopName = pickFirstNonEmpty([
detail.shopName,
getAgisoShopConfig(shopId)?.shopName,
])
return {
order: {
provider,
platform,
shopId,
shopName: resolvedShopName,
platformOrderId,
buyerName: String(detail.buyerName || '').trim(),
totalAmountFen: Number(detail.totalAmount || 0),
totalAmount: formatFenToAmount(detail.totalAmount),
paidAt: detail.paidAt || null,
enriched: Boolean(detailResult?.enriched),
enrichReason: String(detailResult?.reason || '').trim(),
errorMessage: String(detailResult?.errorMessage || '').trim(),
},
items: items.map((item, index) => {
const observed = {
provider,
platform,
shopId,
shopName: resolvedShopName,
externalItemId: pickFirstNonEmpty([item?.externalItemId, item?.itemId]),
externalSkuCode: pickFirstNonEmpty([item?.externalSkuCode, item?.skuCode, item?.externalItemId, item?.itemId]),
externalSkuName: pickFirstNonEmpty([item?.externalSkuName, item?.skuName]),
latestSeenAt: null,
orderItemCount: Math.max(1, Number(item?.quantity || 0) || 1),
}
return {
lineId: [
platformOrderId,
index + 1,
observed.externalSkuCode || 'na',
observed.externalItemId || 'na',
].join(':'),
itemTitle: pickFirstNonEmpty([
item?.skuName,
item?.externalSkuName,
item?.externalSkuCode,
item?.externalItemId,
]),
quantity: observed.orderItemCount,
...mapAdminObservedProductItem(observed, bindings),
}
}),
}
return buildFulfillmentLookupResult({
provider,
platform,
shopId,
platformOrderId,
detail,
detailResult,
bindings,
fallbackShopName: getAgisoShopConfig(shopId)?.shopName,
})
}
/** @param {AdminFulfillmentBindingConfigSaveInput} [payload] */