2026-09-09 11:50:42 +08:00
import ElementPlus , { ElMessage , ElMessageBox } from 'element-plus'
2026-08-04 21:01:39 +08:00
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' ,
2026-10-21 23:20:10 +08:00
props : { customerId : String , inboundId : String , reviewMode : Boolean , correctionMode : Boolean , tableMode : Boolean },
2026-08-18 11:33:01 +08:00
emits : [ 'changed' ],
2026-10-30 15:42:37 +08:00
methods : { reload : async () => {} },
2026-08-18 11:33:01 +08:00
template : '<button class="mock-weight-change" @click="$emit(\'changed\')">重量证据</button>' ,
},
2026-08-14 19:59:32 +08:00
}))
2026-08-18 16:09:43 +08:00
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>' ,
},
}))
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
}
2026-10-30 15:42:37 +08:00
const measuredItem : MeasurementReviewDetail [ 'items' ][ number ] = {
id : 'item-raw' , boxCode : 'BOX-RAW-001' , itemNo : '1' , boxSpecification : null , packageCount : 22 ,
lengthCm : 62 , widthCm : 52 , heightCm : 52 , girthCm : 270 , unitWeightKg : 25.9318 ,
unitVolumeCbm : 0.3275 , totalWeightKg : 570.5 , totalVolumeCbm : 7.205 , volumeWeightKg : 1203.235 ,
intelligentImageFileId : null , machineNo : null , deviceMeasuredAt : null , overLimitWarning : null ,
outerLengthCm : 75 , outerWidthCm : 66 , outerHeightCm : 66 , remark : null ,
}
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-10-23 10:56:35 +08:00
it ( 'keeps context in a scrolling toolbar and moves weight actions and totals to the weight details' , 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 ( '待审核' )
2026-10-23 10:56:35 +08:00
expect ( toolbar . text ()). not . toContain ( '修改重量' )
const weightHeading = wrapper . get ( '.review-weight-items header' )
expect ( weightHeading . text ()). toContain ( '修改重量' )
expect ( weightHeading . text ()). toContain ( '实测毛重 120 kg' )
expect ( weightHeading . text ()). toContain ( '货物重量 110 kg' )
expect ( weightHeading . find ( '.lucide-scale' ). exists ()). toBe ( true )
expect ( weightHeading . find ( '.lucide-pencil' ). exists ()). toBe ( true )
2026-10-20 16:45:09 +08:00
expect ( toolbar . findAll ( 'button' ). map ( button => button . text ())). toContain ( '通过' )
2026-08-18 11:33:01 +08:00
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-18 16:09:43 +08:00
expect ( wrapper . text ()). not . toContain ( '审核历史' )
expect ( listMeasurementReviewHistory ). not . toHaveBeenCalled ()
2026-08-12 16:31:46 +08:00
const source = await import ( './MeasurementReviewPanel.vue?raw' ). then (( module ) => module . default )
2026-10-23 10:56:35 +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-18 16:09:43 +08:00
expect ( source ). not . toContain ( 'listMeasurementReviewHistory' )
2026-08-14 19:59:32 +08:00
})
2026-10-30 15:42:37 +08:00
it . each ([
{ fulfillmentType : 'ONE_CLICK_DROPSHIP' , processStatus : 'WAIT_PALLETIZE' , targetLabel : '待一件代发组托' , initialPricingSucceeded : true },
{ fulfillmentType : 'ONE_CLICK_DROPSHIP' , processStatus : 'WAIT_PALLETIZE' , targetLabel : '待一件代发组托' , initialPricingSucceeded : false },
{ fulfillmentType : 'FBA' , processStatus : 'WAIT_SPLIT' , targetLabel : '待分柜' , initialPricingSucceeded : true },
{ fulfillmentType : 'FBA' , processStatus : 'WAIT_SPLIT' , targetLabel : '待分柜' , initialPricingSucceeded : false },
])( 'describes $fulfillmentType approval target and uses the server result when pricing succeeds=$initialPricingSucceeded' , async ({ fulfillmentType , processStatus , targetLabel , initialPricingSucceeded }) => {
vi . mocked ( approveMeasurement ). mockResolvedValue ({ inboundId : '90001' , measurementId : '70001' , processStatus , feeStatus : initialPricingSucceeded ? 'CALCULATED' : 'UNPRICED' , initialPricingSucceeded , initialPricingMessage : null , initialFee : null })
2026-09-09 11:50:42 +08:00
const confirmation = vi . spyOn ( ElMessageBox , 'confirm' ). mockResolvedValue ( undefined as never )
const success = vi . spyOn ( ElMessage , 'success' )
const warning = vi . spyOn ( ElMessage , 'warning' )
2026-10-30 15:42:37 +08:00
const wrapper = mount ( MeasurementReviewPanel , { props : { customerId : '10001' , inboundId : '90001' , fulfillmentType }, global : { plugins : [ ElementPlus ] } })
2026-09-09 11:50:42 +08:00
await flushPromises ()
2026-10-20 16:45:09 +08:00
expect ( wrapper . text ()). not . toContain ( '通过并进入' )
await wrapper . findAll ( 'button' ). find (( button ) => button . text () === '通过' ) ! . trigger ( 'click' )
2026-09-09 11:50:42 +08:00
await flushPromises ()
2026-10-30 15:42:37 +08:00
expect ( confirmation ). toHaveBeenCalledWith ( `审核通过后,入库单将进入 ${ targetLabel } ` , '确认测量' , expect . objectContaining ({ confirmButtonText : '通过' }))
expect ( initialPricingSucceeded ? success : warning ). toHaveBeenCalledWith ( expect . stringContaining ( `入库单已进入 ${ targetLabel } ` ))
2026-09-09 11:50:42 +08:00
expect ( approveMeasurement ). toHaveBeenCalledTimes ( 1 )
expect ( wrapper . emitted ( 'changed' )). toHaveLength ( 1 )
})
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-10-30 15:42:37 +08:00
it ( 'shows declared facts, raw measured volume, weight totals and box-detail evidence for manual review' , async () => {
2026-08-18 16:09:43 +08:00
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 ( '审核数据总览' )
2026-10-20 10:11:31 +08:00
const comparisonRows = wrapper . findAll ( '.review-comparison tbody tr' )
expect ( comparisonRows . map ( row => row . findAll ( 'th, td' ). map ( cell => cell . text ()))). toEqual ([
[ '箱数' , '9' , '10' , '差异 +1' ],
[ '毛重(kg) ' , '118' , '120' , '差异 +2 kg' ],
2026-10-30 15:42:37 +08:00
[ '体积(m³)' , '2' , '0.72' , '差异 -1.28 m³' ],
2026-10-20 10:11:31 +08:00
])
expect ( wrapper . get ( '.review-comparison' ). text ()). not . toContain ( '实际品名' )
expect ( wrapper . get ( '.review-comparison' ). text ()). not . toContain ( '原单据号码' )
const extraFacts = wrapper . get ( '.review-extra-facts' ). text (). replace ( /\s/g , '' )
expect ( extraFacts ). toContain ( '货物净重110kg' )
2026-10-30 15:42:37 +08:00
expect ( extraFacts ). toContain ( '计费体积重367.4kg' )
2026-08-18 16:09:43 +08:00
const items = wrapper . get ( '.review-items' ). text ()
expect ( items ). toContain ( '箱规测量明细' )
expect ( items ). toContain ( 'BOX-001' )
expect ( items ). toContain ( '60 × 40 × 30' )
2026-10-30 15:42:37 +08:00
expect ( items ). not . toContain ( '120.2' )
2026-08-18 16:09:43 +08:00
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' )
2026-10-20 10:11:31 +08:00
expect ( source ). not . toContain ( '.review-table :deep(.el-table__header .cell) { white-space: nowrap; }' )
2026-08-18 16:09:43 +08:00
expect ( source ). toContain ( '.review-table :deep(.review-time-cell .cell) { white-space: nowrap; }' )
})
2026-10-30 15:42:37 +08:00
it ( 'uses original measured dimensions for unit volume, row volume and summary without replacing billing snapshots' , async () => {
const value = { ... detail , originalVolumeCbm : 3.3 , actualVolumeCbm : 7.205 , items : [{ ... measuredItem }] }
vi . mocked ( getMeasurementReviewDetail ). mockResolvedValue ( value )
const wrapper = mount ( MeasurementReviewPanel , { props : { customerId : '10001' , inboundId : '90001' }, global : { plugins : [ ElementPlus ] } })
await flushPromises ()
const volumeRow = wrapper . get ( '.review-comparison tbody tr:last-child' )
expect ( volumeRow . findAll ( 'td' ). map ( cell => cell . text ())). toEqual ([ '3.3' , '3.688' , '差异 +0.388 m³' ])
const cells = wrapper . get ( '.review-items .el-table__body tbody tr' ). findAll ( 'td' )
expect ( cells . slice ( 5 , 7 ). map ( cell => cell . text ())). toEqual ([ '0.168' , '3.688' ])
expect ( wrapper . get ( '.review-items' ). text ()). not . toContain ( '1,203.235' )
expect ( value . actualVolumeCbm ). toBe ( 7.205 )
expect ( value . items [ 0 ]). toMatchObject ({ unitVolumeCbm : 0.3275 , totalVolumeCbm : 7.205 , outerLengthCm : 75 })
})
it ( 'sums all unrounded measured row volumes before formatting the review comparison' , async () => {
vi . mocked ( getMeasurementReviewDetail ). mockResolvedValue ({ ... detail , originalVolumeCbm : 3.3 , actualVolumeCbm : 9.999 ,
items : [{ ... measuredItem }, { ... measuredItem , id : 'item-2' , boxCode : 'BOX-RAW-002' , lengthCm : 50 , widthCm : 40 , heightCm : 30 , packageCount : 2 }] })
const wrapper = mount ( MeasurementReviewPanel , { props : { customerId : '10001' , inboundId : '90001' }, global : { plugins : [ ElementPlus ] } })
await flushPromises ()
expect ( wrapper . get ( '.review-comparison tbody tr:last-child' ). findAll ( 'td' ). map ( cell => cell . text ())). toEqual ([ '3.3' , '3.808' , '差异 +0.508 m³' ])
})
it ( 'separates warehouse overrides and their saved volume from original measured data' , async () => {
const corrected = { ... measuredItem , outerLengthCm : 63 , outerWidthCm : 53 , outerHeightCm : 53 ,
unitVolumeCbm : 0.177 , totalVolumeCbm : 3.894 , volumeWeightKg : 650.298 }
const unmodified = { ... measuredItem , id : 'item-unmodified' , boxCode : 'BOX-RAW-002' ,
lengthCm : 10 , widthCm : 20 , heightCm : 30 , packageCount : 2 ,
outerLengthCm : null , outerWidthCm : null , outerHeightCm : null ,
unitVolumeCbm : 0.006 , totalVolumeCbm : 0.012 , volumeWeightKg : 2.004 }
const value = { ... detail , originalVolumeCbm : 3.3 , actualPackageCount : 24 ,
actualVolumeCbm : 3.906 , items : [ corrected , unmodified ] }
vi . mocked ( getMeasurementReviewDetail ). mockResolvedValue ( value )
const wrapper = mount ( MeasurementReviewPanel , { props : { customerId : '10001' , inboundId : '90001' }, global : { plugins : [ ElementPlus ] } })
await flushPromises ()
expect ( wrapper . get ( '.review-comparison tbody tr:last-child' ). findAll ( 'td' ). map ( cell => cell . text ())). toEqual ([ '3.3' , '3.7' , '差异 +0.4 m³' ])
const measuredCells = wrapper . get ( '.review-items .el-table__body tbody tr' ). findAll ( 'td' )
expect ( measuredCells [ 3 ] ! . text ()). toBe ( '62 × 52 × 52' )
expect ( measuredCells . slice ( 5 , 7 ). map ( cell => cell . text ())). toEqual ([ '0.168' , '3.688' ])
const outer = wrapper . get ( '.review-outer-items' )
expect ( outer . get ( 'h3' ). text ()). toBe ( '仓库修改外箱' )
expect ( outer . findAll ( '.el-table__body tbody tr' )). toHaveLength ( 1 )
expect ( outer . get ( '.el-table__body tbody tr' ). findAll ( 'td' ). map ( cell => cell . text ())). toEqual ([
'1' , 'BOX-RAW-001' , '63' , '53' , '53' , '22' , '0.177' , '3.894' , '650.298' ,
])
expect ( outer . get ( '.el-table__footer' ). findAll ( 'td' ). map ( cell => cell . text ())). toEqual ([
'合计' , '' , '' , '' , '' , '22' , '—' , '3.894' , '650.298' ,
])
expect ( value . items [ 0 ]). toBe ( corrected )
expect ( corrected ). toMatchObject ({ lengthCm : 62 , unitVolumeCbm : 0.177 , totalVolumeCbm : 3.894 })
})
it ( 'hides the warehouse outer section when no manual dimension was entered' , async () => {
vi . mocked ( getMeasurementReviewDetail ). mockResolvedValue ({ ... detail , items : [{ ... measuredItem ,
outerLengthCm : null , outerWidthCm : null , outerHeightCm : null }] })
const wrapper = mount ( MeasurementReviewPanel , { props : { customerId : '10001' , inboundId : '90001' }, global : { plugins : [ ElementPlus ] } })
await flushPromises ()
expect ( wrapper . find ( '.review-outer-items' ). exists ()). toBe ( false )
expect ( wrapper . get ( '.review-items' ). text ()). toContain ( '3.688' )
})
it ( 'keeps partial outer edits and missing saved volumes explicit, then removes the section after clearing' , async () => {
const item = { ... measuredItem , outerLengthCm : 63 , outerWidthCm : null , outerHeightCm : null ,
unitVolumeCbm : null , totalVolumeCbm : null , volumeWeightKg : null }
vi . mocked ( getMeasurementReviewDetail ). mockResolvedValue ({ ... detail , items : [ item ] })
const wrapper = mount ( MeasurementReviewPanel , { props : { customerId : '10001' , inboundId : '90001' }, global : { plugins : [ ElementPlus ] } })
await flushPromises ()
const outer = wrapper . get ( '.review-outer-items' )
expect ( outer . get ( '.el-table__body tbody tr' ). findAll ( 'td' ). slice ( 2 ). map ( cell => cell . text ())). toEqual ([
'63' , '—' , '—' , '22' , '—' , '—' , '—' ,
])
expect ( outer . get ( '.el-table__footer' ). findAll ( 'td' ). slice ( 7 ). map ( cell => cell . text ())). toEqual ([ '—' , '—' ])
vi . mocked ( getMeasurementReviewDetail ). mockResolvedValue ({ ... detail , version : 5 , items : [{ ... item , outerLengthCm : null }] })
await wrapper . get ( '.review-refresh' ). trigger ( 'click' )
await flushPromises ()
expect ( wrapper . find ( '.review-outer-items' ). exists ()). toBe ( false )
expect ( wrapper . get ( '.review-items' ). text ()). toContain ( '3.688' )
})
it ( 'totals only the saved correction rows before formatting' , async () => {
vi . mocked ( getMeasurementReviewDetail ). mockResolvedValue ({ ... detail , items : [
{ ... measuredItem , totalVolumeCbm : 1.2344 },
{ ... measuredItem , id : 'item-2' , boxCode : 'BOX-RAW-002' , totalVolumeCbm : 1.2344 },
] })
const wrapper = mount ( MeasurementReviewPanel , { props : { customerId : '10001' , inboundId : '90001' }, global : { plugins : [ ElementPlus ] } })
await flushPromises ()
expect ( wrapper . get ( '.review-outer-items .el-table__footer' ). findAll ( 'td' )[ 7 ] ! . text ()). toBe ( '2.469' )
})
it . each ([{ lengthCm : null }, { widthCm : 0 }, { heightCm : Number.NaN }, { packageCount : null }, { packageCount : 1.5 }])( 'keeps incomplete or invalid measured volume unknown instead of using billing totals: %o' , async ( invalid ) => {
vi . mocked ( getMeasurementReviewDetail ). mockResolvedValue ({ ... detail , items : [{ ... measuredItem }, { ... measuredItem , ... invalid , id : 'item-invalid' }] })
const wrapper = mount ( MeasurementReviewPanel , { props : { customerId : '10001' , inboundId : '90001' }, global : { plugins : [ ElementPlus ] } })
await flushPromises ()
expect ( wrapper . get ( '.review-comparison tbody tr:last-child' ). findAll ( 'td' ). map ( cell => cell . text ())). toEqual ([ '2' , '—' , '—' ])
expect ( wrapper . findAll ( '.review-items .el-table__body tbody tr' )[ 1 ] ! . findAll ( 'td' )[ 6 ] ! . text ()). toBe ( '—' )
})
2026-08-18 16:09:43 +08:00
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 ()
2026-10-20 10:11:31 +08:00
const rows = wrapper . findAll ( '.review-comparison tbody tr' )
expect ( rows [ 0 ]. findAll ( 'td' ). at ( - 1 ) ? . text ()). toBe ( '一致' )
expect ( rows [ 1 ]. findAll ( 'td' ). map ( cell => cell . text ())). toEqual ([ '—' , '120' , '—' ])
2026-10-30 15:42:37 +08:00
expect ( rows [ 2 ]. findAll ( 'td' ). map ( cell => cell . text ())). toEqual ([ '—' , '—' , '—' ])
2026-08-18 16:09:43 +08:00
})
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 ()
2026-10-20 16:45:09 +08:00
await wrapper . findAll ( 'button' ). find (( button ) => button . text () === '通过' ) ? . trigger ( 'click' )
2026-08-14 14:27:03 +08:00
await flushPromises ()
2026-09-01 20:49:04 +08:00
expect ( getMeasurementReviewDetail ). toHaveBeenCalledTimes ( 2 )
2026-08-18 11:33:01 +08:00
expect ( approveMeasurement ). toHaveBeenCalledWith ( '10001' , '90001' , { measurementId : '70001' , expectedVersion : 8 })
2026-08-14 14:27:03 +08:00
expect ( wrapper . emitted ( 'changed' )). toHaveLength ( 1 )
})
2026-10-20 10:11:31 +08:00
it ( 'exposes the same guarded actions when a dialog owns the fixed footer' , async () => {
vi . spyOn ( ElMessageBox , 'confirm' ). mockResolvedValue ( undefined as never )
vi . mocked ( approveMeasurement ). mockResolvedValue ({ inboundId : '90001' , measurementId : '70001' , processStatus : 'WAIT_SPLIT' , feeStatus : 'CALCULATED' , initialPricingSucceeded : true , initialPricingMessage : null , initialFee : null })
const wrapper = mount ( MeasurementReviewPanel , {
props : { customerId : '10001' , inboundId : '90001' , showActions : false },
global : { plugins : [ ElementPlus ] },
})
await flushPromises ()
expect ( wrapper . find ( '.review-toolbar__actions' ). exists ()). toBe ( false )
expect ( wrapper . get ( '.review-toolbar__context' ). text ()). toContain ( '客户:柏悦客户' )
2026-10-20 16:45:09 +08:00
expect ( wrapper . vm . approvalButtonLabel ). toBe ( '通过' )
2026-10-23 10:56:35 +08:00
await wrapper . get ( '.review-weight-summary button' ). trigger ( 'click' )
2026-10-20 10:11:31 +08:00
await flushPromises ()
expect ( wrapper . getComponent ({ name : 'WeightOcrPanel' }). props ( 'correctionMode' )). toBe ( true )
await wrapper . vm . confirmApprove ()
expect ( approveMeasurement ). toHaveBeenCalledWith ( '10001' , '90001' , { measurementId : '70001' , expectedVersion : 7 })
expect ( wrapper . emitted ( 'changed' )). toHaveLength ( 1 )
})
2026-09-01 20:49:04 +08:00
it ( 'emits only after approval succeeds and blocks duplicate submissions' , async () => {
let finishApproval : (() => void ) | undefined
vi . mocked ( approveMeasurement ). mockImplementation (() => new Promise (( resolve ) => {
finishApproval = () => resolve ({ 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 ()
2026-10-20 16:45:09 +08:00
const approveButton = wrapper . findAll ( 'button' ). find (( button ) => button . text () === '通过' ) !
2026-09-01 20:49:04 +08:00
await approveButton . trigger ( 'click' )
await approveButton . trigger ( 'click' )
expect ( approveMeasurement ). toHaveBeenCalledTimes ( 1 )
expect ( wrapper . emitted ( 'changed' )). toBeUndefined ()
finishApproval ? .()
await flushPromises ()
expect ( wrapper . emitted ( 'changed' )). toHaveLength ( 1 )
expect ( getMeasurementReviewDetail ). toHaveBeenCalledTimes ( 1 )
})
it ( 'keeps the review panel open and does not emit when approval fails' , async () => {
vi . mocked ( approveMeasurement ). mockRejectedValue ( new Error ( '审核写入失败' ))
vi . spyOn ( ElMessageBox , 'confirm' ). mockResolvedValue ( undefined as never )
const wrapper = mount ( MeasurementReviewPanel , { props : { customerId : '10001' , inboundId : '90001' }, global : { plugins : [ ElementPlus ] } })
await flushPromises ()
2026-10-20 16:45:09 +08:00
await wrapper . findAll ( 'button' ). find (( button ) => button . text () === '通过' ) ! . trigger ( 'click' )
2026-09-01 20:49:04 +08:00
await flushPromises ()
expect ( wrapper . emitted ( 'changed' )). toBeUndefined ()
expect ( wrapper . find ( '.review-panel' ). exists ()). toBe ( true )
2026-10-20 16:45:09 +08:00
expect ( wrapper . findAll ( 'button' ). some (( button ) => button . text () === '通过' )). toBe ( true )
2026-09-01 20:49:04 +08:00
})
it ( 'blocks approval while the order detail navigation is loading' , async () => {
vi . spyOn ( ElMessageBox , 'confirm' ). mockResolvedValue ( undefined as never )
const wrapper = mount ( MeasurementReviewPanel , {
props : { customerId : '10001' , inboundId : '90001' , postApproveLoading : true },
global : { plugins : [ ElementPlus ] },
})
await flushPromises ()
2026-10-20 16:45:09 +08:00
await wrapper . findAll ( 'button' ). find (( button ) => button . text () === '通过' ) ! . trigger ( 'click' )
2026-09-01 20:49:04 +08:00
expect ( approveMeasurement ). not . toHaveBeenCalled ()
})
it ( 'does not submit approval again after approval succeeded but navigation needs retrying' , async () => {
vi . spyOn ( ElMessageBox , 'confirm' ). mockResolvedValue ( undefined as never )
const wrapper = mount ( MeasurementReviewPanel , {
props : { customerId : '10001' , inboundId : '90001' , approvalCompleted : true },
global : { plugins : [ ElementPlus ] },
})
await flushPromises ()
const approveButton = wrapper . findAll ( 'button' ). find (( button ) => button . text (). includes ( '审核已通过' )) !
expect ( approveButton . attributes ( 'disabled' )). toBeDefined ()
await approveButton . trigger ( 'click' )
expect ( approveMeasurement ). not . toHaveBeenCalled ()
})
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
})