import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync } from 'node:fs' import { resolve, dirname } from 'node:path' import { fileURLToPath } from 'node:url' import { createHash } from 'node:crypto' import { AcceptanceRun, parseArguments, validateExecutionOptions } from './admin-test-env-acceptance.mjs' import { createPicker, disablePicker, resolvePickerAccess } from './admin-test-env-modules/phase4-overseas-pda.mjs' import { pickAll, saveReceiptAndComplete, readAutomaticDispatchDelivery, verifyAutomaticDeparture, dispatchInventorySources, inventorySnapshot, dispatchStockConservation, inventorySourcesPresent, relabelOutboundMarksMatch } from './admin-test-env-modules/phase5-overseas-fulfillment.mjs' import { prepareFleetTrip, confirmFleetLoading, finishFleetTrip } from './admin-test-env-modules/fleet-trip-fulfillment.mjs' import { f40CreateDispatchBody } from './admin-test-env-modules/phase5-root-fulfillment.mjs' const LASTLEG = '/api/platform/v1/last-leg' const DISPATCH = `${LASTLEG}/dispatches` const PDA = '/api/pda/v1/last-leg/dispatches' const MODULE = 'native-retained-outbound' const id = value => { if (typeof value === 'number' && !Number.isSafeInteger(value)) throw Error('Retained ID lost precision') return value == null ? '' : String(value) } const validVersion = value => (typeof value === 'string' || typeof value === 'number' && Number.isSafeInteger(value)) && /^(0|[1-9]\d*)$/.test(String(value)) const success = response => response?.http === 200 && String(response?.body?.code) === '200' const same = (a, b) => JSON.stringify(a) === JSON.stringify(b) const requireFact = (condition, message) => { if (!condition) throw Error(message) } const sorted = values => [...new Set(values)].sort() const redact = value => { if (Array.isArray(value)) return value.map(redact) if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value) .filter(([name]) => !/password|token|authorization|base64|^bytes$|^content$/i.test(name)).map(([name, nested]) => [name, redact(nested)])) return value } export function saveRetainedReport(output, result) { mkdirSync(dirname(output), { recursive: true }) writeFileSync(`${output}.tmp`, `${JSON.stringify(redact({ ...result, updatedAt: new Date().toISOString() }), null, 2)}\n`) renameSync(`${output}.tmp`, output) } export function validateRetainedScope(result, scope) { requireFact(result.schema === 'NATIVE_RETAINED_OUTBOUND_V1' && result.environment === 'TEST' && result.marker === scope.marker && result.sourceSha256 === scope.sourceSha256 && result.customerId === id(scope.customerId) && result.warehouseId === id(scope.warehouseId) && same(result.inboundCodes, scope.inboundCodes), 'Retained native marker/source/customer/warehouse scope mismatch; legacy reports cannot resume') requireFact(!result.requests?.some(row => ['SENDING', 'UNKNOWN'].includes(row.status)), 'Unknown write outcome retained: reconcile the original command before further writes') } /** Every business write is saved before sending. Unknown results never generate another key. */ export function installRetainedJournal(context, result, save) { const request = context.request.bind(context), multipart = context.requestMultipart?.bind(context) context.createdResources = result.resources ||= {} context.createdResources.phase5OverseasFulfillment = result.fulfillment ||= {} context.context ||= {} context.context.phase5InventorySources = result.inventorySources || {} context.checkpoint = async () => { result.inventorySources = context.context.phase5InventorySources; await save() } result.requests ||= [] let stopped = false const execute = async (method, path, body, send, file) => { if (method === 'GET' || path.startsWith('/api/auth/')) return send() const cleanup = method === 'PUT' && /^\/api\/platform\/v1\/iam\/users\/[0-9]+\/status$/.test(path) && body?.status === 'DISABLED' if (stopped && !cleanup) throw Error('Unknown write outcome: retained business writes stopped') const entry = { method, path, ...(file ? { file } : { body: structuredClone(body) }), status: 'SENDING' } result.requests.push(entry); await context.checkpoint() let response try { response = await send() } catch (error) { stopped = true; entry.status = 'UNKNOWN'; await context.checkpoint(); throw error } const missingNativeResult = success(response) && (path.startsWith(DISPATCH) || path.startsWith(`${LASTLEG}/fleet/`)) && (response.body?.data == null || typeof response.body.data !== 'object') const known = response?.http > 0 && response.http < 500 && response.body?.code != null && !missingNativeResult Object.assign(entry, { status: known ? 'RESPONDED' : 'UNKNOWN', http: response?.http, code: response?.body?.code, requestId: response?.requestId, traceId: response?.traceId, result: response?.body?.data }) if (entry.status === 'UNKNOWN') stopped = true await context.checkpoint() if (stopped && !cleanup) throw Error('Unknown write outcome retained; no automatic retry or replacement command') return response } context.request = (method, path, body, token, headers) => execute(method, path, body, () => request(method, path, body, token, headers)) context.requestMultipart = (method, path, file, token, headers) => { const bytes = Buffer.from(file.content) return execute(method, path, undefined, () => multipart(method, path, file, token, headers), { name: file.name, type: file.type, size: bytes.length, sha256: createHash('sha256').update(bytes).digest('hex') }) } return context } export async function retainedApi(context, method, path, body, token) { const response = await context.request(method, path, body, token) const data = context.apiSuccess(MODULE, `${method} ${path}`, response, '', { deploymentBlocker: true }) if (!success(response)) throw Error(`Retained request failed: ${method} ${path}; HTTP=${response?.http}; code=${response?.body?.code}`) return data } function sourceKey(selection) { if (selection.sourceType === 'INBOUND_PLAN') return `PLAN:${id(selection.planPalletId)}` if (selection.sourceType === 'RELABEL') return `RELABEL:${selection.relabelNo}:${id(selection.warehousePalletId)}` throw Error('Unsupported retained source selector') } export function validateNativeRetainedDispatch(native, scope) { requireFact(native?.marker === scope.marker && native?.dispatchNo && native.createBody?.idempotencyKey && id(native.createBody.warehouseId) === id(scope.warehouseId) && native.createBody.cargo?.length && native.createResult?.dispatchNo === native.dispatchNo && native.createResult?.action === 'CREATE' && native.createResult?.status === 'ACTIVE' && native.createResult?.stage === 'BEFORE_LOADING' && validVersion(native.createResult.recordVersion) && native.detail?.dispatchNo === native.dispatchNo && native.detail.status === 'ACTIVE' && validVersion(native.detail.recordVersion) && Array.isArray(native.detail.orderDetails) && native.detail.orderDetails.length, 'Retained dispatch requires native Create command/result/Detail and the original marker; dispatch number alone is insufficient') const keys = native.createBody.cargo.map(sourceKey) requireFact(new Set(keys).size === keys.length && same(sorted(keys), sorted(native.detail.orderDetails.map(row => row.sourceKey))) && native.detail.orderDetails.every(row => id(row.customerId) === id(scope.customerId)), 'Retained native source/customer identity mismatch') requireFact(native.createBody.cargo.every(row => /^[a-f\d]{64}$/i.test(row.sourceVersion || '') && /^[1-9]\d*$/.test(id(row.warehousePalletId))), 'Retained selectors require original immutable source versions and physical pallet IDs') return keys } async function ledger(context, scope, dispatchNo) { const rows = []; let total for (let page = 1; page <= 50; page++) { const params = new URLSearchParams({ customerId: id(scope.customerId), businessType: 'SHIP', businessOrderNo: dispatchNo, outboundOrderNo: dispatchNo, page: String(page), size: '200' }) const data = await retainedApi(context, 'GET', `${LASTLEG}/inventory/ledger?${params}`) requireFact(Array.isArray(data.records) && Number.isSafeInteger(Number(data.total)), 'Incomplete retained SHIP ledger') total ??= Number(data.total); requireFact(Number(data.total) === total, 'Ledger snapshot changed during pagination') rows.push(...data.records) if (rows.length >= total) break requireFact(data.records.length > 0, 'Truncated retained SHIP ledger') } requireFact(rows.length === total && new Set(rows.map(row => id(row.id))).size === rows.length && rows.every(row => id(row.customerId) === id(scope.customerId) && row.businessType === 'SHIP' && row.businessOrderNo === dispatchNo && row.outboundOrderNo === dispatchNo), 'Foreign or duplicated retained shipment ledger') return rows.map(row => ({ ...row, id: id(row.id), customerId: id(row.customerId), overseasWarehouseId: id(row.overseasWarehouseId), beforePackages: Number(row.beforePackages), deltaPackages: Number(row.deltaPackages), afterPackages: Number(row.afterPackages) })).sort((a, b) => a.id.localeCompare(b.id)) } const NATIVE_DEPS = { prepareFleetTrip, pickAll, confirmFleetLoading, readAutomaticDispatchDelivery, verifyAutomaticDeparture, saveReceiptAndComplete, finishFleetTrip, inventorySnapshot, dispatchInventorySources, dispatchStockConservation, ledger } /** Continue only a proved native dispatch. All mutations remain real public API commands. */ export async function runRetainedNativeDispatch(context, result, scope, pdaToken, overrides = {}) { const deps = { ...NATIVE_DEPS, ...overrides } const api = (method, path, body, token) => retainedApi(context, method, path, body, token) validateRetainedScope(result, scope) context.marker = scope.marker let native = result.nativeDispatch requireFact(native, 'No original native dispatch proof; create it from current authoritative candidates first') const expectedSources = validateNativeRetainedDispatch(native, scope), dispatchNo = native.dispatchNo, path = `${DISPATCH}/${encodeURIComponent(dispatchNo)}` result.dispatchNo = dispatchNo let detail = await api('GET', path) const command = await api('GET', `${path}/commands/${encodeURIComponent(native.createBody.idempotencyKey)}`) requireFact(command.dispatchNo === dispatchNo && command.action === 'CREATE' && String(command.recordVersion) === String(native.createResult.recordVersion) && same(command.result, native.createResult.result), 'Original CREATE command is absent or differs from retained production receipt') const items = await api('GET', `${path}/items`) requireFact(detail.status === 'ACTIVE' && validVersion(detail.recordVersion) && same(sorted(items.items?.map(row => row.sourceKey) || []), sorted(expectedSources)) && items.items.every(row => id(row.customerId) === id(scope.customerId) && scope.inboundCodes.includes(row.inboundCode)) && Number(detail.counts?.packageCount) === scope.expectedPackages, 'Live native cargo/customer/quantity drift') const task = await api('GET', `${PDA}/${encodeURIComponent(dispatchNo)}`, undefined, pdaToken) requireFact(task.dispatchNo === dispatchNo && id(task.warehouseId) === id(scope.warehouseId) && Array.isArray(task.items) && task.items.length > 0 && (!scope.expectedPalletItems || task.items.length === scope.expectedPalletItems) && task.items.every(row => id(row.customerId) === id(scope.customerId)) && task.items.reduce((sum, row) => sum + Number(row.scheduledPackages), 0) === scope.expectedPackages, 'PDA warehouse/customer/item quantity differs from the original physical sources') const bol = await context.requestBinary('GET', `${path}/prints/bol`) requireFact(bol.http === 200 && /application\/pdf/i.test(bol.contentType || '') && Buffer.from(bol.bytes || []).subarray(0, 5).toString() === '%PDF-', 'BOL is not a real PDF') result.bol = { sha256: createHash('sha256').update(bol.bytes).digest('hex'), size: bol.bytes.length } let trip = result.tripProof || (context.createdResources.fleetTrips || []).find(row => row.dispatchNo === dispatchNo) if (detail.stage === 'BEFORE_LOADING') { requireFact(!trip, 'Partly prepared resources already exist; retain the original commands instead of creating replacements') trip = await deps.prepareFleetTrip(context, MODULE, dispatchNo, id(scope.warehouseId)); result.tripProof = trip detail = await api('GET', path) } requireFact(trip?.preparation === 'VERIFIED' && trip.dispatchNo === dispatchNo && id(trip.warehouseId) === id(scope.warehouseId), 'Native resource/review proof is missing or incomplete') result.tripProof = trip if (detail.stage === 'LOADING') { if (!result.inventory) { requireFact(Number(task.counts?.pickedPackageCount) === 0, 'Cannot reconstruct a pre-scan inventory baseline after physical scanning') const sources = deps.dispatchInventorySources(task, dispatchNo, id(scope.warehouseId)) requireFact(sources, 'Missing real PDA source positions/balances') result.inventory = { ...sources, stockBefore: await deps.inventorySnapshot(context, id(scope.customerId), id(scope.warehouseId), scope.inboundCodes, 'retained-before-pick', sources.sourceBalanceIds), ledgerBefore: await deps.ledger(context, scope, dispatchNo) } requireFact(result.inventory.stockBefore && inventorySourcesPresent(result.inventory.stockBefore, sources.sourcePositionBalances), 'Incomplete original pre-scan source inventory') await context.checkpoint() } requireFact(await deps.pickAll(context, pdaToken, dispatchNo), 'PDA real picks failed') result.inventory.stockAfter = await deps.inventorySnapshot(context, id(scope.customerId), id(scope.warehouseId), scope.inboundCodes, 'retained-after-pick', result.inventory.sourceBalanceIds) result.inventory.ledgerAfter = await deps.ledger(context, scope, dispatchNo) const conservation = deps.dispatchStockConservation(context, { ...result.inventory, customerId: id(scope.customerId), warehouseId: id(scope.warehouseId), dispatchNo, expectedPackages: scope.expectedPackages }) result.inventory.conservation = conservation.proof requireFact(conservation.passed, 'Physical inventory/SHIP conservation failed') await context.checkpoint() try { await deps.confirmFleetLoading(context, MODULE, trip, pdaToken, result.loadingMeasurements || native.loadingMeasurements) } catch (error) { const field = ['palletSpaces', 'weightKg', 'volumeCbm', 'deliveryIndex'].find(value => String(error?.message).includes(value)) result.blocker = { stage: 'LOADING_COMPLETE', code: field ? 'ACTUAL_MEASUREMENT_REQUIRED' : 'LOADING_COMPLETE_NOT_VERIFIED', field: field || null } await context.checkpoint(); throw error } detail = await api('GET', path) } requireFact(result.inventory?.conservation, 'No original scan quantity/ledger proof for this retained native dispatch') if (!result.transfer) { requireFact(detail.stage === 'DELIVERING', 'Current dispatch has no verified automatic departure') result.transfer = await deps.readAutomaticDispatchDelivery(context, dispatchNo) requireFact(result.transfer, 'Automatic delivery documents failed validation') await context.checkpoint() } const deliveries = result.transfer.deliveries, outbounds = result.transfer.outbounds requireFact(Array.isArray(deliveries) && deliveries.length > 0 && Array.isArray(outbounds) && outbounds.length === 1 && id(outbounds[0].customerId) === id(scope.customerId) && deliveries.every(row => id(row.customerId) === id(scope.customerId) && row.outboundNo === outbounds[0].outboundNo) && deliveries.reduce((sum, row) => sum + Number(row.packageCount), 0) === scope.expectedPackages, 'Native arrangement does not conserve the retained customer and physical quantity') result.outboundNo = outbounds[0].outboundNo; result.deliveryNos = deliveries.map(row => row.deliveryNo) if (!result.departure) { const verified = await deps.verifyAutomaticDeparture(context, pdaToken, dispatchNo) requireFact(verified, 'Automatic departure has not converged on both endpoints') result.departure = verified.data await context.checkpoint() } requireFact(result.departure?.dispatchNo === dispatchNo && result.departure.stage === 'DELIVERING' && result.departure.status === 'ACTIVE' && Number(result.departure.counts?.shippedPackageCount) === scope.expectedPackages && ['DELIVERING', 'DELIVERED'].includes(detail.stage), 'Original automatic departure evidence missing or invalid') result.inventory.stockAfterDepart = await deps.inventorySnapshot(context, id(scope.customerId), id(scope.warehouseId), scope.inboundCodes, 'retained-after-depart', result.inventory.sourceBalanceIds) result.inventory.ledgerAfterDepart = await deps.ledger(context, scope, dispatchNo) requireFact(same(result.inventory.stockAfter, result.inventory.stockAfterDepart) && same(result.inventory.ledgerAfter, result.inventory.ledgerAfterDepart), 'Departure repeated SHIP or changed inventory') result.outbound = await api('GET', `${LASTLEG}/outbound-orders/${encodeURIComponent(result.outboundNo)}`) requireFact(result.outbound.outbound?.status === 'OUTBOUNDED' && id(result.outbound.outbound.customerId) === id(scope.customerId) && result.outbound.outbound.outboundNo === result.outboundNo && result.outbound.outbound.dispatchNo === dispatchNo && Number(result.outbound.outbound.packageCount) === scope.expectedPackages, 'Actual retained outbound is not complete') if (scope.relabelCartonMarks) requireFact(relabelOutboundMarksMatch(result.outbound, scope), 'New relabel carton marks were lost, duplicated or replaced with old marks') result.deliveries ||= [] for (const [index, delivery] of deliveries.entries()) { const deliveryPath = `${LASTLEG}/delivery-orders/${encodeURIComponent(delivery.deliveryNo)}?customerId=${encodeURIComponent(scope.customerId)}` const current = await api('GET', deliveryPath) requireFact(id(current.order?.customerId) === id(scope.customerId) && current.order.outboundNo === result.outboundNo, 'Delivery customer/outbound identity drift') if (current.order.status !== 'DELIVERED') { requireFact(current.order.status === 'DELIVERING', 'Actual native departure must advance delivery before POD') requireFact(await deps.saveReceiptAndComplete(context, delivery, id(scope.customerId), index), 'POD did not automatically complete delivery') } else { const original = result.requests.find(row => row.method === 'PUT' && row.path === `${LASTLEG}/delivery-orders/${encodeURIComponent(delivery.deliveryNo)}/documents?customerId=${encodeURIComponent(scope.customerId)}` && row.status === 'RESPONDED' && row.result?.categories) requireFact(original, 'Already signed delivery has no original retained POD save command') const replay = await api('PUT', original.path, original.body) requireFact(replay.replayed === true && String(replay.version) === String(original.result.version), 'POD original-command replay drift') } const signed = await api('GET', deliveryPath) requireFact(signed.order?.status === 'DELIVERED' && signed.order.signedAt, 'Real signedAt/POD completion missing') result.deliveries[index] = signed; await context.checkpoint() } for (let attempt = 0; attempt < 30; attempt++) { detail = await api('GET', path) if (detail.stage === 'DELIVERED') break await new Promise(resolve => setTimeout(resolve, 1000)) } requireFact(detail.status === 'ACTIVE' && detail.stage === 'DELIVERED', 'All normal signatures did not aggregate native DELIVERED') result.nativeCompleted = detail if (!trip.closure) result.tripClosure = await deps.finishFleetTrip(context, MODULE, trip) else requireFact(trip.closure.status === 'VERIFIED', 'Retained resource closure is incomplete; preserve original release commands') result.finalStatus = 'PASSED'; await context.checkpoint(); return result } export async function mainMixed(args = process.argv.slice(2)) { const [sourcePath, containerNo, label, outputPath] = args requireFact(sourcePath && containerNo && ['MIXED-FBA', 'MIXED-DROPSHIP'].includes(label) && outputPath, 'Usage: source-report containerNo MIXED-FBA|MIXED-DROPSHIP output-report; explicit TEST authorization required') const options = parseArguments([]); options.modules = []; validateExecutionOptions(options) requireFact(options.environment === 'test', 'Retained mixed outbound only supports explicit TEST') const sourceBytes = readFileSync(resolve(sourcePath)), source = JSON.parse(sourceBytes) const record = source.createdResources?.dropshipPalletMatrix?.records?.find(row => row.label === label) requireFact(source.environment === 'test' && source.marker && record?.overseas?.plan?.containerNo === containerNo && record.overseas?.receiving?.receivingVerified, 'Explicit current TEST receipt source is required') const inboundCode = record.source.inboundCode, customerId = id(record.overseas.plan.customerId), warehouseId = id(record.overseas.warehouseId) const output = resolve(outputPath), scope = { marker: `${source.marker}-${label}`, customerId, warehouseId, inboundCodes: [inboundCode], expectedPackages: 4, sourceSha256: createHash('sha256').update(sourceBytes).digest('hex') } const result = existsSync(output) ? JSON.parse(readFileSync(output, 'utf8')) : { schema: 'NATIVE_RETAINED_OUTBOUND_V1', environment: 'TEST', ...scope, containerNo, inboundCode, label, source: resolve(sourcePath), requests: [] } validateRetainedScope(result, scope) const context = installRetainedJournal(new AcceptanceRun(options), result, () => saveRetainedReport(output, result)) context.marker = scope.marker const api = (method, path, body, token) => retainedApi(context, method, path, body, token) let picker try { const login = await context.request('POST', '/api/auth/login', { loginId: options.username, password: options.password }, '') requireFact(success(login) && login.body?.data?.accessToken, 'Unified platform login failed') context.token = login.body.data.accessToken if (label === 'MIXED-DROPSHIP') { const candidatePath = `${LASTLEG}/outbound-orders/one-click-dropship/candidates?customerId=${customerId}&warehouseId=${record.overseas.warehouseId}` if (!result.task) { const candidates = await api('GET', candidatePath) const selected = candidates.filter(row => row.inboundCode === inboundCode) const inputs = selected.map(row => { const pallet = record.task.pallets.find(p => p.palletNo === row.palletNo) const cartonMarks = (pallet?.cartons || []).filter(c => String(c.inboundId) === String(record.source.inboundId)).map(c => c.boxCode) if (cartonMarks.length !== Number(row.availablePackages) || row.customerId !== customerId) throw new Error('Dropship pallet scope mismatch') return { inventoryPositionId: row.inventoryPositionId, packages: cartonMarks.length, sku: row.skuCode, cartonMarks } }) if (inputs.length !== 2 || inputs.reduce((n, i) => n + i.packages, 0) !== 4) throw new Error('Expected exactly two pallets and four cartons') result.selected = selected result.inputs = inputs result.createBody = { recipient: `${context.marker} TEST ONLY`, items: inputs, remark: `${context.marker}-test-only`, idempotencyKey: `${context.marker}-OUTBOUND` } await context.checkpoint() result.task = await api('POST', `${LASTLEG}/outbound-orders/one-click-dropship?customerId=${customerId}`, result.createBody) await context.checkpoint() } const before = await api('GET', `${LASTLEG}/outbound-orders/${result.task.outboundNo}`) if (before.outbound.status !== 'OUTBOUNDED') { const access = await resolvePickerAccess(context) picker = access ? await createPicker(context, 9, record.overseas.warehouseId, access) : null if (!picker?.token) throw new Error('PDA operator unavailable') result.pickerMembershipId = picker.membershipId await context.checkpoint() const path = `/api/pda/v1/last-leg/outbound-tasks/${result.task.taskNo}` const detail = await api('GET', path, undefined, picker.token) for (const [index, candidate] of result.selected.entries()) { const item = detail.items.find(i => i.palletNo === candidate.palletNo && i.inboundCode === inboundCode && i.skuCode === candidate.skuCode) if (!item || !item.positions.some(p => String(p.inventoryPositionId) === String(candidate.inventoryPositionId))) throw new Error('PDA source position mismatch') const body = { outboundPalletItemId: item.outboundPalletItemId, inventoryPositionId: candidate.inventoryPositionId, packages: result.inputs[index].packages, cartonMarks: result.inputs[index].cartonMarks, idempotencyKey: `${context.marker}-PICK-${index}` } await api('POST', `${path}/pick`, body, picker.token) await api('POST', `${path}/pick`, body, picker.token) } result.completedTask = await api('POST', `${path}/complete`, { idempotencyKey: `${context.marker}-COMPLETE` }, picker.token) } result.outbound = await api('GET', `${LASTLEG}/outbound-orders/${result.task.outboundNo}`) result.remainingCandidates = (await api('GET', candidatePath)).filter(row => row.inboundCode === inboundCode) result.finalStatus = result.outbound.outbound.status === 'OUTBOUNDED' && result.remainingCandidates.length === 0 ? 'OUTBOUND_PASSED_DELIVERY_NOT_SUPPORTED' : 'OUTBOUND_FAILED' } else { if (!result.nativeDispatch) { const savedCreate = result.requests.find(row => row.method === 'POST' && row.path === DISPATCH && row.status === 'RESPONDED' && String(row.code) === '200' && row.result?.action === 'CREATE') if (savedCreate) { requireFact(result.createBody && same(savedCreate.body, result.createBody), 'Original native CREATE body must remain exact') const detail = await api('GET', `${DISPATCH}/${encodeURIComponent(savedCreate.result.dispatchNo)}`) result.nativeDispatch = { marker: scope.marker, dispatchNo: savedCreate.result.dispatchNo, createBody: savedCreate.body, createResult: savedCreate.result, detail, loadingMeasurements: record.loadingMeasurements } await context.checkpoint() } } if (!result.nativeDispatch) { const page = await api('GET', `${DISPATCH}/candidates?warehouseId=${warehouseId}`) const selected = page?.candidates?.filter(row => row.inboundCode === inboundCode && row.containerNo === containerNo && id(row.customerId) === customerId && id(row.warehouseId) === warehouseId) requireFact(page?.complete === true && selected?.length && selected.reduce((sum, row) => sum + Number(row.availablePackages), 0) === 4 && selected.every(row => row.destination?.warehouseCode === 'ABE4'), 'Live native FBA source/destination/quantity changed') result.createBody ||= f40CreateDispatchBody(scope.marker, warehouseId, customerId, selected) await context.checkpoint() const created = await api('POST', DISPATCH, result.createBody) const detail = await api('GET', `${DISPATCH}/${encodeURIComponent(created.dispatchNo)}`) result.nativeDispatch = { marker: scope.marker, dispatchNo: created.dispatchNo, createBody: result.createBody, createResult: created, detail, loadingMeasurements: record.loadingMeasurements } await context.checkpoint() } const access = await resolvePickerAccess(context) picker = access ? await createPicker(context, 8, warehouseId, access) : null requireFact(picker?.token, 'PDA operator unavailable') result.pickerMembershipId = picker.membershipId; await context.checkpoint() await runRetainedNativeDispatch(context, result, scope, picker.token) for (let attempt = 0; attempt < 30; attempt++) { const data = await api('GET', `/api/platform/inbounds?tenantId=${customerId}&inboundCode=${encodeURIComponent(inboundCode)}&page=1&size=20`) result.domesticInbound = data.records?.find(row => row.inboundCode === inboundCode) if (result.domesticInbound?.processStatus === 'DELIVERED') break await new Promise(resolve => setTimeout(resolve, 1000)) } requireFact(result.domesticInbound?.orderId, 'Missing original domestic source order') result.domesticOrder = (await api('GET', `/api/platform/v1/orders/${result.domesticInbound.orderId}?tenantId=${customerId}`))?.summary requireFact(result.domesticInbound.processStatus === 'DELIVERED' && result.domesticOrder?.mainStatus === 'COMPLETED', 'Domestic source completion callback did not converge') } } catch (error) { result.finalStatus = 'BLOCKED'; result.failedStage = result.nativeDispatch?.dispatchNo ? 'NATIVE_FULFILLMENT' : 'SOURCE_PREPARATION' process.exitCode = 1 } finally { try { await disablePicker(context, picker, label === 'MIXED-DROPSHIP' ? 9 : 8) } catch { result.finalStatus = 'CLEANUP_FAILED' } finally { if (picker) picker.token = null } result.checks = context.steps if (context.steps.some(step => step.passed === false)) result.finalStatus = 'CHECK_FAILED' if (result.finalStatus !== 'PASSED') process.exitCode = 1 await context.checkpoint() console.log(JSON.stringify({ containerNo, inboundCode, finalStatus: result.finalStatus, dispatchNo: result.dispatchNo, outboundNo: result.outboundNo || result.task?.outboundNo, deliveryNos: result.deliveryNos, report: output })) } return result } if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) await mainMixed()