197 lines
9.3 KiB
TypeScript
197 lines
9.3 KiB
TypeScript
import ElementPlus, { ElMessageBox } from 'element-plus'
|
|
import { createPinia, setActivePinia } from 'pinia'
|
|
import { flushPromises, mount } from '@vue/test-utils'
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import { useAuthStore } from '@/core/auth/auth'
|
|
import { ApiResponseError } from '@/shared/api/http'
|
|
import UnifiedOrderWorkflowPanel from './UnifiedOrderWorkflowPanel.vue'
|
|
import {
|
|
getEvidence,
|
|
getOutboundDispatch,
|
|
getPicking,
|
|
getPreload,
|
|
smartSplit,
|
|
voidEvidence,
|
|
} from '../api'
|
|
|
|
vi.mock('../api', async (importOriginal) => {
|
|
const original = await importOriginal<typeof import('../api')>()
|
|
return {
|
|
...original,
|
|
getEvidence: vi.fn(),
|
|
getOutboundDispatch: vi.fn(),
|
|
getPicking: vi.fn(),
|
|
getPreload: vi.fn(),
|
|
smartSplit: vi.fn(),
|
|
voidEvidence: vi.fn(),
|
|
}
|
|
})
|
|
|
|
function preload(cargoReference: string, version = 3) {
|
|
return {
|
|
id: '2086085512165634050', inboundId: '2086085512165634051', tenantId: '2086085512165634052',
|
|
status: 'PENDING', version,
|
|
items: [{ containerCode: 'CAB-01', cargoReference, packageCount: 2, volumeCbm: 1.25, remark: null }],
|
|
remark: null, createdAt: null, updatedAt: null,
|
|
}
|
|
}
|
|
|
|
function mountPanel(permissions: string[], mainStatus: 'WAIT_SPLIT' | 'LOADING' | 'LOADED' = 'WAIT_SPLIT') {
|
|
const pinia = createPinia()
|
|
setActivePinia(pinia)
|
|
useAuthStore().permissions = permissions
|
|
return mount(UnifiedOrderWorkflowPanel, {
|
|
props: {
|
|
tenantId: '2086085512165634052', inboundId: '2086085512165634051', orderId: '2086085512165634053',
|
|
mainStatus, inboundVersion: 4,
|
|
},
|
|
global: { plugins: [pinia, ElementPlus] },
|
|
})
|
|
}
|
|
|
|
describe('UnifiedOrderWorkflowPanel preload editor', () => {
|
|
beforeEach(() => {
|
|
vi.mocked(getOutboundDispatch).mockReset().mockResolvedValue(null as never)
|
|
vi.mocked(getEvidence).mockReset().mockResolvedValue({ inboundId: '2086085512165634051', evidences: [] })
|
|
vi.mocked(getPicking).mockReset().mockResolvedValue({ id: '51', inboundId: '2086085512165634051', status: 'PICKING', version: 2, expectedCount: 10, pickedCount: 4, remark: null })
|
|
vi.mocked(getPreload).mockReset().mockResolvedValue(preload('REF-OLD'))
|
|
vi.mocked(smartSplit).mockReset()
|
|
vi.mocked(voidEvidence).mockReset()
|
|
})
|
|
|
|
it('renders the formal preload table and keeps the operation column first', async () => {
|
|
const wrapper = mountPanel(['firstleg:inbound:preload-view', 'firstleg:inbound:split'])
|
|
await flushPromises()
|
|
expect(getPreload).toHaveBeenCalledWith('2086085512165634052', '2086085512165634051')
|
|
expect(wrapper.text()).toContain('入库预装清单')
|
|
expect(wrapper.text()).toContain('REF-OLD')
|
|
const headers = wrapper.findAll('.el-table__header-wrapper th').map((node) => node.text())
|
|
expect(headers[0]).toContain('操作')
|
|
expect(wrapper.find('button[aria-label="编辑第 1 条预装明细"]').exists()).toBe(true)
|
|
})
|
|
|
|
it('does not query or expose editing controls without permission', async () => {
|
|
const wrapper = mountPanel([])
|
|
await flushPromises()
|
|
expect(getPreload).not.toHaveBeenCalled()
|
|
expect(wrapper.text()).toContain('当前账号无预装清单查询权限')
|
|
expect(wrapper.find('button[aria-label="编辑第 1 条预装明细"]').exists()).toBe(false)
|
|
})
|
|
|
|
it('blocks an empty list before calling the real endpoint', async () => {
|
|
vi.mocked(getPreload).mockResolvedValue({ ...preload('REF-OLD'), items: [] })
|
|
const wrapper = mountPanel(['firstleg:inbound:preload-view', 'firstleg:inbound:split'])
|
|
await flushPromises()
|
|
const save = wrapper.findAll('button').find((button) => button.text().includes('保存分柜清单'))
|
|
expect(save).toBeTruthy()
|
|
await save!.trigger('click')
|
|
expect(smartSplit).not.toHaveBeenCalled()
|
|
expect(wrapper.text()).toContain('请至少添加一条预装明细')
|
|
})
|
|
|
|
it('refreshes the version but preserves submitted rows after a conflict', async () => {
|
|
vi.mocked(getPreload)
|
|
.mockResolvedValueOnce(preload('REF-OLD', 3))
|
|
.mockResolvedValueOnce(preload('REF-SERVER', 4))
|
|
vi.mocked(smartSplit).mockRejectedValue(new ApiResponseError('数据已被其他人修改,请刷新后重试', 'A0409', 409))
|
|
const wrapper = mountPanel(['firstleg:inbound:preload-view', 'firstleg:inbound:split'])
|
|
await flushPromises()
|
|
const save = wrapper.findAll('button').find((button) => button.text().includes('保存分柜清单'))
|
|
await save!.trigger('click')
|
|
await flushPromises()
|
|
expect(getPreload).toHaveBeenCalledTimes(2)
|
|
expect(wrapper.text()).toContain('系统已刷新最新数据,请核对后重新提交')
|
|
expect(wrapper.text()).toContain('REF-OLD')
|
|
expect(wrapper.text()).not.toContain('REF-SERVER')
|
|
})
|
|
|
|
it('keeps completed loading evidence visible and renders every active photo', async () => {
|
|
vi.mocked(getEvidence).mockResolvedValue({
|
|
inboundId: '2086085512165634051',
|
|
evidences: [
|
|
{ id: '1', inboundId: '2086085512165634051', tenantId: '2086085512165634052', evidenceType: 'PHOTO_STEP_1', fileId: '11', fileName: 'empty-1.jpg', sealNo: null, remark: null, version: 0, status: 'ACTIVE', createdAt: null, updatedAt: null },
|
|
{ id: '2', inboundId: '2086085512165634051', tenantId: '2086085512165634052', evidenceType: 'PHOTO_STEP_1', fileId: '12', fileName: 'empty-2.jpg', sealNo: null, remark: null, version: 0, status: 'ACTIVE', createdAt: null, updatedAt: null },
|
|
],
|
|
})
|
|
const wrapper = mountPanel(['platform:firstleg:inbound:loading:view'], 'LOADED')
|
|
await flushPromises()
|
|
|
|
expect(getEvidence).toHaveBeenCalledWith('2086085512165634052', '2086085512165634051')
|
|
expect(wrapper.text()).toContain('empty-1.jpg')
|
|
expect(wrapper.text()).toContain('empty-2.jpg')
|
|
expect(wrapper.text()).toContain('2 张')
|
|
expect(wrapper.text()).not.toContain('完成装柜')
|
|
expect(wrapper.findAll('button').some((button) => button.text() === '作废')).toBe(false)
|
|
})
|
|
|
|
it('uses a validated dialog when voiding evidence', async () => {
|
|
vi.mocked(getEvidence).mockResolvedValue({
|
|
inboundId: '2086085512165634051',
|
|
evidences: [
|
|
{ id: '1', inboundId: '2086085512165634051', tenantId: '2086085512165634052', evidenceType: 'PHOTO_STEP_1', fileId: '11', fileName: 'wrong.jpg', sealNo: null, remark: null, version: 2, status: 'ACTIVE', createdAt: null, updatedAt: null },
|
|
],
|
|
})
|
|
vi.mocked(voidEvidence).mockResolvedValue({} as never)
|
|
const prompt = vi.spyOn(ElMessageBox, 'prompt').mockResolvedValue({ value: '照片上传错误' } as never)
|
|
const wrapper = mountPanel(['platform:firstleg:inbound:loading:view', 'platform:firstleg:inbound:loading:edit'], 'LOADING')
|
|
await flushPromises()
|
|
|
|
const voidButton = wrapper.findAll('button').find((button) => button.text() === '作废')
|
|
expect(voidButton).toBeTruthy()
|
|
await voidButton!.trigger('click')
|
|
await flushPromises()
|
|
|
|
expect(prompt).toHaveBeenCalledWith(
|
|
'作废后照片仍会保留在审计记录中。',
|
|
'作废证据照片',
|
|
expect.objectContaining({
|
|
inputType: 'textarea',
|
|
inputPlaceholder: '请输入作废原因',
|
|
type: 'warning',
|
|
}),
|
|
)
|
|
const options = prompt.mock.calls[0]?.[2]
|
|
expect(options?.inputValidator?.(' ')).toBe('作废原因不能为空')
|
|
expect(voidEvidence).toHaveBeenCalledWith(
|
|
'2086085512165634052',
|
|
'2086085512165634051',
|
|
'1',
|
|
{ expectedVersion: 2, reason: '照片上传错误' },
|
|
)
|
|
prompt.mockRestore()
|
|
})
|
|
|
|
it('renders booking, trucking and picking backend statuses as business Chinese', async () => {
|
|
vi.mocked(getOutboundDispatch).mockResolvedValue({
|
|
orderId: '1', orderNo: 'ORD-1', inboundId: '2', inboundCode: 'IN-2', containerBookingId: '3', containerNo: 'CONT-3',
|
|
loadingPlanId: '4', loadingPlanStatus: 'LOADING', oceanBookingId: '5', oceanBookingStatus: 'RELEASED',
|
|
truckAppointmentId: '6', truckAppointmentStatus: 'PICKUP_IN_PROGRESS', gate: { passed: false, items: [] },
|
|
})
|
|
const wrapper = mountPanel(['platform:firstleg:inbound:picking:view'], 'LOADING')
|
|
await flushPromises()
|
|
|
|
expect(wrapper.text()).toContain('已放舱')
|
|
expect(wrapper.text()).toContain('提柜中')
|
|
expect(wrapper.text()).toContain('拣货中')
|
|
expect(wrapper.text()).not.toContain('RELEASED')
|
|
expect(wrapper.text()).not.toContain('PICKUP_IN_PROGRESS')
|
|
expect(wrapper.text()).not.toContain('PICKING')
|
|
})
|
|
|
|
it('does not expose unknown backend enum values', async () => {
|
|
vi.mocked(getOutboundDispatch).mockResolvedValue({
|
|
orderId: '1', orderNo: 'ORD-1', inboundId: '2', inboundCode: 'IN-2', containerBookingId: null, containerNo: null,
|
|
loadingPlanId: null, loadingPlanStatus: null, oceanBookingId: '5', oceanBookingStatus: 'NEW_BOOKING_STATE',
|
|
truckAppointmentId: '6', truckAppointmentStatus: 'NEW_TRUCK_STATE', gate: { passed: false, items: [] },
|
|
})
|
|
vi.mocked(getPicking).mockResolvedValue({ id: '51', inboundId: '2', status: 'NEW_PICK_STATE', version: 2, expectedCount: null, pickedCount: null, remark: null })
|
|
const wrapper = mountPanel(['platform:firstleg:inbound:picking:view'], 'LOADING')
|
|
await flushPromises()
|
|
|
|
expect(wrapper.text()).toContain('未知状态')
|
|
expect(wrapper.text()).not.toContain('NEW_BOOKING_STATE')
|
|
expect(wrapper.text()).not.toContain('NEW_TRUCK_STATE')
|
|
expect(wrapper.text()).not.toContain('NEW_PICK_STATE')
|
|
})
|
|
})
|