Merge branch 'release/test' of http://git.buggoglobal.com/html/pda-overseas-uniapp into release/test
This commit is contained in:
@@ -76,7 +76,7 @@ test('outbound partial pick submits the selected carton and distinguishes shared
|
||||
assert.deepEqual(outboundPickPayload({ outboundPalletItemId: '13' }, { inventoryPositionId: '23' }, 2).cartonMarks, [])
|
||||
const page = await read('../src/pages/outbound/outbound.vue')
|
||||
assert.match(page, /listOutboundTasks\('PENDING'\)/)
|
||||
assert.match(page, /:key="item.outboundPalletItemId"/)
|
||||
assert.match(page, /:key="item.displayKey \|\| item.outboundPalletItemId"/)
|
||||
assert.match(page, /outboundPickPayload\(this.selectedItem, this.selectedPosition, this.packages, this.selectedScanCode\)/)
|
||||
assert.match(page, /pickOutboundTask\(this.current.taskNo, \{ \.\.\.payload, idempotencyKey: key.value \}\)/)
|
||||
assert.match(page, /pickOutboundPallet\(this.current.taskNo, code, \{ idempotencyKey: key.value \}\)/)
|
||||
@@ -88,7 +88,7 @@ test('return outbound preserves its task type and only permits pallet scans for
|
||||
assert.equal(task.outboundType, 'RETURN_OUTBOUND')
|
||||
const page = await read('../src/pages/outbound/outbound.vue')
|
||||
assert.match(page, /this\.current\?\.outboundType === 'RETURN_OUTBOUND' && item\.palletNo/)
|
||||
assert.match(page, /整托出库请扫描托盘码/)
|
||||
assert.match(page, /整托出库请扫描托盘号/)
|
||||
const item = { outboundPalletItemId: '12', skuCode: 'SKU-A' }
|
||||
const position = { inventoryPositionId: '', cartonMark: 'RC-1' }
|
||||
assert.deepEqual(outboundPickPayload(item, position, 1, 'SKU-A'), {
|
||||
|
||||
@@ -18,6 +18,32 @@ function page(overrides = {}) {
|
||||
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()
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { listReturnCountTasks, scanReturnCountCarton, scanReturnCountTasks, getCurrentReturnCountTask, beginReturnCountTask, confirmReturnCount } from '../src/api/return-counting.js'
|
||||
import { session } from '../src/stores/session.js'
|
||||
|
||||
test('counting preserves box contents and retry key without accepting client warehouse scope', async () => {
|
||||
session.apiBase = '/api'; session.token = 'counting-test'
|
||||
const calls = []
|
||||
globalThis.uni = { request(options) { calls.push(options); options.success({ statusCode: 200, data: { code: '200', data: { status: 'PENDING' } }, header: {} }) } }
|
||||
await listReturnCountTasks({ page: 2, keyword: ' RC#2-1 ' })
|
||||
await scanReturnCountCarton('RC#2-1')
|
||||
const request = { idempotencyKey: 'count-retry', boxes: [{ cartonMark: 'RC#2-1', items: [{ skuCode: 'SKU-1', quantity: 3 }] }, { cartonMark: 'RC#2-2', items: [{ skuCode: 'SKU-1', quantity: 7 }] }] }
|
||||
await confirmReturnCount('RR/1', request)
|
||||
assert.deepEqual(calls.map(row => [row.method, row.url]), [
|
||||
['GET', '/api/pda/v1/last-leg/return-inventory/counting/tasks?page=2&size=10&keyword=RC%232-1'],
|
||||
['GET', '/api/pda/v1/last-leg/return-inventory/counting/cartons/RC%232-1'],
|
||||
['POST', '/api/pda/v1/last-leg/return-inventory/counting/receipts/RR%2F1']
|
||||
])
|
||||
assert.deepEqual(calls[2].data, request)
|
||||
assert.ok(calls.every(row => row.header.Authorization === 'Bearer counting-test'))
|
||||
})
|
||||
|
||||
test('unified scan supports box and pallet codes and preserves the no-count empty result', async () => {
|
||||
session.apiBase = '/api'; session.token = 'counting-test'
|
||||
const urls = []
|
||||
globalThis.uni = { request(options) { urls.push(options.url); options.success({ statusCode: 200, data: { code: '200', data: [] }, header: {} }) } }
|
||||
assert.deepEqual(await scanReturnCountTasks(' RC#2-1 '), [])
|
||||
assert.deepEqual(await scanReturnCountTasks('PLT/1'), [])
|
||||
assert.deepEqual(urls, ['/api/pda/v1/last-leg/return-inventory/counting/scan/RC%232-1', '/api/pda/v1/last-leg/return-inventory/counting/scan/PLT%2F1'])
|
||||
})
|
||||
|
||||
test('current task preserves null and begin uses the operator-scoped claim route', async () => {
|
||||
session.apiBase = '/api'; session.token = 'counting-test'
|
||||
const calls = []
|
||||
globalThis.uni = { request(options) { calls.push(options); options.success({ statusCode: 200, data: { code: '200', data: options.method === 'GET' ? null : { receiptNo: 'RR/1', status: 'COUNTING' } }, header: {} }) } }
|
||||
assert.equal(await getCurrentReturnCountTask(), null)
|
||||
assert.equal((await beginReturnCountTask('RR/1')).status, 'COUNTING')
|
||||
assert.deepEqual(calls.map(row => [row.method, row.url]), [
|
||||
['GET', '/api/pda/v1/last-leg/return-inventory/counting/current'],
|
||||
['POST', '/api/pda/v1/last-leg/return-inventory/counting/receipts/RR%2F1/begin']
|
||||
])
|
||||
assert.equal(calls[1].data, undefined)
|
||||
})
|
||||
@@ -0,0 +1,157 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { runInNewContext } from 'node:vm'
|
||||
|
||||
const source = readFileSync(new URL('../src/pages/return-counting/return-counting.vue', import.meta.url), 'utf8')
|
||||
const script = source.match(/<script>([\s\S]*?)<\/script>/)[1].replace(/^import .+$/gm, '').replace('export default', 'page =')
|
||||
const scope = { PortalHeader: {}, ScanTargetInput: {} }
|
||||
runInNewContext(script, scope)
|
||||
|
||||
function state(a = '2', b = '2', c = '2') {
|
||||
const value = {
|
||||
scanCode: '', selectedMark: '',
|
||||
task: { status: 'COUNTING', expectedItems: [{ skuCode: 'A', quantity: '4' }, { skuCode: 'B', quantity: '2' }], boxes: [
|
||||
{ cartonMark: 'RC#2-1', items: [{ skuCode: 'A', quantity: a }] },
|
||||
{ cartonMark: 'RC#2-2', items: [{ skuCode: 'A', quantity: b }] },
|
||||
{ cartonMark: 'RC#1-1', items: [{ skuCode: 'B', quantity: c }] }
|
||||
] }
|
||||
}
|
||||
value.skuCodes = scope.page.computed.skuCodes.call(value)
|
||||
value.totals = scope.page.computed.totals.call(value)
|
||||
return value
|
||||
}
|
||||
|
||||
test('list entry can confirm without scanning when string wire quantities reconcile', () => {
|
||||
const value = state()
|
||||
assert.equal(value.totals[0].quantity, 4)
|
||||
assert.equal(value.totals[1].quantity, 2)
|
||||
assert.equal(value.totals.every(row => row.actual === row.quantity), true)
|
||||
assert.equal(scope.page.computed.ready.call(value), true)
|
||||
})
|
||||
|
||||
test('counting still rejects mismatched totals and invalid box quantities', () => {
|
||||
for (const value of [state('1', '2', '2'), state('0', '4', '2'), state('1.5', '2.5', '2')]) {
|
||||
assert.equal(scope.page.computed.ready.call(value), false)
|
||||
}
|
||||
const duplicate = state()
|
||||
duplicate.task.boxes[0].items = [{ skuCode: 'A', quantity: 1 }, { skuCode: 'A', quantity: 1 }]
|
||||
assert.equal(scope.page.computed.ready.call(duplicate), false)
|
||||
})
|
||||
|
||||
function scanState() {
|
||||
const value = { task: state().task, tasks: [], error: null, scanCode: '', scanResult: '', selectedMark: '', busy: false,
|
||||
open(row) { this.task = row }, $nextTick(callback) { callback() } }
|
||||
value.run = scope.page.methods.run.bind(value)
|
||||
value.enterTask = scope.page.methods.enterTask.bind(value)
|
||||
return value
|
||||
}
|
||||
|
||||
test('box and single-batch pallet scans claim and open counting directly without start dialog', async () => {
|
||||
for (const mark of ['RC#2-1', 'PLT-1']) {
|
||||
const value = scanState()
|
||||
scope.scanReturnCountTasks = async () => [{ receiptNo: 'RR-SCAN', returnNo: 'RT-SCAN' }]
|
||||
scope.getCurrentReturnCountTask = async () => null
|
||||
let claimed
|
||||
scope.beginReturnCountTask = async receiptNo => { claimed = receiptNo; assert.equal(value.busy, true); return { receiptNo, status: 'COUNTING' } }
|
||||
value.ask = async () => { assert.fail('matching scan should enter directly') }
|
||||
await scope.page.methods.scan.call(value, mark)
|
||||
assert.equal(claimed, 'RR-SCAN')
|
||||
assert.equal(value.task.status, 'COUNTING')
|
||||
assert.equal(value.selectedMark, mark)
|
||||
assert.equal(value.activeTask, value.task)
|
||||
}
|
||||
})
|
||||
|
||||
test('direct scan cannot claim a different task while the operator has unfinished work', async () => {
|
||||
const value = scanState()
|
||||
scope.scanReturnCountTasks = async () => [{ receiptNo: 'RR-NEW', returnNo: 'RT-NEW' }]
|
||||
scope.getCurrentReturnCountTask = async () => ({ receiptNo: 'RR-OLD', returnNo: 'RT-OLD' })
|
||||
scope.beginReturnCountTask = async () => { assert.fail('must not claim a second task') }
|
||||
let dialog
|
||||
value.ask = async options => { dialog = options; return { confirm: false } }
|
||||
await scope.page.methods.scan.call(value, 'PLT-NEW')
|
||||
assert.equal(dialog.title, '有未完成的清点任务')
|
||||
})
|
||||
|
||||
test('a pallet scan shows every pending batch and no pending results clear stale details', async () => {
|
||||
const value = scanState()
|
||||
const rows = [{ receiptNo: 'RR-1', boxes: [] }, { receiptNo: 'RR-2', boxes: [] }]
|
||||
scope.scanReturnCountTasks = async () => rows
|
||||
await scope.page.methods.scan.call(value, 'PLT-1')
|
||||
assert.equal(value.task, null)
|
||||
assert.equal(value.tasks.length, 2)
|
||||
assert.equal(value.scanResult, 'PLT-1')
|
||||
scope.scanReturnCountTasks = async () => []
|
||||
await scope.page.methods.scan.call(value, 'RC-DONE')
|
||||
assert.equal(value.task, null)
|
||||
assert.equal(value.tasks.length, 0)
|
||||
assert.equal(value.scanResult, 'RC-DONE')
|
||||
assert.equal(value.error, null)
|
||||
})
|
||||
|
||||
test('scanner failures remain visible rather than claiming no count is required', async () => {
|
||||
const value = scanState()
|
||||
scope.scanReturnCountTasks = async () => { throw Object.assign(new Error('服务暂不可用'), { code: 'B0500' }) }
|
||||
await scope.page.methods.scan.call(value, 'PLT-1')
|
||||
assert.equal(value.error.code, 'B0500')
|
||||
assert.equal(value.scanResult, '')
|
||||
})
|
||||
|
||||
test('declining start never claims a task and another active task blocks a new claim', async () => {
|
||||
const value = scanState(); value.activeTask = null
|
||||
let begins = 0
|
||||
scope.getCurrentReturnCountTask = async () => null
|
||||
scope.beginReturnCountTask = async () => { begins++; return { status: 'COUNTING' } }
|
||||
value.ask = async () => ({ confirm: false })
|
||||
await scope.page.methods.choose.call(value, { receiptNo: 'RR-NEW', returnNo: 'RT-NEW' })
|
||||
assert.equal(begins, 0)
|
||||
scope.getCurrentReturnCountTask = async () => ({ receiptNo: 'RR-OLD', returnNo: 'RT-OLD' })
|
||||
let dialog
|
||||
value.ask = async options => { dialog = options; return { confirm: false } }
|
||||
await scope.page.methods.choose.call(value, { receiptNo: 'RR-NEW', returnNo: 'RT-NEW' })
|
||||
assert.equal(dialog.title, '有未完成的清点任务')
|
||||
assert.equal(begins, 0)
|
||||
})
|
||||
|
||||
test('affirming start claims the selected task and switches it to working state', async () => {
|
||||
const value = scanState(); value.scanResult = 'RC-1'
|
||||
scope.getCurrentReturnCountTask = async () => null
|
||||
let claimed
|
||||
scope.beginReturnCountTask = async receiptNo => { claimed = receiptNo; return { receiptNo, status: 'COUNTING' } }
|
||||
value.ask = async () => ({ confirm: true })
|
||||
await scope.page.methods.choose.call(value, { receiptNo: 'RR-NEW', returnNo: 'RT-NEW' })
|
||||
assert.equal(claimed, 'RR-NEW')
|
||||
assert.equal(value.task.status, 'COUNTING')
|
||||
const unclaimed = state(); unclaimed.task.status = 'PENDING'
|
||||
assert.equal(scope.page.computed.ready.call(unclaimed), false)
|
||||
})
|
||||
|
||||
test('completed counting returns to page one and refreshes remaining tasks while busy', async () => {
|
||||
const value = scanState(); value.ready = true; value.draftKey = 'count-draft'; value.page = 3; value.scanResult = 'PLT-1'
|
||||
value.resetList = scope.page.methods.resetList.bind(value)
|
||||
value.fetchTasks = scope.page.methods.fetchTasks.bind(value)
|
||||
let removed = false, refreshed = false
|
||||
scope.uni = { setStorageSync() {}, removeStorageSync() { removed = true }, showToast() {} }
|
||||
scope.confirmReturnCount = async () => ({ status: 'CONFIRMED' })
|
||||
scope.getCurrentReturnCountTask = async () => null
|
||||
scope.listReturnCountTasks = async ({ page }) => { assert.equal(page, 1); assert.equal(value.busy, true); refreshed = true; return { records: [{ receiptNo: 'RR-NEXT' }], total: '1' } }
|
||||
await scope.page.methods.confirm.call(value)
|
||||
assert.equal(value.task, null)
|
||||
assert.equal(value.scanResult, '')
|
||||
assert.equal(value.activeTask, null)
|
||||
assert.equal(value.tasks[0].receiptNo, 'RR-NEXT')
|
||||
assert.equal(removed && refreshed, true)
|
||||
})
|
||||
|
||||
test('failed confirmation retains the current task and draft for retry', async () => {
|
||||
const value = scanState(); value.ready = true; value.draftKey = 'count-draft'
|
||||
const original = value.task
|
||||
let removed = false
|
||||
scope.uni = { setStorageSync() {}, removeStorageSync() { removed = true } }
|
||||
scope.confirmReturnCount = async () => { throw new Error('数量汇总不一致') }
|
||||
await scope.page.methods.confirm.call(value)
|
||||
assert.equal(value.task, original)
|
||||
assert.equal(removed, false)
|
||||
assert.equal(value.error.message, '数量汇总不一致')
|
||||
})
|
||||
@@ -10,6 +10,7 @@ test('only operations backed by a dedicated overseas PDA contract are enabled',
|
||||
{ module: 'inbound', name: '仓储转运测量列表' }, { module: 'inbound', name: '转运测量作业' },
|
||||
{ module: 'inbound', name: '退货入库' },
|
||||
{ module: 'inbound', name: '退货合箱' },
|
||||
{ module: 'inbound', name: '退货箱内清点' },
|
||||
{ module: 'outbound', name: '排车拣货' },
|
||||
{ module: 'outbound', name: '出库作业' },
|
||||
{ module: 'inventory', name: '商品移库' },
|
||||
@@ -26,6 +27,7 @@ test('only operations backed by a dedicated overseas PDA contract are enabled',
|
||||
assert.equal(operations.find((item) => item.name === '出库作业')?.route, '/pages/outbound/outbound')
|
||||
assert.equal(operations.find((item) => item.name === '退货入库')?.route, '/pages/return-receipt/return-receipt')
|
||||
assert.equal(operations.find((item) => item.name === '退货合箱')?.route, '/pages/return-packing/return-packing')
|
||||
assert.equal(operations.find((item) => item.name === '退货箱内清点')?.route, '/pages/return-counting/return-counting')
|
||||
assert.equal(operations.find((item) => item.name === '仓储转运测量列表')?.route, '/pages/warehouse-transfer/warehouse-transfer?mode=list')
|
||||
assert.equal(operations.find((item) => item.name === '转运测量作业')?.route, '/pages/warehouse-transfer/warehouse-transfer?mode=work')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user