fix(dispatch): align dispatch actions and native return workflows
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import ElementPlus from 'element-plus'
|
||||
import ElementPlus, { ElMessage } from 'element-plus'
|
||||
import { defineComponent, h, type PropType } from 'vue'
|
||||
import { flushPromises, mount } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
@@ -10,7 +10,8 @@ import { getDeliveryDocuments, deliveryDocumentCategories } from '../api/deliver
|
||||
|
||||
const canPermission = vi.hoisted(() => vi.fn((_code: string) => false))
|
||||
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: canPermission }) }))
|
||||
vi.mock('vue-router', () => ({ useRouter: () => ({ push: vi.fn() }) }))
|
||||
const routerPush = vi.hoisted(() => vi.fn())
|
||||
vi.mock('vue-router', () => ({ useRouter: () => ({ push: routerPush }) }))
|
||||
vi.mock('../api/deliveryOrders', () => ({ getDeliveryOrder: vi.fn(), listDeliveryOrders: vi.fn(), listDeliveryReturnInboundOptions: vi.fn(), markDeliveryReturn: vi.fn() }))
|
||||
vi.mock('../api/customerVisits', () => ({ updateDeliveryOrderStatus: vi.fn() }))
|
||||
vi.mock('../api/pallets', () => ({ listReceivingOverseasWarehouses: vi.fn() }))
|
||||
@@ -28,11 +29,12 @@ function mountView() {
|
||||
|
||||
describe('DeliveryOrdersView permissions and commands', () => {
|
||||
beforeEach(() => {
|
||||
routerPush.mockReset()
|
||||
canPermission.mockReset().mockReturnValue(false)
|
||||
vi.mocked(getDeliveryDocuments).mockReset()
|
||||
vi.mocked(getDeliveryOrder).mockReset().mockResolvedValue({ order: { ...row, version: 7 }, skus: [], fees: [], operations: [] })
|
||||
vi.mocked(listDeliveryOrders).mockReset().mockResolvedValue({ records: [row], page: 1, size: 10, total: 1, pages: 1 })
|
||||
vi.mocked(listDeliveryReturnInboundOptions).mockReset().mockResolvedValue([{ inboundOrderId: '9', inboundCode: 'IN-1', lastMileOrderNo: 'LM-1', customerId: '100', returnSources: [{ outboundPalletItemId: '9007199254740995', inventoryPositionId: '9007199254740996', palletNo: 'PL-1', skuCode: 'SKU-1', maxReturnPackages: 3 }] }])
|
||||
vi.mocked(listDeliveryReturnInboundOptions).mockReset().mockResolvedValue([{ lastMileOrderId: '109', inboundOrderId: '9', inboundCode: 'IN-1', lastMileOrderNo: 'LM-1', customerId: '100', returnSources: [{ outboundPalletItemId: '9007199254740995', inventoryPositionId: '9007199254740996', returnCartonId: null, palletNo: 'PL-1', skuCode: 'SKU-1', maxReturnPackages: 3 }] }])
|
||||
vi.mocked(markDeliveryReturn).mockReset().mockResolvedValue({ id: '19', exceptionNo: 'EXC-1', status: 'PENDING' })
|
||||
vi.mocked(updateDeliveryOrderStatus).mockReset().mockResolvedValue({ deliveryNo: 'DEL/1', deliveryStatus: 'WAIT_DELIVERY', replayed: false })
|
||||
})
|
||||
@@ -68,7 +70,7 @@ describe('DeliveryOrdersView permissions and commands', () => {
|
||||
await wrapper.find('[data-dialog="标记退货"] .dialog-confirm').trigger('click')
|
||||
await flushPromises()
|
||||
expect(markDeliveryReturn).toHaveBeenCalledWith('DEL/1', '100', {
|
||||
inboundOrderId: '9', reason: '客户拒收', returnDirection: 'OVERSEAS_WAREHOUSE',
|
||||
lastMileOrderId: '109', reason: '客户拒收', returnDirection: 'OVERSEAS_WAREHOUSE',
|
||||
returnLogisticsNo: undefined, items: [{ outboundPalletItemId: '9007199254740995', inventoryPositionId: '9007199254740996', packageCount: 2 }], idempotencyKey: expect.any(String),
|
||||
})
|
||||
wrapper.unmount()
|
||||
@@ -96,6 +98,7 @@ describe('DeliveryOrdersView permissions and commands', () => {
|
||||
expect(select.find('option[value="DELIVERING"]').text()).toBe('派送中')
|
||||
expect(select.find('option[value="DELIVERED"]').exists()).toBe(false)
|
||||
expect(select.find('option[value="WAIT_DELIVERY"]').exists()).toBe(false)
|
||||
expect(select.find('option[value="CANCELLED"]').exists()).toBe(false)
|
||||
await select.setValue('DELIVERING')
|
||||
await wrapper.find('[data-dialog="编辑派送状态"] textarea').setValue('123')
|
||||
let reject!: (error: unknown) => void
|
||||
@@ -127,6 +130,110 @@ describe('DeliveryOrdersView permissions and commands', () => {
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('removes status editing when cancellation is the only action and routes through the original outbound', async () => {
|
||||
canPermission.mockReturnValue(true)
|
||||
vi.mocked(listDeliveryOrders).mockResolvedValue({ records: [{ ...row, allowedActions: ['CANCEL_DELIVERY'] }], page: 1, size: 10, total: 1, pages: 1 })
|
||||
const wrapper = mountView(); await flushPromises()
|
||||
expect(wrapper.findAll('button').some(button => button.text() === '编辑状态')).toBe(false)
|
||||
await wrapper.findAll('button').find(button => button.text() === '前往出库')!.trigger('click')
|
||||
expect(routerPush).toHaveBeenCalledExactlyOnceWith({ name: 'overseas-outbounds', query: { documentNo: 'OUT-1' } })
|
||||
expect(updateDeliveryOrderStatus).not.toHaveBeenCalled()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('offers no early delivery or rejection action when the server returns no executable action', async () => {
|
||||
canPermission.mockReturnValue(true)
|
||||
vi.mocked(listDeliveryOrders).mockResolvedValue({ records: [{ ...row, allowedActions: [] }], page: 1, size: 10, total: 1, pages: 1 })
|
||||
const wrapper = mountView(); await flushPromises()
|
||||
expect(wrapper.findAll('button').some(button => ['编辑状态', '标记退货'].includes(button.text()))).toBe(false)
|
||||
expect(updateDeliveryOrderStatus).not.toHaveBeenCalled()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('marks an original return carton as one whole box without submitting an inventory position', async () => {
|
||||
canPermission.mockReturnValue(true)
|
||||
vi.mocked(listDeliveryReturnInboundOptions).mockResolvedValue([{ lastMileOrderId: '9007199254741299', lastMileOrderNo: 'LM-RETURN-1', inboundOrderId: null, inboundCode: null, customerId: '100', returnSources: [{ outboundPalletItemId: '9007199254740995', inventoryPositionId: null, returnCartonId: '9007199254740997', cartonMark: 'RETURN-CARTON-1', maxReturnPackages: 1 }] }])
|
||||
const wrapper = mountView(); await flushPromises()
|
||||
await wrapper.findAll('button').find(button => button.text() === '标记退货')!.trigger('click'); await flushPromises()
|
||||
const dialog = wrapper.get('[data-dialog="标记退货"]')
|
||||
expect(dialog.text()).toContain('原退货箱 RETURN-CARTON-1(整箱退回)')
|
||||
expect(dialog.text()).toContain('LM-RETURN-1 · 无普通入仓关联')
|
||||
expect(dialog.text()).not.toContain('9007199254741299')
|
||||
expect(dialog.text()).not.toContain('9007199254740997')
|
||||
await dialog.get('textarea').setValue('原箱拒收')
|
||||
await dialog.get('input[type="checkbox"]').setValue(true)
|
||||
expect(dialog.get('input[type="number"]').attributes('disabled')).toBeDefined()
|
||||
await dialog.get('.dialog-confirm').trigger('click'); await flushPromises()
|
||||
expect(markDeliveryReturn).toHaveBeenCalledExactlyOnceWith('DEL/1', '100', expect.objectContaining({ lastMileOrderId: '9007199254741299', items: [{ outboundPalletItemId: '9007199254740995', returnCartonId: '9007199254740997', packageCount: 1 }] }))
|
||||
expect(vi.mocked(markDeliveryReturn).mock.calls[0]?.[2]).not.toHaveProperty('inboundOrderId')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('returns an ordinary relabel master with inventory sources without requiring an inbound record', async () => {
|
||||
canPermission.mockReturnValue(true)
|
||||
vi.mocked(listDeliveryReturnInboundOptions).mockResolvedValue([{ lastMileOrderId: '9007199254741300', lastMileOrderNo: 'LM-RELABEL-1', inboundOrderId: null, inboundCode: null, customerId: '100', returnSources: [{ outboundPalletItemId: '11', inventoryPositionId: '12', returnCartonId: null, palletNo: 'RELABEL-PALLET-1', skuCode: 'RELABEL-SKU', maxReturnPackages: 3 }] }])
|
||||
const wrapper = mountView(); await flushPromises()
|
||||
try {
|
||||
await wrapper.findAll('button').find(button => button.text() === '标记退货')!.trigger('click'); await flushPromises()
|
||||
const dialog = wrapper.get('[data-dialog="标记退货"]')
|
||||
expect(dialog.text()).toContain('LM-RELABEL-1 · 无普通入仓关联')
|
||||
expect(dialog.text()).not.toContain('9007199254741300')
|
||||
await dialog.get('textarea').setValue('换标主单拒收')
|
||||
await dialog.get('input[type="checkbox"]').setValue(true)
|
||||
await dialog.get('input[type="number"]').setValue(2)
|
||||
await dialog.get('.dialog-confirm').trigger('click'); await flushPromises()
|
||||
expect(markDeliveryReturn).toHaveBeenCalledExactlyOnceWith('DEL/1', '100', expect.objectContaining({ lastMileOrderId: '9007199254741300', items: [{ outboundPalletItemId: '11', inventoryPositionId: '12', packageCount: 2 }] }))
|
||||
expect(vi.mocked(markDeliveryReturn).mock.calls[0]?.[2]).not.toHaveProperty('inboundOrderId')
|
||||
} finally { wrapper.unmount() }
|
||||
})
|
||||
|
||||
it('keeps native cartons isolated when switching two masters that both have no inbound record', async () => {
|
||||
canPermission.mockReturnValue(true)
|
||||
const cartonA = { outboundPalletItemId: '11', inventoryPositionId: null, returnCartonId: '21', cartonMark: 'ORIGINAL-CARTON-A', maxReturnPackages: 1 }
|
||||
const cartonB = { ...cartonA, returnCartonId: '22', cartonMark: 'ORIGINAL-CARTON-B' }
|
||||
vi.mocked(listDeliveryReturnInboundOptions).mockResolvedValue([
|
||||
{ lastMileOrderId: '109', lastMileOrderNo: 'LM-RETURN-A', inboundOrderId: null, inboundCode: null, customerId: '100', returnSources: [cartonA] },
|
||||
{ lastMileOrderId: '110', lastMileOrderNo: 'LM-RETURN-B', inboundOrderId: null, inboundCode: null, customerId: '100', returnSources: [cartonB] },
|
||||
])
|
||||
const wrapper = mountView(); await flushPromises()
|
||||
try {
|
||||
await wrapper.findAll('button').find(button => button.text() === '标记退货')!.trigger('click'); await flushPromises()
|
||||
const dialog = wrapper.get('[data-dialog="标记退货"]')
|
||||
const masterSelect = dialog.get('select')
|
||||
expect(masterSelect.findAll('option').map(option => option.attributes('value'))).toEqual(['109', '110'])
|
||||
await masterSelect.setValue('109')
|
||||
await dialog.get('textarea').setValue('原箱拒收')
|
||||
await dialog.get('input[type="checkbox"]').setValue(true)
|
||||
await masterSelect.setValue('110')
|
||||
expect(dialog.text()).not.toContain('ORIGINAL-CARTON-A')
|
||||
expect(dialog.text()).toContain('ORIGINAL-CARTON-B')
|
||||
expect((dialog.get('input[type="checkbox"]').element as HTMLInputElement).checked).toBe(false)
|
||||
await dialog.get('.dialog-confirm').trigger('click'); await flushPromises()
|
||||
expect(markDeliveryReturn).not.toHaveBeenCalled()
|
||||
await dialog.get('input[type="checkbox"]').setValue(true)
|
||||
await dialog.get('.dialog-confirm').trigger('click'); await flushPromises()
|
||||
expect(markDeliveryReturn).toHaveBeenCalledExactlyOnceWith('DEL/1', '100', expect.objectContaining({ lastMileOrderId: '110', items: [{ outboundPalletItemId: '11', returnCartonId: '22', packageCount: 1 }] }))
|
||||
} finally { wrapper.unmount() }
|
||||
})
|
||||
|
||||
it('rejects a stale carton selection from another master before posting', async () => {
|
||||
canPermission.mockReturnValue(true)
|
||||
const ownCarton = { outboundPalletItemId: '11', inventoryPositionId: null, returnCartonId: '21', cartonMark: 'CURRENT-CARTON', maxReturnPackages: 1 }
|
||||
vi.mocked(listDeliveryReturnInboundOptions).mockResolvedValue([{ lastMileOrderId: '109', lastMileOrderNo: 'LM-RETURN-A', inboundOrderId: null, inboundCode: null, customerId: '100', returnSources: [ownCarton] }])
|
||||
const warning = vi.spyOn(ElMessage, 'warning')
|
||||
const wrapper = mountView(); await flushPromises()
|
||||
try {
|
||||
await wrapper.findAll('button').find(button => button.text() === '标记退货')!.trigger('click'); await flushPromises()
|
||||
const dialog = wrapper.get('[data-dialog="标记退货"]')
|
||||
await dialog.get('textarea').setValue('拒收')
|
||||
const state = wrapper.vm as unknown as { returnItems: Array<typeof ownCarton & { selected: boolean; packageCount: number }> }
|
||||
state.returnItems = [{ ...ownCarton, returnCartonId: '22', cartonMark: 'FOREIGN-CARTON', selected: true, packageCount: 1 }]
|
||||
await dialog.get('.dialog-confirm').trigger('click'); await flushPromises()
|
||||
expect(markDeliveryReturn).not.toHaveBeenCalled()
|
||||
expect(warning).toHaveBeenCalledWith(expect.stringContaining('不属于当前尾程主单'))
|
||||
} finally { warning.mockRestore(); wrapper.unmount() }
|
||||
})
|
||||
|
||||
it('validates selected return sources and reuses the idempotency key after a failed request', async () => {
|
||||
canPermission.mockImplementation(code => code.endsWith(':return'))
|
||||
const wrapper = mountView(); await flushPromises()
|
||||
@@ -162,21 +269,21 @@ describe('DeliveryOrdersView permissions and commands', () => {
|
||||
} finally { wrapper.unmount() }
|
||||
})
|
||||
|
||||
it('clears the selected source when a different inbound order is chosen', async () => {
|
||||
it('clears the selected source when a different real master is chosen', async () => {
|
||||
canPermission.mockImplementation(code => code.endsWith(':return'))
|
||||
vi.mocked(listDeliveryReturnInboundOptions).mockResolvedValue([
|
||||
{ inboundOrderId: '9', inboundCode: 'IN-1', customerId: '100', returnSources: [{ outboundPalletItemId: '11', inventoryPositionId: '12', palletNo: 'PL-1', skuCode: 'SKU-1', maxReturnPackages: 3 }] },
|
||||
{ inboundOrderId: '10', inboundCode: 'IN-2', customerId: '100', returnSources: [] },
|
||||
{ lastMileOrderId: '109', lastMileOrderNo: 'LM-1', inboundOrderId: '9', inboundCode: 'IN-1', customerId: '100', returnSources: [{ outboundPalletItemId: '11', inventoryPositionId: '12', returnCartonId: null, palletNo: 'PL-1', skuCode: 'SKU-1', maxReturnPackages: 3 }] },
|
||||
{ lastMileOrderId: '110', lastMileOrderNo: 'LM-2', inboundOrderId: '10', inboundCode: 'IN-2', customerId: '100', returnSources: [] },
|
||||
])
|
||||
const wrapper = mountView(); await flushPromises()
|
||||
try {
|
||||
await wrapper.findAll('button').find(button => button.text().includes('标记退货'))!.trigger('click'); await flushPromises()
|
||||
const dialog = wrapper.find('[data-dialog="标记退货"]')
|
||||
await dialog.find('select').setValue('9')
|
||||
await dialog.find('select').setValue('109')
|
||||
await dialog.find('input[type="checkbox"]').setValue(true)
|
||||
await dialog.find('input[type="number"]').setValue(2)
|
||||
await dialog.find('textarea').setValue('拒收')
|
||||
await dialog.find('select').setValue('10')
|
||||
await dialog.find('select').setValue('110')
|
||||
expect(dialog.find('input[type="checkbox"]').exists()).toBe(false)
|
||||
await dialog.find('.dialog-confirm').trigger('click')
|
||||
expect(markDeliveryReturn).not.toHaveBeenCalled()
|
||||
|
||||
Reference in New Issue
Block a user