This commit is contained in:
2026-10-16 13:58:08 +08:00
parent c79b5492c6
commit bb674eb3a0
3 changed files with 51 additions and 11 deletions
@@ -1432,7 +1432,11 @@ async function loadTransferInventoryCandidates(warehouseId: string) {
if (!warehouseId) return
relabelCandidatesLoading.value = true
try {
transferInventoryCandidates.value = await listRelabelInventoryCandidates(warehouseId)
// 共用接口包含退货历史及占用中的箱子,转运只展示当前可整箱使用的来源。
transferInventoryCandidates.value = (await listRelabelInventoryCandidates(warehouseId)).filter(item =>
item.sourceType === 'RETURN_CARTON' || item.sourceType === 'RETURN_PARCEL'
? Number(item.cartonAvailablePackages || 0) > 0
: Number(item.availablePackages || 0) > 0)
} catch (error) {
ElMessage.error(error instanceof Error ? error.message : '当前海外仓库存来源加载失败')
} finally { relabelCandidatesLoading.value = false }
@@ -1483,12 +1487,18 @@ async function resubmitRejectedRelabel() {
const transferInventoryOptions = computed(() => {
const grouped = new Map<string, { value: string; label: string; packages: number }>()
for (const item of transferInventoryCandidates.value) {
const value = String(item.inboundCode || '')
// 退货库存没有普通入仓码,使用带前缀的退货来源编码,避免提交时被当作普通入仓单查询。
const returnNo = String(item.inboundOrderNo || '').trim()
const value = item.sourceType === 'RETURN_CARTON' || item.sourceType === 'RETURN_PARCEL'
? (returnNo ? `RETURN_ORDER:${returnNo}` : '')
: String(item.inboundCode || '')
if (!value) continue
const packages = inventoryQuantity(item.availablePackages)
const packages = item.sourceType === 'RETURN_CARTON' || item.sourceType === 'RETURN_PARCEL'
? inventoryQuantity(item.cartonAvailablePackages || 0)
: inventoryQuantity(item.availablePackages)
const current = grouped.get(value)
if (current) current.packages += packages
else grouped.set(value, { value, label: `${item.inboundOrderNo || value}|${value}`, packages })
else grouped.set(value, { value, label: value.startsWith('RETURN_ORDER:') ? `退货单|${returnNo}` : `${item.inboundOrderNo || value}|${value}`, packages })
}
return [...grouped.values()]
})
@@ -1505,17 +1515,26 @@ async function selectTransferInventory(value: string) {
// here: merchants are not authorized for that endpoint (A0403), and the
// transfer service can derive source orders from inboundCode when IDs are
// omitted. Aggregate candidates for the selected inbound into cargo rows.
const candidates = transferInventoryCandidates.value.filter((item) => String(item.inboundCode || '') === value)
const isReturnSource = value.startsWith('RETURN_ORDER:')
const returnNo = isReturnSource ? value.slice('RETURN_ORDER:'.length) : ''
const candidates = transferInventoryCandidates.value.filter((item) => isReturnSource
? (item.sourceType === 'RETURN_CARTON' || item.sourceType === 'RETURN_PARCEL') && String(item.inboundOrderNo || '') === returnNo
: String(item.inboundCode || '') === value)
const grouped = new Map<string, CargoRow>()
for (const candidate of candidates) {
const sku = String(candidate.skuCode || '')
const current = grouped.get(sku)
const quantity = inventoryQuantity(candidate.availablePackages)
if (current) { current.quantity += quantity; current.expectedPackageCount = Number(current.expectedPackageCount || 0) + quantity }
else grouped.set(sku, { ...createCargoRow(), sku, name: candidate.productNameZh || '', quantity, expectedPackageCount: quantity, availableQuantity: quantity })
const candidateRows = isReturnSource
? (candidate.cartonSkuItems?.length ? candidate.cartonSkuItems : candidate.skuItems || []).map(item => ({ sku: item.skuCode, name: candidate.productNameZh || '', quantity: inventoryQuantity(item.quantity), packageCount: inventoryQuantity(candidate.cartonAvailablePackages || 0) }))
: [{ sku: String(candidate.skuCode || ''), name: candidate.productNameZh || '', quantity: inventoryQuantity(candidate.availablePackages), packageCount: inventoryQuantity(candidate.availablePackages) }]
for (const row of candidateRows) {
if (!row.sku) continue
const current = grouped.get(row.sku)
if (current) { current.quantity += row.quantity; current.expectedPackageCount = Number(current.expectedPackageCount || 0) + row.packageCount; current.availableQuantity = Number(current.availableQuantity || 0) + row.quantity }
else grouped.set(row.sku, { ...createCargoRow(), sku: row.sku, name: row.name, quantity: row.quantity, expectedPackageCount: row.packageCount, availableQuantity: row.quantity })
}
}
cargoRows.value = [...grouped.values()]
try {
if (isReturnSource) return
const historical = await listRelabelHistoricalOrders(form.warehouse, value)
const sourceIds = historical.map((item) => String(item.id || '')).filter(Boolean)
if (selectedTransferInventory.value === value) selectedTransferSourceOrderIds.value = sourceIds
@@ -39,3 +39,24 @@ test('inventory view hides inbound fields and editing actions and shows original
assert.match(view,/!inventoryTransfer\.value && \(!form.warehouse/)
assert.match(view,/estimatedArrivalAt: inventoryTransfer.value \? undefined : form.estimatedArrivalAt/)
})
test('仓储转运来源下拉包含退货单并使用独立来源编码', () => {
assert.match(view, /RETURN_ORDER:\$\{returnNo\}/)
assert.match(view, /退货单|\$\{returnNo\}/)
assert.match(view, /item\.sourceType === 'RETURN_CARTON' \|\| item\.sourceType === 'RETURN_PARCEL'/)
assert.match(view, /isReturnSource[\s\S]*item\.sourceType === 'RETURN_CARTON'/)
})
test('退货来源带出实物 SKU 数量并跳过普通历史订单查询', async () => {
const { ctx, select } = setup()
ctx.transferInventoryCandidates.value = [{ sourceType: 'RETURN_CARTON', inboundOrderNo: 'RET-1',
cartonAvailablePackages: 1, skuItems: [{ skuCode: 'SKU', quantity: 1 }],
cartonSkuItems: [{ skuCode: 'SKU', quantity: 6 }] },
{ sourceType: 'RETURN_CARTON', inboundOrderNo: 'OTHER', cartonAvailablePackages: 1,
cartonSkuItems: [{ skuCode: 'FOREIGN', quantity: 99 }] }]
await select('RETURN_ORDER:RET-1')
assert.equal(ctx.cargoRows.value.length, 1)
assert.equal(ctx.cargoRows.value[0].quantity, 6)
assert.equal(ctx.cargoRows.value[0].expectedPackageCount, 1)
assert.deepEqual(ctx.selectedTransferSourceOrderIds.value, [])
})
@@ -341,7 +341,7 @@ test('仓储转运可在新建页直接提交,也可保存草稿后编辑提
test('编辑仓储转运时加载库存转运来源候选', () => {
assert.match(view, /async function loadTransferInventoryCandidates\(warehouseId: string\)/)
assert.match(view, /if \(props\.service === 'transfer' && form\.warehouse\) \{[\s\S]*await loadTransferInventoryCandidates\(form\.warehouse\)/)
assert.match(view, /transferInventoryCandidates\.value = await listRelabelInventoryCandidates\(warehouseId\)/)
assert.match(view, /transferInventoryCandidates\.value = \(await listRelabelInventoryCandidates\(warehouseId\)\)\.filter/)
assert.match(view, /transferInventoryOptions\.value\.some\(\(option\) => option\.value === sourceInboundCode\)/)
assert.match(view, /selectedTransferInventory\.value = sourceInboundCode/)
assert.match(view, /detail\.sourceOrders \|\| \[\]/)