优化
This commit is contained in:
@@ -1,49 +1,75 @@
|
||||
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readFile } from 'node:fs/promises'
|
||||
|
||||
const api = await readFile(new URL('../src/api/relabel.js', import.meta.url), 'utf8')
|
||||
const page = await readFile(new URL('../src/pages/relabel/relabel.vue', import.meta.url), 'utf8')
|
||||
const palletWork = await readFile(new URL('../src/components/RelabelPalletWork.vue', import.meta.url), 'utf8')
|
||||
|
||||
test('PDA exposes the approved return bulk SKU completion endpoint', () => {
|
||||
assert.match(api, /completeReturnRelabelSkuTotal/)
|
||||
assert.match(api, /\/return-skus\/complete-total/)
|
||||
const source = await readFile(new URL('../src/pages/relabel/relabel.vue', import.meta.url), 'utf8')
|
||||
const helperSource = await readFile(new URL('../src/utils/return-relabel.js', import.meta.url), 'utf8')
|
||||
const helpers = await import('data:text/javascript;base64,' + Buffer.from(helperSource).toString('base64'))
|
||||
const body = source.split('<script>')[1].split('</script>')[0].replace(/^\s*import .*$/gm, '').replace('export default', 'return')
|
||||
function setup(confirm = false) {
|
||||
const calls = [], errors = []
|
||||
const uni = { showModal(options) { options.success({ confirm }) } }
|
||||
const component = new Function('PortalHeader', 'RelabelPalletWork', 'ScanTargetInput', 'ReturnRelabelPackWork', 'requiresReturnPack', 'returnPackFacts', 'completeReturnRelabelSku', 'warehouseIdempotencyKey', 'releaseWarehouseKey', 'uni', body)({}, {}, {}, {}, helpers.requiresReturnPack, helpers.returnPackFacts, async (no, payload) => { calls.push(payload); return value.current }, () => ({ value: 'key', signature: 'signature' }), () => {}, uni)
|
||||
const item = (id, mark) => ({ itemId: id, returnCartonId: id, cartonMark: mark, returnQuantity: 10, operationType: 'RETURN_BULK_SKU_LABEL', skuCode: 'SKU', newSkuCode: 'TARGET', expectedPackages: 4, relabelledPackages: 0, skippedPackages: 0, exceptionPackages: 0 })
|
||||
const value = { current: { relabelNo: 'RL', version: 1, items: [item('1', 'BOX-A'), item('2', 'BOX-B')], history: [] }, busy: false, allProcessed: true, remark: '', photoFileIds: ['photo'], returnCartonCode: '', verifiedReturnCarton: '', returnSkuCode: '', bulkQuantities: {}, returnTargetLabels: {}, setError(error) { errors.push(error.message) }, applyCurrent(result) { this.current = result }, async run(action, work) { await work() } }
|
||||
for (const [name, fn] of Object.entries(component.methods)) if (!value[name]) value[name] = fn.bind(value)
|
||||
for (const name of ['isReturnTask', 'isReturnBulk', 'returnBulkSources', 'returnBulkSkuGroups', 'requiresPalletBinding', 'palletTask', 'completionBlockReason']) Object.defineProperty(value, name, { get: component.computed[name].bind(value) })
|
||||
return { value, calls, errors }
|
||||
}
|
||||
test('SKU alone cannot register or allocate unscanned sources', async () => {
|
||||
const { value, calls, errors } = setup()
|
||||
value.verifyReturnSku('SKU'); assert.match(errors.pop(), /先确认来源/)
|
||||
value.verifyReturnCarton('BOX-A'); value.verifyReturnSku('SKU')
|
||||
assert.deepEqual(value.returnBulkSources.map(item => item.cartonMark), ['BOX-A'])
|
||||
value.returnTargetLabels['2'] = 'TARGET'; value.bulkQuantities['2'] = '4'
|
||||
await value.completeBulkSku(value.current.items[1]); assert.equal(calls.length, 0)
|
||||
})
|
||||
test('quantity is explicit, integral and source bounded; wrong target cannot write', async () => {
|
||||
const { value, calls } = setup(); value.verifyReturnCarton('BOX-A'); value.verifyReturnSku('SKU')
|
||||
const item = value.current.items[0]; value.returnTargetLabels['1'] = 'TARGET'
|
||||
for (const quantity of ['', '1.5', '5', '0']) { value.bulkQuantities['1'] = quantity; await value.completeBulkSku(item) }
|
||||
assert.equal(calls.length, 0)
|
||||
value.bulkQuantities['1'] = '2'; value.returnTargetLabels['1'] = 'WRONG'; await value.completeBulkSku(item); assert.equal(calls.length, 0)
|
||||
value.returnTargetLabels['1'] = 'TARGET'; await value.completeBulkSku(item)
|
||||
assert.deepEqual(calls, [{ itemId: '1', cartonMark: 'BOX-A', skuCode: 'SKU', quantity: 2, expectedVersion: 1, idempotencyKey: 'key' }])
|
||||
})
|
||||
test('all action needs confirmation and rescanning clears quantity and target', async () => {
|
||||
for (const confirm of [false, true]) { const { value } = setup(confirm); value.verifyReturnCarton('BOX-A'); await value.fillAllReturn(value.current.items[0]); assert.equal(value.bulkQuantities['1'], confirm ? '4' : undefined); value.verifyReturnCarton('BOX-B'); assert.deepEqual(value.bulkQuantities, {}); assert.deepEqual(value.returnTargetLabels, {}) }
|
||||
})
|
||||
test('normal return SKU requires source and explicit SKU pieces before server scan', async () => {
|
||||
const { value, errors } = setup(); const item = { ...value.current.items[0], operationType: 'SKU_LABEL', expectedPackages: 1, skuItems: [{ skuCode: 'SKU', newSkuCode: 'TARGET', quantity: 3 }] }
|
||||
value.current.items = [item]; value.pendingSkuRows = [{ item, sku: item.skuItems[0], index: 0 }]
|
||||
await value.submitSku('SKU'); assert.match(errors.pop(), /先扫描/)
|
||||
value.verifyReturnCarton('BOX-A'); value.quickSku = 'SKU'; await value.commitSku(value.pendingSkuRows[0]); assert.match(errors.pop(), /实际件数/)
|
||||
})
|
||||
test('packing uses SKU pieces instead of box count and preserves both sources', () => {
|
||||
const task = { items: [{ returnCartonId: '1', cartonMark: 'A', operationType: 'SKU_LABEL', expectedPackages: 1, returnQuantity: 8, skuItems: [{ skuCode: 'SKU', quantity: 3 }] }, { returnCartonId: '2', cartonMark: 'B', operationType: 'SKU_LABEL', expectedPackages: 1, returnQuantity: 2, skuItems: [{ skuCode: 'SKU', quantity: 2 }] }] }
|
||||
assert.deepEqual(helpers.returnDeductions(task), [{ cartonMark: 'A', skuCode: 'SKU', quantity: 3 }, { cartonMark: 'B', skuCode: 'SKU', quantity: 2 }]); assert.equal(helpers.requiresReturnPack(task), true)
|
||||
task.items[0].returnQuantity = 3; assert.equal(helpers.requiresReturnPack(task), false)
|
||||
})
|
||||
test('only truck requires return pallet binding and counts physical new boxes', () => {
|
||||
const { value } = setup(); for (const method of [null, 'EXPRESS', 'EXPRESS_PICKUP', 'TRUCK']) { value.current.deliveryMethod = method; assert.equal(value.requiresPalletBinding, method === 'TRUCK') }
|
||||
value.current.items.forEach(item => { item.cartonMark = 'NEW'; item.relabelledPackages = item.expectedPackages })
|
||||
assert.equal(value.palletTask.items.length, 1); assert.equal(value.palletTask.items[0].expectedPackages, 1)
|
||||
})
|
||||
test('pack recovery ignores previous rework round', () => {
|
||||
const task = { reviewedAt: 'ROUND2', history: [{ action: 'RETURN_PACK', detail: JSON.stringify({ round: 'INITIAL', cartonMark: 'OLD' }) }, { action: 'RETURN_PACK', detail: JSON.stringify({ round: 'ROUND2', cartonMark: 'NEW' }) }] }
|
||||
assert.deepEqual(helpers.returnPackFacts(task).map(row => row.cartonMark), ['NEW'])
|
||||
})
|
||||
|
||||
test('return relabel pallet binding validates pieces while binding physical carton marks', () => {
|
||||
assert.match(palletWork, /isReturnBulk\(\).*RETURN_BULK_SKU_LABEL/)
|
||||
assert.match(palletWork, /count === packages && this\.completedItems\(\)\.length === this\.eligibleItems\.length && this\.marks\.length === this\.eligibleItems\.length/)
|
||||
assert.match(palletWork, /this\.isReturnBulk \? this\.eligibleItems : this\.eligibleItems\.slice\(0, count\)/)
|
||||
assert.match(page, /requiredMarks[\s\S]*some\(\(mark\) => !marks\.has\(mark\)\)/)
|
||||
})
|
||||
|
||||
test('return relabel mode works by SKU remaining quantity without carton-mark scanning', () => {
|
||||
assert.match(page, /current\?\.operationType === 'RETURN_BULK_SKU_LABEL'/)
|
||||
assert.match(page, /退货贴标 SKU 明细/)
|
||||
assert.match(page, /name="returnSku"[\s\S]*@submit="verifyReturnSku"/)
|
||||
assert.match(page, /activeTarget === 'return-sku'[\s\S]*this\.verifyReturnSku\(value\)/)
|
||||
assert.match(page, /verifiedReturnSkuKey !== group\.key/)
|
||||
assert.match(page, /Number\.isSafeInteger\(quantity\)[\s\S]*quantity > group\.remaining/)
|
||||
assert.match(page, /returnBulkSkuGroups/)
|
||||
assert.match(page, /来源托盘拆箱数量/)
|
||||
assert.match(page, /登记本次贴标.*group\.remaining/)
|
||||
assert.match(page, /completeReturnRelabelSkuTotal/)
|
||||
assert.match(page, /taskTab === 'carton' && !isReturnBulk/)
|
||||
})
|
||||
|
||||
test('new relabel pallet is immediately copied into the target scan field', () => {
|
||||
assert.match(palletWork, /this\.targetCode = this\.newPallet\.palletCode \|\| ''/)
|
||||
assert.match(palletWork, /退货换标无需扫描箱唛/)
|
||||
})
|
||||
|
||||
test('return relabel distinguishes a whole pallet from a partial unpack', () => {
|
||||
assert.match(palletWork, /wholeSource\(\).*plannedPackages === Number\(this\.selectedSource\.stockPackages/)
|
||||
assert.match(palletWork, /eligibleItems\(\).*this\.isReturnBulk \? this\.sourceItems\(\) : this\.completedItems\(\)/)
|
||||
assert.match(palletWork, /this\.selectedDestination = this\.sourceItems\(\)\[0\]/)
|
||||
assert.match(palletWork, /this\.completedItems\(\)\.length !== this\.eligibleItems\.length/)
|
||||
assert.match(palletWork, /贴标数量等于本托盘全部/)
|
||||
assert.match(palletWork, /贴标数量少于本托盘库存/)
|
||||
assert.match(palletWork, /v-if="!isReturnBulk"[\s\S]*name="relabel-source"/)
|
||||
assert.match(palletWork, /await this\.run\(async \(\) => \{ const value = await resolvePallet\(row\.palletCode\)/)
|
||||
test('partial return completion requires current-round independent box then truck binding and photo', () => {
|
||||
const { value } = setup()
|
||||
value.current.items.forEach(item => { item.relabelledPackages = item.expectedPackages })
|
||||
value.current.relabelledPackages = 8
|
||||
value.current.deliveryMethod = 'EXPRESS'
|
||||
assert.match(value.completionBlockReason, /独立新箱/)
|
||||
value.current.history.push({ action: 'RETURN_PACK', detail: JSON.stringify({ round: 'INITIAL', cartonMark: 'NEW' }) })
|
||||
value.current.items.forEach(item => { item.cartonMark = 'NEW'; item.returnQuantity = 8 })
|
||||
assert.equal(value.completionBlockReason, '')
|
||||
value.current.deliveryMethod = 'TRUCK'
|
||||
assert.match(value.completionBlockReason, /组托/)
|
||||
value.current.history.push({ action: 'PALLET_BIND', detail: JSON.stringify({ round: 'INITIAL', cartonMarks: ['NEW'] }) })
|
||||
assert.equal(value.completionBlockReason, '')
|
||||
value.photoFileIds = []
|
||||
assert.match(value.completionBlockReason, /照片/)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user