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 }) } }) }