import { flushPromises, shallowMount } from '@vue/test-utils' import { ElMessageBox } from 'element-plus' import { beforeEach, describe, expect, it, vi } from 'vitest' import { ApiResponseError } from '@/shared/api/http' import { listPlatformUnifiedOrders } from '@/modules/unified-orders/api/unifiedOrders' import { batchQueryPlatformCoLoadGroups, cancelPlatformCoLoadGroup, createPlatformCoLoadGroup } from '@/modules/unified-orders/api/coLoadGroups' import UnifiedOrdersView from './UnifiedOrdersView.vue' vi.mock('vue-router', () => ({ useRoute: () => ({ params: {}, query: {} }), useRouter: () => ({ push: vi.fn() }) })) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) })) 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/unified-orders/workflows', () => ({ UnifiedOrderWorkflowPanel: { template: '
' } })) vi.mock('@/modules/unified-orders/api/unifiedOrders', () => ({ addPlatformUnifiedOrderNote: vi.fn(), approvePlatformUnifiedOrder: vi.fn(), batchApprovePlatformUnifiedOrders: vi.fn(), getPlatformUnifiedOrder: vi.fn(), listPlatformUnifiedOrderFeeDetails: vi.fn(), listPlatformUnifiedOrders: vi.fn(), unifiedOrderStatusOptions: [{ value: 'WAIT_SPLIT', label: '待分柜' }], })) vi.mock('@/modules/unified-orders/api/coLoadGroups', () => ({ batchQueryPlatformCoLoadGroups: vi.fn(), cancelPlatformCoLoadGroup: vi.fn(), createPlatformCoLoadGroup: vi.fn(), })) const rows = ['5001', '5002'].map((orderId, index) => ({ orderId, tenantId: `600${index}`, orderNo: `ORD-${index + 1}`, mainStatus: 'WAIT_SPLIT' as const, nextAction: null, relation: null, internalOrderStatus: 'WAIT_SPLIT', customerId: `600${index}`, customerName: `客户${index + 1}`, channelName: 'NY-EXX', receivingWarehouseId: '8', receivingWarehouseName: '义乌仓', destinationWarehouseId: '9', destinationWarehouseName: 'ONT8', inboundId: `700${index}`, inboundVersion: 1, inboundCode: `IN-${index + 1}`, expectedShipDate: null, totalPackageCount: 5, locationCodes: null, actualGrossWeightKg: 10, actualVolumeCbm: 1, measurement: null, preload: null, loading: null, delivery: null, transport: null, createdAt: null, updatedAt: null, expectedArrivalAt: null, feeReviewStatus: null, requestedPickupAt: null, totalPalletCount: null, chargeableVolumeCbm: null, volumetricWeightKg: null, totalNetWeightKg: null, measurementCompletedAt: null, inboundCompletedAt: null, expressCompany: null, expressTrackingNo: null, skuNos: null, totalQuantity: null, shipmentId: null, referenceId: null, })) const group = { groupId: '9001', groupNo: 'CLG-9001', status: 'ACTIVE' as const, warehouseId: '8', channelCode: 'NY-EXX', lockedPlanId: null, version: 0, fullPackageCount: 10, members: rows.map((row) => ({ tenantId: String(row.tenantId), orderId: String(row.orderId), inboundId: String(row.inboundId), fullPackageCount: 5 })), createdAt: '2026-08-29T10:00:00', updatedAt: '2026-08-29T10:00:00', } function mountView() { return shallowMount(UnifiedOrdersView, { global: { directives: { loading: () => undefined }, stubs: { AdminPage: { template: '
' }, ListPageLayout: { template: '
' }, QueryToolbar: { template: '
' }, DataTable: { name: 'DataTable', props: ['data'], emits: ['selection-change'], template: '
' }, AppDialog: { name: 'AppDialog', props: ['modelValue', 'confirmText'], emits: ['confirm', 'update:modelValue'], template: '
' }, ApiErrorMeta: { props: ['details'], template: '
{{ details.code }} {{ details.requestId }} {{ details.traceId }}
' }, CopyButton: { props: ['value', 'label'], template: '' }, 'el-button': { template: '' }, 'el-tag': { template: '' }, 'el-alert': { props: ['title'], template: '
{{ title }}
' }, } }, }) } describe('UnifiedOrdersView co-load group behavior', () => { beforeEach(() => { vi.clearAllMocks() vi.mocked(listPlatformUnifiedOrders).mockResolvedValue({ records: rows, page: 1, size: 20, total: 2, pages: 1 }) vi.mocked(batchQueryPlatformCoLoadGroups).mockResolvedValue([]) }) it('keeps both selected orders and backend tracing evidence when create validation fails', async () => { vi.mocked(createPlatformCoLoadGroup).mockRejectedValue(new ApiResponseError('成员路线不一致', 'A0409', 409, undefined, 'request-coload', 'trace-coload')) const wrapper = mountView() await flushPromises() await wrapper.get('[data-testid="select"]').trigger('click') await wrapper.findAll('button').find((button) => button.text().includes('设为同柜不可拆组'))!.trigger('click') await wrapper.get('[data-testid="dialog-confirm"]').trigger('click') await flushPromises() expect(createPlatformCoLoadGroup).toHaveBeenCalledWith(['5001', '5002']) expect(wrapper.text()).toContain('设为同柜不可拆组(2)') expect(wrapper.text()).toContain('成员路线不一致') expect(wrapper.text()).toContain('A0409 request-coload trace-coload') expect(listPlatformUnifiedOrders).toHaveBeenCalledTimes(1) }) it('shows group number and member count and cancels only the whole ACTIVE group', async () => { const multiInboundGroup = { ...group, fullPackageCount: 13, members: [group.members[0], { ...group.members[0], inboundId: '7003', fullPackageCount: 3 }, group.members[1]], } vi.mocked(batchQueryPlatformCoLoadGroups).mockResolvedValue(rows.map((row) => ({ orderId: row.orderId, group: multiInboundGroup }))) vi.mocked(cancelPlatformCoLoadGroup).mockResolvedValue({ ...multiInboundGroup, status: 'CANCELLED', version: 1 }) vi.spyOn(ElMessageBox, 'prompt').mockResolvedValue({ value: '计划调整', action: 'confirm' } as never) const wrapper = mountView() await flushPromises() expect(wrapper.text()).toContain('CLG-9001') expect(wrapper.text()).toContain('CLG-9001 · 2 票 · 未锁定') await wrapper.get('[aria-label="查看同柜组 CLG-9001 详情"]').trigger('click') expect(wrapper.text()).toContain('2 票 · 3 个入仓片段 · 未锁定') expect(wrapper.text()).toContain('13 箱') expect(wrapper.text()).toContain('7003') expect(wrapper.findAll('[data-copy="7003"]')).toHaveLength(1) expect(wrapper.findAll('[data-copy="5001"]')).toHaveLength(2) await wrapper.findAll('button').find((button) => button.text().includes('取消同柜组'))!.trigger('click') await flushPromises() expect(cancelPlatformCoLoadGroup).toHaveBeenCalledWith('9001', 0, '计划调整') expect(ElMessageBox.prompt).toHaveBeenCalledWith(expect.stringContaining('不支持单独移除成员'), '取消同柜不可拆组', expect.any(Object)) }) })