抽离后台履约查询结果组装逻辑
This commit is contained in:
@@ -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] */
|
||||
|
||||
Reference in New Issue
Block a user