2026-10-20 10:35:52 +08:00
import { readFileSync , existsSync } from 'node:fs'
import { resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
import { createHash } from 'node:crypto'
import { AcceptanceRun , parseArguments , validateExecutionOptions } from './admin-test-env-acceptance.mjs'
import { installRetainedJournal , retainedApi , saveRetainedReport } from './mixed-container-outbound-test-env.mjs'
2026-09-14 09:58:30 +08:00
2026-10-20 10:35:52 +08:00
const MODULE = 'overseas-relabel-producer'
const DISPATCH = '/api/platform/v1/last-leg/dispatches'
const id = value => {
if ( typeof value === 'number' && ! Number . isSafeInteger ( value )) throw Error ( 'Relabel ID lost precision' )
return value == null ? '' : String ( value )
2026-09-14 09:58:30 +08:00
}
2026-10-20 10:35:52 +08:00
const exactVersion = value => {
if (( typeof value === 'number' && ! Number . isSafeInteger ( value )) || ! /^(0|[1-9]\d*)$/ . test ( String ( value ))) throw Error ( 'Authoritative relabel version is missing or imprecise' )
return value
}
const quantity = value => /^ ( 0 | [ 1 - 9 ] \ d * ) $ / . test ( String ( value )) && Number . isSafeInteger ( Number ( value )) ? Number ( value ) : null
const sorted = values => [... new Set ( values )]. sort ()
const same = ( a , b ) => JSON . stringify ( a ) === JSON . stringify ( b )
const canonical = value => JSON . stringify ( normalize ( value ))
function normalize ( value ) {
if ( Array . isArray ( value )) return value . map ( normalize )
return value && typeof value === 'object' ? Object . fromEntries ( Object . keys ( value ). sort (). map ( key => [ key , normalize ( value [ key ])])) : value
}
function requireFact ( condition , message ) { if ( ! condition ) throw Error ( message ) }
function producerResponsePresent ( path , data ) {
if ( data == null || typeof data !== 'object' ) return false
if ( path === DISPATCH ) return data . dispatchNo && data . action === 'CREATE' && data . recordVersion != null && data . result ? . dispatchNo === data . dispatchNo
if ( path . endsWith ( '/photos/files' )) return /^[1-9]\d*$/ . test ( id ( data . fileId ))
if ( path . endsWith ( '/pallets/resolve' )) return data . palletCode && /^[1-9]\d*$/ . test ( id ( data . palletId )) && data . totalPackages != null
if ( path . endsWith ( '/relabel-pallet-label' )) return data . palletCode && /^[1-9]\d*$/ . test ( id ( data . palletId ))
if ( path . includes ( '/relabel-tasks/' )) return data . relabelNo && data . version != null && Array . isArray ( data . items )
if ( path . includes ( '/relabel-applications' )) return data . application ? . relabelNo && Array . isArray ( data . items )
return true
}
export function installRelabelJournal ( context , report , save ) {
installRetainedJournal ( context , report , save )
const request = context . request . bind ( context ), upload = context . requestMultipart . bind ( context )
const verify = async ( method , path , send ) => {
if ( method !== 'GET' && report . requests . some ( row => [ 'SENDING' , 'UNKNOWN' ]. includes ( row . status ))) throw Error ( 'Unknown producer write outcome: stop with original commands' )
const response = await send ()
if ( method !== 'GET' && response . http === 200 && String ( response . body ? . code ) === '200'
&& ! producerResponsePresent ( path , response . body ? . data )) {
const entry = report . requests . at ( - 1 )
if ( entry ) entry . status = 'UNKNOWN'
await context . checkpoint (); throw Error ( 'Unknown producer write result: native business response missing' )
}
return response
2026-09-14 09:58:30 +08:00
}
2026-10-20 10:35:52 +08:00
context . request = ( method , path , body , token , headers ) => verify ( method , path , () => request ( method , path , body , token , headers ))
context . requestMultipart = ( method , path , file , token , headers ) => verify ( method , path , () => upload ( method , path , file , token , headers ))
return context
}
function validateLoadingEvidence ( value , marker , dispatchNo , targets ) {
const physicalIds = sorted ( targets . map ( row => id ( row . palletId )))
requireFact ( value ? . environment === 'TEST' && value . marker === marker && typeof value . dispatchNo === 'string' && value . dispatchNo . trim () && value . dispatchNo === dispatchNo && Array . isArray ( value . pallets )
&& same ( sorted ( value . pallets . map ( row => id ( row . warehousePalletId ))), physicalIds )
&& value . pallets . length === physicalIds . length , 'Loading evidence belongs to another marker/dispatch/physical pallet' )
for ( const row of value . pallets ) {
requireFact ( Number . isSafeInteger ( row . palletSpaces ) && row . palletSpaces > 0 && Number . isSafeInteger ( row . deliveryIndex ) && row . deliveryIndex > 0 ,
'Actual pallet spaces and physical stop index are required' )
for ( const field of [ 'palletSpaces' , 'weightKg' , 'volumeCbm' ]) {
requireFact ( /^(0|[1-9]\d*)(?:\.\d+)?$/ . test ( String ( row [ field ])) && Number ( row [ field ]) > 0
&& row . evidence ? . some ( e => e . actual === true && e . field === field && e . sourceType && e . sourceBusinessNo
&& Number . isFinite ( Date . parse ( e . observedAt )) && Number ( e . value ) === Number ( row [ field ])), 'Actual physical loading field evidence is missing' )
}
}
return value
}
export function selectExplicitRelabelSource ( candidates , config ) {
requireFact ( config . sourceBalanceId && config . sourcePositionId , 'Explicit source balance and position are required; no historical inventory fallback' )
const matches = candidates . filter ( row => id ( row . inventoryBalanceId ) === id ( config . sourceBalanceId )
&& id ( row . overseasWarehouseId ) === id ( config . warehouseId )
&& ( ! config . inboundCode || row . inboundCode === config . inboundCode )
&& row . positions ? . some ( position => id ( position . positionId ) === id ( config . sourcePositionId )))
requireFact ( matches . length === 1 , 'Explicit relabel source is absent or ambiguous in this customer warehouse' )
const source = matches [ 0 ], positions = source . positions . filter ( row => id ( row . positionId ) === id ( config . sourcePositionId ))
requireFact ( positions . length === 1 && String ( source . inboundOrderNo || '' ). startsWith ( 'DS-' ) && source . destinationFbaWarehouseCode
&& quantity ( source . availablePackages ) >= 5 && quantity ( positions [ 0 ]. availablePackages ) >= 5 , 'Explicit source requires original DS inventory and five real available cartons' )
return { source , sourcePosition : positions [ 0 ] }
}
function scenarioCargo ( scenario , rows , customerId , warehouseId ) {
const targets = sorted ( scenario . targets . map ( target => id ( target . palletId )))
const expected = scenario . items . reduce (( sum , item ) => sum + Number ( item . notifiedPackageCount ), 0 )
requireFact ( rows . length === targets . length && same ( sorted ( rows . map ( row => id ( row . selection ? . warehousePalletId ))), targets )
&& rows . reduce (( sum , row ) => sum + Number ( row . availablePackages ), 0 ) === expected , 'Native relabel candidates do not exactly cover original targets and carton quantity' )
const cargo = rows . map ( row => {
const selection = row . selection
requireFact ( selection ? . sourceType === 'RELABEL' && selection . relabelNo === scenario . relabelNo
&& selection . planPalletId == null && selection . returnReceiptId == null
&& /^[1-9]\d*$/ . test ( id ( selection . warehousePalletId ))
&& row . sourceKey === `RELABEL: ${ scenario . relabelNo } : ${ id ( selection . warehousePalletId ) } `
&& /^[a-f\d]{64}$/i . test ( row . sourceVersion || '' ) && row . sourceVersion === selection . sourceVersion
&& id ( row . customerId ) === id ( customerId ) && id ( row . warehouseId ) === id ( warehouseId )
&& quantity ( row . availablePackages ) > 0 && row . destination ? . type === 'FBA'
&& row . destination . warehouseCode === scenario . destinationFbaWarehouseCode
&& Array . isArray ( row . blockReasons ) && ! row . blockReasons . length && Array . isArray ( row . appointmentBatches ) && ! row . appointmentBatches . length ,
'Incomplete native RELABEL selector, warehouse/customer/destination or source version' )
requireFact ( ! Object . hasOwn ( row , 'cartonMarks' ) && ! Object . hasOwn ( row , 'relabelCartonMarks' ), 'Native candidates must not expose legacy carton mark projections' )
return { sourceType : 'RELABEL' , planPalletId : null , returnReceiptId : null , relabelNo : scenario . relabelNo ,
warehousePalletId : id ( selection . warehousePalletId ), sourceVersion : selection . sourceVersion }
})
return cargo
}
/** The complete server group is the selection unit, including the whole application closure. */
export function nativeRelabelCreateBody ( marker , scenarios , rows , customerId , warehouseId ) {
requireFact ( Array . isArray ( scenarios ) && scenarios . length > 0 && rows . length > 0
&& new Set ( scenarios . map ( s => s . relabelNo )). size === scenarios . length , 'Owned relabel scenarios are missing or duplicate' )
const group = rows [ 0 ]. selectionGroup
requireFact ( typeof group ? . groupKey === 'string' && group . groupKey . trim () && typeof group . groupVersion === 'string' && group . groupVersion . trim ()
&& group . complete === true && Array . isArray ( group . blockReasons ) && ! group . blockReasons . length
&& Array . isArray ( group . sources ) && group . sources . length && Array . isArray ( group . pallets ) && group . pallets . length ,
'Authoritative complete selectionGroup and groupVersion are required' )
requireFact ( rows . every ( row => canonical ( row . selectionGroup ) === canonical ( group )), 'Selection group version or membership drift' )
requireFact ( new Set ( rows . map ( row => row . sourceKey )). size === rows . length && group . sources . length === rows . length
&& new Set ( group . sources . map ( source => source . sourceKey )). size === group . sources . length
&& same ( sorted ( group . sources . map ( source => source . sourceKey )), sorted ( rows . map ( row => row . sourceKey ))), 'Selection group logical sources are incomplete or duplicate' )
const cargo = scenarios . flatMap ( scenario => scenarioCargo ( scenario , rows . filter ( row => row . selection ? . relabelNo === scenario . relabelNo ), customerId , warehouseId ))
requireFact ( cargo . length === rows . length , 'Selection group contains an unknown relabel business member' )
for ( const source of group . sources ) {
const row = rows . find ( value => value . sourceKey === source . sourceKey ), scenario = scenarios . find ( value => value . relabelNo === source . sourceBusinessNo )
requireFact ( scenario ? . relabelVerified === true && row && source . sourceBusinessType && id ( source . customerId ) === id ( customerId )
&& source . sourceBusinessNo === row . selection . relabelNo && canonical ( source . selection ) === canonical ( row . selection )
&& quantity ( source . packages ) > 0 && quantity ( source . packages ) === quantity ( row . availablePackages ), 'Selection group business, selector version or source quantity drift' )
}
requireFact ( new Set ( group . pallets . map ( p => id ( p . warehousePalletId ))). size === group . pallets . length
&& same ( sorted ( group . pallets . map ( p => id ( p . warehousePalletId ))), sorted ( cargo . map ( c => id ( c . warehousePalletId )))), 'Selection group physical pallet closure is incomplete or duplicate' )
for ( const pallet of group . pallets ) {
const sources = group . sources . filter ( source => id ( source . selection . warehousePalletId ) === id ( pallet . warehousePalletId ))
requireFact ( /^[1-9]\d*$/ . test ( id ( pallet . warehousePalletId )) && pallet . palletNo && sources . length
&& Array . isArray ( pallet . sourceKeys ) && pallet . sourceKeys . length === sources . length
&& same ( sorted ( pallet . sourceKeys ), sorted ( sources . map ( source => source . sourceKey )))
&& quantity ( pallet . packageCount ) === sources . reduce (( sum , source ) => sum + quantity ( source . packages ), 0 )
&& rows . filter ( row => id ( row . selection . warehousePalletId ) === id ( pallet . warehousePalletId )). every ( row => row . palletNo === pallet . palletNo ),
'Selection group physical source contributions or carton quantity drift' )
}
return { idempotencyKey : createHash ( 'sha256' ). update ( ` ${ marker } \n ${ group . groupKey } \n ${ group . groupVersion } \nCREATE` ). digest ( 'hex' ), warehouseId : id ( warehouseId ),
vehicle : null , driver : null , plannedDepartureAt : null , estimatedArrivalAt : null , remark : ` ${ marker } - ${ scenarios . map ( s => s . name ). join ( '+' ) } ` , cargo , batchAllocations : [] }
}
export function nativeRelabelGroups ( scenarios , rows , customerId , warehouseId ) {
const groups = new Map ()
for ( const scenario of scenarios ) {
requireFact ( scenario . relabelVerified === true && scenario . relabelCartonMarks ? . length === scenario . expectedCartons
&& new Set ( scenario . relabelCartonMarks ). size === scenario . relabelCartonMarks . length
&& scenario . relabelCartonMarks . every ( mark => typeof mark === 'string' && mark . trim () && ! scenario . sourceCartonMarks . includes ( mark )), 'Original/new relabel carton mark evidence is incomplete or overlaps' )
const candidates = rows . filter ( row => row . selection ? . sourceType === 'RELABEL' && row . selection . relabelNo === scenario . relabelNo )
scenarioCargo ( scenario , candidates , customerId , warehouseId )
requireFact ( candidates . every ( row => row . selectionGroup ? . groupKey ) && new Set ( candidates . map ( row => row . selectionGroup . groupKey )). size === 1 ,
'Whole relabel application must belong to one authoritative complete selection group' )
const key = candidates [ 0 ]. selectionGroup . groupKey
if ( ! groups . has ( key )) groups . set ( key , { scenarios : [], selectionGroup : candidates [ 0 ]. selectionGroup })
groups . get ( key ). scenarios . push ( scenario )
scenario . dispatchCandidates = candidates
}
const physicalOwners = new Map (), result = []
for ( const entry of groups . values ()) {
const keys = entry . selectionGroup . sources ? . map ( source => source . sourceKey ) || []
entry . candidates = rows . filter ( row => keys . includes ( row . sourceKey ))
entry . createBody = nativeRelabelCreateBody ( 'PREFLIGHT' , entry . scenarios , entry . candidates , customerId , warehouseId )
for ( const pallet of entry . selectionGroup . pallets ) {
const key = id ( pallet . warehousePalletId )
requireFact ( ! physicalOwners . has ( key ), 'One physical pallet cannot be split across selection groups' )
physicalOwners . set ( key , entry . selectionGroup . groupKey )
}
result . push ( entry )
}
return result
}
// Compare physical values once, retaining per-scenario raw receipts without adding whole-pallet weights.
export function relabelGroupMeasurements ( scenarios , group ) {
const values = new Map (), fields = [ 'palletCode' , 'packages' , 'stockVersion' , 'lengthCm' , 'widthCm' , 'heightCm' , 'grossWeightKg' , 'volumeCbm' ]
for ( const scenario of scenarios ) for ( const measured of scenario . physicalMeasurements || []) {
const palletId = id ( measured . palletId ), pallet = group . pallets . find ( p => id ( p . warehousePalletId ) === palletId )
if ( ! pallet ) continue
requireFact ( measured . palletCode === pallet . palletNo && quantity ( measured . packages ) === quantity ( pallet . packageCount ), 'Physical measurement does not cover the complete shared pallet' )
const old = values . get ( palletId )
if ( old ) requireFact ( fields . every ( field => canonical ( old [ field ]) === canonical ( measured [ field ])), 'Shared physical measurement versions or values conflict' )
else values . set ( palletId , structuredClone ( measured ))
}
return [... values . values ()]
}
function groupLoadingEvidence ( config , report , scenarios , group , dispatchNo ) {
const targets = group . pallets . map ( p => ({ palletId : id ( p . warehousePalletId ) })), inputs = scenarios . map ( s => config . loadingEvidenceByScenario ? .[ s . name ]). filter ( Boolean )
if ( ! inputs . length ) return undefined
const validated = inputs . map ( value => validateLoadingEvidence ( value , report . marker , dispatchNo || value . dispatchNo , targets ))
const physical = value => [... value . pallets ]. sort (( a , b ) => id ( a . warehousePalletId ). localeCompare ( id ( b . warehousePalletId ))). map ( p => ({ warehousePalletId : id ( p . warehousePalletId ),
palletSpaces : p . palletSpaces , weightKg : String ( Number ( p . weightKg )), volumeCbm : String ( Number ( p . volumeCbm )), deliveryIndex : p . deliveryIndex }))
requireFact ( validated . every ( value => value . dispatchNo === validated [ 0 ]. dispatchNo && canonical ( physical ( value )) === canonical ( physical ( validated [ 0 ]))), 'Shared scenario loading receipts must name the same dispatch and identical physical measurements' )
return structuredClone ( validated [ 0 ])
}
/** Read the complete candidate universe and validate every owned group before the first CREATE. */
export async function publishNativeRelabelDispatches ( context , report , config ) {
const scenarios = report . scenarios . filter ( row => row . operationType === 'CARTON_AND_SKU' )
report . blockers ||= []
const block = ( scenario , code , evidence ) => {
scenario . dispatchStatus = 'BLOCKED' ; scenario . nativeDispatchBlocker = { code , ... evidence }
report . blockers . push ({ scenario : scenario . name , code , ... evidence })
context . addCoverageGap ? .( MODULE , scenario . name , 'Native relabel dispatch' , code , 'physical-source-blocked' , { deploymentBlocker : true })
context . addStep ? .( MODULE , ` ${ scenario . name } native dispatch blocked` , null , false , code , { deploymentBlocker : true })
}
const page = await retainedApi ( context , 'GET' , ` ${ DISPATCH } /candidates? ${ new URLSearchParams ({ warehouseId : id ( config . warehouseId ), sourceStage : 'RELABEL' } )}` )
requireFact ( page ? . complete === true && Array . isArray ( page . candidates ), 'Native candidate list is incomplete' )
for ( const scenario of scenarios ) {
scenario . sourceAfter = Number (( await retainedApi ( context , 'POST' , '/api/pda/v1/last-leg/pallet-operations/pallets/resolve' , { code : report . sourcePosition . palletNo }, config . pdaToken )). totalPackages )
scenario . targetPallets = scenario . targets . map ( target => ({ palletId : id ( target . palletId ), palletCode : target . palletCode }))
scenario . physicalMeasurements = await retainedApi ( context , 'POST' , '/api/pda/v1/last-leg/pallet-operations/relabel-pallet-measurements' , { relabelNo : scenario . relabelNo }, config . pdaToken )
await context . checkpoint ()
}
let groups
try {
groups = nativeRelabelGroups ( scenarios , page . candidates , config . customerId , config . warehouseId )
for ( const group of groups ) {
group . physicalMeasurements = relabelGroupMeasurements ( group . scenarios , group . selectionGroup )
groupLoadingEvidence ( config , report , group . scenarios , group . selectionGroup )
}
} catch ( error ) {
report . nativeSelectionFailure = { message : error . message , candidates : page . candidates }
for ( const scenario of scenarios ) block ( scenario , 'NATIVE_SELECTION_GROUP_NOT_VERIFIED' , { reason : error . message })
await context . checkpoint (); throw error
}
for ( const group of groups ) {
const members = group . scenarios
try {
const createBody = nativeRelabelCreateBody ( report . marker , members , group . candidates , config . customerId , config . warehouseId )
const native = { marker : report . marker , customerId : id ( config . customerId ), warehouseId : id ( config . warehouseId ), createBody , physicalMeasurements : group . physicalMeasurements }
for ( const scenario of members ) {
scenario . nativeDispatch = native ; scenario . nativeCreateBody = createBody ; scenario . selectionGroup = group . selectionGroup
scenario . sourceContributions = group . selectionGroup . sources . filter ( source => source . sourceBusinessNo === scenario . relabelNo )
}
await context . checkpoint ()
const createResult = await retainedApi ( context , 'POST' , DISPATCH , createBody )
Object . assign ( native , { createResult , dispatchNo : createResult ? . dispatchNo }); await context . checkpoint ()
requireFact ( createResult ? . action === 'CREATE' && createResult . dispatchNo && createResult . status === 'ACTIVE'
&& createResult . stage === 'BEFORE_LOADING' , 'Native CREATE did not return original active vehicle identity' )
exactVersion ( createResult . recordVersion )
const replay = await retainedApi ( context , 'POST' , DISPATCH , createBody )
native . createReplay = replay ; await context . checkpoint ()
requireFact ( replay . replayed === true && replay . dispatchNo === createResult . dispatchNo
&& String ( replay . recordVersion ) === String ( createResult . recordVersion ) && same ( replay . result , createResult . result ), 'Native CREATE replay changed original result' )
const detail = await retainedApi ( context , 'GET' , ` ${ DISPATCH } / ${ encodeURIComponent ( createResult . dispatchNo ) } ` )
const sourceKeys = group . candidates . map ( row => row . sourceKey )
requireFact ( detail ? . dispatchNo === createResult . dispatchNo && detail . status === 'ACTIVE' && detail . stage === 'BEFORE_LOADING'
&& quantity ( detail . counts ? . packageCount ) === group . selectionGroup . sources . reduce (( sum , source ) => sum + quantity ( source . packages ), 0 )
&& quantity ( detail . counts ? . palletCount ) === group . selectionGroup . pallets . length
&& id ( exactVersion ( detail . recordVersion )) === id ( createResult . recordVersion )
&& detail . orderDetails ? . length >= sourceKeys . length
&& same ( sorted ( detail . orderDetails ? . map ( row => row . sourceKey ) || []), sorted ( sourceKeys ))
&& detail . orderDetails . every ( row => group . selectionGroup . sources . some ( source => source . sourceKey === row . sourceKey && id ( source . customerId ) === id ( row . customerId )
&& source . sourceBusinessNo === row . sourceBusinessNo && source . sourceBusinessType === row . sourceBusinessType )), 'Native Detail source/business/customer/carton identity drift' )
native . detail = detail
const measurements = groupLoadingEvidence ( config , report , members , group . selectionGroup , createResult . dispatchNo )
if ( measurements ) native . loadingMeasurements = measurements
for ( const scenario of members ) {
scenario . dispatchNo = createResult . dispatchNo ; scenario . dispatchStatus = detail . status
if ( ! measurements ) block ( scenario , 'ACTUAL_LOADING_MEASUREMENT_REQUIRED' , { fields : [ 'palletSpaces' , 'weightKg' , 'volumeCbm' , 'deliveryIndex' ],
dispatchNo : createResult . dispatchNo , evidenceKind : 'PHYSICAL_MEASUREMENTS_NOT_FABRICATED' })
}
await context . checkpoint ()
} catch ( error ) {
if ( report . requests ? . some ( row => [ 'SENDING' , 'UNKNOWN' ]. includes ( row . status ))) throw error
for ( const scenario of members ) block ( scenario , 'NATIVE_DISPATCH_NOT_VERIFIED' , { candidates : scenario . dispatchCandidates , sourceBeforePackages : scenario . sourceBefore ,
sourceAfterPackages : scenario . sourceAfter , reason : error . message , evidenceKind : 'RETAINED_ORIGINAL_REQUEST_AND_RESPONSE' })
await context . checkpoint ()
}
}
return scenarios
2026-09-14 09:58:30 +08:00
}
async function poll ( action , predicate , attempts = 40 ) {
for ( let index = 0 ; index < attempts ; index += 1 ) {
const value = await action ()
if ( predicate ( value )) return value
await new Promise (( resolve ) => setTimeout ( resolve , 500 ))
}
throw new Error ( 'Polling timed out' )
}
function assert ( condition , message ) { if ( ! condition ) throw new Error ( message ) }
2026-10-20 10:35:52 +08:00
export async function runRelabelProducer ( context , config , report ) {
const { customerId , warehouseId , merchantToken , pdaToken , chargeAmount } = config
const platformToken = context . token , marker = report . marker
const key = value => ` ${ marker } - ${ value } ` . slice ( 0 , 200 )
const api = ( token , method , path , body ) => retainedApi ( context , method , path , body , token )
const candidates = await api ( merchantToken , 'GET' , `/api/merchant/v1/last-leg/relabel-applications/inventory-candidates?operationWarehouseId= ${ warehouseId } ` )
const { source , sourcePosition } = selectExplicitRelabelSource ( candidates , config )
report . source = source ; report . sourcePosition = sourcePosition
await context . checkpoint ()
async function resolvePallet ( palletCode ) {
return api ( pdaToken , 'POST' , '/api/pda/v1/last-leg/pallet-operations/pallets/resolve' , { code : palletCode })
}
2026-09-14 09:58:30 +08:00
2026-10-20 10:35:52 +08:00
async function createAndConfirm ( name , cartonCount , operationType = 'CARTON_AND_SKU' , prepare = null ) {
const suffix = ` ${ name } - ${ marker } `
const items = Array . from ({ length : cartonCount }, ( _ , index ) => ({
sourceInventoryBalanceId : id ( source . inventoryBalanceId ), sourceInventoryPositionId : id ( sourcePosition . positionId ),
sourceInboundOrderId : id ( source . inboundOrderId ), skuCode : source . skuCode ,
productNameZh : source . productNameZh || source . skuCode , totalPackageCount : 1 , notifiedPackageCount : 1 ,
shipmentNo : `SHIP- ${ suffix } - ${ index + 1 } ` , newSkuCode : ` ${ source . skuCode } -NEW- ${ suffix } - ${ index + 1 } ` . slice ( 0 , 128 ),
labelRequirement : marker , cartonMark : `CM- ${ suffix } - ${ index + 1 } ` ,
fbaCartonMark : operationType === 'CARTON_AND_SKU' ? `FBA- ${ suffix } - ${ index + 1 } ` : null ,
destinationFbaWarehouseCode : source . destinationFbaWarehouseCode ,
operationType , skuItems : [{ skuCode : source . skuCode ,
newSkuCode : ` ${ source . skuCode } -NEW- ${ suffix } - ${ index + 1 } ` . slice ( 0 , 128 ), quantity : 1 , remark : marker }],
}))
const scenario = { name , operationType , expectedCartons : cartonCount , items : [], targets : [], requestItems : items ,
sourceCartonMarks : items . map ( item => item . cartonMark ), relabelVerified : false }
report . scenarios . push ( scenario )
const created = await api ( platformToken , 'POST' ,
`/api/platform/v1/last-leg/relabel-applications?customerId= ${ customerId } ` , {
customerOrderNo : `TAIL- ${ suffix } ` . slice ( 0 , 128 ), operationWarehouseId : warehouseId ,
labelDeadline : new Date ( Date . now () + 7 * 86400000 ). toISOString (). slice ( 0 , 19 ),
contactName : '尾程验收' , contactPhone : '13800000006' , ...( chargeAmount > 0 ? { chargeAmount } : {}),
remark : ` ${ marker } - ${ name } ` , idempotencyKey : key ( ` ${ name } -CREATE` ),
deliveryMethod : operationType === 'SKU_LABEL' ? null : 'TRUCK' ,
printShippingLabel : false , items ,
})
const relabelNo = created . application . relabelNo
scenario . relabelNo = relabelNo ; scenario . createResult = created
assert ( created . application . overseasWarehouseId === warehouseId , 'Created relabel belongs to a different warehouse' )
await context . checkpoint ()
if ( chargeAmount > 0 ) await poll (() => api ( platformToken , 'GET' ,
`/api/platform/v1/last-leg/relabel-applications/ ${ relabelNo } ` ),
( detail ) => detail . application . walletStatus === 'FROZEN' )
let detail = await api ( pdaToken , 'POST' ,
`/api/pda/v1/last-leg/relabel-tasks/ ${ relabelNo } /claim` , { idempotencyKey : key ( ` ${ name } -CLAIM` ) })
for ( const [ index , item ] of detail . items . entries ()) {
if ( operationType === 'CARTON_AND_SKU' ) {
const cartonBody = { cartonMark : item . cartonMark , expectedVersion : exactVersion ( detail . version ), idempotencyKey : key ( ` ${ name } -CARTON- ${ index } ` ) }
detail = await api ( pdaToken , 'POST' , `/api/pda/v1/last-leg/relabel-tasks/ ${ relabelNo } /cartons/scan` , cartonBody )
const cartonReplay = await api ( pdaToken , 'POST' , `/api/pda/v1/last-leg/relabel-tasks/ ${ relabelNo } /cartons/scan` , cartonBody )
assert ( String ( cartonReplay . version ) === String ( detail . version ) && same ( cartonReplay . items , detail . items ), 'Original carton scan replay changed quantities/version' )
const stageItem = detail . items . find (( row ) => id ( row . itemId ) === id ( item . itemId ))
assert ( Number ( stageItem ? . cartonProcessedPackages ) === 1
&& Number ( stageItem ? . skuProcessedPackages ) === 0 ,
` ${ relabelNo } carton stage incorrectly locked or completed SKU stage` )
}
const scanBody = {
itemId : id ( item . itemId ), oldLabel : item . cartonMark , newLabel : null ,
skuVerifications : ( item . skuItems || []). map (( sku ) => ({
skuCode : sku . skuCode , newSkuCode : sku . newSkuCode , quantity : Number ( sku . quantity ),
})),
expectedVersion : exactVersion ( detail . version ), idempotencyKey : key ( ` ${ name } -SCAN- ${ index } ` ),
}
detail = await api ( pdaToken , 'POST' , `/api/pda/v1/last-leg/relabel-tasks/ ${ relabelNo } /units` , scanBody )
const scanReplay = await api ( pdaToken , 'POST' , `/api/pda/v1/last-leg/relabel-tasks/ ${ relabelNo } /units` , scanBody )
assert ( String ( scanReplay . version ) === String ( detail . version ) && same ( scanReplay . items , detail . items ), 'Original SKU verification replay changed quantities/version' )
2026-09-17 17:37:29 +08:00
}
2026-10-20 10:35:52 +08:00
const sourceBefore = Number (( await resolvePallet ( sourcePosition . palletNo )). totalPackages )
Object . assign ( scenario , { items : detail . items , sourceBefore , sourceCartonMarks : items . map ( item => item . cartonMark ), requestItems : items })
if ( prepare ) await prepare ( scenario )
// Valid synthetic PNG for API validation; no physical warehouse photograph is claimed.
const photoResponse = await context . requestMultipart ( 'POST' ,
`/api/pda/v1/last-leg/relabel-tasks/ ${ relabelNo } /photos/files` , {
name : ` ${ marker } - ${ name } .png` , type : 'image/png' ,
content : Buffer . from ( 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aWQAAAABJRU5ErkJggg==' , 'base64' ),
}, pdaToken )
const photo = context . apiSuccess ( 'overseas-relabel-producer' , 'Upload synthetic relabel PNG' , photoResponse )
assert ( photoResponse . http === 200 && String ( photoResponse . body ? . code ) === '200' && photo ? . fileId , 'Synthetic photo upload failed' )
scenario . photo = { fileId : id ( photo . fileId ), evidence : 'SYNTHETIC_PNG_NOT_PHYSICAL_WORK' }
detail = await api ( pdaToken , 'POST' , `/api/pda/v1/last-leg/relabel-tasks/ ${ relabelNo } /complete` , {
photoFileIds : [ id ( photo . fileId )], expectedVersion : exactVersion ( detail . version ),
idempotencyKey : key ( ` ${ name } -COMPLETE` ), remark : marker ,
})
assert ( detail . applicationStatus === 'CUSTOMER_CONFIRM_PENDING' , ` ${ relabelNo } did not reach customer confirmation` )
const confirmed = await api ( merchantToken , 'POST' ,
`/api/merchant/v1/last-leg/relabel-applications/ ${ relabelNo } /confirm-completion` , {
idempotencyKey : key ( ` ${ name } -CONFIRM` ), remark : marker ,
})
const expectedStatus = operationType === 'SKU_LABEL' ? 'COMPLETED' : 'READY_FOR_OUTBOUND'
assert ( confirmed . application . status === expectedStatus , ` ${ relabelNo } did not reach ${ expectedStatus } ` )
assert ( confirmed . items . every (( item ) => item . destinationFbaWarehouseCode === source . destinationFbaWarehouseCode ),
` ${ relabelNo } did not preserve the destination warehouse` )
Object . assign ( scenario , { items : confirmed . items , confirmedResult : confirmed , relabelVerified : true ,
relabelCartonMarks : operationType === 'CARTON_AND_SKU' ? confirmed . items . map ( item => item . fbaCartonMark ) : [] })
scenario . sourceAfterRelabel = Number (( await resolvePallet ( sourcePosition . palletNo )). totalPackages )
await context . checkpoint ()
return scenario
}
async function printPallet ( relabelNo , suffix ) {
return api ( pdaToken , 'POST' , '/api/pda/v1/last-leg/pallet-operations/relabel-pallet-label' , {
relabelNo , idempotencyKey : key ( `PRINT- ${ suffix } ` ),
2026-09-14 09:58:30 +08:00
})
}
2026-10-20 10:35:52 +08:00
async function moveCarton ( scenario , item , target , index ) {
const sourcePallet = await resolvePallet ( sourcePosition . palletNo )
const targetPallet = await resolvePallet ( target . palletCode )
const result = await api ( pdaToken , 'POST' , '/api/pda/v1/last-leg/pallet-operations/relabel-disassemble' , {
idempotencyKey : key ( ` ${ scenario . name } -MOVE- ${ index } ` ), relabelNo : scenario . relabelNo ,
source : { palletCode : sourcePallet . palletCode , expectedVersion : exactVersion ( sourcePallet . palletVersion ) },
target : { palletCode : targetPallet . palletCode , expectedVersion : exactVersion ( targetPallet . palletVersion ) },
cartonMarks : [ item . cartonMark ], reason : ` ${ marker } - ${ scenario . name } ` ,
2026-09-14 09:58:30 +08:00
})
2026-10-20 10:35:52 +08:00
assert ( result . operationType === 'RELABEL_PALLET_DISASSEMBLE' , 'Unexpected pallet operation type' )
return result
}
2026-09-14 09:58:30 +08:00
2026-10-20 10:35:52 +08:00
const skuOnly = await createAndConfirm ( 'SKU-ONLY' , 1 , 'SKU_LABEL' )
skuOnly . sourceAfter = Number (( await resolvePallet ( sourcePosition . palletNo )). totalPackages )
assert ( skuOnly . sourceAfter === skuOnly . sourceBefore , 'SKU-only relabel must not split or regroup pallets' )
skuOnly . targetPallets = []
skuOnly . destinationFbaWarehouseCode = source . destinationFbaWarehouseCode
let sharedTarget
const fresh = await createAndConfirm ( 'NEW-PALLET' , 1 , 'CARTON_AND_SKU' , async scenario => {
sharedTarget = await printPallet ( scenario . relabelNo , 'SHARED' )
const printReplay = await printPallet ( scenario . relabelNo , 'SHARED' )
assert ( id ( sharedTarget . palletId ) === id ( printReplay . palletId ), 'Pallet print replay was not idempotent' )
await moveCarton ( scenario , scenario . items [ 0 ], sharedTarget , 0 )
scenario . targets = [ sharedTarget ]
2026-09-14 09:58:30 +08:00
})
2026-10-20 10:35:52 +08:00
const sharedBefore = Number (( await resolvePallet ( sharedTarget . palletCode )). totalPackages )
const merged = await createAndConfirm ( 'MERGE-EXISTING' , 1 , 'CARTON_AND_SKU' , async scenario => {
await moveCarton ( scenario , scenario . items [ 0 ], sharedTarget , 0 )
scenario . targets = [ sharedTarget ]
2026-09-14 09:58:30 +08:00
})
2026-10-20 10:35:52 +08:00
const sharedAfter = Number (( await resolvePallet ( sharedTarget . palletCode )). totalPackages )
assert ( sharedAfter === sharedBefore + 1 , 'Existing target pallet carton count did not increase' )
merged . targets = [ sharedTarget ]
report . sharedMerge = { palletId : id ( sharedTarget . palletId ), palletCode : sharedTarget . palletCode , beforePackages : sharedBefore , afterPackages : sharedAfter ,
sourceScenarios : [{ name : fresh . name , cartons : fresh . expectedCartons }, { name : merged . name , cartons : merged . expectedCartons }] }
2026-09-14 09:58:30 +08:00
2026-10-20 10:35:52 +08:00
const split = await createAndConfirm ( 'SPLIT-TWO-TARGETS' , 2 , 'CARTON_AND_SKU' , async scenario => {
const splitA = await printPallet ( scenario . relabelNo , 'SPLIT-A' )
const splitB = await printPallet ( scenario . relabelNo , 'SPLIT-B' )
await moveCarton ( scenario , scenario . items [ 0 ], splitA , 0 )
await moveCarton ( scenario , scenario . items [ 1 ], splitB , 1 )
scenario . targets = [ splitA , splitB ]
2026-09-14 09:58:30 +08:00
})
2026-10-20 10:35:52 +08:00
for ( const scenario of [ fresh , merged , split ]) scenario . destinationFbaWarehouseCode = source . destinationFbaWarehouseCode
const ledger = await api ( platformToken , 'GET' , `/api/platform/v1/last-leg/inventory/ledger?customerId= ${ customerId } &page=1&size=100&businessType=RELABEL_PALLET_DISASSEMBLE` )
const ledgerRows = ledger . records . filter ( row => String ( row . remark || '' ). includes ( marker ))
assert ( ledgerRows . some ( row => Number ( row . deltaPackages ) < 0 ), 'Missing negative relabel pallet ledger' )
assert ( ledgerRows . some ( row => Number ( row . deltaPackages ) > 0 ), 'Missing positive relabel pallet ledger' )
assert ( ledgerRows . every ( row => String ( row . remark ). startsWith ( '换标拆托' )), 'Unexpected relabel ledger reason' )
report . ledger = { rows : ledgerRows . length , deltas : ledgerRows . map ( row => Number ( row . deltaPackages )), records : ledgerRows }
report . relabelScenariosVerified = report . scenarios . length === 4 && report . scenarios . every ( scenario => scenario . relabelVerified )
await publishNativeRelabelDispatches ( context , report , config )
report . passed = report . relabelScenariosVerified && ! report . blockers . length
report . finalStatus = report . passed ? 'RELABEL_AND_NATIVE_DISPATCH_VERIFIED' : 'BLOCKED_NATIVE_DISPATCH_OR_PHYSICAL_LOADING_EVIDENCE'
await context . checkpoint (); return report
2026-09-14 09:58:30 +08:00
}
2026-10-20 10:35:52 +08:00
export async function mainRelabelProducer ( env = process . env ) {
const options = parseArguments ([]); options . modules = []
const output = resolve ( env . OVERSEAS_ACCEPTANCE_OUTPUT || 'reports/overseas-relabel-real-acceptance.json' )
options . output = output ; validateExecutionOptions ( options )
requireFact ( options . environment === 'test' , 'Relabel producer only supports explicit TEST' )
const customerId = id ( env . OVERSEAS_TEST_CUSTOMER_ID ), warehouseId = id ( env . OVERSEAS_TEST_WAREHOUSE_ID )
const sourceBalanceId = id ( env . OVERSEAS_RELABEL_SOURCE_BALANCE_ID ), sourcePositionId = id ( env . OVERSEAS_RELABEL_SOURCE_POSITION_ID )
requireFact ( /^[1-9]\d*$/ . test ( customerId ) && /^[1-9]\d*$/ . test ( warehouseId ) && /^[1-9]\d*$/ . test ( sourceBalanceId ) && /^[1-9]\d*$/ . test ( sourcePositionId ),
'Explicit customer, warehouse, original source balance and position IDs are required before HTTP' )
requireFact ( ! existsSync ( output ), 'Existing producer checkpoint must be reconciled with its original commands; do not overwrite it or regenerate a marker' )
const chargeAmount = Number ( env . OVERSEAS_RELABEL_CHARGE_AMOUNT ?? '0.01' )
requireFact ( Number . isFinite ( chargeAmount ) && chargeAmount >= 0 , 'Invalid charge amount' )
const context = new AcceptanceRun ( options ), marker = env . OVERSEAS_RELABEL_MARKER || context . marker
requireFact ( /^[A-Za-z0-9_-]+$/ . test ( marker ), 'Invalid explicit producer marker' )
const report = { marker , environment : 'TEST' , startedAt : new Date (). toISOString (), scope : 'RELABEL_PRODUCER_AND_NATIVE_DISPATCH_ONLY' ,
chargeAmount , photoEvidence : 'SYNTHETIC_PNG_NOT_PHYSICAL_WORK' , scenarios : [], blockers : [], passed : false , requests : [] }
const raw = async ( method , path , body , token ) => {
const response = await context . request ( method , path , body , token )
requireFact ( response . http === 200 && String ( response . body ? . code ) === '200' , 'Unified authentication failed' )
return response . body . data
}
try {
// Session tickets and tokens remain in memory and never enter the business journal.
const platform = await raw ( 'POST' , '/api/auth/login' , { loginId : options . username , password : options . password }, '' )
context . token = platform . accessToken
requireFact ( context . token && options . pdaUsername && options . pdaPassword , 'Unified platform and explicit PDA credentials required' )
const pda = await raw ( 'POST' , '/api/auth/login' , { loginId : options . pdaUsername , password : options . pdaPassword }, '' )
let merchant
if ( options . merchantUsername && options . merchantPassword ) merchant = await raw ( 'POST' , '/api/auth/login' , { loginId : options . merchantUsername , password : options . merchantPassword }, '' )
else {
const ticket = await raw ( 'POST' , `/api/platform/v1/iam/tenants/ ${ customerId } /impersonation` , { reason : ` ${ marker } TEST relabel producer` }, context . token )
merchant = await raw ( 'POST' , '/api/auth/impersonations/exchange' , { code : ticket . code }, '' )
}
requireFact ( pda . accessToken && merchant . accessToken , 'Unified PDA/customer accessToken required' )
const profile = await raw ( 'GET' , '/api/merchant/v1/iam/tenant/profile' , undefined , merchant . accessToken )
requireFact ( id ( profile . id ) === customerId , 'Merchant session does not own the explicit customer' )
installRelabelJournal ( context , report , () => saveRetainedReport ( output , report )); context . marker = marker
const loadingEvidenceByScenario = env . OVERSEAS_RELABEL_LOADING_EVIDENCE ? JSON . parse ( readFileSync ( resolve ( env . OVERSEAS_RELABEL_LOADING_EVIDENCE ), 'utf8' )) : undefined
await runRelabelProducer ( context , { customerId , warehouseId , sourceBalanceId , sourcePositionId , inboundCode : env . OVERSEAS_RELABEL_SOURCE_INBOUND_CODE ,
merchantToken : merchant . accessToken , pdaToken : pda . accessToken , chargeAmount , loadingEvidenceByScenario }, report )
} catch ( error ) {
report . passed = false ; report . finalStatus = report . requests . some ( row => [ 'SENDING' , 'UNKNOWN' ]. includes ( row . status )) ? 'UNKNOWN_WRITE_STOPPED' : 'BLOCKED'
report . failedStage = report . scenarios . at ( - 1 ) ? . name || 'CUSTOMER_OR_EXPLICIT_SOURCE' ; process . exitCode = 1
} finally {
report . checks = context . steps ; report . finishedAt = new Date (). toISOString ()
if ( ! report . passed || context . steps . some ( step => step . passed === false )) process . exitCode = 1
saveRetainedReport ( output , report )
console . log ( JSON . stringify ({ marker , finalStatus : report . finalStatus , passed : report . passed ,
relabelScenariosVerified : report . relabelScenariosVerified , scenarios : report . scenarios . map ( scenario => ({ name : scenario . name ,
relabelNo : scenario . relabelNo , dispatchNo : scenario . dispatchNo , dispatchStatus : scenario . dispatchStatus })), report : output }))
}
return report
}
if ( process . argv [ 1 ] && resolve ( process . argv [ 1 ]) === fileURLToPath ( import . meta . url )) await mainRelabelProducer ()