diff --git a/src/modules/overseas-warehouse/views/WarehouseServiceView.vue b/src/modules/overseas-warehouse/views/WarehouseServiceView.vue index 81691bf..773e3f9 100644 --- a/src/modules/overseas-warehouse/views/WarehouseServiceView.vue +++ b/src/modules/overseas-warehouse/views/WarehouseServiceView.vue @@ -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() 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() 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 diff --git a/src/modules/overseas-warehouse/views/inventoryTransferForm.test.mjs b/src/modules/overseas-warehouse/views/inventoryTransferForm.test.mjs index 6025146..cf0d64a 100644 --- a/src/modules/overseas-warehouse/views/inventoryTransferForm.test.mjs +++ b/src/modules/overseas-warehouse/views/inventoryTransferForm.test.mjs @@ -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, []) +}) diff --git a/src/modules/overseas-warehouse/views/warehouseTransferForm.test.mjs b/src/modules/overseas-warehouse/views/warehouseTransferForm.test.mjs index 9b67d49..6892ed3 100644 --- a/src/modules/overseas-warehouse/views/warehouseTransferForm.test.mjs +++ b/src/modules/overseas-warehouse/views/warehouseTransferForm.test.mjs @@ -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 \|\| \[\]/)