优化
This commit is contained in:
@@ -9,7 +9,7 @@ function page(overrides = {}) {
|
||||
const deps = { PortalHeader: {}, ScanTargetInput: {}, session: { currentUser: {} }, fulfillment: { outbound: { packages: 1, currentTaskNo: 'OLD-TASK' } },
|
||||
updateFulfillmentDraft() {}, fulfillmentIdempotencyKey: () => ({ value: 'key' }),
|
||||
playErrorSound() {}, playSuccessSound() {}, pickOutboundPallet: async () => calls.push('pick'), completeOutboundTask: async () => calls.push('complete'),
|
||||
listCargoDiscardPhotos: async () => [], getOutboundTask: async () => ({ taskNo: 'TASK', status: 'COMPLETED', items: [] }), normalizeCargoDiscardTask: value => value,
|
||||
listCargoDiscardPhotos: async () => [], getOutboundTask: async () => ({ taskNo: 'TASK', status: 'COMPLETED', items: [] }), normalizeCargoDiscardTask: value => value, resolveCargoDiscardPallet: async code => vm.discardTasks.find(task => task.palletNo === code || task.cartonMarks?.includes(code)),
|
||||
...overrides }
|
||||
const definition = new Function(...Object.keys(deps), source.replace(/^\s*import .*$/gm, '').replace('export default', 'return'))(...Object.values(deps))
|
||||
const vm = definition.data()
|
||||
@@ -79,10 +79,13 @@ test('同单按箱废弃允许扫描当前托盘及其他托盘所选箱唛,
|
||||
await vm.resolveDiscardPallet('P-1')
|
||||
assert.equal(vm.error, null)
|
||||
await vm.resolveDiscardPallet('BOX-2')
|
||||
assert.equal(vm.discardTask.palletNo, 'P-1')
|
||||
await vm.resolveDiscardPallet('P-2')
|
||||
await vm.resolveDiscardPallet('BOX-2')
|
||||
assert.equal(vm.discardTask.palletNo, 'P-2')
|
||||
assert.deepEqual(vm.discardScannedMarks, ['BOX-2'])
|
||||
await vm.resolveDiscardPallet('BOX-3')
|
||||
assert.deepEqual(requests, ['P-1', 'BOX-2'])
|
||||
assert.deepEqual(requests, ['P-1', 'P-2'])
|
||||
assert.equal(vm.discardTask.palletNo, 'P-2')
|
||||
assert.deepEqual(vm.discardScannedMarks, ['BOX-2'])
|
||||
})
|
||||
@@ -206,6 +209,19 @@ test('无箱唛对应托盘扫码后实际数保持未填,必须由仓库实
|
||||
assert.equal(vm.canConfirmDiscard, false)
|
||||
})
|
||||
|
||||
test('无箱号任务先扫托盘,实际箱数保持未填供现场实点', async () => {
|
||||
const { vm } = page()
|
||||
vm.discardTasks = [{ discardNo: 'CD-NO-BOX', palletNo: 'P-1', status: 'PENDING', scanMode: 'PALLET', cartonMarks: [], skus: [{ itemId: 'sku', plannedPackages: 2, actualQuantityEditable: true }] }]
|
||||
vm.openDiscardOrder('CD-NO-BOX'); await Promise.resolve()
|
||||
assert.equal(vm.discardTask, null)
|
||||
await vm.resolveDiscardPallet('P-1')
|
||||
assert.equal(vm.discardTask.palletNo, 'P-1')
|
||||
assert.equal(vm.discardActuals.sku, '')
|
||||
vm.discardPhotoReady = true; vm.discardPhotos = [{ fileId: '101' }]
|
||||
assert.equal(vm.canConfirmDiscard, false)
|
||||
vm.discardActuals.sku = 2; assert.equal(vm.canConfirmDiscard, true)
|
||||
})
|
||||
|
||||
test('discard retry stays in the discard workflow instead of restoring an old outbound task', async () => {
|
||||
const { vm } = page(); const calls = []
|
||||
vm.discardCurrent = { discardNo: 'DISCARD' }; vm.discardPalletCode = 'PALLET'
|
||||
@@ -224,11 +240,12 @@ test('closing or switching discard order ignores the previous resolve response',
|
||||
})
|
||||
|
||||
|
||||
test('loose disposal opens all marks, blocks partial scans and accepts explicit bulk confirmation', async () => {
|
||||
test('loose disposal opens all marks and requires every carton scan', async () => {
|
||||
const { vm } = page()
|
||||
vm.discardTasks = [{ discardNo: 'CD-L', palletNo: '散箱', scanMode: 'CARTON', cartonMarks: ['BOX-1', 'BOX-2'], status: 'PENDING', skus: [{ itemId: 'sku', plannedPackages: 2 }] }]
|
||||
vm.openDiscardOrder('CD-L')
|
||||
await Promise.resolve()
|
||||
assert.equal(vm.discardTask, null)
|
||||
await vm.resolveDiscardPallet('BOX-1')
|
||||
assert.equal(vm.discardTask.scanMode, 'CARTON')
|
||||
assert.deepEqual(vm.discardTask.cartonMarks, ['BOX-1', 'BOX-2'])
|
||||
vm.discardPhotoReady = true; vm.discardPhotos = [{ fileId: '101' }]
|
||||
@@ -240,7 +257,7 @@ test('loose disposal opens all marks, blocks partial scans and accepts explicit
|
||||
assert.equal(vm.canConfirmDiscard, true)
|
||||
vm.discardActuals.sku = 1; assert.equal(vm.canConfirmDiscard, false)
|
||||
vm.discardActuals.sku = 2; vm.discardScannedMarks = []; vm.discardAllCartons = true
|
||||
assert.equal(vm.canConfirmDiscard, true)
|
||||
assert.equal(vm.canConfirmDiscard, false)
|
||||
vm.discardPhotos = []; assert.equal(vm.canConfirmDiscard, false)
|
||||
})
|
||||
|
||||
@@ -250,6 +267,8 @@ test('partial bound pallet confirmation sends only selected marks and preserves
|
||||
const { vm } = page({ confirmCargoDiscard: async (...args) => { payload = args }, listCargoDiscardTasks: async () => [] })
|
||||
vm.discardTasks = [{ discardNo: 'CD-P', outboundNo: 'OB-P', palletNo: 'P-1', scanMode: 'CARTON', cartonMarks: ['BOX-2'], status: 'PENDING', skus: [{ itemId: 'sku', plannedPackages: 1 }] }]
|
||||
vm.openDiscardOrder('CD-P'); await Promise.resolve()
|
||||
assert.equal(vm.discardTask, null)
|
||||
await vm.resolveDiscardPallet('P-1')
|
||||
vm.discardPhotoReady = true; vm.discardPhotos = [{ fileId: '101' }]
|
||||
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')
|
||||
@@ -267,6 +286,7 @@ test('discard confirmation preserves inputs after stock command failure and star
|
||||
})
|
||||
vm.discardCurrent = { discardNo: 'CD-RETRY' }
|
||||
vm.discardTask = { palletNo: 'P-1', status: 'PENDING', skus: [{ itemId: 'sku', plannedPackages: 1 }] }
|
||||
vm.discardTasks = [{ ...vm.discardTask, discardNo: 'CD-RETRY' }]
|
||||
vm.discardActuals = { sku: 1 }; vm.discardPhotoReady = true; vm.discardPhotos = [{ fileId: '101' }]
|
||||
await vm.confirmDiscard()
|
||||
assert.equal(vm.discardBusy, false)
|
||||
@@ -283,3 +303,129 @@ test('discard confirmation preserves inputs after stock command failure and star
|
||||
assert.equal(vm.error, null)
|
||||
assert.match(vm.successMessage, /已完成/)
|
||||
})
|
||||
|
||||
test('dedicated scanner validates carton scope and preserves photos', () => {
|
||||
const { vm } = page()
|
||||
vm.discardTask = { palletNo: 'P-1', scanMode: 'CARTON', cartonMarks: ['BX-1', 'BX-2'], status: 'PENDING', skus: [{ itemId: 'sku', plannedPackages: 2 }] }
|
||||
vm.discardActuals = { sku: 2 }; vm.discardPhotoReady = true; vm.discardPhotos = [{ fileId: '101' }]
|
||||
assert.deepEqual(vm.discardPendingMarks, ['BX-1', 'BX-2'])
|
||||
assert.deepEqual(vm.discardQuantityLines, [])
|
||||
vm.discardCartonCode = ' BX-1 '; vm.scanDiscardCarton()
|
||||
assert.equal(vm.discardCartonCode, '')
|
||||
vm.scanDiscardCarton('BX-1'); vm.scanDiscardCarton('OTHER')
|
||||
assert.deepEqual(vm.discardScannedMarks, ['BX-1']); assert.equal(vm.canConfirmDiscard, false)
|
||||
assert.deepEqual(vm.discardPendingMarks, ['BX-2'])
|
||||
assert.match(vm.error.message, /箱号不属于/)
|
||||
vm.discardCartonsExpanded = false
|
||||
vm.scanDiscardCarton('BX-2'); assert.equal(vm.canConfirmDiscard, true)
|
||||
assert.deepEqual(vm.discardPendingMarks, [])
|
||||
assert.equal(vm.discardCartonsExpanded, false)
|
||||
assert.deepEqual(vm.discardPhotos, [{ fileId: '101' }]); assert.equal(vm.discardTask.palletNo, 'P-1')
|
||||
vm.discardTask.status = 'COMPLETED'; vm.scanDiscardCarton('OTHER'); assert.equal(vm.error, null)
|
||||
})
|
||||
|
||||
test('bound discard opens only after its pallet scan and summary hides carton numbers', async () => {
|
||||
const { vm } = page()
|
||||
vm.discardTasks = [{ discardNo: 'CD-1', palletNo: 'P-1', status: 'PENDING', scanMode: 'CARTON', cartonMarks: ['BX-1'], skus: [{ itemId: 'sku', plannedPackages: 1 }] }]
|
||||
vm.openDiscardOrder('CD-1'); assert.equal(vm.discardTask, null)
|
||||
await vm.resolveDiscardPallet('BX-1'); assert.equal(vm.discardTask, null)
|
||||
await vm.resolveDiscardPallet('OTHER'); assert.equal(vm.discardTask, null)
|
||||
await vm.resolveDiscardPallet('P-1'); assert.equal(vm.discardTask.palletNo, 'P-1')
|
||||
assert.deepEqual(vm.discardScannedMarks, [])
|
||||
assert.deepEqual(vm.discardTask.cartonMarks, ['BX-1'])
|
||||
const template = readFileSync(new URL('../src/pages/outbound/outbound.vue', import.meta.url), 'utf8')
|
||||
const summary = template.slice(template.indexOf('本单待废弃托盘'), template.indexOf('<ScanTargetInput name="cargo-discard"'))
|
||||
assert.doesNotMatch(summary, /task.cartonMarks/)
|
||||
})
|
||||
|
||||
test('multi-pallet discard prompts remaining pallet numbers and requires a new pallet scan', async () => {
|
||||
const first = { discardNo: 'CD-M', palletNo: 'P-1', status: 'PENDING', scanMode: 'CARTON', cartonMarks: ['BX-1'], skus: [{ itemId: 'sku1', plannedPackages: 1 }] }
|
||||
const second = { ...first, palletNo: 'P-2', cartonMarks: ['BX-2'], skus: [{ itemId: 'sku2', plannedPackages: 1 }] }
|
||||
const other = { ...first, discardNo: 'CD-OTHER', palletNo: 'P-OTHER' }
|
||||
let completed = 0
|
||||
const { vm } = page({ confirmCargoDiscard: async () => { completed++ }, listCargoDiscardTasks: async () => [{ ...first, status: 'COMPLETED' }, { ...second, status: completed === 2 ? 'COMPLETED' : 'PENDING' }, other] })
|
||||
vm.discardTasks = [first, second, other]; vm.openDiscardOrder('CD-M')
|
||||
await vm.resolveDiscardPallet('P-1'); vm.scanDiscardCarton('BX-1')
|
||||
vm.discardPhotoReady = true; vm.discardPhotos = [{ fileId: '101' }]
|
||||
await vm.confirmDiscard()
|
||||
assert.equal(vm.discardTask, null); assert.equal(vm.discardPalletCode, '')
|
||||
assert.deepEqual(vm.discardPendingTasks.map(task => task.palletNo), ['P-2'])
|
||||
assert.match(vm.successMessage, /P-1.*已完成.*P-2.*继续扫描/)
|
||||
assert.doesNotMatch(vm.successMessage, /P-OTHER|全部/)
|
||||
assert.equal(vm.canConfirmDiscard, false)
|
||||
await vm.resolveDiscardPallet('P-2'); assert.equal(vm.successMessage, '')
|
||||
assert.deepEqual(vm.discardPendingMarks, ['BX-2']); assert.deepEqual(vm.discardPhotos, [])
|
||||
vm.scanDiscardCarton('BX-2'); vm.discardPhotoReady = true; vm.discardPhotos = [{ fileId: '102' }]
|
||||
await vm.confirmDiscard()
|
||||
assert.deepEqual(vm.discardPendingTasks, []); assert.match(vm.successMessage, /本单全部.*已完成/)
|
||||
})
|
||||
|
||||
test('successful discard followed by refresh failure does not claim the whole order completed', async () => {
|
||||
let confirms = 0
|
||||
const { vm } = page({ confirmCargoDiscard: async () => { confirms++ }, listCargoDiscardTasks: async () => { throw new Error('offline') } })
|
||||
const first = { discardNo: 'CD-M', palletNo: 'P-1', status: 'PENDING', skus: [{ itemId: 'sku', plannedPackages: 1 }] }
|
||||
vm.discardTasks = [first, { ...first, palletNo: 'P-2' }]; vm.openDiscardOrder('CD-M')
|
||||
await vm.resolveDiscardPallet('P-1'); vm.discardPhotoReady = true; vm.discardPhotos = [{ fileId: '101' }]
|
||||
await vm.confirmDiscard(); await vm.confirmDiscard()
|
||||
assert.equal(confirms, 1); assert.equal(vm.discardTask, null)
|
||||
assert.match(vm.successMessage, /P-1.*已完成.*刷新失败/); assert.doesNotMatch(vm.successMessage, /全部/)
|
||||
assert.deepEqual(vm.discardPendingTasks.map(task => task.palletNo), ['P-2'])
|
||||
})
|
||||
|
||||
test('仓租余额不足显示原因并保留当前托盘、已扫箱号、实际数与现场照片', async () => {
|
||||
const { vm } = page({ confirmCargoDiscard: async () => { throw Object.assign(new Error('仓租校验未通过:可用余额和授信额度不足'), { code: 'A0409' }) } })
|
||||
vm.discardCurrent = { discardNo: 'CD-1' }
|
||||
vm.discardTask = { palletNo: 'P-1', scanMode: 'CARTON', status: 'PENDING', cartonMarks: ['BX-1'], skus: [{ itemId: 'sku', plannedPackages: 1 }] }
|
||||
vm.discardActuals = { sku: 1 }; vm.discardScannedMarks = ['BX-1']
|
||||
vm.discardPhotoReady = true; vm.discardPhotos = [{ fileId: '101' }]
|
||||
await vm.confirmDiscard()
|
||||
assert.match(vm.error.message, /余额和授信额度不足/); assert.equal(vm.error.code, 'A0409')
|
||||
assert.equal(vm.discardTask.palletNo, 'P-1'); assert.deepEqual(vm.discardScannedMarks, ['BX-1'])
|
||||
assert.deepEqual(vm.discardActuals, { sku: 1 }); assert.deepEqual(vm.discardPhotos, [{ fileId: '101' }])
|
||||
assert.equal(vm.successMessage, ''); assert.equal(vm.discardBusy, false)
|
||||
})
|
||||
|
||||
for (const action of ['pallet', 'carton', 'retry']) {
|
||||
test(`出库资金不足后通过 ${action} 重新核对并再次手动提交`, async () => {
|
||||
let submits = 0; let reads = 0; let key = 0; const keys = []
|
||||
const task = { discardNo: 'CD-1', palletNo: 'P-1', status: 'PENDING', scanMode: 'CARTON', cartonMarks: ['BX-1'], skus: [{ itemId: 'sku', plannedPackages: 1 }] }
|
||||
const { vm } = page({
|
||||
resolveCargoDiscardPallet: async () => { reads++; return { ...task } },
|
||||
confirmCargoDiscard: async (...args) => { keys.push(args.at(-1).idempotencyKey); if (++submits === 1) throw new Error('可用余额和授信额度不足') },
|
||||
fulfillmentIdempotencyKey: () => ({ value: `key-${++key}` }), listCargoDiscardTasks: async () => []
|
||||
})
|
||||
vm.discardCurrent = { discardNo: 'CD-1' }; vm.discardTask = task; vm.discardTasks = [task]
|
||||
vm.discardActuals = { sku: 1 }; vm.discardScannedMarks = ['BX-1']
|
||||
vm.discardPhotos = [{ fileId: '101' }]; vm.discardPhotoReady = true
|
||||
await vm.confirmDiscard(); assert.equal(vm.discardNeedsRefresh, true)
|
||||
if (action === 'pallet') await vm.resolveDiscardPallet('P-1')
|
||||
else if (action === 'carton') await vm.scanDiscardCarton('BX-1')
|
||||
else await vm.retry()
|
||||
assert.equal(reads, 1); assert.equal(submits, 1); assert.equal(vm.error, null)
|
||||
assert.equal(vm.discardNeedsRefresh, false); assert.deepEqual(vm.discardScannedMarks, ['BX-1'])
|
||||
assert.deepEqual(vm.discardActuals, { sku: 1 }); assert.deepEqual(vm.discardPhotos, [{ fileId: '101' }])
|
||||
await vm.confirmDiscard(); assert.equal(submits, 2); assert.notEqual(keys[0], keys[1])
|
||||
assert.match(vm.successMessage, /全部.*已完成/)
|
||||
})
|
||||
}
|
||||
|
||||
test('结果不明后刷新发现已完成,不再重复出库', async () => {
|
||||
let submits = 0
|
||||
const task = { discardNo: 'CD-1', palletNo: 'P-1', status: 'PENDING', skus: [{ itemId: 'sku', plannedPackages: 1 }] }
|
||||
const { vm } = page({ confirmCargoDiscard: async () => { submits++; throw Object.assign(new Error('网络中断'), { network: true }) }, resolveCargoDiscardPallet: async () => ({ ...task, status: 'COMPLETED' }) })
|
||||
vm.discardCurrent = { discardNo: 'CD-1' }; vm.discardTask = task
|
||||
vm.discardActuals = { sku: 1 }; vm.discardPhotos = [{ fileId: '101' }]; vm.discardPhotoReady = true
|
||||
await vm.confirmDiscard(); await vm.confirmDiscard()
|
||||
assert.equal(submits, 1); assert.equal(vm.discardTask.status, 'COMPLETED'); assert.equal(vm.canConfirmDiscard, false)
|
||||
})
|
||||
|
||||
test('失败后刷新仍报错,保留草稿且不再发送出库', async () => {
|
||||
let submits = 0
|
||||
const { vm } = page({ confirmCargoDiscard: async () => { submits++; throw new Error('出库失败') }, resolveCargoDiscardPallet: async () => { throw new Error('任务读取失败') } })
|
||||
vm.discardCurrent = { discardNo: 'CD-1' }; vm.discardTask = { palletNo: 'P-1', status: 'PENDING', skus: [{ itemId: 'sku', plannedPackages: 1 }] }
|
||||
vm.discardActuals = { sku: 1 }; vm.discardPhotos = [{ fileId: '101' }]; vm.discardPhotoReady = true
|
||||
await vm.confirmDiscard(); await vm.confirmDiscard()
|
||||
assert.equal(submits, 1); assert.match(vm.error.message, /任务读取失败/)
|
||||
assert.equal(vm.discardNeedsRefresh, true); assert.equal(vm.discardBusy, false); assert.equal(vm.discardLoading, false)
|
||||
assert.deepEqual(vm.discardPhotos, [{ fileId: '101' }])
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user