110 lines
6.8 KiB
JavaScript
110 lines
6.8 KiB
JavaScript
import test from 'node:test'
|
|
import assert from 'node:assert/strict'
|
|
import { readFileSync } from 'node:fs'
|
|
|
|
const source = readFileSync(new URL('../src/pages/outbound/outbound.vue', import.meta.url), 'utf8').match(/<script>([\s\S]*?)<\/script>/)[1]
|
|
const deferred = () => { let resolve; const promise = new Promise(done => { resolve = done }); return { promise, resolve } }
|
|
function page(overrides = {}) {
|
|
const calls = []
|
|
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,
|
|
...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 }
|
|
}
|
|
|
|
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: [] }))
|
|
const loose = { outboundPalletItemId: 6, palletNo: '', plannedPackages: 2, pickedPackages: 0, positions: [] }
|
|
vm.current = { outboundType: 'RETURN_OUTBOUND', items: [...cartons, loose, { outboundPalletItemId: 7, palletNo: 'OTHER', plannedPackages: 2, pickedPackages: 1, positions: [] }] }
|
|
const before = JSON.stringify(vm.current.items)
|
|
assert.equal(vm.displayItems.length, 3)
|
|
assert.equal(vm.displayItems[0].plannedPackages, 5)
|
|
assert.equal(vm.displayItems[0].pickedPackages, 3)
|
|
assert.equal(vm.displayItems[1], loose)
|
|
assert.equal(vm.displayItems[2].palletNo, 'OTHER')
|
|
assert.equal(JSON.stringify(vm.current.items), before)
|
|
assert.match(vm.scanLabel, /扫描托盘号/)
|
|
assert.match(vm.scanPlaceholder, /扫描托盘号/)
|
|
cartons.forEach(item => { item.pickedPackages = 1 })
|
|
assert.equal(vm.displayItems[0].pickedPackages, 5)
|
|
})
|
|
|
|
test('other outbound types preserve individual selectable lines', () => {
|
|
const { vm } = page()
|
|
const items = [{ palletNo: 'PALLET', plannedPackages: 1, pickedPackages: 0, positions: [] }, { palletNo: 'PALLET', plannedPackages: 2, pickedPackages: 0, positions: [] }]
|
|
vm.current = { outboundType: 'NORMAL', items }
|
|
assert.equal(vm.displayItems, items)
|
|
assert.equal(vm.hasReturnPallet, false)
|
|
})
|
|
|
|
test('completed and cancelled tasks cannot submit pallet picking or task completion', async () => {
|
|
for (const status of ['COMPLETED', 'CANCELLED']) {
|
|
const { vm, calls } = page()
|
|
vm.current = { taskNo: 'TASK', status, items: [{ palletNo: 'PALLET', plannedPackages: 1, pickedPackages: 1, positions: [] }] }
|
|
await vm.handleScan('PALLET'); await vm.submitPalletPick('PALLET'); await vm.completeTask()
|
|
assert.deepEqual(calls, [])
|
|
}
|
|
})
|
|
|
|
test('pending and in-progress tasks still submit exactly once while busy', async () => {
|
|
for (const status of ['PENDING', 'IN_PROGRESS']) {
|
|
const pending = deferred(); let submits = 0
|
|
const { vm } = page({ pickOutboundPallet: async () => { submits++; await pending.promise } })
|
|
vm.current = { taskNo: 'TASK', status, items: [{ palletNo: 'PALLET', plannedPackages: 1, pickedPackages: 0, positions: [] }] }
|
|
const first = vm.handleScan('PALLET'); const second = vm.handleScan('PALLET')
|
|
pending.resolve(); await Promise.all([first, second]); assert.equal(submits, 1)
|
|
}
|
|
})
|
|
|
|
test('failed new pallet lookup clears the previous selectable pallet and preserves retry code', async () => {
|
|
const { vm } = page({ resolveCargoDiscardPallet: async () => { throw new Error('offline') } })
|
|
vm.discardCurrent = { discardNo: 'DISCARD' }; vm.discardTask = { palletNo: 'OLD', status: 'PENDING', skus: [] }
|
|
await vm.resolveDiscardPallet('NEW')
|
|
assert.equal(vm.discardTask, null); assert.equal(vm.canConfirmDiscard, false)
|
|
assert.equal(vm.discardPalletCode, 'NEW'); assert.equal(vm.error.message, 'offline'); assert.equal(vm.discardLoading, false)
|
|
})
|
|
|
|
test('discard pallet scans do not overlap and cannot leave the previous pallet ready to submit', async () => {
|
|
const pending = deferred(); let resolves = 0
|
|
const { vm } = page({ resolveCargoDiscardPallet: async () => { resolves++; return pending.promise } })
|
|
vm.discardCurrent = { discardNo: 'DISCARD' }; vm.discardTask = { palletNo: 'OLD', status: 'PENDING', skus: [{ itemId: 'sku', plannedPackages: 1 }] }; vm.discardActuals = { sku: 1 }
|
|
const first = vm.resolveDiscardPallet('NEW')
|
|
const second = vm.resolveDiscardPallet('SECOND')
|
|
const canConfirmDuringLoad = vm.canConfirmDiscard
|
|
pending.resolve({ discardNo: 'DISCARD', palletNo: 'NEW', status: 'PENDING', skus: [] }); await Promise.all([first, second])
|
|
assert.equal(resolves, 1); assert.equal(canConfirmDuringLoad, false)
|
|
assert.equal(vm.discardTask.palletNo, 'NEW')
|
|
})
|
|
|
|
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()
|
|
pending.resolve(); await Promise.all([first, second]); assert.equal(submits, 1)
|
|
})
|
|
|
|
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'
|
|
vm.resolveDiscardPallet = async code => calls.push(['discard', code]); vm.openTask = async code => calls.push(['outbound', code])
|
|
await vm.retry(); assert.deepEqual(calls, [['discard', 'PALLET']])
|
|
})
|
|
|
|
test('closing or switching discard order ignores the previous resolve response', async () => {
|
|
const pending = deferred()
|
|
const { vm } = page({ resolveCargoDiscardPallet: () => pending.promise })
|
|
vm.discardCurrent = { discardNo: 'FIRST' }
|
|
const first = vm.resolveDiscardPallet('PALLET')
|
|
vm.openDiscardOrder('SECOND')
|
|
pending.resolve({ discardNo: 'FIRST', palletNo: 'PALLET', skus: [] }); await first
|
|
assert.equal(vm.discardTask, null); assert.equal(vm.error, null)
|
|
})
|