feat(admin): validate and cancel co-load groups
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { flushPromises, shallowMount } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ElMessageBox } from 'element-plus'
|
||||
import { ApiResponseError } from '@/shared/api/http'
|
||||
import { listPlatformUnifiedOrders } from '@/modules/unified-orders/api/unifiedOrders'
|
||||
import { createPlatformCoLoadGroup, getPlatformCoLoadGroupByInboundCode } from '@/modules/unified-orders/api/coLoadGroups'
|
||||
import { cancelPlatformCoLoadGroup, createPlatformCoLoadGroup, getPlatformCoLoadGroupByInboundCode } from '@/modules/unified-orders/api/coLoadGroups'
|
||||
import UnifiedOrdersView from './UnifiedOrdersView.vue'
|
||||
|
||||
vi.mock('vue-router', () => ({ useRoute: () => ({ params: {}, query: {} }), useRouter: () => ({ push: vi.fn() }) }))
|
||||
@@ -17,7 +18,7 @@ vi.mock('@/modules/unified-orders/api/unifiedOrders', () => ({
|
||||
unifiedOrderStatusOptions: [{ value: 'WAIT_SPLIT', label: '待分柜' }],
|
||||
}))
|
||||
vi.mock('@/modules/unified-orders/api/coLoadGroups', () => ({
|
||||
createPlatformCoLoadGroup: vi.fn(), getPlatformCoLoadGroupByInboundCode: vi.fn(),
|
||||
cancelPlatformCoLoadGroup: vi.fn(), createPlatformCoLoadGroup: vi.fn(), getPlatformCoLoadGroupByInboundCode: vi.fn(),
|
||||
}))
|
||||
|
||||
const rows = ['5001', '5002'].map((orderId, index) => ({
|
||||
@@ -38,10 +39,10 @@ function mountView() {
|
||||
ListPageLayout: { template: '<section><slot name="toolbar" /><slot name="state" /><slot /></section>' },
|
||||
QueryToolbar: { template: '<div />' },
|
||||
DataTable: { name: 'DataTable', props: ['data'], emits: ['selection-change'], template: '<section><slot name="toolbar-actions" /><button data-testid="select" @click="$emit(\'selection-change\', data)">勾选两票</button><template v-for="row in data" :key="row.orderId"><slot name="actions" :row="row" /><slot name="inboundCode" :row="row" /></template></section>' },
|
||||
AppDialog: { name: 'AppDialog', props: ['modelValue', 'confirmText'], emits: ['confirm', 'update:modelValue'], template: '<section v-if="modelValue"><slot /><button data-testid="dialog-confirm" @click="$emit(\'confirm\')">{{ confirmText }}</button></section>' },
|
||||
AppDialog: { name: 'AppDialog', props: ['modelValue', 'confirmText', 'confirmDisabled', 'confirmLoading'], emits: ['confirm', 'update:modelValue'], methods: { close() { this.$emit('update:modelValue', false) } }, template: '<section v-if="modelValue"><slot /><slot name="footer" :close="close" :submitting="confirmLoading"><button data-testid="dialog-confirm" :disabled="confirmDisabled" @click="$emit(\'confirm\')">{{ confirmText }}</button></slot></section>' },
|
||||
ApiErrorMeta: { props: ['details'], template: '<div>{{ details.code }} {{ details.requestId }} {{ details.traceId }}</div>' },
|
||||
CopyButton: { props: ['value', 'label'], template: '<button :data-copy="value">复制 ID</button>' },
|
||||
'el-button': { template: '<button @click="$emit(\'click\')"><slot /></button>' },
|
||||
'el-button': { props: ['disabled', 'loading'], emits: ['click'], template: '<button :disabled="disabled" @click="$emit(\'click\')"><slot /></button>' },
|
||||
'el-tag': { template: '<span><slot /></span>' },
|
||||
'el-alert': { props: ['title'], template: '<div>{{ title }}</div>' },
|
||||
} },
|
||||
@@ -52,6 +53,7 @@ describe('UnifiedOrdersView co-load group behavior', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(listPlatformUnifiedOrders).mockResolvedValue({ records: rows, page: 1, size: 20, total: 2, pages: 1 })
|
||||
vi.spyOn(ElMessageBox, 'confirm').mockResolvedValue({ action: 'confirm', value: '' } as never)
|
||||
})
|
||||
|
||||
it('keeps both selected orders and backend tracing evidence when create validation fails', async () => {
|
||||
@@ -69,6 +71,22 @@ describe('UnifiedOrdersView co-load group behavior', () => {
|
||||
expect(listPlatformUnifiedOrders).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it.each([
|
||||
['平台收货仓不同', [{ ...rows[0], receivingWarehouseId: '8' }, { ...rows[1], receivingWarehouseId: '9' }], '所选订单的平台收货仓不同,不能设为同柜组'],
|
||||
['渠道不同', [{ ...rows[0], channelName: 'NY-EXX' }, { ...rows[1], channelName: 'LA-SEA' }], '所选订单的渠道不同,不能设为同柜组'],
|
||||
['已有同柜组', [rows[0], { ...rows[1], coLoadGroup: { status: 'ACTIVE', memberCount: 2, memberInboundCodes: ['IN-2', 'IN-3'] } }], '所选订单中已有同柜组,请先解除原同柜组'],
|
||||
])('blocks create immediately when %s', async (_case, selectedRows, reason) => {
|
||||
vi.mocked(listPlatformUnifiedOrders).mockResolvedValue({ records: selectedRows, page: 1, size: 20, total: 2, pages: 1 })
|
||||
const wrapper = mountView()
|
||||
await flushPromises()
|
||||
await wrapper.get('[data-testid="select"]').trigger('click')
|
||||
const createButton = wrapper.findAll('button').find((button) => button.text().includes('设为同柜不可拆组'))!
|
||||
expect(createButton.attributes('disabled')).toBeDefined()
|
||||
expect(wrapper.text()).toContain(reason)
|
||||
await createButton.trigger('click')
|
||||
expect(createPlatformCoLoadGroup).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('uses inbound codes as the only row identifier and shows an explicit pending state', async () => {
|
||||
vi.mocked(listPlatformUnifiedOrders).mockResolvedValue({ records: [rows[0], { ...rows[1], inboundCode: null }], page: 1, size: 20, total: 2, pages: 1 })
|
||||
const wrapper = mountView()
|
||||
@@ -104,6 +122,59 @@ describe('UnifiedOrdersView co-load group behavior', () => {
|
||||
expect(wrapper.text()).not.toContain('5001')
|
||||
})
|
||||
|
||||
it('cancels an ACTIVE group by the clicked member inbound code and preserves dialog error evidence', async () => {
|
||||
const rowsWithGroup = rows.map((row) => ({ ...row, coLoadGroup: { status: 'ACTIVE', memberCount: 2, memberInboundCodes: ['IN-1', 'IN-2'] } }))
|
||||
vi.mocked(listPlatformUnifiedOrders).mockResolvedValue({ records: rowsWithGroup, page: 1, size: 20, total: 2, pages: 1 })
|
||||
vi.mocked(getPlatformCoLoadGroupByInboundCode).mockResolvedValue({ status: 'ACTIVE', memberCount: 2, members: [{ inboundCode: 'IN-1' }, { inboundCode: 'IN-2' }] })
|
||||
vi.mocked(cancelPlatformCoLoadGroup).mockRejectedValueOnce(new ApiResponseError('组已被并发锁定', 'A0409', 409, undefined, 'request-cancel', 'trace-cancel'))
|
||||
const wrapper = mountView()
|
||||
await flushPromises()
|
||||
await wrapper.get('[aria-label="查看同柜组 · 2 票 · 未锁定成员"]').trigger('click')
|
||||
await flushPromises()
|
||||
const cancelButton = wrapper.findAll('button').find((button) => button.text() === '解除同柜组')!
|
||||
expect(cancelButton.exists()).toBe(true)
|
||||
await cancelButton.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(ElMessageBox.confirm).toHaveBeenCalledWith(expect.stringContaining('解除整个同柜组'), '解除同柜组', expect.objectContaining({ confirmButtonText: '确认解除' }))
|
||||
expect(cancelPlatformCoLoadGroup).toHaveBeenCalledWith('IN-1', '重新调整同柜组成员')
|
||||
expect(wrapper.text()).toContain('组已被并发锁定')
|
||||
expect(wrapper.text()).toContain('A0409 request-cancel trace-cancel')
|
||||
expect(wrapper.findAllComponents({ name: 'AppDialog' }).some((dialog) => dialog.props('modelValue') === true)).toBe(true)
|
||||
expect(wrapper.text()).not.toContain('CLG-')
|
||||
expect(wrapper.text()).not.toContain('5001')
|
||||
})
|
||||
|
||||
it('closes and refreshes after one successful whole-group cancellation request', async () => {
|
||||
const rowsWithGroup = rows.map((row) => ({ ...row, coLoadGroup: { status: 'ACTIVE', memberCount: 2, memberInboundCodes: ['IN-1', 'IN-2'] } }))
|
||||
vi.mocked(listPlatformUnifiedOrders).mockResolvedValue({ records: rowsWithGroup, page: 1, size: 20, total: 2, pages: 1 })
|
||||
vi.mocked(getPlatformCoLoadGroupByInboundCode).mockResolvedValue({ status: 'ACTIVE', memberCount: 2, members: [{ inboundCode: 'IN-1' }, { inboundCode: 'IN-2' }] })
|
||||
vi.mocked(cancelPlatformCoLoadGroup).mockResolvedValue({} as never)
|
||||
const wrapper = mountView()
|
||||
await flushPromises()
|
||||
await wrapper.get('[aria-label="查看同柜组 · 2 票 · 未锁定成员"]').trigger('click')
|
||||
await flushPromises()
|
||||
await wrapper.findAll('button').find((button) => button.text() === '解除同柜组')!.trigger('click')
|
||||
await flushPromises()
|
||||
|
||||
expect(cancelPlatformCoLoadGroup).toHaveBeenCalledTimes(1)
|
||||
expect(cancelPlatformCoLoadGroup).toHaveBeenCalledWith('IN-1', '重新调整同柜组成员')
|
||||
expect(listPlatformUnifiedOrders).toHaveBeenCalledTimes(2)
|
||||
expect(wrapper.findAllComponents({ name: 'AppDialog' }).every((dialog) => dialog.props('modelValue') !== true)).toBe(true)
|
||||
})
|
||||
|
||||
it.each(['LOCKED', 'CONSUMED'])('does not offer group cancellation for %s', async (status) => {
|
||||
const rowsWithGroup = rows.map((row) => ({ ...row, coLoadGroup: { status, memberCount: 2, memberInboundCodes: ['IN-1', 'IN-2'] } }))
|
||||
vi.mocked(listPlatformUnifiedOrders).mockResolvedValue({ records: rowsWithGroup, page: 1, size: 20, total: 2, pages: 1 })
|
||||
vi.mocked(getPlatformCoLoadGroupByInboundCode).mockResolvedValue({ status: status as 'LOCKED' | 'CONSUMED', memberCount: 2, members: [{ inboundCode: 'IN-1' }, { inboundCode: 'IN-2' }] })
|
||||
const wrapper = mountView()
|
||||
await flushPromises()
|
||||
await wrapper.get(`[aria-label="查看同柜组 · 2 票 · ${status === 'LOCKED' ? '已锁定' : '已消费'}成员"]`).trigger('click')
|
||||
await flushPromises()
|
||||
expect(wrapper.findAll('button').some((button) => button.text() === '解除同柜组')).toBe(false)
|
||||
expect(cancelPlatformCoLoadGroup).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it.each([
|
||||
['undefined', undefined],
|
||||
['null', null],
|
||||
|
||||
Reference in New Issue
Block a user