diff --git a/src/api/fulfillment.js b/src/api/fulfillment.js
index 78eadf9..1e4150e 100644
--- a/src/api/fulfillment.js
+++ b/src/api/fulfillment.js
@@ -188,9 +188,36 @@ export async function resolveCargoDiscardPallet(palletNo, discardNo) {
export async function confirmCargoDiscard(discardNo, palletNo, lines, photoFileIds, scannedCartonMarks = [], confirmAllCartons = false, request = {}) {
const body = { lines, photoFileIds, scannedCartonMarks, confirmAllCartons, idempotencyKey: request.idempotencyKey }
requireStockCommandIdentity(request)
+ // 出库前按同一份货物、照片和幂等键校验仓租,资金就绪后才提交库存扣减。
+ await prepareDiscardRent({ mode: 'DISCARD', businessNo: discardNo, palletNo, ...body })
return normalizeCargoDiscardTask(await overseasRequest('POST', `${CARGO_DISCARD_BASE}/${encoded(discardNo)}/pallets/${encoded(palletNo)}/confirm`, body))
}
+/** 本次点击内只查询资金回执,不缓存或自动重放失败的出库命令。 */
+async function prepareDiscardRent(body) {
+ const base = '/pda/v1/last-leg/storage-rent/preparations'
+ let preparationId
+ try {
+ let preparation = await overseasRequest('POST', base, body)
+ preparationId = preparation?.preparationId
+ for (let attempt = 0; preparation?.ready !== true && preparation?.status === 'FREEZE_PENDING' && preparationId && attempt < 20; attempt++) {
+ await new Promise(resolve => setTimeout(resolve, 500))
+ preparation = await overseasRequest('GET', `${base}/${encoded(preparationId)}`)
+ }
+ if (preparation?.ready !== true) {
+ const message = preparation?.message ? `仓租校验未通过:${preparation.message}` : '仓租资金预校验尚未通过,请稍后重新提交'
+ throw Object.assign(new Error(message), { code: 'A0409' })
+ }
+ } catch (error) {
+ // 尚未发送出库命令时释放已知的未使用预占,释放失败保留原错误诊断。
+ if (preparationId) {
+ try { await overseasRequest('DELETE', `${base}/${encoded(preparationId)}`) }
+ catch (releaseError) { console.error('[Overseas PDA discard] rent preparation release failed', { code: releaseError?.code, requestId: releaseError?.requestId, traceId: releaseError?.traceId }) }
+ }
+ throw error
+ }
+}
+
const discardPhotoPath = (discardNo, palletNo) => `${CARGO_DISCARD_BASE}/${encoded(discardNo)}/pallets/${encoded(palletNo)}/photos/files`
export const listCargoDiscardPhotos = (discardNo, palletNo) => overseasRequest('GET', discardPhotoPath(discardNo, palletNo))
export const uploadCargoDiscardPhoto = (discardNo, palletNo, filePath) => overseasUpload(discardPhotoPath(discardNo, palletNo), filePath)
diff --git a/src/pages/outbound/outbound.vue b/src/pages/outbound/outbound.vue
index a138331..400cf78 100644
--- a/src/pages/outbound/outbound.vue
+++ b/src/pages/outbound/outbound.vue
@@ -11,7 +11,7 @@
正在加载出库任务…
- {{ error.message }}错误码:{{ error.code }}requestId:{{ error.requestId }}traceId:{{ error.traceId }}网络连接失败,当前任务、扫码草稿和幂等键均已保留。
+ {{ error.message }}错误码:{{ error.code }}requestId:{{ error.requestId }}traceId:{{ error.traceId }}网络连接失败,请刷新核对任务状态后重试。
待处理出库任务{{ tasks.length }} 个任务
@@ -44,12 +44,25 @@
废弃订单{{ discardCurrent.discardNo }}出库单 {{ discardCurrent.outboundNo }}
- 本单相关托盘及废弃范围{{ task.palletNo === '散箱' ? '散箱' : task.palletNo }}{{ task.status === 'COMPLETED' ? '已完成' : '待废弃' }}{{ mark }}缺少箱唛与托盘对应关系,请核对相关托盘,以仓库实际废弃箱数为准。
-
- {{ discardTask.scanMode === 'CARTON' ? (discardTask.palletNo === '散箱' ? '散箱废弃' : '按箱废弃 · ' + discardTask.palletNo) : '托盘 ' + discardTask.palletNo }}{{ discardTask.status === 'COMPLETED' ? '已完成' : '待废弃' }}本次废弃 {{ discardQuantitySummary }}{{ sku.skuCode || '—' }}{{ sku.productName || '—' }} · 申请 {{ sku.plannedPackages }} {{ sku.quantityUnit || '箱' }}实际废弃箱数箱(0–{{ sku.plannedPackages }})实际废弃 {{ sku.actualPackages }} {{ sku.quantityUnit || '箱' }}按现场实点填写,未废弃的预留库存将释放。已核对 {{ discardScannedMarks.length }}/{{ discardTask.cartonMarks.length }} 箱{{ mark }}{{ discardAllCartons ? '全部废弃' : discardScannedMarks.includes(mark) ? '已扫描' : '待扫描' }}已确认本单所选箱子全部废弃,拍照后点击废弃出库完成。废弃现场照片(至少 1 张)照片关联当前废弃作业,完成出库后可在废弃单详情查看。
+ {{ successMessage }}
+ 本单待废弃托盘{{ task.palletNo === '散箱' ? '散箱' : task.palletNo }}待废弃
+
+ {{ discardTask.palletNo }}{{ discardTask.status === 'COMPLETED' ? '已完成' : '待废弃' }}
+
+
+ {{ sku.skuCode || '—' }}
+ 实际废弃箱数箱(0–{{ sku.plannedPackages }})
+ 按现场实点填写,未废弃的预留库存将释放。
+
+ 箱号核对待扫描 {{ discardPendingMarks.length }} 箱
+ 已核对 {{ discardScannedMarks.length }}/{{ discardTask.cartonMarks.length }} 箱
+
+ {{ mark }}
+ 箱号已全部核对
+ 扫描箱号出库废弃现场照片(至少 1 张)
- {{ successMessage }}
+ {{ successMessage }}
@@ -66,8 +79,11 @@
export default {
components: { PortalHeader, ScanTargetInput },
- data() { return { user: session.currentUser, tasks: [], discardTasks: [], current: null, discardCurrent: null, discardTask: null, discardActuals: {}, discardScannedMarks: [], discardAllCartons: false, discardPhotos: [], discardPhotoUrls: {}, discardPhotoErrors: {}, discardPhotoBusy: false, discardPhotoReady: false, discardPalletCode: '', discardLoading: false, discardBusy: false, selectedItem: null, selectedPosition: null, selectedScanCode: '', scanCode: '', loading: false, busy: false, busyAction: '', error: null, successMessage: '', bootstrapped: false } },
+ data() { return { user: session.currentUser, tasks: [], discardTasks: [], current: null, discardCurrent: null, discardTask: null, discardActuals: {}, discardScannedMarks: [], discardCartonsExpanded: true, discardCartonCode: '', discardPhotos: [], discardPhotoUrls: {}, discardPhotoErrors: {}, discardPhotoBusy: false, discardPhotoReady: false, discardPalletCode: '', discardLoading: false, discardBusy: false, discardNeedsRefresh: false, selectedItem: null, selectedPosition: null, selectedScanCode: '', scanCode: '', loading: false, busy: false, busyAction: '', error: null, successMessage: '', bootstrapped: false } },
computed: {
+ // 已扫描箱号保留提交凭据,列表只显示尚未核对的箱子。
+ discardPendingMarks() { return (this.discardTask?.cartonMarks || []).filter(mark => !this.discardScannedMarks.includes(mark)) },
+ discardQuantityLines() { return (this.discardTask?.skus || []).filter(sku => sku.actualQuantityEditable) },
displayItems() {
const items = this.current?.items || []
if (this.current?.outboundType !== 'RETURN_OUTBOUND') return items
@@ -101,6 +117,7 @@
canComplete() { return this.plannedPackages > 0 && this.remainingPackages === 0 && this.taskOperable },
discardOrders() { const groups = new Map(); for (const task of this.discardTasks) { const current = groups.get(task.discardNo) || { discardNo: task.discardNo, outboundNo: task.outboundNo, palletCount: 0, cartonCount: 0, pendingPalletCount: 0 }; if (task.scanMode === 'CARTON') current.cartonCount += (task.cartonMarks || []).length; else current.palletCount += 1; if (task.status !== 'COMPLETED') current.pendingPalletCount += 1; groups.set(task.discardNo, current) } return [...groups.values()] },
discardOrderCount() { return this.discardOrders.length },
+ discardPendingTasks() { return this.discardCurrentTasks.filter(task => task.status !== 'COMPLETED') },
discardCurrentTasks() { return this.discardTasks.filter(task => task.discardNo === this.discardCurrent?.discardNo) },
discardQuantitySummary() {
const task = this.discardTask
@@ -109,7 +126,7 @@
const boxes = new Set(task.cartonMarks || []).size || task.skus.filter(sku => sku.quantityUnit !== '件').reduce((total, sku) => total + sku.plannedPackages, 0)
return `${boxes} 箱${pieces ? ` · ${pieces} 件` : ''}`
},
- canConfirmDiscard() { const task = this.discardTask; return Boolean(this.discardPhotoReady && this.discardPhotos.length > 0 && !this.discardPhotoBusy && !this.discardLoading && !this.discardBusy && task && task.status !== 'COMPLETED' && task.skus.length && task.skus.every(sku => this.validDiscardActual(sku)) && (task.scanMode !== 'CARTON' || (task.cartonMarks?.length && (this.discardAllCartons || task.cartonMarks.every(mark => this.discardScannedMarks.includes(mark)))))) }
+ canConfirmDiscard() { const task = this.discardTask; return Boolean(this.discardPhotoReady && this.discardPhotos.length > 0 && !this.discardPhotoBusy && !this.discardLoading && !this.discardBusy && task && task.status !== 'COMPLETED' && task.skus.length && task.skus.every(sku => this.validDiscardActual(sku)) && (task.scanMode !== 'CARTON' || (task.cartonMarks?.length && task.cartonMarks.every(mark => this.discardScannedMarks.includes(mark))))) }
},
onShow() { if (!requireSession()) return; this.user = session.currentUser; restoreFulfillmentDraft(`${this.user?.id || ''}:${this.user?.warehouseId || ''}`); this.startScanner(); if (this.discardPhotoBusy || this.discardBusy) return; if (this.discardCurrent) { if (this.discardTask) this.restoreDiscardPhotos(); return } if (!this.bootstrapped) { this.bootstrapped = true; this.scanCode = ''; this.loadTasks(); return } if (this.current?.taskNo) this.openTask(this.current.taskNo); else this.loadTasks() },
onHide() { this.stopScanner() },
@@ -137,17 +154,32 @@
}, true)
},
closeTask() { this.current = null; this.selectedItem = null; this.selectedPosition = null; this.error = null; this.successMessage = ''; updateFulfillmentDraft('outbound', { currentTaskNo: '', scanCode: '', packages: 1 }); this.loadTasks() },
- openDiscardOrder(discardNo) { if (this.discardPhotoBusy) return; this.resetDiscardPhotos(); const code = String(discardNo || '').trim(); if (!code || this.discardBusy || this.busy || this.loading) return; this.current = null; this.discardLoading = false; this.discardCurrent = { discardNo: code, outboundNo: this.discardOrders.find(order => order.discardNo === code)?.outboundNo || '' }; this.discardTask = null; this.discardPalletCode = ''; this.discardActuals = {}; this.discardScannedMarks = []; this.discardAllCartons = false; this.error = null; const loose = this.discardTasks.find(task => task.discardNo === code && task.scanMode === 'CARTON' && task.status !== 'COMPLETED'); if (loose) { this.discardTask = normalizeCargoDiscardTask(loose); this.discardActuals = Object.fromEntries(this.discardTask.skus.map(sku => [sku.itemId, sku.plannedPackages])); this.restoreDiscardPhotos() } },
+ openDiscardOrder(discardNo) { if (this.discardPhotoBusy) return; this.resetDiscardPhotos(); const code = String(discardNo || '').trim(); if (!code || this.discardBusy || this.busy || this.loading) return; this.current = null; this.discardLoading = false; this.discardCurrent = { discardNo: code, outboundNo: this.discardOrders.find(order => order.discardNo === code)?.outboundNo || '' }; this.discardTask = null; this.discardPalletCode = ''; this.discardActuals = {}; this.discardScannedMarks = []; this.discardCartonsExpanded = true; this.discardCartonCode = ''; this.discardNeedsRefresh = false; this.error = null; this.successMessage = ''; },
closeDiscard() { if (this.discardBusy || this.discardPhotoBusy) return; this.resetDiscardPhotos(); this.discardLoading = false; this.discardCurrent = null; this.discardTask = null; this.discardPalletCode = ''; this.discardActuals = {}; this.loadTasks() },
updateDiscardPalletCode(value) { this.discardPalletCode = value },
+ // 下方扫描框只核对当前任务的真实箱号,不切换托盘或清空照片。
+ async scanDiscardCarton(raw) {
+ if (!this.discardTask || this.discardTask.status === 'COMPLETED' || this.discardBusy || this.discardLoading || this.discardPhotoBusy) return
+ const code = String(typeof raw === 'string' ? raw : raw?.detail?.value ?? this.discardCartonCode).trim()
+ if (!code) return
+ if (this.discardNeedsRefresh && !await this.refreshDiscardTask()) return
+ if (this.discardTask.status === 'COMPLETED') return
+ if (!this.discardTask.cartonMarks.includes(code)) { this.setError(new Error('箱号不属于当前废弃任务'), '箱号核对失败'); return }
+ if (!this.discardScannedMarks.includes(code)) this.discardScannedMarks = [...this.discardScannedMarks, code]
+ this.discardCartonCode = ''; this.error = null; playSuccessSound()
+ },
async resolveDiscardPallet(raw) {
const code = String(typeof raw === 'string' ? raw : raw?.detail?.value ?? this.discardPalletCode).trim()
if (!code || !this.discardCurrent || this.discardBusy || this.discardLoading || this.discardPhotoBusy) return
- if (this.discardTask?.scanMode === 'CARTON' && this.discardTask.cartonMarks.includes(code)) { if (!this.discardScannedMarks.includes(code)) this.discardScannedMarks = [...this.discardScannedMarks, code]; this.error = null; playSuccessSound(); return }
+ if (this.discardTask?.scanMode === 'CARTON' && this.discardTask.cartonMarks.includes(code)) { await this.scanDiscardCarton(code); return }
+ if (this.discardTask?.palletNo === code) { await this.refreshDiscardTask(); return }
// 当前托盘可重扫,同一废弃单内也可通过托盘或所选箱唛切换任务。
if (this.discardTask?.scanMode === 'CARTON' && !this.discardTasks.some(task => task.discardNo === this.discardCurrent.discardNo && (task.palletNo === code || (task.cartonMarks || []).includes(code)))) { this.setError(new Error('托盘或箱唛不属于当前废弃单所选货物'), '箱唛核对失败'); return }
+ // 有托盘的任务须先扫描本单托盘,箱号仅用于展开后的逐箱核对。
+ const selectedTasks = this.discardTasks.filter(task => task.discardNo === this.discardCurrent.discardNo)
+ if (selectedTasks.length && !selectedTasks.some(task => task.palletNo !== '散箱' ? task.palletNo === code : (task.cartonMarks || []).includes(code))) { this.setError(new Error('请先扫描当前废弃单对应的托盘码'), '托盘核对失败'); return }
const order = this.discardCurrent
- this.resetDiscardPhotos(); this.discardScannedMarks = []; this.discardAllCartons = false; this.discardPalletCode = code; this.discardLoading = true; this.discardTask = null; this.discardActuals = {}; this.error = null
+ this.resetDiscardPhotos(); this.discardScannedMarks = []; this.discardCartonsExpanded = true; this.discardCartonCode = ''; this.discardPalletCode = code; this.discardLoading = true; this.discardNeedsRefresh = false; this.discardTask = null; this.discardActuals = {}; this.error = null; this.successMessage = ''
try {
const task = await resolveCargoDiscardPallet(code, order.discardNo)
if (this.discardCurrent !== order) return
@@ -158,6 +190,29 @@
} catch (error) { if (this.discardCurrent === order) this.setError(error, '废弃托盘读取失败') }
finally { if (this.discardCurrent === order) this.discardLoading = false }
},
+ /** 重扫与重试先读取当前权威任务,只保留仍匹配的箱号和实点数;照片仍归当前托盘。 */
+ async refreshDiscardTask() {
+ if (!this.discardCurrent || !this.discardTask || this.discardLoading || this.discardPhotoBusy) return false
+ const order = this.discardCurrent; const previous = this.discardTask
+ this.discardLoading = true
+ try {
+ const latest = await resolveCargoDiscardPallet(previous.palletNo, order.discardNo)
+ if (this.discardCurrent !== order || this.discardTask !== previous) return false
+ if (latest.discardNo !== order.discardNo || latest.palletNo !== previous.palletNo) throw new Error('刷新结果不属于当前废弃托盘')
+ this.discardTask = normalizeCargoDiscardTask(latest)
+ this.discardScannedMarks = this.discardScannedMarks.filter(mark => (this.discardTask.cartonMarks || []).includes(mark))
+ this.discardActuals = Object.fromEntries(this.discardTask.skus.map(sku => {
+ const old = previous.skus.find(line => line.itemId === sku.itemId)
+ const unchanged = old && old.plannedPackages === sku.plannedPackages && old.actualQuantityEditable === sku.actualQuantityEditable
+ return [sku.itemId, sku.actualPackages ?? (unchanged ? this.discardActuals[sku.itemId] : sku.actualQuantityEditable ? '' : sku.plannedPackages)]
+ }))
+ this.discardPalletCode = latest.palletNo; this.discardNeedsRefresh = false; this.error = null
+ if (!this.discardPhotoReady) await this.restoreDiscardPhotos()
+ if (latest.status === 'COMPLETED') this.successMessage = '当前托盘已完成废弃,请返回列表核对本单剩余任务'
+ return true
+ } catch (error) { if (this.discardCurrent === order) { this.discardNeedsRefresh = true; this.setError(error, '废弃任务刷新失败,请重新扫描') }; return false }
+ finally { if (this.discardCurrent === order) this.discardLoading = false }
+ },
resetDiscardPhotos() { this.discardPhotos = []; this.discardPhotoUrls = {}; this.discardPhotoErrors = {}; this.discardPhotoReady = false },
async restoreDiscardPhotos() {
if (!this.discardTask || !this.discardCurrent) return
@@ -195,7 +250,29 @@
// 未填写、负数、小数或超出预留数均阻止提交,保留输入供仓库修正。
validDiscardActual(sku) { const raw = this.discardActuals[sku.itemId]; if (raw == null || String(raw).trim() === '') return false; const actual = Number(raw); return Number.isSafeInteger(actual) && actual >= 0 && (sku.actualQuantityEditable === true ? actual <= sku.plannedPackages : actual === sku.plannedPackages) },
updateDiscardActual(itemId, event) { this.discardActuals = { ...this.discardActuals, [itemId]: event.detail.value } },
- async confirmDiscard() { if (this.discardBusy || this.discardLoading || !this.canConfirmDiscard || !this.discardTask || !this.discardCurrent) return; this.error = null; this.successMessage = ''; this.discardBusy = true; try { const lines = this.discardTask.skus.map((sku) => ({ itemId: sku.itemId, actualPackages: Number(this.discardActuals[sku.itemId]) })); const photoFileIds = this.discardPhotos.map(photo => String(photo.fileId)); const key = fulfillmentIdempotencyKey('outbound', 'DISCARD', { discardNo: this.discardCurrent.discardNo, palletNo: this.discardTask.palletNo, lines, photoFileIds, scannedCartonMarks: this.discardScannedMarks, confirmAllCartons: this.discardAllCartons }); await confirmCargoDiscard(this.discardCurrent.discardNo, this.discardTask.palletNo, lines, photoFileIds, this.discardScannedMarks, this.discardAllCartons, { idempotencyKey: key.value }); this.resetDiscardPhotos(); this.discardPalletCode = ''; this.discardTask = null; this.discardTasks = await listCargoDiscardTasks(); this.successMessage = '废弃出库作业及照片已提交,已完成'; playSuccessSound() } catch (error) { this.setError(error, '废弃出库失败') } finally { this.discardBusy = false } },
+ /** 单托完成后刷新同单待办,仍有托盘时明确提示继续扫码。 */
+ async confirmDiscard() {
+ if (this.discardBusy || this.discardLoading || !this.canConfirmDiscard || !this.discardTask || !this.discardCurrent) return
+ this.error = null; this.successMessage = ''; this.discardBusy = true
+ const palletNo = this.discardTask.palletNo; const discardNo = this.discardCurrent.discardNo
+ try {
+ if (this.discardNeedsRefresh) {
+ if (!await this.refreshDiscardTask() || this.discardTask.status === 'COMPLETED' || !this.discardPhotoReady || !this.discardTask.skus.every(sku => this.validDiscardActual(sku)) || (this.discardTask.scanMode === 'CARTON' && !this.discardTask.cartonMarks.every(mark => this.discardScannedMarks.includes(mark)))) return
+ }
+ const lines = this.discardTask.skus.map(sku => ({ itemId: sku.itemId, actualPackages: Number(this.discardActuals[sku.itemId]) }))
+ const photoFileIds = this.discardPhotos.map(photo => String(photo.fileId))
+ const key = fulfillmentIdempotencyKey('outbound', 'DISCARD', { discardNo, palletNo, lines, photoFileIds, scannedCartonMarks: this.discardScannedMarks, confirmAllCartons: false })
+ await confirmCargoDiscard(discardNo, palletNo, lines, photoFileIds, this.discardScannedMarks, false, { idempotencyKey: key.value })
+ this.resetDiscardPhotos(); this.discardPalletCode = ''; this.discardTask = null; this.discardScannedMarks = []; this.discardCartonCode = ''
+ // 确认已成功时,刷新失败不能被显示为出库失败或宣告整单完成。
+ try { this.discardTasks = await listCargoDiscardTasks() }
+ catch (error) { this.discardTasks = this.discardTasks.filter(task => task.discardNo !== discardNo || task.palletNo !== palletNo); this.successMessage = '托盘 ' + palletNo + ' 废弃已完成,其他托盘待办刷新失败,请返回列表重新加载'; this.setError(error, '待办刷新失败'); return }
+ const remaining = this.discardPendingTasks.map(task => task.palletNo)
+ this.successMessage = remaining.length ? '托盘 ' + palletNo + ' 废弃已完成,本单还有 ' + remaining.length + ' 个待废弃任务:' + remaining.join('、') + ',请继续扫描对应托盘' : '本单全部废弃出库作业及照片已提交,已完成'
+ playSuccessSound()
+ } catch (error) { this.discardNeedsRefresh = true; this.setError(error, '废弃出库失败') }
+ finally { this.discardBusy = false }
+ },
handleScan(raw) {
const code = String(raw?.detail?.value ?? raw ?? this.scanCode).trim(); if (!code || this.busy || this.loading || this.discardBusy || this.discardLoading) return
console.info('[Overseas PDA outbound] scan', { code, task: this.current?.taskNo || '', discard: this.discardCurrent?.discardNo || '' }); this.scanCode = code; updateFulfillmentDraft('outbound', { scanCode: code })
@@ -226,7 +303,7 @@
const key = fulfillmentIdempotencyKey('outbound', 'complete', { taskNo: this.current.taskNo })
await this.run('完成出库任务', async () => { await completeOutboundTask(this.current.taskNo, { idempotencyKey: key.value }); this.current = await getOutboundTask(this.current.taskNo); this.successMessage = '出库任务已完成'; playSuccessSound() })
},
- async retry() { if (this.discardCurrent) { if (this.discardPalletCode) await this.resolveDiscardPallet(this.discardPalletCode); return } if (this.current?.taskNo) await this.openTask(this.current.taskNo); else if (fulfillment.outbound.currentTaskNo) await this.openTask(fulfillment.outbound.currentTaskNo); else if (this.scanCode) await this.openScannedTask(this.scanCode); else await this.loadTasks() },
+ async retry() { if (this.discardCurrent) { if (this.discardTask) await this.refreshDiscardTask(); else if (this.discardPalletCode) await this.resolveDiscardPallet(this.discardPalletCode); return } if (this.current?.taskNo) await this.openTask(this.current.taskNo); else if (fulfillment.outbound.currentTaskNo) await this.openTask(fulfillment.outbound.currentTaskNo); else if (this.scanCode) await this.openScannedTask(this.scanCode); else await this.loadTasks() },
async run(action, work, loading = false) { if (this.busy || this.loading) return; this.error = null; this.successMessage = ''; if (loading) this.loading = true; else { this.busy = true; this.busyAction = action.includes('完成') ? 'complete' : 'pick' } try { await work() } catch (error) { this.setError(error, `${action}失败`) } finally { this.loading = false; this.busy = false; this.busyAction = '' } },
setError(error, fallback) { const forbidden = ['403', 'A0403'].includes(String(error?.code || '')); this.error = { message: forbidden ? '当前账号没有海外仓 PDA 作业权限,请联系管理员授权' : error?.message || fallback, code: error?.code, requestId: error?.requestId, traceId: error?.traceId, network: Boolean(error?.network) }; console.error('[Overseas PDA outbound] failed', this.error); playErrorSound() },
lineBusinessCode(position) { return position?.cartonMark || position?.palletNo || position?.skuCode || position?.inboundCode || '业务码缺失' },
@@ -239,6 +316,12 @@