2026-09-03 11:13:28 +08:00
|
|
|
import ElementPlus, { ElMessage, ElMessageBox } from 'element-plus'
|
2026-09-02 10:24:07 +08:00
|
|
|
import { flushPromises, mount } from '@vue/test-utils'
|
|
|
|
|
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
|
import { updatePlatformUnifiedOrderDeclaration } from '@/modules/unified-orders/api/unifiedOrders'
|
2026-09-03 11:13:28 +08:00
|
|
|
import { listWarehouseLogisticsChannels } from '@/modules/ocean-bookings'
|
2026-09-02 10:24:07 +08:00
|
|
|
import OrderDeclarationEditorDialog from './OrderDeclarationEditorDialog.vue'
|
|
|
|
|
|
|
|
|
|
vi.mock('@/modules/unified-orders/api/unifiedOrders', () => ({ updatePlatformUnifiedOrderDeclaration: vi.fn() }))
|
2026-09-03 11:13:28 +08:00
|
|
|
vi.mock('@/modules/ocean-bookings', () => ({ listWarehouseLogisticsChannels: vi.fn() }))
|
2026-09-02 10:24:07 +08:00
|
|
|
|
|
|
|
|
const detail = {
|
|
|
|
|
summary: {
|
|
|
|
|
orderId: '90001', tenantId: '208', inboundCode: 'RK-001', mainStatus: 'WAIT_MEASURE',
|
2026-09-03 11:13:28 +08:00
|
|
|
receivingWarehouseId: '700000000000000001', receivingWarehouseName: '深圳仓', destinationWarehouseName: '洛杉矶中转仓',
|
2026-09-02 10:24:07 +08:00
|
|
|
channelCode: 'OLD', channelName: '原渠道', channelChanged: false,
|
2026-09-03 11:47:32 +08:00
|
|
|
expectedArrivalAt: '2026-09-06T10:00:00', estimatedVolumeCbm: 0.625, customerRemark: '原备注',
|
2026-09-02 10:24:07 +08:00
|
|
|
},
|
|
|
|
|
order: {
|
2026-09-03 11:13:28 +08:00
|
|
|
order: { version: 7, receivingWarehouseId: '700000000000000001', channelCode: 'OLD', channelName: '原渠道', customerRemark: '原备注' },
|
|
|
|
|
cargoes: [{ itemNo: 1, cargoName: '台灯', packageCount: 2, totalQuantity: 20, lengthCm: 40, widthCm: 30, heightCm: 20, grossWeightKg: 18, volumeCbm: 0.048, currency: 'USD', material: '铝', usage: '照明' }],
|
2026-09-02 10:24:07 +08:00
|
|
|
parties: [], internalNotes: [], statusHistory: [],
|
|
|
|
|
},
|
|
|
|
|
timeline: [],
|
|
|
|
|
} as never
|
|
|
|
|
|
|
|
|
|
function createWrapper() {
|
|
|
|
|
return mount(OrderDeclarationEditorDialog, {
|
|
|
|
|
props: { modelValue: true, detail },
|
|
|
|
|
global: {
|
|
|
|
|
plugins: [ElementPlus],
|
|
|
|
|
stubs: {
|
|
|
|
|
AppDialog: {
|
2026-09-03 11:13:28 +08:00
|
|
|
props: ['modelValue', 'confirmLoading', 'confirmDisabled'], emits: ['confirm', 'update:modelValue'],
|
|
|
|
|
template: '<section v-if="modelValue"><slot /><button data-testid="save" :disabled="confirmDisabled" @click="$emit(\'confirm\')">保存</button></section>',
|
2026-09-02 10:24:07 +08:00
|
|
|
},
|
|
|
|
|
LoadErrorState: { props: ['title', 'message'], template: '<div class="load-error">{{ title }} {{ message }}<slot name="details" /></div>' },
|
|
|
|
|
ApiErrorMeta: true,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
describe('OrderDeclarationEditorDialog', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.mocked(updatePlatformUnifiedOrderDeclaration).mockReset()
|
2026-09-03 11:13:28 +08:00
|
|
|
vi.mocked(listWarehouseLogisticsChannels).mockReset()
|
|
|
|
|
vi.mocked(listWarehouseLogisticsChannels).mockResolvedValue([
|
|
|
|
|
{ channelCode: 'OLD', channelName: '原渠道' },
|
|
|
|
|
{ channelCode: 'NEW', channelName: '新渠道' },
|
|
|
|
|
])
|
2026-09-02 10:24:07 +08:00
|
|
|
vi.spyOn(ElMessageBox, 'confirm').mockResolvedValue('confirm' as never)
|
2026-09-03 11:13:28 +08:00
|
|
|
vi.spyOn(ElMessage, 'success').mockImplementation(() => ({ close: vi.fn() }) as never)
|
2026-09-02 10:24:07 +08:00
|
|
|
})
|
|
|
|
|
|
2026-09-03 11:13:28 +08:00
|
|
|
it('loads formal warehouse channels and submits the code/name pair from one selected option', async () => {
|
2026-09-02 10:24:07 +08:00
|
|
|
vi.mocked(updatePlatformUnifiedOrderDeclaration).mockResolvedValue({ version: 8 } as never)
|
|
|
|
|
const wrapper = createWrapper()
|
2026-09-03 11:13:28 +08:00
|
|
|
await flushPromises()
|
|
|
|
|
expect(listWarehouseLogisticsChannels).toHaveBeenCalledWith('700000000000000001')
|
2026-09-02 10:24:07 +08:00
|
|
|
expect(wrapper.text()).toContain('国内收货仓和中转仓不可修改,如需变更请重新下单')
|
|
|
|
|
expect(wrapper.text()).toContain('RK-001')
|
2026-09-03 11:13:28 +08:00
|
|
|
const channelSelect = wrapper.findAllComponents({ name: 'ElSelect' })[0]!
|
|
|
|
|
channelSelect.vm.$emit('update:modelValue', 'NEW')
|
|
|
|
|
channelSelect.vm.$emit('change', 'NEW')
|
|
|
|
|
await wrapper.vm.$nextTick()
|
2026-09-02 10:24:07 +08:00
|
|
|
await wrapper.get('[data-testid="save"]').trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
2026-09-03 11:13:28 +08:00
|
|
|
expect(ElMessageBox.confirm).toHaveBeenCalledWith(expect.stringContaining('原箱码继续有效,无需更换已打印箱唛'), '确认变更渠道', expect.any(Object))
|
|
|
|
|
expect(ElMessageBox.confirm).toHaveBeenCalledWith(expect.stringContaining('纸面上的旧渠道文字不会自动变化'), '确认变更渠道', expect.any(Object))
|
2026-09-03 11:47:32 +08:00
|
|
|
expect(updatePlatformUnifiedOrderDeclaration).toHaveBeenCalledWith('208', '90001', expect.objectContaining({ expectedVersion: 7, estimatedVolumeCbm: 0.625, channelCode: 'NEW', channelName: '新渠道' }))
|
2026-09-03 11:13:28 +08:00
|
|
|
expect(ElMessage.success).toHaveBeenCalledWith(expect.stringContaining('订单已标记为“曾变更渠道”'))
|
2026-09-02 10:24:07 +08:00
|
|
|
expect(wrapper.emitted('updated')).toHaveLength(1)
|
|
|
|
|
expect(wrapper.emitted('update:modelValue')).toContainEqual([false])
|
|
|
|
|
})
|
|
|
|
|
|
2026-09-03 11:13:28 +08:00
|
|
|
it('does not expose free-text channel inputs and presents cargoes as a compact table with row editing', async () => {
|
|
|
|
|
const wrapper = createWrapper()
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
|
|
|
|
expect(wrapper.find('input[aria-label="渠道编码"]').attributes('readonly')).toBeDefined()
|
|
|
|
|
expect(wrapper.findAllComponents({ name: 'ElSelect' })).toHaveLength(1)
|
|
|
|
|
expect(wrapper.find('.cargo-table').exists()).toBe(true)
|
|
|
|
|
expect(wrapper.text()).toContain('箱数 / 数量')
|
|
|
|
|
expect(wrapper.text()).toContain('材质 / 用途')
|
|
|
|
|
expect(wrapper.text()).toContain('2 箱 / 20')
|
|
|
|
|
|
|
|
|
|
const editButton = wrapper.findAll('button').find((button) => button.text() === '编辑')!
|
|
|
|
|
await editButton.trigger('click')
|
|
|
|
|
expect(wrapper.find('input[aria-label="币种"]').attributes('readonly')).toBeDefined()
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
it('preserves the editor when the versioned update fails', async () => {
|
2026-09-02 10:24:07 +08:00
|
|
|
vi.mocked(updatePlatformUnifiedOrderDeclaration).mockRejectedValue(new Error('订单版本已变化,请刷新详情后重试'))
|
|
|
|
|
const wrapper = createWrapper()
|
2026-09-03 11:13:28 +08:00
|
|
|
await flushPromises()
|
2026-09-02 10:24:07 +08:00
|
|
|
await wrapper.get('[data-testid="save"]').trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
|
|
|
|
expect(wrapper.find('.load-error').exists()).toBe(true)
|
2026-09-03 11:13:28 +08:00
|
|
|
expect(wrapper.text()).toContain('原渠道')
|
2026-09-02 10:24:07 +08:00
|
|
|
expect(wrapper.emitted('update:modelValue')).toBeUndefined()
|
|
|
|
|
})
|
2026-09-03 11:13:28 +08:00
|
|
|
|
|
|
|
|
it('fails closed and keeps API error evidence when formal channel candidates cannot load', async () => {
|
|
|
|
|
vi.mocked(listWarehouseLogisticsChannels).mockRejectedValue(Object.assign(new Error('无权限'), { requestId: 'req-1', traceId: 'trace-1' }))
|
|
|
|
|
const wrapper = createWrapper()
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
|
|
|
|
expect(wrapper.text()).toContain('渠道候选加载失败')
|
|
|
|
|
expect(wrapper.get('[data-testid="save"]').attributes('disabled')).toBeDefined()
|
|
|
|
|
expect(updatePlatformUnifiedOrderDeclaration).not.toHaveBeenCalled()
|
|
|
|
|
})
|
2026-09-02 10:24:07 +08:00
|
|
|
})
|