fix: complete single-order acceptance and add Lingxing WMS integration
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
import { resolve } from 'node:path'
|
||||
import { fingerprint, validateOrderInput } from './prod-release-order-stage.mjs'
|
||||
import { normalizeOrderInput } from './prod-release-measurement-plan.mjs'
|
||||
import { executeOrder } from './prod-release-acceptance.mjs'
|
||||
import { executeDelivery } from './prod-release-delivery-stage.mjs'
|
||||
import { createFlowContext, withFlowJournal, requireSuccess } from './prod-release-http.mjs'
|
||||
import { runDomesticStage } from './prod-release-domestic-stage.mjs'
|
||||
import { runOverseasStage, finishSingleOrderTrip } from './prod-release-overseas-stage.mjs'
|
||||
|
||||
export async function executeFullFlow(settings, rawInput, journalPath, resume = false, fetcher = fetch) {
|
||||
const input = { ...normalizeOrderInput(rawInput), customer: settings.customer,
|
||||
environment: { mode: settings.environment, apiBase: settings.apiBase },
|
||||
operator: { merchant: settings.credentials.MERCHANT.loginId, admin: settings.credentials.ADMIN.loginId } }
|
||||
validateOrderInput(input)
|
||||
const binding = fingerprint({ input, credentials: Object.fromEntries(Object.entries(settings.credentials).map(([key,value])=>[key,value.loginId])) })
|
||||
return withFlowJournal(journalPath, binding, async (state, save) => {
|
||||
if (state.status !== 'NEW' && !resume) throw Error('已有全流程检查点,须保持原输入并显式 --resume')
|
||||
const ctx = createFlowContext({ settings, state, save, fetcher })
|
||||
ctx.marker = input.batch
|
||||
const checkpoint = name => {
|
||||
const local = state.stages[name] || {}
|
||||
return { state: local, save: async patch => {
|
||||
const next = { ...local, ...patch }
|
||||
await save({ stages: { ...state.stages, [name]: next } }); Object.assign(local, next)
|
||||
} }
|
||||
}
|
||||
const bridgeFetch = async (url, options) => {
|
||||
if (!url.startsWith(settings.apiBase + '/')) throw Error('全流程请求偏离统一Gateway')
|
||||
const path = '/api' + url.slice(settings.apiBase.length)
|
||||
const token = options.headers?.Authorization?.replace(/^Bearer /, '')
|
||||
const body = options.body instanceof FormData ? options.body : options.body ? JSON.parse(options.body) : undefined
|
||||
const result = await ctx.requestWithBody(options.method, path, body, token ?? null,
|
||||
options.headers?.['Idempotency-Key'] ? { 'Idempotency-Key': options.headers['Idempotency-Key'] } : {})
|
||||
if (path === '/api/auth/login' && result.body?.code === '200') ctx.registerSession(result.body.data)
|
||||
return { ok: result.http >= 200 && result.http < 300, status: result.http, json: async () => result.body }
|
||||
}
|
||||
try {
|
||||
await save({ status: 'RUNNING' })
|
||||
ctx.token = (await ctx.login('ADMIN')).accessToken
|
||||
ctx.merchantToken = (await ctx.login('MERCHANT')).accessToken
|
||||
ctx.pdaToken = (await ctx.login('CN_PDA')).accessToken
|
||||
ctx.overseasPdaToken = (await ctx.login('OVERSEAS_LAX_PDA')).accessToken
|
||||
const binding = requireSuccess(await ctx.request('GET', '/api/pda/v1/last-leg/receiving-containers/warehouse-profile', undefined, ctx.overseasPdaToken))
|
||||
if (binding.warehouseCode !== 'US-LAX-01') throw Error('海外PDA不是洛杉矶接柜仓')
|
||||
const orderCp = checkpoint('order')
|
||||
let order = orderCp.state.result
|
||||
if (!order) {
|
||||
order = await executeOrder(settings, input, resolve(`${journalPath}.order.json`), resume, bridgeFetch)
|
||||
await orderCp.save({ result: order })
|
||||
}
|
||||
console.log('单票阶段:ORDER_APPROVED')
|
||||
const domesticCp = checkpoint('domestic')
|
||||
let domestic = domesticCp.state.result
|
||||
if (!domestic) {
|
||||
domestic = await runDomesticStage({ input, order, context: ctx, checkpoint: domesticCp })
|
||||
await domesticCp.save({ result: domestic })
|
||||
}
|
||||
console.log(`单票阶段:${domestic.status}`)
|
||||
const overseasCp = checkpoint('overseas')
|
||||
let overseas = overseasCp.state.result
|
||||
if (!overseas) {
|
||||
overseas = await runOverseasStage({ input: { ...input, customerId: settings.customer.id,
|
||||
packageCount: input.payload.cargoes[0].packageCount, warehouseId: binding.warehouseId },
|
||||
domestic: { ...domestic, sourceInboundId: domestic.inboundId, orderId: order.orderId, orderNo: order.orderNo, inboundCode: order.inboundCode },
|
||||
context: ctx, checkpoint: overseasCp })
|
||||
await overseasCp.save({ result: overseas })
|
||||
}
|
||||
console.log(`单票阶段:${overseas.status}`)
|
||||
const delivery = await executeDelivery(settings, { batch: input.batch, ...overseas,
|
||||
orderId: order.orderId, orderNo: order.orderNo, inboundCode: order.inboundCode,
|
||||
packageCount: input.payload.cargoes[0].packageCount, warehouseId: binding.warehouseId },
|
||||
resolve(`${journalPath}.delivery.json`), bridgeFetch)
|
||||
if (delivery.blockers.length) throw Error(`签收/回传未闭环:${delivery.blockers.join(',')}`)
|
||||
const tripClosure = await finishSingleOrderTrip({ input: { ...input, customerId: settings.customer.id }, overseas, context: ctx, checkpoint: checkpoint('tripClosure') })
|
||||
const result = { status: 'SINGLE_ORDER_HTTP_COMPLETED', fullFlowVerified: true,
|
||||
scope: 'ONE_ORDER_NORMAL_HTTP_PATH', orderNo: order.orderNo, inboundCode: order.inboundCode,
|
||||
containerNo: domestic.containerNo, dispatchNo: overseas.dispatchNo, deliveryNo: overseas.deliveryNo,
|
||||
domesticOrderStatus: delivery.domesticOrderStatus, domesticInboundStatus: delivery.domesticInboundStatus,
|
||||
tripClosureStatus: tripClosure.status,
|
||||
syntheticEvidence: true, excluded: ['真实货物及设备', '浏览器视觉', '故障注入', '多客户/拆单/异常分支', '真实付款/第三方下单/邮件'],
|
||||
completedAt: new Date().toISOString() }
|
||||
await save({ status: 'COMPLETED', result })
|
||||
return result
|
||||
} catch (error) {
|
||||
await save({ status: 'BLOCKED', failure: error.message })
|
||||
throw error
|
||||
} finally {
|
||||
const cleanup = await ctx.close()
|
||||
if (state.result) state.result.sessionCleanup = cleanup.length ? 'FAILED' : 'COMPLETED'
|
||||
await save({ cleanup })
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user