fix(fleet): verify dispatch cargo and retain checked costs on continuation
This commit is contained in:
@@ -15,7 +15,7 @@ function departedRecoveryFixture() {
|
||||
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' }] },
|
||||
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'] } }] } }
|
||||
@@ -38,6 +38,46 @@ function departedRecoveryFixture() {
|
||||
return { report, ctx, calls, responses, base, trip }
|
||||
}
|
||||
|
||||
function afterCostsRecoveryFixture() {
|
||||
const fixture = departedRecoveryFixture(), { report, trip, responses, base } = fixture
|
||||
report.createdResources.fleetAcceptance.stage = 'IN_TRANSIT_HANDOVER'
|
||||
report.steps[0].name = '每张排车已发车且货量与客户起运仓一致'
|
||||
trip.costAcceptance.status = 'VERIFIED'
|
||||
trip.costAcceptance.costs = [{ costNo: 'CNY-COST' }, { costNo: 'USD-COST' }]
|
||||
trip.costAcceptance.final = { page: { records: structuredClone(trip.costAcceptance.costs), total: 2 },
|
||||
summary: { manifestHash: 'a'.repeat(64), departureVersion: '27', totals: [{ currency: 'CNY', directCost: '100.03' }, { currency: 'USD', directCost: '13.07' }], allocationBlockReason: 'MULTI_STOP' } }
|
||||
trip.handover = { status: 'FAILED', requests: [{ method: 'GET', status: 'RESPONDED', http: 200, code: '200' }] }
|
||||
responses.set(base + '/costs?page=1&size=100', structuredClone(trip.costAcceptance.final))
|
||||
return fixture
|
||||
}
|
||||
|
||||
test('after-cost recovery requires its exact known read-only handover failure and rejects any handover write', () => {
|
||||
const { report } = afterCostsRecoveryFixture()
|
||||
assert.equal(reviewedFullCheckpoint(report, 'DEPARTED_AFTER_COSTS_BEFORE_HANDOVER').costsAlreadyVerified, true)
|
||||
report.createdResources.fleetTrips[0].handover.requests.push({ method: 'POST', status: 'RESPONDED', http: 200, code: '200' })
|
||||
assert.throws(() => reviewedFullCheckpoint(report, 'DEPARTED_AFTER_COSTS_BEFORE_HANDOVER'), /checkpoint/)
|
||||
})
|
||||
|
||||
test('after-cost recovery checks original cost contents and refuses a changed amount before any write', async () => {
|
||||
const { ctx, responses, base } = afterCostsRecoveryFixture()
|
||||
responses.get(base + '/costs?page=1&size=100').summary.totals[0].directCost = '100.04'
|
||||
await assert.rejects(continueReviewedDeparture(ctx, { costsAlreadyVerified: true,
|
||||
prepareActors: () => assert.fail('no new actors'), makePicker: () => assert.fail('no new PDA before verification') }), /costs already written or cargo basis changed/)
|
||||
})
|
||||
|
||||
test('after-cost recovery retains verified costs and only enters handover, signatures and resource closure', async () => {
|
||||
const { ctx, trip } = afterCostsRecoveryFixture(), costs = trip.costAcceptance, oldHandover = trip.handover, operations = []
|
||||
await continueReviewedDeparture(ctx, { costsAlreadyVerified: true, prepareActors: () => assert.fail('verified costs need no new cost actors'),
|
||||
resolveAccess: async () => ({}), makePicker: async () => ({ token: 'new-pda', membershipId: 'NEW-PDA' }), closePicker: async () => operations.push('close-pda'),
|
||||
complete: (context, trip, picker, details, options) => completeDepartedFleetScenario(context, trip, picker, details, { ...options,
|
||||
verifyCosts: () => assert.fail('costs must never be recreated'), handover: async () => operations.push('handover'),
|
||||
completeDelivery: async (_ctx, order) => { operations.push('sign:' + order.deliveryNo); return { order: { status: 'DELIVERED', signedAt: 'signed' } } },
|
||||
closeTrip: async () => { operations.push('release'); return { status: 'CARGO_COMPLETED_AND_RESOURCES_RELEASED' } }, cleanup: async () => operations.push('cleanup'),
|
||||
}) })
|
||||
assert.equal(trip.costAcceptance, costs); assert.equal(trip.previousReadOnlyHandoverAttempts[0], oldHandover)
|
||||
assert.deepEqual(operations, ['handover', 'sign:DL-OWN', 'sign:DL-EXT', 'release', 'cleanup', 'close-pda'])
|
||||
})
|
||||
|
||||
test('departed recovery requires reviewed SHA and exactly the original read-only cost failure', () => {
|
||||
assert.throws(() => parseFleetArguments(['--resume-report', 'reports/r5/report.json', '--resume-stage', 'DEPARTED_BEFORE_COSTS']), /SHA256/)
|
||||
const args = ['--resume-report', 'reports/r5/report.json', '--resume-stage', 'DEPARTED_BEFORE_COSTS', '--resume-sha256', 'a'.repeat(64)]
|
||||
|
||||
Reference in New Issue
Block a user