import ElementPlus, { ElMessage, ElMessageBox } from 'element-plus' import { AxiosError } from 'axios' import { createPinia, setActivePinia } from 'pinia' import { flushPromises, mount } from '@vue/test-utils' import { beforeEach, describe, expect, it, vi } from 'vitest' import QueryToolbar from '@/shared/components/QueryToolbar.vue' import GlobalSmartLoadingView from './GlobalSmartLoadingView.vue' import { getSmartLoadingPlanByBooking, listContainers, listSmartLoadingCandidates, recommendSmartLoading, removeSmartLoadingItem, } from '../api/platformContainerLoading' const { routerPush } = vi.hoisted(() => ({ routerPush: vi.fn() })) vi.mock('vue-router', () => ({ useRouter: () => ({ push: routerPush }), })) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }), })) vi.mock('../api/platformContainerLoading', () => ({ listContainers: vi.fn(), getSmartLoadingPlanByBooking: vi.fn(), listSmartLoadingCandidates: vi.fn(), createSmartLoadingPlan: vi.fn(), assignSmartLoadingItem: vi.fn(), recommendSmartLoading: vi.fn(), adjustSmartLoadingItem: vi.fn(), removeSmartLoadingItem: vi.fn(), pushSmartLoadingPreload: vi.fn(), })) const container = { id: '9007199254740993', planId: '9007199254740994', bookingNo: 'BK-1', cabinetNo: 'CB-1', containerNo: 'MSCU-1', warehouseId: '8', warehouseName: '义乌仓', containerType: '40HQ', planStatus: 'EDITING' as const, version: 1, billOfLadingNo: null, vesselName: null, voyageNo: null, shippingCompanyId: null, shippingCompanyName: null, departurePortCode: null, departurePortName: null, destinationPortCode: null, destinationPortName: null, destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', capacityCbm: 68, maxWeightKg: 28000, terminalTruckingCompany: null, loadingAt: null, estimatedSailingAt: null, actualSailingAt: null, estimatedPortArrivalAt: null, actualPortArrivalAt: null, estimatedWarehouseArrivalAt: null, actualWarehouseArrivalAt: null, } const plan = { id: '99', bookingId: container.id, planNo: 'LP-99', status: 'EDITING' as const, containerNo: 'MSCU-1', voyageNo: null, version: 2, 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 }], } describe('GlobalSmartLoadingView', () => { beforeEach(() => { setActivePinia(createPinia()) 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', inboundDetailId: '6', orderId: '7', orderNo: 'ORD-2', inboundCode: 'IN-2', warehouseId: '8', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '另一企业', goodsName: '鞋类', availablePackageCount: 12, unitWeightKg: 10, unitVolumeCbm: 0.2 }]) }) it('loads real containers, candidates and the selected plan into the three-column workbench', async () => { const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() expect(listContainers).toHaveBeenCalledWith({ page: 1, size: 100 }) expect(getSmartLoadingPlanByBooking).toHaveBeenCalledWith(container.id) expect(listSmartLoadingCandidates).toHaveBeenCalledWith(container.warehouseId) expect(wrapper.text()).toContain('候选订单与货物') expect(wrapper.text()).toContain('ORD-2') expect(wrapper.text()).toContain('CB-1 装柜方案') expect(wrapper.text()).toContain('LP-99') expect(wrapper.text()).toContain('ORD-1') }) it('keeps unsupported prototype filters out of the real workbench', async () => { const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() const statusField = wrapper.findComponent(QueryToolbar).props('fields') .find((field: { key: string }) => field.key === 'status') expect(statusField?.options).toEqual([ { label: '待装柜', value: 'WAIT_LOADING' }, { label: '预装柜中', value: 'EDITING' }, { label: '拣货中', value: 'LOADING' }, ]) expect(wrapper.text()).not.toContain('税率') expect(wrapper.text()).not.toContain('是否退税') expect(wrapper.text()).not.toContain('美森正班') }) it('prefers an editable container on first load so pushed plans do not show a false editing error', async () => { const pushed = { ...container, id: '9007199254740995', planStatus: 'LOADING' as const } vi.mocked(listContainers).mockResolvedValue({ records: [pushed, container], page: 1, size: 100, total: 2, pages: 1 }) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() expect(getSmartLoadingPlanByBooking).toHaveBeenCalledWith(container.id) expect(wrapper.text()).toContain('CB-1 装柜方案') }) it('does not overwrite the container status when selecting it', async () => { const waiting = { ...container, id: '9007199254740995', planId: '9007199254740996', cabinetNo: 'CB-WAIT', planStatus: 'WAIT_LOADING' as const } vi.mocked(listContainers).mockResolvedValue({ records: [container, waiting], page: 1, size: 100, total: 2, pages: 1 }) vi.mocked(getSmartLoadingPlanByBooking).mockImplementation(async (bookingId) => ( bookingId === waiting.id ? { ...plan, bookingId: waiting.id, status: 'LOADING' as const } : plan )) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() const waitingOption = wrapper.findAll('.container-option').find((item) => item.text().includes('CB-WAIT'))! expect(waitingOption.text()).toContain('待装柜') await waitingOption.trigger('click') await flushPromises() expect(getSmartLoadingPlanByBooking).toHaveBeenCalledWith(waiting.id) expect(waitingOption.text()).toContain('待装柜') expect(waitingOption.text()).not.toContain('拣货中') }) it('keeps picking containers editable while hiding the push action', async () => { const pushed = { ...container, id: '9007199254740995', planId: '9007199254740996', cabinetNo: 'CB-2', planStatus: 'LOADING' as const } const pickingPlan = { ...plan, bookingId: pushed.id, status: 'LOADING' as const } vi.mocked(listContainers).mockResolvedValue({ records: [pushed], page: 1, size: 100, total: 1, pages: 1 }) vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue(pickingPlan) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() expect(getSmartLoadingPlanByBooking).toHaveBeenCalledWith(pushed.id) expect(listSmartLoadingCandidates).toHaveBeenCalledWith(pushed.warehouseId) expect(wrapper.text()).toContain('CB-2') expect(wrapper.text()).toContain('拣货中') expect(wrapper.text()).toContain('智能推荐') expect(wrapper.text()).toContain('加入') expect(wrapper.findAll('button').some((item) => item.text().trim() === '推送预装')).toBe(false) }) it('hides completed containers from the smart loading workbench', async () => { const completed = { ...container, id: '9007199254740997', planId: '9007199254740998', cabinetNo: 'CB-3', planStatus: 'COMPLETED' as const } vi.mocked(listContainers).mockResolvedValue({ records: [completed], page: 1, size: 100, total: 1, pages: 1 }) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() expect(getSmartLoadingPlanByBooking).not.toHaveBeenCalled() expect(wrapper.text()).not.toContain('CB-3') expect(wrapper.text()).not.toContain('装柜完成') }) it('refreshes the latest plan after A0409 without losing the selected container', async () => { const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue({ ...plan, version: 3 }) vi.mocked(recommendSmartLoading).mockRejectedValue(new AxiosError( 'conflict', 'ERR_BAD_REQUEST', undefined, undefined, { data: { code: 'A0409', message: '数据已被修改', requestId: 'request-1', traceId: 'trace-1' }, status: 200, statusText: 'OK', headers: {}, config: {} as never }, )) const button = wrapper.findAll('button').find((item) => item.text().includes('智能推荐')) expect(button).toBeDefined() await button!.trigger('click') await flushPromises() expect(recommendSmartLoading).toHaveBeenCalledWith(plan.id, 2) expect(getSmartLoadingPlanByBooking).toHaveBeenCalledTimes(2) expect(wrapper.text()).toContain('版本 3') expect(wrapper.text()).toContain('已刷新最新版本') expect(wrapper.text()).toContain('request-1') }) it('shows a picking-specific message when a picked order cannot be removed', async () => { const loadingContainer = { ...container, planStatus: 'LOADING' as const } const loadingPlan = { ...plan, status: 'LOADING' as const } vi.mocked(listContainers).mockResolvedValue({ records: [loadingContainer], page: 1, size: 100, total: 1, pages: 1 }) vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue(loadingPlan) vi.mocked(removeSmartLoadingItem).mockRejectedValue(new AxiosError( 'conflict', 'ERR_BAD_REQUEST', undefined, undefined, { data: { code: 'A0409', message: '已有拣货记录的明细不能删除' }, status: 200, statusText: 'OK', headers: {}, config: {} as never }, )) vi.spyOn(ElMessageBox, 'confirm').mockResolvedValue('confirm' as never) const errorMessage = vi.spyOn(ElMessage, 'error').mockImplementation(() => undefined as never) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() const removeButton = wrapper.find('.row-actions .el-button--danger') expect(removeButton.exists()).toBe(true) await removeButton.trigger('click') await flushPromises() expect(removeSmartLoadingItem).toHaveBeenCalled() expect(errorMessage).toHaveBeenCalledWith('该订单在拣货中不许删除') expect(wrapper.text()).toContain('该订单在拣货中不许删除') }) })