Files
admin-web/src/modules/unified-orders/pages/UnifiedOrdersView.coLoad.test.ts
T

125 lines
8.5 KiB
TypeScript
Raw Normal View History

import { flushPromises, shallowMount } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import { ApiResponseError } from '@/shared/api/http'
import { listPlatformUnifiedOrders } from '@/modules/unified-orders/api/unifiedOrders'
2026-08-31 10:48:23 +08:00
import { createPlatformCoLoadGroup, getPlatformCoLoadGroupByInboundCode } from '@/modules/unified-orders/api/coLoadGroups'
import UnifiedOrdersView from './UnifiedOrdersView.vue'
vi.mock('vue-router', () => ({ useRoute: () => ({ params: {}, query: {} }), useRouter: () => ({ push: vi.fn() }) }))
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) }))
vi.mock('@/modules/iam', () => ({ listPlatformTenants: vi.fn().mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 0 }) }))
vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn().mockResolvedValue([]) }))
vi.mock('@/modules/inbounds', () => ({ downloadWeightImage: vi.fn(), getInboundMeasurement: vi.fn() }))
vi.mock('@/modules/unified-orders/workflows', () => ({ UnifiedOrderWorkflowPanel: { template: '<div />' } }))
vi.mock('@/modules/unified-orders/api/unifiedOrders', () => ({
addPlatformUnifiedOrderNote: vi.fn(), approvePlatformUnifiedOrder: vi.fn(), batchApprovePlatformUnifiedOrders: vi.fn(),
getPlatformUnifiedOrder: vi.fn(), listPlatformUnifiedOrderFeeDetails: vi.fn(), listPlatformUnifiedOrders: vi.fn(),
unifiedOrderStatusOptions: [{ value: 'WAIT_SPLIT', label: '待分柜' }],
}))
vi.mock('@/modules/unified-orders/api/coLoadGroups', () => ({
2026-08-31 10:48:23 +08:00
createPlatformCoLoadGroup: vi.fn(), getPlatformCoLoadGroupByInboundCode: vi.fn(),
}))
const rows = ['5001', '5002'].map((orderId, index) => ({
orderId, tenantId: `600${index}`, orderNo: `ORD-${index + 1}`, mainStatus: 'WAIT_SPLIT' as const, nextAction: null,
relation: null, internalOrderStatus: 'WAIT_SPLIT', customerId: `600${index}`, customerName: `客户${index + 1}`,
channelName: 'NY-EXX', receivingWarehouseId: '8', receivingWarehouseName: '义乌仓', destinationWarehouseId: '9', destinationWarehouseName: 'ONT8',
inboundId: `700${index}`, inboundVersion: 1, inboundCode: `IN-${index + 1}`, expectedShipDate: null, totalPackageCount: 5,
locationCodes: null, actualGrossWeightKg: 10, actualVolumeCbm: 1, measurement: null, preload: null, loading: null, delivery: null, transport: null,
createdAt: null, updatedAt: null, expectedArrivalAt: null, feeReviewStatus: null, requestedPickupAt: null, totalPalletCount: null,
chargeableVolumeCbm: null, volumetricWeightKg: null, totalNetWeightKg: null, measurementCompletedAt: null, inboundCompletedAt: null,
expressCompany: null, expressTrackingNo: null, skuNos: null, totalQuantity: null, shipmentId: null, referenceId: null,
2026-08-31 10:48:23 +08:00
coLoadGroup: null,
}))
function mountView() {
return shallowMount(UnifiedOrdersView, {
global: { directives: { loading: () => undefined }, stubs: {
AdminPage: { template: '<main><slot /></main>' },
ListPageLayout: { template: '<section><slot name="toolbar" /><slot name="state" /><slot /></section>' },
QueryToolbar: { template: '<div />' },
2026-08-31 10:09:17 +08:00
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>' },
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-tag': { template: '<span><slot /></span>' },
'el-alert': { props: ['title'], template: '<div>{{ title }}</div>' },
} },
})
}
describe('UnifiedOrdersView co-load group behavior', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(listPlatformUnifiedOrders).mockResolvedValue({ records: rows, page: 1, size: 20, total: 2, pages: 1 })
})
it('keeps both selected orders and backend tracing evidence when create validation fails', async () => {
vi.mocked(createPlatformCoLoadGroup).mockRejectedValue(new ApiResponseError('成员路线不一致', 'A0409', 409, undefined, 'request-coload', 'trace-coload'))
const wrapper = mountView()
await flushPromises()
await wrapper.get('[data-testid="select"]').trigger('click')
await wrapper.findAll('button').find((button) => button.text().includes('设为同柜不可拆组'))!.trigger('click')
await wrapper.get('[data-testid="dialog-confirm"]').trigger('click')
await flushPromises()
expect(createPlatformCoLoadGroup).toHaveBeenCalledWith(['5001', '5002'])
expect(wrapper.text()).toContain('设为同柜不可拆组(2)')
expect(wrapper.text()).toContain('成员路线不一致')
expect(wrapper.text()).toContain('A0409 request-coload trace-coload')
expect(listPlatformUnifiedOrders).toHaveBeenCalledTimes(1)
})
2026-08-31 10:09:17 +08:00
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()
await flushPromises()
expect(wrapper.text()).toContain('IN-1')
expect(wrapper.text()).toContain('待生成入仓码')
expect(wrapper.text()).not.toContain('ORD-1')
expect(wrapper.text()).not.toContain('ORD-2')
expect(wrapper.text()).not.toContain('5001')
expect(wrapper.text()).not.toContain('7000')
2026-08-31 10:48:23 +08:00
expect(wrapper.text()).not.toContain('履约详情')
expect(wrapper.text()).not.toContain('关联数据')
2026-08-31 10:09:17 +08:00
})
it('shows a business-only group summary and all server-provided inbound codes in the dialog', async () => {
2026-08-31 10:48:23 +08:00
const rowsWithGroup = rows.map((row) => ({ ...row, coLoadGroup: { status: 'ACTIVE', memberCount: 2, memberInboundCodes: ['IN-1', 'IN-1-B', 'IN-2'] } }))
vi.mocked(listPlatformUnifiedOrders).mockResolvedValue({ records: rowsWithGroup, page: 1, size: 20, total: 2, pages: 1 })
vi.mocked(getPlatformCoLoadGroupByInboundCode).mockResolvedValue({ status: 'ACTIVE', memberCount: 3, members: [{ inboundCode: 'IN-1' }, { inboundCode: 'IN-1-B' }, { inboundCode: 'IN-2' }] })
const wrapper = mountView()
await flushPromises()
2026-08-31 10:09:17 +08:00
expect(wrapper.text()).toContain('同柜组 · 2 票 · 未锁定')
expect(wrapper.text()).not.toContain('CLG-9001')
await wrapper.get('[aria-label="查看同柜组 · 2 票 · 未锁定成员"]').trigger('click')
2026-08-31 10:48:23 +08:00
await flushPromises()
expect(getPlatformCoLoadGroupByInboundCode).toHaveBeenCalledWith('IN-1')
2026-08-31 10:31:53 +08:00
expect(wrapper.text()).toContain('3 个成员')
2026-08-31 10:09:17 +08:00
expect(wrapper.text()).toContain('IN-1')
expect(wrapper.text()).toContain('IN-1-B')
expect(wrapper.text()).toContain('IN-2')
expect(wrapper.text()).not.toContain('ORD-1')
expect(wrapper.text()).not.toContain('ORD-2')
2026-08-30 17:43:03 +08:00
expect(wrapper.text()).not.toContain('7003')
expect(wrapper.text()).not.toContain('5001')
})
2026-08-31 10:31:53 +08:00
it.each([
['undefined', undefined],
['null', null],
['blank', ' '],
])('keeps the member dialog readable when a normalized inbound code is %s', async (_case, inboundCode) => {
2026-08-31 10:48:23 +08:00
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: inboundCode as unknown as string }, { inboundCode: 'IN-2' }] })
2026-08-31 10:31:53 +08:00
const wrapper = mountView()
await flushPromises()
await wrapper.get('[aria-label="查看同柜组 · 2 票 · 未锁定成员"]').trigger('click')
2026-08-31 10:48:23 +08:00
await flushPromises()
2026-08-31 10:31:53 +08:00
expect(wrapper.text()).toContain('同柜组成员入仓码尚未返回,请刷新后重试')
expect(wrapper.text()).not.toContain('CLG-9001')
expect(wrapper.text()).not.toContain('ORD-1')
expect(wrapper.text()).not.toContain('5001')
})
})