@@ -199,13 +199,15 @@ const selectedTransferSourceOrderIds = ref<string[]>([])
const isReturnRelabelSource = computed ( ( ) => props . service === 'labeling' && selectedRelabelInboundOrderId . value . startsWith ( 'RETURN_ORDER:' ) )
const isReturnRelabelSource = computed ( ( ) => props . service === 'labeling' && selectedRelabelInboundOrderId . value . startsWith ( 'RETURN_ORDER:' ) )
const selectedReturnOrderNo = computed ( ( ) => isReturnRelabelSource . value ? selectedRelabelInboundOrderId . value . slice ( 'RETURN_ORDER:' . length ) : '' )
const selectedReturnOrderNo = computed ( ( ) => isReturnRelabelSource . value ? selectedRelabelInboundOrderId . value . slice ( 'RETURN_ORDER:' . length ) : '' )
const selectedReturnCandidates = computed ( ( ) => relabelCandidates . value . filter ( item => Boolean ( item . returnCartonId ) && returnSourceNos ( item ) . includes ( selectedReturnOrderNo . value ) ) )
const selectedReturnCandidates = computed ( ( ) => relabelCandidates . value . filter ( item => Boolean ( item . returnCartonId ) && returnSourceNos ( item ) . includes ( selectedReturnOrderNo . value ) ) )
const returnBoxCandidates = computed ( ( ) => selectedReturnCandidates . value . filter ( item => item . sourceType === 'RETURN_CARTON' ) )
const availableReturnCandidates = computed ( ( ) => selectedReturnCandidates . value . filter ( item => inventoryQuantity ( item . availablePackages ) > 0 ) . map ( item => ( { ... item , skuItems : ( item . skuItems || [ ] ) . filter ( sku => inventoryQuantity ( sku . quantity ) > 0 ) } ) ) )
const returnLooseCandidates = computed ( ( ) => selectedReturnCandidates . value . filter ( item => item . sourceType === 'RETURN_P ARCEL ' ) )
const returnBoxCandidates = computed ( ( ) => availableReturnCandidates . value . filter ( item => item . sourceType === 'RETURN_C ARTON ' ) )
const returnLooseCandidates = computed ( ( ) => availableReturnCandidates . value . filter ( item => item . sourceType === 'RETURN_PARCEL' ) )
const requiresBothTotalFiles = computed ( ( ) => ( props . service === 'labeling' || ( props . service === 'transfer' && form . relabel ) ) && labelingMode . value === 'carton_and_sku' )
const requiresBothTotalFiles = computed ( ( ) => ( props . service === 'labeling' || ( props . service === 'transfer' && form . relabel ) ) && labelingMode . value === 'carton_and_sku' )
function inventoryQuantity ( value : number | string | null | undefined ) {
function inventoryQuantity ( value : number | string | null | undefined ) {
const quantity = Number ( value )
const quantity = Number ( value )
return Number . isFinite ( quantity ) && quantity > 0 ? quantity : 0
return Number . isFinite ( quantity ) && quantity > 0 ? quantity : 0
}
}
const relabelInventorySearch = ref ( '' )
function returnSourceNos ( candidate : RelabelInventoryCandidate ) : string [ ] {
function returnSourceNos ( candidate : RelabelInventoryCandidate ) : string [ ] {
return [ ... new Set ( ( candidate . inboundOrderNo || '' ) . split ( ',' ) . map ( value => value . trim ( ) ) . filter ( Boolean ) ) ]
return [ ... new Set ( ( candidate . inboundOrderNo || '' ) . split ( ',' ) . map ( value => value . trim ( ) ) . filter ( Boolean ) ) ]
}
}
@@ -213,24 +215,40 @@ function returnRelabelProgress(candidate: RelabelInventoryCandidate) {
const labels : Record < string , string > = { UNLABELLED : '未换标' , BOUND : '处理中' , PROCESSING : '处理中' , PARTIALLY _COMPLETED : '部分完成' , COMPLETED : '全部完成' }
const labels : Record < string , string > = { UNLABELLED : '未换标' , BOUND : '处理中' , PROCESSING : '处理中' , PARTIALLY _COMPLETED : '部分完成' , COMPLETED : '全部完成' }
return [ labels [ candidate . relabelStatus || '' ] || '—' , '实际完成 ' + ( candidate . relabelledQuantity ? ? '—' ) + ' 件' , '处理中占用 ' + ( candidate . relabelReservedQuantity ? ? '—' ) + ' 件' ] . join ( '| ' )
return [ labels [ candidate . relabelStatus || '' ] || '—' , '实际完成 ' + ( candidate . relabelledQuantity ? ? '—' ) + ' 件' , '处理中占用 ' + ( candidate . relabelReservedQuantity ? ? '—' ) + ' 件' ] . join ( '| ' )
}
}
function returnInventoryState ( candidate : RelabelInventoryCandidate ) {
const labels : Record < string , string > = { AVAILABLE : '可选库存' , RELABEL _PENDING : '换标占用' , PACKING _RESERVED : '合箱计划预留' , OUTBOUND _PENDING : '出库占用' , DISCARD _PENDING : '废弃申请占用' , OUTBOUNDED : '已出库' , MERGED : '已合箱' }
return [ ... ( candidate . mergedIntoCartonMarks ? . length ? [ '历史部分转入 ' + candidate . mergedIntoCartonMarks . join ( '、' ) + '(不代表当前库存位置)' ] : [ ] ) , labels [ candidate . inventoryStatus || '' ] || ( inventoryQuantity ( candidate . availablePackages ) > 0 ? '可选库存' : '当前不可选' ) , candidate . occupiedRelabelNo ] . filter ( Boolean ) . join ( ' · ' )
}
const relabelInboundOptions = computed ( ( ) => {
const relabelInboundOptions = computed ( ( ) => {
const grouped = new Map < string , { value : string ; inboundOrderNo : string ; inboundCode : string ; availablePackages : number ; quantityUnit : ' 箱 ' | ' 件 ' } > ( )
const grouped = new Map < string , { value : string ; inboundOrderNo : string ; inboundCode : string ; availablePackages : number ; quantityUnit : ' 箱 ' | ' 件 ' ; stockQuantity : number | null ; units : number ; hasOutbound : boolean ; reasons : string [ ] } > ( )
for ( const candidate of relabelCandidates . value ) {
for ( const candidate of relabelCandidates . value ) {
const sources = candidate . returnCartonId ? returnSourceNos ( candidate ) : [ String ( candidate . inboundOrderId ) ]
const sources = candidate . returnCartonId ? returnSourceNos ( candidate ) : [ String ( candidate . inboundOrderId ) ]
for ( const sourceNo of sources ) {
for ( const sourceNo of sources ) {
const value = candidate . returnCartonId ? 'RETURN_ORDER:' + sourceNo : sourceNo
const value = candidate . returnCartonId ? 'RETURN_ORDER:' + sourceNo : sourceNo
const current = grouped . get ( value )
const current = grouped . get ( value )
if ( current ) current . availablePackages += inventoryQuantity ( candidate . availablePackages )
if ( current ) {
current . hasOutbound || = candidate . hasOutbound === true || candidate . inventoryStatus === 'OUTBOUNDED'
current . availablePackages += inventoryQuantity ( candidate . availablePackages )
current . units += candidate . returnCartonId && candidate . stockQuantity === 0 ? 0 : 1
current . stockQuantity = current . stockQuantity == null || candidate . stockQuantity == null ? null : current . stockQuantity + inventoryQuantity ( candidate . stockQuantity )
if ( candidate . returnCartonId ) current . reasons . push ( returnInventoryState ( candidate ) )
}
else grouped . set ( value , {
else grouped . set ( value , {
value , inboundOrderNo : candidate . returnCartonId ? '退货单 ' + sourceNo : ( candidate . inboundOrderNo || '—' ) ,
value , inboundOrderNo : candidate . returnCartonId ? '退货单 ' + sourceNo : ( candidate . inboundOrderNo || '—' ) ,
inboundCode : candidate . returnCartonId ? '已签收退货库存(含合箱)' : ( candidate . inboundCode || '—' ) ,
inboundCode : candidate . returnCartonId ? '已签收退货库存(含合箱)' : ( candidate . inboundCode || '—' ) ,
availablePackages : inventoryQuantity ( candidate . availablePackages ) ,
availablePackages : inventoryQuantity ( candidate . availablePackages ) ,
quantityUnit : candidate . returnCartonId ? '件' : '箱' ,
quantityUnit : candidate . returnCartonId ? '件' : '箱' ,
stockQuantity : candidate . stockQuantity == null ? null : inventoryQuantity ( candidate . stockQuantity ) , units : candidate . returnCartonId && candidate . stockQuantity === 0 ? 0 : 1 , hasOutbound : candidate . hasOutbound === true || candidate . inventoryStatus === 'OUTBOUNDED' , reasons : candidate . returnCartonId ? [ returnInventoryState ( candidate ) ] : [ ] ,
} )
} )
}
}
}
}
return [ ... grouped . values ( ) ]
return [ ... grouped . values ( ) ] . map ( option => ( { ... option , reasons : [ ... new Set ( option . reasons ) ] } ) )
} )
} )
const visibleRelabelInboundOptions = computed ( ( ) => relabelInboundOptions . value . filter ( option => option . availablePackages > 0 ) )
function relabelOptionLabel ( option : typeof relabelInboundOptions . value [ number ] ) {
if ( option . quantityUnit === '箱' ) return option . inboundOrderNo + '| ' + option . inboundCode + '|可选 ' + option . availablePackages + ' 箱'
return option . inboundOrderNo + '|在仓 ' + option . units + ' 包裹/箱 · ' + ( option . stockQuantity ? ? '待查询' ) + ' 件' + ( option . hasOutbound && Number ( option . stockQuantity ) > 0 ? '|部分出库' : '' )
}
const loading = ref ( false )
const loading = ref ( false )
const saving = ref ( false )
const saving = ref ( false )
const cartonTemplateInput = ref < HTMLInputElement | null > ( null )
const cartonTemplateInput = ref < HTMLInputElement | null > ( null )
@@ -1480,7 +1498,7 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
< label v-if = "props.service === 'relabel'"><span><i>*</i>来源 FBA 仓库</span><el-select v-model="form.sourceWarehouse" :loading="fbaWarehousesLoading" :disabled="Boolean(fbaWarehousesError)" placeholder="请选择平台维护的 FBA 仓"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" / > < / el-select > < / label >
< label v-if = "props.service === 'relabel'"><span><i>*</i>来源 FBA 仓库</span><el-select v-model="form.sourceWarehouse" :loading="fbaWarehousesLoading" :disabled="Boolean(fbaWarehousesError)" placeholder="请选择平台维护的 FBA 仓"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" / > < / el-select > < / label >
< label v-else-if = "props.service === 'disposal'" class="warehouse-disposal-field"><span><i>*</i>废弃原因</span><el-select v-model="form.reason" placeholder="请选择"><el-option label="客户退货" value="客户退货" /><el-option label="亚马逊移除" value="亚马逊移除" /><el-option label="残损不可售" value="残损不可售" / > < / el-select > < / label >
< label v-else-if = "props.service === 'disposal'" class="warehouse-disposal-field"><span><i>*</i>废弃原因</span><el-select v-model="form.reason" placeholder="请选择"><el-option label="客户退货" value="客户退货" /><el-option label="亚马逊移除" value="亚马逊移除" /><el-option label="残损不可售" value="残损不可售" / > < / el-select > < / label >
< label v-else > < span > < i > * < / i > { { props . service === 'labeling' ? '海外操作仓(中转仓)' : props . service === 'transfer' ? '入库海外仓' : '操作仓' } } < / span > < el-select v-model = "form.warehouse" :loading="fbaWarehousesLoading || operationWarehouseDetailLoading" :disabled="Boolean(operationWarehousesError)" clearable :placeholder="props.service === 'transfer' ? '请选择海外仓' : '请选择国外中转仓'" @change="props.service === 'labeling' ? selectOperationWarehouse() : props.service === 'transfer' && selectTransferWarehouse()"><el-option v-for="warehouse in operationWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="String(warehouse.id)" /></el-select><small v-if="operationWarehousesError" class="warehouse-hint" > {{ operationWarehousesError }} < / small > < / label >
< label v-else > < span > < i > * < / i > { { props . service === 'labeling' ? '海外操作仓(中转仓)' : props . service === 'transfer' ? '入库海外仓' : '操作仓' } } < / span > < el-select v-model = "form.warehouse" :loading="fbaWarehousesLoading || operationWarehouseDetailLoading" :disabled="Boolean(operationWarehousesError)" clearable :placeholder="props.service === 'transfer' ? '请选择海外仓' : '请选择国外中转仓'" @change="props.service === 'labeling' ? selectOperationWarehouse() : props.service === 'transfer' && selectTransferWarehouse()"><el-option v-for="warehouse in operationWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="String(warehouse.id)" /></el-select><small v-if="operationWarehousesError" class="warehouse-hint" > {{ operationWarehousesError }} < / small > < / label >
< label v-if = "props.service === 'labeling'" class="warehouse-span-2"><span><i>*</i>选择有库存的 入仓单或退货单</span><el-select v-model="selectedRelabelInboundOrderId" filterable clearable :loading="relabelCandidatesLoading" :disabled="!form.warehouse" no-data-text="当前中转仓暂无可用 库存" placeholder="请选择当前仍有可用库存的入仓单或退货单 " @change="selectRelabelInventory"><el-option v-for="option in r elabelInboundOptions" :key="option.value" :label="`${option.inboundOrderNo}| ${option.inboundCode}|可用 ${option.availablePackages} ${option.quantityUnit}`" :value="option.value" / > < / el-select > < / label >
< label v-if = "props.service === 'labeling'" class="warehouse-span-2"><span><i>*</i>选择入仓单或退货单</span><el-select v-model="selectedRelabelInboundOrderId" filterable clearable :filter-method="(value: string) => relabelInventorySearch = value" :loading="relabelCandidatesLoading" :disabled="!form.warehouse" no-data-text="暂无匹配的可选 库存" placeholder="搜索单号或选择可用库存 " @change="selectRelabelInventory"><el-option v-for="option in visibleR elabelInboundOptions.filter(item => !relabelInventorySearch.trim() || item.inboundOrderNo.toUpperCase().includes(relabelInventorySearch.trim().toUpperCase()) || item.inboundCode.toUpperCase().includes(relabelInventorySearch.trim().toUpperCase()))" :key="option.value" :label="relabelOptionLabel(option)" :value="option.value" :disabled="option.availablePackages <= 0" /></el-select><small class="warehouse-hint" > 在仓数量包含被占用货物 ; 本次可选数量才可加入申请 。 合箱后的库存按当前包裹 / 箱展示 , 来源退货单保留 。 < / small > < / label >
< label v-if = "props.service === 'transfer'"><span>库存转运来源(可选)</span><el-select v-model="selectedTransferInventory" filterable clearable :loading="relabelCandidatesLoading" :disabled="!form.warehouse" no-data-text="当前海外仓暂无可用库存" placeholder="不选则按新货转运;选择后免 PDA 测量" @change="selectTransferInventory"><el-option v-for="option in transferInventoryOptions" :key="option.value" :label="`${option.label}|可用 ${option.packages} 箱`" :value="option.value" /></el-select><small class="warehouse-hint" > 库存转运可直接进入目的仓 、 换标和排车流程 , PDA 可拆托 、 重打或同目的仓合托 。 < / small > < / label >
< label v-if = "props.service === 'transfer'"><span>库存转运来源(可选)</span><el-select v-model="selectedTransferInventory" filterable clearable :loading="relabelCandidatesLoading" :disabled="!form.warehouse" no-data-text="当前海外仓暂无可用库存" placeholder="不选则按新货转运;选择后免 PDA 测量" @change="selectTransferInventory"><el-option v-for="option in transferInventoryOptions" :key="option.value" :label="`${option.label}|可用 ${option.packages} 箱`" :value="option.value" /></el-select><small class="warehouse-hint" > 库存转运可直接进入目的仓 、 换标和排车流程 , PDA 可拆托 、 重打或同目的仓合托 。 < / small > < / label >
< label v-if = "props.service === 'labeling' && (isReturnRelabelSource || labelingMode !== 'sku')"><span><i>*</i>FBA 目的仓</span><el-select v-model="form.destination" :loading="fbaWarehousesLoading" :disabled="Boolean(fbaWarehousesError)" filterable placeholder="请选择本张申请统一的 FBA 目的仓"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" / > < / el-select > < / label >
< label v-if = "props.service === 'labeling' && (isReturnRelabelSource || labelingMode !== 'sku')"><span><i>*</i>FBA 目的仓</span><el-select v-model="form.destination" :loading="fbaWarehousesLoading" :disabled="Boolean(fbaWarehousesError)" filterable placeholder="请选择本张申请统一的 FBA 目的仓"><el-option v-for="warehouse in fbaWarehouses" :key="warehouse.id" :label="warehouseLabel(warehouse)" :value="warehouse.warehouseCode" / > < / el-select > < / label >
< div v-if = "props.service === 'transfer'" class="warehouse-choice-field warehouse-span-2"><span><i>*</i>入仓方式</span><el-radio-group v-model="form.deliveryMethod" @change="selectTransferInboundMethod"><el-radio-button value="WAREHOUSE_PICKUP">仓库上门提货</el-radio-button><el-radio-button value="CUSTOMER_DELIVERY" > 客户送货到仓 < / el -radio -button > < / el-radio-group > < / div >
< div v-if = "props.service === 'transfer'" class="warehouse-choice-field warehouse-span-2"><span><i>*</i>入仓方式</span><el-radio-group v-model="form.deliveryMethod" @change="selectTransferInboundMethod"><el-radio-button value="WAREHOUSE_PICKUP">仓库上门提货</el-radio-button><el-radio-button value="CUSTOMER_DELIVERY" > 客户送货到仓 < / el -radio -button > < / el-radio-group > < / div >
@@ -1548,11 +1566,11 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
< div v-if = "isReturnRelabelSource" class="warehouse-table-scroll" >
< div v-if = "isReturnRelabelSource" class="warehouse-table-scroll" >
< template v-if = "returnLooseCandidates.length" >
< template v-if = "returnLooseCandidates.length" >
< div class = "warehouse-section-heading" > < h3 > 可用包裹 < / h3 > < el-button text type = "primary" native -type = " button " :aria-expanded = "!availableSkusCollapsed" aria -controls = " return -available -skus " @click ="availableSkusCollapsed = !availableSkusCollapsed" > {{ availableSkusCollapsed ? ' 展开明细 ' : ' 收起明细 ' }} < / el -button > < / div >
< div class = "warehouse-section-heading" > < h3 > 可用包裹 < / h3 > < el-button text type = "primary" native -type = " button " :aria-expanded = "!availableSkusCollapsed" aria -controls = " return -available -skus " @click ="availableSkusCollapsed = !availableSkusCollapsed" > {{ availableSkusCollapsed ? ' 展开明细 ' : ' 收起明细 ' }} < / el -button > < / div >
< table v-show = "!availableSkusCollapsed" id="return-available-skus" class="warehouse-edit-table"><thead><tr><th>包裹识别码</th><th>来源退货单</th><th>SKU</th><th>可用数量(件)</th><th>换标进度</th><th> 操作</th></tr></thead><tbody><template v-for="source in returnLooseCandidates" :key="source.returnCartonId"><tr v-for="sku in source.skuItems" :key="sku.skuCode"><td>{{ source.cartonMark }}</td><td>{{ source.inboundOrderNo }}</td><td>{{ sku.skuCode }}</td><td>{{ sku.quantity }}</td><td>{{ returnRelabelProgress(source) }}</td><td><el-button size="small " @click="labelingMode === 'sku' ? addReturnSkuBinding(source, sku.skuCode) : addReturnBoxBinding(source)">添加贴标</el-button></td></tr></template><tr v-if="!returnLooseCandidates.length"><td colspan="6 " > 暂无可用包裹库存 < / td > < / tr > < / tbody > < / table >
< table v-show = "!availableSkusCollapsed" id="return-available-skus" class="warehouse-edit-table"><thead><tr><th>包裹识别码</th><th>来源退货单</th><th>SKU</th><th>可用数量(件)</th><th>操作</th></tr></thead><tbody><template v-for="source in returnLooseCandidates" :key="source.returnCartonId"><tr v-for="sku in source.skuItems" :key="sku.skuCode"><td>{{ source.cartonMark }}</td><td>{{ source.inboundOrderNo }}</td><td>{{ sku.skuCode }}</td><td>{{ sku.quantity }}</td><td><el-button size="small" :disabled="inventoryQuantity(source.availablePackages) <= 0 " @click="labelingMode === 'sku' ? addReturnSkuBinding(source, sku.skuCode) : addReturnBoxBinding(source)">添加贴标</el-button></td></tr></template><tr v-if="!returnLooseCandidates.length"><td colspan="5 " > 暂无可用包裹库存 < / td > < / tr > < / tbody > < / table >
< / template >
< / template >
< template v-if = "returnBoxCandidates.length" >
< template v-if = "returnBoxCandidates.length" >
< div class = "warehouse-section-heading" > < h3 > 可用箱子 ( { { returnBoxCandidates . length } } 箱 ) < / h3 > < el-button text type = "primary" native -type = " button " :aria-expanded = "!availableBoxesCollapsed" aria -controls = " return -available -boxes " @click ="availableBoxesCollapsed = !availableBoxesCollapsed" > {{ availableBoxesCollapsed ? ' 展开明细 ' : ' 收起明细 ' }} < / el -button > < / div >
< div class = "warehouse-section-heading" > < h3 > 可用箱子 ( { { returnBoxCandidates . length } } 箱 ) < / h3 > < el-button text type = "primary" native -type = " button " :aria-expanded = "!availableBoxesCollapsed" aria -controls = " return -available -boxes " @click ="availableBoxesCollapsed = !availableBoxesCollapsed" > {{ availableBoxesCollapsed ? ' 展开明细 ' : ' 收起明细 ' }} < / el -button > < / div >
< table v-show = "!availableBoxesCollapsed" id="return-available-boxes" class="warehouse-edit-table"><thead><tr><th>仓库箱识别码</th><th>来源退货单</th><th>箱内 SKU</th><th>可用数量(件)</th><th>换标进度</th><th> 操作</th></tr></thead><tbody><tr v-for="source in returnBoxCandidates" :key="source.returnCartonId"><td><strong>{{ source.cartonMark }}</strong></td><td>{{ source.inboundOrderNo }}</td><td><div v-for="sku in source.skuItems" :key="sku.skuCode">{{ sku.skuCode }}</div></td><td><div v-for="sku in source.skuItems" :key="sku.skuCode">{{ sku.quantity }}</div></td><td>{{ returnRelabelProgress(source) }}</td><td><el-button size="small " @click="addReturnBoxBinding(source)">添加贴标</el-button></td></tr><tr v-if="!returnBoxCandidates.length"><td colspan="6 " > 暂无可用箱子库存 < / td > < / tr > < / tbody > < / table >
< table v-show = "!availableBoxesCollapsed" id="return-available-boxes" class="warehouse-edit-table"><thead><tr><th>仓库箱识别码</th><th>来源退货单</th><th>箱内 SKU</th><th>可用数量(件)</th><th>操作</th></tr></thead><tbody><tr v-for="source in returnBoxCandidates" :key="source.returnCartonId"><td><strong>{{ source.cartonMark }}</strong></td><td>{{ source.inboundOrderNo }}</td><td><div v-for="sku in source.skuItems" :key="sku.skuCode">{{ sku.skuCode }}</div></td><td><div v-for="sku in source.skuItems" :key="sku.skuCode">{{ sku.quantity }}</div></td><td><el-button size="small" :disabled="inventoryQuantity(source.availablePackages) <= 0 " @click="addReturnBoxBinding(source)">添加贴标</el-button></td></tr><tr v-if="!returnBoxCandidates.length"><td colspan="5 " > 暂无可用箱子库存 < / td > < / tr > < / tbody > < / table >
< / template >
< / template >
< / div >
< / div >
< div class = "warehouse-labeling-mode" >
< div class = "warehouse-labeling-mode" >