@@ -47,7 +47,7 @@
< view class = "card-head" > < view > < text class = "card-title" > 国内装柜照片 ( 只读 ) < / text > < text class = "container-meta" > 现场对照国内装柜过程与封柜状态 < / text > < / view > < text class = "status-tag status-good" > { { domesticPhotoCount } } / 5 < / text > < / view >
< view class = "domestic-photo-grid" >
< view v-for = "photo in domesticLoadingPhotos" :key="photo.evidenceType" class="domestic-photo-item" @tap="previewDomesticPhoto(photo)" >
< image v-if = "photo.localPath" class="domestic-photo-thumb" :src="photo.localPath" mode="aspectFill" / >
< image v-if = "photo.localPath" class="domestic-photo-thumb" :src="photo.localPath" mode="aspectFill" @error="invalidatePhotoPreview(photo)" / >
< view v-else class = "domestic-photo-placeholder" > < uni-load-more v-if = "photo.status === 'loading'" status="loading" :show-text="false" /><uni-icons v-else type="image" color="#8090a3" size="22" / > < text > { { photo . status === 'error' ? '加载失败,点按重试' : '正在准备照片' } } < / text > < / view >
< text > { { photo . label } } < / text >
< / view >
@@ -58,7 +58,7 @@
< view class = "photo-grid" >
< view v-for = "(step, index) in photoSteps" :key="step.role" class="photo-role" :class="{ complete: photoSlots[index]?.fileId }" >
< view class = "photo-preview" : class = "{ failed: photoSlots[index]?.status === 'error' }" @tap ="previewPhoto(index)" >
< image v-if = "photoSlots[index]?.localPath" class="photo-thumb" :src="photoSlots[index].localPath" mode="aspectFill" / >
< image v-if = "photoSlots[index]?.localPath" class="photo-thumb" :src="photoSlots[index].localPath" mode="aspectFill" @error="invalidatePhotoPreview(photoSlots[index])" / >
< view v-else class = "photo-placeholder" > < uni-icons type = "camera" color = "#8090a3" size = "23" / > < text > 未添加 < / text > < / view >
< / view >
< view class = "photo-role-info" >
@@ -76,10 +76,10 @@
< template v-else-if = "phase === 'receive'" >
< ReceivingSessionSummary :session = "current" / >
< view class = "photo-card domestic-photo-card domestic-photo-collapsible" >
< view class = "card-head" @tap ="domesticPhotosExpanded = !d omesticPhotosExpanded " > < view > < text class = "card-title" > 国内装柜照片 < / text > < text class = "container-meta" > 已同步 { { domesticPhotoCount } } / 5 , 点按 { { domesticPhotosExpanded ? '收起' : '展开' } } 对照 < / text > < / view > < text class = "container-detail-link" > { { domesticPhotosExpanded ? '收起' : '查看' } } › < / text > < / view >
< view class = "card-head" @tap ="toggleD omesticPhotos" > < view > < text class = "card-title" > 国内装柜照片 < / text > < text class = "container-meta" > 已同步 { { domesticPhotoCount } } / 5 , 点按 { { domesticPhotosExpanded ? '收起' : '展开' } } 对照 < / text > < / view > < text class = "container-detail-link" > { { domesticPhotosExpanded ? '收起' : '查看' } } › < / text > < / view >
< view v-if = "domesticPhotosExpanded" class="domestic-photo-grid" >
< view v-for = "photo in domesticLoadingPhotos" :key="photo.evidenceType" class="domestic-photo-item" @tap.stop="previewDomesticPhoto(photo)" >
< image v-if = "photo.localPath" class="domestic-photo-thumb" :src="photo.localPath" mode="aspectFill" / >
< image v-if = "photo.localPath" class="domestic-photo-thumb" :src="photo.localPath" mode="aspectFill" @error="invalidatePhotoPreview(photo)" / >
< view v-else class = "domestic-photo-placeholder" > < uni-load-more v-if = "photo.status === 'loading'" status="loading" :show-text="false" /><uni-icons v-else type="image" color="#8090a3" size="22" / > < text > { { photo . status === 'error' ? '加载失败,点按重试' : '正在准备照片' } } < / text > < / view >
< text > { { photo . label } } < / text >
< / view >
@@ -139,7 +139,7 @@
import ScanTargetInput from '@/components/ScanTargetInput.vue'
import { assertReceivingWarehouse , isNoCurrentReceiving , isReceivingConflict , RECEIVING _PHOTO _ROLES , toReceivingError } from '@/api/receiving-adapter.js'
import { applyWarehouseProfile , session , requireSession } from '@/stores/session.js'
import { idempotencyKey , receiving , receivingPhase , receivingProgress , releaseIdempotencyKey , resetReceivingDraft , setCurrentReceiving } from '@/stores/receiving.js'
import { idempotencyKey , receiving , receivingPhase , receivingProgress , releaseIdempotencyKey , resetReceivingDraft , restoreReceivingPhoto , setCurrentReceiving } from '@/stores/receiving.js'
import { startScanBroadcast } from '@/utils/scan-broadcast.js'
import { playErrorSound , playSuccessSound } from '@/utils/audio-feedback.js'
import { completeCurrentReceiving , downloadCurrentDomesticLoadingPhoto , downloadCurrentUnloadPhoto , getCurrentReceiving , getCurrentWarehouseProfile , getReceivingContainer , listReceivingCandidates , prepareReceivingContainer , receiveCurrentBulk , receiveCurrentCarton , receiveCurrentWholePallet , resolveCurrentCarton , resolveCurrentPallet , signCurrentReceiving , uploadCurrentUnloadPhoto } from '@/api/inbound.js'
@@ -167,8 +167,8 @@
pageSubtitle ( ) { return { candidates : '选择集装箱后拍摄 3 张照片完成签收' , detail : '核对柜号与整柜计划箱数' , sign : '完成现场取证后进入收货' , receive : '扫描箱码定位入仓码后开始收货' , completed : '库存与柜级进度已由服务端核平' } [ this . phase ] }
} ,
onLoad ( options ) { this . moduleKey = options . module || 'query' ; const key = options . op || ( this . moduleKey === 'inbound' ? 'receiving' : 'inventoryQuery' ) ; this . operationName = ( { receiving : '拆柜收货' , stocktake : '库存盘点' , move : '商品移库' , pallet : '托盘管理' , relabelReceive : '换标列表' , relabelExecute : '换标执行' , relabelReview : '换标复核' , inventoryQuery : '库存查询' , locationQuery : '库位查询' , palletQuery : '托盘查询' } ) [ key ] || '库存查询' } ,
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 ( ) } ,
onShow ( ) { if ( ! requireSession ( ) ) return ; this . user = session . currentUser ; this . _pageActive = true ; this . startScanner ( ) ; if ( this . inboundMode && ( ! this . bootstrapped || this . phase === 'candidates' || this . _resumeReceiving ) ) { if ( ! this . busy ) { this . _resumeReceiving = false ; this . bootstrap ( ) } } } ,
onHide ( ) { this . suspendReceiving ( ) } , onUnload ( ) { this . suspendReceiving ( ) } , beforeUnmount ( ) { this . suspendReceiving ( ) } ,
methods : {
candidateKey ( item ) { return ` ${ item ? . sourceContainerBookingId || '' } : ${ item ? . containerNo || '' } ` } ,
statusLabel ( status ) { return ( { WAIT _SIGN : '待签收' , SIGNED : '已签收' , RECEIVING : '收货中' , COMPLETED : '已完成' , COMPLETED _WITH _DIFFERENCE : '差异待核平' } ) [ status ] || status } ,
@@ -180,16 +180,19 @@
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 ) { 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 ) { const palletWorkflowActive = Boolean ( this . activeInbound || this . resolvedPallet ) ; const target = palletWorkflowActive ? 'pallet' : scan . target ; console . info ( '[Overseas PDA scan] dispatch' , { value : scan . value , target , queuedTarget : scan . target } ) ; if ( target === 'pallet' ) { 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 ) } } ,
handleHardwareScan ( value ) { if ( ! this . _pageActive || this . loading ) return ; 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 ) { if ( ! this . _pageActive || this . loading ) return ; const palletWorkflowActive = Boolean ( this . activeInbound || this . resolvedPallet ) ; const target = palletWorkflowActive ? 'pallet' : scan . target ; console . info ( '[Overseas PDA scan] dispatch' , { value : scan . value , target , queuedTarget : scan . target } ) ; if ( target === 'pallet' ) { this . palletInput = scan . value ; return this . resolvePallet ( scan . value ) } this . cartonInput = scan . value ; return this . resolveCarton ( scan . value ) } ,
drainScanQueue ( ) { if ( ! this . _pageActive || this . loading || this . busy || this . activeTarget === 'quantity' || ! this . scanQueue . length || this . phase !== 'receive' ) return ; const next = this . scanQueue . shift ( ) ; this . dispatchHardwareScan ( next ) } ,
suspendReceiving ( ) { this . _pageActive = false ; this . _scanEpoch = ( this . _scanEpoch || 0 ) + 1 ; this . _resumeReceiving = true ; clearTimeout ( this . _drainTimer ) ; clearTimeout ( this . _toastTimer ) ; this . scanQueue = [ ] ; this . stopScanner ( ) } ,
scheduleScanDrain ( ) { clearTimeout ( this . _drainTimer ) ; if ( ! this . _pageActive ) return ; const epoch = this . _scanEpoch || 0 ; this . _drainTimer = setTimeout ( ( ) => { if ( this . _pageActive && epoch === ( this . _scanEpoch || 0 ) ) this . drainScanQueue ( ) } , 0 ) } ,
async run ( action , work ) { if ( this . busy || ! this . _pageActive ) throw new Error ( '当前页面未就绪,请返回作业页重试' ) ; 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 ; if ( this . _pageActive && this . _resumeReceiving ) { this . _resumeReceiving = false ; this . bootstrap ( ) } else this . scheduleScanDrain ( ) } } ,
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 restoreDomesticPhotoPreviews ( ) { await Promise . all ( this . domesticLoadingPhotos . filter ( ( photo ) => photo . downloadPath && ! photo . localPath ) . map ( ( photo ) => this . loadDomesticPhoto ( photo ) ) ) } ,
async loadDomesticPhoto ( photo ) { if ( ! photo ? . downloadPath || photo . status === 'loading' ) return ; photo . status = 'loading' ; try { photo . localPath = await downloadCurrentDomesticLoadingPhoto ( photo . downloadPath ) ; photo . status = 'success' } catch ( _ ) { photo . status = 'error' } } ,
async restorePhotoPreviews ( ) { await Promise . all ( receiving . photoSlots . filter ( ( photo ) => photo ? . downloadPath && ! photo . localPath ) . map ( async ( photo ) => { try { await restoreReceivingPhoto ( photo , downloadCurrentUnloadPhoto ) } catch ( error ) { this . error = error } } ) ) } ,
async restoreDomesticPhotoPreviews ( ) { if ( this . phase !== 'sign' && ! this . domesticPhotosExpanded ) return ; await Promise . all ( this . domesticLoadingPhotos . filter ( ( photo ) => photo . downloadPath && ! photo . localPath ) . map ( ( photo ) => this . loadDomesticPhoto ( photo ) ) ) } ,
async toggleDomesticPhotos ( ) { this . domesticPhotosExpanded = ! this . domesticPhotosExpanded ; if ( this . domesticPhotosExpanded ) await this . restoreDomesticPhotoPreviews ( ) } ,
async loadDomesticPhoto ( photo ) { if ( ! photo ? . downloadPath ) return ; try { await restoreReceivingPhoto ( photo , downloadCurrentDomesticLoadingPhoto ) } catch ( _ ) { photo . status = 'error' } } ,
async previewDomesticPhoto ( photo ) { if ( ! photo ? . localPath ) await this . loadDomesticPhoto ( photo ) ; if ( photo ? . localPath ) { const urls = this . domesticLoadingPhotos . map ( ( item ) => item . localPath ) . filter ( Boolean ) ; uni . previewImage ( { urls , current : photo . localPath } ) } } ,
async applyCurrentReceiving ( value ) { this . validateReceivingWarehouse ( value , '当前接柜会话' ) ; setCurrentReceiving ( value ) ; await Promise . all ( [ this . restorePhotoPreviews ( ) , this . restoreDomesticPhotoPreviews ( ) ] ) ; this . setScanTargetForCurrent ( value ) } ,
async applyCurrentReceiving ( value ) { this . validateReceivingWarehouse ( value , '当前接柜会话' ) ; setCurrentReceiving ( value , JSON . stringify ( [ session . tenantId , this . user ? . id , this . user ? . warehouseId ] ) ) ; if ( ! this . _pageActive ) return ; await Promise . all ( [ this . restorePhotoPreviews ( ) , this . restoreDomesticPhotoPreviews ( ) ] ) ; this . setScanTargetForCurrent ( value ) } ,
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 } } ,
@@ -198,10 +201,11 @@
async prepareContainer ( ) { if ( ! this . selected ? . containerNo || ! this . selected ? . sourceContainerBookingId || ! this . selected ? . allPlansReady || ! this . selected ? . operable || this . busy ) return ; const key = idempotencyKey ( 'prepare' , { sourceContainerBookingId : this . selected . sourceContainerBookingId , 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 , this . selected . sourceContainerBookingId , { 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 ) } } ) } ,
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 } ) } ,
invalidatePhotoPreview ( photo ) { if ( ! photo ) return ; photo . localPath = '' ; photo . status = 'error' ; photo . errorMessage = '照片加载失败,点按重试' } ,
async previewPhoto ( index ) { const photo = this . photoSlots [ index ] ; if ( ! photo ) return ; try { const path = await restoreReceivingPhoto ( photo , downloadCurrentUnloadPhoto ) ; if ( path ) uni . previewImage ( { urls : [ path ] , current : path } ) } catch ( error ) { this . error = error } } ,
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 = '' ; 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 } ) } } ,
clearReceiptContext ( ) { receiving . resolvedCarton = null ; receiving . resolvedPallet = null ; this . cartonInput = '' ; this . palletInput = '' ; this . packageCount = '' ; receiving . activeScanTarget = 'carton' ; this . scheduleScanDrain ( ) } ,
async resolveCarton ( raw ) { const cartonMark = String ( raw ? . detail ? . value ? ? raw ? ? this . cartonInput ) . trim ( ) ; if ( ! cartonMark || ! this . _pageActive ) return ; const scanEpoch = this . _scanEpoch || 0 ; 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 } ; if ( ! this . _pageActive || scanEpoch !== ( this . _scanEpoch || 0 ) ) throw new Error ( '页面已离开,请返回后重新扫码确认收货' ) ; 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 ( ) { const isFba = this . activeInbound ? . destinationType === 'FBA' ; if ( isFba && Number ( this . packageCount ) > MAX _PACKAGES _PER _PALLET ) { this . error = Object . assign ( new Error ( PALLET _CAPACITY _MESSAGE ) , { code : 'A0409' } ) ; playErrorSound ( ) ; this . showToast ( PALLET _CAPACITY _MESSAGE , 'error' ) ; return } if ( ! this . canSubmitReceipt ) return ; 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 ( _ ) { } } ,