This commit is contained in:
2026-08-24 19:58:35 +08:00
parent 9e0f41e78b
commit c93fdc44d5
23 changed files with 965 additions and 313 deletions
+32 -18
View File
@@ -20,6 +20,10 @@ function pageOk(value) { return Array.isArray(value?.records) && Number.isFinite
function success(response) { return Boolean(response) && response.http === 200 && String(response.body?.code) === '200' }
function sleep(milliseconds) { return new Promise((resolve) => setTimeout(resolve, milliseconds)) }
function key(context, operation) { return `${context.marker}-${operation}`.slice(0, 128) }
export function requestedOrderDestinationType(context) {
return String(context?.context?.orderDestinationType || 'FBA').trim().toUpperCase() === 'OVERSEAS'
? 'OVERSEAS' : 'FBA'
}
export function approvedInboundReady(orderDetail, taskResponse, tenantId, orderId) {
const summary = orderDetail?.summary
const task = success(taskResponse) ? taskResponse.body?.data : null
@@ -33,12 +37,12 @@ function tomorrow(days = 30) {
return date.toISOString().slice(0, 10)
}
function pricingEstimateRequest(receiving, channel, pricingTaxMode, destination) {
function pricingEstimateRequest(receiving, channel, pricingTaxMode, destination, destinationType = 'FBA') {
return {
originWarehouseId: receiving.id,
channelCode: channel.channelCode,
destinationCode: destination.warehouseCode,
destinationType: 'FBA',
destinationType,
destinationWarehouseId: destination.id,
destinationName: destination.warehouseName,
pricingTaxMode,
@@ -56,7 +60,7 @@ function estimateMatches(response, candidate) {
}
export async function findPricedOrderRoute({ receiving, destinations, channels, estimate,
concurrency = PRICING_PROBE_CONCURRENCY }) {
concurrency = PRICING_PROBE_CONCURRENCY, destinationType = 'FBA' }) {
const candidates = []
for (const channel of channels) {
if (!channel?.priceBookVersionId || !channel?.channelCode || !Array.isArray(channel.pricingTaxModes)) continue
@@ -65,7 +69,7 @@ export async function findPricedOrderRoute({ receiving, destinations, channels,
for (const destination of destinations) {
if (!destination?.id || !destination?.warehouseCode) continue
candidates.push({ channel, pricingTaxMode, destination,
request: pricingEstimateRequest(receiving, channel, pricingTaxMode, destination) })
request: pricingEstimateRequest(receiving, channel, pricingTaxMode, destination, destinationType) })
}
}
}
@@ -98,7 +102,7 @@ export async function findPricedOrderRoute({ receiving, destinations, channels,
}
export async function findPricedOrderRouteAcrossOrigins({ receivings, destinations, loadChannels, estimate,
concurrency = PRICING_PROBE_CONCURRENCY }) {
concurrency = PRICING_PROBE_CONCURRENCY, destinationType = 'FBA' }) {
const originFailures = []
let attempts = 0
let response = null
@@ -115,7 +119,8 @@ export async function findPricedOrderRouteAcrossOrigins({ receivings, destinatio
traceId: channelResponse?.traceId ?? 'missing', count: channels.length })
continue
}
const result = await findPricedOrderRoute({ receiving, destinations, channels: priceableChannels, estimate, concurrency })
const result = await findPricedOrderRoute({ receiving, destinations, channels: priceableChannels,
estimate, concurrency, destinationType })
attempts += result.attempts
response = result.response || response
if (result.priced) return { receiving, ...result, originFailures }
@@ -196,19 +201,21 @@ async function assertProductImageDownload(context, merchantToken, fileId) {
}
async function loadMasterData(context, merchantToken) {
const destinationType = requestedOrderDestinationType(context)
const domesticResponse = await request(context, 'GET',
'/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=DOMESTIC', undefined, merchantToken)
const domesticPage = context.apiSuccess(MODULE, 'Client 查询国内收货仓', domesticResponse)
context.assert(MODULE, '存在可用国内收货仓', domesticResponse, records(domesticPage).length > 0,
`count=${records(domesticPage).length}`)
const fbaResponse = await request(context, 'GET',
'/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=FBA', undefined, merchantToken)
const fbaPage = context.apiSuccess(MODULE, 'Client 查询 FBA 目的仓', fbaResponse)
context.assert(MODULE, '存在可用 FBA 目的仓', fbaResponse, records(fbaPage).length > 0,
`count=${records(fbaPage).length}`)
const destinationResponse = await request(context, 'GET',
`/api/merchant/v1/warehouses?page=1&size=200&status=ACTIVE&warehouseType=${encodeURIComponent(destinationType)}`,
undefined, merchantToken)
const destinationPage = context.apiSuccess(MODULE, `Client 查询 ${destinationType} 目的仓`, destinationResponse)
context.assert(MODULE, `存在可用 ${destinationType} 目的仓`, destinationResponse,
records(destinationPage).length > 0, `count=${records(destinationPage).length}`)
const receivings = records(domesticPage).filter((warehouse) => warehouse?.status === 'ACTIVE')
const destinations = records(fbaPage)
const destinations = records(destinationPage).filter((warehouse) => warehouse?.status === 'ACTIVE')
if (receivings.length === 0 || destinations.length === 0) throw new Error('缺少创建测试订单所需的收货仓或目的仓')
const pricingResult = await findPricedOrderRouteAcrossOrigins({
@@ -217,6 +224,7 @@ async function loadMasterData(context, merchantToken) {
`/api/merchant/v1/first-leg/pricing/channels?originWarehouseId=${encodeURIComponent(receiving.id)}`,
undefined, merchantToken),
estimate: (body) => request(context, 'POST', '/api/merchant/v1/first-leg/pricing/estimate', body, merchantToken),
destinationType,
})
const priced = pricingResult.priced
const receiving = pricingResult.receiving
@@ -225,7 +233,7 @@ async function loadMasterData(context, merchantToken) {
? `origin=${receiving.id}, priceBookVersionId=${priced.channel.priceBookVersionId}, channel=${priced.channel.channelCode}, pricingTaxMode=${priced.pricingTaxMode}, destination=${priced.destination.warehouseCode}, attempts=${pricingResult.attempts}`
: originPricingFailureEvidence(pricingResult),
CONTRACT_OPTIONS)
if (!priced) throw new Error('当前 ACTIVE 国内收货仓、渠道、报价税制和FBA目的仓没有可计价组合')
if (!priced) throw new Error(`当前 ACTIVE 国内收货仓、渠道、报价税制和 ${destinationType} 目的仓没有可计价组合`)
const partnerResponse = await request(context, 'GET',
'/api/merchant/v1/partners/candidates?type=SHIPPER', undefined, merchantToken)
@@ -234,7 +242,7 @@ async function loadMasterData(context, merchantToken) {
? partners.find((item) => item.status === 'ACTIVE' && item.types?.includes?.('SHIPPER'))
: null
return { receiving, destination: priced.destination, channel: priced.channel,
pricingTaxMode: priced.pricingTaxMode, partner }
pricingTaxMode: priced.pricingTaxMode, destinationType, partner }
}
function buildPayload(context, profile, master, productImageFileId) {
@@ -254,9 +262,10 @@ function buildPayload(context, profile, master, productImageFileId) {
channelCode: master.channel.channelCode, channelName: master.channel.channelName,
carrierId: null, carrierName: null, taxMode: 'BUY_DECLARATION',
pricingTaxMode: master.pricingTaxMode, containerMode: 'LCL',
customsInspection: false, destinationType: 'FBA',
amazonWarehouseCode: master.destination.warehouseCode,
shipmentId, referenceId,
customsInspection: false, destinationType: master.destinationType,
amazonWarehouseCode: master.destinationType === 'FBA' ? master.destination.warehouseCode : null,
shipmentId: master.destinationType === 'FBA' ? shipmentId : null,
referenceId: master.destinationType === 'FBA' ? referenceId : null,
sender: {
companyName, contactName: '订单验收联系人', phone: '13800138000',
email: 'order-acceptance@example.com', countryCode: 'CN', province: '广东省',
@@ -279,7 +288,9 @@ function buildPayload(context, profile, master, productImageFileId) {
material: '塑料 / Plastic', usage: '接口验收 / API acceptance',
model: `MODEL-${box.boxType}`, brand: 'TEST-NO-LOGO',
productLengthCm: 12 + index, productWidthCm: 8 + index, productHeightCm: 6 + index,
shipmentId, referenceId, upcNo: `${Date.now()}${index}`.slice(-12),
shipmentId: master.destinationType === 'FBA' ? shipmentId : null,
referenceId: master.destinationType === 'FBA' ? referenceId : null,
upcNo: `${Date.now()}${index}`.slice(-12),
productImageFileId, dangerousGoods: false,
cargoRemark: `${context.marker} boxType=${box.boxType}`,
}
@@ -387,6 +398,9 @@ export async function run(context) {
marker: context.marker, tenantId, orderId, productImageFileId,
boxTypes: BOXES.map((box) => box.boxType), inboundId: null,
receivingWarehouseId: id(master.receiving.id),
destinationType: master.destinationType,
destinationWarehouseId: id(master.destination.id),
destinationWarehouseCode: master.destination.warehouseCode,
}
const replayResponse = await request(context, 'POST', createPath, payload, merchantToken,