优化
This commit is contained in:
@@ -42,6 +42,61 @@ export function fixtureInboundCode(policy, marker) {
|
||||
// Short enough for the 34-character carton QR limit; stable within this fresh fixture.
|
||||
return { inboundCode: `AT${createHash('sha256').update(marker).digest('hex').slice(0, 20).toUpperCase()}` }
|
||||
}
|
||||
|
||||
export function cargoRequirementNegativeCases(payload) {
|
||||
const cases = [
|
||||
{ name: '木制品未选择商检', at: 'CREATE', mutate: value => { value.customsInspection = false; value.cargoes[0].woodenProduct = true } },
|
||||
{ name: '货物零箱数', at: 'CREATE', mutate: value => { value.cargoes[0].packageCount = 0 } },
|
||||
{ name: '货物总件数与每箱件数不守恒', at: 'CREATE', mutate: value => { value.cargoes[0].totalQuantity++ } },
|
||||
]
|
||||
if (payload.destinationType === 'FBA') {
|
||||
cases.push({ name: 'FBA缺少订单货件编号', at: 'SUBMIT', mutate: value => { value.shipmentId = null } },
|
||||
{ name: 'FBA缺少逐行跟踪编号', at: 'SUBMIT', mutate: value => { value.cargoes[0].referenceId = null } })
|
||||
}
|
||||
if (payload.fulfillmentType === 'ONE_CLICK_DROPSHIP') {
|
||||
cases.push({ name: '一件代发缺少SKU', at: 'CREATE_OR_SUBMIT', mutate: value => { value.cargoes[0].skuNo = null } },
|
||||
{ name: '一件代发混箱组箱规冲突', at: 'SUBMIT', mutate: value => {
|
||||
const extra = structuredClone(value.cargoes[0]); extra.skuNo = `${extra.skuNo}-B`.slice(0, 64); extra.lengthCm++
|
||||
value.cargoes.push(extra)
|
||||
} })
|
||||
}
|
||||
return cases.map(({ mutate, ...item }) => { const body = structuredClone(payload); mutate(body); return { ...item, body } })
|
||||
}
|
||||
|
||||
async function verifyCargoRequirementNegatives(context, payload, policy, merchantToken) {
|
||||
const base = '/api/merchant/v1/orders/first-leg'
|
||||
const cases = cargoRequirementNegativeCases(payload)
|
||||
const evidence = []
|
||||
for (const [index, item] of cases.entries()) {
|
||||
const body = { ...item.body, ...fixtureInboundCode(policy, `${context.marker}-NEG-${index}`) }
|
||||
const created = await request(context, 'POST', base, body, merchantToken,
|
||||
{ 'Idempotency-Key': key(context, `cargo-negative-${index}-create`) })
|
||||
if (item.at === 'CREATE' || String(created?.body?.code) !== '200') {
|
||||
context.assert(MODULE, `${payload.destinationType}/${payload.fulfillmentType} ${item.name}创建拒绝`, created,
|
||||
created.http === 200 && String(created.body?.code) === 'A0422',
|
||||
`HTTP=${created.http}, code=${created.body?.code || 'missing'}`, CONTRACT_OPTIONS)
|
||||
evidence.push({ name: item.name, stage: 'CREATE', code: created.body?.code,
|
||||
orderId: String(created.body?.code) === '200' ? id(created.body?.data) : null })
|
||||
continue
|
||||
}
|
||||
const orderId = id(created.body?.data)
|
||||
if (!orderId) throw new Error('negative draft missing order id')
|
||||
const before = await request(context, 'GET', `${base}/${orderId}`, undefined, merchantToken)
|
||||
const submitted = await request(context, 'POST', `${base}/${orderId}/submit`, undefined, merchantToken,
|
||||
{ 'Idempotency-Key': key(context, `cargo-negative-${index}-submit`) })
|
||||
const after = await request(context, 'GET', `${base}/${orderId}`, undefined, merchantToken)
|
||||
context.assert(MODULE, `${payload.destinationType}/${payload.fulfillmentType} ${item.name}提交拒绝且草稿零漂移`, submitted,
|
||||
submitted.http === 200 && String(submitted.body?.code) === 'A0422'
|
||||
&& String(before.body?.code) === '200' && String(after.body?.code) === '200'
|
||||
&& after.body.data?.order?.status === 'DRAFT'
|
||||
&& JSON.stringify(before.body.data) === JSON.stringify(after.body.data),
|
||||
`orderId=${orderId}, code=${submitted.body?.code || 'missing'}`, CONTRACT_OPTIONS)
|
||||
evidence.push({ name: item.name, stage: 'SUBMIT', code: submitted.body?.code, orderId })
|
||||
}
|
||||
context.createdResources.orderCargoNegatives ??= []
|
||||
context.createdResources.orderCargoNegatives.push({ marker: context.marker,
|
||||
destinationType: payload.destinationType, fulfillmentType: payload.fulfillmentType, cases: evidence })
|
||||
}
|
||||
function declarationCargo(source, index) {
|
||||
const number = (value) => Number.isFinite(Number(value)) ? Number(value) : 0
|
||||
const optionalNumber = (value) => value === null || value === undefined || value === '' ? null : number(value)
|
||||
@@ -152,6 +207,33 @@ export async function verifyOrderApprovalReplay(context, { tenantId, orderId, in
|
||||
context.createdResources.orders.approvalReplay = evidence
|
||||
return evidence
|
||||
}
|
||||
|
||||
export async function finishOrderLifecycle(context, { tenantId, orderId, firstLegDetailPath }) {
|
||||
const stopAtApproved = context.context?.orderStopAtApproved === true
|
||||
if (!stopAtApproved) {
|
||||
const acceptResponse = await context.request('POST',
|
||||
`/api/platform/v1/orders/first-leg/${encodeURIComponent(orderId)}/accept?tenantId=${encodeURIComponent(tenantId)}`)
|
||||
context.apiSuccess(MODULE, 'Admin 受理本轮订单', acceptResponse)
|
||||
const startResponse = await context.request('POST',
|
||||
`/api/platform/v1/orders/first-leg/${encodeURIComponent(orderId)}/start?tenantId=${encodeURIComponent(tenantId)}`)
|
||||
context.apiSuccess(MODULE, 'Admin 启动本轮订单履约', startResponse)
|
||||
}
|
||||
const finalDetailResponse = await context.request('GET', firstLegDetailPath)
|
||||
const finalDetail = context.apiSuccess(MODULE, 'Admin 回查本轮订单履约状态', finalDetailResponse)
|
||||
const history = finalDetail?.statusHistory
|
||||
const valid = finalDetail?.order?.status === (stopAtApproved ? 'APPROVED' : 'PROCESSING')
|
||||
&& history?.some?.((item) => item.toStatus === 'APPROVED')
|
||||
&& (stopAtApproved
|
||||
? !history.some((item) => ['ACCEPTED', 'PROCESSING'].includes(item.toStatus))
|
||||
: history.some((item) => item.toStatus === 'ACCEPTED')
|
||||
&& history.some((item) => item.toStatus === 'PROCESSING'))
|
||||
context.assert(MODULE, stopAtApproved ? '显式审批停点保留原始APPROVED且未受理启动' : '本轮订单已进入处理中且保留状态历史',
|
||||
finalDetailResponse, valid,
|
||||
`status=${finalDetail?.order?.status ?? 'missing'}, history=${history?.length ?? 'n/a'}`, CONTRACT_OPTIONS)
|
||||
if (stopAtApproved && !valid) throw new Error('显式审批停点未保留原始APPROVED,不能继续准备该专项')
|
||||
context.createdResources.orders.rawOrderStatus = finalDetail?.order?.status || null
|
||||
context.createdResources.orders.stoppedAtApproved = stopAtApproved
|
||||
}
|
||||
function tomorrow(days = 30) {
|
||||
const date = new Date(); date.setUTCDate(date.getUTCDate() + days)
|
||||
return date.toISOString().slice(0, 10)
|
||||
@@ -239,6 +321,14 @@ export async function findPricedOrderRouteAcrossOrigins({ receivings, destinatio
|
||||
traceId: channelResponse?.traceId ?? 'missing', count: channels.length })
|
||||
continue
|
||||
}
|
||||
if (destinationType === 'PRIVATE') {
|
||||
// Private destinations use customer-owned addresses, not warehouse-rate
|
||||
// destination rows. Match the same authoritative channel path already
|
||||
// used by fixed-origin private orders.
|
||||
const channel = priceableChannels[0]
|
||||
return { receiving, priced: { channel, pricingTaxMode: channel.pricingTaxModes[0], destination: null },
|
||||
response: channelResponse, attempts: 0, failures: [], originFailures }
|
||||
}
|
||||
const result = await findPricedOrderRoute({ receiving, destinations, channels: priceableChannels,
|
||||
estimate, concurrency, destinationType })
|
||||
attempts += result.attempts
|
||||
@@ -439,7 +529,9 @@ async function loadMasterData(context, merchantToken) {
|
||||
}
|
||||
const priced = pricingResult.priced
|
||||
const receiving = pricingResult.receiving
|
||||
context.addStep(MODULE, 'Client 从 ACTIVE 国内仓选择具有真实价格覆盖的渠道和目的仓', pricingResult.response, Boolean(priced),
|
||||
context.addStep(MODULE, destinationType === 'PRIVATE'
|
||||
? 'Client 私人地址订单选择有效国内仓渠道与报价税制'
|
||||
: 'Client 从 ACTIVE 国内仓选择具有真实价格覆盖的渠道和目的仓', pricingResult.response, Boolean(priced),
|
||||
priced
|
||||
? `origin=${receiving.id}, priceBookVersionId=${priced.channel.priceBookVersionId}, channel=${priced.channel.channelCode}, pricingTaxMode=${priced.pricingTaxMode}, destination=${priced.destination?.warehouseCode || 'PRIVATE_ADDRESS'}, attempts=${pricingResult.attempts}`
|
||||
: originPricingFailureEvidence(pricingResult),
|
||||
@@ -559,7 +651,8 @@ function buildPayload(context, profile, master, productImageFileId, customerAddr
|
||||
shipmentId: master.destinationType === 'FBA' ? shipmentId : null,
|
||||
referenceId: master.destinationType === 'FBA' ? referenceId : null,
|
||||
upcNo: `${Date.now()}${index}`.slice(-12),
|
||||
productImageFileId, dangerousGoods: false, woodenProduct: false,
|
||||
productImageFileId, dangerousGoods: orderProfile.dangerousGoods === true,
|
||||
woodenProduct: orderProfile.woodenProduct === true,
|
||||
cargoRemark: `${context.marker} boxType=${box.boxType}`,
|
||||
}
|
||||
}),
|
||||
@@ -673,6 +766,9 @@ export async function run(context) {
|
||||
const policy = context.apiSuccess(MODULE, 'Client 读取入仓码录入策略', policyResponse, '', CONTRACT_OPTIONS)
|
||||
const payload = { ...buildPayload(context, profile, master, productImageFileId, customerAddresses),
|
||||
...fixtureInboundCode(policy, context.marker) }
|
||||
if (context.context.verifyOrderCargoNegatives === true) {
|
||||
await verifyCargoRequirementNegatives(context, payload, policy, merchantToken)
|
||||
}
|
||||
const createResponse = await request(context, 'POST', createPath, payload, merchantToken,
|
||||
{ 'Idempotency-Key': key(context, 'client-create') })
|
||||
const orderId = id(context.apiSuccess(MODULE, 'Client 创建四箱型隔离订单', createResponse,
|
||||
@@ -910,19 +1006,5 @@ export async function run(context) {
|
||||
`taskId=${task.id}, total=${history?.total ?? 'n/a'}`)
|
||||
}
|
||||
|
||||
const acceptResponse = await context.request('POST',
|
||||
`/api/platform/v1/orders/first-leg/${encodeURIComponent(orderId)}/accept?tenantId=${encodeURIComponent(tenantId)}`)
|
||||
context.apiSuccess(MODULE, 'Admin 受理本轮订单', acceptResponse)
|
||||
const startResponse = await context.request('POST',
|
||||
`/api/platform/v1/orders/first-leg/${encodeURIComponent(orderId)}/start?tenantId=${encodeURIComponent(tenantId)}`)
|
||||
context.apiSuccess(MODULE, 'Admin 启动本轮订单履约', startResponse)
|
||||
|
||||
const finalDetailResponse = await context.request('GET', firstLegDetailPath)
|
||||
const finalDetail = context.apiSuccess(MODULE, 'Admin 回查本轮订单履约状态', finalDetailResponse)
|
||||
context.assert(MODULE, '本轮订单已进入处理中且保留状态历史', finalDetailResponse,
|
||||
finalDetail?.order?.status === 'PROCESSING'
|
||||
&& finalDetail?.statusHistory?.some?.((item) => item.toStatus === 'APPROVED')
|
||||
&& finalDetail?.statusHistory?.some?.((item) => item.toStatus === 'ACCEPTED')
|
||||
&& finalDetail?.statusHistory?.some?.((item) => item.toStatus === 'PROCESSING'),
|
||||
`status=${finalDetail?.order?.status ?? 'missing'}, history=${finalDetail?.statusHistory?.length ?? 'n/a'}`)
|
||||
await finishOrderLifecycle(context, { tenantId, orderId, firstLegDetailPath })
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user