@@ -12,6 +12,7 @@ import { prepareTransferCartonLabels } from '../utils/transferCartonPrinting'
import CompletionConfirmationDialog from '../components/CompletionConfirmationDialog.vue'
import ReviewRejectionNotice from '../components/ReviewRejectionNotice.vue'
import { returnRelabelCandidate } from '../utils/returnRelabelCandidates'
import { groupDiscardDocuments } from '../utils/discardDocuments'
import {
createDiscard , createRelabel , createTransfer , getDiscard , getRelabel , getTransfer , resubmitRelabel ,
getTransferCartonLabel , submitTransfer , updateTransfer , increaseTransferForecastCartons , getTransferSourceCartons , type TransferSourceCarton ,
@@ -73,8 +74,8 @@ async function refreshAfterCompletion(no: string) {
}
const canCancelDiscard = computed ( ( ) => auth . hasPermission ( 'merchant:overseas-warehouse:discard:cancel' ) )
const configs : Record < ServiceType , ServiceConfig > = {
labeling : { title : '贴标申请' , createTitle : '新增贴标申请' , numberLabel : '贴标申请单号' , emptyText : '暂无贴标申请' , columns : [ '操作' , '贴标申请单号' , '关联业务单号' , '海外操作仓' , 'SKU 数(种)' , '总箱数/包裹' , '换标类型' , '状态' , '审核时间' , '贴标完成时间' , '贴标出库时间' , '创建时间' ] } ,
transfer : { title : '仓储转运单' , createTitle : '新增仓储转运单' , numberLabel : '转运单号' , emptyText : '暂无仓储转运订单' , columns : [ '操作' , '转运单号' , '预计箱数' , '海外仓' , 'FBA 目的仓' , '入仓方式' , '是否换标' , '换标单号' , '换标状态' , '状态' , '审核时间' , '测量时间' , '出库时间' , '创建时间' ] } ,
labeling : { title : '贴标申请' , createTitle : '新增贴标申请' , numberLabel : '贴标申请单号' , emptyText : '暂无贴标申请' , columns : [ '操作' , '贴标申请单号' , '关联业务单号' , '海外操作仓' , 'SKU 数(种)' , '总箱数/包裹' , '换标类型' , '状态' , '创建时间' ] } ,
transfer : { title : '仓储转运单' , createTitle : '新增仓储转运单' , numberLabel : '转运单号' , emptyText : '暂无仓储转运订单' , columns : [ '操作' , '转运单号' , '预计箱数' , '海外仓' , 'FBA 目的仓' , '入仓方式' , '是否换标' , '换标单号' , '换标状态' , '状态' , '创建时间' ] } ,
disposal : { title : '货物废弃' , createTitle : '新增货物废弃申请' , numberLabel : '废弃申请单号' , emptyText : '暂无货物废弃申请' , columns : [ '操作' , '废弃申请单号' , '废弃出库单号' , '进仓编号' , '操作仓' , 'SKU 汇总' , '废弃数量' , '预估费用' , '状态' , '废弃完成时间' , '创建时间' ] } ,
relabel : { title : '退货换标' , createTitle : '新建退货换标单' , numberLabel : '退货换标单号' , emptyText : '暂无退货换标订单' , columns : [ '操作' , '退货换标单号' , '来源 FBA 仓' , '目的 FBA 仓' , 'SKU 汇总' , '退件总数' , '是否换标' , '换标状态' , '尾程状态' , '审核时间' , '测量时间' , '出库时间' , '创建时间' ] } ,
}
@@ -259,21 +260,11 @@ const listQueryFields = computed<QueryField[]>(() => {
if ( props . service === 'transfer' || props . service === 'relabel' ) fields . push ( { key : 'destination' , label : '目的 FBA 仓' , type : 'input' , placeholder : '请输入仓代码' } )
fields . push ( { key : 'status' , label : '订单状态' , type : 'select' , options : statusOptions . value } )
fields . push ( { key : 'createdDates' , label : '创建时间' , type : 'daterange' } )
if ( props . service === 'labeling' ) fields . push (
{ key : 'reviewDates' , label : '审核时间' , type : 'daterange' } ,
{ key : 'completedDates' , label : '贴标完成时间' , type : 'daterange' } ,
{ key : 'outboundDates' , label : '贴标出库时间' , type : 'daterange' } ,
)
if ( props . service === 'relabel' ) fields . push (
{ key : 'reviewDates' , label : '审核时间' , type : 'daterange' } ,
{ key : 'measuredDates' , label : '测量时间' , type : 'daterange' } ,
{ key : 'outboundDates' , label : '出库时间' , type : 'daterange' } ,
)
if ( props . service === 'transfer' ) fields . push (
{ key : 'reviewDates' , label : '审核时间' , type : 'daterange' } ,
{ key : 'completedDates' , label : '测量时间' , type : 'daterange' } ,
{ key : 'outboundDates' , label : '出库时间' , type : 'daterange' } ,
)
return fields
} )
const listColumns = computed < DataTableColumn [ ] > ( ( ) => config . value . columns . map ( ( label , index ) => index === 0
@@ -284,13 +275,9 @@ const listColumns = computed<DataTableColumn[]>(() => config.value.columns.map((
const discardSelectedBoxKeys = ref < string [ ] > ( [ ] )
const discardBoxSearch = ref ( '' )
const discardCandidates = ref < import ( ' .. / api / warehouseServices ' ) .DiscardCandidate [ ] > ( [ ] )
const discardSelectedCandidate = computed ( ( ) => discardCandidates . value . find ( item => item . orderId === selectedDiscardOrderId . value ) )
const discardSelectionSources = computed ( ( ) => {
const current = discardSelectedCandidate . value
if ( ! current ) return [ ]
if ( current . sourceType !== 'RETURN_CARTON' ) return [ current ]
return discardCandidates . value . filter ( item => item . sourceType === current . sourceType && item . inboundCode === current . inboundCode && item . overseasWarehouseId === current . overseasWarehouseId )
} )
const discardDocuments = computed ( ( ) => groupDiscardDocuments ( discardCandidates . value ) )
const discardSelectedCandidate = computed ( ( ) => discardDocuments . value . find ( item => item . orderId === selectedDiscardOrderId . value ) )
const discardSelectionSources = computed ( ( ) => discardSelectedCandidate . value ? . sources || [ ] )
const discardSelectableBoxes = computed ( ( ) => discardSelectionSources . value . flatMap ( source => ( source . cartons || [ ] ) . map ( box => ( { ... box , key : ` ${ source . sourceType } : ${ box . cartonId } ` , source } ) ) ) )
const discardVisibleBoxes = computed ( ( ) => discardSelectableBoxes . value . filter ( box => ! discardBoxSearch . value . trim ( ) || box . cartonMark . includes ( discardBoxSearch . value . trim ( ) ) || box . skus . some ( sku => sku . skuCode . includes ( discardBoxSearch . value . trim ( ) ) ) ) )
function toggleDiscardBox ( key : string , selected : boolean ) {
@@ -992,17 +979,17 @@ function importSelectedBindingsToDetails() {
function selectDiscardInbound ( orderId : string ) {
discardSelectedBoxKeys . value = [ ]
discardBoxSearch . value = ''
const candidate = discardCandidates . value . find ( ( item ) => item . orderId === orderId )
const candidate = discardDocuments . value . find ( ( item ) => item . orderId === orderId )
if ( ! candidate ) { cargoRows . value = [ ] ; return }
form . inboundNo = candidate . inboundCode || candidate . inboundOrderNo
form . warehouse = candidate . overseasWarehouseId
if ( candidate . cartonSelectionSupported ) { selectAllDiscardBoxes ( true ) ; return }
cargoRows . value = candidate . skus . map ( ( sku ) => ( {
cargoRows . value = candidate . sources . flatMap ( source => source . skus . map ( ( sku ) => ( {
id : ++ cargoSequence , sku : sku . skuCode , name : sku . productName , quantity : sku . availablePackages ,
availableQuantity : sku . availablePackages , sourceOrderId : candidate . orderId , sourceType : candidate . sourceType ,
returnCartonId : candidate . returnCartonId , sourceInventoryPositionId : candidate . inventoryPositionId , palletNo : candidate . palletNo , cartonMark : candidate . cartonMark || '' ,
availableQuantity : sku . availablePackages , sourceOrderId : source . orderId , sourceType : source . sourceType ,
returnCartonId : source . returnCartonId , sourceInventoryPositionId : source . inventoryPositionId , palletNo : source . palletNo , cartonMark : source . cartonMark || '' ,
fnsku : '' , shipmentNo : '' , newSkuCode : '' , shippingMark : '' , customerCartonMarks : '' , fbaCartonMark : '' , destinationWarehouseId : '' , remark : '' ,
} ) )
} ) ) )
}
function dateTime ( value : string , end = false ) { return value ? ` ${ value } T ${ end ? '23:59:59' : '00:00:00' } ` : undefined }
function row ( values : Array < string | number | undefined | null > ) { return Object . fromEntries ( values . map ( ( value , index ) => [ String ( index ) , value == null || value === '' ? '—' : String ( value ) ] ) ) }
@@ -1132,7 +1119,7 @@ async function load() {
total . value = Number . isFinite ( Number ( result . total ) ) ? Number ( result . total ) : result . records . length
submittedRows . value = result . records . map ( ( item ) => {
const destinationWarehouses = warehouseDisplay ( item . overseasWarehouseId )
return { ... row ( [ item . relabelNo , ( item . relatedBusinessOrderNos || [ ] ) . join ( '、' ) || '—' , destinationWarehouses , new Set ( item . skuCodes . split ( ',' ) . map ( code => code . trim ( ) ) . filter ( Boolean ) ) . size , item . totalPackageCount , ( item . operationTypes || [ ] ) . map ( type => ( { CARTON _LABEL : '只换箱唛' , SKU _LABEL : '只换 SKU' , RETURN _BULK _SKU _LABEL : '只换 SKU' , CARTON _AND _SKU : 'SKU 和箱唛都换' } [ type ] || type ) ) . join ( '、' ) || '—' , statusLabel ( item . status ) , formatDateTime ( item . reviewedAt ) , formatDateTime ( item . relabelCompletedAt ) , formatDateTime ( item . outboundAt ) , formatDateTime ( item . createdAt ) ] ) , _ _status : item . status , _ _confirmedAt : item . customerConfirmedAt || '' }
return { ... row ( [ item . relabelNo , ( item . relatedBusinessOrderNos || [ ] ) . join ( '、' ) || '—' , destinationWarehouses , new Set ( item . skuCodes . split ( ',' ) . map ( code => code . trim ( ) ) . filter ( Boolean ) ) . size , item . totalPackageCount , ( item . operationTypes || [ ] ) . map ( type => ( { CARTON _LABEL : '只换箱唛' , SKU _LABEL : '只换 SKU' , RETURN _BULK _SKU _LABEL : '只换 SKU' , CARTON _AND _SKU : 'SKU 和箱唛都换' } [ type ] || type ) ) . join ( '、' ) || '—' , statusLabel ( item . status ) , formatDateTime ( item . createdAt ) ] ) , _ _status : item . status , _ _confirmedAt : item . customerConfirmedAt || '' }
} )
} else if ( props . service === 'disposal' ) {
const result = await listDiscards ( { page : page . value , size : pageSize . value , discardNo : filters . number || undefined , inboundCode : filters . inboundNo || undefined , overseasWarehouseId : filters . warehouse || undefined , status : filters . status || undefined , createdFrom : dateTime ( filters . start ) , createdTo : dateTime ( filters . end , true ) , completedFrom : dateTime ( filters . completedStart ) , completedTo : dateTime ( filters . completedEnd , true ) } )
@@ -1145,7 +1132,7 @@ async function load() {
const operationWarehouse = warehouseDisplay ( item . operatingWarehouseId )
const relabelStatus = transferRelabelStatus ( item )
return props . service === 'transfer'
? { ... row ( [ item . transferNo , item . packageCount , operationWarehouse , item . fbaWarehouseCode , deliveryMethodLabel ( item . deliveryMethod ) , item . needRelabel ? '是' : '否' , item . relabelNo , relabelStatus . label , statusLabel ( item . status ) , formatDateTime ( item . reviewedAt ) , formatDateTime ( item . measuredAt ) , formatDateTime ( item . outboundAt ) , formatDateTime ( item . createdAt ) ] ) , _ _status : item . status , _ _statusLabel : statusLabel ( item . status ) , _ _relabelStatus : relabelStatus . code , _ _relabelStatusLabel : relabelStatus . label }
? { ... row ( [ item . transferNo , item . packageCount , operationWarehouse , item . fbaWarehouseCode , deliveryMethodLabel ( item . deliveryMethod ) , item . needRelabel ? '是' : '否' , item . relabelNo , relabelStatus . label , statusLabel ( item . status ) , formatDateTime ( item . createdAt ) ] ) , _ _status : item . status , _ _statusLabel : statusLabel ( item . status ) , _ _relabelStatus : relabelStatus . code , _ _relabelStatusLabel : relabelStatus . label }
: row ( [ item . transferNo , '—' , item . fbaWarehouseCode , '—' , item . packageCount , '是' , item . relabelNo ? '已生成' : '待生成' , statusLabel ( item . status ) , formatDateTime ( item . reviewedAt ) , formatDateTime ( item . measuredAt ) , formatDateTime ( item . outboundAt ) , formatDateTime ( item . createdAt ) ] )
} )
}
@@ -1694,7 +1681,6 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
< div class = "warehouse-card warehouse-table-card" >
< DataTable v -model :page = "page" v -model :page-size = "pageSize" :data = "submittedRows" :columns = "listColumns" :loading = "loading" :total = "total" : page -sizes = " [ 15 , 30 , 50 , 100 ] " :row-actions = "listRowActions" :empty-text = "config.emptyText" >
< template # toolbar -actions > < el-button v-if = "canCreate" type="primary" @click="openCreate"><Plus :size="16" / > { { props . service === 'relabel' ? '新建退货换标单' : props . service === 'transfer' ? '新增订单' : ` 新增 ${ config . title } ` } } < / el-button > < / template >
< template v-if = "props.service === 'transfer' && submittedRows.some((item) => item.__status === 'WAIT_CUSTOMER_CONFIRMATION')" #before-table><div class="warehouse-list-notice" > 部分入库完成订单已有 PDA 实测数据 , 请在对应行确认测量数据 。 < / div > < / template >
< template # transferRelabelStatus = "{ row }" > < el-tag :type = "transferRelabelStatusTagType(row.__relabelStatus)" effect = "light" > { { row . _ _relabelStatusLabel } } < / el-tag > < / template >
< template # labelingType = "{ row }" > < span v-if = "row['5'] === '—'">—</span><span v-else class="labeling-type-tags"><el-tag v-for="label in labelingTypeLabels(row['5'])" :key="label" :type="labelingTypeTagType(label)" size="small" effect="light" > {{ label }} < / el -tag > < / span > < / template >
< template # labelingStatus = "{ row }" > < el-tag :type = "labelingStatusTagType(row.__status)" size = "small" effect = "light" > { { statusLabel ( row . _ _status ) } } < / el-tag > < / template >
@@ -1718,7 +1704,7 @@ function addReturnBoxBinding(source: RelabelInventoryCandidate) {
< / div >
< div v-show = "props.service !== 'labeling' || !orderInfoCollapsed" class="warehouse-form-grid" >
< label v-if = "props.service === 'disposal'" class="warehouse-disposal-field"><span><i>*</i>操作仓</span><el-select v-model="form.warehouse" filterable clearable :loading="fbaWarehousesLoading" :disabled="Boolean(operationWarehousesError)" placeholder="请选择海外仓" @change="selectDiscardWarehouse"><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><small v-else class="warehouse-hint" > 请先选择本次废弃货物所在的海外仓 。 < / small > < / label >
< label v-if = "props.service === 'disposal'" class="warehouse-disposal-field"><span><i>*</i>选择入库单</span><el-select v-model="selectedDiscardOrderId" filterable clearable :loading="discardCandidatesLoading" :disabled="!form.warehouse" no-data-text="当前海外仓暂无可废弃库存" placeholder="请先选择操作仓,再选择有可用库存的入库单" @change="selectDiscardInbound"><el-option v-for="candidate in discardCandidate s" :key="candidate.orderId" :label="candidate.sourceType === 'RETURN_CARTON' ? ` 退货|${candidate.inboundOrderNo || '—'}| ${candidate.cartonMark || '— '}|可用 ${candidate.availablePackages}` : ` ${candidate.inboundOrderNo || candidate.inboundCode || '—'}| ${candidate.inboundCode || '—'}| ${candidate.palletNo || '散箱'}| 可用 ${candidate.availablePackages} 箱`" :value="candidate.orderId" /></el-select><small class="warehouse-hint" > 显示当前操作仓内有可用库存的单据 ; 带出箱唛后可勾选部分箱子 , 按整箱废弃 。 < / small > < / label >
< label v-if = "props.service === 'disposal'" class="warehouse-disposal-field"><span><i>*</i>选择入库单</span><el-select v-model="selectedDiscardOrderId" filterable clearable :loading="discardCandidatesLoading" :disabled="!form.warehouse" no-data-text="当前海外仓暂无可废弃库存" placeholder="请先选择操作仓,再选择有可用库存的入库单" @change="selectDiscardInbound"><el-option v-for="candidate in discardDocument s" :key="candidate.orderId" :label="`${ candidate.sourceType === 'RETURN_CARTON' ? ' 退货|' : ''}${candidate.inboundOrderNo || candidate.inboundCode || '—'}|可用 ${candidate.availablePackages} 箱`" :value="candidate.orderId" /></el-select><small class="warehouse-hint" > 显示当前操作仓内有可用库存的单据 ; 带出箱唛后可勾选部分箱子 , 按整箱废弃 。 < / small > < / label >
< label v-if = "props.service === 'relabel'"><span><i>*</i>进仓编号</span><el-input v-model="form.inboundNo" placeholder="请输入已有入仓编号" / > < / 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 >