fix(pda): 收货提交与验托绑定当前箱码输入
This commit was merged in pull request #15.
This commit is contained in:
@@ -178,7 +178,15 @@
|
||||
activeTarget: { get() { return receiving.activeScanTarget }, set(value) { receiving.activeScanTarget = value } },
|
||||
phase() { return this.detailOpen && !this.current ? 'detail' : receivingPhase(this.current) }, progress() { return receivingProgress(this.current) }, activeInbound() { return this.resolvedCarton },
|
||||
uploadedPhotoCount() { return this.photoSlots.filter((item) => item?.fileId).length }, photosReady() { return this.uploadedPhotoCount === 3 && !this.photoSlots.some((item) => item?.status === 'uploading') },
|
||||
canSubmitReceipt() { if (!this.activeInbound || !this.resolvedPallet?.compatible || this.palletInput.trim() !== this.resolvedPallet.palletCode) return false; if (this.activeInbound.destinationType === 'FBA') { const count = Number(this.packageCount); return Number.isInteger(count) && count > 0 } return Boolean(this.activeInbound.cartonMark) },
|
||||
canSubmitReceipt() {
|
||||
if (!this.activeInbound || this.cartonInput.trim() !== this.activeInbound.cartonMark
|
||||
|| !this.resolvedPallet?.compatible || this.palletInput.trim() !== this.resolvedPallet.palletCode) return false
|
||||
if (this.activeInbound.destinationType === 'FBA') {
|
||||
const count = Number(this.packageCount)
|
||||
return Number.isInteger(count) && count > 0
|
||||
}
|
||||
return Boolean(this.activeInbound.cartonMark)
|
||||
},
|
||||
allInboundsReconciled() { const values = this.current?.inbounds || []; return values.length > 0 && !this.current?.hasDifference && values.every((item) => Number(item.pendingPackages) === 0) },
|
||||
canFinishOnsite() { return Boolean(this.current && ['SIGNED', 'RECEIVING'].includes(this.current.status)) },
|
||||
pageTitle() { return { candidates: '待卸货集装箱', detail: '柜号详情', sign: '拆柜拍照签收', receive: '当前柜收货', completed: '入库完成' }[this.phase] }
|
||||
@@ -307,12 +315,14 @@
|
||||
},
|
||||
async resolvePallet(raw) {
|
||||
const palletCode = String(raw?.detail?.value ?? raw ?? this.palletInput).trim()
|
||||
if (!palletCode || !this.activeInbound || this.busy) return
|
||||
const inbound = this.activeInbound, current = this.current, inboundCode = inbound.inboundCode
|
||||
const inbound = this.activeInbound
|
||||
if (!palletCode || !inbound || this.busy || this.cartonInput.trim() !== inbound.cartonMark) return
|
||||
const current = this.current, inboundCode = inbound.inboundCode
|
||||
const scanEpoch = this._scanEpoch || 0, operatorId = session.currentUser?.id, warehouseId = session.currentUser?.warehouseId, tenantId = session.tenantId
|
||||
// 响应只属于发起时的输入、入仓单、柜会话和作业身份;离开再返回也必须重验。
|
||||
const isCurrent = () => this._pageActive && scanEpoch === (this._scanEpoch || 0)
|
||||
&& this.current === current && this.activeInbound === inbound && this.activeInbound.inboundCode === inboundCode
|
||||
&& this.cartonInput.trim() === inbound.cartonMark
|
||||
&& session.currentUser?.id === operatorId && session.currentUser?.warehouseId === warehouseId && session.tenantId === tenantId
|
||||
&& this.palletInput.trim() === palletCode
|
||||
receiving.resolvedPallet = null; this.palletInput = palletCode; this.packageCount = ''; receiving.activeScanTarget = 'pallet'
|
||||
|
||||
@@ -60,10 +60,11 @@ test('first scan and receipt requests confirm server reservations without resour
|
||||
assert.match(page, /activeInbound\.assigned/)
|
||||
assert.match(page, /activeInbound\.urgent/)
|
||||
assert.match(page, /activeInbound\.mergeAllowed && activeInbound\.mergeHint/)
|
||||
const gate = page.match(/canSubmitReceipt\(\) \{.*?\},/)?.[0]
|
||||
const gate = page.match(/canSubmitReceipt\(\) \{[\s\S]*?\},/)?.[0]
|
||||
assert.ok(gate)
|
||||
assert.doesNotMatch(gate, /mergeAllowed|assigned|urgent/)
|
||||
assert.match(gate, /resolvedPallet\?\.compatible/)
|
||||
assert.match(gate, /cartonInput\.trim\(\) !== this\.activeInbound\.cartonMark/)
|
||||
assert.match(page, /const MAX_PACKAGES_PER_PALLET = 66/)
|
||||
assert.match(page, /Number\(this\.packageCount\) > MAX_PACKAGES_PER_PALLET/)
|
||||
assert.match(page, /一个托盘最多只能收66箱,请拆分到其他托盘/)
|
||||
|
||||
@@ -18,6 +18,8 @@ function page(overrides = {}) {
|
||||
receivingPhase: store.receivingPhase, toReceivingError: error => error, ...overrides }
|
||||
const definition = new Function(...Object.keys(deps), source.replace(/^\s*import .*$/gm, '').replace('export default', 'return'))(...Object.values(deps))
|
||||
const vm = { ...definition.data(), _pageActive: true, _scanEpoch: 0 }
|
||||
// 预置解析结果的用例同时还原解析成功后页面显示的箱码。
|
||||
vm.cartonInput = store.receiving.resolvedCarton?.cartonMark || ''
|
||||
for (const [name, method] of Object.entries(definition.methods)) vm[name] = method.bind(vm)
|
||||
for (const [name, descriptor] of Object.entries(definition.computed)) Object.defineProperty(vm, name,
|
||||
typeof descriptor === 'function' ? { get: () => descriptor.call(vm) } : { get: () => descriptor.get.call(vm), set: value => descriptor.set.call(vm, value) })
|
||||
@@ -67,6 +69,78 @@ test('resolving a new carton invalidates the previous receipt even when the new
|
||||
assert.equal(writes, 0)
|
||||
})
|
||||
|
||||
test('editing the carton input cannot submit the previously resolved carton or FBA group', async t => {
|
||||
for (const destinationType of ['OVERSEAS', 'FBA']) await t.test(destinationType, async t => {
|
||||
store.resetReceivingSession(); store.setCurrentReceiving(sessionValue())
|
||||
store.receiving.resolvedCarton = { inboundCode: 'IN-A', cartonMark: 'IN-A#1', destinationType }
|
||||
store.receiving.resolvedPallet = { palletCode: 'PAL-A', compatible: true, version: 1 }
|
||||
let writes = 0
|
||||
const { vm } = page({ receiveCurrentCarton: async () => { writes++; return {} },
|
||||
receiveCurrentBulk: async () => { writes++; return {} }, getCurrentReceiving: async () => sessionValue() })
|
||||
t.after(() => vm.suspendReceiving())
|
||||
vm.cartonInput = 'IN-A#1'; vm.palletInput = 'PAL-A'; vm.packageCount = 2
|
||||
assert.equal(vm.canSubmitReceipt, true)
|
||||
vm.cartonInput = 'IN-B#2'
|
||||
assert.equal(vm.canSubmitReceipt, false)
|
||||
await vm.submitReceipt()
|
||||
assert.equal(writes, 0)
|
||||
})
|
||||
})
|
||||
|
||||
test('pallet validation requires the current carton input to match the resolved carton', async t => {
|
||||
store.resetReceivingSession(); store.setCurrentReceiving(sessionValue())
|
||||
store.receiving.resolvedCarton = { inboundCode: 'IN-A', cartonMark: 'IN-A#1', destinationType: 'FBA' }
|
||||
let reads = 0
|
||||
const { vm } = page({ resolveCurrentPallet: async palletCode => {
|
||||
reads++; return { palletCode, compatible: true, version: 1 }
|
||||
} })
|
||||
t.after(() => vm.suspendReceiving())
|
||||
vm.cartonInput = 'IN-B#2'
|
||||
await vm.resolvePallet('PAL-A')
|
||||
assert.equal(reads, 0); assert.equal(vm.resolvedPallet, null)
|
||||
})
|
||||
|
||||
test('editing the carton input while validating a pallet discards the previous carton result', async t => {
|
||||
store.resetReceivingSession(); store.setCurrentReceiving(sessionValue())
|
||||
store.receiving.resolvedCarton = { inboundCode: 'IN-A', cartonMark: 'IN-A#1', destinationType: 'FBA' }
|
||||
const pending = defer(), toasts = []; let successes = 0
|
||||
const { vm } = page({ resolveCurrentPallet: () => pending.promise, playSuccessSound: () => successes++ })
|
||||
t.after(() => vm.suspendReceiving())
|
||||
vm.cartonInput = 'IN-A#1'; vm.showToast = (message, type) => toasts.push({ message, type })
|
||||
const validating = vm.resolvePallet('PAL-A')
|
||||
vm.cartonInput = 'IN-B#2'
|
||||
pending.resolve({ palletCode: 'PAL-A', compatible: true, version: 1, currentReceivedPackages: 2 })
|
||||
await validating
|
||||
assert.equal(vm.resolvedPallet, null); assert.equal(vm.packageCount, '')
|
||||
assert.equal(vm.canSubmitReceipt, false); assert.equal(successes, 0); assert.deepEqual(toasts, [])
|
||||
})
|
||||
|
||||
test('matching parsed carton and validated pallet still submit the exact receipt through normal entry points', async t => {
|
||||
for (const destinationType of ['OVERSEAS', 'FBA']) await t.test(destinationType, async t => {
|
||||
store.resetReceivingSession(); store.setCurrentReceiving(sessionValue())
|
||||
const receipts = []
|
||||
const { vm } = page({ resolveCurrentCarton: async cartonMark => ({ cartonMark, inboundCode: 'IN-A', destinationType }),
|
||||
resolveCurrentPallet: async palletCode => ({ palletCode, compatible: true, version: 7 }),
|
||||
receiveCurrentCarton: async payload => { receipts.push(payload); return {} },
|
||||
receiveCurrentBulk: async payload => { receipts.push(payload); return {} },
|
||||
getCurrentReceiving: async () => sessionValue() })
|
||||
t.after(() => vm.suspendReceiving())
|
||||
vm.applyCurrentReceiving = async value => store.setCurrentReceiving(value)
|
||||
await vm.resolveCarton('IN-A#1')
|
||||
await vm.resolvePallet('PAL-A')
|
||||
vm.packageCount = 2; vm.cartonInput = ' IN-A#1 '
|
||||
assert.equal(vm.canSubmitReceipt, true)
|
||||
await vm.submitReceipt()
|
||||
assert.equal(receipts.length, 1)
|
||||
const { idempotencyKey, ...payload } = receipts[0]
|
||||
assert.ok(idempotencyKey)
|
||||
assert.deepEqual(payload, destinationType === 'FBA'
|
||||
? { inboundCode: 'IN-A', palletCode: 'PAL-A', packageCount: 2, expectedPalletVersion: 7 }
|
||||
: { cartonMark: 'IN-A#1', palletCode: 'PAL-A' })
|
||||
assert.equal(vm.resolvedCarton, null); assert.equal(vm.resolvedPallet, null)
|
||||
})
|
||||
})
|
||||
|
||||
test('late carton resolution cannot overwrite edited input or another receiving context', async t => {
|
||||
for (const [name, change] of [
|
||||
['input', ({ vm }) => { vm.cartonInput = 'IN-B#1' }],
|
||||
|
||||
Reference in New Issue
Block a user