77 lines
5.1 KiB
JavaScript
77 lines
5.1 KiB
JavaScript
const MODULE = 'phase4-overseas-business'
|
|
const OPTIONS = { scenario: 'us-truck-fulfillment', deploymentBlocker: true }
|
|
|
|
/** 沿用本轮独立卡车原单,以正式履约命令完成实收、分配和运输。 */
|
|
export async function verifyUsTruckFulfillment(context, proof) {
|
|
if (!proof || proof.marker !== context.marker || !proof.orderNo || !proof.customerId
|
|
|| context.createdResources?.phase4OverseasBusiness?.truck !== proof || proof.fulfillment
|
|
|| proof.finalStatus !== 'WAITING_INBOUND') throw Error('卡车履约必须使用本轮未执行的待入仓原单')
|
|
const evidence = proof.fulfillment = { status: 'RUNNING', requests: [] }
|
|
const base = `/api/platform/v1/last-leg/us-truck-orders/${encodeURIComponent(proof.orderNo)}`
|
|
const suffix = `?customerId=${encodeURIComponent(proof.customerId)}`
|
|
const check = (name, ok) => {
|
|
context.assert(MODULE, name, null, Boolean(ok), `orderNo=${proof.orderNo}`, OPTIONS)
|
|
if (!ok) throw Error(name)
|
|
}
|
|
const request = async (method, action, body) => {
|
|
const log = { method, action, status: 'SENDING' }
|
|
evidence.requests.push(log)
|
|
const response = await context.request(method, `${base}${action}${suffix}`, body)
|
|
Object.assign(log, { http: response?.http, code: response?.body?.code,
|
|
requestId: response?.requestId, traceId: response?.traceId })
|
|
const ok = response?.http === 200 && String(response?.body?.code) === '200'
|
|
log.status = ok ? 'SUCCEEDED' : !response?.http || response.http >= 500 || !response.body ? 'UNKNOWN_RESULT' : 'REJECTED'
|
|
context.apiSuccess(MODULE, `本土卡车${method} ${action || '详情'}`, response,
|
|
`HTTP=${response?.http}; requestId=${response?.requestId}; traceId=${response?.traceId}`, OPTIONS)
|
|
if (!ok) throw Error(`卡车履约请求失败,不自动重发: ${action}`)
|
|
return response.body.data
|
|
}
|
|
const identity = detail => check('卡车原单客户和版本有效', detail?.summary?.orderNo === proof.orderNo
|
|
&& String(detail.summary.customerId) === String(proof.customerId)
|
|
&& Number.isSafeInteger(detail.summary.version) && detail.summary.version >= 0)
|
|
try {
|
|
let current = await request('GET', '')
|
|
identity(current)
|
|
check('卡车实收前仍为本轮待入仓单', current.summary.status === 'WAITING_INBOUND'
|
|
&& current.remark?.startsWith(context.marker) && Number(current.summary.receivedPalletCount) === 0)
|
|
const packages = Number(current.summary.packageCount)
|
|
check('卡车实收箱数来自原订单且为有效正整数', Number.isSafeInteger(packages) && packages > 0)
|
|
const stages = [
|
|
['receive-pallets', 'WAITING_CONSOLIDATION'], ['allocate-pallets', 'ALLOCATED'],
|
|
['prepare-dispatch', 'PENDING_DISPATCH'], ['dispatch', 'DISPATCHED'],
|
|
['start-transit', 'IN_TRANSIT'], ['complete', 'COMPLETED'],
|
|
]
|
|
let palletId, palletNo
|
|
for (const [action, state] of stages) {
|
|
const beforeVersion = current.summary.version
|
|
const body = { version: beforeVersion, idempotencyKey: `${context.marker}-truck-${action}`, remark: context.marker }
|
|
if (action === 'receive-pallets') Object.assign(body, { actualPalletCount: 1, packageCount: packages, palletPackageCounts: [packages] })
|
|
if (action === 'allocate-pallets') body.palletIds = [palletId]
|
|
const next = await request('POST', `/${action}`, body)
|
|
identity(next)
|
|
check(`卡车${action}状态和版本正确`, next.summary.status === state && next.summary.version === beforeVersion + 1)
|
|
check('卡车原单实收托盘及箱数守恒', next.pallets?.length === 1 && Number(next.summary.receivedPalletCount) === 1
|
|
&& Number(next.pallets[0].packageCount) === packages && /^[1-9]\d*$/.test(String(next.pallets[0].id))
|
|
&& typeof next.pallets[0].palletNo === 'string' && next.pallets[0].palletNo.startsWith(`${proof.orderNo}-P`))
|
|
palletId ??= next.pallets[0].id
|
|
palletNo ??= next.pallets[0].palletNo
|
|
check('卡车履约复用原实收托盘', String(next.pallets[0].id) === String(palletId) && next.pallets[0].palletNo === palletNo
|
|
&& next.pallets[0].status === (action === 'receive-pallets' ? 'RECEIVED' : 'ALLOCATED')
|
|
&& Number(next.summary.allocatedPalletCount) === (action === 'receive-pallets' ? 0 : 1))
|
|
check('卡车履约写入对应操作历史', next.histories?.some(row => row.idempotencyKey === body.idempotencyKey
|
|
&& row.toStatus === state && row.fromStatus === current.summary.status))
|
|
const replay = await request('POST', `/${action}`, body)
|
|
check('卡车同键重放不重复收托或推进版本', JSON.stringify(replay) === JSON.stringify(next))
|
|
current = await request('GET', '')
|
|
check('卡车履约状态已持久化且回查一致', JSON.stringify(current) === JSON.stringify(next))
|
|
}
|
|
Object.assign(proof, { finalStatus: 'COMPLETED', version: current.summary.version, retained: true })
|
|
Object.assign(evidence, { status: 'HTTP_VERIFIED', palletId, palletNo, packages })
|
|
} catch (error) {
|
|
for (const log of evidence.requests) if (log.status === 'SENDING') log.status = 'UNKNOWN_RESULT'
|
|
evidence.status = 'FAILED'
|
|
evidence.failure = error.message
|
|
throw error
|
|
}
|
|
}
|