优化
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { findPricedOrderRouteAcrossOrigins, freezeOrderCommand } from './orders.mjs'
|
||||
import { findPricedOrderRouteAcrossOrigins, findPricedOrderRoute, freezeOrderCommand } from './orders.mjs'
|
||||
|
||||
const MODULE = 'order-inbound-status-fixtures'
|
||||
const OPTIONS = { scenario: 'manual-order-inbound-status-matrix', deploymentBlocker: true }
|
||||
@@ -178,19 +178,143 @@ async function waitForInbound(context, fixture, orderId, allowCompletedOrder = f
|
||||
return { response: null, summary: null }
|
||||
}
|
||||
|
||||
export async function retainFeeConfirmingFixture(context, { inboundId, tenantId }) {
|
||||
const suffix = `${query(inboundId)}?tenantId=${query(tenantId)}`
|
||||
const detailPath = `/api/platform/inbounds/${suffix}`
|
||||
const beforeResponse = await context.request('GET', detailPath)
|
||||
const before = context.apiSuccess(MODULE, '试算前核对独立待审核测量样本', beforeResponse, '', OPTIONS)
|
||||
if (id(before?.summary?.id) !== id(inboundId)
|
||||
|| id(before?.summary?.tenantId) !== id(tenantId)
|
||||
|| before?.summary?.processStatus !== 'MEASURE_PENDING' || !before?.summary?.inboundCode) {
|
||||
throw new Error('FEE_CONFIRMING 必须从本轮独立 MEASURE_PENDING 入库单生成')
|
||||
}
|
||||
const calculatedResponse = await context.request('POST',
|
||||
`/api/platform/v1/inbounds/${query(inboundId)}/fees/calculate?tenantId=${query(tenantId)}`,
|
||||
{ weightSource: 'ACTUAL', volumeSource: 'ACTUAL' }, undefined,
|
||||
{ 'Idempotency-Key': key(context, `retain-fee-confirming-${inboundId}`) })
|
||||
const calculated = context.apiSuccess(MODULE, '审核前真实试算并保留待费用确认样本', calculatedResponse, '', OPTIONS)
|
||||
if (!calculated?.feeId || calculated?.feeStatus !== 'CALCULATED'
|
||||
|| calculated?.nextProcessStatus !== 'FEE_CONFIRMING') {
|
||||
throw new Error('真实费用试算未生成 FEE_CONFIRMING/CALCULATED')
|
||||
}
|
||||
const latestResponse = await context.request('GET',
|
||||
`/api/platform/v1/inbounds/${query(inboundId)}/fees/latest?tenantId=${query(tenantId)}`)
|
||||
const latest = context.apiSuccess(MODULE, '回读待确认费用的同一快照', latestResponse, '', OPTIONS)
|
||||
const detailResponse = await context.request('GET', detailPath)
|
||||
const detail = context.apiSuccess(MODULE, '回读待费用确认样本持久状态', detailResponse, '', OPTIONS)
|
||||
const valid = id(detail?.summary?.id) === id(inboundId)
|
||||
&& id(detail?.summary?.tenantId) === id(tenantId)
|
||||
&& detail?.summary?.processStatus === 'FEE_CONFIRMING'
|
||||
&& detail?.summary?.measureReviewStatus === 'PENDING'
|
||||
&& id(latest?.feeId) === id(calculated.feeId)
|
||||
&& latest?.feeVersion === calculated.feeVersion && latest?.feeStatus === 'CALCULATED'
|
||||
context.assert(MODULE, '费用已持久化且测量尚未审核,保留 FEE_CONFIRMING', detailResponse, valid,
|
||||
`status=${detail?.summary?.processStatus || 'missing'}, feeId=${id(latest?.feeId)}`, OPTIONS)
|
||||
if (!valid) throw new Error('FEE_CONFIRMING 持久状态或费用快照不一致')
|
||||
const listResponse = await context.request('GET',
|
||||
`/api/platform/inbounds?tenantId=${query(tenantId)}&inboundCode=${query(before.summary.inboundCode)}&processStatus=FEE_CONFIRMING&page=1&size=20`)
|
||||
const page = context.apiSuccess(MODULE, '按 FEE_CONFIRMING 查询保留样本', listResponse, '', OPTIONS)
|
||||
const visible = rows(page).some(row => id(row.id) === id(inboundId) && row.processStatus === 'FEE_CONFIRMING')
|
||||
context.assert(MODULE, '待费用确认筛选包含本轮真实样本', listResponse, visible,
|
||||
`inboundId=${inboundId}`, OPTIONS)
|
||||
if (!visible) throw new Error('FEE_CONFIRMING 列表未包含本轮样本')
|
||||
return { feeId: id(latest.feeId), feeVersion: latest.feeVersion }
|
||||
}
|
||||
|
||||
export async function retainWaitPricingFixture(context, { inboundId, tenantId, orderId }) {
|
||||
const detailPath = `/api/platform/inbounds/${query(inboundId)}?tenantId=${query(tenantId)}`
|
||||
const before = context.apiSuccess(MODULE, '渠道变更前核对独立费用确认样本',
|
||||
await context.request('GET', detailPath), '', OPTIONS)
|
||||
const orderDetail = context.apiSuccess(MODULE, '渠道变更前读取当前订单版本与申报',
|
||||
await context.request('GET', `/api/platform/v1/orders/first-leg/${query(orderId)}?tenantId=${query(tenantId)}`), '', OPTIONS)
|
||||
const order = orderDetail?.order
|
||||
if (id(before?.summary?.id) !== id(inboundId) || id(before?.summary?.tenantId) !== id(tenantId)
|
||||
|| id(before?.summary?.orderId) !== id(orderId) || before?.summary?.processStatus !== 'FEE_CONFIRMING'
|
||||
|| id(order?.id) !== id(orderId) || order?.status !== 'APPROVED' || !orderDetail?.cargoes?.length) {
|
||||
throw new Error('WAIT_PRICING 必须从本轮 APPROVED 订单及 FEE_CONFIRMING 入库单生成')
|
||||
}
|
||||
const fixture = context.context?.merchantQualificationFixture
|
||||
const username = fixture?.username || context.options?.merchantUsername || process.env.MERCHANT_TEST_USERNAME
|
||||
const password = fixture?.password || context.options?.merchantPassword || process.env.MERCHANT_TEST_PASSWORD
|
||||
const login = context.apiSuccess(MODULE, '渠道样本客户独立登录',
|
||||
await context.request('POST', '/api/auth/login', { loginId: username, password }, ''), '', OPTIONS)
|
||||
if (!login?.accessToken) throw new Error('WAIT_PRICING 客户登录缺少会话')
|
||||
const token = login.accessToken
|
||||
try {
|
||||
const profile = context.apiSuccess(MODULE, '核对渠道目录所属客户',
|
||||
await context.request('GET', '/api/merchant/v1/iam/tenant/profile', undefined, token), '', OPTIONS)
|
||||
if (id(profile?.id) !== id(tenantId)) throw new Error('WAIT_PRICING 渠道目录客户身份不一致')
|
||||
const channels = context.apiSuccess(MODULE, '查询同仓可替换报价渠道',
|
||||
await context.request('GET', `/api/merchant/v1/first-leg/pricing/channels?originWarehouseId=${query(order.receivingWarehouseId)}`,
|
||||
undefined, token), '', OPTIONS)
|
||||
const route = await findPricedOrderRoute({
|
||||
receiving: { id: order.receivingWarehouseId },
|
||||
destinations: [{ id: order.destinationWarehouseId, warehouseCode: order.amazonWarehouseCode,
|
||||
warehouseName: order.destinationWarehouseName }],
|
||||
channels: (Array.isArray(channels) ? channels : []).filter(channel => channel.channelCode !== order.channelCode
|
||||
&& channel.pricingTaxModes?.includes(order.pricingTaxMode))
|
||||
.map(channel => ({ ...channel, pricingTaxModes: [order.pricingTaxMode] })),
|
||||
destinationType: order.destinationType, concurrency: 1,
|
||||
estimate: body => context.request('POST', '/api/merchant/v1/first-leg/pricing/estimate', body, token),
|
||||
})
|
||||
if (!route?.priced?.channel) throw new Error('WAIT_PRICING 没有同仓同目的仓同税制的实际可计价替换渠道')
|
||||
const channel = route.priced.channel
|
||||
const changedResponse = await context.request('PUT',
|
||||
`/api/platform/v1/orders/${query(orderId)}/declaration?tenantId=${query(tenantId)}`,
|
||||
{ expectedVersion: Number(order.version), customerRemark: order.customerRemark,
|
||||
expectedArrivalAt: order.expectedArrivalAt || null, estimatedVolumeCbm: order.estimatedVolumeCbm ?? null,
|
||||
channelCode: channel.channelCode, channelName: channel.channelName,
|
||||
cargoes: orderDetail.cargoes.map(cargo => Object.fromEntries([
|
||||
'itemNo', 'cargoName', 'cargoNameEn', 'skuNo', 'model', 'packageCount', 'totalQuantity',
|
||||
'lengthCm', 'widthCm', 'heightCm', 'grossWeightKg', 'volumeCbm', 'netWeightKg',
|
||||
'hsCode', 'customsHsCode', 'unitsPerPackage', 'unitDeclaredValue', 'declaredValue',
|
||||
'currency', 'material', 'usage', 'brand', 'cargoRemark',
|
||||
].map(field => [field, cargo[field] ?? (field === 'cargoRemark' ? cargo.remark : null) ?? null]))) })
|
||||
const changed = context.apiSuccess(MODULE, '通过正式申报接口变更本轮未发布费用订单渠道', changedResponse, '', OPTIONS)
|
||||
if (Number(changed?.version) !== Number(order.version) + 1 || changed?.channelChanged !== true) {
|
||||
throw new Error('WAIT_PRICING 渠道变更未确认当前订单版本')
|
||||
}
|
||||
let detail
|
||||
let response
|
||||
for (let attempt = 0; attempt < 30; attempt += 1) {
|
||||
response = await context.request('GET', detailPath)
|
||||
detail = context.apiSuccess(MODULE, '等待渠道变更及费用失效事实收敛', response, '', OPTIONS)
|
||||
if (detail?.summary?.processStatus === 'WAIT_PRICING'
|
||||
&& detail?.summary?.channelCode === channel.channelCode) break
|
||||
if (attempt < 29) await wait(500)
|
||||
}
|
||||
const valid = id(detail?.summary?.id) === id(inboundId) && id(detail?.summary?.tenantId) === id(tenantId)
|
||||
&& detail?.summary?.processStatus === 'WAIT_PRICING' && detail?.summary?.feeStatus === 'UNPRICED'
|
||||
&& detail?.summary?.channelCode === channel.channelCode
|
||||
context.assert(MODULE, '真实渠道变更保留 WAIT_PRICING 且旧费用失效', response, valid,
|
||||
`status=${detail?.summary?.processStatus}, feeStatus=${detail?.summary?.feeStatus}`, OPTIONS)
|
||||
if (!valid) throw new Error('WAIT_PRICING 渠道变更事实未收敛')
|
||||
const listResponse = await context.request('GET',
|
||||
`/api/platform/inbounds?tenantId=${query(tenantId)}&inboundCode=${query(before.summary.inboundCode)}&processStatus=WAIT_PRICING&page=1&size=20`)
|
||||
const page = context.apiSuccess(MODULE, '按 WAIT_PRICING 筛选本轮样本', listResponse, '', OPTIONS)
|
||||
const visible = rows(page).some(row => id(row.id) === id(inboundId) && row.processStatus === 'WAIT_PRICING')
|
||||
context.assert(MODULE, '待计费筛选包含本轮真实样本', listResponse, visible, `inboundId=${inboundId}`, OPTIONS)
|
||||
if (!visible) throw new Error('WAIT_PRICING 列表未包含本轮样本')
|
||||
} finally {
|
||||
context.apiSuccess(MODULE, '退出渠道目录客户独立会话',
|
||||
await context.request('POST', '/api/auth/logout', { refreshToken: login.refreshToken }, token), '', OPTIONS)
|
||||
}
|
||||
}
|
||||
|
||||
async function prepareInboundStatus(context, target) {
|
||||
const originalMarker = context.marker
|
||||
const originalRequest = context.request
|
||||
const fixtureMarker = `${originalMarker}-IN-${target}`
|
||||
const stop = Symbol(target)
|
||||
const createdKeys = ['orders', 'ocrInbound', 'testInbound', 'inboundMeasurement', 'inboundWarehouseResources']
|
||||
const contextKeys = ['inboundWeightOcr', 'ocrInbound', 'testInbound', 'createdInbound']
|
||||
const contextKeys = ['inboundWeightOcr', 'ocrInbound', 'testInbound', 'createdInbound', 'orderStopAtApproved']
|
||||
const createdSnapshot = new Map(createdKeys.map((name) => [name,
|
||||
{ present: Object.hasOwn(context.createdResources, name), value: context.createdResources[name] }]))
|
||||
const contextSnapshot = new Map(contextKeys.map((name) => [name,
|
||||
{ present: Object.hasOwn(context.context, name), value: context.context[name] }]))
|
||||
try {
|
||||
context.marker = fixtureMarker
|
||||
if (target === 'WAIT_PRICING') context.context.orderStopAtApproved = true
|
||||
const orders = await import(new URL('./orders.mjs', import.meta.url))
|
||||
await orders.run(context)
|
||||
const order = { ...context.createdResources.orders }
|
||||
@@ -206,7 +330,7 @@ async function prepareInboundStatus(context, target) {
|
||||
|
||||
context.request = async (method, path, body, token, headers) => {
|
||||
const isSubmit = path.includes(`/inbounds/${inboundId}/measurement/submit`)
|
||||
if (target === 'MEASURE_PENDING' && isSubmit) {
|
||||
if (['MEASURE_PENDING', 'FEE_CONFIRMING', 'WAIT_PRICING'].includes(target) && isSubmit) {
|
||||
const response = await originalRequest.call(context, method, path, body, token, headers)
|
||||
if (!ok(response)) throw new Error(`MEASURE_PENDING 提交失败: HTTP=${response?.http}, code=${response?.body?.code}`)
|
||||
throw stop
|
||||
@@ -224,6 +348,13 @@ async function prepareInboundStatus(context, target) {
|
||||
}
|
||||
}
|
||||
|
||||
if (['FEE_CONFIRMING', 'WAIT_PRICING'].includes(target)) {
|
||||
await retainFeeConfirmingFixture(context, { inboundId, tenantId })
|
||||
}
|
||||
if (target === 'WAIT_PRICING') {
|
||||
await retainWaitPricingFixture(context, { inboundId, tenantId, orderId: id(order.orderId) })
|
||||
}
|
||||
|
||||
let response = null
|
||||
let detail = null
|
||||
for (let attempt = 1; attempt <= 8; attempt += 1) {
|
||||
@@ -320,7 +451,7 @@ export async function run(context, matrix = context.createdResources.manualState
|
||||
id: waitMeasure.orderId, status: 'WAIT_MEASURE', detailPath: `/orders/${waitMeasure.orderId}`,
|
||||
}
|
||||
|
||||
for (const status of ['MEASURING', 'MEASURE_PENDING', 'WAIT_SPLIT']) {
|
||||
for (const status of ['MEASURING', 'MEASURE_PENDING', 'FEE_CONFIRMING', 'WAIT_PRICING', 'WAIT_SPLIT']) {
|
||||
const sample = await prepareInboundStatus(context, status)
|
||||
result.inbounds[status] = sample
|
||||
matrix.pages.inbounds.fixtures[status] = {
|
||||
@@ -335,22 +466,7 @@ export async function run(context, matrix = context.createdResources.manualState
|
||||
}
|
||||
|
||||
if (!result.inbounds.WAIT_SPLIT) {
|
||||
const legacyResponse = await context.request('GET',
|
||||
`/api/platform/v1/inbounds?tenantId=${query(fixture.tenantId)}&processStatus=WAIT_SPLIT&page=1&size=20`)
|
||||
const legacyPage = context.apiSuccess(MODULE, '查询存量 WAIT_SPLIT 样本', legacyResponse, '', OPTIONS)
|
||||
const legacyInbound = rows(legacyPage)[0]
|
||||
if (legacyInbound?.id && legacyInbound?.orderId) {
|
||||
const sample = { inboundId: id(legacyInbound.id), orderId: id(legacyInbound.orderId), tenantId: fixture.tenantId,
|
||||
status: 'WAIT_SPLIT', inboundCode: legacyInbound.inboundCode || null }
|
||||
result.inbounds.WAIT_SPLIT = sample
|
||||
result.unifiedOrders.WAIT_SPLIT = sample
|
||||
matrix.pages.inbounds.fixtures.WAIT_SPLIT = {
|
||||
id: sample.inboundId, status: 'WAIT_SPLIT', detailPath: `/orders/${sample.orderId}`,
|
||||
}
|
||||
matrix.pages.unifiedOrders.fixtures.WAIT_SPLIT = {
|
||||
id: sample.orderId, status: 'WAIT_SPLIT', detailPath: `/orders/${sample.orderId}`,
|
||||
}
|
||||
}
|
||||
throw new Error('本轮 WAIT_SPLIT 独立样本缺失,不允许借用历史数据')
|
||||
}
|
||||
|
||||
context.addStep(MODULE, '登记订单/入库人工状态矩阵', null, true,
|
||||
|
||||
Reference in New Issue
Block a user