Files
admin-web/src/modules/inbounds/review/MeasurementReviewPanel.test.ts
T
2026-08-18 16:09:43 +08:00

214 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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'
import { approveMeasurement, getMeasurementReviewDetail, listMeasurementReviewHistory } from './api'
import MeasurementReviewPanel from './MeasurementReviewPanel.vue'
import type { MeasurementReviewDetail } from './types'
vi.mock('./api', () => ({
approveMeasurement: vi.fn(),
getMeasurementReviewDetail: vi.fn(),
listMeasurementReviewHistory: vi.fn(),
}))
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>',
},
}))
vi.mock('../measurement/components/MeasurementEvidenceThumbnail.vue', () => ({
default: {
name: 'MeasurementEvidenceThumbnail',
props: { customerId: String, inboundId: String, fileId: String, alt: String, emptyText: String },
emits: ['open'],
template: '<button class="mock-measurement-image" @click="$emit(\'open\')">测量图片</button>',
},
}))
const detail: MeasurementReviewDetail = {
inboundId: '90001', inboundCode: 'RK90001', companyName: '柏悦客户', channelCode: 'SEA', channelName: '海运',
processStatus: 'MEASURE_PENDING', measureReviewStatus: 'PENDING', recommendedPalletType: 'STANDARD', reviewReason: null, reviewerId: null, reviewerName: null,
measurementId: '70001', sourceType: 'DEVICE', measurementStatus: 'SUBMITTED', inboundVersion: 7, version: 4,
originalDocumentNo: 'ORD-001', originalGrossWeightKg: 100, originalVolumeCbm: 2, originalPackageCount: 10,
actualPackageCount: 10, actualGrossWeightKg: 120, actualNetWeightKg: 110, actualVolumeCbm: 2.2, volumeWeightKg: 367.4, actualGoodsName: '测试货物',
items: [],
weightResults: [],
}
enableAutoUnmount(afterEach)
describe('MeasurementReviewPanel', () => {
beforeEach(() => {
vi.clearAllMocks()
setActivePinia(createPinia())
const auth = useAuthStore()
auth.token = 'token'
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',
]
useAccessPreviewStore().initializeAfterLogin()
vi.mocked(getMeasurementReviewDetail).mockResolvedValue(detail)
vi.mocked(listMeasurementReviewHistory).mockResolvedValue({ records: [], page: 1, size: 20, total: 0, pages: 0 })
})
it('keeps customer context and pending actions in one non-sticky first-screen toolbar', async () => {
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001' }, global: { plugins: [ElementPlus] } })
await flushPromises()
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)
expect(wrapper.get('.review-toolbar__status').find('.review-toolbar__context').exists()).toBe(true)
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('异常放行')
expect(wrapper.text()).not.toContain('审核历史')
expect(listMeasurementReviewHistory).not.toHaveBeenCalled()
const source = await import('./MeasurementReviewPanel.vue?raw').then((module) => module.default)
expect(source).not.toContain('position: sticky')
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')
expect(source).not.toContain('listMeasurementReviewHistory')
})
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('客户:欧阳的公司 · 渠道:海运')
})
it('shows declared facts, authoritative measurement totals, and box-detail evidence for manual review', async () => {
vi.mocked(getMeasurementReviewDetail).mockResolvedValue({
...detail,
originalPackageCount: 9,
originalGrossWeightKg: 118,
originalVolumeCbm: 2,
actualPackageCount: 10,
actualGrossWeightKg: 120,
actualNetWeightKg: 110,
actualVolumeCbm: 2.2,
volumeWeightKg: 367.4,
items: [{
id: 'item-1', boxCode: 'BOX-001', itemNo: '1', boxSpecification: null, packageCount: 10,
lengthCm: 60, widthCm: 40, heightCm: 30, girthCm: 200, unitWeightKg: 12,
unitVolumeCbm: 0.072, totalWeightKg: 120, totalVolumeCbm: 0.72, volumeWeightKg: 120.2,
intelligentImageFileId: '88001', machineNo: 'DEVICE-01', deviceMeasuredAt: '2026-08-18T10:30:00',
overLimitWarning: null, outerLengthCm: null, outerWidthCm: null, outerHeightCm: null, remark: null,
}],
})
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001' }, global: { plugins: [ElementPlus] } })
await flushPromises()
const summary = wrapper.get('.review-data').text()
expect(summary).toContain('审核数据总览')
const comparisonRows = wrapper.findAll('.review-comparison__row')
expect(comparisonRows).toHaveLength(2)
expect(comparisonRows[0].text()).toContain('客户申报')
expect(comparisonRows[0].text()).toContain('箱数9')
expect(comparisonRows[0].text()).toContain('毛重118 kg')
expect(comparisonRows[0].text()).not.toContain('实际品名')
expect(comparisonRows[0].text()).not.toContain('原单据号码')
expect(comparisonRows[1].text()).toContain('平台实测')
expect(comparisonRows[1].text()).toContain('箱数10 差异 +1')
expect(comparisonRows[1].text()).toContain('毛重120 kg · 差异 +2 kg')
expect(comparisonRows[1].text()).toContain('货物净重110 kg')
expect(comparisonRows[1].text()).toContain('体积2.2 m³ · 差异 +0.2 m³')
expect(comparisonRows[1].text()).toContain('体积重367.4 kg')
const items = wrapper.get('.review-items').text()
expect(items).toContain('箱规测量明细')
expect(items).toContain('BOX-001')
expect(items).toContain('60 × 40 × 30')
expect(items).toContain('120.2')
expect(items).toContain('2026-08-18 10:30:00')
expect(items).not.toContain('每箱毛重(kg)')
expect(items).not.toContain('总重量(kg)')
const image = wrapper.getComponent({ name: 'MeasurementEvidenceThumbnail' })
expect(image.props('customerId')).toBe('10001')
expect(image.props('inboundId')).toBe('90001')
expect(image.props('fileId')).toBe('88001')
const source = await import('./MeasurementReviewPanel.vue?raw').then((module) => module.default)
expect(source).toContain(':file-id="row.intelligentImageFileId"')
expect(source).toContain('openImagePreview(row.intelligentImageFileId')
expect(source).toContain('downloadInboundMeasurementImage')
expect(source).toContain('.review-table :deep(.el-table__header .cell) { white-space: nowrap; }')
expect(source).toContain('.review-table :deep(.review-time-cell .cell) { white-space: nowrap; }')
})
it('keeps difference labels absent when declared values are unavailable', async () => {
vi.mocked(getMeasurementReviewDetail).mockResolvedValue({ ...detail, originalGrossWeightKg: null, originalVolumeCbm: null })
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001' }, global: { plugins: [ElementPlus] } })
await flushPromises()
expect(wrapper.get('.review-comparison').text()).not.toContain('差异')
})
it('reuses WeightOcrPanel and enables only its review correction mode', async () => {
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001' }, global: { plugins: [ElementPlus] } })
await flushPromises()
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)
})
it('refreshes inboundVersion after a weight correction before approval', async () => {
vi.mocked(getMeasurementReviewDetail)
.mockResolvedValueOnce(detail)
.mockResolvedValueOnce({ ...detail, inboundVersion: 8, actualGrossWeightKg: 125, actualNetWeightKg: 115 })
vi.mocked(approveMeasurement).mockResolvedValue({ inboundId: '90001', measurementId: '70001', processStatus: 'WAIT_SPLIT', feeStatus: 'CALCULATED', initialPricingSucceeded: true, initialPricingMessage: null, initialFee: null })
vi.spyOn(ElMessageBox, 'confirm').mockResolvedValue(undefined as never)
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001' }, global: { plugins: [ElementPlus] } })
await flushPromises()
await wrapper.get('.mock-weight-change').trigger('click')
await flushPromises()
await wrapper.findAll('button').find((button) => button.text().includes('通过并进入待分柜'))?.trigger('click')
await flushPromises()
expect(getMeasurementReviewDetail).toHaveBeenCalledTimes(3)
expect(approveMeasurement).toHaveBeenCalledWith('10001', '90001', { measurementId: '70001', expectedVersion: 8 })
expect(wrapper.emitted('changed')).toHaveLength(1)
})
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: '历史审核说明' })
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001' }, global: { plugins: [ElementPlus] } })
await flushPromises()
expect(wrapper.text()).toContain('历史记录为审核驳回,仅供查看')
expect(wrapper.text()).toContain('历史审核说明')
expect(wrapper.text()).not.toContain('修改重量')
expect(wrapper.text()).not.toContain('异常放行')
})
})