完善预约确认与排车表单并统一计费和筛选展示

This commit is contained in:
2026-10-28 12:01:43 +08:00
parent 0c3f4ece2a
commit 39b9a6a1a0
27 changed files with 277 additions and 77 deletions
@@ -13,7 +13,7 @@ import {
submitInboundMeasurement,
updateConfirmedPackageCount,
} from './api'
import type { InboundMeasurementDetail } from './types'
import type { InboundMeasurementDetail, SaveInboundMeasurementItemRequest } from './types'
import InboundMeasurementPanel from './InboundMeasurementPanel.vue'
vi.mock('./api', () => ({
@@ -152,6 +152,10 @@ async function mountPanel(permissions = [
return wrapper
}
function measurementDraft(wrapper: Awaited<ReturnType<typeof mountPanel>>) {
return (wrapper.vm.$ as unknown as { setupState: { form: { items: SaveInboundMeasurementItemRequest[] } } }).setupState.form
}
enableAutoUnmount(afterEach)
afterAll(() => vi.unstubAllGlobals())
@@ -317,6 +321,50 @@ describe('InboundMeasurementPanel', () => {
expect(wrapper.find('.measurement-declaration').element.parentElement).toBe(wrapper.find('.measurement-summary-section').element.parentElement)
})
it('shows raw measured volume rather than customer weighted billing volume in the rows and comparison', async () => {
const value = measurement({ originalVolumeCbm: 3.3, originalPackageCount: 22, actualPackageCount: 22, actualVolumeCbm: 7.205, volumeWeightKg: 1203.235 })
Object.assign(value.items[0]!, {
lengthCm: 62, widthCm: 52, heightCm: 52, packageCount: 22,
coefficientMode: 'MULTIPLY', chargeRuleId: '6001',
calculatedOuterLengthCm: 77.5, calculatedOuterWidthCm: 65, calculatedOuterHeightCm: 65,
unitVolumeCbm: 0.3275, totalVolumeCbm: 7.205,
})
vi.mocked(getInboundMeasurement).mockResolvedValueOnce(value)
const wrapper = await mountPanel()
const comparisonVolume = () => wrapper.get('.measurement-comparison tbody tr:last-child').findAll('td').map(cell => cell.text())
expect(comparisonVolume()).toEqual(['3.300', '3.688', '差异 +0.388 m³'])
expect(wrapper.get('.measurement-table').text()).toContain('3.688')
expect(wrapper.get('.measurement-table').text()).not.toContain('7.205')
expect(wrapper.get('.measurement-extra-facts').text()).toContain('计费材积重')
await wrapper.get('input[aria-label="仓库修改外箱长"]').setValue('80')
expect(comparisonVolume()[1]).toBe('3.688')
await wrapper.get('.measurement-count-editor input').setValue('11')
expect(comparisonVolume()).toEqual(['3.300', '1.844', '差异 -1.456 m³'])
expect(wrapper.get('.measurement-table').text()).toContain('1.844')
expect(value.actualVolumeCbm).toBe(7.205)
expect(saveInboundMeasurementDraft).not.toHaveBeenCalled()
expect(updateConfirmedPackageCount).not.toHaveBeenCalled()
})
it('sums measured volumes across rows and does not substitute billing totals for incomplete dimensions', async () => {
const value = measurement({ actualVolumeCbm: 9.999 })
value.items.push({ ...value.items[0]!, id: '9102', itemNo: '002', boxCode: 'RK202608040001#10-2', lengthCm: 10, widthCm: 20, heightCm: 30, packageCount: 2, totalVolumeCbm: 8.888 })
vi.mocked(getInboundMeasurement).mockResolvedValueOnce(value)
const wrapper = await mountPanel()
expect(wrapper.get('.measurement-comparison tbody tr:last-child').findAll('td')[1]!.text()).toBe('0.612')
value.items[1]!.lengthCm = 0
vi.mocked(getInboundMeasurement).mockResolvedValueOnce(value)
await wrapper.setProps({ inboundId: '7002' })
await flushPromises()
const cells = wrapper.get('.measurement-comparison tbody tr:last-child').findAll('td')
expect(cells[1]!.text()).toBe('未测量')
expect(cells[2]!.text()).toBe('待测量')
expect(wrapper.get('.measurement-table').text()).not.toContain('8.888')
})
it('shows pending comparison rather than a negative count when no boxes have been measured', async () => {
vi.mocked(getInboundMeasurement).mockResolvedValueOnce(measurement({
items: [], actualPackageCount: null, actualGrossWeightKg: null, actualVolumeCbm: null,
@@ -424,7 +472,7 @@ describe('InboundMeasurementPanel', () => {
it('retains server-calculated facts in the draft without the removed expandable row', async () => {
const wrapper = await mountPanel()
expect(wrapper.find('.el-table__expand-icon').exists()).toBe(false)
expect(wrapper.vm.form.items[0]).toMatchObject({
expect(measurementDraft(wrapper).items[0]).toMatchObject({
boxSpecification: '50×40×30cm', coefficientMode: 'ACTUAL',
calculatedOuterLengthCm: 50, calculatedOuterWidthCm: 40, calculatedOuterHeightCm: 30,
calculationRuleVersion: 'DIMENSION_ACTUAL_V1',
@@ -621,7 +669,7 @@ describe('InboundMeasurementPanel', () => {
it('retains the formal deviceMeasuredAt response field in the measurement draft', async () => {
const wrapper = await mountPanel()
expect(wrapper.vm.form.items[0].measuredAt).toBe('2026-08-04T10:30:00+08:00')
expect(measurementDraft(wrapper).items[0]!.measuredAt).toBe('2026-08-04T10:30:00+08:00')
const source = await import('./InboundMeasurementPanel.vue?raw').then((module) => module.default)
expect(source).toContain('measuredAt: item.deviceMeasuredAt')
expect(source).not.toContain('measuredAt: item.measuredAt')
@@ -120,11 +120,28 @@ const mayMaintain = computed(() => canEdit.value && countEditableProcess.value &
const manualEntryEditableProcess = computed(() => ['PREPARING', 'WAIT_MEASURE', 'MEASURING', 'MEASURE_REJECTED'].includes(detail.value?.processStatus || ''))
const mayCreateManualEntry = computed(() => canManualEntry.value && manualEntryEditableProcess.value && !loading.value && !saving.value && !submitting.value && !isDirty.value)
const confirmedPackageCount = computed(() => form.items.reduce((sum, item) => sum + (Number.isFinite(item.packageCount) ? item.packageCount : 0), 0))
// No scan rows means no measured count yet, rather than a measured zero.
// 实测展示只使用原始尺寸与确认箱数,不套用计费规则或仓库外箱修正,也不回写计费快照。
function measuredVolumeCbm(item: Pick<EditableMeasurementItem, 'lengthCm' | 'widthCm' | 'heightCm' | 'packageCount'>) {
if (![item.lengthCm, item.widthCm, item.heightCm].every(value => Number.isFinite(value) && value > 0)
|| !Number.isInteger(item.packageCount) || item.packageCount < 1) return null
const volume = item.lengthCm * item.widthCm * item.heightCm * item.packageCount / 1_000_000
return Number.isFinite(volume) ? volume : null
}
const measuredTotalVolumeCbm = computed(() => {
if (!form.items.length) return null
let total = 0
for (const item of form.items) {
const volume = measuredVolumeCbm(item)
if (volume == null) return null
total += volume
}
return total
})
// 尚无扫描行时保持未测量,不把缺失的实测事实显示为零。
const comparisonMetrics = computed(() => [
{ label: '箱数', declared: detail.value?.originalPackageCount, actual: form.items.length ? confirmedPackageCount.value : null, digits: 0, unit: '' },
{ label: '毛重(kg)', declared: detail.value?.originalGrossWeightKg, actual: detail.value?.actualGrossWeightKg, digits: 3, unit: ' kg' },
{ label: '总体积(m³)', declared: detail.value?.originalVolumeCbm, actual: detail.value?.actualVolumeCbm, digits: 3, unit: ' m³' },
{ label: '总体积(m³)', declared: detail.value?.originalVolumeCbm, actual: measuredTotalVolumeCbm.value, digits: 3, unit: ' m³' },
])
const expectedPackageCount = computed(() => detail.value?.originalPackageCount ?? null)
@@ -734,8 +751,8 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
</table></div>
<div class="measurement-extra-facts">
<span>货物净重 <strong>{{ formatNumber(detail.actualNetWeightKg) }}</strong> kg</span>
<span>材积重 <strong>{{ formatNumber(detail.volumeWeightKg) }}</strong> kg</span>
<el-tooltip content="净重、材积重为平台数据,不与客户申报毛重比较"><span tabindex="0">平台补充数据</span></el-tooltip>
<span>计费材积重 <strong>{{ formatNumber(detail.volumeWeightKg) }}</strong> kg</span>
<el-tooltip content="实测体积按原始尺寸和确认箱数展示;计费材积重沿用服务端按客户计费规则计算的结果,不与客户申报毛重比较"><span tabindex="0">平台补充数据</span></el-tooltip>
</div>
</section>
@@ -827,7 +844,7 @@ defineExpose({ saveDraft, submitForReview, updateWeightEvidence, submissionBlock
</template>
</el-table-column>
<el-table-column label="总体积(m³)" width="108" align="right">
<template #default="{ row }">{{ formatNumber(row.serverValues.totalVolumeCbm, 3, 3) }}</template>
<template #default="{ row }">{{ formatNumber(measuredVolumeCbm(row as EditableMeasurementItem), 3, 3) }}</template>
</el-table-column>
<el-table-column label="图片" width="82" align="center">
<template #default="{ row }">