优化
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import test from 'node:test'
|
||||
import { relabelOutboundMarksMatch, inventoryQuantitySnapshot, inventorySnapshot, dispatchInventorySources, inventorySourcesPresent, dispatchStockConservation, verifyDepartureReadiness, verifyPartialOrderCompletion, verifyFulfillmentRootStatus,
|
||||
import { relabelOutboundMarksMatch, inventoryQuantitySnapshot, inventorySnapshot, dispatchInventorySources, inventorySourcesPresent, dispatchStockConservation, verifyAutomaticDeparture, verifyPartialOrderCompletion, verifyFulfillmentRootStatus,
|
||||
createDeliveryReturn, completeReturnedException, returnedExceptionFactsMatch, verifyCustomerSignedDocuments, createSignedDocumentIsolationCustomer,
|
||||
dispatchDestinationConservation, pickAll, arrangeDispatchDelivery, fulfillmentEvidenceContext } from './phase5-overseas-fulfillment.mjs'
|
||||
dispatchDestinationConservation, pickAll, readAutomaticDispatchDelivery, fulfillmentEvidenceContext } from './phase5-overseas-fulfillment.mjs'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFile, mkdtemp, rm } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
@@ -64,21 +64,20 @@ test('phase-five fulfillment is a registered default HTTP module after the real
|
||||
|
||||
test('native fulfillment uses real public DTOs and scans before loading, arranging and departing', () => {
|
||||
for (const token of ["const DISPATCH_BASE = '/api/platform/v1/last-leg/dispatches'",
|
||||
"const PDA_DISPATCH_BASE = '/api/pda/v1/last-leg/dispatches'", '/transfer-delivery', '/delivery-arrangement',
|
||||
'cargoHash: value.cargoHash', 'loadingCompletionHash: value.loadingCompletionHash',
|
||||
"const PDA_DISPATCH_BASE = '/api/pda/v1/last-leg/dispatches'", 'value?.documents?.deliveries', 'value?.documents?.outbounds',
|
||||
'recordVersion: version(detail.data.recordVersion)', 'positionId: id',
|
||||
'departed.data.result?.shippedPackages', 'outboundRef.outboundNo']) {
|
||||
'ready.data.counts?.shippedPackageCount', 'outboundRef.outboundNo']) {
|
||||
if (token === 'positionId: id') assert.match(source, /positionId: source.position \? id\(source.position.positionId\)/)
|
||||
else assert.ok(source.includes(token), token)
|
||||
}
|
||||
assert.doesNotMatch(source, /READY_TO_DEPART|PENDING_DEPARTURE|DISPATCH-COMPLETE|\/items\/.*['"]DELETE['"]/)
|
||||
assert.match(source, /data.status === 'ACTIVE' && data.stage === 'AFTER_LOADING'/)
|
||||
assert.match(source, /departed.data.stage === 'DELIVERING'/)
|
||||
assert.match(source, /data.status === 'ACTIVE' && data.stage === 'DELIVERING'/)
|
||||
assert.doesNotMatch(source, /\/transfer-delivery|\/delivery-arrangement|\/depart`/)
|
||||
const run = source.slice(source.indexOf('export async function run'))
|
||||
assert.ok(run.indexOf('prepareFleetTrip(') < run.indexOf('await pickAll('))
|
||||
assert.ok(run.indexOf('await pickAll(') < run.indexOf('await confirmFleetLoading('))
|
||||
assert.ok(run.indexOf('await confirmFleetLoading(') < run.indexOf('await arrangeDispatchDelivery('))
|
||||
assert.ok(run.indexOf('await arrangeDispatchDelivery(') < run.indexOf('await verifyDepartureReadiness('))
|
||||
assert.ok(run.indexOf('await confirmFleetLoading(') < run.indexOf('await readAutomaticDispatchDelivery('))
|
||||
assert.ok(run.indexOf('await readAutomaticDispatchDelivery(') < run.indexOf('await verifyAutomaticDeparture('))
|
||||
assert.ok(run.indexOf('全部客户正常签收汇总整车DELIVERED') < run.indexOf('await finishFleetTrip('))
|
||||
assert.ok(run.indexOf('await finishFleetTrip(') < run.indexOf('const returnRequest'))
|
||||
})
|
||||
@@ -133,9 +132,8 @@ test('phase-five module uses marker-owned fixtures and always disables its tempo
|
||||
})
|
||||
|
||||
test('phase-five writes carry idempotency and record deployment blockers instead of passing empty fixtures', () => {
|
||||
assert.match(source, /idempotencyKey: key\(context, 'TRANSFER', dispatchNo\)/)
|
||||
assert.match(source, /idempotencyKey: key\(context, 'DISPATCH-PICK'/)
|
||||
assert.match(source, /idempotencyKey: key\(context, 'DISPATCH-DEPART', dispatchNo\)/)
|
||||
assert.doesNotMatch(source, /DISPATCH-DEPART/)
|
||||
assert.match(source, /addCoverageGap\(MODULE, SCENARIO/)
|
||||
assert.match(source, /deploymentBlocker: true/)
|
||||
})
|
||||
@@ -497,13 +495,13 @@ test('inventory evidence captures zero drift independently for every balance buc
|
||||
})
|
||||
|
||||
test('departure requires independent matching native stage, shared version, reconciled picks and both action gates', async () => {
|
||||
const valid = { dispatchNo: 'DS-1', status: 'ACTIVE', stage: 'AFTER_LOADING', recordVersion: '9007199254740993',
|
||||
pickingReconciled: true, actions: { DEPART: { allowed: true } } }
|
||||
for (const change of [null, { stage: 'LOADING' }, { stage: 'DELIVERING' }, { status: 'CANCELLED' },
|
||||
const valid = { dispatchNo: 'DS-1', status: 'ACTIVE', stage: 'DELIVERING', recordVersion: '9007199254740993',
|
||||
pickingReconciled: true, counts: { packageCount: 2, shippedPackageCount: 2 } }
|
||||
for (const change of [null, { stage: 'LOADING' }, { stage: 'AFTER_LOADING' }, { status: 'CANCELLED' },
|
||||
{ dispatchNo: 'OTHER' }, { recordVersion: '9007199254740994' }, { pickingReconciled: false },
|
||||
{ actions: { DEPART: { allowed: false } } }]) {
|
||||
{ counts: { packageCount: 2, shippedPackageCount: 1 } }]) {
|
||||
const context = contextFor((method, path) => ok(path.startsWith('/api/pda/') ? valid : { ...valid, ...change }))
|
||||
assert.equal(Boolean(await verifyDepartureReadiness(context, 'PDA', 'DS-1')), change == null)
|
||||
assert.equal(Boolean(await verifyAutomaticDeparture(context, 'PDA', 'DS-1')), change == null)
|
||||
assert.equal(context.calls.length, 2)
|
||||
assert.equal(context.calls.some(call => call.method !== 'GET'), false)
|
||||
}
|
||||
@@ -787,50 +785,56 @@ test('native PDA scans use exact current versions including zero, immutable repl
|
||||
assert.deepEqual(context.assertions.filter(row => !row.passed), [])
|
||||
})
|
||||
|
||||
function nativeArrangementPreview(recordVersion = '0') {
|
||||
return { dispatchNo: 'DS-1', recordVersion, status: 'ACTIVE', stage: 'AFTER_LOADING', cargoHash: 'CARGO',
|
||||
loadingCompletionHash: 'LOAD', arrange: { allowed: true }, groups: ['100', '200'].map(customerId => ({
|
||||
groupKey: `GROUP-${customerId}`, customerId, destination: { key: 'FBA:ONT8', warehouseCode: 'ONT8' },
|
||||
groupCargoHash: `GROUP-HASH-${customerId}`, externalFreightHash: 'FREIGHT', feeCoverageCargoHash: 'COVERAGE',
|
||||
requiredServices: ['DELIVERY_FEE'], feeSources: [], cargo: [{ customerId, packages: 1 }], blockReasons: [] })) }
|
||||
function automaticDispatch(recordVersion = '0') {
|
||||
return { dispatchNo: 'DS-1', recordVersion, status: 'ACTIVE', stage: 'DELIVERING', pickingReconciled: true,
|
||||
counts: { packageCount: 2, pickedPackageCount: 2, shippedPackageCount: 2 },
|
||||
documents: { outbounds: ['100', '200'].map(customerId => ({ customerId, outboundNo: `OB-${customerId}`, status: 'OUTBOUNDED', packageCount: 1 })),
|
||||
deliveries: ['100', '200'].map(customerId => ({ customerId, outboundNo: `OB-${customerId}`, deliveryNo: `DL-${customerId}`,
|
||||
destination: { key: 'FBA:ONT8' }, status: 'DELIVERING', packageCount: 1 })) } }
|
||||
}
|
||||
|
||||
test('native arrangement carries frozen group and cost hashes, preserves all customer outbounds, and replays the exact original command', async () => {
|
||||
test('automatic delivery reader preserves customer grouping and performs no writes', async () => {
|
||||
for (const recordVersion of ['0', '9007199254740993']) {
|
||||
const preview = nativeArrangementPreview(recordVersion)
|
||||
let original, result
|
||||
const context = contextFor((method, path, body) => {
|
||||
if (method === 'GET') { assert.match(path, /delivery-arrangement$/); return ok(preview) }
|
||||
assert.match(path, /transfer-delivery$/)
|
||||
if (original) { assert.deepEqual(body, original); return ok({ ...result, replayed: true }) }
|
||||
original = structuredClone(body)
|
||||
assert.equal(body.recordVersion, recordVersion)
|
||||
assert.equal(body.cargoHash, 'CARGO'); assert.equal(body.loadingCompletionHash, 'LOAD')
|
||||
assert.deepEqual(body.groups.map(row => row.groupKey), ['GROUP-100', 'GROUP-200'])
|
||||
assert.ok(body.groups.every(row => row.externalFreightHash === 'FREIGHT' && row.feeCoverageCargoHash === 'COVERAGE'))
|
||||
result = { dispatchNo: 'DS-1', status: 'ACTIVE', stage: 'AFTER_LOADING', recordVersion: '9007199254740994',
|
||||
result: { dispatchNo: 'DS-1', cargoHash: 'CARGO', outbounds: ['100', '200'].map(customerId => ({
|
||||
customerId, outboundNo: `OB-${customerId}`, status: 'WAIT_OUTBOUND', packageCount: 1 })),
|
||||
deliveries: ['100', '200'].map(customerId => ({ customerId, outboundNo: `OB-${customerId}`,
|
||||
deliveryNo: `DL-${customerId}`, destination: { key: 'FBA:ONT8', warehouseCode: 'ONT8' }, status: 'WAIT_DELIVERY', packageCount: 1 })) } }
|
||||
return ok(result)
|
||||
})
|
||||
const arranged = await arrangeDispatchDelivery(context, 'DS-1')
|
||||
assert.deepEqual(arranged.outbounds.map(row => row.outboundNo), ['OB-100', 'OB-200'])
|
||||
assert.equal(context.calls.filter(call => call.method === 'POST').length, 2)
|
||||
assert.deepEqual(context.assertions.filter(row => !row.passed), [])
|
||||
const current = automaticDispatch(recordVersion)
|
||||
const context = contextFor((method, path) => { assert.equal(method, 'GET'); assert.ok(path.endsWith('/DS-1')); return ok(current) })
|
||||
const result = await readAutomaticDispatchDelivery(context, 'DS-1')
|
||||
assert.equal(result.data.recordVersion, recordVersion)
|
||||
assert.deepEqual(result.outbounds.map(row => row.outboundNo), ['OB-100', 'OB-200'])
|
||||
assert.equal(context.calls.length, 1)
|
||||
}
|
||||
})
|
||||
test('automatic delivery reader rejects incomplete departure and customer or quantity drift', async () => {
|
||||
for (const mutate of [v => { v.stage = 'AFTER_LOADING' }, v => { v.status = 'CANCELLED' },
|
||||
v => { v.documents.deliveries[0].customerId = '999' }, v => { v.documents.outbounds[0].packageCount = 2 },
|
||||
v => { v.documents.deliveries = [] }, v => { v.documents.deliveries[1].deliveryNo = 'DL-100' },
|
||||
v => { v.counts.shippedPackageCount = 1 }, v => { v.documents.outbounds[0].status = 'WAIT_OUTBOUND' }]) {
|
||||
const current = automaticDispatch(); mutate(current)
|
||||
const context = contextFor(method => { assert.equal(method, 'GET'); return ok(current) })
|
||||
assert.equal(await readAutomaticDispatchDelivery(context, 'DS-1'), null)
|
||||
assert.ok(context.assertions.some(row => !row.passed))
|
||||
}
|
||||
})
|
||||
|
||||
test('arrangement cannot run before loading or with missing hashes, groups or server gate', async () => {
|
||||
for (const mutate of [value => { value.stage = 'LOADING' }, value => { value.status = 'CANCELLED' },
|
||||
value => { value.cargoHash = '' }, value => { value.loadingCompletionHash = '' },
|
||||
value => { value.groups = [] }, value => { value.arrange.allowed = false },
|
||||
value => { value.groups[0].blockReasons = ['MISSING_FEE'] }]) {
|
||||
const preview = nativeArrangementPreview(); mutate(preview)
|
||||
const context = contextFor((method) => { assert.equal(method, 'GET'); return ok(preview) })
|
||||
assert.equal(await arrangeDispatchDelivery(context, 'DS-1'), null)
|
||||
assert.ok(context.assertions.some(row => !row.passed))
|
||||
test('automatic delivery cannot hide duplicated customer or destination groups behind conserved totals', async () => {
|
||||
for (const sameOutbound of [false, true]) {
|
||||
const current = automaticDispatch()
|
||||
current.documents.outbounds[1].customerId = '100'
|
||||
current.documents.deliveries[1].customerId = '100'
|
||||
if (sameOutbound) {
|
||||
current.documents.outbounds.pop()
|
||||
current.documents.outbounds[0].packageCount = 2
|
||||
current.documents.deliveries[1].outboundNo = 'OB-100'
|
||||
} else current.documents.deliveries[1].destination.key = 'FBA:ONT9'
|
||||
const context = contextFor(method => { assert.equal(method, 'GET'); return ok(current) })
|
||||
assert.equal(await readAutomaticDispatchDelivery(context, 'DS-1'), null)
|
||||
}
|
||||
})
|
||||
|
||||
test('automatic delivery accepts canonical Long quantities and rejects coercible non-quantities', async () => {
|
||||
for (const quantity of ['1', true, '01', '1e0', ' 1 ', '9007199254740993']) {
|
||||
const current = automaticDispatch()
|
||||
current.documents.deliveries[0].packageCount = quantity
|
||||
const context = contextFor(method => { assert.equal(method, 'GET'); return ok(current) })
|
||||
assert.equal(Boolean(await readAutomaticDispatchDelivery(context, 'DS-1')), quantity === '1')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user