2026-10-22 15:41:50 +08:00
|
|
|
import test from 'node:test'
|
|
|
|
|
import assert from 'node:assert/strict'
|
2026-10-23 04:48:02 +08:00
|
|
|
import { findReusableTransferReceiptTarget, receiveWarehouseTransfer } from './warehouse-transfer-receipt.mjs'
|
2026-10-22 15:41:50 +08:00
|
|
|
|
|
|
|
|
// 收货替身使用独立目标仓资源,覆盖资源不足、串单、重复入库和结算未完成。
|
|
|
|
|
function fixture(fault = '') {
|
|
|
|
|
const allocation = { inventoryPositionId: '301', palletId: '401', packageCount: 1, skuCode: 'NEW', inboundCode: 'IN-1' }
|
|
|
|
|
const proof = { marker: 'BATCH', customerId: '101', warehouseId: '201', transferNo: 'WT-1', relabelNo: 'RL-1', relabelApplicationId: '501', inboundCode: 'IN-1',
|
|
|
|
|
status: 'OUTBOUNDED', outboundAllocations: [allocation], outbound: { status: 'OUTBOUNDED_HTTP_VERIFIED' } }
|
|
|
|
|
const pallet = { id: '601', warehouseId: '202', customerId: '0', locationId: '701', palletCode: 'TARGET', status: 'IDLE', fbaWarehouseCode: 'FBA-1' }
|
|
|
|
|
const calls = [], saved = []
|
|
|
|
|
let received = false, writes = 0
|
|
|
|
|
const context = { marker: 'BATCH', context: { merchantQualificationFixture: { tenantId: '101' } },
|
|
|
|
|
createdResources: { phase4OverseasClientServices: { transferFulfillment: proof } }, assert() {},
|
|
|
|
|
async request(method, path, body) {
|
|
|
|
|
calls.push({ method, path, body })
|
|
|
|
|
let data
|
|
|
|
|
if (method === 'POST') {
|
|
|
|
|
writes++
|
|
|
|
|
if (fault === 'network') throw Error('connection lost')
|
|
|
|
|
assert.equal(body.destinationOverseasWarehouseId, '202')
|
|
|
|
|
assert.equal(body.destinationPalletId, '601')
|
|
|
|
|
received = true
|
|
|
|
|
}
|
|
|
|
|
if (path.includes('/pallets?')) data = { total: fault === 'no resource' ? 0 : 1, records: fault === 'no resource' ? [] : [{ ...pallet }] }
|
|
|
|
|
else if (path.endsWith('/pallets/601')) data = { ...pallet, status: fault === 'occupied' ? 'OCCUPIED' : 'IDLE' }
|
|
|
|
|
else if (path.endsWith('/locations/701')) data = { id: '701', warehouseId: '202', customerId: '0', locationCode: 'A0', locationType: 'VIRTUAL', status: fault === 'location blocked' ? 'OCCUPIED' : 'ACTIVE' }
|
|
|
|
|
else if (path.includes('/warehouse-admin/')) data = { warehouse: { id: '202', tenantId: '0', warehouseType: 'OVERSEAS', status: 'ACTIVE', warehouseCode: 'WH2', warehouseName: 'Target' } }
|
|
|
|
|
else if (path.includes('/ledger?')) {
|
|
|
|
|
const rows = received ? [{ id: '801', customerId: fault === 'foreign customer' ? '999' : '101', overseasWarehouseId: '202', businessOrderNo: 'WT-1',
|
|
|
|
|
businessType: 'STOCK_IN', inboundCode: 'IN-1', skuCode: 'NEW', palletNo: 'TARGET', warehouseLocationCode: 'A0', deltaPackages: 1 }] : []
|
|
|
|
|
if (fault === 'double receipt' && received) rows.push({ ...rows[0], id: '802' })
|
|
|
|
|
if (fault === 'replay drift' && writes === 2) rows[0].id = '899'
|
|
|
|
|
data = { total: rows.length, records: rows }
|
|
|
|
|
} else if (path.includes('/relabel-applications/')) data = { application: { relabelNo: 'RL-1', customerOrderNo: 'WT-1', status: 'COMPLETED', fulfillmentStatus: 'COMPLETED', walletStatus: fault === 'unsettled' ? 'SETTLE_PENDING' : 'SETTLED' } }
|
|
|
|
|
else data = { transferNo: 'WT-1', status: received ? 'RECEIVED' : 'OUTBOUNDED', relabelNo: 'RL-1', relabelApplicationId: '501', fbaWarehouseCode: 'FBA-1',
|
|
|
|
|
receiptCommands: received ? [{ status: 'COMPLETED', sourceInventoryPositionId: '301', request: { idempotencyKey: 'BATCH-TRANSFER-RECEIVE', batchNo: 'WT-1' },
|
|
|
|
|
target: { warehouseId: '202', palletId: fault === 'target drift' ? '999' : '601', palletNo: 'TARGET', locationId: '701', locationCode: 'A0', fbaCode: 'FBA-1' } }] : [],
|
|
|
|
|
allocations: [{ ...allocation, status: received ? 'RECEIVED' : 'SHIPPED', destinationInventoryPositionId: received ? '901' : null }] }
|
|
|
|
|
return { http: 200, body: { code: '200', data } }
|
|
|
|
|
} }
|
|
|
|
|
const options = { proof, merchantToken: 'secret', delay: async () => {}, checkpoint: async () => { saved.push(structuredClone(proof)); if (fault === 'disk') throw Error('disk full') } }
|
|
|
|
|
return { context, options, proof, calls, saved }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
test('原转运复用异仓托盘收货并核对唯一入库和结算终态', async () => {
|
|
|
|
|
const f = fixture()
|
|
|
|
|
await receiveWarehouseTransfer(f.context, f.options)
|
|
|
|
|
assert.equal(f.proof.status, 'RECEIVED')
|
|
|
|
|
assert.equal(f.proof.receipt.status, 'RECEIVED_AND_SETTLED_HTTP_VERIFIED')
|
|
|
|
|
assert.equal(f.calls.filter(c => c.method === 'POST').length, 2)
|
|
|
|
|
assert.ok(f.calls.filter(c => c.method === 'POST').every(c => c.path.includes('/receipts?')))
|
|
|
|
|
assert.equal(JSON.stringify(f.saved).includes('secret'), false)
|
|
|
|
|
const count = f.calls.length
|
|
|
|
|
await assert.rejects(receiveWarehouseTransfer(f.context, f.options))
|
|
|
|
|
assert.equal(f.calls.length, count)
|
|
|
|
|
})
|
|
|
|
|
|
2026-10-23 04:48:02 +08:00
|
|
|
test('造单前只读选取异仓目的仓,翻过同仓和未绑定目的仓的托盘且不改主档', async () => {
|
|
|
|
|
const f = fixture()
|
|
|
|
|
const request = async (method, path) => {
|
|
|
|
|
assert.equal(method, 'GET')
|
|
|
|
|
if (path.includes('/pallets?') && path.includes('page=1&')) return { total: '201', records: Array.from({ length: 200 }, (_, i) => ({
|
|
|
|
|
id: String(1000 + i), customerId: '0', warehouseId: i % 2 ? '201' : '202', status: 'IDLE', locationId: '701',
|
|
|
|
|
fbaWarehouseCode: i % 2 ? 'FBA-OTHER' : null })) }
|
|
|
|
|
if (path.includes('/pallets?')) return { total: '201', records: [{ id: '601', customerId: '0', warehouseId: '202', status: 'IDLE', locationId: '701', fbaWarehouseCode: 'FBA-1' }] }
|
|
|
|
|
return (await f.context.request(method, path)).body.data
|
|
|
|
|
}
|
|
|
|
|
const target = await findReusableTransferReceiptTarget({ sourceWarehouseId: '201', request,
|
|
|
|
|
check: (name, ok) => assert.ok(ok, name) })
|
|
|
|
|
assert.equal(target.pallet.fbaWarehouseCode, 'FBA-1')
|
|
|
|
|
assert.equal(target.warehouse.id, '202')
|
|
|
|
|
assert.equal(f.calls.some(c => c.method !== 'GET'), false)
|
|
|
|
|
})
|
|
|
|
|
|
2026-10-22 15:41:50 +08:00
|
|
|
for (const fault of ['no resource', 'occupied', 'location blocked', 'network', 'target drift', 'foreign customer', 'double receipt', 'replay drift', 'unsettled', 'disk']) {
|
|
|
|
|
test(`转运收货遇到 ${fault} 明确失败且不补造资源`, async () => {
|
|
|
|
|
const f = fixture(fault)
|
|
|
|
|
await assert.rejects(receiveWarehouseTransfer(f.context, f.options))
|
|
|
|
|
assert.equal(f.proof.receipt.status, 'FAILED')
|
|
|
|
|
const writes = f.calls.filter(c => c.method === 'POST').length
|
|
|
|
|
assert.equal(writes, ['no resource', 'occupied', 'location blocked', 'disk'].includes(fault) ? 0 : ['replay drift', 'unsettled'].includes(fault) ? 2 : 1)
|
|
|
|
|
if (fault === 'network') assert.equal(f.proof.receipt.requests.at(-1).status, 'UNKNOWN_RESULT')
|
|
|
|
|
})
|
|
|
|
|
}
|