diff --git a/src/pages/dispatch/dispatch.vue b/src/pages/dispatch/dispatch.vue index 033ed8a..723da3c 100644 --- a/src/pages/dispatch/dispatch.vue +++ b/src/pages/dispatch/dispatch.vue @@ -5,14 +5,14 @@ 排车拣货与发车 - 客户与当前海外仓由登录令牌和服务端确定;扫描排车单号或任务中的托盘码开始作业 + 扫描排车单号打开任务,再扫描任务中的托盘码进行拣货 当前任务排车拣货详情 - 网络失败不会清空草稿,再次提交会沿用同一幂等键。 + 网络中断会保留当前作业,恢复后可重试。 正在加载排车任务… @@ -21,7 +21,7 @@ 错误码:{{ error.code }} requestId:{{ error.requestId }} traceId:{{ error.traceId }} - 网络连接失败,未完成作业与幂等键已保留。 + 网络连接失败,当前作业已保留,请恢复网络后重试。 @@ -154,17 +154,21 @@ const code = String(raw?.detail?.value ?? raw ?? this.scanCode).trim(); if (!code || this.busy || this.loading) return console.info('[Overseas PDA dispatch] scan', { code, task: this.current?.dispatchNo || '' }) this.scanCode = code + this.error = null; this.successMessage = '' + updateFulfillmentDraft('dispatch', { palletCode: '', dispatchItemId: '', cartonCode: '', packages: 1 }) if (!this.current || code === this.current.dispatchNo || code === this.current.taskNo) return this.openTask(code === this.current?.taskNo ? this.current.dispatchNo : code) const cartonItem = this.current.items.find((item) => item.returnCartons?.some((carton) => carton.cartonMark === code)) if (cartonItem) { if (cartonItem.returnCartons.find((carton) => carton.cartonMark === code)?.status !== 'OUTBOUND_PENDING') return this.setError(new Error('该箱已拣货或不可出库'), '扫码未匹配') this.selectPallet(cartonItem); updateFulfillmentDraft('dispatch', { cartonCode: code, packages: 1 }); this.scanCode = ''; playSuccessSound(); return } - const item = this.current.items.find((value) => [value.palletNo, value.fbaWarehouseCode].includes(code)) - if (!item) return this.setError(new Error('扫码未匹配当前排车任务的托盘或稳定业务码'), '扫码未匹配') + const matches = this.current.items.filter((value) => value.palletNo === code && value.scheduledPackages > value.pickedPackages) + if (matches.length > 1) return this.setError(new Error('此托盘包含多条待拣明细,请选择对应货物卡片'), '请选择拣货明细') + const item = matches[0] + if (!item) return this.setError(new Error(this.current.items.some((value) => value.palletNo === code) ? '该托盘已完成拣货,请勿重复操作' : '扫码未匹配当前任务的托盘码或来源箱唛'), '扫码未匹配') this.selectPallet(item); this.scanCode = ''; playSuccessSound() }, - selectPallet(item) { if (!item?.palletNo || item.scheduledPackages <= item.pickedPackages) return; updateFulfillmentDraft('dispatch', { palletCode: item.palletNo, dispatchItemId: item.dispatchItemId, cartonCode: '', packages: Math.max(1, Math.min(item.scheduledPackages - item.pickedPackages, item.positions?.[0]?.availablePackages || 1)) }) }, + selectPallet(item) { if (!item?.palletNo || item.scheduledPackages <= item.pickedPackages) return; this.error = null; this.successMessage = ''; updateFulfillmentDraft('dispatch', { palletCode: item.palletNo, dispatchItemId: item.dispatchItemId, cartonCode: '', packages: Math.max(1, Math.min(item.scheduledPackages - item.pickedPackages, item.positions?.[0]?.availablePackages || 1)) }) }, async submitPick() { if (!this.canPick) return const pick = this.selectedReturnCarton ? { positionId: null, cartonMark: this.selectedReturnCarton.cartonMark, packages: 1, productQuantity: this.selectedReturnCarton.productQuantity } : { positionId: this.selectedPosition.id, packages: Number(this.packages), productQuantity: 0 } diff --git a/src/pages/return-receipt/return-receipt.vue b/src/pages/return-receipt/return-receipt.vue index 2fd2f72..4470d52 100644 --- a/src/pages/return-receipt/return-receipt.vue +++ b/src/pages/return-receipt/return-receipt.vue @@ -24,7 +24,7 @@ 包裹码/快递单号 diff --git a/tests/fulfillment-workflows.test.mjs b/tests/fulfillment-workflows.test.mjs index 00d63c0..fef2092 100644 --- a/tests/fulfillment-workflows.test.mjs +++ b/tests/fulfillment-workflows.test.mjs @@ -219,7 +219,7 @@ test('pages expose loading empty error offline and permission diagnostics with g assert.match(page, /正在加载/) assert.match(page, /暂无/) assert.match(page, /requestId[\s\S]*traceId/) - assert.match(page, /error\.network[\s\S]*幂等键/) + assert.match(page, /error\.network[\s\S]*保留/) assert.match(page, /fulfillmentIdempotencyKey/) assert.match(page, /releaseFulfillmentKey/) assert.match(page, /if \(!this\.can/) @@ -297,3 +297,34 @@ test('dispatch review gate preserves the server decision without treating a stri assert.match(source, /canPick\(\) \{ return this.current\?\.canPick === true/) assert.match(source, /canComplete\(\) \{ return this.current\?\.canPick === true/) }) + +async function dispatchScanHarness(items) { + const source = (await read('../src/pages/dispatch/dispatch.vue')).match(/