fix(dispatch): align dispatch actions and native return workflows

This commit is contained in:
2026-10-18 16:37:37 +08:00
parent 4806f88d7c
commit a5a1976924
29 changed files with 854 additions and 150 deletions
@@ -1,22 +1,24 @@
import ElementPlus from 'element-plus'
import ElementPlus, { ElMessage, ElMessageBox } from 'element-plus'
import { readFileSync } from 'node:fs'
import { resolve } from 'node:path'
import { flushPromises, mount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { listOutbounds } from '../api/outbounds'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { cancelOutbound, listOutbounds } from '../api/outbounds'
import OutboundsView from './OutboundsView.vue'
import StatusBadge from '@/shared/components/StatusBadge.vue'
const canPermission = vi.hoisted(() => vi.fn((_code: string) => true))
const routerPush = vi.hoisted(() => vi.fn())
const routeQuery = vi.hoisted(() => ({} as Record<string, string>))
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: canPermission }) }))
vi.mock('vue-router', () => ({ useRouter: () => ({ push: routerPush }) }))
vi.mock('vue-router', () => ({ useRoute: () => ({ query: routeQuery }), useRouter: () => ({ push: routerPush }) }))
vi.mock('../api/outbounds', () => ({ listOutbounds: vi.fn(), cancelOutbound: vi.fn(), createOneClickDropshipOutbound: vi.fn() }))
vi.mock('../api/inventory', () => ({ listInventoryBalances: vi.fn(), listInventoryPositions: vi.fn() }))
class ResizeObserverStub { observe() {} unobserve() {} disconnect() {} }
describe('OutboundsView permissions and navigation', () => {
afterEach(() => vi.restoreAllMocks())
it('distinguishes outbound stages and does not present unknown states as picking', async () => {
const statuses = [
['WAIT_PICK', '待拣货', 'warning'], ['WAIT_OUTBOUND', '待出库', 'warning'], ['PICKING', '拣货中', 'info'],
@@ -36,6 +38,8 @@ describe('OutboundsView permissions and navigation', () => {
})
beforeEach(() => {
Object.keys(routeQuery).forEach(key => delete routeQuery[key])
vi.mocked(cancelOutbound).mockReset().mockResolvedValue(null)
vi.stubGlobal('ResizeObserver', ResizeObserverStub)
vi.stubGlobal('localStorage', { getItem: () => null, setItem: () => undefined, removeItem: () => undefined })
vi.mocked(listOutbounds).mockReset().mockResolvedValue({ records: [{ id: '1', outboundNo: 'OUT-1', dispatchNo: 'PC-1', status: 'WAIT_PICK', palletCount: 1, packageCount: 2 }], page: 1, size: 20, total: 1, pages: 1 })
@@ -43,6 +47,27 @@ describe('OutboundsView permissions and navigation', () => {
routerPush.mockReset()
})
it('uses an original outbound deep link as the server query', async () => {
routeQuery.documentNo = 'OUT/FROM-DELIVERY'
const wrapper = mount(OutboundsView, { global: { plugins: [ElementPlus] } }); await flushPromises()
expect(listOutbounds).toHaveBeenCalledWith(expect.objectContaining({ documentNo: 'OUT/FROM-DELIVERY' }))
wrapper.unmount()
})
it('explains the cancellation order and routes the cancelled outbound to its original dispatch', async () => {
const prompt = vi.spyOn(ElMessageBox, 'prompt').mockResolvedValue({ value: '重新排车', action: 'confirm' } as Awaited<ReturnType<typeof ElMessageBox.prompt>>)
const success = vi.spyOn(ElMessage, 'success')
const wrapper = mount(OutboundsView, { global: { plugins: [ElementPlus] } }); await flushPromises()
vi.mocked(listOutbounds).mockResolvedValue({ records: [{ id: '1', outboundNo: 'OUT-1', dispatchNo: 'PC-1', status: 'CANCELLED', palletCount: 1, packageCount: 2 }], page: 1, size: 20, total: 1, pages: 1 })
await wrapper.findAll('.el-table__body-wrapper button').find(button => button.text() === '取消')!.trigger('click'); await flushPromises()
expect(prompt).toHaveBeenCalledWith(expect.stringContaining('出库取消后再取消原排车'), '取消出库单', expect.any(Object))
expect(cancelOutbound).toHaveBeenCalledExactlyOnceWith('OUT-1', '重新排车')
expect(success).toHaveBeenCalledWith(expect.stringContaining('再从入仓单或换标单重新预排'))
await wrapper.findAll('button').find(button => button.text() === '处理原排车')!.trigger('click')
expect(routerPush).toHaveBeenCalledWith({ name: 'overseas-dispatches', query: { dispatchNo: 'PC-1' } })
wrapper.unmount()
})
it('uses list access for detail and independent create/cancel permissions', async () => {
canPermission.mockImplementation((code: string) => code.endsWith(':list'))
const wrapper = mount(OutboundsView, { global: { plugins: [ElementPlus] } })