91 lines
6.6 KiB
JavaScript
91 lines
6.6 KiB
JavaScript
import test from 'node:test'
|
|||
|
|
import assert from 'node:assert/strict'
|
||
|
|
import { verifyFleetExternalDispatch } from './fleet-external-dispatch-acceptance.mjs'
|
||
|
|
|
||
|
|
function fixture(fault = '') {
|
||
|
|
const segment = { fromIndex: 0, toIndex: 1, occupied: { palletSpaces: 2, weightKg: 200, volumeCbm: 2 },
|
||
|
|
remaining: { palletSpaces: 28, weightKg: 17800, volumeCbm: 88 } }
|
||
|
|
const tripProof = { tripNo: 'TR-CURRENT', warehouseId: '92', dispatchNo: 'DP-OWN',
|
||
|
|
stops: [{ name: '起点' }, { name: 'ONT8', destinationKey: 'FBA:ONT8' }], externalReceipt: {
|
||
|
|
status: 'VERIFIED', createRequest: { customerId: '900719925474099301' }, incomingNo: 'WT-CURRENT', bookingNo: 'FB-CURRENT',
|
||
|
|
warehousePalletId: '900719925474099311', palletNo: 'PALLET-EXT', finalSegments: [segment], finalBooking: {
|
||
|
|
cargo: { destinationKey: 'FBA:ONT8' }, receipt: { source: { pallets: [{ weightKg: 100, volumeCbm: 1 }] } },
|
||
|
|
},
|
||
|
|
} }
|
||
|
|
let version = 10, linked = false, replanned = false, transferred = false
|
||
|
|
const calls = [], assertions = []
|
||
|
|
const customerId = tripProof.externalReceipt.createRequest.customerId
|
||
|
|
const item = { warehousePalletId: tripProof.externalReceipt.warehousePalletId, packageCount: 1 }
|
||
|
|
const booking = () => ({ bookingNo: 'FB-CURRENT', status: linked ? 'BOUND' : 'RECEIVED', incomingNo: 'WT-CURRENT', recordVersion: linked ? 4 : 3,
|
||
|
|
dispatchNo: linked ? 'DP-EXT' : null })
|
||
|
|
const binding = () => ({ tripRecordVersion: version, booking: booking(), replayed: false })
|
||
|
|
const dispatches = [{ dispatchNo: 'DP-OWN', review: { status: 'PENDING' } }]
|
||
|
|
const detail = () => ({ trip: { recordVersion: version, maxPalletSpaces: 30, vehiclePlate: 'TEST-TRUCK', driverName: 'TEST-DRIVER', driverPhone: '19900000000' }, dispatches })
|
||
|
|
const response = (data, message = '') => ({ http: 200, body: { code: message ? 'A0409' : '200', message, data: structuredClone(data) }, requestId: 'req', traceId: 'trace' })
|
||
|
|
const context = { marker: 'OFFLINE', createdResources: { fleetTrips: [tripProof] },
|
||
|
|
assert(module, name, res, passed, evidence, options) { assertions.push({ name, passed, options }) },
|
||
|
|
async request(method, path, body) {
|
||
|
|
calls.push({ method, path, body })
|
||
|
|
if (path.endsWith('/capacity-plan')) {
|
||
|
|
if (method === 'POST') { replanned = true; version++ }
|
||
|
|
return response({ recordVersion: version, manifestHash: 'a'.repeat(64), planValid: !linked || replanned || fault === 'stale-plan',
|
||
|
|
capacityPlan: { pallets: [{ warehousePalletId: 'OWN-PALLET', palletSpaces: 1, weightKg: 100, volumeCbm: 1, deliveryIndex: 1 }] },
|
||
|
|
cargo: [{ dispatchNo: 'DP-OWN', warehousePalletId: 'OWN-PALLET', packages: 1 }, ...(linked ? [{ dispatchNo: 'DP-EXT', warehousePalletId: tripProof.externalReceipt.warehousePalletId, packages: 1 }] : [])],
|
||
|
|
availableSegments: replanned && fault === 'double-capacity' ? [{ ...segment, remaining: { ...segment.remaining, palletSpaces: 27 } }] : [segment] })
|
||
|
|
}
|
||
|
|
if (path.includes('/candidates?')) return response([{ customerId: fault === 'foreign-customer' ? 'wrong' : customerId, overseasWarehouseId: '92',
|
||
|
|
inboundCode: 'WT-CURRENT', warehousePalletId: tripProof.externalReceipt.warehousePalletId, palletNo: 'PALLET-EXT', packageCount: 1,
|
||
|
|
sourceStage: 'RECEIVED', planPalletId: 'PLAN-PALLET' }])
|
||
|
|
if (path.endsWith('/drafts/containers')) return response({ id: 'CONTAINER' })
|
||
|
|
if (path.endsWith('/draft-locks/lock')) return response(null)
|
||
|
|
if (path.endsWith('/pre-arrange')) return response({ dispatchNo: 'DP-EXT', packageCount: 1, pickedPackageCount: 0, shippedPackageCount: 0, items: [item] })
|
||
|
|
if (path.endsWith('/bookings')) return response({ tripRecordVersion: version, records: [booking()] })
|
||
|
|
if (path.endsWith('/bookings/FB-CURRENT/dispatch')) {
|
||
|
|
if (linked) return response({ ...binding(), replayed: true })
|
||
|
|
linked = true; version++; dispatches.push({ dispatchNo: 'DP-EXT', review: { status: 'PENDING' } }); return response(binding())
|
||
|
|
}
|
||
|
|
if (path.endsWith('/review')) {
|
||
|
|
assert.equal(body.recordVersion, version); version++
|
||
|
|
const number = path.split('/').at(-2)
|
||
|
|
if (fault !== 'review-not-applied') dispatches.find(row => row.dispatchNo === number).review.status = 'APPROVED'
|
||
|
|
return response(detail())
|
||
|
|
}
|
||
|
|
if (path.endsWith('/trips/TR-CURRENT')) return response(detail())
|
||
|
|
if (path.endsWith('/external-freight')) return response({ requiresDeliveryFee: false, snapshotHash: 'b'.repeat(64), quotes: [
|
||
|
|
{ bookingNo: 'FB-CURRENT', incomingNo: 'WT-CURRENT', currency: 'CNY', amount: fault === 'stale-price' ? 50 : 30, quantity: 1, unitPrice: 30 },
|
||
|
|
] })
|
||
|
|
if (path.endsWith('/transfer-delivery')) {
|
||
|
|
if (body.fees.length) return response(null, '本排车全部货物已带入外接约定运费,请勿重复填写派送应收')
|
||
|
|
const replayed = transferred; transferred = true
|
||
|
|
return response({ dispatchNo: 'DP-EXT', outboundNo: 'OUT-EXT', packageCount: 1, deliveries: [{ deliveryNo: 'DEL-EXT' }], replayed })
|
||
|
|
}
|
||
|
|
assert.match(path, /\/delivery-orders\/DEL-EXT\?customerId=900719925474099301$/)
|
||
|
|
const rows = [{ feeDirection: 'RECEIVABLE', feeType: 'DELIVERY_FEE', currency: 'CNY', amount: 30, sourceStatus: 'ACTIVE', reviewStatus: 'APPROVED' }]
|
||
|
|
if (fault === 'duplicate-fee' && calls.filter(row => row.path === path).length > 1) rows.push({ ...rows[0] })
|
||
|
|
return response({ order: { dispatchNo: 'DP-EXT' }, fees: rows })
|
||
|
|
},
|
||
|
|
}
|
||
|
|
return { context, tripProof, calls, assertions }
|
||
|
|
}
|
||
|
|
|
||
|
|
test('external dispatch helper preserves reservation when binding physical cargo and publishes only agreed source fee', async () => {
|
||
|
|
const f = fixture()
|
||
|
|
const proof = await verifyFleetExternalDispatch(f.context, 'fleet-dispatch', f.tripProof)
|
||
|
|
assert.equal(proof.status, 'VERIFIED')
|
||
|
|
assert.equal(proof.dispatchNo, 'DP-EXT')
|
||
|
|
assert.equal(proof.sourceFees.length, 1)
|
||
|
|
assert.equal(proof.financeAcknowledgement, 'NOT_VERIFIED')
|
||
|
|
assert.ok(f.assertions.every(row => row.passed && row.options.deploymentBlocker))
|
||
|
|
assert.ok(f.calls.every(row => !/\/pick|\/depart|\/cancel/.test(row.path)))
|
||
|
|
await assert.rejects(() => verifyFleetExternalDispatch(f.context, 'fleet-dispatch', f.tripProof), /尚未执行关联/)
|
||
|
|
})
|
||
|
|
|
||
|
|
for (const fault of ['foreign-customer', 'stale-plan', 'double-capacity', 'review-not-applied', 'stale-price', 'duplicate-fee']) {
|
||
|
|
test(`external dispatch proof blocks ${fault}`, async () => {
|
||
|
|
const f = fixture(fault)
|
||
|
|
await assert.rejects(() => verifyFleetExternalDispatch(f.context, 'fleet-dispatch', f.tripProof))
|
||
|
|
assert.equal(f.tripProof.externalDispatch.status, 'CHECKING')
|
||
|
|
assert.ok(f.assertions.some(row => !row.passed))
|
||
|
|
})
|
||
|
|
}
|