From b6a839a3c69c608f4863ca84dd96684726cc04a0 Mon Sep 17 00:00:00 2001 From: liuyihe <2513762445@qq.com> Date: Thu, 17 Sep 2026 16:34:36 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../overseas-warehouse/api/outbounds.ts | 2 +- .../components/SmartDispatchDialog.vue | 2 +- .../pages/OutboundsView.test.ts | 15 +++++++++++ .../pages/OutboundsView.vue | 27 +++++++++++++++++-- .../pages/prototypeContract.test.ts | 1 + 5 files changed, 43 insertions(+), 4 deletions(-) diff --git a/src/modules/overseas-warehouse/api/outbounds.ts b/src/modules/overseas-warehouse/api/outbounds.ts index 858d97fa..8855b7a2 100644 --- a/src/modules/overseas-warehouse/api/outbounds.ts +++ b/src/modules/overseas-warehouse/api/outbounds.ts @@ -20,7 +20,7 @@ export interface OutboundDetail { outbound: OutboundRow; pallets?: OutboundPalle export interface OutboundQuery { page: number; size: number; documentNo?: string; lxCode?: string; warehouseId?: string; status?: string; outboundType?: string } export interface ReturnOutboundCandidate { returnNo?: string | null; overseasWarehouseId?: string | null; cartonMark?: string | null; fbaCartonMark?: string | null; destinationWarehouseCode?: string | null; destinationWarehouseName?: string | null; totalQuantity?: number | null; status?: string | null } export interface OneClickOutboundCandidate { inventoryPositionId: string; orderNo?: string | null; customerId?: string | null; customerName?: string | null; overseasWarehouseId?: string | null; overseasWarehouseName?: string | null; palletNo?: string | null; inboundCode?: string | null; skuCode?: string | null; productName?: string | null; availablePackages: number } -export interface WarehouseTransferOutboundCandidate { customerId: string; customerName?: string | null; transferNo: string; overseasWarehouseId: string; transferStatus: string; needRelabel: boolean; relabelNo?: string | null; packageCount: number; grossWeightKg?: number | null; volumeCbm?: number | null } +export interface WarehouseTransferOutboundCandidate { customerId: string; customerName?: string | null; transferNo: string; overseasWarehouseId: string; transferStatus: string; needRelabel: boolean; relabelNo?: string | null; destinationType?: 'FBA' | 'PRIVATE' | null; destinationWarehouseCode?: string | null; privateDestinationAddress?: string | null; privateDestinationPostalCode?: string | null; packageCount: number; grossWeightKg?: number | null; volumeCbm?: number | null } export interface WarehouseTransferDestinationPayload { transfers: Array<{ customerId: string; transferNo: string }>; destinationType: 'FBA' | 'PRIVATE'; destinationWarehouseId?: string; destinationWarehouseCode?: string; destinationWarehouseName?: string; privateDestinationAddress?: string; privateDestinationPostalCode?: string; privateDestinationContactName?: string; privateDestinationContactPhone?: string; idempotencyKey: string } export async function listOutbounds(query: OutboundQuery) { const response = await overseasHttp.get>, ApiEnvelope>>('/platform/v1/last-leg/outbound-orders', { params: query }) diff --git a/src/modules/overseas-warehouse/components/SmartDispatchDialog.vue b/src/modules/overseas-warehouse/components/SmartDispatchDialog.vue index f36410e8..cc9413bd 100644 --- a/src/modules/overseas-warehouse/components/SmartDispatchDialog.vue +++ b/src/modules/overseas-warehouse/components/SmartDispatchDialog.vue @@ -371,7 +371,7 @@ watch([warehouseTab, () => query.orderType], () => {
- + diff --git a/src/modules/overseas-warehouse/pages/OutboundsView.test.ts b/src/modules/overseas-warehouse/pages/OutboundsView.test.ts index d1ba73e9..a4d58cb2 100644 --- a/src/modules/overseas-warehouse/pages/OutboundsView.test.ts +++ b/src/modules/overseas-warehouse/pages/OutboundsView.test.ts @@ -87,4 +87,19 @@ describe('OutboundsView permissions and navigation', () => { expect(api).toContain('/platform/v1/last-leg/warehouse-transfers/bind-destination') expect(source).not.toContain('await createWarehouseTransferOutbound') }) + + it('auto-selects the sole warehouse-transfer candidate and shows destination state', () => { + const source = readFileSync(resolve(process.cwd(), 'src/modules/overseas-warehouse/pages/OutboundsView.vue'), 'utf8') + const api = readFileSync(resolve(process.cwd(), 'src/modules/overseas-warehouse/api/outbounds.ts'), 'utf8') + expect(source).toContain("if (transferCandidates.value.length === 1) selectedTransferPalletIds.value = [transferSelectionKey(transferCandidates.value[0])]") + expect(source).toContain("transferCandidates.value.length === 1 ? [transferCandidates.value[0]] : []") + expect(source).toContain('label="主单目的去向"') + expect(source).toContain("['私人仓', row.privateDestinationAddress?.trim(), row.privateDestinationPostalCode?.trim()]") + expect(source).toContain('`FBA · ${row.destinationWarehouseCode.trim()}`') + expect(source).toContain("return '未设置'") + expect(api).toContain('destinationWarehouseCode?: string | null') + expect(api).toContain("destinationType?: 'FBA' | 'PRIVATE' | null") + expect(api).toContain('privateDestinationAddress?: string | null') + expect(api).toContain('privateDestinationPostalCode?: string | null') + }) }) diff --git a/src/modules/overseas-warehouse/pages/OutboundsView.vue b/src/modules/overseas-warehouse/pages/OutboundsView.vue index 463d42ed..236144db 100644 --- a/src/modules/overseas-warehouse/pages/OutboundsView.vue +++ b/src/modules/overseas-warehouse/pages/OutboundsView.vue @@ -116,9 +116,31 @@ async function loadDropshipCandidates() { if (!selectedWarehouseId.value) { drop async function changeDropshipWarehouse() { selectedDropshipIds.value = []; await loadDropshipCandidates() } async function selectCustomer() { selectedPositionId.value = ''; selectedDropshipIds.value = []; selectedReturnNo.value = ''; selectedReturnMarks.value = []; selectedReturnSkus.value = []; returnCandidates.value = []; returnStockRows.value = []; returnCartonRows.value = []; returnCartonDetails.value = {}; dropshipCandidates.value = []; dropshipPage.value = 1; if (outboundType.value === 'RETURN_OUTBOUND') { if (selectedCustomerId.value && selectedWarehouseId.value) await loadReturnCandidates(); return }; await loadDropshipCandidates() } async function changeOutboundType() { selectedReturnNo.value = ''; selectedReturnMarks.value = []; selectedReturnSkus.value = []; returnCandidates.value = []; returnStockRows.value = []; returnCartonRows.value = []; returnCartonDetails.value = {}; selectedDropshipIds.value = []; selectedTransferPalletIds.value = []; dropshipCandidates.value = []; transferCandidates.value = []; if (outboundType.value === 'ONE_CLICK_DROPSHIP') await loadDropshipCandidates(); if (outboundType.value === 'WAREHOUSE_TRANSFER_OUTBOUND') await loadTransferCandidates() } -async function loadTransferCandidates() { inventoryLoading.value = true; try { transferCandidates.value = await listWarehouseTransferOutboundCandidates({ customerId: selectedCustomerId.value || undefined, warehouseId: selectedWarehouseId.value || undefined }) } catch (cause) { transferCandidates.value = []; ElMessage.error(getApiErrorDetails(cause, '无目的地仓储转运单加载失败').message) } finally { inventoryLoading.value = false } } +async function loadTransferCandidates() { + inventoryLoading.value = true + try { + transferCandidates.value = await listWarehouseTransferOutboundCandidates({ customerId: selectedCustomerId.value || undefined, warehouseId: selectedWarehouseId.value || undefined }) + const candidateKeys = new Set(transferCandidates.value.map(transferSelectionKey)) + selectedTransferPalletIds.value = selectedTransferPalletIds.value.filter(key => candidateKeys.has(key)) + if (transferCandidates.value.length === 1) selectedTransferPalletIds.value = [transferSelectionKey(transferCandidates.value[0])] + } catch (cause) { + transferCandidates.value = [] + selectedTransferPalletIds.value = [] + ElMessage.error(getApiErrorDetails(cause, '无目的地仓储转运单加载失败').message) + } finally { inventoryLoading.value = false } +} function transferSelectionKey(row: WarehouseTransferOutboundCandidate) { return `${row.customerId}:${row.transferNo}` } function toggleTransferPallet(row: WarehouseTransferOutboundCandidate, checked: boolean | string | number) { const key = transferSelectionKey(row); selectedTransferPalletIds.value = checked ? [...new Set([...selectedTransferPalletIds.value, key])] : selectedTransferPalletIds.value.filter(id => id !== key) } +function transferDestinationLabel(row: WarehouseTransferOutboundCandidate) { + if (row.destinationType === 'PRIVATE' || row.privateDestinationAddress?.trim()) { + return ['私人仓', row.privateDestinationAddress?.trim(), row.privateDestinationPostalCode?.trim()].filter(Boolean).join(' · ') + } + if (row.destinationWarehouseCode?.trim()) return `FBA · ${row.destinationWarehouseCode.trim()}` + return '未设置' +} +function transferHasDestination(row: WarehouseTransferOutboundCandidate) { + return Boolean(row.destinationWarehouseCode?.trim() || row.privateDestinationAddress?.trim()) +} async function loadReturnCandidates() { if (!selectedCustomerId.value || !selectedWarehouseId.value) return; inventoryLoading.value = true; try { const [candidates, stock, cartons] = await Promise.all([listReturnOutboundCandidates(selectedCustomerId.value, selectedWarehouseId.value), listReturnStock({ page: 1, size: 200, customerId: selectedCustomerId.value, overseasWarehouseId: selectedWarehouseId.value, status: 'AVAILABLE' }), listReturnCartons({ page: 1, size: 200, customerId: selectedCustomerId.value, overseasWarehouseId: selectedWarehouseId.value, status: 'AVAILABLE' })]); returnCandidates.value = candidates; returnStockRows.value = stock.records; returnCartonRows.value = cartons.records; const details = await Promise.all(cartons.records.map(async (row) => [row.cartonMark, await getReturnCarton(row.cartonMark)] as const)); returnCartonDetails.value = Object.fromEntries(details); selectedReturnSkus.value = []; selectedReturnMarks.value = [] } catch (cause) { ElMessage.error(getApiErrorDetails(cause, '客户库存加载失败').message) } finally { inventoryLoading.value = false } } function dropshipCandidateKey(item: OneClickOutboundCandidate) { return `${item.inventoryPositionId}:${item.skuCode || ''}` } function isDropshipSelected(item: OneClickOutboundCandidate) { return selectedDropshipIds.value.includes(dropshipCandidateKey(item)) } @@ -142,7 +164,7 @@ async function submit() { saving.value = true try { if (outboundType.value === 'WAREHOUSE_TRANSFER_OUTBOUND') { - const selected = selectedTransferRows.value + const selected = selectedTransferRows.value.length ? selectedTransferRows.value : transferCandidates.value.length === 1 ? [transferCandidates.value[0]] : [] if (!selected.length) { ElMessage.warning('请至少选择一个无目的地的仓储转运单'); return } const destination = fbaWarehouseOptions.value.find(item => item.value === selectedDestinationWarehouseId.value) if (destinationType.value === 'FBA' && !destination) { ElMessage.warning('请选择FBA目的仓'); return } @@ -247,6 +269,7 @@ onMounted(load) +
暂无已入库且尚无目的地的仓储转运单
diff --git a/src/modules/overseas-warehouse/pages/prototypeContract.test.ts b/src/modules/overseas-warehouse/pages/prototypeContract.test.ts index 6dae1101..0cafbd93 100644 --- a/src/modules/overseas-warehouse/pages/prototypeContract.test.ts +++ b/src/modules/overseas-warehouse/pages/prototypeContract.test.ts @@ -261,6 +261,7 @@ describe('overseas warehouse first-batch prototype contract', () => { expect(dialog).not.toContain('customerId:') expect(dialog).toContain("planIds: query.sourceStage !== 'RELABEL' && planIds.value.length ? planIds.value.join(',') : undefined") expect(dialog).toContain('v-model="query.fbaWarehouseCode" clearable') + expect(dialog).toContain("订单号 / 换标单号 / 原入仓码") expect(dialog).toContain('label="私人仓"') expect(dialog).toContain('item.deliveryAddress, item.postalCode') expect(dialog).not.toContain(':model-value="fbaWarehouseCode" readonly')