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'
|
|
|
|
|
|
import DataTable from '@/shared/components/DataTable.vue'
|
2026-08-13 20:00:38 +08:00
|
|
|
|
import { approveMeasurement, getMeasurementReviewDetail, listMeasurementReviewHistory, rejectMeasurement } from './api'
|
2026-08-04 21:01:39 +08:00
|
|
|
|
import MeasurementReviewPanel from './MeasurementReviewPanel.vue'
|
|
|
|
|
|
|
|
|
|
|
|
vi.mock('./api', () => ({
|
|
|
|
|
|
approveMeasurement: vi.fn(),
|
|
|
|
|
|
getMeasurementReviewDetail: vi.fn(),
|
|
|
|
|
|
listMeasurementReviewHistory: vi.fn(),
|
|
|
|
|
|
rejectMeasurement: vi.fn(),
|
|
|
|
|
|
releaseRejectedMeasurement: vi.fn(),
|
|
|
|
|
|
}))
|
|
|
|
|
|
|
|
|
|
|
|
const detail = {
|
|
|
|
|
|
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-04 21:01:39 +08:00
|
|
|
|
actualPackageCount: 10, actualGrossWeightKg: 110, actualVolumeCbm: 2.2, volumeWeightKg: 367.4, actualGoodsName: '测试货物',
|
2026-08-07 16:17:45 +08:00
|
|
|
|
items: [{ id: '1', boxCode: 'RK90001#10-1', itemNo: '001', boxSpecification: '60×40×30', packageCount: 10, lengthCm: 60, widthCm: 40, heightCm: 30, girthCm: 240, unitWeightKg: 11, unitVolumeCbm: 0.072, totalWeightKg: 110, totalVolumeCbm: 0.72, volumeWeightKg: 120.24, intelligentImageFileId: '30001', machineNo: 'MJ20210327', measuredAt: '2026-08-04T10:30:00+08:00', overLimitWarning: '周长超过渠道限制,请人工核验尺寸后再作出审核决定', outerLengthCm: 61, outerWidthCm: 41, outerHeightCm: 31, remark: '外箱存在轻微变形' }],
|
2026-08-04 21:01:39 +08:00
|
|
|
|
weightResults: [{ id: '2', fileId: 'file-1', ocrProvider: 'OCR', ocrRawText: '设备屏幕原始识别内容 110.5 KG,包含一段需要自动换行展示的较长文本', ocrWeightValue: 110.5, ocrWeightUnit: 'KG', confidence: 0.98, adjustedWeightValue: 110, adjustedWeightUnit: 'KG', finalWeightKg: 110, recognitionStatus: 'CONFIRMED', adjustmentReason: '去除托盘重量后采用净值', adjustedBy: '20001', adjustedAt: '2026-08-04T09:58:00', confirmedBy: '20002', confirmedAt: '2026-08-04T10:00:00', version: 1 }],
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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', 'firstleg:inbound:exception-release']
|
|
|
|
|
|
useAccessPreviewStore().initializeAfterLogin()
|
|
|
|
|
|
vi.mocked(getMeasurementReviewDetail).mockResolvedValue(detail)
|
|
|
|
|
|
vi.mocked(listMeasurementReviewHistory).mockResolvedValue({ records: [], page: 1, size: 20, total: 0, pages: 0 })
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('loads and compares original, device and OCR data without exposing a normal fee action', async () => {
|
|
|
|
|
|
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001' }, global: { plugins: [ElementPlus] } })
|
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
|
|
|
|
|
|
expect(wrapper.text()).toContain('原单申报')
|
|
|
|
|
|
expect(wrapper.text()).toContain('设备实测')
|
2026-08-11 19:35:28 +08:00
|
|
|
|
expect(wrapper.text()).toContain('设备采集')
|
|
|
|
|
|
expect(wrapper.text()).not.toContain('DEVICE')
|
2026-08-04 21:01:39 +08:00
|
|
|
|
expect(wrapper.text()).toContain('称重 OCR')
|
2026-08-07 16:25:56 +08:00
|
|
|
|
expect(wrapper.text()).toContain('实测长 × 宽 × 高(cm)')
|
2026-08-04 21:01:39 +08:00
|
|
|
|
expect(wrapper.text()).toContain('周长超过渠道限制')
|
|
|
|
|
|
expect(wrapper.text()).toContain('OCR 原文:设备屏幕原始识别内容')
|
|
|
|
|
|
expect(wrapper.text()).toContain('调整原因:去除托盘重量后采用净值')
|
|
|
|
|
|
expect(wrapper.findComponent(DataTable).exists()).toBe(true)
|
|
|
|
|
|
expect(wrapper.text()).not.toContain('租户')
|
|
|
|
|
|
expect(wrapper.text()).toContain('通过并完成入库')
|
|
|
|
|
|
expect(wrapper.text()).not.toContain('确认费用')
|
|
|
|
|
|
expect(getMeasurementReviewDetail).toHaveBeenCalledWith('10001', '90001')
|
2026-08-12 16:31:46 +08:00
|
|
|
|
const source = await import('./MeasurementReviewPanel.vue?raw').then((module) => module.default)
|
|
|
|
|
|
expect(source).toContain('defineExpose({ confirmApprove, openReasonDialog, isPending, isRejected, canReview, canRelease, actionLoading })')
|
2026-08-04 21:01:39 +08:00
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('shows the rejected release action only when the permission and rejected state exist', 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('重量图片不清晰')
|
|
|
|
|
|
|
|
|
|
|
|
const auth = useAuthStore()
|
|
|
|
|
|
auth.permissions = ['firstleg:inbound:measurement-review-history']
|
|
|
|
|
|
useAccessPreviewStore().initializeAfterLogin()
|
|
|
|
|
|
await wrapper.vm.$nextTick()
|
|
|
|
|
|
expect(wrapper.findAll('button').some((button) => button.text().includes('异常放行'))).toBe(false)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
it('emits changed after a successful review write', async () => {
|
2026-08-13 14:55:59 +08:00
|
|
|
|
vi.mocked(approveMeasurement).mockResolvedValue({ feeId: '1', feeNo: 'FEE-1', feeVersion: 1, feeStatus: 'SENT', inboundId: '90001', totalAmount: 100, currency: 'CNY', nextProcessStatus: 'WAIT_SPLIT' })
|
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()
|
|
|
|
|
|
const buttons = wrapper.findAll('button')
|
|
|
|
|
|
const approve = buttons.find((button) => button.text().includes('通过并完成入库'))
|
|
|
|
|
|
expect(approve).toBeDefined()
|
|
|
|
|
|
await approve?.trigger('click')
|
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
|
|
|
|
|
|
expect(ElMessageBox.confirm).toHaveBeenCalledWith(
|
|
|
|
|
|
expect.stringContaining('自动试算并发布费用'),
|
|
|
|
|
|
'确认测量审核通过',
|
|
|
|
|
|
expect.objectContaining({ confirmButtonText: '通过并完成入库' }),
|
|
|
|
|
|
)
|
2026-08-13 20:00:38 +08:00
|
|
|
|
expect(approveMeasurement).toHaveBeenCalledWith('10001', '90001', { measurementId: '70001', expectedVersion: 7 })
|
2026-08-04 21:01:39 +08:00
|
|
|
|
expect(wrapper.emitted('changed')).toHaveLength(1)
|
|
|
|
|
|
})
|
2026-08-13 20:00:38 +08:00
|
|
|
|
|
|
|
|
|
|
it('rejects with the inbound optimistic-lock version instead of the measurement version', async () => {
|
|
|
|
|
|
vi.mocked(rejectMeasurement).mockResolvedValue(undefined)
|
|
|
|
|
|
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001' }, global: { plugins: [ElementPlus] } })
|
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
|
|
|
|
|
|
const reject = wrapper.findAll('button').find((button) => button.text().includes('驳回'))
|
|
|
|
|
|
await reject?.trigger('click')
|
|
|
|
|
|
await wrapper.find('textarea').setValue('重量图片需要重新核对')
|
|
|
|
|
|
const confirm = wrapper.findAll('button').find((button) => button.text().includes('确认驳回'))
|
|
|
|
|
|
await confirm?.trigger('click')
|
|
|
|
|
|
await flushPromises()
|
|
|
|
|
|
|
|
|
|
|
|
expect(rejectMeasurement).toHaveBeenCalledWith('10001', '90001', {
|
|
|
|
|
|
measurementId: '70001',
|
|
|
|
|
|
expectedVersion: 7,
|
|
|
|
|
|
reason: '重量图片需要重新核对',
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
2026-08-04 21:01:39 +08:00
|
|
|
|
})
|