From a14e39893cc6e67c615f62d6b01ebcae02475726 Mon Sep 17 00:00:00 2001 From: ouyangxq <775153912@qq.com> Date: Tue, 1 Sep 2026 21:09:39 +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 --- .../api/platformContainerLoading.test.ts | 4 +- .../pages/GlobalSmartLoadingView.test.ts | 59 +++++++- .../pages/GlobalSmartLoadingView.vue | 42 +++++- src/modules/container-loading/types.ts | 2 + .../downloads/api/platformExportTasks.ts | 3 +- .../downloads/pages/DownloadCenterView.vue | 1 + .../inbounds/fee/InboundFeePanel.test.ts | 2 +- src/modules/inbounds/fee/InboundFeePanel.vue | 4 +- .../pages/UnifiedOrdersView.requests.test.ts | 52 ++++++- .../pages/UnifiedOrdersView.test.ts | 28 +++- .../pages/UnifiedOrdersView.vue | 134 ++++++++++++++++-- src/modules/unified-orders/routes.ts | 11 ++ 12 files changed, 313 insertions(+), 29 deletions(-) diff --git a/src/modules/container-loading/api/platformContainerLoading.test.ts b/src/modules/container-loading/api/platformContainerLoading.test.ts index 983e3b00..05f266a1 100644 --- a/src/modules/container-loading/api/platformContainerLoading.test.ts +++ b/src/modules/container-loading/api/platformContainerLoading.test.ts @@ -66,9 +66,9 @@ describe('platform container loading API', () => { it('normalizes every long id in candidates and plans', async () => { vi.mocked(http.get) .mockResolvedValueOnce({ code: '200', data: container }) - .mockResolvedValueOnce({ code: '200', data: [{ accountId: 1, inboundId: 2, orderId: 4, inboundCode: 'IN-1', warehouseId: 5, destinationWarehouseId: 6, channelCode: 'NY_EXX', channelName: '纽约EXX海铁', goodsSummary: '服装、鞋类', availablePackageCount: '7', unitGrossWeightKg: '12.5', unitNetWeightKg: '11.8', averageUnitVolumeCbm: '0.187', availableVolumeCbm: '1.309', coLoadGroupId: 9007199254740999n, groupNo: 'CLG-9007199254740999', memberCount: '3', groupTotalBoxes: '21', locked: false, blockReason: null }] }) + .mockResolvedValueOnce({ code: '200', data: [{ accountId: 1, inboundId: 2, orderId: 4, inboundCode: 'IN-1', orderStatusSnapshot: 'WAIT_SPLIT', expectedArrivalAt: '2026-09-10T09:30:00', warehouseId: 5, destinationWarehouseId: 6, channelCode: 'NY_EXX', channelName: '纽约EXX海铁', goodsSummary: '服装、鞋类', availablePackageCount: '7', unitGrossWeightKg: '12.5', unitNetWeightKg: '11.8', averageUnitVolumeCbm: '0.187', availableVolumeCbm: '1.309', coLoadGroupId: 9007199254740999n, groupNo: 'CLG-9007199254740999', memberCount: '3', groupTotalBoxes: '21', locked: false, blockReason: null }] }) expect((await getContainer('9')).id).toBe(String(container.id)) - expect(await listSmartLoadingCandidates('7', '5')).toMatchObject([{ accountId: '1', inboundId: '2', inboundCode: 'IN-1', destinationWarehouseId: '6', channelCode: 'NY_EXX', channelName: '纽约EXX海铁', goodsSummary: '服装、鞋类', availablePackageCount: 7, unitGrossWeightKg: 12.5, unitNetWeightKg: 11.8, averageUnitVolumeCbm: 0.187, availableVolumeCbm: 1.309, coLoadGroupId: '9007199254740999', groupNo: 'CLG-9007199254740999', memberCount: 3, groupTotalBoxes: 21, locked: false, blockReason: null }]) + expect(await listSmartLoadingCandidates('7', '5')).toMatchObject([{ accountId: '1', inboundId: '2', inboundCode: 'IN-1', orderStatusSnapshot: 'WAIT_SPLIT', expectedArrivalAt: '2026-09-10T09:30:00', destinationWarehouseId: '6', channelCode: 'NY_EXX', channelName: '纽约EXX海铁', goodsSummary: '服装、鞋类', availablePackageCount: 7, unitGrossWeightKg: 12.5, unitNetWeightKg: 11.8, averageUnitVolumeCbm: 0.187, availableVolumeCbm: 1.309, coLoadGroupId: '9007199254740999', groupNo: 'CLG-9007199254740999', memberCount: 3, groupTotalBoxes: 21, locked: false, blockReason: null }]) expect(http.get).toHaveBeenLastCalledWith('/platform/v1/first-leg/smart-loading/inbound-candidates', { params: { bookingId: '7', warehouseId: '5' } }) }) diff --git a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts index f21c9cda..6602a913 100644 --- a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts +++ b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts @@ -60,6 +60,15 @@ const plan = { pdaTaskId: null, pdaTaskStatus: null, pdaTaskVersion: null, items: [{ id: '101', loadingPlanId: '99', accountId: '1', inboundId: '2', inboundDetailId: '3', orderId: '4', orderNo: 'ORD-1', inboundCode: 'IN-1', customerName: '示例企业', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', skuCode: 'SKU-1', productNameZh: '服装', packageCount: 6, productQuantity: 6, grossWeightKg: 60, volumeCbm: 1.2, loadSequence: 1, version: 1, pickedPackageCount: 0, minPackageCount: 1, canAdjust: true, canRemove: true }], } +const candidate = { + accountId: '1', inboundId: '5', orderId: '7', orderNo: 'ORD-2', inboundCode: 'IN-2', + orderStatusSnapshot: 'WAIT_SPLIT', expectedArrivalAt: '2026-09-10T09:30:00', + warehouseId: '8', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', destinationWarehouseId: '9', + destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '另一企业', goodsSummary: '鞋类', + availablePackageCount: 12, unitGrossWeightKg: 10, unitNetWeightKg: 9, averageUnitVolumeCbm: 0.2, + availableVolumeCbm: 2.4, coLoadGroupId: null, groupNo: null, memberCount: null, groupTotalBoxes: null, + locked: null, blockReason: null, +} describe('GlobalSmartLoadingView', () => { beforeEach(() => { @@ -67,7 +76,7 @@ describe('GlobalSmartLoadingView', () => { vi.clearAllMocks() vi.mocked(listContainers).mockResolvedValue({ records: [container], page: 1, size: 100, total: 1, pages: 1 }) vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue(plan) - vi.mocked(listSmartLoadingCandidates).mockResolvedValue([{ accountId: '1', inboundId: '5', orderId: '7', orderNo: 'ORD-2', inboundCode: 'IN-2', warehouseId: '8', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '另一企业', goodsSummary: '鞋类', availablePackageCount: 12, unitGrossWeightKg: 10, unitNetWeightKg: 9, averageUnitVolumeCbm: 0.2, availableVolumeCbm: 2.4, coLoadGroupId: null, groupNo: null, memberCount: null, groupTotalBoxes: null, locked: null, blockReason: null }]) + vi.mocked(listSmartLoadingCandidates).mockResolvedValue([candidate]) }) it('loads real containers, candidates and the selected plan into the three-column workbench', async () => { @@ -199,6 +208,47 @@ describe('GlobalSmartLoadingView', () => { expect(source).toContain(': candidate.availablePackageCount') }) + it('filters candidates by expected arrival range and order status', async () => { + vi.mocked(listSmartLoadingCandidates).mockResolvedValue([ + { ...candidate, inboundId: '51', inboundCode: 'IN-MATCH', orderStatusSnapshot: 'WAIT_SPLIT', expectedArrivalAt: '2026-09-11T08:00:00' }, + { ...candidate, inboundId: '52', inboundCode: 'IN-WRONG-STATUS', orderStatusSnapshot: 'LOADING', expectedArrivalAt: '2026-09-11T08:00:00' }, + { ...candidate, inboundId: '53', inboundCode: 'IN-WRONG-DATE', orderStatusSnapshot: 'WAIT_SPLIT', expectedArrivalAt: '2026-09-20T08:00:00' }, + ]) + const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) + await flushPromises() + + wrapper.getComponent(QueryToolbar).vm.$emit('search', { + customerId: '', inboundCode: '', orderStatus: 'WAIT_SPLIT', expectedArrivalRange: ['2026-09-10', '2026-09-12'], + }) + await flushPromises() + + const candidates = wrapper.get('.candidate-pane').text() + expect(candidates).toContain('IN-MATCH') + expect(candidates).not.toContain('IN-WRONG-STATUS') + expect(candidates).not.toContain('IN-WRONG-DATE') + }) + + it('paginates large inbound candidate results with twenty rows by default', async () => { + vi.mocked(listSmartLoadingCandidates).mockResolvedValue(Array.from({ length: 25 }, (_, index) => ({ + ...candidate, + inboundId: String(100 + index), + orderId: String(200 + index), + inboundCode: `IN-${String(index + 1).padStart(3, '0')}`, + }))) + const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) + await flushPromises() + + const pane = wrapper.get('.candidate-pane') + expect(pane.text()).toContain('IN-020') + expect(pane.text()).not.toContain('IN-021') + expect(pane.text()).toContain('共 25 条 · 第 1 / 2 页') + + await pane.findAll('button').find((button) => button.attributes('aria-label') === '下一页')!.trigger('click') + await flushPromises() + expect(pane.text()).toContain('IN-021') + expect(pane.text()).not.toContain('IN-001') + }) + it('aggregates a two-order candidate group and calls only whole-group assignment', async () => { vi.mocked(listSmartLoadingCandidates).mockResolvedValue([ { accountId: '1', inboundId: '201', orderId: '301', orderNo: 'ORD-201', inboundCode: 'NYEXX2608290051', warehouseId: '8', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '客户一', goodsSummary: '货物一', availablePackageCount: 4, unitGrossWeightKg: 10, unitNetWeightKg: 9, averageUnitVolumeCbm: 0.2, availableVolumeCbm: 0.8, coLoadGroupId: '2093539779795795969', groupNo: 'CLG-2093539779795795969', memberCount: 3, groupTotalBoxes: 12, locked: false, blockReason: null }, @@ -260,13 +310,13 @@ describe('GlobalSmartLoadingView', () => { expect(wrapper.findAll('button[aria-label="列设置"]')).toHaveLength(0) }) - it('keeps only customer and inbound code filters', async () => { + it('keeps the confirmed candidate filters without unrelated customs fields', async () => { const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/GlobalSmartLoadingView.vue'), 'utf8') const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() const fields = wrapper.findComponent(QueryToolbar).props('fields') as Array<{ key: string; options?: Array<{ label: string; value: string }> }> - expect(fields.map((field) => field.key)).toEqual(['customerId', 'inboundCode']) + expect(fields.map((field) => field.key)).toEqual(['customerId', 'inboundCode', 'expectedArrivalRange', 'orderStatus']) expect(source).not.toContain('目的仓') expect(wrapper.text()).not.toContain('税率') @@ -529,6 +579,9 @@ describe('GlobalSmartLoadingView', () => { expect(source).toContain('.container-pane { max-height: min(58vh, 460px); }') expect(source).toContain('grid-template-columns: minmax(390px, 1.1fr) minmax(320px, .9fr) minmax(460px, 1.35fr)') expect(source).toContain('min-height: 112px') + expect(source).toContain("label: '预计到货时间'") + expect(source).toContain("label: '订单状态'") + expect(source).toContain(':page-sizes="[20, 50, 100]"') }) it('uses overflow-aware channel tabs with a compact right-aligned summary', () => { diff --git a/src/modules/container-loading/pages/GlobalSmartLoadingView.vue b/src/modules/container-loading/pages/GlobalSmartLoadingView.vue index b0e32d80..b59e6917 100644 --- a/src/modules/container-loading/pages/GlobalSmartLoadingView.vue +++ b/src/modules/container-loading/pages/GlobalSmartLoadingView.vue @@ -54,8 +54,10 @@ const selectedCandidate = ref(null) const assignForm = reactive({ packageCount: 1 }) const quantityDrafts = reactive>({}) const planGroupsByInboundId = ref(new Map()) -const query = ref({ customerId: '', inboundCode: '' }) +const query = ref({ customerId: '', inboundCode: '', expectedArrivalRange: [], orderStatus: '' }) const appliedQuery = ref({ ...query.value }) +const candidatePage = ref(1) +const candidatePageSize = ref(20) let selectionRequest = 0 const canAssign = computed(() => access.can('platform:firstleg:smart-loading:assign')) @@ -171,9 +173,23 @@ const volumePercent = computed(() => volumeLimit.value ? totalVolume.value / vol const weightPercent = computed(() => weightLimit.value ? totalWeight.value / weightLimit.value * 100 : 0) const displayedVolumePercent = computed(() => percentage(volumePercent.value)) const displayedWeightPercent = computed(() => percentage(weightPercent.value)) +const candidateOrderStatusLabels: Record = { + DRAFT: '草稿', SUBMITTED: '待审核', PENDING_REVIEW: '待审核', APPROVED: '已审核', ACCEPTED: '已受理', + REJECTED: '已驳回', PROCESSING: '处理中', WAIT_MEASURE: '待测量数据', WAIT_SPLIT: '待分柜', + LOADING: '装柜中', LOADED: '装柜完成', COMPLETED: '已完成', DELIVERED: '派送完成', CANCELLED: '已取消', +} +function candidateOrderStatusLabel(status: string) { + return candidateOrderStatusLabels[status] || '未知状态' +} +const candidateOrderStatusOptions = computed(() => [...new Set(candidates.value + .map((candidate) => candidate.orderStatusSnapshot?.trim().toUpperCase() || '') + .filter(Boolean))] + .map((status) => ({ value: status, label: candidateOrderStatusLabel(status) }))) const queryFields = computed(() => [ { key: 'customerId', type: 'select', label: '客户', placeholder: '全部客户', width: 220, options: customers.value.filter((customer) => customer.status === 'ACTIVE').map((customer) => ({ label: customer.tenantName, value: String(customer.id) })), props: { filterable: true, loading: customersLoading.value } }, { key: 'inboundCode', type: 'input', label: '入仓码', placeholder: '请输入入仓码', width: 240 }, + { key: 'expectedArrivalRange', type: 'daterange', label: '预计到货时间', placeholder: '请选择预计到货时间范围', width: 300 }, + { key: 'orderStatus', type: 'select', label: '订单状态', placeholder: '全部订单状态', width: 200, options: candidateOrderStatusOptions.value }, ]) const filteredContainers = computed(() => containers.value.filter((item) => ( @@ -184,13 +200,27 @@ const filteredContainers = computed(() => containers.value.filter((item) => ( const filteredCandidates = computed(() => { const customerId = String(appliedQuery.value.customerId || '') const inboundCode = String(appliedQuery.value.inboundCode || '').trim().toLowerCase() + const orderStatus = String(appliedQuery.value.orderStatus || '').trim().toUpperCase() + const arrivalRange = Array.isArray(appliedQuery.value.expectedArrivalRange) + ? appliedQuery.value.expectedArrivalRange.map((value) => String(value || '').slice(0, 10)) + : [] const channelCode = selectedContainer.value?.channelCode || activeChannelCode.value return displayedCandidates.value.filter((item) => { if (!channelCode || item.channelCode !== channelCode) return false if (customerId && !item.sourceCandidates.some((member) => member.accountId === customerId)) return false - return !inboundCode || item.memberInboundCodes.some((code) => code.toLowerCase().includes(inboundCode)) + if (inboundCode && !item.memberInboundCodes.some((code) => code.toLowerCase().includes(inboundCode))) return false + if (orderStatus && !item.sourceCandidates.some((member) => member.orderStatusSnapshot?.trim().toUpperCase() === orderStatus)) return false + if (arrivalRange.length === 2 && !item.sourceCandidates.some((member) => { + const arrivalDate = member.expectedArrivalAt?.slice(0, 10) || '' + return arrivalDate && arrivalDate >= arrivalRange[0] && arrivalDate <= arrivalRange[1] + })) return false + return true }) }) +const pagedCandidates = computed(() => { + const start = (candidatePage.value - 1) * candidatePageSize.value + return filteredCandidates.value.slice(start, start + candidatePageSize.value) +}) function preferredContainerId() { const visible = filteredContainers.value @@ -273,6 +303,7 @@ async function loadSelectedData(preserved: Record = {}) { clearSelectionData() if (!container) return if (container.planStatus === 'COMPLETED') return + candidatePage.value = 1 selectionLoading.value = true try { const [candidateResult, planResult] = await Promise.all([ @@ -351,6 +382,7 @@ async function selectChannel(channelCode: string) { } async function applyFilters(model: QueryModel) { appliedQuery.value = { ...model } + candidatePage.value = 1 if (!filteredContainers.value.some((item) => item.id === selectedContainerId.value)) { selectedContainerId.value = preferredContainerId() await loadSelectedData() @@ -548,9 +580,13 @@ onMounted(() => { void load(); void loadCustomers() })
候选装柜明细{{ activeChannel?.name || '未选择渠道' }} · {{ selectedContainer?.warehouseName || '请先选择柜次' }}
+ parameters: Record } const basePath = '/platform/v1/files/export-tasks' diff --git a/src/modules/downloads/pages/DownloadCenterView.vue b/src/modules/downloads/pages/DownloadCenterView.vue index d1a43321..c5b3badf 100644 --- a/src/modules/downloads/pages/DownloadCenterView.vue +++ b/src/modules/downloads/pages/DownloadCenterView.vue @@ -27,6 +27,7 @@ type DownloadFilters = QueryModel & { const exportTypeMeta: Record = { CUSTOMER_LIST: { label: '客户列表', retryPermission: 'platform:customer:export' }, + DOMESTIC_WAREHOUSE_ORDER_LIST: { label: '国内仓订单列表', retryPermission: 'platform:order:export' }, FINANCE_CUSTOMER_ACCOUNT_LIST: { label: '客户账户', retryPermission: 'platform:finance:customer-account:export' }, FINANCE_CUSTOMER_ACCOUNT_LEDGER: { label: '客户账户流水', retryPermission: 'platform:finance:customer-account:export' }, FINANCE_FEE_SOURCE_LIST: { label: '客户应收应付费用来源', retryPermission: 'platform:finance:customer-arap:export' }, diff --git a/src/modules/inbounds/fee/InboundFeePanel.test.ts b/src/modules/inbounds/fee/InboundFeePanel.test.ts index 7ed571a6..d9d84726 100644 --- a/src/modules/inbounds/fee/InboundFeePanel.test.ts +++ b/src/modules/inbounds/fee/InboundFeePanel.test.ts @@ -152,7 +152,7 @@ describe('InboundFeePanel', () => { expect(wrapper.findAll('.calculation-flow > div > span').map((node) => node.text())).toEqual([ '实际重量', '实际体积', - '折算量', + '体积重量', '取大后的计费量', ]) expect(wrapper.findAll('.fee-detail-grid h4').map((node) => node.text())).toEqual([ diff --git a/src/modules/inbounds/fee/InboundFeePanel.vue b/src/modules/inbounds/fee/InboundFeePanel.vue index 4d2280d0..190bb852 100644 --- a/src/modules/inbounds/fee/InboundFeePanel.vue +++ b/src/modules/inbounds/fee/InboundFeePanel.vue @@ -376,13 +376,13 @@ defineExpose({ reload: loadFee })
-

计费依据

实际量与折算量取大后计费,金额以服务端快照为准。

+

计费依据

实际重量与体积重量取大后计费,金额以服务端快照为准。

{{ taxModeLabel(snapshot.taxMode) }}
实际重量{{ formatNumber(snapshot.actualWeightKg, 3) }} KG
实际体积{{ formatNumber(snapshot.actualVolumeCbm, 4) }} CBM
-
折算量{{ formatNumber(snapshot.convertedQuantity, snapshot.roundingScale) }} {{ chargeUnitLabel }}系数 {{ formatNumber(snapshot.conversionFactor, 4) }}
+
体积重量{{ formatNumber(snapshot.convertedQuantity, snapshot.roundingScale) }} {{ chargeUnitLabel }}系数 {{ formatNumber(snapshot.conversionFactor, 4) }}
取大后的计费量{{ formatNumber(snapshot.chargeableQuantity, snapshot.roundingScale) }} {{ chargeUnitLabel }}
diff --git a/src/modules/unified-orders/pages/UnifiedOrdersView.requests.test.ts b/src/modules/unified-orders/pages/UnifiedOrdersView.requests.test.ts index 5b2888a4..10af8bde 100644 --- a/src/modules/unified-orders/pages/UnifiedOrdersView.requests.test.ts +++ b/src/modules/unified-orders/pages/UnifiedOrdersView.requests.test.ts @@ -1,6 +1,8 @@ import { flushPromises, shallowMount } from '@vue/test-utils' import { beforeEach, describe, expect, it, vi } from 'vitest' import { listPlatformUnifiedOrders } from '@/modules/unified-orders/api/unifiedOrders' +import { createPlatformExportTask } from '@/modules/downloads' +import { showExportTaskCreatedDialog } from '@/shared/components/exportTaskCreatedDialog' import UnifiedOrdersView from './UnifiedOrdersView.vue' vi.mock('vue-router', () => ({ @@ -11,6 +13,8 @@ vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ vi.mock('@/modules/iam', () => ({ listPlatformTenants: vi.fn().mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 0 }) })) vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn().mockResolvedValue([]) })) vi.mock('@/modules/inbounds', () => ({ downloadWeightImage: vi.fn(), getInboundMeasurement: vi.fn() })) +vi.mock('@/modules/downloads', () => ({ createPlatformExportTask: vi.fn() })) +vi.mock('@/shared/components/exportTaskCreatedDialog', () => ({ showExportTaskCreatedDialog: vi.fn() })) vi.mock('@/modules/unified-orders/workflows', () => ({ UnifiedOrderWorkflowPanel: { template: '
' } })) vi.mock('@/modules/unified-orders/api/coLoadGroups', () => ({ cancelPlatformCoLoadGroup: vi.fn(), @@ -45,7 +49,7 @@ function mountView() { AdminPage: { template: '
' }, ListPageLayout: { template: '
' }, QueryToolbar: { name: 'QueryToolbar', props: ['modelValue'], emits: ['update:modelValue', 'search', 'reset'], template: '
' }, - DataTable: { name: 'DataTable', props: ['page', 'pageSize'], emits: ['update:page', 'update:pageSize'], template: '
' }, + DataTable: { name: 'DataTable', props: ['page', 'pageSize'], emits: ['update:page', 'update:pageSize', 'selection-change'], template: '
' }, 'el-button': { template: '' }, }, }, @@ -55,6 +59,8 @@ function mountView() { describe('UnifiedOrdersView list request coordination', () => { beforeEach(() => { vi.mocked(listPlatformUnifiedOrders).mockReset().mockResolvedValue(emptyPage()) + vi.mocked(createPlatformExportTask).mockReset().mockResolvedValue({ taskName: '国内仓订单', taskNo: 'EXP-1' } as never) + vi.mocked(showExportTaskCreatedDialog).mockReset().mockResolvedValue(false) }) it('loads once initially and once for query, reset, status tab and pagination', async () => { @@ -135,4 +141,48 @@ describe('UnifiedOrdersView list request coordination', () => { await flushPromises() expect(listPlatformUnifiedOrders).toHaveBeenLastCalledWith(expect.objectContaining({ fullContainer: undefined })) }) + + it('exports the selected business order numbers without loading other pages', async () => { + const wrapper = mountView() + await flushPromises() + wrapper.getComponent({ name: 'DataTable' }).vm.$emit('selection-change', [ + { orderId: '1', orderNo: 'ORDER-1', tenantId: '8' }, + { orderId: '2', orderNo: 'ORDER-2', tenantId: '8' }, + ]) + await wrapper.vm.$nextTick() + + await wrapper.findAll('button').find((button) => button.text().includes('导出'))!.trigger('click') + await flushPromises() + + expect(createPlatformExportTask).toHaveBeenCalledWith({ + exportType: 'DOMESTIC_WAREHOUSE_ORDER_LIST', format: 'XLSX', taskName: '国内仓订单-已选2票', + parameters: { selectedOrderNos: ['ORDER-1', 'ORDER-2'] }, + }) + expect(listPlatformUnifiedOrders).toHaveBeenCalledTimes(1) + }) + + it('exports every result matching the applied filters instead of the current page', async () => { + const wrapper = mountView() + await flushPromises() + const toolbar = wrapper.getComponent({ name: 'QueryToolbar' }) + toolbar.vm.$emit('update:modelValue', { + ...toolbar.props('modelValue'), inboundCode: ' IN-8 ', loaded: 'false', createdRange: ['2026-08-01', '2026-08-31'], + }) + toolbar.vm.$emit('search') + await flushPromises() + + await wrapper.findAll('button').find((button) => button.text() === '导出')!.trigger('click') + await flushPromises() + + expect(createPlatformExportTask).toHaveBeenCalledWith(expect.objectContaining({ + exportType: 'DOMESTIC_WAREHOUSE_ORDER_LIST', taskName: '国内仓订单-当前筛选', + parameters: expect.objectContaining({ + inboundCode: 'IN-8', loaded: false, + createdFrom: '2026-08-01T00:00:00', createdTo: '2026-08-31T23:59:59', + }), + })) + expect(createPlatformExportTask).not.toHaveBeenCalledWith(expect.objectContaining({ + parameters: expect.objectContaining({ page: expect.anything(), size: expect.anything() }), + })) + }) }) diff --git a/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts b/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts index 34fae74f..66b05d99 100644 --- a/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts +++ b/src/modules/unified-orders/pages/UnifiedOrdersView.test.ts @@ -117,7 +117,8 @@ describe('UnifiedOrdersView contract', () => { expect(columnSource).not.toContain("label: '配载状态'") expect(columnSource).not.toContain("key: 'recommendedPalletType'") expect(columnSource).toContain("key: 'requestedPickupAt', prop: 'requestedPickupAt', label: '预约提货时间'") - expect(columnSource.match(/defaultVisible: false/g)).toHaveLength(14) + expect(columnSource.match(/defaultVisible: false/g)).toHaveLength(13) + expect(columnSource).toContain("key: 'expectedArrivalAt', prop: 'expectedArrivalAt', label: '预计到货时间', minWidth: 160, slotName: 'field' }") expect(columnSource).toContain("key: 'effectiveSailingAt', prop: 'effectiveSailingAt', label: '开船时间', minWidth: 180, slotName: 'field' }") expect(columnSource).toContain("key: 'effectivePortArrivalAt', prop: 'effectivePortArrivalAt', label: '到港时间', minWidth: 180, slotName: 'field' }") expect(source).toContain('preference-key="unified-orders-table-v2"') @@ -162,6 +163,15 @@ describe('UnifiedOrdersView contract', () => { expect(source).toContain('') }) + it('uses one asynchronous export entry for either selected orders or the applied filter snapshot', () => { + expect(source).toContain("exportType: 'DOMESTIC_WAREHOUSE_ORDER_LIST'") + expect(source).toContain("parameters: selectedOrderNos.length") + expect(source).toContain('? { selectedOrderNos }') + expect(source).toContain(': exportFilterParameters()') + expect(source).toContain("can('platform:order:export') && can('platform:download:list')") + expect(source).not.toContain('page: page.value,\n size: pageSize.value,\n })\n const task = await createPlatformExportTask') + }) + it('states only the domestic warehouse and channel qualification without local destination or split blocking', () => { const createSource = source.slice(source.indexOf('function openCoLoadCreate'), source.indexOf('async function batchApproveOrders')) const dialogSource = source.slice(source.indexOf(' { }) it('renders order detail as a routed full page instead of a drawer', () => { - expect(source).toContain('