This commit is contained in:
2026-10-19 15:59:04 +08:00
parent 34650b06df
commit dcdaf1438c
4 changed files with 93 additions and 30 deletions
@@ -0,0 +1,38 @@
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
import ts from 'typescript'
const compiled = ts.transpileModule(readFileSync(new URL('./discardDocuments.ts', import.meta.url), 'utf8'), { compilerOptions: { module: ts.ModuleKind.ESNext, target: ts.ScriptTarget.ES2022 } }).outputText
const { groupDiscardDocuments } = await import(`data:text/javascript;base64,${Buffer.from(compiled).toString('base64')}`)
const returned = (id, warehouse = 'WH', document = 'TT-BYWL-261019152551') => ({
orderId: `return:${id}`, returnCartonId: id, sourceType: 'RETURN_CARTON', overseasWarehouseId: warehouse,
inboundCode: document, inboundOrderNo: document, availablePackages: 5, cartonSelectionSupported: true,
cartons: [{ cartonId: id, cartonMark: `BOX-${id}`, skus: [{ skuCode: 'A', availablePackages: 5 }] }], skus: [{ skuCode: 'A', availablePackages: 5 }],
})
test('同单两箱合并成一个选项,可用量为两箱而非十件,保留原始提交身份', () => {
const sources = [returned('1'), returned('2')]
const result = groupDiscardDocuments(sources)
assert.equal(result.length, 1)
assert.equal(result[0].availablePackages, 2)
assert.deepEqual(result[0].sources, sources)
assert.equal(result[0].cartonSelectionSupported, true)
})
test('不同单据或操作仓独立展示,混装SKU不重复计算物理箱', () => {
assert.equal(groupDiscardDocuments([returned('1'), returned('2', 'OTHER'), returned('3', 'WH', 'TT-OTHER')]).length, 3)
const source = { ...returned('1'), sourceType: 'INVENTORY', orderId: 'inventory:1', inventoryPositionId: '1', availablePackages: 10 }
const second = { ...source, orderId: 'inventory:2', inventoryPositionId: '2', cartons: source.cartons }
assert.equal(groupDiscardDocuments([source, second])[0].availablePackages, 1)
})
test('缺少箱级记录时合并仓位可用箱数并保持全部废弃明细', () => {
const first = { ...returned('1'), sourceType: 'INVENTORY', orderId: 'inventory:1', cartonSelectionSupported: false, availablePackages: 3 }
const second = { ...first, orderId: 'inventory:2', availablePackages: 4 }
const result = groupDiscardDocuments([first, second])[0]
assert.equal(result.availablePackages, 7)
assert.equal(result.cartonSelectionSupported, false)
assert.equal(result.sources.length, 2)
})
@@ -0,0 +1,38 @@
import type { DiscardCandidate } from '../api/warehouseServices'
export interface DiscardDocument extends DiscardCandidate {
sources: DiscardCandidate[]
}
/** 下拉按来源单据和操作仓汇总;原始库存身份保留给选箱与提交。 */
export function groupDiscardDocuments(candidates: DiscardCandidate[]): DiscardDocument[] {
const groups = new Map<string, DiscardDocument>()
for (const candidate of candidates) {
const documentNo = candidate.inboundOrderNo || candidate.inboundCode
const key = JSON.stringify([candidate.overseasWarehouseId, candidate.sourceType, documentNo || candidate.orderId])
let document = groups.get(key)
if (!document) {
document = { ...candidate, orderId: key, sources: [], availablePackages: 0 }
groups.set(key, document)
}
document.sources.push(candidate)
}
for (const document of groups.values()) {
const boxes = new Set<string>()
let untrackedPackages = 0
for (const source of document.sources) {
if (source.sourceType === 'RETURN_CARTON' && source.returnCartonId) {
// 退货候选的 availablePackages 是箱内 SKU 件数,一条退货箱只计一箱。
boxes.add(`RETURN_CARTON:${source.returnCartonId}`)
} else if (source.cartonSelectionSupported && source.cartons?.length) {
for (const box of source.cartons) boxes.add(`${source.sourceType}:${box.cartonId}`)
} else {
untrackedPackages += source.availablePackages
}
}
document.availablePackages = boxes.size + untrackedPackages
document.cartonSelectionSupported = document.sources.every(source => source.cartonSelectionSupported)
document.cartonSelectionBlockReason = document.sources.find(source => !source.cartonSelectionSupported)?.cartonSelectionBlockReason
}
return [...groups.values()]
}
@@ -12,6 +12,7 @@ import { prepareTransferCartonLabels } from '../utils/transferCartonPrinting'
import CompletionConfirmationDialog from '../components/CompletionConfirmationDialog.vue' import CompletionConfirmationDialog from '../components/CompletionConfirmationDialog.vue'
import ReviewRejectionNotice from '../components/ReviewRejectionNotice.vue' import ReviewRejectionNotice from '../components/ReviewRejectionNotice.vue'
import { returnRelabelCandidate } from '../utils/returnRelabelCandidates' import { returnRelabelCandidate } from '../utils/returnRelabelCandidates'
import { groupDiscardDocuments } from '../utils/discardDocuments'
import { import {
createDiscard, createRelabel, createTransfer, getDiscard, getRelabel, getTransfer, resubmitRelabel, createDiscard, createRelabel, createTransfer, getDiscard, getRelabel, getTransfer, resubmitRelabel,
getTransferCartonLabel, submitTransfer, updateTransfer, increaseTransferForecastCartons, getTransferSourceCartons, type TransferSourceCarton, 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 canCancelDiscard = computed(() => auth.hasPermission('merchant:overseas-warehouse:discard:cancel'))
const configs: Record<ServiceType, ServiceConfig> = { const configs: Record<ServiceType, ServiceConfig> = {
labeling: { title: '贴标申请', createTitle: '新增贴标申请', numberLabel: '贴标申请单号', emptyText: '暂无贴标申请', columns: ['操作', '贴标申请单号', '关联业务单号', '海外操作仓', 'SKU 数(种)', '总箱数/包裹', '换标类型', '状态', '审核时间', '贴标完成时间', '贴标出库时间', '创建时间'] }, labeling: { title: '贴标申请', createTitle: '新增贴标申请', numberLabel: '贴标申请单号', emptyText: '暂无贴标申请', columns: ['操作', '贴标申请单号', '关联业务单号', '海外操作仓', 'SKU 数(种)', '总箱数/包裹', '换标类型', '状态', '创建时间'] },
transfer: { title: '仓储转运单', createTitle: '新增仓储转运单', numberLabel: '转运单号', emptyText: '暂无仓储转运订单', columns: ['操作', '转运单号', '预计箱数', '海外仓', 'FBA 目的仓', '入仓方式', '是否换标', '换标单号', '换标状态', '状态', '审核时间', '测量时间', '出库时间', '创建时间'] }, transfer: { title: '仓储转运单', createTitle: '新增仓储转运单', numberLabel: '转运单号', emptyText: '暂无仓储转运订单', columns: ['操作', '转运单号', '预计箱数', '海外仓', 'FBA 目的仓', '入仓方式', '是否换标', '换标单号', '换标状态', '状态', '创建时间'] },
disposal: { title: '货物废弃', createTitle: '新增货物废弃申请', numberLabel: '废弃申请单号', emptyText: '暂无货物废弃申请', columns: ['操作', '废弃申请单号', '废弃出库单号', '进仓编号', '操作仓', 'SKU 汇总', '废弃数量', '预估费用', '状态', '废弃完成时间', '创建时间'] }, disposal: { title: '货物废弃', createTitle: '新增货物废弃申请', numberLabel: '废弃申请单号', emptyText: '暂无货物废弃申请', columns: ['操作', '废弃申请单号', '废弃出库单号', '进仓编号', '操作仓', 'SKU 汇总', '废弃数量', '预估费用', '状态', '废弃完成时间', '创建时间'] },
relabel: { title: '退货换标', createTitle: '新建退货换标单', numberLabel: '退货换标单号', emptyText: '暂无退货换标订单', columns: ['操作', '退货换标单号', '来源 FBA 仓', '目的 FBA 仓', '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: '请输入仓代码' }) 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: 'status', label: '订单状态', type: 'select', options: statusOptions.value })
fields.push({ key: 'createdDates', label: '创建时间', type: 'daterange' }) 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( if (props.service === 'relabel') fields.push(
{ key: 'reviewDates', label: '审核时间', type: 'daterange' }, { key: 'reviewDates', label: '审核时间', type: 'daterange' },
{ key: 'measuredDates', label: '测量时间', type: 'daterange' }, { key: 'measuredDates', label: '测量时间', type: 'daterange' },
{ key: 'outboundDates', 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 return fields
}) })
const listColumns = computed<DataTableColumn[]>(() => config.value.columns.map((label, index) => index === 0 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 discardSelectedBoxKeys = ref<string[]>([])
const discardBoxSearch = ref('') const discardBoxSearch = ref('')
const discardCandidates = ref<import('../api/warehouseServices').DiscardCandidate[]>([]) const discardCandidates = ref<import('../api/warehouseServices').DiscardCandidate[]>([])
const discardSelectedCandidate = computed(() => discardCandidates.value.find(item => item.orderId === selectedDiscardOrderId.value)) const discardDocuments = computed(() => groupDiscardDocuments(discardCandidates.value))
const discardSelectionSources = computed(() => { const discardSelectedCandidate = computed(() => discardDocuments.value.find(item => item.orderId === selectedDiscardOrderId.value))
const current = discardSelectedCandidate.value const discardSelectionSources = computed(() => discardSelectedCandidate.value?.sources || [])
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 discardSelectableBoxes = computed(() => discardSelectionSources.value.flatMap(source => (source.cartons || []).map(box => ({ ...box, key: `${source.sourceType}:${box.cartonId}`, source })))) 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())))) 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) { function toggleDiscardBox(key: string, selected: boolean) {
@@ -992,17 +979,17 @@ function importSelectedBindingsToDetails() {
function selectDiscardInbound(orderId: string) { function selectDiscardInbound(orderId: string) {
discardSelectedBoxKeys.value = [] discardSelectedBoxKeys.value = []
discardBoxSearch.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 } if (!candidate) { cargoRows.value = []; return }
form.inboundNo = candidate.inboundCode || candidate.inboundOrderNo form.inboundNo = candidate.inboundCode || candidate.inboundOrderNo
form.warehouse = candidate.overseasWarehouseId form.warehouse = candidate.overseasWarehouseId
if (candidate.cartonSelectionSupported) { selectAllDiscardBoxes(true); return } 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, id: ++cargoSequence, sku: sku.skuCode, name: sku.productName, quantity: sku.availablePackages,
availableQuantity: sku.availablePackages, sourceOrderId: candidate.orderId, sourceType: candidate.sourceType, availableQuantity: sku.availablePackages, sourceOrderId: source.orderId, sourceType: source.sourceType,
returnCartonId: candidate.returnCartonId, sourceInventoryPositionId: candidate.inventoryPositionId, palletNo: candidate.palletNo, cartonMark: candidate.cartonMark || '', returnCartonId: source.returnCartonId, sourceInventoryPositionId: source.inventoryPositionId, palletNo: source.palletNo, cartonMark: source.cartonMark || '',
fnsku: '', shipmentNo: '', newSkuCode: '', shippingMark: '', customerCartonMarks: '', fbaCartonMark: '', destinationWarehouseId: '', remark: '', 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 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)])) } 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 total.value = Number.isFinite(Number(result.total)) ? Number(result.total) : result.records.length
submittedRows.value = result.records.map((item) => { submittedRows.value = result.records.map((item) => {
const destinationWarehouses = warehouseDisplay(item.overseasWarehouseId) 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') { } 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) }) 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 operationWarehouse = warehouseDisplay(item.operatingWarehouseId)
const relabelStatus = transferRelabelStatus(item) const relabelStatus = transferRelabelStatus(item)
return props.service === 'transfer' 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)]) : 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"> <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"> <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 #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 #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 #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> <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>
<div v-show="props.service !== 'labeling' || !orderInfoCollapsed" class="warehouse-form-grid"> <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="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 discardCandidates" :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 discardDocuments" :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>进仓编号</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-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>
@@ -14,9 +14,10 @@ test('货物废弃先选择海外操作仓,再加载并选择该仓入库单',
test('废弃来源包含通用仓位,选择后带出全部库存且不把选择键提交为订单ID', () => { test('废弃来源包含通用仓位,选择后带出全部库存且不把选择键提交为订单ID', () => {
const body = view.match(/function selectDiscardInbound\(orderId: string\) \{([\s\S]*?)\n\}/)[1] const body = view.match(/function selectDiscardInbound\(orderId: string\) \{([\s\S]*?)\n\}/)[1]
const select = new Function('discardSelectedBoxKeys', 'discardBoxSearch', 'selectAllDiscardBoxes', 'discardCandidates', 'cargoRows', 'form', 'cargoSequence', 'orderId', body) const select = new Function('discardSelectedBoxKeys', 'discardBoxSearch', 'selectAllDiscardBoxes', 'discardDocuments', 'cargoRows', 'form', 'cargoSequence', 'orderId', body)
const cargoRows = { value: [] }; const form = {} const cargoRows = { value: [] }; const form = {}
select({ value: [] }, { value: '' }, () => {}, { value: [{ orderId: 'inventory:42', sourceType: 'INVENTORY', inventoryPositionId: '42', overseasWarehouseId: '99', inboundCode: 'WT-1', skus: [{ skuCode: 'A', productName: '商品', availablePackages: 100 }] }] }, cargoRows, form, 0, 'inventory:42') const source = { orderId: 'inventory:42', sourceType: 'INVENTORY', inventoryPositionId: '42', overseasWarehouseId: '99', inboundCode: 'WT-1', skus: [{ skuCode: 'A', productName: '商品', availablePackages: 100 }] }
select({ value: [] }, { value: '' }, () => {}, { value: [{ ...source, orderId: 'document:WT-1', sources: [source] }] }, cargoRows, form, 0, 'document:WT-1')
assert.equal(cargoRows.value[0].quantity, 100) assert.equal(cargoRows.value[0].quantity, 100)
assert.equal(cargoRows.value[0].sourceInventoryPositionId, '42') assert.equal(cargoRows.value[0].sourceInventoryPositionId, '42')
assert.equal(form.inboundNo, 'WT-1') assert.equal(form.inboundNo, 'WT-1')