抽离后台履约查询结果组装逻辑
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'
|
} from '../order/kuaishou-cloud-fulfillment-config-service.js'
|
||||||
import { getFulfillmentProfileByKey } from '../../repositories/fulfillment-profile-repo.js'
|
import { getFulfillmentProfileByKey } from '../../repositories/fulfillment-profile-repo.js'
|
||||||
import { createHttpError } from '../../utils/http.js'
|
import { createHttpError } from '../../utils/http.js'
|
||||||
import { formatFenToAmount } from '../../utils/money.js'
|
|
||||||
import { syncConfiguredFulfillmentBindings } from '../bootstrap/fulfillment-bootstrap-service.js'
|
import { syncConfiguredFulfillmentBindings } from '../bootstrap/fulfillment-bootstrap-service.js'
|
||||||
import { normalizeProductName } from '../order/product-match-service.js'
|
import { normalizeProductName } from '../order/product-match-service.js'
|
||||||
import { resolveDisplayShopName } from './admin-read-shared-helpers.js'
|
import { resolveDisplayShopName } from './admin-read-shared-helpers.js'
|
||||||
@@ -86,6 +85,11 @@ import {
|
|||||||
resolveCloudtentaclesAdminContext,
|
resolveCloudtentaclesAdminContext,
|
||||||
resolveKuaishouEticketAdminContext,
|
resolveKuaishouEticketAdminContext,
|
||||||
} from './admin-platform-config-context.js'
|
} from './admin-platform-config-context.js'
|
||||||
|
import {
|
||||||
|
buildFulfillmentLookupResult,
|
||||||
|
normalizeFulfillmentLookupPayload,
|
||||||
|
resolveFulfillmentLookupDetail,
|
||||||
|
} from './admin-platform-config-fulfillment.js'
|
||||||
import {
|
import {
|
||||||
applyOptionalStringField,
|
applyOptionalStringField,
|
||||||
mapAdminAgisoMessagingDefaults,
|
mapAdminAgisoMessagingDefaults,
|
||||||
@@ -789,31 +793,7 @@ export async function updateAdminKuaishouCloudFulfillmentConfig(
|
|||||||
|
|
||||||
/** @param {AdminFulfillmentBindingLookupInput} [payload] */
|
/** @param {AdminFulfillmentBindingLookupInput} [payload] */
|
||||||
export async function lookupAdminFulfillmentBindingOrder(payload = /** @type {AdminFulfillmentBindingLookupInput} */ ({})) {
|
export async function lookupAdminFulfillmentBindingOrder(payload = /** @type {AdminFulfillmentBindingLookupInput} */ ({})) {
|
||||||
const provider = String(payload.provider || 'agiso').trim() || 'agiso'
|
const { provider, platform, shopId, platformOrderId } = normalizeFulfillmentLookupPayload(payload)
|
||||||
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 detailResult = await enrichAgisoXianyuTradeOrder({
|
const detailResult = await enrichAgisoXianyuTradeOrder({
|
||||||
provider,
|
provider,
|
||||||
@@ -835,80 +815,18 @@ export async function lookupAdminFulfillmentBindingOrder(payload = /** @type {Ad
|
|||||||
requestId: `admin-fulfillment-lookup:${shopId}:${platformOrderId}`,
|
requestId: `admin-fulfillment-lookup:${shopId}:${platformOrderId}`,
|
||||||
})
|
})
|
||||||
|
|
||||||
const detail = isPlainObject(detailResult?.parsed) ? detailResult.parsed : {}
|
const { detail } = resolveFulfillmentLookupDetail(detailResult, platformOrderId)
|
||||||
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 bindings = getOrderFulfillmentBindingConfigs()
|
const bindings = getOrderFulfillmentBindingConfigs()
|
||||||
const resolvedShopName = pickFirstNonEmpty([
|
return buildFulfillmentLookupResult({
|
||||||
detail.shopName,
|
provider,
|
||||||
getAgisoShopConfig(shopId)?.shopName,
|
platform,
|
||||||
])
|
shopId,
|
||||||
|
platformOrderId,
|
||||||
return {
|
detail,
|
||||||
order: {
|
detailResult,
|
||||||
provider,
|
bindings,
|
||||||
platform,
|
fallbackShopName: getAgisoShopConfig(shopId)?.shopName,
|
||||||
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),
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param {AdminFulfillmentBindingConfigSaveInput} [payload] */
|
/** @param {AdminFulfillmentBindingConfigSaveInput} [payload] */
|
||||||
|
|||||||
Reference in New Issue
Block a user