diff --git a/scripts/admin-test-env-modules/fleet-quotes-operations.mjs b/scripts/admin-test-env-modules/fleet-quotes-operations.mjs index 640cb3ec6..694f8a10c 100644 --- a/scripts/admin-test-env-modules/fleet-quotes-operations.mjs +++ b/scripts/admin-test-env-modules/fleet-quotes-operations.mjs @@ -12,9 +12,18 @@ export function operationsFixtureFromHandover(bytes, { sha256, marker, runId }) || f.departure?.tripNo !== f.tripNo || f.departure.status !== 'IN_TRANSIT' || !Number.isFinite(Date.parse(f.departure.departedAt)) || snapshot.trip.departedAt !== f.departure.departedAt || !load?.manifestHash || c?.status !== 'VERIFIED' || c.marker !== marker || c.tripNo !== f.tripNo - || c.expected?.finalCny !== '100.03' || c.expected?.usd !== '13.07' || c.expected?.allocationMethod !== 'SAME_ROUTE_PALLET_SPACES') fail() + || c.expected?.finalCny !== '100.03' || c.expected?.usd !== '13.07') fail() const positive = value => Number.isSafeInteger(Number(value)) && Number(value) > 0 if (!Array.isArray(load.pallets) || !load.pallets.length || !Array.isArray(load.cargo) || !load.cargo.length || !Array.isArray(c.shares) || !c.shares.length) fail() + const stops = snapshot.trip.stops + if (!Array.isArray(stops) || load.pallets.some(p => !Number.isSafeInteger(p.deliveryIndex) || p.deliveryIndex <= 0 + || p.deliveryIndex >= stops.length || !stops[p.deliveryIndex]?.destinationKey + || !load.cargo.some(row => String(row.warehousePalletId) === String(p.warehousePalletId)) + || load.cargo.filter(row => String(row.warehousePalletId) === String(p.warehousePalletId)).some(row => row.destinationKey !== stops[p.deliveryIndex].destinationKey))) fail() + const sameRoute = new Set(load.pallets.map(p => p.deliveryIndex)).size === 1 + if (sameRoute ? c.expected.allocationMethod !== 'SAME_ROUTE_PALLET_SPACES' || c.expected.allocationStatus !== 'AVAILABLE' + : c.expected.allocationMethod !== null || c.expected.allocationStatus !== 'UNAVAILABLE' + || c.expected.allocationBlockReason !== '各票卸货站不同,暂不可按同路线托位自动分摊') fail() const pallets = new Map() for (const p of load.pallets) { if (!p.warehousePalletId || pallets.has(String(p.warehousePalletId)) || !positive(p.palletSpaces)) fail(); pallets.set(String(p.warehousePalletId), Number(p.palletSpaces)) } const seen = new Set(), shipped = new Map() @@ -53,6 +62,7 @@ export function operationsFixtureFromHandover(bytes, { sha256, marker, runId }) const total = [...shipped.values()].reduce((a, b) => a + b, 0), ext = [...external.values()].reduce((a, b) => a + b, 0) if (total !== Number(f.departure.shippedPackages) || ext <= 0 || ext >= total) fail() const costs = [['CNY', '100.03'], ['USD', '13.07']].map(([currency, amount]) => { + if (!sameRoute) return { currency, directCost: Number(amount), ownCost: null, externalCost: null } const allocations = expectedAllocations(amount, c.shares) const sum = type => Number(allocations.filter(row => row.sourceType === type).reduce((n, row) => n + BigInt(row.cents), 0n)) / 100 return { currency, directCost: Number(amount), ownCost: sum('OWN'), externalCost: sum('EXTERNAL') } diff --git a/scripts/fleet-operations-test-env-contract.test.mjs b/scripts/fleet-operations-test-env-contract.test.mjs index 53858bf45..d8d8aa68f 100644 --- a/scripts/fleet-operations-test-env-contract.test.mjs +++ b/scripts/fleet-operations-test-env-contract.test.mjs @@ -10,11 +10,11 @@ function sample() { return { marker, runId: 'current-run', tripNo, warehouseId: '92', status: 'DEPART_AND_HANDOVER_VERIFIED', handoverNo: 'HO-CURRENT', departure: { tripNo, status: 'IN_TRANSIT', departedAt, shippedPackages: 14 }, immutableDeparture: { action: 'DEPART', recordVersion: '5', snapshot: { - trip: { tripNo, warehouseId: '92', departedAt, remark: marker }, confirmedLoad: { manifestHash: 'original-load', - pallets: [{ warehousePalletId: '1', palletSpaces: 1 }, { warehousePalletId: '2', palletSpaces: 1 }], - cargo: [{ dispatchNo: 'DP-OWN', dispatchItemId: '1', customerId: '9', warehousePalletId: '1', packages: 10 }, { dispatchNo: 'DP-EXT', dispatchItemId: '2', customerId: '9', warehousePalletId: '2', packages: 4 }] }, + trip: { tripNo, warehouseId: '92', departedAt, remark: marker, stops: [{ name: 'origin' }, { destinationKey: 'FBA:A' }, { destinationKey: 'FBA:B' }] }, confirmedLoad: { manifestHash: 'original-load', + pallets: [{ warehousePalletId: '1', palletSpaces: 1, deliveryIndex: 1 }, { warehousePalletId: '2', palletSpaces: 1, deliveryIndex: 1 }], + cargo: [{ dispatchNo: 'DP-OWN', dispatchItemId: '1', customerId: '9', warehousePalletId: '1', packages: 10, destinationKey: 'FBA:A' }, { dispatchNo: 'DP-EXT', dispatchItemId: '2', customerId: '9', warehousePalletId: '2', packages: 4, destinationKey: 'FBA:A' }] }, externalBookings: [{ bookingNo: 'FB-EXT', status: 'BOUND', dispatchNo: 'DP-EXT', customerId: '9', agreedAmount: '30.00', cargo: { currency: 'CNY' }, receipt: { source: { receivedPackages: 4, pallets: [{ warehousePalletId: '2', packages: 4 }] } } }] } }, - costs: { marker, tripNo, status: 'VERIFIED', expected: { finalCny: '100.03', usd: '13.07', allocationMethod: 'SAME_ROUTE_PALLET_SPACES' }, shares: [ + costs: { marker, tripNo, status: 'VERIFIED', expected: { finalCny: '100.03', usd: '13.07', allocationMethod: 'SAME_ROUTE_PALLET_SPACES', allocationStatus: 'AVAILABLE' }, shares: [ { businessNo: 'DP-OWN', sourceType: 'OWN', dispatchNo: 'DP-OWN', palletSpaces: 1, warehousePalletIds: ['1'] }, { businessNo: 'FB-EXT', sourceType: 'EXTERNAL', dispatchNo: 'DP-EXT', palletSpaces: 1, warehousePalletIds: ['2'] }] } } } @@ -30,6 +30,14 @@ test('adapts actual C artifact with independent physical cargo and exact cent cu { currency: 'USD', directCost: 13.07, ownCost: 6.54, externalCost: 6.53 }] }) assert.equal(f.stage, 'CURRENT_RUN_DEPARTED'); assert.equal(f.departureVerified, true) }) +test('multi-stop costs retain full trip amounts and never infer source shares', () => { + const f = sample(), load = f.immutableDeparture.snapshot.confirmedLoad + load.pallets[1].deliveryIndex = 2; load.cargo[1].destinationKey = 'FBA:B' + Object.assign(f.costs.expected, { allocationMethod: null, allocationStatus: 'UNAVAILABLE', allocationBlockReason: '各票卸货站不同,暂不可按同路线托位自动分摊' }) + assert.deepEqual(convert(f).expected.costs, [{ currency: 'CNY', directCost: 100.03, ownCost: null, externalCost: null }, { currency: 'USD', directCost: 13.07, ownCost: null, externalCost: null }]) + f.costs.expected.allocationMethod = 'SAME_ROUTE_PALLET_SPACES' + assert.throws(() => convert(f)) +}) for (const [name, mutate] of [ ['stale marker', f => { f.marker = 'OLD' }], ['stale run', f => { f.runId = 'old' }], ['unverified costs', f => { f.costs.status = 'CHECKING' }], ['wrong currency amount', f => { f.costs.expected.usd = '0' }],