修复采购审核竞态并整理管理页面展示
This commit is contained in:
@@ -19,6 +19,7 @@ vi.mock('../weight/components/WeightOcrPanel.vue', () => ({
|
||||
name: 'WeightOcrPanel',
|
||||
props: { customerId: String, inboundId: String, reviewMode: Boolean, correctionMode: Boolean, tableMode: Boolean },
|
||||
emits: ['changed'],
|
||||
methods: { reload: async () => {} },
|
||||
template: '<button class="mock-weight-change" @click="$emit(\'changed\')">重量证据</button>',
|
||||
},
|
||||
}))
|
||||
@@ -42,6 +43,14 @@ const detail: MeasurementReviewDetail = {
|
||||
weightResults: [],
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
enableAutoUnmount(afterEach)
|
||||
|
||||
describe('MeasurementReviewPanel', () => {
|
||||
@@ -98,18 +107,23 @@ describe('MeasurementReviewPanel', () => {
|
||||
expect(source).not.toContain('listMeasurementReviewHistory')
|
||||
})
|
||||
|
||||
it.each([true, false])('describes dropship palletizing and uses the server result when pricing succeeds=%s', async (initialPricingSucceeded) => {
|
||||
vi.mocked(approveMeasurement).mockResolvedValue({ inboundId: '90001', measurementId: '70001', processStatus: 'WAIT_PALLETIZE', feeStatus: initialPricingSucceeded ? 'CALCULATED' : 'UNPRICED', initialPricingSucceeded, initialPricingMessage: null, initialFee: null })
|
||||
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 })
|
||||
const confirmation = vi.spyOn(ElMessageBox, 'confirm').mockResolvedValue(undefined as never)
|
||||
const success = vi.spyOn(ElMessage, 'success')
|
||||
const warning = vi.spyOn(ElMessage, 'warning')
|
||||
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001', fulfillmentType: 'ONE_CLICK_DROPSHIP' }, global: { plugins: [ElementPlus] } })
|
||||
const wrapper = mount(MeasurementReviewPanel, { props: { customerId: '10001', inboundId: '90001', fulfillmentType }, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
expect(wrapper.text()).not.toContain('通过并进入')
|
||||
await wrapper.findAll('button').find((button) => button.text() === '通过')!.trigger('click')
|
||||
await flushPromises()
|
||||
expect(confirmation).toHaveBeenCalledWith('审核通过后,入库单将进入待分柜', '确认测量', expect.objectContaining({ confirmButtonText: '通过' }))
|
||||
expect(initialPricingSucceeded ? success : warning).toHaveBeenCalledWith(expect.stringContaining('入库单已进入待一件代发组托'))
|
||||
expect(confirmation).toHaveBeenCalledWith(`审核通过后,入库单将进入${targetLabel}`, '确认测量', expect.objectContaining({ confirmButtonText: '通过' }))
|
||||
expect(initialPricingSucceeded ? success : warning).toHaveBeenCalledWith(expect.stringContaining(`入库单已进入${targetLabel}`))
|
||||
expect(approveMeasurement).toHaveBeenCalledTimes(1)
|
||||
expect(wrapper.emitted('changed')).toHaveLength(1)
|
||||
})
|
||||
@@ -125,7 +139,7 @@ describe('MeasurementReviewPanel', () => {
|
||||
expect(wrapper.get('.review-toolbar__context').text()).toBe('客户:欧阳的公司 · 渠道:海运')
|
||||
})
|
||||
|
||||
it('shows declared facts, authoritative measurement totals, and box-detail evidence for manual review', async () => {
|
||||
it('shows declared facts, raw measured volume, weight totals and box-detail evidence for manual review', async () => {
|
||||
vi.mocked(getMeasurementReviewDetail).mockResolvedValue({
|
||||
...detail,
|
||||
originalPackageCount: 9,
|
||||
@@ -153,19 +167,19 @@ describe('MeasurementReviewPanel', () => {
|
||||
expect(comparisonRows.map(row => row.findAll('th, td').map(cell => cell.text()))).toEqual([
|
||||
['箱数', '9', '10', '差异 +1'],
|
||||
['毛重(kg)', '118', '120', '差异 +2 kg'],
|
||||
['体积(m³)', '2', '2.2', '差异 +0.2 m³'],
|
||||
['体积(m³)', '2', '0.72', '差异 -1.28 m³'],
|
||||
])
|
||||
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')
|
||||
expect(extraFacts).toContain('体积重367.4kg')
|
||||
expect(extraFacts).toContain('计费体积重367.4kg')
|
||||
|
||||
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).not.toContain('120.2')
|
||||
expect(items).toContain('2026-08-18 10:30:00')
|
||||
expect(items).not.toContain('每箱毛重(kg)')
|
||||
expect(items).not.toContain('总重量(kg)')
|
||||
@@ -182,6 +196,103 @@ describe('MeasurementReviewPanel', () => {
|
||||
expect(source).toContain('.review-table :deep(.review-time-cell .cell) { white-space: nowrap; }')
|
||||
})
|
||||
|
||||
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('—')
|
||||
})
|
||||
|
||||
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] } })
|
||||
@@ -190,7 +301,7 @@ describe('MeasurementReviewPanel', () => {
|
||||
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', '—'])
|
||||
expect(rows[2].findAll('td').map(cell => cell.text())).toEqual(['—', '2.2', '—'])
|
||||
expect(rows[2].findAll('td').map(cell => cell.text())).toEqual(['—', '—', '—'])
|
||||
})
|
||||
|
||||
it('reuses WeightOcrPanel and enables only its review correction mode', async () => {
|
||||
|
||||
Reference in New Issue
Block a user