diff --git a/src/modules/container-loading/api/platformContainerLoading.test.ts b/src/modules/container-loading/api/platformContainerLoading.test.ts index 27fa86be..3582800e 100644 --- a/src/modules/container-loading/api/platformContainerLoading.test.ts +++ b/src/modules/container-loading/api/platformContainerLoading.test.ts @@ -68,7 +68,10 @@ describe('platform container loading API', () => { .mockResolvedValueOnce({ code: '200', data: container }) .mockResolvedValueOnce({ code: '200', data: { records: [{ accountId: 1, customerId: 1, inboundId: 2, orderId: 4, orderNo: 'ORD-1', inboundCode: 'IN-1', orderStatusSnapshot: 'WAIT_SPLIT', expectedArrivalAt: '2026-09-10T09:30:00', warehouseId: 5, destinationWarehouseId: 6, channelCode: 'NY_EXX', channelName: '纽约EXX海铁', customerName: '示例企业', 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, groupMembers: [] }], page: '1', size: '20', total: '1', pages: '1' } }) expect((await getContainer('9')).id).toBe(String(container.id)) - expect(await listSmartLoadingCandidates({ channelGroupCodes: ['EXX', 'HEDE'], warehouseId: '5', page: 1, size: 20 })).toMatchObject({ records: [{ accountId: '1', customerId: '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 }], total: 1 }) + expect(await listSmartLoadingCandidates({ channelGroupCodes: ['EXX', 'HEDE'], warehouseId: '5', orderStatuses: ['PENDING_REVIEW', 'WAIT_MEASURE'], page: 1, size: 20 })).toMatchObject({ records: [{ accountId: '1', customerId: '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 }], total: 1 }) + const candidateParams = vi.mocked(http.get).mock.calls.at(-1)?.[1]?.params as URLSearchParams + expect(candidateParams.getAll('orderStatuses')).toEqual(['PENDING_REVIEW', 'WAIT_MEASURE']) + expect(candidateParams.has('orderStatus')).toBe(false) const params = vi.mocked(http.get).mock.calls.at(-1)?.[1]?.params as URLSearchParams expect(params.getAll('channelGroupCodes')).toEqual(['EXX', 'HEDE']) expect(params.get('warehouseId')).toBe('5') @@ -112,7 +115,7 @@ describe('platform container loading API', () => { page: '1', size: '200', total: '1', pages: '1', } }) - const result = await listSmartLoadingWorkbenchContainers({ channelGroupCodes: ['MATSON_CLX', 'EXX'], customerId: '12', inboundCode: 'IN-1', createdStart: '2026-09-01', createdEnd: '2026-09-03', orderStatus: 'WAIT_SPLIT', page: 1, size: 200 }) + const result = await listSmartLoadingWorkbenchContainers({ channelGroupCodes: ['MATSON_CLX', 'EXX'], customerId: '12', inboundCode: 'IN-1', createdStart: '2026-09-01', createdEnd: '2026-09-03', orderStatuses: ['WAIT_SPLIT'], page: 1, size: 200 }) expect(result).toMatchObject({ records: [{ bookingId: '9007199254740993', warehouseId: '8', planId: '9007199254740994', version: 2 }], total: 1 }) const params = vi.mocked(http.get).mock.calls.at(-1)?.[1]?.params as URLSearchParams @@ -121,7 +124,8 @@ describe('platform container loading API', () => { expect(params.get('inboundCode')).toBe('IN-1') expect(params.get('createdStart')).toBe('2026-09-01') expect(params.get('createdEnd')).toBe('2026-09-03') - expect(params.get('orderStatus')).toBe('WAIT_SPLIT') + expect(params.has('orderStatus')).toBe(false) + expect(params.has('orderStatuses')).toBe(false) expect(params.has('bookingId')).toBe(false) }) diff --git a/src/modules/container-loading/api/platformContainerLoading.ts b/src/modules/container-loading/api/platformContainerLoading.ts index d6d82bda..79ac0b16 100644 --- a/src/modules/container-loading/api/platformContainerLoading.ts +++ b/src/modules/container-loading/api/platformContainerLoading.ts @@ -228,20 +228,23 @@ export async function completeContainerClearance(containerId: string, expectedVe return document(unwrapApiResponse(response)) } -function smartLoadingQueryParams(query: SmartLoadingCandidateQuery) { +function smartLoadingQueryParams(query: SmartLoadingCandidateQuery, includeOrderStatuses = false) { const params = new URLSearchParams() for (const channelGroupCode of query.channelGroupCodes) params.append('channelGroupCodes', channelGroupCode) + if (includeOrderStatuses) { + for (const orderStatus of query.orderStatuses || []) params.append('orderStatuses', orderStatus) + } const optionalParams: Array<[string, string | number | undefined]> = [ ['warehouseId', query.warehouseId], ['customerId', query.customerId], ['inboundCode', query.inboundCode], ['createdStart', query.createdStart], ['createdEnd', query.createdEnd], - ['orderStatus', query.orderStatus], ['page', query.page], ['size', query.size], + ['page', query.page], ['size', query.size], ] for (const [key, value] of optionalParams) if (value !== undefined && value !== '') params.append(key, String(value)) return params } export async function listSmartLoadingCandidates(query: SmartLoadingCandidateQuery) { - const params = smartLoadingQueryParams(query) + const params = smartLoadingQueryParams(query, true) const response = await http.get, ApiEnvelope>(`${smartLoadingPath}/inbound-candidates`, { params }) const result = unwrapApiResponse(response) return { diff --git a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts index 688b41a3..a481f868 100644 --- a/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts +++ b/src/modules/container-loading/pages/GlobalSmartLoadingView.test.ts @@ -280,7 +280,7 @@ describe('GlobalSmartLoadingView', () => { expect(wrapper.text()).toContain('MSCU-DG') const containerRequestCount = vi.mocked(listSmartLoadingWorkbenchContainers).mock.calls.length - wrapper.findComponent(QueryToolbar).vm.$emit('search', { warehouseId: '18', customerId: '', inboundCode: '', createdRange: [], orderStatus: '' }) + wrapper.findComponent(QueryToolbar).vm.$emit('search', { warehouseId: '18', customerId: '', inboundCode: '', createdRange: [], orderStatuses: [] }) await flushPromises() expect(wrapper.text()).toContain('MSCU-DG') @@ -307,7 +307,7 @@ describe('GlobalSmartLoadingView', () => { await flushPromises() const containerRequestCount = vi.mocked(listSmartLoadingWorkbenchContainers).mock.calls.length - wrapper.findComponent(QueryToolbar).vm.$emit('search', { warehouseId: '18', customerId: '', inboundCode: '', createdRange: [], orderStatus: '' }) + wrapper.findComponent(QueryToolbar).vm.$emit('search', { warehouseId: '18', customerId: '', inboundCode: '', createdRange: [], orderStatuses: [] }) await flushPromises() expect(wrapper.find('.channel-tags .el-checkbox-button.is-checked').text()).toBe('美森正班') @@ -338,7 +338,7 @@ describe('GlobalSmartLoadingView', () => { const containerRequestCount = vi.mocked(listSmartLoadingWorkbenchContainers).mock.calls.length wrapper.findComponent(QueryToolbar).vm.$emit('search', { - warehouseId: '', customerId: '', inboundCode: 'NYEXX2609030002', createdRange: [], orderStatus: '', + warehouseId: '', customerId: '', inboundCode: 'NYEXX2609030002', createdRange: [], orderStatuses: [], }) await flushPromises() @@ -363,7 +363,7 @@ describe('GlobalSmartLoadingView', () => { expect(source).toContain(': candidate.availablePackageCount') }) - it('filters candidates by expected arrival range and order status', async () => { + it('filters candidates by creation range and multiple order statuses', async () => { vi.mocked(listSmartLoadingCandidates) .mockResolvedValueOnce(candidatePage([ { ...candidate, inboundId: '51', inboundCode: 'IN-MATCH', orderStatusSnapshot: 'WAIT_SPLIT', expectedArrivalAt: '2026-09-11T08:00:00' }, @@ -376,7 +376,7 @@ describe('GlobalSmartLoadingView', () => { const containerRequestCount = vi.mocked(listSmartLoadingWorkbenchContainers).mock.calls.length wrapper.getComponent(QueryToolbar).vm.$emit('search', { - customerId: '', inboundCode: '', orderStatus: 'WAIT_SPLIT', createdRange: ['2026-09-10', '2026-09-12'], + customerId: '', inboundCode: '', orderStatuses: ['WAIT_SPLIT', 'LOADING'], createdRange: ['2026-09-10', '2026-09-12'], }) await flushPromises() @@ -384,7 +384,7 @@ describe('GlobalSmartLoadingView', () => { expect(candidates).toContain('IN-MATCH') expect(candidates).not.toContain('IN-WRONG-STATUS') expect(candidates).not.toContain('IN-WRONG-DATE') - expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], createdStart: '2026-09-10', createdEnd: '2026-09-12', orderStatus: 'WAIT_SPLIT', page: 1, size: 20 }) + expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], createdStart: '2026-09-10', createdEnd: '2026-09-12', orderStatuses: ['WAIT_SPLIT', 'LOADING'], page: 1, size: 20 }) expect(listSmartLoadingWorkbenchContainers).toHaveBeenCalledTimes(containerRequestCount) expect(listSmartLoadingWorkbenchContainers).toHaveBeenLastCalledWith({ channelGroupCodes: ['MATSON_CLX'], page: 1, size: 200 }) }) @@ -520,16 +520,16 @@ describe('GlobalSmartLoadingView', () => { 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(['warehouseId', 'customerId', 'inboundCode', 'createdRange', 'orderStatus']) + const fields = wrapper.findComponent(QueryToolbar).props('fields') as Array<{ key: string; options?: Array<{ label: string; value: string }>; props?: Record }> + expect(fields.map((field) => field.key)).toEqual(['warehouseId', 'customerId', 'inboundCode', 'createdRange', 'orderStatuses']) expect(fields.find((field) => field.key === 'warehouseId')?.options).toEqual([{ label: '义乌仓', value: '8' }]) - expect(fields.find((field) => field.key === 'orderStatus')?.options).toEqual([ - { value: '', label: '全部' }, + expect(fields.find((field) => field.key === 'orderStatuses')?.options).toEqual([ { value: 'PENDING_REVIEW', label: '待审核' }, { value: 'WAIT_MEASURE', label: '待测量数据' }, { value: 'WAIT_SPLIT', label: '待分柜' }, { value: 'LOADING', label: '装柜中' }, ]) + expect(fields.find((field) => field.key === 'orderStatuses')?.props).toMatchObject({ multiple: true, collapseTags: true, maxCollapseTags: 2 }) expect(source).not.toContain('目的仓') expect(wrapper.text()).not.toContain('税率') diff --git a/src/modules/container-loading/pages/GlobalSmartLoadingView.vue b/src/modules/container-loading/pages/GlobalSmartLoadingView.vue index fa284539..3dc99403 100644 --- a/src/modules/container-loading/pages/GlobalSmartLoadingView.vue +++ b/src/modules/container-loading/pages/GlobalSmartLoadingView.vue @@ -63,8 +63,8 @@ const selectedCandidate = ref(null) const assignForm = reactive({ packageCount: 1 }) const quantityDrafts = reactive>({}) const planGroupsByInboundId = ref(new Map()) -const query = ref({ warehouseId: '', customerId: '', inboundCode: '', createdRange: [], orderStatus: '' }) -const appliedQuery = ref({ ...query.value }) +const query = ref({ warehouseId: '', customerId: '', inboundCode: '', createdRange: [], orderStatuses: [] }) +const appliedQuery = ref({ ...query.value, orderStatuses: [] }) const candidatePage = ref(1) const candidatePageSize = ref(20) const candidateTotal = ref(0) @@ -185,7 +185,6 @@ const weightPercent = computed(() => weightLimit.value ? totalWeight.value / wei const displayedVolumePercent = computed(() => percentage(volumePercent.value)) const displayedWeightPercent = computed(() => percentage(weightPercent.value)) const candidateOrderStatusOptions = [ - { value: '', label: '全部' }, { value: 'PENDING_REVIEW', label: '待审核' }, { value: 'WAIT_MEASURE', label: '待测量数据' }, { value: 'WAIT_SPLIT', label: '待分柜' }, @@ -199,7 +198,7 @@ const queryFields = computed(() => [ { key: 'customerId', type: 'select', label: '客户', placeholder: '全部客户', width: 180, 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: 210 }, { key: 'createdRange', type: 'daterange', label: '创建日期', placeholder: '请选择创建日期范围', width: 260 }, - { key: 'orderStatus', type: 'select', label: '订单状态', placeholder: '全部订单状态', width: 160, options: candidateOrderStatusOptions }, + { key: 'orderStatuses', type: 'select', label: '订单状态', placeholder: '全部订单状态', width: 210, options: candidateOrderStatusOptions, props: { multiple: true, collapseTags: true, collapseTagsTooltip: true, maxCollapseTags: 2 } }, ]) const filteredContainers = computed(() => containers.value) @@ -300,7 +299,9 @@ function candidateQueryParams(): Parameters[0 const warehouseId = String(appliedQuery.value.warehouseId || '') const customerId = String(appliedQuery.value.customerId || '') const inboundCode = String(appliedQuery.value.inboundCode || '').trim() - const orderStatus = String(appliedQuery.value.orderStatus || '') as typeof params.orderStatus + const orderStatuses = Array.isArray(appliedQuery.value.orderStatuses) + ? appliedQuery.value.orderStatuses.map((value) => String(value)).filter(Boolean) as NonNullable + : [] if (warehouseId) params.warehouseId = warehouseId if (customerId) params.customerId = customerId if (inboundCode) params.inboundCode = inboundCode @@ -308,7 +309,7 @@ function candidateQueryParams(): Parameters[0 params.createdStart = createdRange[0] params.createdEnd = createdRange[1] } - if (orderStatus) params.orderStatus = orderStatus + if (orderStatuses.length) params.orderStatuses = orderStatuses return params } @@ -443,7 +444,10 @@ async function selectChannels(codes: string[]) { await loadWorkbenchData() } async function applyFilters(model: QueryModel) { - appliedQuery.value = { ...model } + appliedQuery.value = { + ...model, + orderStatuses: Array.isArray(model.orderStatuses) ? model.orderStatuses.map((value) => String(value)) : [], + } candidatePage.value = 1 await loadCandidateData() } diff --git a/src/modules/container-loading/types.ts b/src/modules/container-loading/types.ts index 8e0b4743..28e69baa 100644 --- a/src/modules/container-loading/types.ts +++ b/src/modules/container-loading/types.ts @@ -279,7 +279,7 @@ export interface SmartLoadingCandidateQuery { inboundCode?: string createdStart?: string createdEnd?: string - orderStatus?: 'PENDING_REVIEW' | 'WAIT_MEASURE' | 'WAIT_SPLIT' | 'LOADING' + orderStatuses?: Array<'PENDING_REVIEW' | 'WAIT_MEASURE' | 'WAIT_SPLIT' | 'LOADING'> page: number size: number }