import test from 'node:test' import assert from 'node:assert/strict' import { readFileSync } from 'node:fs' import vm from 'node:vm' import { parse, compileScript, compileTemplate } from 'vue/compiler-sfc' const filename = 'src/components/ReturnBoxMergeWork.vue' const source = readFileSync(new URL('../' + filename, import.meta.url), 'utf8') const { descriptor } = parse(source) test('box merge component compiles both its script and template', () => { compileScript(descriptor, { id: 'box-merge' }) assert.deepEqual(compileTemplate({ source: descriptor.template.content, filename, id: 'box-merge' }).errors, []) }) function instance(runtime = {}) { 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 context = vm.createContext({ reactive: value => value, ScanTargetInput: {}, ReturnLabelConfirmation: {}, component: null, scanReturnCarton: async mark => boxes.find(box => box.cartonMark === mark), ...runtime }) vm.runInContext(readFileSync(new URL('../src/stores/warehouse-work.js', import.meta.url), 'utf8').replace(/^import .*$/gm, '').replace(/export /g, ''), context) const component = vm.runInContext(descriptor.script.content.replace(/^import .*$/gm, '').replace('export default', 'component ='), context) 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 = () => {} Object.defineProperty(work, 'canComplete', { get: () => component.computed.canComplete.call(work) }) return {work,component} } 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') await work.acceptScan('A'); await work.acceptScan('B') 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),7) assert.equal(component.computed.canComplete.call(work),true) }) test('partial merge preserves the scanned snapshot after failure and accepts a fresh scan without locking', async () => { let available = 10 const calls = [], errors = [] const { work } = instance({ scanReturnCarton: async () => ({ cartonMark: 'A', customerId: 'C1', items: [{ skuCode: 'SKU1', quantity: available }] }), mergeReturnBoxes: async request => { calls.push(JSON.parse(JSON.stringify(request))) if (request.expectedSourceQuantities[0].quantity !== available) throw new Error('来源箱库存已变化') available -= request.sourceDeductions[0].quantity if (calls.length === 1) throw new Error('response lost after commit') return { label: { cartonMark: 'NEW' } } } }) work.plan = null; work.choose('NEW'); work.$emit = (name, value) => { if (name === 'error') errors.push(value) } await work.scanSource('A'); work.setDeduction('A', 'SKU1', 3) work.dimensions = { length: 10, width: 10, height: 10, weight: 1 } await work.complete(); await work.complete() assert.equal(available, 7) assert.equal(work.busy, false) assert.equal(errors.at(-1).message, '来源箱库存已变化') assert.notEqual(calls[0].idempotencyKey, calls[1].idempotencyKey) work.remove('A'); await work.scanSource('A'); work.setDeduction('A', 'SKU1', 2) await work.complete() assert.equal(available, 5) assert.deepEqual(calls.map(call => call.expectedSourceQuantities[0].quantity), [10, 10, 7]) assert.equal(work.busy, false) }) 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,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) assert.equal(work.plannedItems().length,0) }) 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') work.sourceCode='B' assert.equal(component.computed.canComplete.call(work),false) await work.scanSource('B') assert.equal(work.sources.length,1) assert.equal(work.sources[0].cartonMark,'B') assert.equal(work.sourceCode,'B') assert.equal(work.target,null) assert.equal(work.targetCode,'') assert.equal(work.plannedItems().length,0) await work.scanTarget('A'); work.setDeduction('B','SKU1',1) 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 work.choose('EXISTING') assert.equal(work.sources[0].cartonMark,'A');assert.equal(work.sourceCode,'A') work.choose('NEW') assert.equal(work.sources[0].items.length,2) work.choose('EXISTING') assert.equal(work.sourceCode,'A');assert.equal(work.active,'target') })