优化
This commit is contained in:
@@ -107,6 +107,7 @@ test('fulfillment API calls the frozen list detail pick complete and review rout
|
||||
request(options) {
|
||||
calls.push({ method: options.method, url: options.url, data: options.data, authorization: options.header.Authorization })
|
||||
let data = {}
|
||||
if (options.url.endsWith('/storage-rent/preparations')) data = { status: 'READY', ready: true }
|
||||
if (options.method === 'GET' && /\/dispatches(?:\?|$)/.test(options.url)) data = []
|
||||
else if (options.method === 'GET' && /\/outbound-tasks(?:\?|$)/.test(options.url)) data = []
|
||||
else if (options.method === 'GET' && options.url.includes('/dispatches/')) data = { dispatchNo: 'DSP-1', taskNo: 'TASK-1', items: [] }
|
||||
@@ -126,7 +127,7 @@ test('fulfillment API calls the frozen list detail pick complete and review rout
|
||||
await pickOutboundPallet('OUT/TASK/1', 'DP/1', { idempotencyKey: 'out-pallet-key' })
|
||||
await completeOutboundTask('OUT/TASK/1', { idempotencyKey: 'out-complete-key' })
|
||||
|
||||
assert.equal(calls.length, 13)
|
||||
assert.equal(calls.length, 16)
|
||||
assert.deepEqual(calls.find(call => call.url.endsWith('/dispatches/DSP%2F1/loading-review')).data, { recordVersion: 0, idempotencyKey: 'review-key', decision: 'REJECTED', reason: '车牌不符' })
|
||||
assert.ok(calls.some((call) => call.url.endsWith('/dispatches')))
|
||||
assert.ok(calls.some((call) => call.url.includes('/dispatches/DSP%2F1/items/11/pick')))
|
||||
|
||||
@@ -254,5 +254,31 @@ test('partial bound pallet confirmation sends only selected marks and preserves
|
||||
await vm.resolveDiscardPallet('BOX-2'); assert.equal(vm.canConfirmDiscard, true)
|
||||
await vm.resolveDiscardPallet('BOX-OTHER'); assert.deepEqual(vm.discardScannedMarks, ['BOX-2']); assert.equal(vm.discardTask.palletNo, 'P-1')
|
||||
await vm.confirmDiscard()
|
||||
assert.deepEqual(payload, ['CD-P', 'P-1', [{ itemId: 'sku', actualPackages: 1 }], ['101'], ['BOX-2'], false])
|
||||
assert.deepEqual(payload, ['CD-P', 'P-1', [{ itemId: 'sku', actualPackages: 1 }], ['101'], ['BOX-2'], false, { idempotencyKey: 'key' }])
|
||||
})
|
||||
|
||||
test('discard confirmation retains its original request key until a definite successful response', async () => {
|
||||
const released = []; const submitted = []
|
||||
const retry = deferred()
|
||||
let attempt = 0
|
||||
const { vm } = page({
|
||||
confirmCargoDiscard: async (...args) => { submitted.push(args); if (++attempt === 1) throw new Error('费用确认中'); await retry.promise },
|
||||
releaseFulfillmentKey: (...args) => released.push(args), listCargoDiscardTasks: async () => []
|
||||
})
|
||||
vm.discardCurrent = { discardNo: 'CD-RETRY' }
|
||||
vm.discardTask = { palletNo: 'P-1', status: 'PENDING', skus: [{ itemId: 'sku', plannedPackages: 1 }] }
|
||||
vm.discardActuals = { sku: 1 }; vm.discardPhotoReady = true; vm.discardPhotos = [{ fileId: '101' }]
|
||||
await vm.confirmDiscard()
|
||||
assert.equal(released.length, 0)
|
||||
assert.ok(vm.discardTask)
|
||||
assert.equal(vm.error.message, '费用确认中')
|
||||
vm.successMessage = '上次作业已完成'
|
||||
const retried = vm.confirmDiscard()
|
||||
assert.equal(vm.error, null)
|
||||
assert.equal(vm.successMessage, '')
|
||||
retry.resolve(); await retried
|
||||
assert.deepEqual(submitted[0], submitted[1])
|
||||
assert.deepEqual(released, [['outbound', 'signature']])
|
||||
assert.equal(vm.error, null)
|
||||
assert.match(vm.successMessage, /已完成/)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { pickDispatchItem, pickOutboundTask, pickOutboundPallet, confirmCargoDiscard } from '../src/api/fulfillment.js'
|
||||
import { session } from '../src/stores/session.js'
|
||||
|
||||
function transport(reply) {
|
||||
const calls = []
|
||||
session.apiBase = '/api'; session.token = 'overseas-token'
|
||||
globalThis.uni = { request(options) {
|
||||
calls.push({ url: options.url, data: options.data })
|
||||
if (reply instanceof Error) return options.fail({ errMsg: reply.message })
|
||||
options.success({ statusCode: 200, data: { code: '200', data: options.url.endsWith('/preparations') ? reply : { success: true } }, header: {} })
|
||||
} }
|
||||
return calls
|
||||
}
|
||||
|
||||
test('all stock scan modes require ready preparation with the original command key and source identifiers', async () => {
|
||||
const calls = transport({ status: 'READY', ready: true })
|
||||
const request = { recordVersion: '9007199254740993', positionId: '9007199254740994', packages: 2, cartonMark: 'RC-1', productQuantity: 7, idempotencyKey: 'original-dispatch-key' }
|
||||
await pickDispatchItem('DSP-1', '9007199254740995', request)
|
||||
assert.deepEqual(calls[0].data, { mode: 'DISPATCH', businessNo: 'DSP-1', itemId: '9007199254740995', positionId: request.positionId, packages: 2, cartonMark: 'RC-1', productQuantity: 7, idempotencyKey: request.idempotencyKey })
|
||||
assert.deepEqual(calls[1].data, request)
|
||||
await pickOutboundTask('TASK-1', { outboundPalletItemId: '9007199254740996', inventoryPositionId: null, packages: 1, cartonMarks: [], skuCode: 'SKU-A', productQuantity: 5, idempotencyKey: 'original-sku-key' })
|
||||
assert.deepEqual(calls[2].data, { mode: 'OUTBOUND', businessNo: 'TASK-1', itemId: '9007199254740996', positionId: null, packages: 1, cartonMarks: [], skuCode: 'SKU-A', productQuantity: 5, idempotencyKey: 'original-sku-key' })
|
||||
await pickOutboundPallet('TASK-1', 'DP-1', { idempotencyKey: 'original-pallet-key' })
|
||||
assert.deepEqual(calls[4].data, { mode: 'OUTBOUND_PALLET', businessNo: 'TASK-1', palletNo: 'DP-1', idempotencyKey: 'original-pallet-key' })
|
||||
assert.equal(calls.length, 6)
|
||||
for (let i = 0; i < calls.length; i += 2) assert.ok(calls[i].url.endsWith('/preparations'))
|
||||
})
|
||||
|
||||
test('carton outbound preparation uses the same identity as the backend stock command', async () => {
|
||||
const calls = transport({ status: 'READY', ready: true })
|
||||
const request = { outboundPalletItemId: '21', inventoryPositionId: null, packages: 1, cartonMarks: ['RETURN-BOX-1'], idempotencyKey: 'carton-key' }
|
||||
await pickOutboundTask('TASK-2', request)
|
||||
assert.deepEqual(calls[0].data.cartonMarks, request.cartonMarks)
|
||||
// 出库后台消费只使用 cartonMarks,不能额外派生 cartonMark 导致幂等摘要不一致。
|
||||
assert.equal(Object.hasOwn(calls[0].data, 'cartonMark'), false)
|
||||
assert.deepEqual(calls[1].data, request)
|
||||
})
|
||||
|
||||
test('pending, failed, expired, malformed and network responses never send a stock mutation', async () => {
|
||||
for (const response of [{ status: 'FREEZE_PENDING', ready: false }, { status: 'FAILED', message: '余额及授信不足' }, { status: 'EXPIRED', ready: false }, { status: 'READY', ready: false }, { status: 'UNKNOWN', ready: true }, {}, new Error('network down')]) {
|
||||
const calls = transport(response)
|
||||
await assert.rejects(pickOutboundPallet('TASK-1', 'DP-1', { idempotencyKey: 'stable-key' }), error => error.storageRentPreparation === true)
|
||||
assert.equal(calls.length, 1)
|
||||
}
|
||||
})
|
||||
|
||||
test('retry reuses the preparation key; exempt and already consumed commands can recover the stock result', async () => {
|
||||
const request = { idempotencyKey: 'stable-key' }
|
||||
let calls = transport({ status: 'FREEZE_PENDING', ready: false })
|
||||
await assert.rejects(pickOutboundPallet('TASK-1', 'DP-1', request))
|
||||
const original = calls[0].data
|
||||
for (const status of ['NOT_REQUIRED', 'READY', 'CONSUMED']) {
|
||||
calls = transport({ status, ready: true })
|
||||
await pickOutboundPallet('TASK-1', 'DP-1', request)
|
||||
assert.deepEqual(calls[0].data, original)
|
||||
assert.equal(calls.length, 2)
|
||||
}
|
||||
const page = await readFile(new URL('../src/pages/dispatch/dispatch.vue', import.meta.url), 'utf8')
|
||||
assert.match(page, /!cause\.storageRentPreparation/)
|
||||
})
|
||||
|
||||
test('invalid dispatch version or missing command identity cannot start a reservation', async () => {
|
||||
const calls = transport({ status: 'READY', ready: true })
|
||||
await assert.rejects(pickDispatchItem('DSP-1', '11', { idempotencyKey: 'key' }), /版本/)
|
||||
await assert.rejects(pickOutboundPallet('TASK-1', 'DP-1', {}), /标识/)
|
||||
assert.equal(calls.length, 0)
|
||||
})
|
||||
|
||||
test('discard requires funds before stock confirmation and preserves the complete source payload on retry', async () => {
|
||||
const lines = [{ itemId: '9007199254740993', actualPackages: 2 }]
|
||||
const submit = () => confirmCargoDiscard('CD-1', 'P-1', lines, ['101'], ['BOX-1'], false, { idempotencyKey: 'discard-stable-key' })
|
||||
let calls = transport({ status: 'FREEZE_PENDING', ready: false })
|
||||
await assert.rejects(submit(), error => error.storageRentPreparation === true)
|
||||
assert.equal(calls.length, 1)
|
||||
const original = calls[0].data
|
||||
assert.deepEqual(original, { mode: 'DISCARD', businessNo: 'CD-1', palletNo: 'P-1', lines, photoFileIds: ['101'], scannedCartonMarks: ['BOX-1'], confirmAllCartons: false, idempotencyKey: 'discard-stable-key' })
|
||||
calls = transport({ status: 'READY', ready: true })
|
||||
await submit()
|
||||
assert.deepEqual(calls[0].data, original)
|
||||
assert.equal(calls.length, 2)
|
||||
assert.ok(calls[1].url.endsWith('/cargo-discards/CD-1/pallets/P-1/confirm'))
|
||||
assert.deepEqual(calls[1].data, { lines, photoFileIds: ['101'], scannedCartonMarks: ['BOX-1'], confirmAllCartons: false, idempotencyKey: 'discard-stable-key' })
|
||||
})
|
||||
Reference in New Issue
Block a user