import ElementPlus, { ElInputNumber, 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 { readFileSync } from 'node:fs' import { resolve } from 'node:path' 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('@/modules/iam', () => ({ listPlatformTenants: vi.fn().mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 0 }), })) 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: '义乌仓', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', containerType: '40HC', 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: 76, maxWeightKg: 28000, terminalTruckingCompanyId: null, 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, canAssign: true, canEdit: true, blockedReason: null, finalCompletionStatus: 'NOT_STARTED' as const, 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 }], } 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', 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 }]) }) 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.id, container.warehouseId) expect(wrapper.text()).toContain('候选装柜明细') expect(wrapper.text()).toContain('美森正班海卡包税') expect(wrapper.text()).toContain('另一企业') expect(wrapper.text()).toContain('MSCU-1 装柜方案') expect(wrapper.text()).not.toContain('LP-99') expect(wrapper.text()).toContain('示例企业') expect(wrapper.text()).not.toContain('服装') expect(wrapper.text()).not.toContain('鞋类') expect(wrapper.text()).not.toContain('SKU-1') expect(wrapper.text()).toContain('IN-1') expect(wrapper.text()).toContain('IN-2') expect(wrapper.text()).not.toContain('ORD-1') expect(wrapper.text()).not.toContain('ORD-2') }) it('uses the top-level container channel snapshot without expecting a nested snapshot', async () => { vi.mocked(listContainers).mockResolvedValue({ records: [{ ...container, channelCode: 'EXX_RAIL_NY', channelName: '纽约EXX海铁' }], page: 1, size: 100, total: 1, pages: 1, }) vi.mocked(listSmartLoadingCandidates).mockResolvedValue([{ accountId: '1', inboundId: '5', orderId: '7', orderNo: 'ORD-2', inboundCode: 'IN-2', warehouseId: '8', channelCode: 'EXX_RAIL_NY', channelName: '纽约EXX海铁', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '另一企业', goodsSummary: '鞋类', availablePackageCount: 12, unitGrossWeightKg: 10, unitNetWeightKg: 9, averageUnitVolumeCbm: 0.2, availableVolumeCbm: 2.4 }]) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() expect(wrapper.text()).toContain('纽约EXX海铁') expect(wrapper.text()).not.toContain('缺少渠道快照') expect(getSmartLoadingPlanByBooking).toHaveBeenCalledWith(container.id) }) it('shows one user-facing container number and hides internal manual or split references', async () => { vi.mocked(listContainers).mockResolvedValue({ records: [{ ...container, cabinetNo: 'CAB-260814-0001', containerNo: 'SPLIT-814022117898chain1' }], page: 1, size: 100, total: 1, pages: 1, }) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() expect(wrapper.text()).toContain('CAB-260814-0001') expect(wrapper.text()).not.toContain('SPLIT-814022117898chain1') expect(wrapper.text()).not.toContain('LP-99') }) it('explains simulated empty cabinets instead of presenting an internal id as a container number', async () => { vi.mocked(listContainers).mockResolvedValue({ records: [{ ...container, cabinetNo: '2088475410134773762', containerNo: null, channelName: '美森正班海卡包税', warehouseName: '义乌国内仓', containerType: '40HC', }], page: 1, size: 100, total: 1, pages: 1, }) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() const option = wrapper.find('.container-option') expect(option.text()).toContain('模拟柜次') expect(option.text()).toContain('柜次号:待生成') expect(option.text()).toContain('集装箱号:待录入') expect(option.text()).not.toContain('开始装柜前录入集装箱号') expect(option.text()).toContain('渠道:美森正班海卡包税') expect(option.text()).toContain('仓库:义乌国内仓') expect(option.text()).toContain('柜型:40HC') expect(option.text()).not.toContain('2088475410134773762') expect(wrapper.text()).not.toContain('真实柜号') }) it('groups containers by channel and reloads candidates when the channel changes', async () => { const other = { ...container, id: '9007199254740998', planId: '9007199254740999', cabinetNo: 'CB-2', containerNo: 'MSCU-2', channelCode: 'HDHK', channelName: '合德海卡包税', } vi.mocked(listContainers).mockResolvedValue({ records: [container, other], page: 1, size: 100, total: 2, pages: 1 }) vi.mocked(getSmartLoadingPlanByBooking).mockImplementation(async (bookingId) => ({ ...plan, bookingId })) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() expect(wrapper.findAll('.container-option')).toHaveLength(1) expect(wrapper.text()).toContain('MSCU-1') expect(wrapper.text()).not.toContain('MSCU-2') expect(wrapper.text()).not.toContain('个柜次') const tab = wrapper.findAll('[role="tab"]').find((item) => item.text().includes('合德海卡包税')) expect(tab).toBeDefined() await tab!.trigger('click') await flushPromises() expect(wrapper.findAll('.container-option')).toHaveLength(1) expect(wrapper.text()).toContain('MSCU-2') expect(wrapper.text()).not.toContain('MSCU-1') expect(listSmartLoadingCandidates).toHaveBeenLastCalledWith(other.id, other.warehouseId) }) it('filters candidates by an active name-only customer selector and accountId', () => { const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/GlobalSmartLoadingView.vue'), 'utf8') expect(source).toContain("listPlatformTenants({ page: 1, size: 200, status: 'ACTIVE' })") expect(source).toContain("label: customer.tenantName") expect(source).toContain('item.accountId !== customerId') expect(source).not.toContain('customer.tenantCode') }) it('defaults candidate assignment to all available boxes', () => { const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/GlobalSmartLoadingView.vue'), 'utf8') expect(source).toContain('assignForm.packageCount = candidate.availablePackageCount') }) it('rounds capacity percentages and hides column settings in embedded tables', async () => { vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue({ ...plan, items: [{ ...plan.items[0], volumeCbm: 0.187, grossWeightKg: 13.6 }], }) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() expect(wrapper.text()).toContain('0.22%') expect(wrapper.text()).toContain('0.04%') expect(wrapper.text()).not.toContain('0.169999999') expect(wrapper.findAll('button[aria-label="列设置"]')).toHaveLength(0) }) it('keeps only customer and inbound code filters', 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(source).not.toContain('目的仓') expect(wrapper.text()).not.toContain('税率') expect(wrapper.text()).not.toContain('是否退税') }) it('prefers an editing container on first load while keeping loading containers available', 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('MSCU-1 装柜方案') }) it('keeps waiting and editing empty containers without exposing their status labels', async () => { const waiting = { ...container, id: '9007199254740995', planId: '9007199254740996', cabinetNo: 'CB-WAIT', containerNo: null, 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('CB-WAIT') expect(waitingOption.text()).not.toContain('待装柜') await waitingOption.trigger('click') await flushPromises() expect(getSmartLoadingPlanByBooking).toHaveBeenCalledWith(waiting.id) expect(waitingOption.text()).toContain('CB-WAIT') expect(waitingOption.text()).not.toContain('拣货中') }) it('keeps picking containers available when the plan capability allows increments', async () => { const pushed = { ...container, id: '9007199254740995', planId: '9007199254740996', cabinetNo: 'CB-2', planStatus: 'LOADING' as const } vi.mocked(listContainers).mockResolvedValue({ records: [pushed], page: 1, size: 100, total: 1, pages: 1 }) vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue({ ...plan, bookingId: pushed.id, status: 'LOADING', pdaTaskId: '700', pdaTaskStatus: 'LOADING', pdaTaskVersion: 4 }) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() expect(getSmartLoadingPlanByBooking).toHaveBeenCalledWith(pushed.id) expect(listSmartLoadingCandidates).toHaveBeenCalledWith(pushed.id, pushed.warehouseId) expect(wrapper.findAll('.container-option')).toHaveLength(1) expect(wrapper.text()).toContain('CB-2') expect(wrapper.text()).not.toContain('已下发 PDA,仍可维护未完成的装柜明细') expect(wrapper.text()).not.toContain('刷新 PDA 明细与版本') expect(wrapper.text()).toContain('示例企业') }) 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()).toContain('当前柜次已完成装柜,不可继续编辑') expect(wrapper.text()).not.toContain('缺少渠道快照') }) it('isolates a missing-channel row without hiding another valid container', async () => { const missingChannel = { ...container, id: '9007199254740997', cabinetNo: 'CB-MISSING', channelCode: null, channelName: null } vi.mocked(listContainers).mockResolvedValue({ records: [missingChannel, container], page: 1, size: 100, total: 2, pages: 1 }) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() expect(wrapper.findAll('.container-option')).toHaveLength(1) expect(wrapper.text()).toContain('CB-1') expect(wrapper.text()).not.toContain('CB-MISSING') expect(wrapper.text()).not.toContain('缺少渠道快照') expect(getSmartLoadingPlanByBooking).toHaveBeenCalledWith(container.id) }) it('matches candidate goods by their own formal top-level channel code', async () => { vi.mocked(listSmartLoadingCandidates).mockResolvedValue([ { accountId: '1', inboundId: '5', orderId: '7', orderNo: 'ORD-2', inboundCode: 'IN-MATCH', 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 }, { accountId: '2', inboundId: '8', orderId: '10', orderNo: 'ORD-3', inboundCode: 'IN-OTHER', warehouseId: '8', channelCode: 'OTHER', channelName: '其他渠道', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '其他企业', goodsSummary: '其他货物', availablePackageCount: 8, unitGrossWeightKg: 10, unitNetWeightKg: 9, averageUnitVolumeCbm: 0.2, availableVolumeCbm: 1.6 }, ]) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() expect(wrapper.text()).toContain('匹配企业') expect(wrapper.text()).not.toContain('IN-OTHER') expect(wrapper.text()).not.toContain('其他货物') }) it('refreshes the latest plan after A0409 without losing the selected container', async () => { const warningMessage = vi.spyOn(ElMessage, 'warning').mockImplementation(() => undefined as never) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue({ ...plan, version: 3 }) vi.mocked(recommendSmartLoading).mockRejectedValueOnce(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 }, )).mockResolvedValueOnce({ ...plan, version: 4 }) 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(recommendSmartLoading).toHaveBeenCalledWith(plan.id, 3) expect(getSmartLoadingPlanByBooking).toHaveBeenCalledTimes(3) expect(warningMessage).not.toHaveBeenCalled() expect(wrapper.text()).not.toContain('装柜操作失败') expect(wrapper.text()).not.toContain('request-1') }) it('shows a picking-specific message when a picked order cannot be removed', async () => { 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()).not.toContain('装柜操作失败') expect(wrapper.text()).not.toContain('该订单在拣货中不许删除') }) it('removes every item with the latest returned plan version', async () => { const secondItem = { ...plan.items[0], id: '102', inboundDetailId: '4', inboundCode: 'IN-2' } const initialPlan = { ...plan, items: [plan.items[0], secondItem] } const afterFirst = { ...plan, version: 3, items: [secondItem] } const emptyPlan = { ...plan, version: 4, items: [] } vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValueOnce(initialPlan).mockResolvedValue(emptyPlan) vi.mocked(removeSmartLoadingItem).mockResolvedValueOnce(afterFirst).mockResolvedValueOnce(emptyPlan) vi.spyOn(ElMessageBox, 'confirm').mockResolvedValue('confirm' as never) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() const removeAllButton = wrapper.findAll('button').find((button) => button.text().includes('移除未拣')) expect(removeAllButton).toBeDefined() await removeAllButton!.trigger('click') await flushPromises() expect(removeSmartLoadingItem).toHaveBeenNthCalledWith(1, plan.id, plan.items[0].id, 2, 1) expect(removeSmartLoadingItem).toHaveBeenNthCalledWith(2, plan.id, secondItem.id, 3, 1) expect(wrapper.text()).toContain('当前柜次暂无装柜明细') }) it('shows one row per inbound code and removes all internal specification rows', async () => { const secondDetail = { ...plan.items[0], id: '102', inboundDetailId: '4', skuCode: 'SKU-2', productNameZh: '鞋类', packageCount: 4, grossWeightKg: 40, volumeCbm: 0.8, loadSequence: 2, } const groupedPlan = { ...plan, items: [plan.items[0], secondDetail] } const afterFirst = { ...plan, version: 3, items: [secondDetail] } const emptyPlan = { ...plan, version: 4, items: [] } vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValueOnce(groupedPlan).mockResolvedValue(emptyPlan) vi.mocked(removeSmartLoadingItem).mockResolvedValueOnce(afterFirst).mockResolvedValueOnce(emptyPlan) vi.spyOn(ElMessageBox, 'confirm').mockResolvedValue('confirm' as never) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() const inboundRows = wrapper.findAll('tr').filter((row) => row.text().includes('IN-1')) expect(inboundRows).toHaveLength(1) expect(inboundRows[0].text()).toContain('多规格;改箱数请移除后重加') expect(inboundRows[0].text()).toContain('100 kg') expect(inboundRows[0].text()).toContain('2 m³') await inboundRows[0].find('.el-button--danger').trigger('click') await flushPromises() expect(removeSmartLoadingItem).toHaveBeenNthCalledWith(1, plan.id, plan.items[0].id, 2, 1) expect(removeSmartLoadingItem).toHaveBeenNthCalledWith(2, plan.id, secondDetail.id, 3, 1) }) it('uses item capabilities to protect picked quantities and removal', async () => { vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue({ ...plan, status: 'LOADING', pdaTaskId: '700', pdaTaskStatus: 'LOADING', pdaTaskVersion: 4, items: [{ ...plan.items[0], pickedPackageCount: 3, minPackageCount: 3, canAdjust: true, canRemove: false }], }) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() expect(wrapper.text()).toContain('已拣 3 箱') expect(wrapper.findAll('.row-actions .el-button--danger')).toHaveLength(0) const quantityInput = wrapper.findAllComponents(ElInputNumber).find((input) => input.props('modelValue') === 6) expect(quantityInput?.props('min')).toBe(3) expect(wrapper.findAll('button').some((button) => button.text().includes('移除未拣'))).toBe(false) }) it('renders final completion capability as read-only with the server reason', async () => { vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue({ ...plan, canAssign: false, canEdit: false, blockedReason: '正在校验装柜证据', finalCompletionStatus: 'IN_PROGRESS', }) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() expect(wrapper.text()).toContain('正在最终确认,装柜方案暂时只读') expect(wrapper.text()).toContain('正在校验装柜证据') expect(wrapper.text()).not.toContain('请先完成拖车') expect(wrapper.findAll('.row-actions button')).toHaveLength(0) expect(wrapper.findAll('button').some((button) => button.text().includes('加入'))).toBe(false) }) it('limits adjusted package count to the assigned count plus remaining availability', async () => { vi.mocked(listSmartLoadingCandidates).mockResolvedValue([{ accountId: '1', inboundId: '2', orderId: '4', orderNo: 'ORD-1', inboundCode: 'IN-1', 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 }]) const wrapper = mount(GlobalSmartLoadingView, { global: { plugins: [ElementPlus] } }) await flushPromises() const maxima = wrapper.findAllComponents(ElInputNumber).map((input) => input.props('max')) expect(maxima).toContain(10) }) it('uses bordered assignment and adjustment actions at narrow widths', () => { const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/GlobalSmartLoadingView.vue'), 'utf8') expect(source).toContain('size="small"') expect(source).toContain('type="danger" plain') expect(source).toContain(':max="adjustmentMax(row)"') expect(source).not.toContain('link type="danger"') expect(source).toContain('@media (max-width: 720px)') expect(source.match(/ { const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/GlobalSmartLoadingView.vue'), 'utf8') expect(source).toContain('height: clamp(520px, 68vh, 720px); min-height: 0;') expect(source).toContain('.dispatch-pane { display: flex; min-width: 0; min-height: 0; flex-direction: column; }') expect(source).toContain('.candidate-pane :deep(.data-table),.plan-pane :deep(.data-table) { min-height: 0; flex: 1; }') expect(source).toContain('.container-list { display: grid; min-height: 0; flex: 1;') expect(source).toContain('grid-auto-rows: max-content;') expect(source).toContain('overflow-wrap: anywhere;') expect(source).toContain('.candidate-pane,.plan-pane { height: min(68vh, 560px); min-height: 420px; }') 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') }) it('uses overflow-aware channel tabs with a compact right-aligned summary', () => { const source = readFileSync(resolve(process.cwd(), 'src/modules/container-loading/pages/GlobalSmartLoadingView.vue'), 'utf8') expect(source).toContain('候选明细') expect(source).toContain('
柜次
') expect(source).toContain('
已装
') expect(source).toContain('
总重量
') expect(source).toContain('
总体积
') expect(source).toContain('.channel-tabs :deep(.el-tabs__content) { display: none; }') expect(source).not.toContain('class="summary-band"') expect(source).toContain('class="capacity-grid"') expect(source).toContain('智能推荐') expect(source).toContain('推送预装') expect(source).toContain('推送预装的流程说明已由操作确认框承载') expect(source).not.toContain("canPush && plan?.status === 'EDITING' && selectedContainer?.containerNo") }) })