fix(dispatch): confirm scanned loading without repeated measurements

This commit is contained in:
2026-10-21 18:44:26 +08:00
parent 6088ff10c2
commit 538a63c635
20 changed files with 105 additions and 73 deletions
@@ -70,11 +70,11 @@ function model(fault = '') {
if(fault==='outage disguised as rejection')return {http:503,body:{code:'S0503'},requestId:'r',traceId:'t'}
return reject(fault==='unrelated gate rejection'?'车次版本已变化':'逐项真实拣货尚未核平')
}
assert.equal(body.recordVersion,nativeVersion); assert.equal(body.measurements.length,2)
assert.equal(body.recordVersion,nativeVersion); assert.equal('measurements' in body,false)
stage='DELIVERING';nativeVersion++;trip.trip.status='IN_TRANSIT'
for(const [i,s] of state.entries()) { const m=members[i];s.outbound={outboundNo:`OUT${i}`,dispatchNo:'DS-ONE',overseasWarehouseId:'WH',customerId:m.customerId,status:'OUTBOUNDED',packageCount:1};s.delivery={order:{deliveryNo:`DL${i}`,customerId:fault==='foreign delivery'?'OTHER':m.customerId,dispatchNo:'DS-ONE',outboundNo:`OUT${i}`,status:fault==='partial departure'&&i===1?'WAIT_DELIVERY':'DELIVERING',version:0}} }
if(fault==='depart deducts again'){state[0].stock.balances[0].availablePackages='-1';state[0].stock.positions[0].availablePackages='-1'}
confirmedLoad={manifestHash:body.manifestHash,pallets:copy(body.measurements),operatorId:'PDA-ACTOR',confirmedAt:'2026-10-19T01:00:00Z'}
confirmedLoad={manifestHash:body.manifestHash,pallets:members.map((m,i)=>({warehousePalletId:m.palletId,palletSpaces:1,weightKg:100,volumeCbm:1,deliveryIndex:i+1})),operatorId:'PDA-ACTOR',confirmedAt:'2026-10-19T01:00:00Z'}
return remember(body,{confirmedLoad,loadingCompletionHash:'LOADED'},token,'LOADING_COMPLETE')
}
if (path.endsWith('/pick')) {
@@ -119,13 +119,13 @@ test('current DTO-shaped two-customer trip rejects unready gate, departs once an
assert.ok(!m.calls.some(c=>c.raw.endsWith('/complete')||c.raw.includes('/dispatches/bind')))
})
test('missing physical pallet-space evidence blocks after both real picks without fabricating loading or delivery',async()=>{
test('scanned pallets confirm without a separate pallet-space measurement receipt',async()=>{
const m=model();delete m.fixture.loadingMeasurements
await assert.rejects(verifyFleetCrossCustomer(m.context,m.fixture,{execute:true,readInventory:m.readInventory}),/palletSpaces/)
assert.ok(m.state.every(row=>row.picked===1&&row.delivery===null))
assert.equal(m.calls.filter(call=>call.raw.endsWith('/loading-complete')).length,1,'only the earlier expected unready rejection was sent')
assert.equal(m.calls.some(call=>call.raw.endsWith('/transfer-delivery')),false)
await verifyFleetCrossCustomer(m.context,m.fixture,{execute:true,readInventory:m.readInventory})
assert.ok(m.state.every(row=>row.picked===1&&row.delivery!==null))
assert.ok(m.calls.filter(call=>call.raw.endsWith('/loading-complete')).every(call=>!('measurements' in call.body)))
})
test('explicit rollback verifier runs after both picks and before successful departure',async()=>{
const m=model();let calls=0
const proof=await verifyFleetCrossCustomer(m.context,m.fixture,{execute:true,readInventory:m.readInventory,