From a5c1170da6b3c12c60285a70ca0662eb67a9e439 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 7 Sep 2026 20:47:55 +0800 Subject: [PATCH] fix(pda): resolve dispatch scans from outbound --- src/api/fulfillment.js | 4 ++++ src/pages/operation/operation.vue | 3 ++- src/pages/outbound/outbound.vue | 28 ++++++++++++++++++++++------ tests/fulfillment-workflows.test.mjs | 17 +++++++++++++++-- 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/src/api/fulfillment.js b/src/api/fulfillment.js index 3fdaef1..85c1f89 100644 --- a/src/api/fulfillment.js +++ b/src/api/fulfillment.js @@ -104,6 +104,10 @@ export async function getOutboundTask(taskNo) { return normalizeOutboundTask(await overseasRequest('GET', `${OUTBOUND_BASE}/${encoded(taskNo)}`)) } +export function resolveOutboundTask(businessNo) { + return overseasRequest('GET', `${OUTBOUND_BASE}/resolve/${encoded(businessNo)}`) +} + export function pickOutboundTask(taskNo, request) { return overseasRequest('POST', `${OUTBOUND_BASE}/${encoded(taskNo)}/pick`, request) } diff --git a/src/pages/operation/operation.vue b/src/pages/operation/operation.vue index cb0973e..ccd223c 100644 --- a/src/pages/operation/operation.vue +++ b/src/pages/operation/operation.vue @@ -160,7 +160,7 @@ async bootstrap() { if (this.loading) return; this.loading = true; this.error = null; this.detailOpen = false; try { await this.refreshBoundWarehouse(); try { const current = await getCurrentReceiving(); await this.applyCurrentReceiving(current); this.candidateError = null } catch (error) { if (!isNoCurrentReceiving(error)) throw error; resetReceivingDraft(); await this.loadCandidates(false, false) } this.bootstrapped = true } catch (error) { this.candidateError = error } finally { this.loading = false } }, async recoverCurrentAfterConflict(error, preserveEvidence = false) { if (!isReceivingConflict(error)) return false; try { const current = await getCurrentReceiving(); await this.applyCurrentReceiving(current); this.detailOpen = false; this.error = preserveEvidence ? error : null; this.showToast(`账号已有未完成柜 ${current.containerNo},已返回当前柜`, 'warning'); return true } catch (recoveryError) { if (isNoCurrentReceiving(recoveryError)) { this.detailOpen = false; receiving.selected = null; await this.loadCandidates(false); this.candidateError = preserveEvidence ? error : null; this.error = preserveEvidence ? error : null; this.showToast('柜状态已变化,列表已刷新', 'warning'); return true } this.error = error; return false } }, async loadCandidates(feedback = true, refreshWarehouse = true) { if (this.candidateLoading) return; this.candidateLoading = true; this.candidateError = null; this.error = null; try { if (refreshWarehouse) await this.refreshBoundWarehouse(); const values = await listReceivingCandidates(); receiving.candidates = values.map((item) => this.validateReceivingWarehouse(item, `柜号 ${item.containerNo || '—'}`)); if (feedback) this.showToast('刷新待卸柜成功', 'success') } catch (error) { receiving.candidates = []; this.candidateError = error; if (feedback) { playErrorSound(); this.showToast(error.message || '刷新待卸柜失败', 'error') } } finally { this.candidateLoading = false } }, - async openContainer(candidate) { const containerNo = String(candidate?.containerNo || '').trim(); if (!containerNo || this.busy) return; this.error = null; try { this.validateReceivingWarehouse(candidate, `柜号 ${containerNo}`); if (!candidate.operable) { receiving.selected = candidate; this.detailOpen = true; return } const detail = await this.run('查询柜号', async () => this.validateReceivingWarehouse(await getReceivingContainer(containerNo), `柜号 ${containerNo}`)); receiving.selected = { ...candidate, ...detail, allPlansReady: candidate.allPlansReady, operable: candidate.operable, blockReason: candidate.blockReason }; this.detailOpen = true; playSuccessSound() } catch (error) { if (!await this.recoverCurrentAfterConflict(error)) { this.error = error; this.showToast(error.message || '查询柜号失败', 'error') } } }, + async openContainer(candidate) { const containerNo = String(candidate?.containerNo || '').trim(); if (!containerNo || this.busy) return; this.error = null; try { this.validateReceivingWarehouse(candidate, `柜号 ${containerNo}`); if (!candidate.operable) { receiving.selected = candidate; this.detailOpen = true; return } const detail = await this.run('查询柜号', async () => this.validateReceivingWarehouse(await getReceivingContainer(containerNo), `柜号 ${containerNo}`)); receiving.selected = { ...candidate, ...detail, allPlansReady: candidate.allPlansReady, operable: candidate.operable, blockReason: candidate.blockReason }; this.detailOpen = true; playSuccessSound() } catch (error) { if (!await this.recoverCurrentAfterConflict(error, true)) { this.error = error; this.showToast(error.message || '查询柜号失败', 'error') } } }, cancelDetail() { this.detailOpen = false; receiving.selected = null }, async prepareContainer() { if (!this.selected?.containerNo || !this.selected?.allPlansReady || !this.selected?.operable || this.busy) return; const key = idempotencyKey('prepare', { containerNo: this.selected.containerNo }); try { this.validateReceivingWarehouse(this.selected, `柜号 ${this.selected.containerNo}`); const value = await this.run('选择当前柜', async () => this.validateReceivingWarehouse(await prepareReceivingContainer(this.selected.containerNo, { idempotencyKey: key.value }), '新建接柜会话')); releaseIdempotencyKey(key.signature); await this.applyCurrentReceiving(value); this.detailOpen = false } catch (error) { if (!await this.recoverCurrentAfterConflict(error, true)) this.error = error } }, choosePhoto(index) { if (this.busy) return; uni.chooseImage({ count: 1, sourceType: ['camera'], success: ({ tempFilePaths, tempFiles }) => { const path = tempFilePaths?.[0]; const size = Number(tempFiles?.[0]?.size || 0); if (size > MAX_UNLOAD_PHOTO_BYTES) { const message = '照片不能超过10MB'; this.error = Object.assign(new Error(message), { code: 'A0400' }); this.showToast(message, 'error'); playErrorSound(); return } if (path) this.replacePhoto(index, path) } }) }, @@ -201,6 +201,7 @@ .matched-inbound-code{display:block;color:#17324d;font-size:15px;font-weight:800} .matched-inbound-progress{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:6px;margin-top:8px;color:#5f7185;font-size:10px} .matched-inbound-progress text{padding:6px 4px;border-radius:6px;text-align:center;background:#edf3f9} + .error-card text{overflow-wrap:anywhere;word-break:break-word} @media(max-width:360px){.photo-role{grid-template-columns:68px minmax(0,1fr) 64px;gap:7px;padding:8px}.photo-preview{width:68px;height:64px}.photo-actions button{min-height:34px;padding:0 2px;line-height:34px}.photo-state{font-size:9px;line-height:13px}} .receiving-page{padding-bottom:env(safe-area-inset-bottom,0px)} .receiving-content{box-sizing:border-box;padding-bottom:calc(24px + env(safe-area-inset-bottom,0px))} diff --git a/src/pages/outbound/outbound.vue b/src/pages/outbound/outbound.vue index 3c8cfd8..03b0b3c 100644 --- a/src/pages/outbound/outbound.vue +++ b/src/pages/outbound/outbound.vue @@ -3,11 +3,11 @@ - 出库任务包含排车转出的出库任务和一件代发出库任务;派送单与派送状态不进入 PDA + 出库任务列表仅显示普通/一件代发出库任务;扫描排车业务号会转入“排车拣货” - + - 扫描只接受服务端任务内的稳定业务码;网络失败可用原幂等键安全重试。 + 任务号直接恢复;出库单号或排车单号先由服务端解析流程,不会作为出库任务号误查。 正在加载出库任务… @@ -49,7 +49,7 @@