2026-08-10 15:36:53 +08:00
|
|
|
import ElementPlus from 'element-plus'
|
2026-08-11 11:15:44 +08:00
|
|
|
import { AxiosError } from 'axios'
|
2026-08-10 15:36:53 +08:00
|
|
|
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'
|
2026-08-12 18:00:26 +08:00
|
|
|
import { readFileSync } from 'node:fs'
|
|
|
|
|
import { resolve } from 'node:path'
|
2026-08-10 15:36:53 +08:00
|
|
|
import SmartLoadingWorkbenchView from './SmartLoadingWorkbenchView.vue'
|
2026-08-11 11:15:44 +08:00
|
|
|
import { getContainer, getSmartLoadingPlanByBooking, listSmartLoadingCandidates, recommendSmartLoading } from '../api/platformContainerLoading'
|
2026-08-10 15:36:53 +08:00
|
|
|
|
|
|
|
|
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(),
|
|
|
|
|
}))
|
2026-08-11 11:15:44 +08:00
|
|
|
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) }))
|
2026-08-10 15:36:53 +08:00
|
|
|
|
|
|
|
|
const container = {
|
|
|
|
|
id: '9007199254740993', planId: '9007199254740994', bookingNo: 'BK-1', cabinetNo: 'CB-1', containerNo: 'MSCU-1',
|
2026-08-17 12:31:27 +08:00
|
|
|
warehouseId: '8', warehouseName: '义乌仓', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', containerType: '40HC', planStatus: 'EDITING' as const, version: 1,
|
2026-08-10 15:36:53 +08:00
|
|
|
billOfLadingNo: null, vesselName: null, voyageNo: null, shippingCompanyId: null, shippingCompanyName: null,
|
|
|
|
|
departurePortCode: null, departurePortName: null, destinationPortCode: null, destinationPortName: null,
|
2026-08-17 12:31:27 +08:00
|
|
|
destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', capacityCbm: 76, maxWeightKg: 28000,
|
2026-08-10 15:36:53 +08:00
|
|
|
terminalTruckingCompany: null, loadingAt: null, estimatedSailingAt: null, actualSailingAt: null,
|
|
|
|
|
estimatedPortArrivalAt: null, actualPortArrivalAt: null, estimatedWarehouseArrivalAt: null, actualWarehouseArrivalAt: null,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('SmartLoadingWorkbenchView', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
setActivePinia(createPinia())
|
2026-08-11 11:15:44 +08:00
|
|
|
vi.clearAllMocks()
|
2026-08-10 15:36:53 +08:00
|
|
|
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: [] })
|
2026-08-14 19:02:34 +08:00
|
|
|
vi.mocked(listSmartLoadingCandidates).mockResolvedValue([{ accountId: '1', inboundId: '2', inboundDetailId: '3', orderId: '4', orderNo: 'ORD-1', inboundCode: 'IN-1', warehouseId: '8', channelCode: 'ZBYHK', channelName: '美森正班海卡包税', destinationWarehouseId: '9', destinationWarehouseCode: 'LAX9', destinationWarehouseName: 'LAX9', customerName: '示例企业', goodsName: '服装', availablePackageCount: 12, unitWeightKg: 10, unitVolumeCbm: 0.2 }])
|
2026-08-10 15:36:53 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
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)
|
2026-08-12 18:43:54 +08:00
|
|
|
expect(listSmartLoadingCandidates).toHaveBeenCalledWith(container.id, '8')
|
2026-08-14 19:59:32 +08:00
|
|
|
expect(wrapper.text()).toContain('MSCU-1 装柜方案')
|
|
|
|
|
expect(wrapper.text()).not.toContain('LP-99')
|
|
|
|
|
expect(wrapper.text()).toContain('IN-1')
|
|
|
|
|
expect(wrapper.text()).not.toContain('ORD-1')
|
|
|
|
|
expect(wrapper.text()).toContain('110% 容量上限')
|
2026-08-10 15:36:53 +08:00
|
|
|
})
|
2026-08-11 11:15:44 +08:00
|
|
|
|
2026-08-14 19:59:32 +08:00
|
|
|
it('keeps picking containers out of intelligent loading and links to the preload list', async () => {
|
2026-08-11 11:15:44 +08:00
|
|
|
vi.mocked(getContainer).mockResolvedValue({ ...container, planStatus: 'LOADING' })
|
2026-08-11 11:59:39 +08:00
|
|
|
vi.mocked(getSmartLoadingPlanByBooking).mockResolvedValue({ id: '99', bookingId: container.id, planNo: 'LP-99', status: 'LOADING', containerNo: 'MSCU-1', voyageNo: null, version: 2, items: [] })
|
2026-08-11 11:15:44 +08:00
|
|
|
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()
|
|
|
|
|
|
2026-08-14 19:59:32 +08:00
|
|
|
expect(getSmartLoadingPlanByBooking).not.toHaveBeenCalled()
|
|
|
|
|
expect(listSmartLoadingCandidates).not.toHaveBeenCalled()
|
2026-08-15 17:04:00 +08:00
|
|
|
expect(wrapper.text()).toContain('该柜次已进入拣货流程')
|
2026-08-14 19:59:32 +08:00
|
|
|
expect(wrapper.text()).toContain('查看预装清单')
|
|
|
|
|
expect(wrapper.text()).not.toContain('智能推荐')
|
|
|
|
|
expect(wrapper.text()).not.toContain('ORD-1')
|
2026-08-11 11:15:44 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
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('已刷新最新版本')
|
|
|
|
|
expect(wrapper.text()).toContain('request-1')
|
|
|
|
|
})
|
2026-08-12 18:00:26 +08:00
|
|
|
|
|
|
|
|
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")
|
2026-08-15 17:04:00 +08:00
|
|
|
expect(source).toContain('推送后生成预装清单,不会直接开放 PDA')
|
2026-08-12 18:00:26 +08:00
|
|
|
expect(source).not.toContain('link type="primary" :disabled="!routeMatches(row)')
|
2026-08-14 19:59:32 +08:00
|
|
|
expect(source).not.toContain("label: '目的仓'")
|
|
|
|
|
expect(source).not.toContain('plan.planNo')
|
2026-08-12 18:16:44 +08:00
|
|
|
expect(source.match(/<DataTable[^>]*\bstripe\b/g)).toHaveLength(2)
|
2026-08-15 17:04:00 +08:00
|
|
|
expect(source).toContain('<span>柜次号</span>')
|
|
|
|
|
expect(source).toContain('<span>集装箱号</span>')
|
|
|
|
|
expect(source).not.toContain('当前柜号')
|
2026-08-12 18:00:26 +08:00
|
|
|
})
|
2026-08-10 15:36:53 +08:00
|
|
|
})
|