2026-10-03 07:42:18 +08:00
import { controlledPalletMeasurements } from './fleet-trip-fulfillment.mjs'
const MODULE = 'fleet-cross-customer-acceptance'
const ROOT = '/api/platform/v1/last-leg'
const PDA = '/api/pda/v1/last-leg/dispatches'
const OPTIONS = { scenario : 'fleet-cross-customer-departure' , deploymentBlocker : true }
const id = value => value == null ? '' : String ( value )
const ok = response => response ? . http === 200 && id ( response . body ? . code ) === '200'
const encode = encodeURIComponent
const scoped = ( path , customerId ) => ` ${ path } ? ${ new URLSearchParams ({ customerId } )}`
const canonical = value => JSON . stringify ( normalize ( value ))
function normalize ( value ) {
// Route stops and other business arrays are ordered. Raw readers return stable ID order.
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 integer ( value ) {
if ( ! [ 'number' , 'string' ]. includes ( typeof value ) || ! /^(0|[1-9][0-9]*)$/ . test ( String ( value )) || ! Number . isSafeInteger ( Number ( value ))) throw new Error ( 'Invalid inventory integer' )
return Number ( value )
}
const buckets = [ 'availablePackages' , 'holdPackages' , 'allocatedPackages' , 'pickedPackages' , 'receivingPackages' ]
const balanceBuckets = [... buckets , 'inTransitPackages' , 'procurementPackages' ]
export const fixtureContract = Object . freeze ({
stage : 'TWO_FRESH_RECEIVED_DISPATCHES_BOUND_AND_TRANSFERRED_NOT_PICKED' ,
fixture : 'marker, warehouseId, tripNo, pdaToken, members[2]: {marker, customerId, incomingNo, dispatchNo, outboundNo, deliveryNo, balanceId, positionId, palletId, palletNo, receiptVerified:true}' ,
preparation : 'Two independently qualified current-run customers; each formally received one physical pallet/box in the same warehouse; draft lock/pre-arrange separately; bind both to one assigned qualified vehicle/driver trip; transfer each to its own outbound and delivery. No picking yet.' ,
reader : 'readInventory(scope) => {scope:{marker,customerId,warehouseId,coverage:"CUSTOMER_WAREHOUSE_COMPLETE"}, balances:all raw rows including zero, positions:all raw rows including zero, ledger:all immutable movement rows}. Retain camelCase IDs and every quantity bucket; never substitute a grouped public SKU page.' ,
helperLimit : 'Existing external-receipt helper selects plan.cargo[0].customerId and requires an existing own-cargo capacity plan; external-dispatch helper has one mutable externalReceipt slot. Do not run either twice pretending to create two customers.' ,
boundary : 'This exercises a prerequisite gate rejection, not a mid-transaction rollback. No fault injection, account activation, SQL writes, deployment, cleanup deletion or implicit fixture creation.' ,
})
/** Import/default call is network-free. Explicit execute:true requires separately authorized TEST execution.
* Tokens stay in the caller, never in the persisted proof. Failure retains fixture and exact request keys.
*/
2026-10-04 10:32:26 +08:00
export async function verifyFleetCrossCustomer ( context , fixture , { execute = false , readInventory , verifyDepartureRollback } = {}) {
2026-10-03 07:42:18 +08:00
if ( ! execute ) return fixtureContract
if ( ! context ? . marker || fixture ? . marker !== context . marker || fixture . stage !== fixtureContract . stage
|| ! fixture . tripNo || ! fixture . warehouseId || ! fixture . pdaToken || typeof readInventory !== 'function'
|| fixture . members ? . length !== 2 || new Set ( fixture . members . map ( row => row . customerId )). size !== 2
|| fixture . members . some ( row => row . marker !== context . marker || row . receiptVerified !== true
|| [ 'customerId' , 'incomingNo' , 'dispatchNo' , 'outboundNo' , 'deliveryNo' , 'balanceId' , 'positionId' , 'palletId' , 'palletNo' ]. some ( key => ! id ( row [ key ])))) {
throw new Error ( 'Requires two distinct current-run customers, immutable receipt references and a complete raw inventory reader' )
}
for ( const key of [ 'dispatchNo' , 'outboundNo' , 'deliveryNo' , 'positionId' , 'palletId' ]) {
if ( new Set ( fixture . members . map ( row => id ( row [ key ]))). size !== 2 ) throw new Error ( `Cross-customer fixture reuses ${ key } ` )
}
context . createdResources ||= {}
if ( context . createdResources . fleetCrossCustomer ) throw new Error ( 'Proof already exists; retain failed run and reconcile explicitly' )
const proof = context . createdResources . fleetCrossCustomer = { marker : context . marker , tripNo : fixture . tripNo ,
warehouseId : fixture . warehouseId , members : fixture . members . map (({ marker , customerId , incomingNo , dispatchNo , outboundNo , deliveryNo , balanceId , positionId , palletId , palletNo })=>({ marker , customerId , incomingNo , dispatchNo , outboundNo , deliveryNo , balanceId , positionId , palletId , palletNo })),
status : 'CHECKING' , requests : [], snapshots : {}, gateProof : 'NOT_RUN' , midTransactionRollback : 'NOT_TESTED' , cleanup : 'Caller owns account/session lifecycle; retain all business fixtures' }
const check = ( name , passed ) => {
context . assert ( MODULE , name , null , Boolean ( passed ), '' , OPTIONS )
if ( ! passed ) throw new Error ( name )
}
const key = suffix => ` ${ context . marker } -CROSS- ${ suffix } `
const tripPath = ` ${ ROOT } /fleet/trips/ ${ encode ( fixture . tripNo ) } `
const request = async ( method , path , body , label , pda = false , rejection = false ) => {
const response = await context . request ( method , path , body , pda ? fixture . pdaToken : context . token ,{})
proof . requests . push ({ method , path , body , label , http : response ? . http , code : response ? . body ? . code , requestId : response ? . requestId , traceId : response ? . traceId })
const denied = response ? . http === 409 || ( response ? . http === 200 && id ( response . body ? . code ) === 'A0409' )
check ( label , Boolean ( response ? . requestId && response ? . traceId ) && ( rejection ? denied && rejection ( id ( response . body ? . message )) : ok ( response )))
return response . body ? . data
}
const stock = async member => {
const scope = { marker : context . marker , customerId : member . customerId , warehouseId : fixture . warehouseId }
const raw = await readInventory ( scope )
check ( 'Inventory reader proves complete customer/warehouse scope' , canonical ( raw ? . scope ) === canonical ({... scope , coverage : 'CUSTOMER_WAREHOUSE_COMPLETE' }))
for ( const collection of [ 'balances' , 'positions' , 'ledger' ]) check ( `Complete ${ collection } rows have unique identities and customer ownership` , Array . isArray ( raw [ collection ])
&& new Set ( raw [ collection ]. map ( row => id ( row . id ))). size === raw [ collection ]. length
&& raw [ collection ]. every ( row => id ( row . id ) && id ( row . customerId ) === id ( member . customerId ) && id ( row . overseasWarehouseId ) === id ( fixture . warehouseId )))
check ( 'Original receipt balance and physical position are retained even at zero' , raw . balances . some ( row => id ( row . id ) === id ( member . balanceId ))
&& raw . positions . some ( row => id ( row . id ) === id ( member . positionId ) && id ( row . balanceId ) === id ( member . balanceId ) && id ( row . palletId ) === id ( member . palletId ) && row . palletNo === member . palletNo ))
for ( const balance of raw . balances ) {
for ( const field of balanceBuckets ) integer ( balance [ field ])
const positions = raw . positions . filter ( row => id ( row . balanceId ) === id ( balance . id ))
for ( const field of buckets ) check ( `Raw balance and every position conserve ${ field } ` , integer ( balance [ field ]) === positions . reduce (( sum , row )=> sum + integer ( row [ field ]), 0 ))
}
check ( 'No orphan stock positions' , raw . positions . every ( row => raw . balances . some ( balance => id ( balance . id ) === id ( row . balanceId ))))
return raw
}
const snapshot = async label => {
const trip = await request ( 'GET' , tripPath , undefined , 'Read shared trip' )
check ( 'Trip members are exactly the two independent customers' , trip . trip ? . tripNo === fixture . tripNo && id ( trip . trip . warehouseId ) === id ( fixture . warehouseId )
&& trip . dispatches ? . length === 2 && fixture . members . every ( member => trip . dispatches . some ( row => row . dispatchNo === member . dispatchNo && id ( row . customerId ) === id ( member . customerId ))))
const members = []
for ( const member of fixture . members ) {
const dispatch = await request ( 'GET' , ` ${ ROOT } /dispatches/ ${ encode ( member . dispatchNo ) } ` , undefined , 'Read customer dispatch' )
const outbound = await request ( 'GET' , scoped ( ` ${ ROOT } /outbound-orders/ ${ encode ( member . outboundNo ) } ` , member . customerId ), undefined , 'Read customer outbound' )
const delivery = await request ( 'GET' , scoped ( ` ${ ROOT } /delivery-orders/ ${ encode ( member . deliveryNo ) } ` , member . customerId ), undefined , 'Read customer delivery' )
const task = await request ( 'GET' , ` ${ PDA } / ${ encode ( member . dispatchNo ) } ` , undefined , 'Read PDA task' , true )
check ( 'Execution records retain exact customer, warehouse and dispatch ownership' , dispatch . dispatchNo === member . dispatchNo
&& id ( dispatch . overseasWarehouseId ) === id ( fixture . warehouseId ) && dispatch . items ? . length === 1
&& id ( dispatch . items [ 0 ]. warehousePalletId ) === id ( member . palletId )
&& outbound . outbound ? . outboundNo === member . outboundNo && outbound . outbound . dispatchNo === member . dispatchNo
&& id ( outbound . outbound . overseasWarehouseId ) === id ( fixture . warehouseId ) && outbound . deliveries ? . length === 1
&& outbound . deliveries [ 0 ]. deliveryNo === member . deliveryNo && id ( outbound . deliveries [ 0 ]. customerId ) === id ( member . customerId )
&& delivery . order ? . deliveryNo === member . deliveryNo && id ( delivery . order . customerId ) === id ( member . customerId )
&& delivery . order . dispatchNo === member . dispatchNo && delivery . order . outboundNo === member . outboundNo
&& task . dispatchNo === member . dispatchNo && id ( task . warehouseId ) === id ( fixture . warehouseId ) && task . tripNo === fixture . tripNo )
members . push ({ dispatch , outbound , delivery , task , stock : await stock ( member )})
}
const value = { trip , members }; proof . snapshots [ label ] = value ; return value
}
const equal = ( a , b , name )=> check ( name , canonical ( a ) === canonical ( b ))
const pick = async ( member , index ) => {
const before = await snapshot ( `BEFORE_PICK_ ${ index } ` ), task = before . members [ index ]. task
check ( 'PDA exposes the single real current-run pallet and position' , task . items ? . length === 1 && integer ( task . items [ 0 ]. scheduledPackages ) === 1
&& integer ( task . items [ 0 ]. pickedPackages ) === 0 && task . items [ 0 ]. palletNo === member . palletNo && task . items [ 0 ]. positions ? . length === 1
&& id ( task . items [ 0 ]. positions [ 0 ]. id ) === id ( member . positionId ) && integer ( task . items [ 0 ]. positions [ 0 ]. availablePackages ) === 1 )
const body = { positionId : member . positionId , packages : 1 , productQuantity : 1 , idempotencyKey : key ( `PICK- ${ index } ` ), remark : context . marker }
const path = ` ${ PDA } / ${ encode ( member . dispatchNo ) } /items/ ${ encode ( task . items [ 0 ]. dispatchItemId ) } /pick`
await request ( 'POST' , path , body , 'Pick this customer only' , true )
await request ( 'POST' , ` ${ PDA } / ${ encode ( member . dispatchNo ) } /complete` ,{ idempotencyKey : key ( `COMPLETE- ${ index } ` )}, 'Complete this customer picking' , true )
const after = await snapshot ( `AFTER_PICK_ ${ index } ` )
equal ( before . members [ 1 - index ]. stock , after . members [ 1 - index ]. stock , 'Other customer inventory unchanged by picking' )
const expected = structuredClone ( before . members [ index ]. stock )
for ( const collection of [ 'balances' , 'positions' ]) {
const row = expected [ collection ]. find ( row => id ( row . id ) === id ( collection === 'balances' ? member . balanceId : member . positionId ))
row . availablePackages = integer ( row . availablePackages ) - 1
}
const counts = raw => Object . fromEntries ([ 'balances' , 'positions' ]. map ( collection =>[ collection , raw [ collection ]. map ( row =>({ id : id ( row . id ),... Object . fromEntries (( collection === 'balances' ? balanceBuckets : buckets ). map ( field =>[ field , integer ( row [ field ])]))}))]))
equal ( counts ( expected ), counts ( after . members [ index ]. stock ), 'Only selected customer position and balance lose one box' )
const old = before . members [ index ]. stock . ledger , newRows = after . members [ index ]. stock . ledger . filter ( row => ! old . some ( prior => id ( prior . id ) === id ( row . id )))
equal ( old , after . members [ index ]. stock . ledger . filter ( row => old . some ( prior => id ( prior . id ) === id ( row . id ))), 'Immutable old movements preserved' )
check ( 'Single ALLOCATE/PICK/SHIP sequence has exact source and quantity' , newRows . length === 3 && [ 'ALLOCATE' , 'PICK' , 'SHIP' ]. every ( type => newRows . filter ( row => row . businessType === type
&& id ( row . positionId ) === id ( member . positionId ) && row . businessOrderNo === member . dispatchNo && Number ( row . deltaPackages ) === ( type === 'SHIP' ?- 1 : 1 )). length === 1 ))
await request ( 'POST' , path , body , 'Replay exact customer pick' , true )
equal ( after , await snapshot ( `PICK_REPLAY_ ${ index } ` ), 'Pick replay changes neither customer nor any execution record' )
}
try {
let initial = await snapshot ( 'INITIAL' )
check ( 'Minimal fresh fixture has one available box per customer and no prior execution' , initial . trip . trip . status === 'PLANNED'
&& initial . members . every (( row , index )=> row . dispatch . status === 'PENDING_DEPARTURE' && integer ( row . dispatch . packageCount ) === 1
&& integer ( row . dispatch . pickedPackageCount ) === 0 && row . outbound . outbound . status === 'WAIT_OUTBOUND' && row . delivery . order . status === 'WAIT_DELIVERY'
&& integer ( row . stock . positions . find ( p => id ( p . id ) === id ( fixture . members [ index ]. positionId )). availablePackages ) === 1
&& row . stock . balances . every ( balance => balanceBuckets . filter ( field => field !== 'availablePackages' ). every ( field => integer ( balance [ field ]) === 0 ))
&& row . stock . balances . reduce (( sum , balance )=> sum + integer ( balance . availablePackages ), 0 ) === 1 ))
let detail = initial . trip
for ( const member of fixture . members ) detail = await request ( 'POST' , ` ${ tripPath } /dispatches/ ${ encode ( member . dispatchNo ) } /review` ,
{ recordVersion : detail . trip . recordVersion , decision : 'APPROVED' }, 'Review each customer dispatch separately' )
check ( 'Both customer dispatches approved' , detail . dispatches . every ( row => row . review ? . status === 'APPROVED' ))
await pick ( fixture . members [ 0 ], 0 )
const rejectedBefore = await snapshot ( 'GATE_BEFORE' )
check ( 'Precisely one customer is ready' , rejectedBefore . members [ 0 ]. dispatch . status === 'READY_TO_DEPART' && integer ( rejectedBefore . members [ 1 ]. dispatch . pickedPackageCount ) === 0 )
await request ( 'POST' , ` ${ PDA } / ${ encode ( fixture . members [ 0 ]. dispatchNo ) } /depart` ,
{ recordVersion : rejectedBefore . trip . trip . recordVersion , idempotencyKey : key ( 'UNREADY-DEPART' ), remark : context . marker }, 'Unready second customer blocks whole trip before execution' , true ,
message => message . includes ( fixture . members [ 1 ]. dispatchNo ) && message . includes ( '请完成拣货并核平全部箱数' ))
equal ( rejectedBefore , await snapshot ( 'GATE_AFTER' ), 'Rejected departure leaves both complete inventories/outbounds/deliveries/trip unchanged' )
proof . gateProof = 'PREREQUISITE_REJECTION_VERIFIED'
await pick ( fixture . members [ 1 ], 1 )
const loading = await request ( 'GET' , ` ${ tripPath } /loading` , undefined , 'Read ready shared loading' )
check ( 'Both real pallets ready for loading' , ! loading . blockReason && loading . cargo ? . length === 2 )
detail = await request ( 'GET' , tripPath , undefined , 'Read current trip route' )
const measured = controlledPalletMeasurements ( loading . cargo , detail . trip . stops )
const loaded = await request ( 'POST' , ` ${ tripPath } /loading` ,{ recordVersion : loading . recordVersion , manifestHash : loading . manifestHash , pallets : measured , reason : context . marker }, 'Confirm controlled TEST measurements' )
const reviewed = await request ( 'POST' , ` ${ tripPath } /loading-review` ,{ recordVersion : loaded . recordVersion , decision : 'APPROVED' }, 'Approve whole-trip loading' )
const ready = await snapshot ( 'READY' )
const departBody = { recordVersion : reviewed . trip . recordVersion , idempotencyKey : key ( 'DEPART' ), remark : context . marker }
const departPath = ` ${ PDA } / ${ encode ( fixture . members [ 0 ]. dispatchNo ) } /depart`
2026-10-04 10:32:26 +08:00
if ( verifyDepartureRollback !== undefined ) {
check ( 'Explicit rollback verifier is callable' , typeof verifyDepartureRollback === 'function' )
const rollback = await verifyDepartureRollback ({ context , fixture , ready , departPath , departBody , snapshot })
check ( 'Mid-transaction rollback has exact owned verification' , rollback ? . verified === true && rollback . scope === 'OWNED_TWO_CUSTOMER_DEPARTURE_ROLLBACK' )
equal ( ready , await snapshot ( 'ROLLBACK_RESTORED' ), 'Mid-transaction rollback restores both complete customer execution and inventory snapshots' )
proof . rollbackEvidence = rollback ; proof . midTransactionRollback = 'VERIFIED'
}
2026-10-03 07:42:18 +08:00
const departed = await request ( 'POST' , departPath , departBody , 'Depart whole trip once' , true )
check ( 'Departure includes both customer dispatches' , departed . tripNo === fixture . tripNo && departed . status === 'IN_TRANSIT' && integer ( departed . shippedPackages ) === 2
&& canonical ( departed . dispatches . map ( row => row . dispatchNo ). sort ()) === canonical ( fixture . members . map ( row => row . dispatchNo ). sort ()))
const after = await snapshot ( 'DEPARTED' )
check ( 'Both outbounds completed and trip in transit' , after . trip . trip . status === 'IN_TRANSIT' && after . members . every ( row => row . dispatch . status === 'DEPARTED' && row . outbound . outbound . status === 'OUTBOUNDED' ))
equal ( ready . members . map ( row => row . stock ), after . members . map ( row => row . stock ), 'Departure does not deduct either inventory again' )
const replay = await request ( 'POST' , departPath , departBody , 'Replay exact shared departure' , true )
check ( 'Shared departure replay is explicit' , replay . replayed === true )
equal ( after , await snapshot ( 'DEPART_REPLAY' ), 'Departure replay preserves both complete inventories and execution facts' )
for ( const [ index , member ] of fixture . members . entries ()) {
const before = await snapshot ( `BEFORE_SIGN_ ${ index } ` ), path = scoped ( ` ${ ROOT } /delivery-orders/ ${ encode ( member . deliveryNo ) } ` , member . customerId )
await request ( 'PUT' , scoped ( ` ${ ROOT } /delivery-orders/ ${ encode ( member . deliveryNo ) } /status` , member . customerId ),
{ status : 'DELIVERING' , expectedVersion : before . members [ index ]. delivery . order . version , remark : context . marker }, 'Start only this customer delivery' )
const documentPath = scoped ( ` ${ ROOT } /delivery-orders/ ${ encode ( member . deliveryNo ) } /documents` , member . customerId )
const docs = await request ( 'GET' , documentPath , undefined , 'Read this delivery document version' )
const uploaded = await context . requestMultipart ( 'POST' , scoped ( ` ${ ROOT } /delivery-orders/ ${ encode ( member . deliveryNo ) } /documents/RECEIPT/files` , member . customerId ),
{ name : ` ${ context . marker } - ${ index } -receipt.txt` , type : 'text/plain' , content : new TextEncoder (). encode ( ` ${ context . marker } synthetic receipt ${ member . deliveryNo } ` )})
proof . requests . push ({ method : 'POST' , path : scoped ( ` ${ ROOT } /delivery-orders/ ${ encode ( member . deliveryNo ) } /documents/RECEIPT/files` , member . customerId ), label : 'Upload own synthetic receipt' , http : uploaded ? . http , code : uploaded ? . body ? . code , requestId : uploaded ? . requestId , traceId : uploaded ? . traceId })
check ( 'Receipt uploaded through correct delivery endpoint' , ok ( uploaded ) && id ( uploaded . body ? . data ? . fileId ))
const documents = ( docs . categories || []). filter ( row => row . category !== 'RECEIPT' ). map ( row =>({ category : row . category , fileIds : ( row . files || []). map ( file => file . fileId )}))
documents . push ({ category : 'RECEIPT' , fileIds : [ id ( uploaded . body . data . fileId )]})
await request ( 'PUT' , documentPath ,{ expectedVersion : docs . version , idempotencyKey : key ( `SIGN- ${ index } ` ), bolNo : docs . bolNo || null , documents }, 'Attach receipt to independently complete this delivery' )
const signed = await snapshot ( `SIGNED_ ${ index } ` )
check ( 'This customer independently signed' , signed . members [ index ]. delivery . order . status === 'DELIVERED' )
equal ( before . members [ 1 - index ]. delivery , signed . members [ 1 - index ]. delivery , 'Other customer delivery is not signed or changed implicitly' )
equal ( before . members . map ( row => row . stock ), signed . members . map ( row => row . stock ), 'Signing does not mutate either customer inventory' )
// Retain the independently readable result as evidence, not a fabricated completion command.
await request ( 'GET' , path , undefined , 'Read signed customer delivery' )
}
proof . status = 'VERIFIED' ; return proof
} catch ( error ) { proof . status = 'FAILED' ; proof . error = error . message ; throw error }
}