fix(pda): align return workflows with current test backend

This commit is contained in:
2026-09-29 23:25:00 +08:00
parent fddaaf71fb
commit 33c395937a
8 changed files with 31 additions and 333 deletions
+2 -102
View File
@@ -20,8 +20,6 @@ function setup(confirm, saved = new Map(), scope = 'operator:warehouse') {
palletCode: ' PLT-TEST ', palletLength: 10, palletWidth: 10, palletHeight: 10, palletWeight: 1,
quantity: () => 2, boxCount: () => 1
})
instance.changeScope = scope => vm.runInContext(`restoreWarehouseWork(${JSON.stringify(scope)})`, context)
Object.defineProperty(instance, 'receiptBatch', { get: () => vm.runInContext('warehouseWork.returnReceipt.pendingBatch', context) })
return instance
}
@@ -32,6 +30,8 @@ test('PALLET page submits the required palletCode through the actual confirmatio
assert.equal(calls.length, 1)
assert.equal(calls[0].palletCode, 'PLT-TEST')
assert.equal(calls[0].unitCode, undefined)
assert.deepEqual(Object.keys(calls[0]).sort(), ['idempotencyKey', 'items', 'palletCode', 'returnNo', 'startedBy', 'trackingNo'].sort())
assert.deepEqual(Object.keys(calls[0].items[0]).sort(), ['receivedQuantity', 'skuCode'].sort())
assert.equal(calls[0].returnNo, 'RT-TEST')
assert.equal(calls[0].trackingNo, 'TRACK-TEST')
assert.equal(calls[0].items[0].receivedQuantity, 2)
@@ -50,103 +50,3 @@ test('PALLET response failure keeps the draft and same idempotency key for retry
assert.equal(calls.length, 2)
assert.equal(calls[0].idempotencyKey, calls[1].idempotencyKey)
})
function boxPage(confirm, saved, scope) {
return Object.assign(setup(confirm, saved, scope), { unitType: 'BOX', boxRows: [{ items: { 'SKU-TEST': 1 } }, { items: { 'SKU-TEST': 2 } }] })
}
test('BOX partial success resumes only unconfirmed boxes with the original payload and key', async () => {
const calls = []; let fail = true
const page = boxPage(async payload => {
calls.push(structuredClone(payload))
if (payload.items[0].receivedQuantity === 2 && fail) throw new Error('response lost')
return { unitType: 'BOX', cartonMark: `BOX-${payload.items[0].receivedQuantity}`, printableHtml: '<p>label</p>' }
})
await page.confirmReceipt()
assert.equal(calls.length, 2)
page.boxRows[0].items['SKU-TEST'] = 3
page.boxRows[1].items['SKU-TEST'] = 4
page.trackingNo = 'changed'
fail = false
await page.confirmReceipt()
assert.equal(calls.length, 3)
assert.deepEqual(calls[2], calls[1])
assert.equal(page.result.labels.length, 2)
assert.equal(page.receiptBatch, null)
})
test('BOX progress survives a new page instance even when the order no longer allows a fresh receipt', async () => {
const saved = new Map(); const calls = []; let fail = true
const confirm = async payload => {
calls.push(structuredClone(payload))
if (payload.items[0].receivedQuantity === 2 && fail) throw new Error('response lost')
return { unitType: 'BOX', cartonMark: `BOX-${payload.items[0].receivedQuantity}`, printableHtml: '<p>label</p>' }
}
await boxPage(confirm, saved).confirmReceipt()
const restored = boxPage(confirm, saved)
restored.current = null; restored.canConfirm = false
fail = false
await restored.confirmReceipt()
assert.equal(calls.length, 3)
assert.deepEqual(calls[2], calls[1])
assert.equal(restored.result.labels.length, 2)
})
test('pending BOX receipt cannot be discarded by switching order and does not leak to another operator', async () => {
const saved = new Map()
const page = boxPage(async () => { throw new Error('response lost') }, saved)
await page.confirmReceipt()
page.clearCurrent()
assert.ok(page.receiptBatch)
assert.equal(page.current.returnNo, 'RT-TEST')
const other = boxPage(async () => { throw new Error('must not submit') }, saved, 'other:warehouse')
assert.equal(other.receiptBatch, null)
})
test('BOX request is not sent when the immutable batch cannot be persisted', async () => {
class FailingStorage extends Map {
set(key, value) { if (value.returnReceipt?.pendingBatch) throw new Error('storage unavailable'); return super.set(key, value) }
}
let calls = 0
const page = boxPage(async () => { calls++; return {} }, new FailingStorage())
await page.confirmReceipt()
assert.equal(calls, 0)
assert.equal(page.error.message, 'storage unavailable')
assert.equal(page.busy, false)
})
test('BOX lost progress persistence replays only with the same durable key after reload', async () => {
class OnceFailingStorage extends Map {
fail = true
set(key, value) {
if (this.fail && value.returnReceipt?.pendingBatch?.entries[0].result) { this.fail = false; throw new Error('storage unavailable') }
return super.set(key, value)
}
}
const saved = new OnceFailingStorage(); const calls = []
const confirm = async payload => { calls.push(structuredClone(payload)); return { unitType: 'BOX', cartonMark: 'BOX', printableHtml: '<p>label</p>' } }
const first = boxPage(confirm, saved)
await first.confirmReceipt()
assert.equal(calls.length, 1)
assert.equal(first.error.message, 'storage unavailable')
const restored = boxPage(confirm, saved)
await restored.confirmReceipt()
assert.equal(calls.length, 3)
assert.deepEqual(calls[0], calls[1])
assert.equal(restored.result.labels.length, 2)
})
test('BOX response arriving after account change cannot repopulate another account draft', async () => {
const saved = new Map(); let calls = 0
const page = boxPage(async () => {
calls++
// Simulate the real shared store changing identity while the request is in flight.
page.changeScope('other:warehouse')
return { unitType: 'BOX', cartonMark: 'BOX' }
}, saved)
await page.confirmReceipt()
assert.equal(calls, 1)
assert.equal(page.receiptBatch, null)
assert.match(page.error.message, /账号或仓库已改变/)
})