import ElementPlus from 'element-plus' import { AxiosError } from 'axios' import { createPinia, setActivePinia } from 'pinia' import { createMemoryHistory, createRouter } from 'vue-router' import { flushPromises, mount } from '@vue/test-utils' import { beforeEach, describe, expect, it, vi } from 'vitest' import { readFileSync } from 'node:fs' import { resolve } from 'node:path' import SmartLoadingWorkbenchView from './SmartLoadingWorkbenchView.vue' import { getContainer, getSmartLoadingPlanByBooking, listSmartLoadingCandidates, recommendSmartLoading } from '../api/platformContainerLoading' vi.mock('../api/platformContainerLoading', () => ({ getContainer: 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(), })) vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) })) 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, } describe('SmartLoadingWorkbenchView', () => { beforeEach(() => { setActivePinia(createPinia()) vi.clearAllMocks() vi.mocked(getContainer).mockResolvedValue(container) vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue({ id: '99', bookingId: container.id, planNo: 'LP-99', status: 'EDITING', containerNo: 'MSCU-1', voyageNo: null, version: 2, items: [] }) vi.mocked(listSmartLoadingCandidates).mockResolvedValue([{ accountId: '1', inboundId: '2', inboundDetailId: '3', orderId: '4', orderNo: 'ORD-1', inboundCode: 'IN-1', warehouseId: '8', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '示例企业', goodsName: '服装', availablePackageCount: 12, unitWeightKg: 10, unitVolumeCbm: 0.2 }]) }) it('restores the real draft and explains candidate capacity', async () => { const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/container-loading/:containerId/smart-loading', component: SmartLoadingWorkbenchView }] }) await router.push(`/container-loading/${container.id}/smart-loading`); await router.isReady() const wrapper = mount(SmartLoadingWorkbenchView, { global: { plugins: [ElementPlus, router] } }) await flushPromises() expect(getContainer).toHaveBeenCalledWith(container.id) expect(getSmartLoadingPlanByBooking).toHaveBeenCalledWith(container.id) expect(listSmartLoadingCandidates).toHaveBeenCalledWith('8') expect(wrapper.text()).toContain('LP-99') expect(wrapper.text()).toContain('ORD-1') expect(wrapper.text()).toContain('按 110% 上限') }) it('keeps picking containers editable while hiding the push action', async () => { vi.mocked(getContainer).mockResolvedValue({ ...container, planStatus: 'LOADING' }) vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue({ id: '99', bookingId: container.id, planNo: 'LP-99', status: 'LOADING', containerNo: 'MSCU-1', voyageNo: null, version: 2, items: [] }) const router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/container-loading/:containerId/smart-loading', component: SmartLoadingWorkbenchView }] }) await router.push(`/container-loading/${container.id}/smart-loading`); await router.isReady() const wrapper = mount(SmartLoadingWorkbenchView, { global: { plugins: [ElementPlus, router] } }) await flushPromises() expect(getSmartLoadingPlanByBooking).toHaveBeenCalledWith(container.id) expect(listSmartLoadingCandidates).toHaveBeenCalledWith(container.warehouseId) expect(wrapper.text()).toContain('拣货中') expect(wrapper.text()).toContain('智能推荐') expect(wrapper.text()).toContain('ORD-1') expect(wrapper.findAll('button').some((button) => button.text().includes('推送预装'))).toBe(false) }) it('refreshes the latest plan after A0409', async () => { vi.mocked(getSmartLoadingPlanByBooking) .mockResolvedValueOnce({ id: '99', bookingId: container.id, planNo: 'LP-99', status: 'EDITING', containerNo: 'MSCU-1', voyageNo: null, version: 2, items: [] }) .mockResolvedValueOnce({ id: '99', bookingId: container.id, planNo: 'LP-99', status: 'EDITING', containerNo: 'MSCU-1', voyageNo: null, version: 3, items: [] }) 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 router = createRouter({ history: createMemoryHistory(), routes: [{ path: '/container-loading/:containerId/smart-loading', component: SmartLoadingWorkbenchView }] }) await router.push(`/container-loading/${container.id}/smart-loading`); await router.isReady() const wrapper = mount(SmartLoadingWorkbenchView, { global: { plugins: [ElementPlus, router] } }) await flushPromises() await wrapper.findAll('button').find((button) => button.text().includes('智能推荐'))!.trigger('click') await flushPromises() expect(getSmartLoadingPlanByBooking).toHaveBeenCalledTimes(2) expect(wrapper.text()).toContain('版本 3') expect(wrapper.text()).toContain('已刷新最新版本') expect(wrapper.text()).toContain('request-1') }) it('uses bordered row actions and keeps push independent from containerNo', () => { const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/SmartLoadingWorkbenchView.vue'), 'utf8') expect(source).toContain('size="small" type="primary" plain') expect(source).toContain('size="small" type="danger" plain') expect(source).toContain("canPush && plan?.status === 'EDITING'") expect(source).not.toContain("canPush && plan?.status === 'EDITING' && container?.containerNo") expect(source).not.toContain('link type="primary" :disabled="!routeMatches(row)') expect(source.match(/]*\bstripe\b/g)).toHaveLength(2) }) })