优化
This commit is contained in:
@@ -8,36 +8,30 @@ import { run as runInboundMeasurement } from './admin-test-env-modules/inbound-m
|
||||
import { verifyPalletLabels } from './admin-test-env-modules/phase3-loading-finalize.mjs'
|
||||
|
||||
function departedRecoveryFixture() {
|
||||
const snapshotTrip = { tripNo: 'TR-OWN', status: 'IN_TRANSIT', recordVersion: '27', vehicleId: 'V1', driverId: 'D1', warehouseId: 'W1', departedAt: '2026-10-03T11:00:00Z' }
|
||||
const cargo = [{ dispatchNo: 'OWN', dispatchItemId: 'I1', warehousePalletId: 'P1', packages: 2 }, { dispatchNo: 'EXT', dispatchItemId: 'I2', warehousePalletId: 'P2', packages: 1 }]
|
||||
const departure = { action: 'DEPART', recordVersion: '27', snapshot: { trip: snapshotTrip, confirmedLoad: { manifestHash: 'a'.repeat(64), cargo } } }
|
||||
const proof = { marker: 'RUN', tripNo: 'TR-OWN', warehouseId: 'W1', customerId: 'C1', status: 'CHECKING', stage: 'TRIP_OPERATING_COSTS', checkpoints: [],
|
||||
dispatchNos: ['OWN', 'EXT'], picker: { membershipId: 'OLD-PDA' }, departureRequest: { idempotencyKey: 'old-key' },
|
||||
departure: { tripNo: 'TR-OWN', status: 'IN_TRANSIT', departedAt: snapshotTrip.departedAt, shippedPackages: 3 } }
|
||||
const trip = { marker: 'RUN', tripNo: 'TR-OWN', costAcceptance: { marker: 'RUN', status: 'FAILED', costs: [], departure,
|
||||
requests: [{ method: 'GET', status: 'RESPONDED', http: 200, code: '200', requestId: 'r', traceId: 't' }] },
|
||||
externalReceipt: { status: 'VERIFIED', bookingNo: 'BK', warehousePalletId: 'P2' }, externalDispatch: { dispatchNo: 'EXT', deliveryNo: 'DL-EXT' } }
|
||||
const report = { success: false, steps: [{ name: '独立预期覆盖全部实物且没有重叠托盘', passed: false }, { name: '模块执行', passed: false }],
|
||||
createdResources: { fleetAcceptance: proof, fleetTrips: [trip], fleetCostActors: { status: 'DISABLED' }, fleetFeeCoverageCases: [{ mode: 'PARTIAL', proof: { deliveryNos: ['DL-OWN'] } }] } }
|
||||
const base = '/api/platform/v1/last-leg/fleet/trips/TR-OWN'
|
||||
const responses = new Map([[base, { trip: structuredClone(snapshotTrip), dispatches: [{ dispatchNo: 'OWN', customerId: 'C1' }, { dispatchNo: 'EXT', customerId: 'C1' }] }],
|
||||
[base + '/history?page=1&size=100', { total: 1, records: [structuredClone(departure)] }],
|
||||
[base + '/costs?page=1&size=100', { page: { records: [], total: 0 }, summary: { manifestHash: 'a'.repeat(64), departureVersion: '27', totals: [] } }],
|
||||
[base + '/handovers', { records: [], total: 0 }]])
|
||||
for (const row of cargo) {
|
||||
responses.set('/api/platform/v1/last-leg/dispatches/' + row.dispatchNo, { dispatchNo: row.dispatchNo, status: 'DEPARTED', overseasWarehouseId: 'W1',
|
||||
packageCount: row.packages, pickedPackageCount: row.packages, shippedPackageCount: row.packages,
|
||||
items: [{ id: row.dispatchItemId, warehousePalletId: row.warehousePalletId, packageCount: row.packages }] })
|
||||
responses.set('/api/platform/v1/last-leg/delivery-orders/DL-' + row.dispatchNo + '?customerId=C1', { order: { deliveryNo: 'DL-' + row.dispatchNo,
|
||||
customerId: 'C1', dispatchNo: row.dispatchNo, status: 'WAIT_DELIVERY', version: '1', packageCount: row.packages,
|
||||
documentSummary: { categories: [{ fileCount: 0 }] } } })
|
||||
}
|
||||
const calls = [], ctx = { marker: 'RUN', token: 'writer', context: {}, createdResources: report.createdResources, async checkpoint() { calls.push('checkpoint') },
|
||||
async request(method, path) { assert.equal(method, 'GET', 'recovery verification must never write'); calls.push(path); assert.ok(responses.has(path), path)
|
||||
return { http: 200, body: { code: '200', data: responses.get(path) }, requestId: 'r', traceId: 't' } } }
|
||||
return { report, ctx, calls, responses, base, trip }
|
||||
const snapshotTrip={tripNo:'TR-OWN',status:'IN_TRANSIT',recordVersion:'27',vehicleId:'V1',driverId:'D1',warehouseId:'W1',departedAt:'2026-10-03T11:00:00Z'}
|
||||
const cargo=[{dispatchNo:'OWN',dispatchItemId:'I1',customerId:'C1',warehousePalletId:'P1',packages:2},{dispatchNo:'OWN',dispatchItemId:'I2',customerId:'C1',warehousePalletId:'P2',packages:1}]
|
||||
const measured=[{warehousePalletId:'P1',palletSpaces:2},{warehousePalletId:'P2',palletSpaces:1}]
|
||||
const departure={action:'DEPART',recordVersion:'27',snapshot:{trip:snapshotTrip,confirmedLoad:{manifestHash:'a'.repeat(64),cargo,pallets:measured}}}
|
||||
const refs=[{customerId:'C1',deliveryNo:'DL-OWN',outboundNo:'OUT-C1',packageCount:2},{customerId:'C1',deliveryNo:'DL-EXT',outboundNo:'OUT-C1',packageCount:1}]
|
||||
const detail={dispatchNo:'OWN',status:'ACTIVE',stage:'DELIVERING',recordVersion:'31',counts:{packageCount:3,pickedPackageCount:3,shippedPackageCount:3},documents:{deliveries:refs}}
|
||||
const items={dispatchNo:'OWN',recordVersion:'31',items:cargo.map(row=>({dispatchItemId:row.dispatchItemId,customerId:row.customerId,warehousePalletId:row.warehousePalletId,scheduledPackages:row.packages,pickedPackages:row.packages}))}
|
||||
const orders=refs.map(ref=>({...ref,dispatchNo:'OWN',status:'DELIVERING',version:'1',documentSummary:{categories:[{fileCount:0}]}}))
|
||||
const proof={protocol:'NATIVE_DISPATCH_V1',marker:'RUN',tripNo:'TR-OWN',dispatchNo:'OWN',warehouseId:'W1',customerId:'C1',status:'CHECKING',stage:'TRIP_OPERATING_COSTS',checkpoints:[],
|
||||
dispatchNos:['OWN'],picker:{membershipId:'OLD-PDA'},departureRequest:{idempotencyKey:'old-key'},departureSnapshot:{detail:structuredClone(detail),items:structuredClone(items)},departureDeliveries:structuredClone(orders),
|
||||
departure:{action:'DEPART',dispatchNo:'OWN',status:'ACTIVE',stage:'DELIVERING',recordVersion:'31',result:{departedAt:snapshotTrip.departedAt,shippedPackages:3,deliveries:refs}}}
|
||||
const trip={marker:'RUN',tripNo:'TR-OWN',dispatchNo:'OWN',warehouseId:'W1',confirmedLoad:{manifestHash:'a'.repeat(64),pallets:measured},costAcceptance:{marker:'RUN',status:'FAILED',costs:[],departure,
|
||||
requests:[{method:'GET',status:'RESPONDED',http:200,code:'200',requestId:'r',traceId:'t'}]},externalReceipt:{status:'VERIFIED',bookingNo:'BK',warehousePalletId:'P2'},externalDispatch:{dispatchNo:'OWN',deliveryNo:'DL-EXT'}}
|
||||
const report={success:false,steps:[{name:'独立预期覆盖全部实物且没有重叠托盘',passed:false},{name:'模块执行',passed:false}],createdResources:{fleetAcceptance:proof,fleetTrips:[trip],fleetCostActors:{status:'DISABLED'},fleetFeeCoverageCases:[{mode:'PARTIAL',dispatchNo:'OWN',proof:{deliveryNos:['DL-OWN','DL-EXT']}}]}}
|
||||
const base='/api/platform/v1/last-leg/fleet/trips/TR-OWN'
|
||||
const responses=new Map([[base,{trip:structuredClone(snapshotTrip),dispatches:[{dispatchNo:'OWN'}]}],
|
||||
[base+'/history?page=1&size=100',{total:1,records:[structuredClone(departure)]}],
|
||||
[base+'/costs?page=1&size=100',{page:{records:[],total:0},summary:{manifestHash:'a'.repeat(64),departureVersion:'27',totals:[]}}],[base+'/handovers',{records:[],total:0}],
|
||||
['/api/platform/v1/last-leg/dispatches/OWN',structuredClone(detail)],['/api/platform/v1/last-leg/dispatches/OWN/items',structuredClone(items)]])
|
||||
for(const order of orders)responses.set(`/api/platform/v1/last-leg/delivery-orders/${order.deliveryNo}?customerId=C1`,{order:structuredClone(order)})
|
||||
const calls=[],ctx={marker:'RUN',token:'writer',context:{},createdResources:report.createdResources,async checkpoint(){calls.push('checkpoint')},
|
||||
async request(method,path){assert.equal(method,'GET','recovery verification must never write');calls.push(path);assert.ok(responses.has(path),path);return {http:200,body:{code:'200',data:responses.get(path)},requestId:'r',traceId:'t'}}}
|
||||
return {report,ctx,calls,responses,base,trip}
|
||||
}
|
||||
|
||||
function createdVehicleRecoveryFixture() {
|
||||
const f = afterCostsRecoveryFixture(), snapshot = { id: '2106360046045675522', plateNumber: 'HO-24255909-3BC', recordVersion: '0',
|
||||
status: 'IN_SERVICE', ownership: 'OWNED', region: 'OVERSEAS', maxPalletSpaces: 30, maxLoadKg: 18000, maxVolumeCbm: 90 }
|
||||
@@ -51,57 +45,40 @@ function createdVehicleRecoveryFixture() {
|
||||
}
|
||||
|
||||
function returnSourceRecoveryFixture() {
|
||||
const f = afterCostsRecoveryFixture(), { report, trip, responses, base, ctx } = f, run = report.createdResources.fleetAcceptance
|
||||
run.stage = 'RETURN_REDISPATCH'; run.receipts = []; run.departure.shippedPackages = 5
|
||||
report.steps = [{ name: '模块执行', passed: false, evidence: 'Fresh redispatch fixture requires source A with at least 3 boxes and a separate domestic source B' }]
|
||||
const current = { ...responses.get(base).trip, recordVersion: '30', vehicleId: 'V2', driverId: 'D2', vehiclePlate: 'NEW', driverName: 'NEW', driverPhone: 'NEW' }
|
||||
responses.get(base).trip = current
|
||||
const original = trip.costAcceptance.departure
|
||||
original.snapshot.confirmedLoad.cargo = [{ dispatchNo: 'OWN', dispatchItemId: 'IA', warehousePalletId: 'PA', packages: 3 },
|
||||
{ dispatchNo: 'OWN', dispatchItemId: 'IB', warehousePalletId: 'PB', packages: 1 }, { dispatchNo: 'EXT', dispatchItemId: 'IX', warehousePalletId: 'PX', packages: 1 }]
|
||||
const history = { records: [structuredClone(original), { action: 'HANDOVER', recordVersion: '28' },
|
||||
{ action: 'RELEASE', recordVersion: '29' }, { action: 'RELEASE', recordVersion: '30' }], total: 4 }
|
||||
responses.set(base + '/history?page=1&size=100', history)
|
||||
const handover = { records: [{ handoverNo: 'HO-CURRENT', recordVersion: '2' }], total: 1 }
|
||||
const vehicle = { id: 'V2', recordVersion: '0', plateNumber: 'NEW', status: 'IN_SERVICE' }
|
||||
const driver = { id: 'D2', recordVersion: '2', active: true, reviewStatus: 'APPROVED', licenseFileId: 'FILE', details: {} }
|
||||
trip.handover = { status: 'VERIFIED', finalTrip: structuredClone(current), handoverNo: 'HO-CURRENT', requests: [
|
||||
{ method: 'GET', path: base + '/history?page=1&size=100', result: structuredClone(history) },
|
||||
{ method: 'GET', path: base + '/handovers?page=1&size=10', result: structuredClone(handover) },
|
||||
{ method: 'POST', path: '/api/platform/v1/last-leg/master-data/vehicles', result: structuredClone(vehicle) },
|
||||
{ method: 'POST', path: '/api/platform/v1/last-leg/fleet/drivers/D2/review', result: structuredClone(driver) }] }
|
||||
responses.set(base + '/handovers', handover); responses.set('/api/platform/v1/last-leg/master-data/vehicles/V2', vehicle)
|
||||
responses.set('/api/platform/v1/last-leg/fleet/drivers/D2', driver)
|
||||
ctx.context.readRecoveryTripResources = async () => [{ resourceKind: 'VEHICLE', resourceId: 'V2', tripNo: 'TR-OWN' }, { resourceKind: 'DRIVER', resourceId: 'D2', tripNo: 'TR-OWN' }]
|
||||
const rows = [['A', 3, 'OWN'], ['B', 1, 'OWN'], ['EXT', 1, 'EXT']]
|
||||
const originals = rows.map(([label, packages, dispatchNo]) => ({ deliveryNo: 'DL-' + label, outboundNo: 'OUT-' + label, dispatchNo,
|
||||
customerId: 'C1', overseasWarehouseId: 'W1', status: 'WAIT_DELIVERY', version: '2', packageCount: packages,
|
||||
documentSummary: { categories: [{ fileCount: 0 }] } }))
|
||||
trip.returnRedispatch = { marker: 'RUN', status: 'FAILED', failure: report.steps[0].evidence, originalDeliveries: originals,
|
||||
requests: originals.map(order => ({ method: 'PUT', path: `/api/platform/v1/last-leg/delivery-orders/${order.deliveryNo}/status?customerId=C1`,
|
||||
body: { status: 'DELIVERING', expectedVersion: 2 }, status: 'RESPONDED', http: 200, code: '200', requestId: 'r', traceId: 't',
|
||||
result: { deliveryNo: order.deliveryNo, deliveryStatus: 'DELIVERING', replayed: false } })) }
|
||||
report.createdResources.fleetFeeCoverageCases[0].proof.deliveryNos = ['DL-A', 'DL-B']
|
||||
for (const [label, packages, dispatchNo] of rows) {
|
||||
const order = originals.find(row => row.deliveryNo === 'DL-' + label)
|
||||
responses.set(`/api/platform/v1/last-leg/delivery-orders/${order.deliveryNo}?customerId=C1`, { order: { ...structuredClone(order),
|
||||
status: 'DELIVERING', version: '3', vehiclePlate: 'NEW', driverName: 'NEW', driverPhone: 'NEW' } })
|
||||
const relation = { lastMileOrderId: label, inboundCode: 'IN-' + label, deliveryNo: order.deliveryNo, outboundNo: order.outboundNo,
|
||||
returnSources: [{ maxReturnPackages: packages }] }
|
||||
responses.set(`/api/platform/v1/last-leg/delivery-orders/${order.deliveryNo}/return-inbound-options?customerId=C1`, [relation])
|
||||
responses.set(`/api/platform/v1/last-leg/orders/${label}?customerId=C1`, { order: { id: label, customerId: 'C1', overseasWarehouseId: 'W1',
|
||||
orderType: label === 'EXT' ? 'WAREHOUSE_TRANSFER' : 'FIRST_LEG', sourceInboundId: 'SOURCE-' + label, inboundCode: relation.inboundCode, packageCount: packages } })
|
||||
}
|
||||
for (const no of ['OWN', 'EXT']) {
|
||||
const cargo = original.snapshot.confirmedLoad.cargo.filter(row => row.dispatchNo === no), total = cargo.reduce((n,row)=>n+row.packages,0)
|
||||
responses.set('/api/platform/v1/last-leg/dispatches/' + no, { dispatchNo: no, status: 'DEPARTED', overseasWarehouseId: 'W1',
|
||||
packageCount: total, pickedPackageCount: total, shippedPackageCount: total,
|
||||
items: cargo.map(row=>({id:row.dispatchItemId,warehousePalletId:row.warehousePalletId,packageCount:row.packages})) })
|
||||
const f=afterCostsRecoveryFixture(),{report,trip,responses,base,ctx}=f,run=report.createdResources.fleetAcceptance
|
||||
run.stage='RETURN_REDISPATCH';run.receipts=[];run.departure.result.shippedPackages=5
|
||||
report.steps=[{name:'模块执行',passed:false,evidence:'Fresh redispatch fixture requires source A with at least 3 boxes and a separate domestic source B'}]
|
||||
const current={...responses.get(base).trip,recordVersion:'30',vehicleId:'V2',driverId:'D2',vehiclePlate:'NEW',driverName:'NEW',driverPhone:'NEW'}
|
||||
responses.get(base).trip=current
|
||||
const original=trip.costAcceptance.departure
|
||||
original.snapshot.confirmedLoad.cargo=[{dispatchNo:'OWN',customerId:'C1',dispatchItemId:'IA',warehousePalletId:'PA',packages:3},
|
||||
{dispatchNo:'OWN',customerId:'C1',dispatchItemId:'IB',warehousePalletId:'PB',packages:1},{dispatchNo:'OWN',customerId:'C1',dispatchItemId:'IX',warehousePalletId:'PX',packages:1}]
|
||||
const history={records:[structuredClone(original),{action:'HANDOVER',recordVersion:'28'},{action:'RELEASE',recordVersion:'29'},{action:'RELEASE',recordVersion:'30'}],total:4}
|
||||
responses.set(base+'/history?page=1&size=100',history)
|
||||
const handover={records:[{handoverNo:'HO-CURRENT',recordVersion:'2'}],total:1},vehicle={id:'V2',recordVersion:'0',plateNumber:'NEW',status:'IN_SERVICE'},driver={id:'D2',recordVersion:'2',active:true,reviewStatus:'APPROVED',licenseFileId:'FILE',details:{}}
|
||||
trip.handover={status:'VERIFIED',finalTrip:structuredClone(current),handoverNo:'HO-CURRENT',requests:[
|
||||
{method:'GET',path:base+'/history?page=1&size=100',result:structuredClone(history)},{method:'GET',path:base+'/handovers?page=1&size=10',result:structuredClone(handover)},
|
||||
{method:'POST',path:'/api/platform/v1/last-leg/master-data/vehicles',result:structuredClone(vehicle)},{method:'POST',path:'/api/platform/v1/last-leg/fleet/drivers/D2/review',result:structuredClone(driver)}]}
|
||||
responses.set(base+'/handovers',handover);responses.set('/api/platform/v1/last-leg/master-data/vehicles/V2',vehicle);responses.set('/api/platform/v1/last-leg/fleet/drivers/D2',driver)
|
||||
ctx.context.readRecoveryTripResources=async()=>[{resourceKind:'VEHICLE',resourceId:'V2',tripNo:'TR-OWN'},{resourceKind:'DRIVER',resourceId:'D2',tripNo:'TR-OWN'}]
|
||||
const rows=[['A',3],['B',1],['EXT',1]]
|
||||
const originals=rows.map(([label,packages])=>({deliveryNo:'DL-'+label,outboundNo:'OUT-C1',dispatchNo:'OWN',customerId:'C1',overseasWarehouseId:'W1',status:'DELIVERING',version:'3',packageCount:packages,documentSummary:{categories:[{fileCount:0}]}}))
|
||||
trip.returnRedispatch={marker:'RUN',status:'FAILED',failure:report.steps[0].evidence,originalDeliveries:originals,
|
||||
requests:originals.map(order=>({method:'GET',path:`/api/platform/v1/last-leg/delivery-orders/${order.deliveryNo}?customerId=C1`,status:'RESPONDED',http:200,code:'200',requestId:'r',traceId:'t',result:{order:structuredClone(order)}}))}
|
||||
report.createdResources.fleetFeeCoverageCases[0].proof.deliveryNos=['DL-A','DL-B','DL-EXT']
|
||||
const detail={dispatchNo:'OWN',status:'ACTIVE',stage:'DELIVERING',recordVersion:'31',counts:{packageCount:5,pickedPackageCount:5,shippedPackageCount:5},documents:{deliveries:originals.map(o=>({deliveryNo:o.deliveryNo,outboundNo:o.outboundNo,customerId:o.customerId,packageCount:o.packageCount}))}}
|
||||
const items={dispatchNo:'OWN',recordVersion:'31',items:original.snapshot.confirmedLoad.cargo.map(c=>({dispatchItemId:c.dispatchItemId,customerId:c.customerId,warehousePalletId:c.warehousePalletId,scheduledPackages:c.packages,pickedPackages:c.packages}))}
|
||||
run.departureSnapshot={detail:structuredClone(detail),items:structuredClone(items)}
|
||||
responses.set('/api/platform/v1/last-leg/dispatches/OWN',detail);responses.set('/api/platform/v1/last-leg/dispatches/OWN/items',items)
|
||||
for(const[label,packages]of rows){const order=originals.find(o=>o.deliveryNo==='DL-'+label)
|
||||
responses.set(`/api/platform/v1/last-leg/delivery-orders/${order.deliveryNo}?customerId=C1`,{order:{...structuredClone(order),vehiclePlate:'NEW',driverName:'NEW',driverPhone:'NEW'}})
|
||||
const relation={lastMileOrderId:label,inboundCode:'IN-'+label,deliveryNo:order.deliveryNo,outboundNo:order.outboundNo,returnSources:[{maxReturnPackages:packages}]}
|
||||
responses.set(`/api/platform/v1/last-leg/delivery-orders/${order.deliveryNo}/return-inbound-options?customerId=C1`,[relation])
|
||||
responses.set(`/api/platform/v1/last-leg/orders/${label}?customerId=C1`,{order:{id:label,customerId:'C1',overseasWarehouseId:'W1',orderType:label==='EXT'?'WAREHOUSE_TRANSFER':'FIRST_LEG',sourceInboundId:'SOURCE-'+label,inboundCode:relation.inboundCode,packageCount:packages}})
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
test('source-selection recovery only accepts original three explicit starts and rejects any actual return or unsigned result', () => {
|
||||
test('source-selection recovery accepts three definitive native reads and rejects any actual return or unknown result', () => {
|
||||
const f = returnSourceRecoveryFixture()
|
||||
assert.equal(reviewedFullCheckpoint(f.report, 'RETURN_SOURCE_SELECTION_BEFORE_RETURN').returnSourceSelection, true)
|
||||
for (const change of [x=>x.trip.returnRedispatch.requests.push({ method:'POST',path:'/mark-return' }),
|
||||
@@ -226,7 +203,7 @@ for (const mutation of ['none', 'version', 'history', 'cost', 'handover', 'cargo
|
||||
if (mutation === 'history') responses.get(base + '/history?page=1&size=100').records[0].snapshot.confirmedLoad.manifestHash = 'changed'
|
||||
if (mutation === 'cost') responses.get(base + '/costs?page=1&size=100').page.records.push({ costNo: 'COST' })
|
||||
if (mutation === 'handover') responses.get(base + '/handovers').records.push({ handoverNo: 'H' })
|
||||
if (mutation === 'cargo') responses.get('/api/platform/v1/last-leg/dispatches/OWN').items[0].packageCount = 3
|
||||
if (mutation === 'cargo') responses.get('/api/platform/v1/last-leg/dispatches/OWN/items').items[0].scheduledPackages = 3
|
||||
const delivery = responses.get('/api/platform/v1/last-leg/delivery-orders/DL-OWN?customerId=C1').order
|
||||
if (mutation === 'signature') delivery.signedAt = 'now'
|
||||
if (mutation === 'document') delivery.documentSummary.categories[0].fileCount = 1
|
||||
@@ -241,7 +218,7 @@ test('reviewed recovery archives old evidence and creates fresh sessions before
|
||||
resolveAccess: async () => ({}), makePicker: async child => { assert.match(child.marker, /RECOVERY-PDA/); calls.push('new-picker'); return { membershipId: 'NEW', token: 'new-pda' } },
|
||||
closePicker: async () => calls.push('close-picker'),
|
||||
complete: async (context, actualTrip, picker, dispatches) => {
|
||||
assert.equal(actualTrip, trip); assert.equal(trip.costAcceptance, undefined); assert.equal(picker.membershipId, 'NEW'); assert.equal(dispatches.length, 2)
|
||||
assert.equal(actualTrip, trip); assert.equal(trip.costAcceptance, undefined); assert.equal(picker.membershipId, 'NEW'); assert.equal(dispatches.length, 1)
|
||||
assert.equal(trip.previousReadOnlyCostAttempts[0], oldCost); assert.equal(context.createdResources.fleetAcceptance.previousPicker.membershipId, 'OLD-PDA')
|
||||
calls.push('continue-from-costs')
|
||||
},
|
||||
@@ -283,7 +260,7 @@ test('changed source report SHA blocks recovery before creating context or loggi
|
||||
rmSync(temporary, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
import { parseFleetArguments, fleetExecutionPlan, runFleetAcceptance, reviewedFullCheckpoint, verifyFleetTerminalState, runFleetHandoverStage, fleetCostShares, prepareFleetCostActors, findFleetActorPhone, guardFleetWrites, redactFleetWrite, verifyDepartedRecovery, continueReviewedDeparture, completeDepartedFleetScenario, readReviewedVehicleOccupation, verifyReturnSourceRecovery, continueReviewedReturnSources } from './fleet-test-env-acceptance.mjs'
|
||||
import { selectNativeFleetFullSource, nativeFleetDeliveries, departNativeFleet, currentFleetMeasurements, parseFleetArguments, fleetExecutionPlan, runFleetAcceptance, reviewedFullCheckpoint, verifyFleetTerminalState, runFleetHandoverStage, fleetCostShares, prepareFleetCostActors, findFleetActorPhone, guardFleetWrites, redactFleetWrite, verifyDepartedRecovery, continueReviewedDeparture, completeDepartedFleetScenario, readReviewedVehicleOccupation, verifyReturnSourceRecovery, continueReviewedReturnSources } from './fleet-test-env-acceptance.mjs'
|
||||
|
||||
for (const response of [
|
||||
{ http: 0 }, { http: 503, body: { code: 'A0500' }, requestId: 'r', traceId: 't' },
|
||||
@@ -302,6 +279,7 @@ for (const response of [
|
||||
body.expectedVersion = 99
|
||||
assert.equal(saved[0].body.expectedVersion, 6)
|
||||
assert.equal(saved[1].status, 'UNKNOWN_RESULT')
|
||||
assert.equal(ctx.createdResources.fleetWriteBoundary.status, 'STOPPED')
|
||||
for (const method of ['POST', 'GET', 'DELETE']) await assert.rejects(ctx.request(method, '/must-not-run'), /Unknown write outcome/)
|
||||
assert.deepEqual(calls, ['/orders/1/remarks?tenantId=2'])
|
||||
})
|
||||
@@ -481,10 +459,10 @@ test('return redispatch is explicit, fresh-only and declares source-event proof
|
||||
})
|
||||
|
||||
test('handover stage exports only a verified same-run departure fixture before the optional UI checkpoint', async () => {
|
||||
const order = [], departure = { tripNo: 'TR-NEW', status: 'IN_TRANSIT' }
|
||||
const order = [], departure = { action:'DEPART', dispatchNo:'PC-NEW', status:'ACTIVE', stage:'DELIVERING', result:{departedAt:'2026-10-19T00:00:00Z',shippedPackages:1} }
|
||||
const context = { marker: 'CURRENT', context: { saveFleetHandoverFixture(value) { order.push('save'); assert.equal(value.marker, 'CURRENT') } },
|
||||
createdResources: { fleetAcceptance: { departure } } }
|
||||
const trip = { tripNo: 'TR-NEW', warehouseId: '92', costAcceptance: { status: 'VERIFIED', marker: 'CURRENT', expected: { finalCny: '100.03', usd: '13.07' } } }, details = [{ dispatchNo: 'PC-NEW' }]
|
||||
createdResources: { fleetAcceptance: { tripNo:'TR-NEW', dispatchNo:'PC-NEW', departure } } }
|
||||
const trip = { tripNo: 'TR-NEW', dispatchNo:'PC-NEW', warehouseId: '92', costAcceptance: { status: 'VERIFIED', marker: 'CURRENT', expected: { finalCny: '100.03', usd: '13.07' } } }, details = [{ dispatchNo: 'PC-NEW' }]
|
||||
const verified = { status: 'VERIFIED', marker: 'CURRENT', originalDeparture: { snapshot: { confirmedLoad: { pallets: [] } } },
|
||||
browser: { tripNo: 'TR-NEW' }, handoverNo: 'HO-NEW' }
|
||||
const result = await runFleetHandoverStage(context, trip, 'secret-picker-token', details, {
|
||||
@@ -499,16 +477,22 @@ test('handover stage exports only a verified same-run departure fixture before t
|
||||
}
|
||||
})
|
||||
|
||||
test('cost shares use controlled physical pallets and the original external booking, never statistical totals', () => {
|
||||
const trip = { externalReceipt: { status: 'VERIFIED', bookingNo: 'BK-ONE', warehousePalletId: '3' }, externalDispatch: { dispatchNo: 'EXT' } }
|
||||
const details = [{ dispatchNo: 'OWN', items: [{ warehousePalletId: '1' }, { warehousePalletId: '1' }, { warehousePalletId: '2' }] },
|
||||
{ dispatchNo: 'EXT', items: [{ warehousePalletId: '3' }] }]
|
||||
test('one dispatch cost shares use actual physical measurements and the original external booking', () => {
|
||||
const trip = { dispatchNo: 'OWN', externalReceipt: { status: 'VERIFIED', bookingNo: 'BK-ONE', warehousePalletId: '3' },
|
||||
externalDispatch: { dispatchNo: 'OWN' }, confirmedLoad: { manifestHash: 'ACTUAL', pallets: [
|
||||
{ warehousePalletId: '1', palletSpaces: 2 }, { warehousePalletId: '2', palletSpaces: 3 }, { warehousePalletId: '3', palletSpaces: 4 }] } }
|
||||
const details = [{ detail: { dispatchNo: 'OWN', recordVersion: '0' }, items: { dispatchNo: 'OWN', recordVersion: '0',
|
||||
items: [{ warehousePalletId: '1', customerId: 'A' }, { warehousePalletId: '1', customerId: 'B' },
|
||||
{ warehousePalletId: '2', customerId: 'A' }, { warehousePalletId: '3', customerId: 'B' }] } }]
|
||||
assert.deepEqual(fleetCostShares(trip, details), [
|
||||
{ businessNo: 'OWN', sourceType: 'OWN', dispatchNo: 'OWN', warehousePalletIds: ['1', '2'], palletSpaces: 2 },
|
||||
{ businessNo: 'BK-ONE', sourceType: 'EXTERNAL', dispatchNo: 'EXT', warehousePalletIds: ['3'], palletSpaces: 1 },
|
||||
{ businessNo: 'OWN', sourceType: 'OWN', dispatchNo: 'OWN', warehousePalletIds: ['1', '2'], palletSpaces: 5 },
|
||||
{ businessNo: 'BK-ONE', sourceType: 'EXTERNAL', dispatchNo: 'OWN', warehousePalletIds: ['3'], palletSpaces: 4 },
|
||||
])
|
||||
assert.throws(() => fleetCostShares(trip, [{ ...details[0], items: [{ warehousePalletId: '3' }] }, details[1]]), /distinct/)
|
||||
assert.throws(() => fleetCostShares(trip, [details[0], { ...details[1], items: [{ warehousePalletId: '4' }] }]), /receipt/)
|
||||
assert.throws(() => fleetCostShares({ ...trip, confirmedLoad: null }, details), /measurements/)
|
||||
assert.throws(() => fleetCostShares(trip, [{ ...details[0], items: { ...details[0].items, recordVersion: '1' } }]), /version/)
|
||||
assert.throws(() => fleetCostShares({ ...trip, externalReceipt: { ...trip.externalReceipt, warehousePalletId: '4' } }, details), /distinct/)
|
||||
assert.throws(() => fleetCostShares({ ...trip, confirmedLoad: { ...trip.confirmedLoad,
|
||||
pallets: [...trip.confirmedLoad.pallets, trip.confirmedLoad.pallets[0]] } }, details), /measurements/)
|
||||
})
|
||||
|
||||
test('missing isolated cost actor preparation rejects before readers or fixture creation', async () => {
|
||||
@@ -592,16 +576,18 @@ test('phone lookup failure stops without any user creation or guessing', async (
|
||||
assert.equal(calls, 1)
|
||||
})
|
||||
|
||||
test('main chain performs handover after actual DEPART and before receipts, and cleanup follows current-resource closure', async () => {
|
||||
const fullSource = readFileSync(new URL('./fleet-test-env-acceptance.mjs', import.meta.url), 'utf8')
|
||||
const source = fullSource.slice(fullSource.indexOf('export async function runFleetScenario'))
|
||||
const savedDeparture = source.indexOf('proof.departure = departed'), handover = source.indexOf("stage('IN_TRANSIT_HANDOVER')")
|
||||
const receipt = source.indexOf("stage('RECEIPTS_AND_RESOURCE_RELEASE')"), closure = source.indexOf('proof.closure = await closeTrip')
|
||||
const cleanup = source.indexOf('await cleanup(context, MODULE, trip)')
|
||||
assert.ok(savedDeparture > 0 && savedDeparture < handover && handover < receipt && receipt < closure && closure < cleanup)
|
||||
assert.match(source, /finally \{ await disablePicker\(context, picker, 71\) \}/)
|
||||
test('main chain performs handover after actual native DEPART and before receipts, and cleanup follows current-resource closure', async () => {
|
||||
const fullSource=readFileSync(new URL('./fleet-test-env-acceptance.mjs',import.meta.url),'utf8')
|
||||
const source=fullSource.slice(fullSource.indexOf('export async function runFleetScenario'))
|
||||
assert.ok(source.indexOf('const departure = await departNativeFleet')<source.indexOf('await completeDepartedFleetScenario'))
|
||||
const handover=source.indexOf("stage('IN_TRANSIT_HANDOVER')"),receipt=source.indexOf("stage('RECEIPTS_AND_RESOURCE_RELEASE')"),closure=source.indexOf('proof.closure=await closeTrip'),cleanup=source.indexOf('await cleanup(context,MODULE,trip)')
|
||||
assert.ok(handover>0&&handover<receipt&&receipt<closure&&closure<cleanup)
|
||||
assert.match(source,/finally \{ await disablePicker\(context, picker, 71\) \}/)
|
||||
assert.doesNotMatch(source,/dispatches\/(?:unbind|bind)|READY_TO_DEPART|CANCEL_FULL_AND_REARRANGE/)
|
||||
assert.ok(source.indexOf('proof.fullExecution.closure = await finishFleetTrip')<source.indexOf("stage('RESERVATIONS')"))
|
||||
assert.ok(source.indexOf('await disablePicker(context, fullPicker, 161)')<source.indexOf("stage('RESERVATIONS')"))
|
||||
assert.ok(source.indexOf("stage('RESERVATIONS')")<source.indexOf('createPicker(child, 71'))
|
||||
})
|
||||
|
||||
async function executeFixture(failProducer = false) {
|
||||
const temporary = mkdtempSync(join(tmpdir(), 'fleet-entry-contract-'))
|
||||
const root = join(temporary, 'chongchong-global', 'baiyue-firstleg'); mkdirSync(root, { recursive: true })
|
||||
@@ -707,9 +693,9 @@ test('reviewed resume requires explicit paired checkpoint options', () => {
|
||||
|
||||
test('checkpoint guard forbids repeating commands whose write outcome may be unknown', () => {
|
||||
const report = { success: false, createdResources: {
|
||||
fleetAcceptance: { marker: 'M', stage: 'FULL_COVERAGE', status: 'CHECKING', tripNo: 'T', originalDispatch: { dispatchNo: 'D' } },
|
||||
fleetTrips: [{ tripNo: 'T', dispatchNo: 'D' }],
|
||||
fleetFeeCoverageCases: [{ mode: 'FULL', marker: 'M', dispatchNo: 'D', proof: { status: 'CHECKING', requests: [{ method: 'GET' }] } }],
|
||||
fleetAcceptance: { protocol:'NATIVE_DISPATCH_V1', dispatchNo:'D2', dispatchNos:['D2'], fullTripNo:'F', marker: 'M', stage: 'FULL_COVERAGE', status: 'CHECKING', tripNo: 'T', originalDispatch: { detail:{dispatchNo:'D2'} } },
|
||||
fleetTrips: [{ tripNo: 'T', dispatchNo: 'D2' },{tripNo:'F',dispatchNo:'D'}],
|
||||
fleetFeeCoverageCases: [{ mode: 'FULL', marker: 'M', dispatchNo: 'D', proof: { status: 'CHECKING', requests: [{ method: 'GET',status:'RESPONDED',http:200,code:'200',requestId:'r',traceId:'t' }] } },{mode:'PARTIAL',marker:'M',dispatchNo:'D2'}],
|
||||
} }
|
||||
assert.equal(reviewedFullCheckpoint(report).marker, 'M')
|
||||
const mutated = structuredClone(report)
|
||||
@@ -719,16 +705,19 @@ test('checkpoint guard forbids repeating commands whose write outcome may be unk
|
||||
mutated.createdResources.fleetFeeCoverageCases[0].proof.outboundNo = 'OUT-1'
|
||||
assert.throws(() => reviewedFullCheckpoint(mutated), /unknown outcomes/)
|
||||
assert.throws(() => reviewedFullCheckpoint(mutated, 'FULL_COVERAGE_PERSISTED'), /exact successful command/)
|
||||
Object.assign(mutated.createdResources.fleetFeeCoverageCases[0].proof, { deliveryNos: ['DEL-1'], confirmationRequest: { idempotencyKey: 'original-key' } })
|
||||
mutated.createdResources.fleetFeeCoverageCases[0].proof.requests.push({ label: '正式保存本次原费用服务覆盖并转派送', method: 'POST', http: 200, code: '200' })
|
||||
Object.assign(mutated.createdResources.fleetFeeCoverageCases[0].proof, { deliveryNos: ['DEL-1'], confirmationRequest: { recordVersion:0,groups:[{groupKey:'G'}],idempotencyKey: 'original-key' } })
|
||||
mutated.createdResources.fleetFeeCoverageCases[0].proof.requests.push({ path:'/api/platform/v1/last-leg/dispatches/D/transfer-delivery',label: '正式保存本次原费用服务覆盖并转派送', method: 'POST',status:'RESPONDED',requestId:'r',traceId:'t',http: 200, code: '200',
|
||||
body: structuredClone(mutated.createdResources.fleetFeeCoverageCases[0].proof.confirmationRequest), result: { action:'ARRANGE_DELIVERY',dispatchNo:'D',status:'ACTIVE',stage:'AFTER_LOADING',result:{outbounds:[{outboundNo:'OUT-1'}],deliveries:[{deliveryNo:'DEL-1'}]} } })
|
||||
assert.equal(reviewedFullCheckpoint(mutated, 'FULL_COVERAGE_PERSISTED').persisted, true)
|
||||
mutated.createdResources.fleetFeeCoverageCases[0].proof.requests.at(-1).body.idempotencyKey = 'different-key'
|
||||
assert.throws(() => reviewedFullCheckpoint(mutated, 'FULL_COVERAGE_PERSISTED'), /exact successful command/)
|
||||
})
|
||||
|
||||
test('receipt-ready reconciliation cannot repeat a receipt that has already started', () => {
|
||||
const report = { success: false, steps: [{ name: '创建海外PDA仓库操作员71', code: 'A0409', message: '手机号已存在' }], createdResources: {
|
||||
fleetAcceptance: { marker: 'M', stage: 'RESERVATIONS', status: 'CHECKING', tripNo: 'T' },
|
||||
fleetAcceptance: { protocol:'NATIVE_DISPATCH_V1',dispatchNo:'D2',dispatchNos:['D2'],fullExecution:{closure:{status:'CARGO_COMPLETED_AND_RESOURCES_RELEASED'}},marker: 'M', stage: 'RESERVATIONS', status: 'CHECKING', tripNo: 'T' },
|
||||
fleetTrips: [{ tripNo: 'T', dispatchNo: 'D2', reservation: { status: 'VERIFIED' } }],
|
||||
fleetFeeCoverageCases: [{ mode: 'FULL', proof: { status: 'VERIFIED' } }, { mode: 'PARTIAL', dispatchNo: 'D2', proof: { status: 'VERIFIED' } }],
|
||||
fleetFeeCoverageCases: [{ mode: 'FULL', proof: { status: 'VERIFIED' } }, { mode: 'PARTIAL', dispatchNo: 'D2' }],
|
||||
} }
|
||||
assert.equal(reviewedFullCheckpoint(report, 'EXTERNAL_RECEIPT_READY').receiptReady, true)
|
||||
report.createdResources.fleetTrips[0].externalReceipt = { status: 'CHECKING' }
|
||||
@@ -737,12 +726,12 @@ test('receipt-ready reconciliation cannot repeat a receipt that has already star
|
||||
|
||||
test('palletized checkpoint requires a saved successful command, owned operator, and no later confirmation', () => {
|
||||
const report = { success: false, createdResources: {
|
||||
fleetAcceptance: { marker: 'M', stage: 'EXTERNAL_RECEIPT', status: 'CHECKING', tripNo: 'T', picker: { membershipId: '71' }, pickerMarker: 'M-PDA' },
|
||||
fleetAcceptance: { protocol:'NATIVE_DISPATCH_V1',dispatchNo:'D2',dispatchNos:['D2'],fullExecution:{closure:{status:'CARGO_COMPLETED_AND_RESOURCES_RELEASED'}},marker: 'M', stage: 'EXTERNAL_RECEIPT', status: 'CHECKING', tripNo: 'T', picker: { membershipId: '71' }, pickerMarker: 'M-PDA' },
|
||||
fleetTrips: [{ tripNo: 'T', dispatchNo: 'D2', reservation: { status: 'VERIFIED' }, externalReceipt: { status: 'CHECKING',
|
||||
warehousePalletId: 'P1', palletizeRequest: { idempotencyKey: 'original' }, requests: [
|
||||
{ label: 'PDA正式完成外接收货及库存入账', method: 'POST', http: 200, code: '200' },
|
||||
] } }],
|
||||
fleetFeeCoverageCases: [{ mode: 'FULL', proof: { status: 'VERIFIED' } }, { mode: 'PARTIAL', dispatchNo: 'D2', proof: { status: 'VERIFIED' } }],
|
||||
fleetFeeCoverageCases: [{ mode: 'FULL', proof: { status: 'VERIFIED' } }, { mode: 'PARTIAL', dispatchNo: 'D2' }],
|
||||
} }
|
||||
assert.equal(reviewedFullCheckpoint(report, 'EXTERNAL_PALLETIZED').palletized, true)
|
||||
assert.throws(() => reviewedFullCheckpoint(report, 'EXTERNAL_RECEIPT_CONFIRMED'), /exact successful PDA command/)
|
||||
@@ -765,35 +754,32 @@ test('palletized checkpoint requires a saved successful command, owned operator,
|
||||
})
|
||||
|
||||
function terminalFixture(fault = '') {
|
||||
const finalTrip = { status: 'COMPLETED', completedAt: '2026-10-02T22:01:00Z', recordVersion: '30', vehicleRelease: { endLocation: 'ONT8' }, driverRelease: { endLocation: 'ONT8' } }
|
||||
const closure = { status: 'CARGO_COMPLETED_AND_RESOURCES_RELEASED', finalTrip, probes: [{ tripNo: 'PROBE' }] }
|
||||
const trip = { tripNo: 'T', closure, externalReceipt: { status: 'VERIFIED' }, reassignment: { status: 'VERIFIED' }, externalDispatch: { status: 'VERIFIED', dispatchNo: 'EXT', deliveryNo: 'DEL-EXT' } }
|
||||
const proof = { marker: 'M', tripNo: 'T', customerId: '92', stage: 'RECEIPTS_AND_RESOURCE_RELEASE', closure,
|
||||
receipts: ['DEL-OWN', 'DEL-EXT'].map(deliveryNo => ({ deliveryNo, signedAt: '2026-10-02T22:00:00' })),
|
||||
departure: { shippedPackages: 6 }, finance: { status: 'CONSUMER_PERSISTED', source: { sourceFeeId: 'F1', sourceVersion: 1 } } }
|
||||
const resources = { fleetAcceptance: proof, fleetTrips: [trip], fleetFeeCoverageCases: [{ mode: 'PARTIAL', dispatchNo: 'OWN', proof: { deliveryNos: ['DEL-OWN'] } }] }
|
||||
const calls = []
|
||||
const context = { createdResources: resources, assert() {}, async request(method, path) {
|
||||
calls.push({ method, path }); assert.equal(method, 'GET')
|
||||
let data
|
||||
if (path.endsWith('/trips/T')) data = { trip: finalTrip, dispatches: ['OWN', 'EXT'].map(dispatchNo => ({ dispatchNo })) }
|
||||
else if (path.endsWith('/trips/PROBE')) data = { trip: { status: 'CANCELLED' } }
|
||||
else if (path.includes('/dispatches/')) { const number = path.split('/').at(-1), count = number === 'OWN' ? 5 : 1
|
||||
data = { dispatchNo: number, status: 'DEPARTED', packageCount: count, pickedPackageCount: count, shippedPackageCount: fault === 'quantity' ? 0 : count } }
|
||||
else if (path.includes('/delivery-orders/')) data = { order: { deliveryNo: path.split('/').at(-1).split('?')[0], status: fault === 'unsigned' ? 'WAIT_DELIVERY' : 'DELIVERED', signedAt: '2026-10-02T22:00:00' } }
|
||||
else if (path.includes('/fee-pool?')) data = [{ customerId: '92', sourceId: 'F1', sourceVersion: fault === 'wrong-fee-version' ? 2 : 1, amount: 30, currency: 'CNY', sourceType: 'OVERSEAS_DELIVERY_FEE' }]
|
||||
const finalTrip={status:'COMPLETED',completedAt:'2026-10-02T22:01:00Z',recordVersion:'30',vehicleRelease:{endLocation:'ONT8'},driverRelease:{endLocation:'ONT8'}}
|
||||
const closure={status:'CARGO_COMPLETED_AND_RESOURCES_RELEASED',finalTrip,subsequentTripClaimProtection:'VERIFIED_NATIVE_EMPTY_DISPATCH',nextDispatch:{dispatchNo:'PROBE',finalStatus:'CANCELLED'}}
|
||||
const trip={tripNo:'T',dispatchNo:'OWN',closure,externalReceipt:{status:'VERIFIED'},reassignment:{status:'REJECTED_WITHOUT_MUTATION'},externalDispatch:{status:'VERIFIED',dispatchNo:'OWN',deliveryNo:'DEL-EXT'}}
|
||||
const refs=[{customerId:'92',deliveryNo:'DEL-OWN',outboundNo:'OUT',packageCount:5},{customerId:'92',deliveryNo:'DEL-EXT',outboundNo:'OUT',packageCount:1}]
|
||||
const proof={protocol:'NATIVE_DISPATCH_V1',marker:'M',tripNo:'T',dispatchNo:'OWN',dispatchNos:['OWN'],customerId:'92',stage:'RECEIPTS_AND_RESOURCE_RELEASE',closure,
|
||||
receipts:refs.map(row=>({customerId:'92',deliveryNo:row.deliveryNo,signedAt:'2026-10-02T22:00:00'})),
|
||||
departure:{action:'DEPART',dispatchNo:'OWN',status:'ACTIVE',stage:'DELIVERING',result:{departedAt:'2026-10-02T20:00:00Z',shippedPackages:6}},finance:{customerId:'92',status:'CONSUMER_PERSISTED',source:{sourceFeeId:'F1',sourceVersion:1}}}
|
||||
const resources={fleetAcceptance:proof,fleetTrips:[trip],fleetFeeCoverageCases:[{mode:'PARTIAL',dispatchNo:'OWN',proof:{deliveryNos:['DEL-OWN','DEL-EXT']}}]}
|
||||
const calls=[],context={createdResources:resources,assert(){},async request(method,path){
|
||||
calls.push({method,path});assert.equal(method,'GET');let data
|
||||
if(path.endsWith('/trips/T'))data={trip:finalTrip,dispatches:[{dispatchNo:'OWN'}]}
|
||||
else if(path.endsWith('/dispatches/PROBE'))data={dispatchNo:'PROBE',status:'CANCELLED',counts:{packageCount:0,pickedPackageCount:0}}
|
||||
else if(path.endsWith('/dispatches/OWN'))data={dispatchNo:'OWN',status:'ACTIVE',stage:'DELIVERED',counts:{packageCount:6,pickedPackageCount:6,shippedPackageCount:fault==='quantity'?0:6},documents:{deliveries:refs}}
|
||||
else if(path.includes('/delivery-orders/'))data={order:{customerId:'92',deliveryNo:path.split('/').at(-1).split('?')[0],status:fault==='unsigned'?'DELIVERING':'DELIVERED',signedAt:'2026-10-02T22:00:00'}}
|
||||
else if(path.includes('/fee-pool?'))data=[{customerId:'92',sourceId:'F1',sourceVersion:fault==='wrong-fee-version'?2:1,amount:30,currency:'CNY',sourceType:'OVERSEAS_DELIVERY_FEE'}]
|
||||
else throw Error('unexpected path')
|
||||
return { http: 200, body: { code: '200', data: structuredClone(data) }, requestId: 'r', traceId: 't' }
|
||||
} }
|
||||
return { context, calls, report: { success: false, createdResources: resources, steps: [{ name: '本次车次货物结案及车辆司机释放完成', passed: false }] } }
|
||||
return {http:200,body:{code:'200',data:structuredClone(data)},requestId:'r',traceId:'t'}
|
||||
}}
|
||||
return {context,calls,report:{success:false,createdResources:resources,steps:[{name:'本次车次货物结案及车辆司机释放完成',passed:false}]}}
|
||||
}
|
||||
|
||||
test('terminal reconciliation reads actual completion, every dispatch/delivery and exact fee without replaying writes', async () => {
|
||||
const f = terminalFixture()
|
||||
assert.equal(reviewedFullCheckpoint(f.report, 'TERMINAL_READONLY').terminal, true)
|
||||
const result = await verifyFleetTerminalState(f.context)
|
||||
assert.equal(result.terminalReadback.status, 'VERIFIED')
|
||||
assert.equal(f.calls.length, 7)
|
||||
assert.equal(f.calls.length, 6)
|
||||
assert.ok(f.calls.every(row => row.method === 'GET'))
|
||||
const invalid = terminalFixture().report
|
||||
invalid.steps.push({ name: '真实业务失败', passed: false })
|
||||
@@ -805,3 +791,150 @@ for (const fault of ['quantity', 'unsigned', 'wrong-fee-version']) test(`termina
|
||||
await assert.rejects(() => verifyFleetTerminalState(f.context))
|
||||
assert.equal(f.context.createdResources.fleetAcceptance.terminalReadback.status, 'CHECKING')
|
||||
})
|
||||
|
||||
function nativeFullSourceFixture() {
|
||||
const context = { marker: 'RUN', createdResources: {
|
||||
phase4OverseasPda: { marker: 'RUN', finalStatus: 'COMPLETED', customerId: '11', overseasWarehouseId: '12', palletIds: ['101','102','103'] },
|
||||
phase4OverseasDispatch: { marker: 'RUN', status: 'VERIFIED', dispatchNo: 'PARTIAL', customerId: '11', recordVersion: '9007199254740993',
|
||||
routeLifecycle: { stages: [{ label: '新增同目的地第二托', snapshot: { items: [{ sourceKey: 'PLAN:23', warehousePalletId: '103' }] } }] } }
|
||||
} }
|
||||
const original = { warehouseId: '12', data: { dispatchNo: 'PARTIAL', status: 'ACTIVE', stage: 'BEFORE_LOADING', recordVersion: '9007199254740993',
|
||||
counts: { pickedPackageCount: 0, shippedPackageCount: 0 } }, items: { items: [
|
||||
{ sourceKey: 'PLAN:21', warehousePalletId: '101', destination: { key: 'FBA:A' } },
|
||||
{ sourceKey: 'PLAN:22', warehousePalletId: '102', destination: { key: 'FBA:B' } }] } }
|
||||
const candidate = { customerId: '11', warehouseId: '12', availablePackages: 2, blockReasons: [], appointmentBatches: [], sourceKey: 'PLAN:23',
|
||||
sourceVersion: 'a'.repeat(64), destination: { type: 'FBA', key: 'FBA:A' }, selection: {
|
||||
sourceType: 'INBOUND_PLAN', planPalletId: '23', warehousePalletId: '103', sourceVersion: 'a'.repeat(64) } }
|
||||
return { context, original, candidate }
|
||||
}
|
||||
|
||||
test('FULL consumes only the current-run released free duplicate while PARTIAL retains its distinct two destinations', () => {
|
||||
const f = nativeFullSourceFixture()
|
||||
assert.equal(selectNativeFleetFullSource(f.context, f.original, [f.candidate]), f.candidate)
|
||||
for (const mutate of [
|
||||
x => x.original.data.recordVersion = '9007199254740992',
|
||||
x => x.original.data.counts.pickedPackageCount = 1,
|
||||
x => x.original.items.items[1].destination.key = 'FBA:A',
|
||||
x => x.candidate.customerId = '99', x => x.candidate.warehouseId = '99',
|
||||
x => x.candidate.blockReasons.push('已占用'), x => x.candidate.availablePackages = 0,
|
||||
x => x.context.createdResources.phase4OverseasPda.palletIds = ['101','102'],
|
||||
x => x.context.createdResources.phase4OverseasDispatch.routeLifecycle.stages = [],
|
||||
x => x.original.items.items[0].warehousePalletId = '103',
|
||||
x => x.original.items.items[0].sourceKey = 'PLAN:23'
|
||||
]) { const invalid = nativeFullSourceFixture(); mutate(invalid); assert.throws(() => selectNativeFleetFullSource(invalid.context, invalid.original, [invalid.candidate])) }
|
||||
assert.throws(() => selectNativeFleetFullSource(f.context, f.original, [f.candidate, structuredClone(f.candidate)]), /exactly one/)
|
||||
})
|
||||
|
||||
test('native delivery references deduplicate the shared external destination but reject conflicting customer or cargo ownership', () => {
|
||||
const own = { customerId: '11', deliveryNo: 'D-A', outboundNo: 'OUT-A', groupKey: 'G-A', packageCount: 3 }
|
||||
const external = { customerId: '22', deliveryNo: 'D-B', outboundNo: 'OUT-B', groupKey: 'G-B', packageCount: 1 }
|
||||
const detail = { dispatchNo: 'MAIN', documents: { deliveries: [own, external, structuredClone(external)] } }
|
||||
assert.deepEqual(nativeFleetDeliveries(detail), [own, external])
|
||||
for (const changes of [{ customerId: '33' }, { outboundNo: 'FOREIGN' }, { packageCount: 2 }, { groupKey: 'OTHER' }]) {
|
||||
assert.throws(() => nativeFleetDeliveries({ ...detail, documents: { deliveries: [own, external, { ...external, ...changes }] } }))
|
||||
}
|
||||
assert.throws(() => nativeFleetDeliveries({ dispatchNo: 'MAIN', documents: { deliveries: [{ ...own, customerId: null }] } }))
|
||||
assert.throws(() => nativeFleetDeliveries({ dispatchNo: 'MAIN', documents: { deliveries: [] } }))
|
||||
})
|
||||
|
||||
function nativeDepartureFixture(fault = '') {
|
||||
const refs = [{ customerId: '11', deliveryNo: 'DEL-A', outboundNo: 'OUT-A', packageCount: 2 },
|
||||
{ customerId: '22', deliveryNo: 'DEL-B', outboundNo: 'OUT-B', packageCount: 1 }]
|
||||
const before = { dispatchNo: 'MAIN', status: 'ACTIVE', stage: 'AFTER_LOADING', recordVersion: '9007199254740993', pickingReconciled: true,
|
||||
counts: { packageCount: 3, pickedPackageCount: 3, shippedPackageCount: 0 }, documents: { deliveries: refs } }
|
||||
const task = { ...structuredClone(before), warehouseId: '12', actions: { DEPART: { allowed: true } } }
|
||||
const items = { dispatchNo: 'MAIN', recordVersion: before.recordVersion, items: [
|
||||
{ dispatchItemId: '1', scheduledPackages: 2, pickedPackages: 2 }, { dispatchItemId: '2', scheduledPackages: 1, pickedPackages: 1 }] }
|
||||
if (fault === 'stale-task') task.recordVersion = '9007199254740992'
|
||||
if (fault === 'wrong-main') task.dispatchNo = 'OTHER'
|
||||
if (fault === 'not-reconciled') task.pickingReconciled = false
|
||||
if (fault === 'not-loaded') before.stage = 'LOADING'
|
||||
if (fault === 'not-arranged') task.actions.DEPART.allowed = false
|
||||
const command = { dispatchNo: 'MAIN', status: 'ACTIVE', stage: 'DELIVERING', action: 'DEPART', recordVersion: '9007199254740994', replayed: false,
|
||||
result: { departedAt: '2026-10-19T01:00:00Z', shippedPackages: 3, deliveries: structuredClone(refs) } }
|
||||
const after = { ...structuredClone(before), stage: 'DELIVERING', recordVersion: command.recordVersion,
|
||||
counts: { packageCount: 3, pickedPackageCount: 3, shippedPackageCount: 3 } }
|
||||
let departed = false
|
||||
const calls = [], saved = [], run = { tripNo: 'TRIP', dispatchNo: 'MAIN' }, trip = { tripNo: 'TRIP', dispatchNo: 'MAIN', warehouseId: '12' }
|
||||
const context = { async checkpoint() { saved.push(structuredClone(run)) }, async request(method, path, body, token) {
|
||||
calls.push({ method, path, body: structuredClone(body), token }); let value
|
||||
if (method === 'POST') {
|
||||
assert.deepEqual(saved[0].departureRequest, body, 'original current-version command is durable before sending')
|
||||
assert.equal(body.recordVersion, '9007199254740993')
|
||||
if (path.startsWith('/api/pda/')) {
|
||||
assert.equal(token, 'pda'); departed = true; value = structuredClone(command)
|
||||
if (fault === 'changed-owner') value.result.deliveries[0].customerId = '99'
|
||||
if (fault === 'changed-quantities') { value.result.deliveries[0].packageCount = 1; value.result.deliveries[1].packageCount = 2 }
|
||||
} else {
|
||||
assert.equal(token, undefined); assert.deepEqual(body, calls.find(row => row.method === 'POST').body)
|
||||
value = { ...structuredClone(command), replayed: fault !== 'replay-miss' }
|
||||
if (fault === 'replay-version') value.recordVersion = '9007199254740995'
|
||||
}
|
||||
} else if (path.startsWith('/api/pda/')) { assert.equal(token, 'pda'); value = task }
|
||||
else if (path.endsWith('/items')) value = { ...items, recordVersion: departed ? after.recordVersion : before.recordVersion }
|
||||
else if (path.includes('/delivery-orders/')) {
|
||||
const ref = refs.find(row => path.includes('/' + row.deliveryNo + '?'))
|
||||
assert.ok(path.endsWith('customerId=' + ref.customerId))
|
||||
value = { order: { ...ref, dispatchNo: 'MAIN', status: 'DELIVERING', version: '3' } }
|
||||
if (fault === 'foreign-delivery') value.order.customerId = '99'
|
||||
} else value = departed ? after : before
|
||||
return { http: 200, body: { code: '200', data: structuredClone(value) }, requestId: 'r', traceId: 't' }
|
||||
} }
|
||||
return { context, trip, run, calls, saved }
|
||||
}
|
||||
|
||||
test('one native current-version PDA DEPART replays exactly through platform and retains every customer delivery version', async () => {
|
||||
const f = nativeDepartureFixture()
|
||||
const result = await departNativeFleet(f.context, f.trip, 'pda', f.run)
|
||||
assert.equal(result.departed, f.run.departure)
|
||||
assert.deepEqual(f.run.dispatchNos, ['MAIN'])
|
||||
assert.equal(result.dispatchDetails.length, 1)
|
||||
assert.deepEqual(f.run.departureDeliveries.map(row => [row.customerId,row.version]), [['11','3'],['22','3']])
|
||||
assert.equal(f.calls.filter(row => row.method === 'POST').length, 2)
|
||||
const before = f.calls.length
|
||||
await assert.rejects(departNativeFleet(f.context, f.trip, 'pda', f.run), /original command/)
|
||||
assert.equal(f.calls.length, before, 'no fresh key after an original command exists')
|
||||
})
|
||||
|
||||
for (const fault of ['stale-task','wrong-main','not-reconciled','not-loaded','not-arranged','changed-owner','changed-quantities','replay-miss','replay-version','foreign-delivery'])
|
||||
test(`native departure refuses stale or conflicting actual evidence: ${fault}`, async () => {
|
||||
const f = nativeDepartureFixture(fault)
|
||||
await assert.rejects(departNativeFleet(f.context, f.trip, 'pda', f.run))
|
||||
if (['stale-task','wrong-main','not-reconciled','not-loaded','not-arranged'].includes(fault)) {
|
||||
assert.equal(f.calls.filter(row => row.method === 'POST').length, 0)
|
||||
assert.equal(f.saved.length, 0)
|
||||
} else assert.ok(f.run.departureRequest?.idempotencyKey, 'sent requests retain their original key')
|
||||
})
|
||||
|
||||
test('physical loading requires current-run actual per-field evidence and never fills pallet spaces from planned capacity', async () => {
|
||||
const calls = [], trip = { dispatchNo: 'MAIN', stops: [{ destinationKey: 'ORIGIN:12' }, { destinationKey: 'FBA:A' }] }
|
||||
const preview = { dispatchNo: 'MAIN', status: 'ACTIVE', pallets: [{ warehousePalletId: '101', palletNo: 'PALLET', palletSpaces: null, weightKg: null, volumeCbm: null }] }
|
||||
const context = { marker: 'RUN', createdResources: { phase4OverseasDispatch: {} }, async request(method, path) {
|
||||
assert.equal(method, 'GET'); calls.push(path)
|
||||
return { http: 200, body: { code: '200', data: structuredClone(preview) }, requestId: 'r', traceId: 't' }
|
||||
} }
|
||||
await assert.rejects(currentFleetMeasurements(context, trip), /Missing current-run actual palletSpaces/)
|
||||
const actual = { warehousePalletId: '101', palletSpaces: 2, weightKg: 121, volumeCbm: 1.37, deliveryIndex: 1,
|
||||
evidence: ['palletSpaces','weightKg','volumeCbm'].map(field => ({ field, value: { palletSpaces: 2, weightKg: 121, volumeCbm: 1.37 }[field],
|
||||
actual: true, sourceType: 'PDA_MEASUREMENT', sourceBusinessNo: 'CURRENT-INCOMING', observedAt: '2026-10-19T01:00:00Z' })) }
|
||||
context.createdResources.phase4OverseasDispatch.loadingMeasurements = { environment: 'TEST', marker: 'RUN', dispatchNo: 'MAIN', pallets: [actual] }
|
||||
assert.deepEqual((await currentFleetMeasurements(context, trip)).pallets, [actual])
|
||||
for (const mutation of [receipt => receipt.marker = 'OLD', receipt => receipt.dispatchNo = 'OTHER',
|
||||
receipt => receipt.pallets[0].evidence[0].actual = false, receipt => receipt.pallets[0].evidence[0].value = 1,
|
||||
receipt => receipt.pallets.push({ ...receipt.pallets[0], warehousePalletId: '999' })]) {
|
||||
const receipt = structuredClone(context.createdResources.phase4OverseasDispatch.loadingMeasurements)
|
||||
mutation(receipt)
|
||||
await assert.rejects(currentFleetMeasurements({ ...context, createdResources: { phase4OverseasDispatch: { loadingMeasurements: receipt } } }, trip))
|
||||
}
|
||||
assert.equal(calls.length, 7)
|
||||
})
|
||||
|
||||
test('native checkpoint rejects retired flat reports, multi-main binding and globally unknown writes', () => {
|
||||
for (const mutation of [report => delete report.createdResources.fleetAcceptance.protocol,
|
||||
report => report.createdResources.fleetAcceptance.dispatchNos.push('OTHER'),
|
||||
report => report.createdResources.fleetTrips[0].dispatchNo = 'OTHER',
|
||||
report => report.createdResources.fleetWriteBoundary = { status: 'STOPPED', outcome: 'UNKNOWN_RESULT' }]) {
|
||||
const report = terminalFixture().report; mutation(report)
|
||||
assert.throws(() => reviewedFullCheckpoint(report, 'TERMINAL_READONLY'), /native single-dispatch reports/)
|
||||
}
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user