优化
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
|
||||
const source = readFileSync(new URL('inbound-measurement.mjs', import.meta.url), 'utf8')
|
||||
|
||||
test('measurement acceptance scans the stable box code frozen by first print', () => {
|
||||
assert.match(source, /const boxCode = `\$\{inboundCode\}#\$\{originalPackageCount\}-1`/)
|
||||
assert.doesNotMatch(source, /#V\d+#/)
|
||||
})
|
||||
|
||||
test('reprint and Mojia scan share the same frozen box code variable', () => {
|
||||
assert.match(source, /重复整单打印复用已冻结箱码映射[\s\S]*boxCode=\$\{boxCode\}/)
|
||||
assert.match(source, /const firstScanResponse = await multipartRequest[\s\S]*boxCode, \.\.\.firstDimensions/)
|
||||
})
|
||||
@@ -0,0 +1,91 @@
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import test from 'node:test'
|
||||
|
||||
import { findPricedOrderRoute } from './orders.mjs'
|
||||
|
||||
const source = readFileSync(new URL('orders.mjs', import.meta.url), 'utf8')
|
||||
|
||||
test('orders fixture verifies the complete pricing tuple before selecting an FBA warehouse', () => {
|
||||
assert.match(source, /\/api\/merchant\/v1\/first-leg\/pricing\/estimate/)
|
||||
for (const field of ['originWarehouseId', 'channelCode', 'destinationCode', 'destinationType',
|
||||
'destinationWarehouseId', 'pricingTaxMode', 'actualWeightKg', 'actualVolumeCbm']) {
|
||||
assert.match(source, new RegExp(`${field}:`))
|
||||
}
|
||||
assert.match(source, /estimateMatches\(response, candidate\)/)
|
||||
assert.doesNotMatch(source, /const destination = records\(fbaPage\)\[0\]/)
|
||||
})
|
||||
|
||||
test('order payload reuses the tax mode from the successful estimate', () => {
|
||||
assert.match(source, /pricingTaxMode: master\.pricingTaxMode/)
|
||||
assert.doesNotMatch(source, /pricingTaxMode: master\.channel\.pricingTaxModes\[0\]/)
|
||||
assert.match(source, /没有可计价组合/)
|
||||
})
|
||||
|
||||
test('pricing route accepts only an estimate matching the active version channel and tax tuple', async () => {
|
||||
const requests = []
|
||||
const result = await findPricedOrderRoute({
|
||||
receiving: { id: '100' },
|
||||
destinations: [
|
||||
{ id: '201', warehouseCode: 'ABE2', warehouseName: 'ABE2' },
|
||||
{ id: '202', warehouseCode: 'ABE3', warehouseName: 'ABE3' },
|
||||
],
|
||||
channels: [{ priceBookVersionId: '300', channelCode: 'EXX_RAIL_NY',
|
||||
pricingTaxModes: ['TAX_INCLUDED'] }],
|
||||
concurrency: 1,
|
||||
estimate: async (request) => {
|
||||
requests.push(request)
|
||||
if (request.destinationCode === 'ABE2') {
|
||||
return { http: 200, body: { code: 'A0404' }, requestId: 'request-abe2', traceId: 'trace-abe2' }
|
||||
}
|
||||
return { http: 200, body: { code: '200', data: {
|
||||
priceBookVersionId: '300', channelCode: 'EXX_RAIL_NY', pricingTaxMode: 'TAX_INCLUDED',
|
||||
} } }
|
||||
},
|
||||
})
|
||||
assert.equal(result.priced.destination.warehouseCode, 'ABE3')
|
||||
assert.equal(result.attempts, 2)
|
||||
assert.deepEqual(requests[1], {
|
||||
originWarehouseId: '100', channelCode: 'EXX_RAIL_NY', destinationCode: 'ABE3',
|
||||
destinationType: 'FBA', destinationWarehouseId: '202', destinationName: 'ABE3',
|
||||
pricingTaxMode: 'TAX_INCLUDED', actualWeightKg: 46, actualVolumeCbm: 0.278,
|
||||
})
|
||||
assert.equal(result.failures[0].requestId, 'request-abe2')
|
||||
})
|
||||
|
||||
test('pricing route rejects a nominal success from a different version or tuple', async () => {
|
||||
const result = await findPricedOrderRoute({
|
||||
receiving: { id: '100' },
|
||||
destinations: [{ id: '202', warehouseCode: 'ABE3', warehouseName: 'ABE3' }],
|
||||
channels: [{ priceBookVersionId: '300', channelCode: 'EXX_RAIL_NY',
|
||||
pricingTaxModes: ['TAX_INCLUDED'] }],
|
||||
estimate: async () => ({ http: 200, body: { code: '200', data: {
|
||||
priceBookVersionId: 'old-version', channelCode: 'OTHER', pricingTaxMode: 'TAX_EXCLUDED',
|
||||
} }, requestId: 'request-drift', traceId: 'trace-drift' }),
|
||||
})
|
||||
assert.equal(result.priced, null)
|
||||
assert.equal(result.attempts, 1)
|
||||
assert.match(result.failures[0].mismatch, /version=old-version,channel=OTHER,tax=TAX_EXCLUDED/)
|
||||
})
|
||||
|
||||
test('pricing route bounds parallel probes and keeps deterministic candidate selection', async () => {
|
||||
let active = 0; let maximumActive = 0
|
||||
const result = await findPricedOrderRoute({
|
||||
receiving: { id: '100' }, concurrency: 50,
|
||||
destinations: Array.from({ length: 8 }, (_, index) => ({
|
||||
id: String(200 + index), warehouseCode: `FBA${index}`, warehouseName: `FBA ${index}`,
|
||||
})),
|
||||
channels: [{ priceBookVersionId: '300', channelCode: 'CHANNEL', pricingTaxModes: ['TAX_INCLUDED'] }],
|
||||
estimate: async (request) => {
|
||||
active += 1; maximumActive = Math.max(maximumActive, active)
|
||||
await new Promise((resolve) => setTimeout(resolve, request.destinationCode === 'FBA0' ? 5 : 1))
|
||||
active -= 1
|
||||
return { http: 200, body: { code: '200', data: {
|
||||
priceBookVersionId: '300', channelCode: 'CHANNEL', pricingTaxMode: 'TAX_INCLUDED',
|
||||
} } }
|
||||
},
|
||||
})
|
||||
assert.equal(maximumActive, 6)
|
||||
assert.equal(result.priced.destination.warehouseCode, 'FBA0')
|
||||
assert.equal(result.attempts, 6)
|
||||
})
|
||||
@@ -9,6 +9,10 @@ const BOXES = [
|
||||
{ boxType: 'EXTRA_LARGE', lengthCm: 60, widthCm: 50, heightCm: 50 },
|
||||
]
|
||||
const MEASURABLE_STATUSES = new Set(['WAIT_MEASURE', 'MEASURING', 'MEASURE_REJECTED'])
|
||||
const PRICING_PROBE_CONCURRENCY = 6
|
||||
const ORDER_ESTIMATE_WEIGHT_KG = BOXES.reduce((total, _box, index) => total + 10 + index, 0)
|
||||
const ORDER_ESTIMATE_VOLUME_CBM = BOXES.reduce((total, box) =>
|
||||
total + box.lengthCm * box.widthCm * box.heightCm / 1_000_000, 0)
|
||||
|
||||
function id(value) { return value == null ? '' : String(value) }
|
||||
function records(value) { return Array.isArray(value?.records) ? value.records : [] }
|
||||
@@ -29,6 +33,77 @@ function tomorrow(days = 30) {
|
||||
return date.toISOString().slice(0, 10)
|
||||
}
|
||||
|
||||
function pricingEstimateRequest(receiving, channel, pricingTaxMode, destination) {
|
||||
return {
|
||||
originWarehouseId: receiving.id,
|
||||
channelCode: channel.channelCode,
|
||||
destinationCode: destination.warehouseCode,
|
||||
destinationType: 'FBA',
|
||||
destinationWarehouseId: destination.id,
|
||||
destinationName: destination.warehouseName,
|
||||
pricingTaxMode,
|
||||
actualWeightKg: ORDER_ESTIMATE_WEIGHT_KG,
|
||||
actualVolumeCbm: ORDER_ESTIMATE_VOLUME_CBM,
|
||||
}
|
||||
}
|
||||
|
||||
function estimateMatches(response, candidate) {
|
||||
const estimate = success(response) ? response.body?.data : null
|
||||
return Boolean(estimate)
|
||||
&& id(estimate.priceBookVersionId) === id(candidate.channel.priceBookVersionId)
|
||||
&& id(estimate.channelCode) === id(candidate.channel.channelCode)
|
||||
&& id(estimate.pricingTaxMode) === id(candidate.pricingTaxMode)
|
||||
}
|
||||
|
||||
export async function findPricedOrderRoute({ receiving, destinations, channels, estimate,
|
||||
concurrency = PRICING_PROBE_CONCURRENCY }) {
|
||||
const candidates = []
|
||||
for (const channel of channels) {
|
||||
if (!channel?.priceBookVersionId || !channel?.channelCode || !Array.isArray(channel.pricingTaxModes)) continue
|
||||
for (const pricingTaxMode of channel.pricingTaxModes) {
|
||||
if (!pricingTaxMode) continue
|
||||
for (const destination of destinations) {
|
||||
if (!destination?.id || !destination?.warehouseCode) continue
|
||||
candidates.push({ channel, pricingTaxMode, destination,
|
||||
request: pricingEstimateRequest(receiving, channel, pricingTaxMode, destination) })
|
||||
}
|
||||
}
|
||||
}
|
||||
const failures = []
|
||||
let lastResponse = null
|
||||
const batchSize = Math.max(1, Math.min(Number(concurrency) || 1, PRICING_PROBE_CONCURRENCY))
|
||||
let attempts = 0
|
||||
for (let offset = 0; offset < candidates.length; offset += batchSize) {
|
||||
const batch = candidates.slice(offset, offset + batchSize)
|
||||
const results = await Promise.all(batch.map(async (candidate) => ({
|
||||
candidate, response: await estimate(candidate.request),
|
||||
})))
|
||||
attempts += results.length
|
||||
lastResponse = results.at(-1)?.response || lastResponse
|
||||
const match = results.find(({ candidate, response }) => estimateMatches(response, candidate))
|
||||
if (match) return { priced: match.candidate, response: match.response, attempts, failures }
|
||||
for (const { candidate, response } of results) {
|
||||
const returned = success(response) ? response.body?.data : null
|
||||
failures.push({
|
||||
tuple: `${candidate.channel.channelCode}/${candidate.pricingTaxMode}/${candidate.destination.warehouseCode}`,
|
||||
http: response?.http ?? 'missing', code: response?.body?.code ?? 'missing',
|
||||
requestId: response?.requestId ?? 'missing', traceId: response?.traceId ?? 'missing',
|
||||
mismatch: returned
|
||||
? `version=${id(returned.priceBookVersionId) || 'missing'},channel=${id(returned.channelCode) || 'missing'},tax=${id(returned.pricingTaxMode) || 'missing'}`
|
||||
: null,
|
||||
})
|
||||
}
|
||||
}
|
||||
return { priced: null, response: lastResponse, attempts, failures }
|
||||
}
|
||||
|
||||
function pricingFailureEvidence(result) {
|
||||
const describe = (failure) => failure
|
||||
? `${failure.tuple}[HTTP=${failure.http},code=${failure.code},requestId=${failure.requestId},traceId=${failure.traceId}${failure.mismatch ? `,response=${failure.mismatch}` : ''}]`
|
||||
: 'missing'
|
||||
return `没有可计价组合;attempts=${result.attempts}, first=${describe(result.failures[0])}, last=${describe(result.failures.at(-1))}`
|
||||
}
|
||||
|
||||
async function request(context, method, path, body, token, extraHeaders = {}) {
|
||||
const headers = { Accept: 'application/json', Authorization: `Bearer ${token}`, ...extraHeaders }
|
||||
let payload = body
|
||||
@@ -96,19 +171,32 @@ async function loadMasterData(context, merchantToken) {
|
||||
const fbaPage = context.apiSuccess(MODULE, 'Client 查询 FBA 目的仓', fbaResponse)
|
||||
context.assert(MODULE, '存在可用 FBA 目的仓', fbaResponse, records(fbaPage).length > 0,
|
||||
`count=${records(fbaPage).length}`)
|
||||
const receiving = records(domesticPage)[0]; const destination = records(fbaPage)[0]
|
||||
if (!receiving || !destination) throw new Error('缺少创建测试订单所需的收货仓或目的仓')
|
||||
const receiving = records(domesticPage)[0]
|
||||
const destinations = records(fbaPage)
|
||||
if (!receiving || destinations.length === 0) throw new Error('缺少创建测试订单所需的收货仓或目的仓')
|
||||
|
||||
const channelResponse = await request(context, 'GET',
|
||||
`/api/merchant/v1/first-leg/pricing/channels?originWarehouseId=${encodeURIComponent(receiving.id)}`,
|
||||
undefined, merchantToken)
|
||||
const channels = context.apiSuccess(MODULE, 'Client 查询可用计费渠道', channelResponse)
|
||||
const channel = Array.isArray(channels)
|
||||
? channels.find((item) => Array.isArray(item.pricingTaxModes) && item.pricingTaxModes.length > 0)
|
||||
: null
|
||||
context.assert(MODULE, '存在带计价税制的可用渠道', channelResponse, Boolean(channel),
|
||||
const priceableChannels = Array.isArray(channels)
|
||||
? channels.filter((item) => Array.isArray(item.pricingTaxModes) && item.pricingTaxModes.length > 0)
|
||||
: []
|
||||
context.assert(MODULE, '存在带计价税制的可用渠道', channelResponse, priceableChannels.length > 0,
|
||||
`count=${Array.isArray(channels) ? channels.length : 'n/a'}`)
|
||||
if (!channel) throw new Error('缺少创建测试订单所需的可用渠道')
|
||||
if (priceableChannels.length === 0) throw new Error('缺少创建测试订单所需的可用渠道')
|
||||
|
||||
const pricingResult = await findPricedOrderRoute({
|
||||
receiving, destinations, channels: priceableChannels,
|
||||
estimate: (body) => request(context, 'POST', '/api/merchant/v1/first-leg/pricing/estimate', body, merchantToken),
|
||||
})
|
||||
const priced = pricingResult.priced
|
||||
context.addStep(MODULE, 'Client 选择具有真实价格覆盖的渠道和目的仓', pricingResult.response, Boolean(priced),
|
||||
priced
|
||||
? `origin=${receiving.id}, priceBookVersionId=${priced.channel.priceBookVersionId}, channel=${priced.channel.channelCode}, pricingTaxMode=${priced.pricingTaxMode}, destination=${priced.destination.warehouseCode}, attempts=${pricingResult.attempts}`
|
||||
: pricingFailureEvidence(pricingResult),
|
||||
CONTRACT_OPTIONS)
|
||||
if (!priced) throw new Error('当前起运仓、渠道、报价税制和FBA目的仓没有可计价组合')
|
||||
|
||||
const partnerResponse = await request(context, 'GET',
|
||||
'/api/merchant/v1/partners/candidates?type=SHIPPER', undefined, merchantToken)
|
||||
@@ -116,7 +204,8 @@ async function loadMasterData(context, merchantToken) {
|
||||
const partner = Array.isArray(partners)
|
||||
? partners.find((item) => item.status === 'ACTIVE' && item.types?.includes?.('SHIPPER'))
|
||||
: null
|
||||
return { receiving, destination, channel, partner }
|
||||
return { receiving, destination: priced.destination, channel: priced.channel,
|
||||
pricingTaxMode: priced.pricingTaxMode, partner }
|
||||
}
|
||||
|
||||
function buildPayload(context, profile, master, productImageFileId) {
|
||||
@@ -135,7 +224,7 @@ function buildPayload(context, profile, master, productImageFileId) {
|
||||
receivingWarehouseName: master.receiving.warehouseName,
|
||||
channelCode: master.channel.channelCode, channelName: master.channel.channelName,
|
||||
carrierId: null, carrierName: null, taxMode: 'BUY_DECLARATION',
|
||||
pricingTaxMode: master.channel.pricingTaxModes[0], containerMode: 'LCL',
|
||||
pricingTaxMode: master.pricingTaxMode, containerMode: 'LCL',
|
||||
customsInspection: false, destinationType: 'FBA',
|
||||
amazonWarehouseCode: master.destination.warehouseCode,
|
||||
shipmentId, referenceId,
|
||||
|
||||
Reference in New Issue
Block a user