fix(lastleg): repair dispatch cancellation and return execution contracts
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import test from 'node:test'
|
||||
import { relabelOutboundMarksMatch, inventoryQuantitySnapshot, inventorySnapshot, dispatchInventorySources, inventorySourcesPresent, dispatchStockConservation, verifyDepartureReadiness, verifyPartialOrderCompletion, verifyFulfillmentRootStatus,
|
||||
completeReturnedException, returnedExceptionFactsMatch, verifyCustomerSignedDocuments, createSignedDocumentIsolationCustomer,
|
||||
createDeliveryReturn, completeReturnedException, returnedExceptionFactsMatch, verifyCustomerSignedDocuments, createSignedDocumentIsolationCustomer,
|
||||
dispatchDestinationConservation } from './phase5-overseas-fulfillment.mjs'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
@@ -105,7 +105,7 @@ test('phase-five module proves receipt-save completion, cross-region convergence
|
||||
"visitResult: 'SATISFIED'", '/exception-orders',
|
||||
'/return-inbound-options', '/mark-return', "returnDirection: 'OVERSEAS_WAREHOUSE'",
|
||||
'returnSources', 'outboundPalletItemId', 'inventoryPositionId',
|
||||
'value.returnSources.some', 'returnInbound?.deliveryNo === deliveryNo',
|
||||
'value.returnSources.some', 'returnCandidate?.deliveryNo === deliveryNo',
|
||||
"idempotencyKey: key(context, 'DELIVERY-RETURN')", '派送退回同键同载荷重放',
|
||||
'RETURN_PENDING_WITH_COMPLETED_VISIT',
|
||||
]) assert.ok(source.includes(token), token)
|
||||
@@ -183,6 +183,68 @@ function contextFor(request) {
|
||||
}
|
||||
const ok = data => ({ http: 200, body: { code: '200', data } })
|
||||
|
||||
function deliveryReturnFixture() {
|
||||
return { lastMileOrderId: '9007199254740993', lastMileOrderNo: 'LM-1', inboundOrderId: '81', inboundCode: 'IN-A',
|
||||
customerId: '100', overseasWarehouseId: 'WH-1', deliveryNo: 'DL-1', outboundNo: 'OB-1',
|
||||
returnSources: [{ outboundPalletItemId: '51', inventoryPositionId: '61', returnCartonId: null, maxReturnPackages: 2 }] }
|
||||
}
|
||||
const returnScope = { customerId: '100', warehouseId: 'WH-1', deliveryNo: 'DL-1', outboundNo: 'OB-1' }
|
||||
|
||||
for (const ordinaryInbound of ['81', null, undefined]) test(`phase5 mark-return sends the actual master key; inbound=${ordinaryInbound}`, async () => {
|
||||
const candidate = deliveryReturnFixture(); candidate.inboundOrderId = ordinaryInbound
|
||||
if (!ordinaryInbound) candidate.inboundCode = null
|
||||
const context = contextFor((method, path, body) => {
|
||||
if (method === 'GET') { assert.match(path, /return-inbound-options/); return ok([candidate]) }
|
||||
assert.equal(method, 'POST'); assert.match(path, /mark-return/)
|
||||
assert.equal(body.lastMileOrderId, candidate.lastMileOrderId)
|
||||
assert.equal(Object.hasOwn(body, 'inboundOrderId'), false)
|
||||
assert.deepEqual(body.items, [{ outboundPalletItemId: '51', inventoryPositionId: '61', packageCount: 1 }])
|
||||
assert.equal(body.returnDirection, 'OVERSEAS_WAREHOUSE')
|
||||
assert.ok(body.idempotencyKey)
|
||||
return ok({ exceptionNo: 'EX-1', status: 'PENDING', version: 0 })
|
||||
})
|
||||
const result = await createDeliveryReturn(context, returnScope)
|
||||
assert.equal(result.returned.data.exceptionNo, 'EX-1')
|
||||
assert.equal(context.calls.filter(row => row.method === 'POST').length, 1)
|
||||
assert.deepEqual(context.assertions.filter(row => !row.passed), [])
|
||||
})
|
||||
|
||||
test('phase5 keeps the selected master and its own source together when candidates share no inbound relation', async () => {
|
||||
const first = deliveryReturnFixture(); first.inboundOrderId = null; first.inboundCode = null; first.returnSources = []
|
||||
const selected = { ...deliveryReturnFixture(), lastMileOrderId: '9007199254740995', lastMileOrderNo: 'LM-2', inboundOrderId: null, inboundCode: null,
|
||||
returnSources: [{ outboundPalletItemId: '52', inventoryPositionId: '62', maxReturnPackages: 1 }] }
|
||||
const context = contextFor((method, path, body) => {
|
||||
if (method === 'GET') return ok([first, selected])
|
||||
assert.equal(body.lastMileOrderId, selected.lastMileOrderId)
|
||||
assert.equal(Object.hasOwn(body, 'inboundOrderId'), false)
|
||||
assert.deepEqual(body.items, [{ outboundPalletItemId: '52', inventoryPositionId: '62', packageCount: 1 }])
|
||||
return ok({ exceptionNo: 'EX-2' })
|
||||
})
|
||||
await createDeliveryReturn(context, returnScope)
|
||||
assert.equal(context.calls.filter(row => row.method === 'POST').length, 1)
|
||||
})
|
||||
|
||||
for (const [name, mutate] of [
|
||||
['missing-master', row => { delete row.lastMileOrderId }], ['zero-master', row => { row.lastMileOrderId = '0' }],
|
||||
['foreign-customer', row => { row.customerId = 'OTHER' }], ['foreign-warehouse', row => { row.overseasWarehouseId = 'OTHER' }],
|
||||
['wrong-delivery', row => { row.deliveryNo = 'DL-OTHER' }], ['wrong-outbound', row => { row.outboundNo = 'OB-OTHER' }],
|
||||
['missing-source', row => { row.returnSources = [] }], ['missing-position', row => { row.returnSources[0].inventoryPositionId = null }],
|
||||
['ambiguous-source-type', row => { row.returnSources[0].returnCartonId = '701' }],
|
||||
]) test(`phase5 refuses ${name} before any mark-return write even with an inbound ID`, async () => {
|
||||
const candidate = deliveryReturnFixture(); mutate(candidate)
|
||||
const context = contextFor((method) => { assert.equal(method, 'GET'); return ok([candidate]) })
|
||||
assert.equal(await createDeliveryReturn(context, returnScope), null)
|
||||
assert.equal(context.calls.length, 1)
|
||||
assert.ok(context.assertions.some(row => !row.passed))
|
||||
})
|
||||
|
||||
test('the phase5 lifecycle calls the tested request path and replays its exact returned body', () => {
|
||||
assert.match(source, /const returnRequest = await createDeliveryReturn\(context, \{ customerId, warehouseId, deliveryNo, outboundNo \}\)/)
|
||||
assert.match(source, /const \{ returned, returnBody \} = returnRequest/)
|
||||
assert.doesNotMatch(source, /inboundOrderId:\s*id\(returnCandidate/)
|
||||
assert.match(source, /lastMileOrderId: id\(returnCandidate\.lastMileOrderId\)/)
|
||||
})
|
||||
|
||||
test('partial completion rejects different Orders and an already delivered pending fragment', async () => {
|
||||
for (const second of [{ orderId: 'OTHER', processStatus: 'WAIT_SHIP' },
|
||||
{ orderId: 'ORDER', processStatus: 'DELIVERED' }]) {
|
||||
|
||||
Reference in New Issue
Block a user