fix: complete October 22 workflow feedback and request failure recovery

This commit is contained in:
2026-10-22 23:06:06 +08:00
parent 161128ff9e
commit 54e49e2426
28 changed files with 530 additions and 413 deletions
+58 -10
View File
@@ -122,13 +122,12 @@ function setup(confirm, saved = new Map(), scope = 'operator:warehouse', runtime
const component = vm.runInContext(script.replace(/^\s*import .*$/mg, '').replace('export default', 'globalThis.component ='), context)
vm.runInContext(`restoreWarehouseWork(${JSON.stringify(scope)})`, context)
const instance = Object.assign({}, component.methods, {
unitType: 'PALLET', palletBoxCount: 1, canConfirm: true, busy: false,
receiptResults: [], unitType: 'PALLET', palletBoxCount: 1, canConfirm: true, busy: false,
current: { returnNo: 'RT-TEST', expectedItems: [{ skuCode: 'SKU-TEST', expectedQuantity: 10, receivedQuantity: 0 }] },
selectedItems: [{ skuCode: 'SKU-TEST', expectedQuantity: 10, receivedQuantity: 0 }], photoFileIds: [], photoNames: [], photoUrls: {}, trackingNo: ' TRACK-TEST ', startedBy: 'SKU',
palletCode: ' PLT-TEST ', palletLength: 10, palletWidth: 10, palletHeight: 10, palletWeight: 1,
quantity: () => 2, boxCount: () => 1
})
Object.defineProperty(instance, 'receiptBatch', { get: () => vm.runInContext('warehouseWork.returnReceipt.pendingBatch', context) })
Object.defineProperty(instance, 'allBoxRowsConfirmed', { get: () => component.computed.allBoxRowsConfirmed.call(instance) })
return instance
}
@@ -156,7 +155,7 @@ 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(), ['height', 'idempotencyKey', 'items', 'length', 'palletCode', 'returnNo', 'startedBy', 'totalBoxCount', 'trackingNo', 'unitType', 'weight', 'width'].sort())
assert.deepEqual(Object.keys(calls[0]).sort(), ['expectedReceivedQuantity', 'height', 'idempotencyKey', 'items', 'length', 'palletCode', 'returnNo', 'startedBy', 'totalBoxCount', 'trackingNo', 'unitType', 'weight', 'width'].sort())
assert.equal(calls[0].unitType, 'PALLET')
assert.equal(calls[0].totalBoxCount, 1)
assert.deepEqual(Object.keys(calls[0].items[0]).sort(), ['boxCount', 'receivedQuantity', 'skuCode'].sort())
@@ -232,7 +231,7 @@ test('one submission signs multiple mixed-SKU parcels sharing one waybill and in
assert.deepEqual(Array.from(page.result.labels, row => row.cartonMark), ['RP-1', 'RP-2'])
})
test('failed multi-parcel batch resumes immutable requests without re-signing a successful parcel', async () => {
test('failed multi-parcel receipt keeps successful results and accepts edits to remaining rows', async () => {
const saved = new Map(), calls = []
let failSecond = true
const confirm = async request => {
@@ -243,16 +242,65 @@ test('failed multi-parcel batch resumes immutable requests without re-signing a
const page = setup(confirm, saved)
page.unitType = 'PARCEL'; page.photoFileIds = ['PHOTO-EXISTING']; page.unitCode = 'WAYBILL-BATCH'; page.boxRows = [physicalRow({ 'SKU-TEST': 1 }), physicalRow({ 'SKU-TEST': 2 })]
await page.confirmReceipt()
assert.equal(page.receiptBatch.entries[0].result.labels[0].cartonMark, 'RP-1')
assert.equal(page.receiptResults[0].labels[0].cartonMark, 'RP-1'); assert.equal(page.boxRows.length, 1); assert.equal(page.busy, false)
failSecond = false
const restored = setup(confirm, saved)
const restored = page; page.boxRows[0].items['SKU-TEST'] = 3
await restored.confirmReceipt()
assert.equal(calls.length, 3)
assert.equal(calls[1].idempotencyKey, calls[2].idempotencyKey)
assert.deepEqual(calls[1], calls[2])
assert.notEqual(calls[1].idempotencyKey, calls[2].idempotencyKey)
assert.equal(calls[2].items[0].receivedQuantity, 3)
assert.deepEqual(calls.map(call => call.expectedReceivedQuantity), [0, 1, 1])
assert.equal(restored.result.labels.length, 2)
})
test('lost receipt response keeps the old quantity precondition and does not lock editing or refresh', async () => {
const calls = []
let received = 0
const page = setup(async request => {
calls.push(JSON.parse(JSON.stringify(request)))
if (request.expectedReceivedQuantity !== received) throw new Error('退货单实收数量已变化')
received += request.items[0].receivedQuantity
if (calls.length === 1) throw new Error('response lost after commit')
return { unitType: 'PARCEL', fullyMatched: false }
}, new Map(), 'operator:warehouse', {
startReturnReceipt: async () => ({ returnNo: 'RT-TEST', expectedItems: [{ skuCode: 'SKU-TEST', expectedQuantity: 10, receivedQuantity: received }], photos: [{ fileId: 'PHOTO' }] }),
downloadReturnPalletPhoto: async () => 'photo-url'
})
page.unitType = 'PARCEL'; page.unitCode = ''; page.photoFileIds = ['PHOTO']
page.boxRows = [physicalRow({ 'SKU-TEST': 2 })]
await page.confirmReceipt()
await page.confirmReceipt()
assert.equal(received, 2)
assert.equal(page.busy, false)
assert.equal(page.boxRows.length, 1)
assert.equal(page.error.message, '退货单实收数量已变化')
assert.notEqual(calls[0].idempotencyKey, calls[1].idempotencyKey)
// 用户刷新后,按当前实收量登记下一份实物。
await page.reloadCurrent()
assert.equal(page.boxRows[0].confirmed, false)
assert.deepEqual(Object.keys(page.boxRows[0].items), [])
await page.confirmReceipt()
assert.equal(received, 2, '刷新后原包裹不能再次提交')
assert.equal(calls.length, 2)
page.boxRows[0] = physicalRow({ 'SKU-TEST': 3 })
await page.confirmReceipt()
assert.equal(received, 5)
assert.deepEqual(calls.map(call => call.expectedReceivedQuantity), [0, 0, 2])
assert.equal(page.busy, false)
})
test('refresh with unchanged receipt quantities preserves editable input after a rejected request', async () => {
const page = setup(async () => { throw new Error('not committed') }, new Map(), 'operator:warehouse', {
startReturnReceipt: async () => ({ returnNo: 'RT-TEST', expectedItems: [{ skuCode: 'SKU-TEST', expectedQuantity: 10, receivedQuantity: 0 }], photos: [{ fileId: 'PHOTO' }] }),
downloadReturnPalletPhoto: async () => 'photo-url'
})
page.unitType = 'PARCEL'; page.unitCode = ''; page.photoFileIds = ['PHOTO']
page.boxRows = [physicalRow({ 'SKU-TEST': 2 })]
await page.confirmReceipt(); await page.reloadCurrent()
assert.equal(page.boxRows[0].items['SKU-TEST'], 2)
assert.equal(page.busy, false)
})
test('partial receipt continues the same order with fresh pallet measurements', async () => {
const page = setup(async () => ({}))
page.finishReceipt([{ fullyMatched: false }])
@@ -266,7 +314,7 @@ test('partial receipt continues the same order with fresh pallet measurements',
assert.equal(page.palletWeight, '')
})
test('PALLET response failure keeps the draft and same idempotency key for retry', async () => {
test('PALLET response failure keeps editable input and generates a new key for the next click', async () => {
const calls = []
const page = setup(async payload => { calls.push(JSON.parse(JSON.stringify(payload))); throw new Error('Response lost') })
await page.confirmReceipt()
@@ -275,7 +323,7 @@ test('PALLET response failure keeps the draft and same idempotency key for retry
assert.equal(page.error.message, 'Response lost')
await page.confirmReceipt()
assert.equal(calls.length, 2)
assert.equal(calls[0].idempotencyKey, calls[1].idempotencyKey)
assert.notEqual(calls[0].idempotencyKey, calls[1].idempotencyKey)
})
test('continue signing returns to SKU scanning and accepts a fresh scan', () => {