2026-08-04 21:01:39 +08:00
|
|
|
import ElementPlus, { ElMessageBox } from 'element-plus'
|
|
|
|
|
import { createPinia, setActivePinia } from 'pinia'
|
|
|
|
|
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
|
|
|
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
|
import { useAccessPreviewStore } from '@/core/access/accessPreview'
|
|
|
|
|
import { useAuthStore } from '@/core/auth/auth'
|
2026-08-18 11:33:01 +08:00
|
|
|
import { approveMeasurement, getMeasurementReviewDetail, listMeasurementReviewHistory } from './api'
|
2026-08-04 21:01:39 +08:00
|
|
|
import MeasurementReviewPanel from './MeasurementReviewPanel.vue'
|
2026-08-18 11:33:01 +08:00
|
|
|
import type { MeasurementReviewDetail } from './types'
|
2026-08-04 21:01:39 +08:00
|
|
|
|
|
|
|
|
vi.mock('./api', () => ({
|
|
|
|
|
approveMeasurement: vi.fn(),
|
|
|
|
|
getMeasurementReviewDetail: vi.fn(),
|
|
|
|
|
listMeasurementReviewHistory: vi.fn(),
|
|
|
|
|
}))
|
|
|
|
|
|
2026-08-18 11:33:01 +08:00
|
|
|
vi.mock('../weight/components/WeightOcrPanel.vue', () => ({
|
|
|
|
|
default: {
|
|
|
|
|
name: 'WeightOcrPanel',
|
|
|
|
|
props: { customerId: String, inboundId: String, reviewMode: Boolean, correctionMode: Boolean },
|
|
|
|
|
emits: ['changed'],
|
|
|
|
|
template: '<button class="mock-weight-change" @click="$emit(\'changed\')">重量证据</button>',
|
|
|
|
|
},
|
2026-08-14 19:59:32 +08:00
|
|
|
}))
|
|
|
|
|
|
2026-08-18 11:33:01 +08:00
|
|
|
const detail: MeasurementReviewDetail = {
|
2026-08-04 21:01:39 +08:00
|
|
|
inboundId: '90001', inboundCode: 'RK90001', companyName: '柏悦客户', channelCode: 'SEA', channelName: '海运',
|
|
|
|
|
processStatus: 'MEASURE_PENDING', measureReviewStatus: 'PENDING', recommendedPalletType: 'STANDARD', reviewReason: null, reviewerId: null, reviewerName: null,
|
2026-08-13 20:00:38 +08:00
|
|
|
measurementId: '70001', sourceType: 'DEVICE', measurementStatus: 'SUBMITTED', inboundVersion: 7, version: 4,
|
2026-08-07 16:17:45 +08:00
|
|
|
originalDocumentNo: 'ORD-001', originalGrossWeightKg: 100, originalVolumeCbm: 2, originalPackageCount: 10,
|
2026-08-18 11:33:01 +08:00
|
|
|
actualPackageCount: 10, actualGrossWeightKg: 120, actualNetWeightKg: 110, actualVolumeCbm: 2.2, volumeWeightKg: 367.4, actualGoodsName: '测试货物',
|
|
|
|
|
items: [],
|
|
|
|
|
weightResults: [],
|
2026-08-04 21:01:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
enableAutoUnmount(afterEach)
|
|
|
|
|
|
|
|
|
|
describe('MeasurementReviewPanel', () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
vi.clearAllMocks()
|
|
|
|
|
setActivePinia(createPinia())
|
|
|
|
|
const auth = useAuthStore()
|
|
|
|
|
auth.token = 'token'
|
2026-08-18 11:33:01 +08:00
|
|
|
auth.permissions = [
|
|
|
|
|
'firstleg:inbound:measurement-review',
|
|
|
|
|
'firstleg:inbound:measurement-review-history',
|
|
|
|
|
'platform:firstleg:inbound:measure:view',
|
|
|
|
|
'platform:firstleg:inbound:weight:edit',
|
|
|
|
|
'platform:firstleg:inbound:weight:confirm',
|
|
|
|
|
]
|
2026-08-04 21:01:39 +08:00
|
|
|
useAccessPreviewStore().initializeAfterLogin()
|
|
|
|
|
vi.mocked(getMeasurementReviewDetail).mockResolvedValue(detail)
|
|
|
|
|
vi.mocked(listMeasurementReviewHistory).mockResolvedValue({ records: [], page: 1, size: 20, total: 0, pages: 0 })
|
|
|
|
|
})
|
|
|
|
|
|
2026-08-18 11:41:14 +08:00
|
|
|
it('keeps customer context and pending actions in one non-sticky first-screen toolbar', async () => {
|
2026-08-04 21:01:39 +08:00
|
|
|
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001' }, global: { plugins: [ElementPlus] } })
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
2026-08-18 11:33:01 +08:00
|
|
|
const toolbar = wrapper.get('.review-toolbar')
|
|
|
|
|
expect(toolbar.text()).toContain('待审核')
|
|
|
|
|
expect(toolbar.text()).toContain('修改重量')
|
|
|
|
|
expect(toolbar.text()).toContain('通过并进入待分柜')
|
|
|
|
|
expect(toolbar.text()).toContain('刷新')
|
|
|
|
|
expect(toolbar.text()).toContain('客户:柏悦客户')
|
|
|
|
|
expect(toolbar.text()).toContain('渠道:海运')
|
|
|
|
|
expect(wrapper.find('.review-toolbar__context').exists()).toBe(true)
|
2026-08-18 11:41:14 +08:00
|
|
|
expect(wrapper.get('.review-toolbar__status').find('.review-toolbar__context').exists()).toBe(true)
|
2026-08-18 11:33:01 +08:00
|
|
|
expect(wrapper.find('.review-context').exists()).toBe(false)
|
|
|
|
|
expect(wrapper.text()).not.toContain('RK90001')
|
|
|
|
|
expect(wrapper.text()).not.toContain('入仓码')
|
|
|
|
|
expect(wrapper.text()).not.toContain('驳回')
|
|
|
|
|
expect(wrapper.text()).not.toContain('异常放行')
|
2026-08-12 16:31:46 +08:00
|
|
|
const source = await import('./MeasurementReviewPanel.vue?raw').then((module) => module.default)
|
2026-08-18 11:41:14 +08:00
|
|
|
expect(source).not.toContain('position: sticky')
|
2026-08-18 11:33:01 +08:00
|
|
|
expect(source).toContain('review-toolbar__context')
|
|
|
|
|
expect(source.indexOf('class="review-toolbar"')).toBeLessThan(source.indexOf('<WeightOcrPanel'))
|
|
|
|
|
expect(source).not.toContain('reasonDialog')
|
|
|
|
|
expect(source).not.toContain('saveInboundMeasurementDraft')
|
|
|
|
|
expect(source).not.toContain('submitInboundMeasurement')
|
2026-08-14 19:59:32 +08:00
|
|
|
})
|
|
|
|
|
|
2026-08-18 11:41:14 +08:00
|
|
|
it('uses the list customer context when the review detail has no customer name', async () => {
|
|
|
|
|
vi.mocked(getMeasurementReviewDetail).mockResolvedValue({ ...detail, companyName: null })
|
|
|
|
|
const wrapper = mount(MeasurementReviewPanel, {
|
|
|
|
|
props: { customerId: '10001', inboundId: '90001', customerName: '欧阳的公司', channelName: '纽约海铁' },
|
|
|
|
|
global: { plugins: [ElementPlus] },
|
|
|
|
|
})
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
|
|
|
|
expect(wrapper.get('.review-toolbar__context').text()).toBe('客户:欧阳的公司 · 渠道:海运')
|
|
|
|
|
})
|
|
|
|
|
|
2026-08-18 11:33:01 +08:00
|
|
|
it('reuses WeightOcrPanel and enables only its review correction mode', async () => {
|
2026-08-14 19:59:32 +08:00
|
|
|
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001' }, global: { plugins: [ElementPlus] } })
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
2026-08-18 11:33:01 +08:00
|
|
|
const weightPanel = wrapper.getComponent({ name: 'WeightOcrPanel' })
|
|
|
|
|
expect(weightPanel.props('reviewMode')).toBe(true)
|
|
|
|
|
expect(weightPanel.props('correctionMode')).toBe(false)
|
|
|
|
|
await wrapper.findAll('button').find((button) => button.text().includes('修改重量'))?.trigger('click')
|
|
|
|
|
expect(weightPanel.props('correctionMode')).toBe(true)
|
2026-08-14 19:59:32 +08:00
|
|
|
})
|
|
|
|
|
|
2026-08-18 11:33:01 +08:00
|
|
|
it('refreshes inboundVersion after a weight correction before approval', async () => {
|
|
|
|
|
vi.mocked(getMeasurementReviewDetail)
|
|
|
|
|
.mockResolvedValueOnce(detail)
|
|
|
|
|
.mockResolvedValueOnce({ ...detail, inboundVersion: 8, actualGrossWeightKg: 125, actualNetWeightKg: 115 })
|
2026-08-14 14:27:03 +08:00
|
|
|
vi.mocked(approveMeasurement).mockResolvedValue({ inboundId: '90001', measurementId: '70001', processStatus: 'WAIT_SPLIT', feeStatus: 'CALCULATED', initialPricingSucceeded: true, initialPricingMessage: null, initialFee: null })
|
2026-08-04 21:01:39 +08:00
|
|
|
vi.spyOn(ElMessageBox, 'confirm').mockResolvedValue(undefined as never)
|
|
|
|
|
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001' }, global: { plugins: [ElementPlus] } })
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
2026-08-18 11:33:01 +08:00
|
|
|
await wrapper.get('.mock-weight-change').trigger('click')
|
2026-08-14 14:27:03 +08:00
|
|
|
await flushPromises()
|
|
|
|
|
await wrapper.findAll('button').find((button) => button.text().includes('通过并进入待分柜'))?.trigger('click')
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
2026-08-18 11:33:01 +08:00
|
|
|
expect(getMeasurementReviewDetail).toHaveBeenCalledTimes(3)
|
|
|
|
|
expect(approveMeasurement).toHaveBeenCalledWith('10001', '90001', { measurementId: '70001', expectedVersion: 8 })
|
2026-08-14 14:27:03 +08:00
|
|
|
expect(wrapper.emitted('changed')).toHaveLength(1)
|
|
|
|
|
})
|
|
|
|
|
|
2026-08-18 11:33:01 +08:00
|
|
|
it('shows historical rejected data read-only without new reject or release operations', async () => {
|
|
|
|
|
vi.mocked(getMeasurementReviewDetail).mockResolvedValue({ ...detail, processStatus: 'MEASURE_REJECTED', measureReviewStatus: 'REJECTED', reviewReason: '历史审核说明' })
|
2026-08-13 20:00:38 +08:00
|
|
|
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001' }, global: { plugins: [ElementPlus] } })
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
2026-08-18 11:33:01 +08:00
|
|
|
expect(wrapper.text()).toContain('历史记录为审核驳回,仅供查看')
|
|
|
|
|
expect(wrapper.text()).toContain('历史审核说明')
|
|
|
|
|
expect(wrapper.text()).not.toContain('修改重量')
|
|
|
|
|
expect(wrapper.text()).not.toContain('异常放行')
|
2026-08-13 20:00:38 +08:00
|
|
|
})
|
2026-08-04 21:01:39 +08:00
|
|
|
})
|