test(acceptance): verify native outbound ownership and exact relabel carton quantities

This commit is contained in:
2026-10-23 05:21:30 +08:00
parent 18ef338036
commit 2b54e709f8
7 changed files with 148 additions and 20 deletions
@@ -1,7 +1,7 @@
import test from 'node:test'
import { relabelOutboundMarksMatch, inventoryQuantitySnapshot, inventorySnapshot, dispatchInventorySources, inventorySourcesPresent, dispatchStockConservation, verifyAutomaticDeparture, verifyPartialOrderCompletion, verifyFulfillmentRootStatus,
createDeliveryReturn, completeReturnedException, returnedExceptionFactsMatch, verifyCustomerSignedDocuments, createSignedDocumentIsolationCustomer,
dispatchDestinationConservation, pickAll, readAutomaticDispatchDelivery, fulfillmentEvidenceContext, verifyRelabelDispatchOutbound, verifyClientRelabelSettlement } from './phase5-overseas-fulfillment.mjs'
dispatchDestinationConservation, dispatchOutboundMatches, continueDepartedFulfillment, pickAll, readAutomaticDispatchDelivery, fulfillmentEvidenceContext, verifyRelabelDispatchOutbound, verifyClientRelabelSettlement } from './phase5-overseas-fulfillment.mjs'
import assert from 'node:assert/strict'
import { readFile, mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
@@ -11,6 +11,33 @@ import { parseArguments } from '../admin-test-env-acceptance.mjs'
const source = await readFile(fileURLToPath(new URL('./phase5-overseas-fulfillment.mjs', import.meta.url)), 'utf8')
test('原出库客户归属沿正式关联派送明细核对并拒绝串客漏项数量漂移', () => {
const reference = { outboundNo: 'CK-1', customerId: '101', packageCount: '7' }
const deliveries = [{ deliveryNo: 'DL-1', outboundNo: 'CK-1', customerId: '101', packageCount: '3' },
{ deliveryNo: 'DL-2', outboundNo: 'CK-1', customerId: '101', packageCount: '4' }]
const detail = { outbound: { outboundNo: 'CK-1', dispatchNo: 'PC-1', status: 'OUTBOUNDED', packageCount: 7 },
deliveries: deliveries.map(row => ({ ...row, dispatchNo: 'PC-1', packageCount: Number(row.packageCount) })) }
assert.equal(dispatchOutboundMatches(detail, reference, 'PC-1', deliveries), true)
for (const change of [value => { value.deliveries[0].customerId = '102' }, value => { value.deliveries.pop() },
value => { value.deliveries[0].outboundNo = 'CK-2' }, value => { value.deliveries[0].dispatchNo = 'PC-2' },
value => { value.deliveries[1].deliveryNo = 'DL-1' }, value => { value.deliveries[1].packageCount = 3 },
value => { value.outbound.status = 'PICKED' }, value => { value.outbound.packageCount = 8 }]) {
const value = structuredClone(detail); change(value)
assert.equal(dispatchOutboundMatches(value, reference, 'PC-1', deliveries), false)
}
})
test('原发车接续拒绝外批次或已开始签收的证明且不发送请求', async () => {
let calls = 0
const args = { customerId: '101', warehouseId: '42', dispatchNo: 'PC-1', tripProof: { dispatchNo: 'PC-1', warehouseId: '42' },
deliveries: [{}], outbounds: [{}], merchantToken: 'test-only' }
for (const proof of [{ marker: 'other', dispatchNo: 'PC-1' }, { marker: 'batch', dispatchNo: 'PC-1', receiptFiles: [{}] }]) {
await assert.rejects(continueDepartedFulfillment({ marker: 'batch', createdResources: { phase5OverseasFulfillment: proof },
request: async () => { calls++ } }, args), /原发车证明/)
}
assert.equal(calls, 0)
})
for (const fault of ['', 'foreign customer', 'old marks', 'sign failure', 'resource held', 'settlement failed']) {
test(`client original relabel delivery includes receipt and resource release: ${fault || 'normal'}`, async () => {
const relabel = { marker: 'batch', relabelNo: 'RL-CLIENT', dispatchNo: 'DP-CLIENT', warehouseId: '42', customerId: '101',
@@ -22,13 +49,14 @@ for (const fault of ['', 'foreign customer', 'old marks', 'sign failure', 'resou
async request(method, path) {
calls.push('outbound')
assert.equal(method, 'GET'); assert.ok(path.endsWith('/outbound-orders/OUT-1'))
return { http: 200, body: { code: '200', data: { outbound: { outboundNo: 'OUT-1', dispatchNo: 'DP-CLIENT', customerId: '101',
status: 'OUTBOUNDED', packageCount: 1 }, pallets: [{ cartonMarks: fault === 'old marks' ? 'OLD' : 'NEW' }] } } }
return { http: 200, body: { code: '200', data: { outbound: { outboundNo: 'OUT-1', dispatchNo: 'DP-CLIENT',
status: 'OUTBOUNDED', packageCount: 1 }, deliveries: [{ deliveryNo: 'DE-1', outboundNo: 'OUT-1', dispatchNo: 'DP-CLIENT', customerId: '101', packageCount: 1 }],
pallets: [{ cartonMarks: fault === 'old marks' ? 'OLD' : 'NEW' }] } } }
} }
const helpers = {
async prepare(_c, _m, dispatch, warehouse) { calls.push('prepare'); assert.equal(dispatch, relabel.dispatchNo); assert.equal(warehouse, '42'); return { tripNo: 'TR-1' } },
async pick() { calls.push('pick'); return true }, async load() { calls.push('load') },
async read() { calls.push('read'); return { outbounds: [{ customerId: '101', outboundNo: 'OUT-1' }],
async read() { calls.push('read'); return { outbounds: [{ customerId: '101', outboundNo: 'OUT-1', packageCount: 1 }],
deliveries: [{ customerId: fault === 'foreign customer' ? '102' : '101', outboundNo: 'OUT-1', deliveryNo: 'DE-1', packageCount: 1 }] } },
async sign() { calls.push('sign'); return { order: { status: fault === 'sign failure' ? 'DELIVERING' : 'DELIVERED',
deliveryNo: 'DE-1', customerId: '101', outboundNo: 'OUT-1' } } },
@@ -228,7 +256,7 @@ test('relabel marks are checked on the authoritative outbound DTO without weaken
assert.equal(relabelOutboundMarksMatch({ ...detail, pallets: [{ cartonMarks }] }, relabel), false)
}
assert.equal(relabelOutboundMarksMatch({ ...detail, outbound: { packageCount: 3 } }, relabel), false)
assert.match(source, /detail\.data\?\.outbound\?\.dispatchNo === relabel\.dispatchNo/)
assert.match(source, /dispatchOutboundMatches\(detail.data, transferred.outbounds\[0\], relabel.dispatchNo, transferred.deliveries\)/)
assert.match(source, /relabel\?\.marker !== context\.marker/)
})
@@ -868,6 +896,40 @@ test('native PDA scans use exact current versions including zero, immutable repl
assert.deepEqual(context.assertions.filter(row => !row.passed), [])
})
test('normal relabel scans each original carton using explicit ten-unit SKU verification and actual shared position', async () => {
const values = ['0', '9007199254740993', '9007199254740994']
let currentVersion = values[0], picked = 0, firstBody, firstResult
const context = contextFor((method, path, body) => {
if (path.includes('/relabel-applications/')) return ok({ application: { relabelNo: 'TB-1', overseasWarehouseId: '42' },
items: [1, 2].map(n => ({ cartonMark: 'BOX-' + n, sourceInventoryPositionId: '9007199254740996', notifiedPackageCount: 1,
operationType: 'CARTON_AND_SKU', skuItems: [{ skuCode: 'OLD', newSkuCode: 'NEW', quantity: 10 }] })) })
if (method === 'GET') return ok({ dispatchNo: 'DS-1', status: 'ACTIVE', stage: 'LOADING', recordVersion: currentVersion,
pickingReconciled: picked === 2, counts: { packageCount: 2, pickedPackageCount: picked },
items: [{ dispatchItemId: '9007199254740995', customerId: '100', sourceBusinessType: 'RELABEL_ORDER', sourceBusinessNo: 'TB-1', scheduledPackages: 2, pickedPackages: picked,
positions: [{ positionId: '9007199254740996', balanceId: '9007199254740997', availablePackages: 2 - picked }] }] })
assert.match(path, /items\/9007199254740995\/pick$/)
assert.equal(Object.hasOwn(body, 'expectedVersion'), false)
assert.equal(typeof body.positionId, 'string')
if (body.idempotencyKey.endsWith('-STALE')) {
assert.equal(body.recordVersion, '0'); return { http: 200, body: { code: 'A0409' } }
}
if (firstBody?.idempotencyKey === body.idempotencyKey) {
assert.deepEqual(body, firstBody); return ok({ ...firstResult, replayed: true })
}
assert.equal(body.recordVersion, currentVersion); assert.equal(body.packages, 1); assert.equal(body.productQuantity, 10); assert.equal(body.cartonMark, 'BOX-' + (picked + 1))
picked += 1; currentVersion = values[picked]
firstBody = structuredClone(body)
firstResult = { dispatchNo: 'DS-1', status: 'ACTIVE', stage: 'LOADING', recordVersion: currentVersion,
result: { dispatchItemId: '9007199254740995', pickedPackages: picked }, counts: { pickedPackageCount: picked } }
return ok(firstResult)
})
context.createdResources.phase4OverseasRelabel = { marker: context.marker, relabelNo: 'TB-1', customerId: '100', overseasWarehouseId: '42' }
assert.equal(await pickAll(context, 'PDA-IN-MEMORY', 'DS-1'), true)
assert.equal(picked, 2)
assert.equal(context.calls.some(call => /loading-complete|\/complete|\/depart/.test(call.path)), false)
assert.deepEqual(context.assertions.filter(row => !row.passed), [])
})
function automaticDispatch(recordVersion = '0') {
return { dispatchNo: 'DS-1', recordVersion, status: 'ACTIVE', stage: 'DELIVERING', pickingReconciled: true,
counts: { packageCount: 2, pickedPackageCount: 2, shippedPackageCount: 2 },