@@ -1,7 +1,7 @@
< template >
< view v-if = "user" class="page-shell portal-page receiving-page" >
< PortalHeader : title = "inboundMode ? '拆柜收货' : operationName" :user = "user" :fallback-url = "`/pages/module/module?key=${moduleKey}`" / >
< view class = "portal-content receiving-content" : class = "{ 'receiving-content-with-action': phase === 'detail' || phase === 'sign' || (phase === 'receive' && allInboundsReconciled ) }" >
< view class = "portal-content receiving-content" : class = "{ 'receiving-content-with-action': phase === 'detail' || phase === 'sign' || (phase === 'receive' && canFinishOnsite ) }" >
< template v-if = "inboundMode" >
< view v-if = "phase !== 'candidates' && phase !== 'receive'" class="receiving-heading"><view><text class="receiving-title">{{ pageTitle }}</text><text class="receiving-subtitle" > {{ pageSubtitle }} < / text > < / view > < / view >
@@ -66,15 +66,15 @@
< template v-else-if = "phase === 'receive'" >
< ReceivingSessionSummary :session = "current" / >
< view class = "scan-card" >
< text class = "section-label" > 1. 扫描箱码定位入仓码 < / text >
< ScanTargetInput name = "carton" v-model = "cartonInput" label="箱 码" placeholder="NYEXX2608260001#4-1 " :active="activeTarget === 'carton'" @activate="activeTarget = 'carton'" @submit="resolveCarton" / >
< button class = "secondary-button" : disabled = "busy || !cartonInput.trim()" @tap ="resolveCarton(cartonInput)" > 解析箱 码 < / button >
< text class = "section-label" > 1. 扫描 DP 、 箱码或 FBA 任一箱 < / text >
< ScanTargetInput name = "carton" v-model = "cartonInput" label="货物 码" placeholder="扫描 DP 或箱码 " :active="activeTarget === 'carton'" @activate="activeTarget = 'carton'" @submit="resolveCarton" / >
< button class = "secondary-button" : disabled = "busy || !cartonInput.trim()" @tap ="resolveCarton(cartonInput)" > 解析货物 码 < / button >
< template v-if = "activeInbound" >
< view class = "matched-inbound-card" >
< view class = "mode-banner" : class = "activeInbound.destinationType === 'FBA' ? 'fba' : 'overseas'" > < text > { { activeInbound . destinationType === 'FBA' ? 'FBA 按箱数收货' : '海外仓逐箱收货' } } < / text > < text > { { destinationLabel ( activeInbound ) } } < / text > < / view >
< text class = "matched-inbound-code" > { { activeInbound . inboundCode } } < / text >
< view class = "matched-plan-flags" > < text > { { activeInbound . assigned ? '已分配 ' : '未分配' } } < / text > < text : class = "{ optional: activeInbound.mergeAllowed }" > { { activeInbound . mergeAllowed ? '可合托(现场自选) ' : '不合托 ' } } < / text > < text : class = "{ urgent: activeInbound.urgent }" > { { activeInbound . urgent ? '加急' : '普通' } } < / text > < / view >
< text class = "matched-inbound-code" > { { ( activeInbound . inboundCodes || [ activeInbound . inboundCode ] ) . join ( ' / ' ) } } < / text >
< view class = "matched-plan-flags" > < text > { { activeInbound . operationMode === 'FBA_GROUP ' ? '按 FBA 仓成组收货 ' : '逐箱收货 ' } } < / text > < text : class = "{ urgent: activeInbound.urgent }" > { { activeInbound . urgent ? '加急' : '普通' } } < / text > < / view >
< text v-if = "activeInbound.mergeHint" class="merge-option-hint" > {{ activeInbound.mergeHint }} < / text >
< view class = "matched-inbound-progress" > < text > 总计 { { activeInbound . totalPackages } } 箱 < / text > < text > 已收 { { activeInbound . receivedPackages } } 箱 < / text > < text > 待收 { { activeInbound . pendingPackages } } 箱 < / text > < / view >
< / view >
@@ -83,18 +83,18 @@
< button class = "secondary-button" : disabled = "busy || !palletInput.trim()" @tap ="resolvePallet(palletInput)" > 校验托盘 < / button >
< view v-if = "resolvedPallet" class="resolved-card" > < text > { { resolvedPallet . palletCode } } < / text > < text > { { resolvedPallet . compatible ? '目的仓匹配,可收货' : '托盘不匹配' } } < / text > < / view >
< template v-if = "activeInbound.destinationType === 'FBA'" >
< text class = "section-label" > 3. 输入本次 箱数 < / text >
< input v -model .number = " packageCount " class = "quantity-input" type = "number" inputmode = "numeric" : placeholder = "`最多可收 ${activeInbound.pendingPackages} 箱 `" / >
< text class = "section-label" > 3. 输入该托累计 箱数 < / text >
< input v -model .number = " packageCount " class = "quantity-input" type = "number" inputmode = "numeric" :placeholder = "`该托当前累计箱数 `" / >
< / template >
< button class = "primary-button receipt-button" : disabled = "!canSubmitReceipt || busy" @tap ="submitReceipt" > {{ activeInbound.destinationType = = = ' FBA ' ? ' 确认按箱数收货 ' : ' 确认本箱收货 ' }} < / button >
< button class = "primary-button receipt-button" : disabled = "!canSubmitReceipt || busy" @tap ="submitReceipt" > {{ activeInbound.destinationType = = = ' FBA ' ? ' 保存该托累计箱数 ' : ' 确认本箱收货 ' }} < / button >
< / template >
< / view >
< view v-if = "lastReceipt" class="success-card" > 本次已收 {{ lastReceipt.acceptedPackages }} 箱 , {{ lastReceipt.inboundCode }} 剩余 {{ lastReceipt.pendingPackages }} 箱 < / view >
< view v-if = "allInboundsReconciled " class="sticky-action one"><button class="primary-button complete-button" :disabled="busy" @tap="completeContainer" > 完成整柜入库 < / button > < / view >
< view v-if = "canFinishOnsite " class="sticky-action one"><button class="primary-button complete-button" :disabled="busy || activeTarget === 'quantity' || scanQueue.length" @tap="completeContainer" > {{ allInboundsReconciled ? ' 完成整柜入库 ' : ' 现场收货结束 ( 标记差异 ) ' }} < / button > < / view >
< / template >
< template v-else-if = "phase === 'completed'" >
< view class = "completed-card" > < view class = "completed-icon" > ✓ < / view > < text class = "completed-title" > 当前柜已完成入库 < / text > < text > { { current ? . containerNo } } < / text > < text > 实收 { { current ? . receivedPackages } } / { { current ? . totalPackages } } 箱 < / text > < button class = "primary-button" @tap ="finishAndReturn" > 返回待卸货列表 < / button > < / view >
< view class = "completed-card" > < view class = "completed-icon" > ✓ < / view > < text class = "completed-title" > { { current ? . status === 'COMPLETED_WITH_DIFFERENCE' ? '现场收货已结束,差异待核平' : '当前柜已完成入库' } } < / text > < text > { { current ? . containerNo } } < / text > < text > 实收 { { current ? . receivedPackages } } / { { current ? . totalPackages } } 箱 < / text > < button class = "primary-button" @tap ="finishAndReturn" > 返回待卸货列表 < / button > < / view >
< / template >
< view v-if = "error" class="error-card" >
@@ -121,12 +121,12 @@
import { idempotencyKey , receiving , receivingPhase , receivingProgress , releaseIdempotencyKey , resetReceivingDraft , setCurrentReceiving } from '@/stores/receiving.js'
import { startScanBroadcast } from '@/utils/scan-broadcast.js'
import { playErrorSound , playSuccessSound } from '@/utils/audio-feedback.js'
import { completeCurrentReceiving , downloadCurrentUnloadPhoto , getCurrentReceiving , getCurrentWarehouseProfile , getReceivingContainer , listReceivingCandidates , prepareReceivingContainer , receiveCurrentBulk , receiveCurrentCarton , resolveCurrentCarton , resolveCurrentPallet , signCurrentReceiving , uploadCurrentUnloadPhoto } from '@/api/inbound.js'
import { completeCurrentReceiving , downloadCurrentUnloadPhoto , getCurrentReceiving , getCurrentWarehouseProfile , getReceivingContainer , listReceivingCandidates , prepareReceivingContainer , receiveCurrentBulk , receiveCurrentCarton , receiveCurrentWholePallet , resolveCurrentCarton , resolveCurrentPallet , signCurrentReceiving , uploadCurrentUnloadPhoto } from '@/api/inbound.js'
const MAX _UNLOAD _PHOTO _BYTES = 10 * 1024 * 1024
export default {
components : { PortalHeader , ReceivingSessionSummary , ScanTargetInput } ,
data ( ) { return { user : session . currentUser , moduleKey : 'query' , operationName : '库存查询' , loading : false , candidateLoading : false , busy : false , bootstrapped : false , detailOpen : false , cartonInput : '' , palletInput : '' , packageCount : 1 , candidateError : null , error : null , toast : { visible : false , message : '' , type : '' } } } ,
data ( ) { return { user : session . currentUser , moduleKey : 'query' , operationName : '库存查询' , loading : false , candidateLoading : false , busy : false , bootstrapped : false , detailOpen : false , cartonInput : '' , palletInput : '' , packageCount : '' , candidateError : null , error : null , scanQueue : [ ] , toast : { visible : false , message : '' , type : '' } } } ,
computed : {
inboundMode ( ) { return this . moduleKey === 'inbound' } ,
photoSteps ( ) { return RECEIVING _PHOTO _ROLES . map ( ( role ) => ( { role , label : ( { LICENSE _PLATE : '车牌照片' , CONTAINER _SEAL : '集装箱封签' , DOOR _OPEN : '开柜现场' } ) [ role ] } ) ) } ,
@@ -135,8 +135,9 @@
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 ) return false ; if ( this . activeInbound . destinationType === 'FBA' ) { const count = Number ( this . packageCount ) ; return Number . isInteger ( count ) && count > 0 && count <= Number ( this . activeInbound . pendingPackages ) } return Boolean ( this . activeInbound . cartonMark ) } ,
allInboundsReconciled ( ) { const values = this . current ? . inbounds || [ ] ; return values . length > 0 && values . every ( ( item ) => Number ( item . pendingPackages ) === 0 ) } ,
canSubmitReceipt ( ) { if ( ! this . activeInbound || ! this . resolvedPallet ? . compatible ) 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 ) ) } ,
boundWarehouseLabel ( ) { const code = String ( this . user ? . warehouseCode || '' ) . trim ( ) ; const name = String ( this . user ? . warehouseName || '' ) . trim ( ) ; return code && name ? ` ${ code } · ${ name } ` : '海外仓未关联' } ,
pageTitle ( ) { return { candidates : '待卸货集装箱' , detail : '柜号详情' , sign : '拆柜拍照签收' , receive : '当前柜收货' , completed : '入库完成' } [ this . phase ] } ,
pageSubtitle ( ) { return { candidates : '选择集装箱后拍摄 3 张照片完成签收' , detail : '核对柜号与整柜计划箱数' , sign : '完成现场取证后进入收货' , receive : '扫描箱码定位入仓码后开始收货' , completed : '库存与柜级进度已由服务端核平' } [ this . phase ] }
@@ -145,7 +146,7 @@
onShow ( ) { if ( ! requireSession ( ) ) return ; this . user = session . currentUser ; this . startScanner ( ) ; if ( this . inboundMode && ( ! this . bootstrapped || this . phase === 'candidates' ) ) this . bootstrap ( ) } ,
onHide ( ) { this . stopScanner ( ) } , onUnload ( ) { this . stopScanner ( ) } , beforeUnmount ( ) { clearTimeout ( this . _toastTimer ) ; this . stopScanner ( ) } ,
methods : {
statusLabel ( status ) { return ( { WAIT _SIGN : '待签收' , SIGNED : '已签收' , RECEIVING : '收货中' , COMPLETED : '已完成' } ) [ status ] || status } ,
statusLabel ( status ) { return ( { WAIT _SIGN : '待签收' , SIGNED : '已签收' , RECEIVING : '收货中' , COMPLETED : '已完成' , COMPLETED _WITH _DIFFERENCE : '差异待核平' } ) [ status ] || status } ,
statusTone ( status ) { return status === 'RECEIVING' ? 'status-current' : status === 'COMPLETED' ? 'status-good' : 'status-wait' } ,
formatPlannedArrival ( value ) { const text = String ( value || '' ) . trim ( ) ; return text ? text . replace ( 'T' , ' ' ) . slice ( 0 , 16 ) : '待确认' } ,
destinationLabel ( item ) { return item ? . destinationType === 'FBA' ? ( item . fbaWarehouseCode || 'FBA' ) : ( item ? . destinationWarehouseName || item ? . destinationWarehouseCode || '海外仓' ) } ,
@@ -154,8 +155,10 @@
async refreshBoundWarehouse ( ) { this . user = applyWarehouseProfile ( await getCurrentWarehouseProfile ( ) ) ; return this . user } ,
photoStateLabel ( photo ) { if ( photo ? . status === 'uploading' ) return '正在上传…' ; if ( photo ? . status === 'error' ) return photo . errorMessage || ( photo . fileId ? '重拍失败,已保留原图' : '上传失败,请重试' ) ; return photo ? . fileId ? '点击左侧照片可查看大图' : '请拍摄现场照片' } ,
startScanner ( ) { this . stopScanner ( ) ; this . _stopScanner = startScanBroadcast ( { onScan : ( value ) => this . handleHardwareScan ( value ) , onError : ( error ) => { this . error = error ; this . showToast ( '硬件扫码接收器异常,请检查扫描服务' , 'warning' ) } } ) } , stopScanner ( ) { if ( typeof this . _stopScanner === 'function' ) this . _stopScanner ( ) ; this . _stopScanner = null } ,
handleHardwareScan ( value ) { console . info ( '[Overseas PDA scan] dispatch' , { value , phase : this . phase , target : this . activeTarget , matchedInbound : this . activeInbound ? . inboundCode } ) ; if ( ! this . inboundMode ) return this . showToast ( '该功能缺少海外 PDA 专用接口,扫码未提交' , 'warning' ) ; if ( this . phase === 'candidates' ) return this . showToast ( '请直接点击待卸货集装箱进入详情' , 'warning' ) ; if ( this . phase !== 'receive' ) return ; if ( this . activeTarget === 'pallet' && this . activeInbound ) { this . palletInput = value ; return this . resolvePallet ( value ) } this . cartonInput = value ; return this . resolveCarton ( value ) } ,
async run ( action , work ) { this . busy = true ; this . error = null ; try { const result = await work ( ) ; this . showToast ( ` ${ action } 成功 ` , 'success' ) ; return result } catch ( error ) { const mappedError = toReceivingError ( error , ` ${ action } 失败 ` ) ; this . error = mappedError ; playErrorSound ( ) ; this . showToast ( mappedError . message , 'error' ) ; throw mappedError } finally { this . busy = false } } ,
handleHardwareScan ( value ) { const scan = { value : String ( value || '' ) . trim ( ) , target : this . activeTarget } ; if ( ! scan . value ) return ; if ( ! this . inboundMode ) return this . showToast ( '该功能缺少海外 PDA 专用接口,扫码未提交' , 'warning' ) ; if ( this . phase === 'candidates' ) return this . showToast ( '请直接点击待卸货集装箱进入详情' , 'warning' ) ; if ( this . phase !== 'receive' ) return ; if ( this . busy || this . activeTarget === 'quantity' ) { if ( this . scanQueue . length > = 100 ) return this . showToast ( '扫码队列已满,请先处理当前托盘' , 'warning' ) ; this . scanQueue . push ( scan ) ; return this . showToast ( ` 已排队 ${ this . scanQueue . length } 个扫码 ` , 'warning' ) } return this . dispatchHardwareScan ( scan ) } ,
dispatchHardwareScan ( scan ) { console . info ( '[Overseas PDA scan] dispatch' , { value : scan . value , target : scan . target } ) ; if ( scan . target === 'pallet' && this . activeInbound ) { this . palletInput = scan . value ; return this . resolvePallet ( scan . value ) } this . cartonInput = scan . value ; return this . resolveCarton ( scan . value ) } ,
drainScanQueue ( ) { if ( this . busy || this . activeTarget === 'quantity' || ! this . scanQueue . length || this . phase !== 'receive' ) return ; const next = this . scanQueue . shift ( ) ; this . dispatchHardwareScan ( next ) } ,
async run ( action , work ) { this . busy = true ; this . error = null ; try { const result = await work ( ) ; this . showToast ( ` ${ action } 成功 ` , 'success' ) ; return result } catch ( error ) { const mappedError = toReceivingError ( error , ` ${ action } 失败 ` ) ; this . error = mappedError ; playErrorSound ( ) ; this . showToast ( mappedError . message , 'error' ) ; throw mappedError } finally { this . busy = false ; setTimeout ( ( ) => this . drainScanQueue ( ) , 0 ) } } ,
setScanTargetForCurrent ( value ) { receiving . activeScanTarget = value ? . status === 'WAIT_SIGN' ? 'container' : 'carton' } ,
async restorePhotoPreviews ( ) { await Promise . all ( receiving . photoSlots . filter ( ( photo ) => photo ? . downloadPath && ! photo . localPath ) . map ( async ( photo ) => { photo . status = 'restoring' ; try { photo . localPath = await downloadCurrentUnloadPhoto ( photo . downloadPath ) ; photo . status = 'success' } catch ( error ) { photo . status = 'error' ; photo . errorMessage = error . message || '照片加载失败' ; this . error = error } } ) ) } ,
async applyCurrentReceiving ( value ) { this . validateReceivingWarehouse ( value , '当前接柜会话' ) ; setCurrentReceiving ( value ) ; await this . restorePhotoPreviews ( ) ; this . setScanTargetForCurrent ( value ) } ,
@@ -169,11 +172,11 @@
async replacePhoto ( index , path ) { const previous = receiving . photoSlots [ index ] ; receiving . photoSlots . splice ( index , 1 , { ... ( previous || { } ) , role : this . photoSteps [ index ] . role , status : 'uploading' , errorMessage : '' } ) ; this . busy = true ; this . error = null ; try { const file = await uploadCurrentUnloadPhoto ( path ) ; receiving . photoSlots . splice ( index , 1 , { ... file , fileId : String ( file . fileId ) , role : this . photoSteps [ index ] . role , localPath : path , uploaded : true , status : 'success' } ) ; this . showToast ( ` ${ this . photoSteps [ index ] . label } 上传成功 ` , 'success' ) } catch ( error ) { receiving . photoSlots . splice ( index , 1 , previous ? . fileId ? { ... previous , status : 'error' , errorMessage : error . message || '重拍失败,已保留原图' } : { role : this . photoSteps [ index ] . role , localPath : path , uploaded : false , status : 'error' , errorMessage : error . message || '上传失败,请重试' } ) ; this . error = error ; this . showToast ( error . message || '照片上传失败' , 'error' ) ; playErrorSound ( ) } finally { this . busy = false } } ,
previewPhoto ( index ) { const path = this . photoSlots [ index ] ? . localPath ; if ( ! path ) return ; uni . previewImage ( { urls : [ path ] , current : path } ) } ,
async signContainer ( ) { if ( ! this . photosReady ) return ; const photos = this . photoSlots . map ( ( item , index ) => ( { role : this . photoSteps [ index ] . role , fileId : String ( item . fileId ) } ) ) ; const key = idempotencyKey ( 'sign' , { containerNo : this . current . containerNo , photos } ) ; try { const value = await this . run ( '签收当前柜' , async ( ) => this . validateReceivingWarehouse ( await signCurrentReceiving ( { photos , idempotencyKey : key . value } ) , '当前接柜会话' ) ) ; releaseIdempotencyKey ( key . signature ) ; await this . applyCurrentReceiving ( value ) ; this . clearReceiptContext ( ) } catch ( _ ) { } } ,
clearReceiptContext ( ) { receiving . resolvedCarton = null ; receiving . resolvedPallet = null ; this . cartonInput = '' ; this . palletInput = '' ; this . packageCount = 1 ; receiving . activeScanTarget = 'carton' } ,
async resolveCarton ( raw ) { const cartonMark = String ( raw ? . detail ? . value ? ? raw ? ? this . cartonInput ) . trim ( ) ; if ( ! cartonMark ) return ; if ( this . busy ) { console . warn ( '[Overseas PDA carton] ignored while busy' , { cartonMark } ) ; return this . showToast ( '正在处理上一笔扫描,请稍后重试 ', 'warning' ) } console . info ( '[Overseas PDA carton] resolving ', { cartonMark , containerNo : this . current ? . containerNo } ) ; try { const value = await this . run ( '解析箱码' , ( ) => resolveCurrentCarton ( cartonMark ) ) ; console . info ( '[Overseas PDA carton] resolved' , { cartonMark : value . cartonMark , inboundCode : value . inboundCode , destinationType : value . destinationType , pendingPackages : value . pendingPackages } ) ; receiving . resolvedCarton = value ; receiving . resolvedPallet = null ; this . cartonInput = value . cartonMark ; this . palletInput = '' ; this . packageCount = 1 ; playSuccessSound ( ) } catch ( error ) { console . error ( '[Overseas PDA carton ] failed' , { cartonMark , message : error . message , code : error . code , requestId : error . requestId , traceId : error . traceId } ) } } ,
async resolvePallet ( raw ) { const palletCode = String ( raw ? . detail ? . value ? ? raw ? ? this . palletInput ) . trim ( ) ; if ( ! palletCode || ! this . activeInbound || this . busy ) return ; try { const value = await this . run ( '校验托盘' , ( ) => resolveCurrentPallet ( palletCode , this . activeInbound . inboundCode ) ) ; receiving . resolvedPallet = value ; this . palletInput = value . palletCode ; if ( ! value . compatible ) throw Object . assign ( new Error ( '托盘不匹配' ) , { code : 'PALLET_INCOMPATIBLE' } ) ; playSuccessSound ( ) } catch ( error ) { if ( ! this . error ) this . error = error } } ,
async submitReceipt ( ) { if ( ! this . canSubmitReceipt ) return ; const isFba = this . activeInbound . destinationType === 'FBA' ; const payload = isFba ? { inboundCode : this . activeInbound . inboundCode , palletCode : this . resolvedPallet . palletCode , packageCount : Number ( this . packageCount ) } : { cartonMark : this . activeInbound . cartonMark , palletCode : this . resolvedPallet . palletCode } ; const key = idempotencyKey ( isFba ? 'bulk-receipt' : 'carton-receipt' , payload ) ; try { const result = await this . run ( '提交实收' , ( ) => isFba ? receiveCurrentBulk ( { ... payload , idempotencyKey : key . value } ) : receiveCurrentCarton ( { ... payload , idempotencyKey : key . value } ) ) ; const current = this . validateReceivingWarehouse ( await getCurrentReceiving ( ) , '当前接柜会话' ) ; releaseIdempotencyKey ( key . signature ) ; receiving . lastReceipt = result ; await this . applyCurrentReceiving ( current ) ; this . clearReceiptContext ( ) ; playSuccessSound ( ) } catch ( _ ) { } } ,
async completeContainer ( ) { if ( ! this . allInboundsReconciled ) return ; const key = idempotencyKey ( 'complete' , { containerNo : this . current . containerNo } ) ; try { const value = await this . run ( '完成入库 ', async ( ) => this . validateReceivingWarehouse ( await completeCurrentReceiving ( { idempotencyKey : key . value } ) , '当前接柜会话' ) ) ; releaseIdempotencyKey ( key . signature ) ; await this . applyCurrentReceiving ( value ) } catch ( _ ) { } } ,
clearReceiptContext ( ) { receiving . resolvedCarton = null ; receiving . resolvedPallet = null ; this . cartonInput = '' ; this . palletInput = '' ; this . packageCount = '' ; receiving . activeScanTarget = 'carton' ; setTimeout ( ( ) => this . drainScanQueue ( ) , 0 ) } ,
async resolveCarton ( raw ) { const cartonMark = String ( raw ? . detail ? . value ? ? raw ? ? this . cartonInput ) . trim ( ) ; if ( ! cartonMark ) return ; if ( this . busy ) return this . handleHardwareScan ( cartonMark ) ; try { const outcome = await this . run ( '处理货物码' , async ( ) => { const value = await resolveCurrentCarton ( cartonMark ) ; if ( value . operationMode !== 'DROPSHIP_PALLET ') return { value } ; const payload = { sourcePalletNo : value . sourcePalletNo || cartonMark } ; const key = idempotencyKey ( 'whole-pallet-receipt ', { containerNo : this . current ? . containerNo , ... payload } ) ; const result = await receiveCurrentWholePallet ( { ... payload , idempotencyKey : key . value } ) ; const current = this . validateReceivingWarehouse ( await getCurrentReceiving ( ) , '当前接柜会话' ) ; releaseIdempotencyKey ( key . signature ) ; return { value , result , current } } ) ; if ( outcome . result ) { receiving . lastReceipt = outcome . result ; await this . applyCurrentReceiving ( outcome . current ) ; this . clearReceiptContext ( ) ; playSuccessSound ( ) ; return } receiving . resolvedCarton = outcome . value ; receiving . resolvedPallet = null ; this . cartonInput = outcome . value . cartonMark ; this . palletInput = '' ; this . packageCount = '' ; receiving . activeScanTarget = 'pallet' ; playSuccessSound ( ) } catch ( error ) { console . error ( '[Overseas PDA cargo ] failed' , { cartonMark , message : error . message , code : error . code , requestId : error . requestId , traceId : error . traceId } ) } } ,
async resolvePallet ( raw ) { const palletCode = String ( raw ? . detail ? . value ? ? raw ? ? this . palletInput ) . trim ( ) ; if ( ! palletCode || ! this . activeInbound || this . busy ) return ; try { const value = await this . run ( '校验托盘' , ( ) => resolveCurrentPallet ( palletCode , this . activeInbound . inboundCode ) ) ; receiving . resolvedPallet = value ; this . palletInput = value . palletCode ; this . packageCount = Number ( value . currentReceivedPackages || 0 ) || '' ; if ( ! value . compatible ) throw Object . assign ( new Error ( '托盘不匹配' ) , { code : 'PALLET_INCOMPATIBLE' } ) ; receiving . activeScanTarget = this . activeInbound . destinationType === 'FBA' ? 'quantity' : 'carton' ; playSuccessSound ( ) } catch ( error ) { if ( ! this . error ) this . error = error } } ,
async submitReceipt ( ) { if ( ! this . canSubmitReceipt ) return ; const isFba = this . activeInbound . destinationType === 'FBA' ; const payload = isFba ? { inboundCode : this . activeInbound . inboundCode , palletCode : this . resolvedPallet . palletCode , packageCount : Number ( this . packageCount ) , expectedPalletVersion : this . resolvedPallet . version } : { cartonMark : this . activeInbound . cartonMark , palletCode : this . resolvedPallet . palletCode } ; const key = idempotencyKey ( isFba ? 'bulk-receipt' : 'carton-receipt' , { containerNo : this . current ? . containerNo , groupKey : this . activeInbound . groupKey , ... payload } ) ; try { const result = await this . run ( '提交实收' , async ( ) => { const receipt = isFba ? await receiveCurrentBulk ( { ... payload , idempotencyKey : key . value } ) : await receiveCurrentCarton ( { ... payload , idempotencyKey : key . value } ) ; const current = this . validateReceivingWarehouse ( await getCurrentReceiving ( ) , '当前接柜会话' ) ; return { receipt , current } } ) ; releaseIdempotencyKey ( key . signature ) ; receiving . lastReceipt = result . receipt ; await this . applyCurrentReceiving ( result . current ) ; this . clearReceiptContext ( ) ; playSuccessSound ( ) } catch ( _ ) { } } ,
async completeContainer ( ) { if ( ! this . canFinishOnsite || this . activeTarget === 'quantity' || this . scanQueue . length ) return ; const payload = { allowDifference : ! this . allInboundsReconciled } ; const key = idempotencyKey ( 'complete' , { containerNo : this . current . containerNo , ... payload } ) ; try { const value = await this . run ( this . allInboundsReconciled ? '完成入库' : '结束现场收货 ', async ( ) => this . validateReceivingWarehouse ( await completeCurrentReceiving ( { ... payload , idempotencyKey : key . value } ) , '当前接柜会话' ) ) ; releaseIdempotencyKey ( key . signature ) ; await this . applyCurrentReceiving ( value ) } catch ( _ ) { } } ,
async finishAndReturn ( ) { resetReceivingDraft ( ) ; await this . loadCandidates ( false ) } ,
showToast ( message , type ) { clearTimeout ( this . _toastTimer ) ; this . toast = { visible : true , message , type } ; this . _toastTimer = setTimeout ( ( ) => { this . toast . visible = false } , 2600 ) }
}