test(fleet): exercise simplified intake HTTP workflow
This commit is contained in:
@@ -35,6 +35,8 @@ export const POOL_COVERAGE = Object.freeze([
|
||||
'bound-cargo-reservation-removed-without-double-count', 'other-sales-write-and-sensitive-trip-denied',
|
||||
'other-customer-commercial-read-denied-with-source-unchanged',
|
||||
'pool-unspecified-container-and-palletizing-remain-unknown',
|
||||
'save-and-confirm-original-command-replay', 'pool-fulfillment-owner-read-isolation',
|
||||
'pool-fulfillment-explicit-write-permissions', 'pool-arrange-dispatch-without-manual-number',
|
||||
])
|
||||
export const POOL_REMAINING = Object.freeze([
|
||||
'out-of-band-assigned-vehicle-drift-not-constructible-through-supported-api',
|
||||
@@ -311,6 +313,9 @@ async function poolScenario(context, fixture, { execute = false, readInventory,
|
||||
const sales = [], dispatchers = []
|
||||
for (let n = 0; n < 2; n++) sales.push(await createActor('sales', n, ['list', 'inquiry-create', 'inquiry-edit', 'inquiry-cancel'].map(s => PREFIX + s)))
|
||||
for (let n = 0; n < 2; n++) dispatchers.push(await createActor('dispatch', n, [PREFIX + 'manage']))
|
||||
const fulfillmentToken = await createActor('fulfillment', 0, [PREFIX + 'manage',
|
||||
'platform:fleet:trip:booking-edit', 'platform:fleet:trip:booking-confirm', 'platform:fleet:trip:edit',
|
||||
'platform:overseas-warehouse:inbound-order:smart-dispatch'])
|
||||
for (const member of fixture.members) {
|
||||
const credential = credentials?.get(member.customerId)
|
||||
check('Original fixture customer credentials available only in memory', Boolean(credential))
|
||||
@@ -347,6 +352,11 @@ async function poolScenario(context, fixture, { execute = false, readInventory,
|
||||
proof.covered.push('unknown-load-stays-null')
|
||||
const first = await createInquiry(0, 'LOCAL_LOOSEBOARD', 8, 'LOOSE')
|
||||
await reject('Other sales cannot read this inquiry', 'GET', `${POOL}/inquiries/${first.bookingNo}`, undefined, sales[1])
|
||||
const ownFulfillment = await request('GET', `${POOL}/inquiries/${first.bookingNo}/fulfillment`, undefined, sales[0])
|
||||
check('Owner reads only the selected booking without trip-list permission', ownFulfillment.booking.bookingNo === first.bookingNo
|
||||
&& ownFulfillment.booking.customerId === first.customerId && !ownFulfillment.dispatches && !ownFulfillment.bookings)
|
||||
await reject('Other sales cannot read dedicated fulfillment', 'GET', `${POOL}/inquiries/${first.bookingNo}/fulfillment`, undefined, sales[1])
|
||||
proof.covered.push('pool-fulfillment-owner-read-isolation')
|
||||
const second = await createInquiry(1, 'US_TRUCK', 4, 'TRUCK-A')
|
||||
check('Same display name does not collapse distinct sales identities', first.salesOwnerUserId && second.salesOwnerUserId && first.salesOwnerUserId !== second.salesOwnerUserId)
|
||||
proof.covered.push('two-sales-owner-isolation')
|
||||
@@ -393,9 +403,13 @@ async function poolScenario(context, fixture, { execute = false, readInventory,
|
||||
command, businessPath, dispatcherToken: dispatchers[index], label: `CANCEL-${type}` }))
|
||||
}
|
||||
proof.covered.push('confirmed-cancel-before-incoming-releases-capacity-and-replays')
|
||||
let current = await latest(first.bookingNo), firstCommand = command(current, 'CONFIRM-LOOSE')
|
||||
await reject('Sales cannot confirm capacity', 'POST', `${POOL}/inquiries/${first.bookingNo}/confirm`, firstCommand, sales[0])
|
||||
const firstConfirmed = await request('POST', `${POOL}/inquiries/${first.bookingNo}/confirm`, firstCommand, dispatchers[0])
|
||||
let current = await latest(first.bookingNo), firstCommand = poolInquiryWrite(current, key('SAVE-CONFIRM-LOOSE'))
|
||||
await reject('Sales cannot save and confirm capacity', 'POST', `${POOL}/inquiries/${first.bookingNo}/save-and-confirm`, firstCommand, sales[0])
|
||||
const firstConfirmed = await request('POST', `${POOL}/inquiries/${first.bookingNo}/save-and-confirm`, firstCommand, dispatchers[0])
|
||||
const saveReplay = await request('POST', `${POOL}/inquiries/${first.bookingNo}/save-and-confirm`, firstCommand, dispatchers[0])
|
||||
assertSingleCommercial(firstConfirmed.inquiry, saveReplay.inquiry)
|
||||
check('Save and confirm original request replays one commercial commitment', saveReplay.replayed === true)
|
||||
proof.covered.push('save-and-confirm-original-command-replay')
|
||||
assertPoolRemaining(firstConfirmed.offer, destinationKey, 4)
|
||||
proof.covered.push('same-segment-plan-18-of-30-reserve-8-leaves-4')
|
||||
const oversized = await createInquiry(1, 'US_TRUCK', 5, 'OVER-CAPACITY')
|
||||
@@ -426,7 +440,7 @@ async function poolScenario(context, fixture, { execute = false, readInventory,
|
||||
&& foreignRead.requestId && foreignRead.traceId, foreignRead)
|
||||
check('Denied old status and foreign read preserve commercial source', JSON.stringify(source) === JSON.stringify(await request('GET', businessPath(order))))
|
||||
poolMembers.push(await receiveAndBindPoolOrder(context, fixture, order, index, { request, reject, check, key, readInventory,
|
||||
latest, command, businessPath, offerDetail: () => offerDetail(dispatchers[0]), dispatcherToken: dispatchers[0], destinationKey, proof }))
|
||||
latest, command, businessPath, offerDetail: () => offerDetail(dispatchers[0]), dispatcherToken: dispatchers[0], fulfillmentToken, destinationKey, proof }))
|
||||
}
|
||||
proof.covered.push('old-manual-order-endpoints-rejected', 'one-commercial-order-and-one-incoming', 'both-business-types-real-pda-receipt',
|
||||
'other-customer-commercial-read-denied-with-source-unchanged', 'incoming-cancel-rejected-with-source-unchanged',
|
||||
@@ -436,7 +450,7 @@ async function poolScenario(context, fixture, { execute = false, readInventory,
|
||||
const closed = await request('POST', `${POOL}/${proof.offerNo}/close`, { tripRecordVersion: offer.tripRecordVersion,
|
||||
offerRecordVersion: offer.recordVersion, idempotencyKey: key('CLOSE'), reason: context.marker }, dispatchers[0])
|
||||
check('Offer closed without cancelling commitments', closed.offer.status === 'CLOSED')
|
||||
const replay = await request('POST', `${POOL}/inquiries/${first.bookingNo}/confirm`, firstCommand, dispatchers[0])
|
||||
const replay = await request('POST', `${POOL}/inquiries/${first.bookingNo}/save-and-confirm`, firstCommand, dispatchers[0])
|
||||
assertSingleCommercial(firstConfirmed.inquiry, replay.inquiry)
|
||||
check('Successful confirmation still replays after close', replay.replayed === true)
|
||||
for (const row of beforeClose) check('Old confirmation replay cannot revert bound source', inquiryFact(row) === inquiryFact(await latest(row.bookingNo)))
|
||||
@@ -502,14 +516,16 @@ async function poolScenario(context, fixture, { execute = false, readInventory,
|
||||
}
|
||||
|
||||
async function receiveAndBindPoolOrder(context, fixture, order, index, { request, reject, check, key, readInventory,
|
||||
latest, command, businessPath, offerDetail, dispatcherToken, destinationKey, proof }) {
|
||||
latest, command, businessPath, offerDetail, dispatcherToken, fulfillmentToken, destinationKey, proof }) {
|
||||
const tripPath = `${API}/fleet/trips/${encode(fixture.tripNo)}`, bookingPath = `${tripPath}/bookings/${encode(order.bookingNo)}`
|
||||
const poolPath = `${POOL}/inquiries/${encode(order.bookingNo)}`
|
||||
let bookingList = await request('GET', `${tripPath}/bookings`), booking = bookingList.records.find(row => row.bookingNo === order.bookingNo)
|
||||
check('Pool commercial association survived into existing booking workflow', booking?.status === 'CONFIRMED')
|
||||
const body = { tripRecordVersion: bookingList.tripRecordVersion, recordVersion: booking.recordVersion, estimatedArrivalAt: new Date(Date.now() + 300000).toISOString(),
|
||||
deliveryInstructions: `${context.marker} TEST客户自送`, idempotencyKey: key(`INCOMING-${index}`) }
|
||||
const created = await request('POST', `${bookingPath}/incoming`, body), incomingNo = created.booking.incomingNo
|
||||
const repeated = await request('POST', `${bookingPath}/incoming`, body)
|
||||
await reject('Pool management alone cannot arrange incoming', 'POST', `${poolPath}/incoming`, body, dispatcherToken)
|
||||
const created = await request('POST', `${poolPath}/incoming`, body, fulfillmentToken), incomingNo = created.booking.incomingNo
|
||||
const repeated = await request('POST', `${poolPath}/incoming`, body, fulfillmentToken)
|
||||
check('One incoming document on exact replay', incomingNo && repeated.replayed === true && repeated.booking.incomingNo === incomingNo)
|
||||
const incomingBeforeCancel = await request('GET', `${bookingPath}/incoming`), sourceBeforeCancel = await request('GET', businessPath(order))
|
||||
const inquiryBeforeCancel = await latest(order.bookingNo), capacityBeforeCancel = (await offerDetail()).offer
|
||||
@@ -532,14 +548,14 @@ async function receiveAndBindPoolOrder(context, fixture, order, index, { request
|
||||
check('Real TEST measurement file exists', success(uploaded) && uploaded.body.data?.fileId)
|
||||
const received = await request('POST', `${pdaPath}/palletize`, { idempotencyKey: key(`PALLETIZE-${index}`), photoFileIds: [id(uploaded.body.data.fileId)] }, fixture.pdaToken)
|
||||
check('Actual PDA receipt completed one original pallet', received.status === 'WAIT_CUSTOMER_CONFIRMATION' && received.pallets.length === 1 && received.pallets[0].status === 'COMPLETED')
|
||||
const evidence = await request('GET', `${bookingPath}/incoming`)
|
||||
const evidence = (await request('GET', `${poolPath}/fulfillment`, undefined, fulfillmentToken)).preview
|
||||
const beforeOverReceipt = await latest(order.bookingNo), sourceBeforeReceipt = await request('GET', businessPath(order))
|
||||
const capacityBeforeReceipt = (await offerDetail()).offer, tripBeforeReceipt = await request('GET', tripPath)
|
||||
const inventoryBeforeReceipt = await readInventory({ marker: context.marker, customerId: order.customerId, warehouseId: fixture.warehouseId })
|
||||
const overReceipt = await reject('Actual received pallet spaces cannot exceed whole vehicle capacity', 'POST', `${bookingPath}/receipt-confirmation`,
|
||||
const overReceipt = await reject('Actual received pallet spaces cannot exceed whole vehicle capacity', 'POST', `${poolPath}/receipt-confirmation`,
|
||||
{ tripRecordVersion: evidence.tripRecordVersion, recordVersion: evidence.recordVersion, receiptHash: evidence.incoming.receiptHash,
|
||||
pallets: [{ warehousePalletId: id(pallet.warehousePalletId), palletSpaces: Number(tripBeforeReceipt.trip.maxPalletSpaces) + 1 }],
|
||||
unitPrice: 30, idempotencyKey: key(`OVER-RECEIPT-${index}`) })
|
||||
unitPrice: 30, idempotencyKey: key(`OVER-RECEIPT-${index}`) }, fulfillmentToken)
|
||||
check('Receipt overrun is refused by actual capacity calculation', /超过.*限制|容量|载重|容积/.test(id(overReceipt.body?.message)))
|
||||
check('Rejected overreceipt leaves source receipt commercial booking and reservations unchanged',
|
||||
JSON.stringify(evidence) === JSON.stringify(await request('GET', `${bookingPath}/incoming`))
|
||||
@@ -548,20 +564,28 @@ async function receiveAndBindPoolOrder(context, fixture, order, index, { request
|
||||
&& capacitySnapshot(capacityBeforeReceipt) === capacitySnapshot((await offerDetail()).offer))
|
||||
check('Rejected overreceipt leaves complete customer warehouse inventory unchanged', JSON.stringify(inventoryBeforeReceipt)
|
||||
=== JSON.stringify(await readInventory({ marker: context.marker, customerId: order.customerId, warehouseId: fixture.warehouseId })))
|
||||
await request('POST', `${bookingPath}/receipt-confirmation`, { tripRecordVersion: evidence.tripRecordVersion, recordVersion: evidence.recordVersion,
|
||||
receiptHash: evidence.incoming.receiptHash, pallets: [{ warehousePalletId: id(pallet.warehousePalletId), palletSpaces: 1 }], unitPrice: 30, idempotencyKey: key(`RECEIPT-${index}`) })
|
||||
const receiptBody = { tripRecordVersion: evidence.tripRecordVersion, recordVersion: evidence.recordVersion,
|
||||
receiptHash: evidence.incoming.receiptHash, pallets: [{ warehousePalletId: id(pallet.warehousePalletId), palletSpaces: 1 }], unitPrice: 30, idempotencyKey: key(`RECEIPT-${index}`) }
|
||||
await reject('Pool management alone cannot confirm receipt', 'POST', `${poolPath}/receipt-confirmation`, receiptBody, dispatcherToken)
|
||||
await request('POST', `${poolPath}/receipt-confirmation`, receiptBody, fulfillmentToken)
|
||||
const inventory = await readInventory({ marker: context.marker, customerId: order.customerId, warehouseId: fixture.warehouseId })
|
||||
check('Immutable actual incoming STOCK_IN is unique', inventory.scope?.coverage === 'CUSTOMER_WAREHOUSE_COMPLETE'
|
||||
&& inventory.ledger.filter(row => row.businessOrderNo === incomingNo && row.businessType === 'STOCK_IN' && Number(row.deltaPackages) === 1).length === 1)
|
||||
const dispatchBase = `${API}/dispatches`, candidates = await request('GET', `${dispatchBase}/candidates?${new URLSearchParams({ sourceStage: 'RECEIVED', orderNo: incomingNo })}`)
|
||||
check('Received candidate exactly belongs to this commercial incoming', candidates.length === 1 && candidates[0].inboundCode === incomingNo
|
||||
&& id(candidates[0].customerId) === order.customerId && id(candidates[0].warehousePalletId) === id(pallet.warehousePalletId))
|
||||
const draftToken = key(`DRAFT-${index}`), container = await request('POST', `${dispatchBase}/drafts/containers`, { draftToken, capacity: 30 })
|
||||
await request('POST', `${dispatchBase}/draft-locks/lock`, { planPalletId: id(candidates[0].planPalletId), draftToken, draftContainerId: id(container.id) })
|
||||
const dispatch = await request('POST', `${dispatchBase}/pre-arrange`, { planPalletIds: [id(candidates[0].planPalletId)], draftToken })
|
||||
bookingList = await request('GET', `${tripPath}/bookings`); booking = bookingList.records.find(row => row.bookingNo === order.bookingNo)
|
||||
await request('POST', `${bookingPath}/dispatch`, { tripRecordVersion: bookingList.tripRecordVersion, recordVersion: booking.recordVersion,
|
||||
dispatchNo: dispatch.dispatchNo, idempotencyKey: key(`BIND-DISPATCH-${index}`) })
|
||||
const arrangeBody = { tripRecordVersion: bookingList.tripRecordVersion, recordVersion: booking.recordVersion,
|
||||
idempotencyKey: key(`ARRANGE-DISPATCH-${index}`) }
|
||||
await reject('Pool management alone cannot generate and bind dispatch', 'POST', `${poolPath}/arrange-dispatch`, arrangeBody, dispatcherToken)
|
||||
const arranged = await request('POST', `${poolPath}/arrange-dispatch`, arrangeBody, fulfillmentToken)
|
||||
const arrangeReplay = await request('POST', `${poolPath}/arrange-dispatch`, arrangeBody, fulfillmentToken)
|
||||
check('One command generates and binds the actual receipt without a manually supplied number', arranged.booking.status === 'BOUND'
|
||||
&& arranged.booking.dispatchNo && arranged.booking.incomingNo === incomingNo && arranged.booking.customerId === order.customerId)
|
||||
check('Arrange replay returns the original dispatch and version', arrangeReplay.replayed === true
|
||||
&& arrangeReplay.booking.dispatchNo === arranged.booking.dispatchNo && arrangeReplay.tripRecordVersion === arranged.tripRecordVersion)
|
||||
const dispatch = { dispatchNo: arranged.booking.dispatchNo }
|
||||
if (index === 0) proof.covered.push('pool-fulfillment-explicit-write-permissions', 'pool-arrange-dispatch-without-manual-number')
|
||||
let plan = await request('GET', `${tripPath}/capacity-plan`), detail = await request('GET', tripPath)
|
||||
const actualMeasurements = controlledPalletMeasurements(plan.cargo, detail.trip.stops)
|
||||
const restored = await verifyPoolBindingRevalidation({ offerDetail, check,
|
||||
|
||||
Reference in New Issue
Block a user