优化
This commit is contained in:
@@ -9,15 +9,37 @@ function page(overrides = {}) {
|
||||
const deps = { PortalHeader: {}, ScanTargetInput: {}, session: { currentUser: {} }, fulfillment: { outbound: { packages: 1, currentTaskNo: 'OLD-TASK' } },
|
||||
updateFulfillmentDraft() {}, fulfillmentIdempotencyKey: () => ({ value: 'key', signature: 'signature' }), releaseFulfillmentKey() {},
|
||||
playErrorSound() {}, playSuccessSound() {}, pickOutboundPallet: async () => calls.push('pick'), completeOutboundTask: async () => calls.push('complete'),
|
||||
getOutboundTask: async () => ({ taskNo: 'TASK', status: 'COMPLETED', items: [] }), normalizeCargoDiscardTask: value => value,
|
||||
listCargoDiscardPhotos: async () => [], getOutboundTask: async () => ({ taskNo: 'TASK', status: 'COMPLETED', items: [] }), normalizeCargoDiscardTask: value => value,
|
||||
...overrides }
|
||||
const definition = new Function(...Object.keys(deps), source.replace(/^\s*import .*$/gm, '').replace('export default', 'return'))(...Object.values(deps))
|
||||
const vm = definition.data()
|
||||
for (const [name, fn] of Object.entries(definition.methods)) vm[name] = fn.bind(vm)
|
||||
for (const [name, fn] of Object.entries(definition.computed)) Object.defineProperty(vm, name, { get: () => fn.call(vm) })
|
||||
return { vm, calls }
|
||||
return { vm, calls, definition }
|
||||
}
|
||||
|
||||
test('discard requires restored photos and retains task while native camera returns', async () => {
|
||||
const { vm, definition } = page({ requireSession: () => true, restoreFulfillmentDraft() {} })
|
||||
vm.discardCurrent = { discardNo: 'CD-1' }; vm.discardTask = { palletNo: 'PALLET', status: 'PENDING', skus: [{ itemId: 'sku', plannedPackages: 3 }] }; vm.discardActuals = { sku: 1 }
|
||||
assert.equal(vm.canConfirmDiscard, false)
|
||||
vm.discardPhotoReady = true; assert.equal(vm.canConfirmDiscard, false)
|
||||
vm.discardPhotos = [{ fileId: '101' }]; assert.equal(vm.canConfirmDiscard, true)
|
||||
vm.discardPhotoBusy = true; assert.equal(vm.canConfirmDiscard, false)
|
||||
let loads = 0; vm.startScanner = () => {}; vm.loadTasks = () => { loads++ }
|
||||
definition.onShow.call(vm)
|
||||
assert.equal(loads, 0); assert.equal(vm.discardTask.palletNo, 'PALLET')
|
||||
})
|
||||
|
||||
test('discard list failure blocks confirmation and stale downloads cannot replace a new pallet', async () => {
|
||||
const pending = deferred()
|
||||
const { vm } = page({ listCargoDiscardPhotos: async () => { throw new Error('offline photos') }, downloadCargoDiscardPhoto: () => pending.promise })
|
||||
vm.discardCurrent = { discardNo: 'CD-1' }; vm.discardTask = { palletNo: 'A', status: 'PENDING', skus: [{ itemId: 'sku', plannedPackages: 1 }] }; vm.discardActuals = { sku: 1 }
|
||||
vm.discardPhotos = [{ fileId: '101' }]; vm.discardPhotoReady = true
|
||||
await vm.restoreDiscardPhotos(); assert.equal(vm.canConfirmDiscard, false)
|
||||
const first = vm.loadDiscardPhoto('101'); vm.discardTask = { palletNo: 'B' }; vm.resetDiscardPhotos()
|
||||
pending.resolve('old-photo-url'); await first; assert.deepEqual(vm.discardPhotoUrls, {})
|
||||
})
|
||||
|
||||
test('return pallet rows sum carton counts without changing raw pick lines', () => {
|
||||
const { vm } = page()
|
||||
const cartons = Array.from({ length: 5 }, (_, index) => ({ outboundPalletItemId: index + 1, palletNo: 'PALLET', plannedPackages: 1, pickedPackages: index < 3 ? 1 : 0, positions: [] }))
|
||||
@@ -87,7 +109,7 @@ test('discard confirmation is guarded against double submission', async () => {
|
||||
const pending = deferred(); let submits = 0
|
||||
const { vm } = page({ confirmCargoDiscard: async () => { submits++; await pending.promise }, listCargoDiscardTasks: async () => [] })
|
||||
vm.discardCurrent = { discardNo: 'DISCARD' }; vm.discardTask = { palletNo: 'PALLET', status: 'PENDING', skus: [{ itemId: 'sku', plannedPackages: 1 }] }; vm.discardActuals = { sku: 1 }
|
||||
const first = vm.confirmDiscard(); const second = vm.confirmDiscard()
|
||||
vm.discardPhotoReady = true; vm.discardPhotos = [{ fileId: '101' }]; const first = vm.confirmDiscard(); const second = vm.confirmDiscard()
|
||||
pending.resolve(); await Promise.all([first, second]); assert.equal(submits, 1)
|
||||
})
|
||||
|
||||
|
||||
@@ -12,13 +12,14 @@ test('box merge component compiles both its script and template', () => {
|
||||
assert.deepEqual(compileTemplate({ source: descriptor.template.content, filename, id: 'box-merge' }).errors, [])
|
||||
})
|
||||
function instance() {
|
||||
const component = vm.runInNewContext(descriptor.script.content.replace(/^import .*$/gm, '').replace('export default', 'component ='), { ScanTargetInput: {}, ReturnLabelConfirmation: {}, component: null })
|
||||
const boxes = [{cartonMark:'A',customerId:'C1',items:[{skuCode:'SKU1',quantity:2},{skuCode:'SKU2',quantity:3}]},{cartonMark:'B',customerId:'C1',items:[{skuCode:'SKU1',quantity:2}]}]
|
||||
const component = vm.runInNewContext(descriptor.script.content.replace(/^import .*$/gm, '').replace('export default', 'component ='), { ScanTargetInput: {}, ReturnLabelConfirmation: {}, component: null, scanReturnCarton: async mark => boxes.find(box => box.cartonMark === mark) })
|
||||
const work = { ...component.data(), plan: {customerId:'C1',sources:[{cartonMark:'A',items:[{skuCode:'SKU1',quantity:2},{skuCode:'SKU2',quantity:3}]},{cartonMark:'B',items:[{skuCode:'SKU1',quantity:2}]}]}, $emit(){} }
|
||||
for (const [name, method] of Object.entries(component.methods)) work[name] = method.bind(work)
|
||||
work.save = () => {}
|
||||
return {work,component}
|
||||
}
|
||||
test('new plan requires scanning each source and keeps independent SKU deductions', async () => {
|
||||
test('new plan requires scanning each source and preserves locked SKU quantities', async () => {
|
||||
const {work,component}=instance(); work.choose('NEW')
|
||||
assert.equal(work.sources.length,0)
|
||||
assert.equal(work.active,'source')
|
||||
@@ -26,14 +27,20 @@ test('new plan requires scanning each source and keeps independent SKU deduction
|
||||
work.setDeduction('A','SKU1',1); work.setDeduction('A','SKU2',2); work.setDeduction('B','SKU1',2)
|
||||
work.dimensions={length:10,width:20,height:30,weight:1}
|
||||
assert.equal(work.sources.length,2)
|
||||
assert.equal(work.plannedItems().reduce((sum,item)=>sum+item.quantity,0),5)
|
||||
assert.equal(work.plannedItems().reduce((sum,item)=>sum+item.quantity,0),7)
|
||||
assert.equal(component.computed.canComplete.call(work),true)
|
||||
})
|
||||
|
||||
test('planned source carton marks are visible before scanning', () => {
|
||||
const {work, component} = instance()
|
||||
work.plan = { sourceCartonMarks: ['A', 'B'], sources: [] }
|
||||
assert.deepEqual(component.computed.plannedSourceRows.call(work).map(box => box.cartonMark), ['A', 'B'])
|
||||
})
|
||||
test('existing box uses another box as target and never removes target inventory', async () => {
|
||||
const {work,component}=instance(); work.choose('EXISTING'); await work.scanSource('A'); await work.scanTarget('B')
|
||||
assert.equal(work.target.cartonMark,'B'); assert.equal(work.sources.length,1)
|
||||
work.setDeduction('A','SKU1',1)
|
||||
assert.equal(work.plannedItems().length,1)
|
||||
assert.equal(work.plannedItems().length,2)
|
||||
assert.equal(work.target.items[0].quantity,2)
|
||||
assert.equal(component.computed.canComplete.call(work),true)
|
||||
work.choose('NEW'); assert.equal(work.target,null); assert.equal(work.sources.length,0)
|
||||
@@ -42,6 +49,7 @@ test('existing box uses another box as target and never removes target inventory
|
||||
|
||||
test('existing source can be replaced before merging, echoing the mark and clearing old deductions and target', async () => {
|
||||
const {work,component}=instance()
|
||||
work.plan=null
|
||||
work.choose('EXISTING'); await work.scanSource('A'); await work.scanTarget('B')
|
||||
work.setDeduction('A','SKU1',1)
|
||||
assert.equal(work.sourceCode,'A')
|
||||
@@ -58,6 +66,18 @@ test('existing source can be replaced before merging, echoing the mark and clear
|
||||
assert.equal(component.computed.canComplete.call(work),true)
|
||||
})
|
||||
|
||||
test('BOX plan requires every source and accumulates multiple scanned sources', async () => {
|
||||
const {work,component}=instance()
|
||||
work.plan.mode='BOX'
|
||||
work.plan.sources.push({cartonMark:'C',items:[{skuCode:'SKU1',quantity:4}]})
|
||||
work.choose('EXISTING'); await work.scanSource('A'); await work.scanTarget('B')
|
||||
assert.equal(component.computed.canComplete.call(work),false)
|
||||
await work.scanSource('C')
|
||||
assert.equal(work.sources.length,2)
|
||||
assert.equal(component.computed.canComplete.call(work),true)
|
||||
assert.equal(work.plannedItems().reduce((sum,item)=>sum+item.quantity,0),9)
|
||||
})
|
||||
|
||||
test('a directly scanned box survives switching between existing and new targets', async () => {
|
||||
const {work}=instance()
|
||||
const box=work.plan.sources[0];work.plan=null;work.initialSource=box
|
||||
|
||||
@@ -56,9 +56,20 @@ test('existing target immediately clears work and returns to refreshed plans eve
|
||||
test('new target retains labels for printing and can return without putaway', () => {
|
||||
const {page,saved,reset}=instance()
|
||||
page.packingCompleted({targetMode:'NEW',labels:[{cartonMark:'NEW#1-1'}]})
|
||||
assert.equal(page.busy,false)
|
||||
assert.equal(saved().result.labels[0].cartonMark,'NEW#1-1')
|
||||
assert.equal(reset(),false)
|
||||
page.busy=false; page.nextPacking()
|
||||
assert.equal(page.result,null)
|
||||
assert.equal(reset(),true)
|
||||
})
|
||||
|
||||
test('completed packing can return to plans even when the page busy flag is stale', () => {
|
||||
const {page,reset}=instance()
|
||||
page.result={labels:[{cartonMark:'NEW#1-1'}]}
|
||||
page.nextPacking()
|
||||
assert.equal(page.result,null)
|
||||
assert.equal(reset(),true)
|
||||
assert.equal(page.plans[0].planNo,'P2')
|
||||
assert.doesNotMatch(descriptor.template.content, /<button[^>]*:disabled[^>]*@tap="nextPacking"/)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user