From e3a37d6ad088774af6e2cb17b6a103b16ed5c503 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Mon, 7 Sep 2026 20:29:31 +0800 Subject: [PATCH] fix(overseas): show outbound pallet details --- .../overseas-warehouse/api/outbounds.ts | 10 ++++++- .../pages/OutboundDetailView.test.ts | 30 +++++++++++++++++++ .../pages/OutboundDetailView.vue | 11 ++++++- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/modules/overseas-warehouse/api/outbounds.ts b/src/modules/overseas-warehouse/api/outbounds.ts index 311a65db..56f83409 100644 --- a/src/modules/overseas-warehouse/api/outbounds.ts +++ b/src/modules/overseas-warehouse/api/outbounds.ts @@ -4,7 +4,15 @@ import { normalizeOverseasPage, type OverseasPagePayload } from './lastMileOrder import type { DeliveryOrderRow } from './deliveryOrders' export interface OutboundRow { id: string; outboundNo: string; dispatchNo?: string | null; overseasWarehouseId?: string | null; lxCodes?: string | null; status?: string | null; palletCount?: number | null; packageCount?: number | null; createdAt?: string | null } -export interface OutboundDetail { outbound: OutboundRow; delivery?: DeliveryOrderRow | null } +export interface OutboundPalletRow { + palletNo?: string | null + inboundCode?: string | null + destinationWarehouseCode?: string | null + destinationWarehouseName?: string | null + skuCode?: string | null + packageCount?: number | null +} +export interface OutboundDetail { outbound: OutboundRow; pallets: OutboundPalletRow[]; delivery?: DeliveryOrderRow | null } export interface OutboundQuery { page: number; size: number; documentNo?: string; lxCode?: string; warehouseId?: string; status?: 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/pages/OutboundDetailView.test.ts b/src/modules/overseas-warehouse/pages/OutboundDetailView.test.ts index 5b4948a1..55a3123b 100644 --- a/src/modules/overseas-warehouse/pages/OutboundDetailView.test.ts +++ b/src/modules/overseas-warehouse/pages/OutboundDetailView.test.ts @@ -14,6 +14,7 @@ describe('OutboundDetailView', () => { canPermission.mockReset().mockReturnValue(false) vi.mocked(getOutbound).mockReset().mockResolvedValue({ outbound: { id: '1', outboundNo: 'OUT/1', dispatchNo: 'PC-1', status: 'PICKED', palletCount: 2, packageCount: 20, createdAt: '2026-09-01T10:00:00' }, + pallets: [{ palletNo: 'IP-ACY2-20260907-597057-P001', inboundCode: 'NYEXX2609060063', destinationWarehouseCode: 'ONT8', destinationWarehouseName: 'Amazon ONT8', skuCode: 'SKU-001', packageCount: 20 }], delivery: { id: '2', customerId: 'customer-1', deliveryNo: 'DEL-1', outboundNo: 'OUT/1', dispatchNo: 'PC-1', status: 'WAIT_DELIVERY', palletCount: 2, packageCount: 20, createdAt: '2026-09-01T10:00:00' }, }) }) @@ -25,10 +26,39 @@ describe('OutboundDetailView', () => { expect(wrapper.text()).toContain('OUT/1') expect(wrapper.text()).toContain('PC-1') expect(wrapper.text()).toContain('DEL-1') + expect(wrapper.text()).toContain('出库托盘') + expect(wrapper.text()).toContain('IP-ACY2-20260907-597057-P001') + expect(wrapper.text()).toContain('NYEXX2609060063') + expect(wrapper.text()).toContain('Amazon ONT8') + expect(wrapper.text()).toContain('SKU-001') + expect(wrapper.findAll('.outbound-pallet-table .data-table__single-line-cell').length).toBeGreaterThanOrEqual(6) expect(wrapper.text()).not.toContain('取消出库单') wrapper.unmount() }) + it('renders pallet missing values consistently and keeps the empty state in the table', async () => { + vi.mocked(getOutbound).mockResolvedValueOnce({ + outbound: { id: '1', outboundNo: 'OUT/1', status: 'OUTBOUNDED' }, + pallets: [{ palletNo: null, inboundCode: null, destinationWarehouseCode: null, destinationWarehouseName: null, skuCode: null, packageCount: null }], + delivery: null, + }) + const wrapper = mount(OutboundDetailView, { global: { plugins: [ElementPlus] } }) + await flushPromises() + expect(wrapper.get('.outbound-pallet-table').text()).toContain('—') + expect(wrapper.text()).toContain('暂无关联派送单') + wrapper.unmount() + + vi.mocked(getOutbound).mockResolvedValueOnce({ + outbound: { id: '1', outboundNo: 'OUT/1', status: 'OUTBOUNDED' }, + pallets: [], + delivery: null, + }) + const emptyWrapper = mount(OutboundDetailView, { global: { plugins: [ElementPlus] } }) + await flushPromises() + expect(emptyWrapper.text()).toContain('暂无出库托盘') + emptyWrapper.unmount() + }) + it('shows cancellation only for PICKED with the independent permission', async () => { canPermission.mockImplementation((code: string) => code === 'platform:overseas-warehouse:outbound:cancel') const wrapper = mount(OutboundDetailView, { global: { plugins: [ElementPlus] } }) diff --git a/src/modules/overseas-warehouse/pages/OutboundDetailView.vue b/src/modules/overseas-warehouse/pages/OutboundDetailView.vue index 22d4dc93..b6e21113 100644 --- a/src/modules/overseas-warehouse/pages/OutboundDetailView.vue +++ b/src/modules/overseas-warehouse/pages/OutboundDetailView.vue @@ -6,6 +6,7 @@ import { useRoute, useRouter } from 'vue-router' import { useAccessPreviewStore } from '@/core/access/accessPreview' import AdminPage from '@/shared/components/AdminPage.vue' import ApiErrorMeta from '@/shared/components/ApiErrorMeta.vue' +import DataTable, { type DataTableColumn } from '@/shared/components/DataTable.vue' import DetailPageLayout from '@/shared/components/DetailPageLayout.vue' import LoadErrorState from '@/shared/components/LoadErrorState.vue' import OperationErrorToast from '@/shared/components/OperationErrorToast.vue' @@ -20,6 +21,14 @@ const outboundNo = computed(() => String(route.params.outboundNo || '').trim()) const detail = ref(null); const loading = ref(false); const cancelling = ref(false) const loadError = ref(null); const mutationError = ref(null) const statusLabels: Record = { WAIT_OUTBOUND: '待出库', PICKING: '拣货中', PICKED: '已拣货', OUTBOUNDED: '已出库', CANCELLED: '已取消' } +const palletColumns: DataTableColumn[] = [ + { key: 'palletNo', label: '托盘码', width: 220 }, + { key: 'inboundCode', label: '入仓码', width: 190 }, + { key: 'destinationWarehouseCode', label: '目的仓编码', width: 150 }, + { key: 'destinationWarehouseName', label: '目的仓名称', minWidth: 180 }, + { key: 'skuCode', label: '商品 SKU', width: 170 }, + { key: 'packageCount', label: '箱数', width: 100, align: 'right' }, +] function show(value: unknown) { const text = String(value ?? '').trim(); return text || '—' } function statusLabel(value: unknown) { const code = String(value || ''); return statusLabels[code] || show(code) } const canCancel = computed(() => access.can(permission.outboundCancel) && detail.value?.outbound.status === 'PICKED') @@ -34,7 +43,7 @@ watch(outboundNo, () => { void load() }, { immediate: true }) - +