更新计费预览与测量精度并精简作业界面
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
import { flushPromises, shallowMount } from '@vue/test-utils'
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { ApiResponseError } from '@/shared/api/http'
|
||||
import { getLoadingAssociationSummary, getOverseasInboundCodeRelationship } from '@/shared/api/inboundCodeRelations'
|
||||
import { downloadPlatformProductImage, getPlatformUnifiedOrder, listPlatformUnifiedOrders } from '@/modules/unified-orders/api/unifiedOrders'
|
||||
import { calculateInboundFee, confirmInboundFee, downloadPlatformInboundHoldEvidence, getLatestInboundFee, getPlatformInboundHold, holdPlatformInbound, releasePlatformInbound, sendInboundFee, uploadPlatformInboundHoldEvidence } from '@/modules/inbounds'
|
||||
import { adjustPublishedInboundFee, calculateInboundFee, confirmInboundFee, downloadPlatformInboundHoldEvidence, getInboundMeasurement, getLatestInboundFee, getPlatformInboundHold, holdPlatformInbound, previewInboundFee, releasePlatformInbound, sendInboundFee, updateMeasurementOuterDimensions, uploadPlatformInboundHoldEvidence, type InboundFeePreview } from '@/modules/inbounds'
|
||||
import UnifiedOrdersView from './UnifiedOrdersView.vue'
|
||||
|
||||
const { routeQuery, routerPush } = vi.hoisted(() => ({ routeQuery: { tenantId: '6001', from: '' }, routerPush: vi.fn() }))
|
||||
@@ -18,7 +18,9 @@ vi.mock('@/modules/iam', async importOriginal => ({
|
||||
vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn().mockResolvedValue([]) }))
|
||||
vi.mock('@/modules/inbounds', () => ({
|
||||
downloadWeightImage: vi.fn(), getInboundMeasurement: vi.fn(),
|
||||
calculateInboundFee: vi.fn(), confirmInboundFee: vi.fn(), sendInboundFee: vi.fn(), getLatestInboundFee: vi.fn(),
|
||||
updateMeasurementOuterDimensions: vi.fn(),
|
||||
adjustPublishedInboundFee: vi.fn(),
|
||||
calculateInboundFee: vi.fn(), confirmInboundFee: vi.fn(), sendInboundFee: vi.fn(), getLatestInboundFee: vi.fn(), previewInboundFee: vi.fn(),
|
||||
createFeeActionIdempotencyKey: vi.fn((action: string) => `fee-${action}`),
|
||||
createFeeAdjustmentIdempotencyKey: vi.fn(() => 'fee-adjust'),
|
||||
downloadPlatformInboundHoldEvidence: vi.fn(), getPlatformInboundHold: vi.fn(), holdPlatformInbound: vi.fn(), releasePlatformInbound: vi.fn(), uploadPlatformInboundHoldEvidence: vi.fn(),
|
||||
@@ -61,6 +63,7 @@ function mountDetail() {
|
||||
directives: { loading: () => undefined },
|
||||
stubs: {
|
||||
AdminPage: { template: '<main><slot /></main>' },
|
||||
AppDialog: { props: ['modelValue'], template: '<section v-if="modelValue"><slot /></section>' },
|
||||
DetailPageLayout: { template: '<section><slot /></section>' },
|
||||
OrderFulfillmentTreePanel: { template: '<div data-testid="fulfillment-tree" />' },
|
||||
LoadErrorState: { props: ['title', 'message'], template: '<div>{{ title }} {{ message }}<slot name="details" /></div>' },
|
||||
@@ -77,9 +80,44 @@ function mountDetail() {
|
||||
})
|
||||
}
|
||||
|
||||
function feePreview(overrides: Partial<InboundFeePreview> = {}): InboundFeePreview {
|
||||
return { actualWeightKg: 570.5, actualVolumeCbm: 3.894, chargeableQuantity: 650.298, chargeUnit: 'KG',
|
||||
conversionFactor: 167, roundingScale: 6, roundingMode: 'HALF_UP', unitPrice: 13.6,
|
||||
additionalAmount: 0, totalAmount: 8844.05, currency: 'CNY', priceMatched: true, ...overrides }
|
||||
}
|
||||
|
||||
function savedOuterMeasurement() {
|
||||
return { actualPackageCount: 22, actualGrossWeightKg: 570.5, actualVolumeCbm: 3.6894,
|
||||
items: [{ id: '9001', itemNo: 'B1', packageCount: 22, lengthCm: 62, widthCm: 52, heightCm: 52,
|
||||
outerLengthCm: 63, outerWidthCm: 53, outerHeightCm: 53,
|
||||
calculatedOuterLengthCm: 63, calculatedOuterWidthCm: 53, calculatedOuterHeightCm: 53,
|
||||
unitVolumeCbm: 0.177, totalVolumeCbm: 3.894, volumeWeightKg: 650.298,
|
||||
coefficientMode: 'ACTUAL', calculationRuleVersion: 'WAREHOUSE_OUTER_OVERRIDE_V1' }] }
|
||||
}
|
||||
|
||||
type FeeConfirmVm = {
|
||||
measurementDetail: Record<string, unknown>
|
||||
measurementDimensionConfig: Record<string, unknown>
|
||||
measurementSectionModes: { outer: string; actual: string }
|
||||
feeConfirmVolume: number
|
||||
feeConfirmWeight: number
|
||||
feeConfirmUnitPrice: number | null
|
||||
feeConfirmPriceReason: string
|
||||
feeConfirmPriceChanged: boolean
|
||||
feeConfirmReference: InboundFeePreview | null
|
||||
feeConfirmReferenceLoading: boolean
|
||||
feeConfirmEstimatedTotal: number | null
|
||||
feeConfirmRows: { source: string; volume: number }[]
|
||||
openFeeConfirmDialog(): Promise<void>
|
||||
refreshFeeConfirmPreview(initializePrice?: boolean): Promise<void>
|
||||
executeConfirmAndSendFee(): Promise<void>
|
||||
}
|
||||
|
||||
describe('UnifiedOrdersView downstream fulfillment detail', () => {
|
||||
afterEach(() => vi.useRealTimers())
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
vi.mocked(previewInboundFee).mockReset().mockResolvedValue(feePreview())
|
||||
routeQuery.from = ''
|
||||
canPermission.mockImplementation(() => true)
|
||||
vi.mocked(getPlatformUnifiedOrder).mockResolvedValue(detail as never)
|
||||
@@ -122,7 +160,7 @@ describe('UnifiedOrdersView downstream fulfillment detail', () => {
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('keeps unchanged actual and outer volumes on the same server snapshot with three decimal places', async () => {
|
||||
it('shows actual and outer unit volumes and totals consistently with four decimal places', async () => {
|
||||
const wrapper = mountDetail()
|
||||
await flushPromises()
|
||||
const vm = wrapper.vm as unknown as {
|
||||
@@ -138,8 +176,9 @@ describe('UnifiedOrdersView downstream fulfillment detail', () => {
|
||||
{ lengthCm: 60, widthCm: 50, heightCm: 50, packageCount: 10, unitVolumeCbm: 0.15, totalVolumeCbm: 1.5 },
|
||||
{ lengthCm: 61, widthCm: 51, heightCm: 51, packageCount: 9, unitVolumeCbm: 0.159, totalVolumeCbm: 1.431 },
|
||||
{ lengthCm: 50, widthCm: 40, heightCm: 40, packageCount: 1, unitVolumeCbm: 0.08, totalVolumeCbm: 0.08 },
|
||||
{ lengthCm: 62, widthCm: 52, heightCm: 52, packageCount: 22, unitVolumeCbm: 0.1677, totalVolumeCbm: 3.6894 },
|
||||
].map((row, index) => ({
|
||||
...row, itemNo: index + 1, coefficientMode: 'ACTUAL',
|
||||
...row, itemNo: index + 1, coefficientMode: 'ACTUAL', calculationRuleVersion: 'ACTUAL_DIMENSION_V1',
|
||||
calculatedOuterLengthCm: row.lengthCm, calculatedOuterWidthCm: row.widthCm, calculatedOuterHeightCm: row.heightCm,
|
||||
}))
|
||||
vm.measurementDetail = { items }
|
||||
@@ -148,13 +187,13 @@ describe('UnifiedOrdersView downstream fulfillment detail', () => {
|
||||
await flushPromises()
|
||||
|
||||
for (const rows of [vm.outerMeasurementRows, vm.actualMeasurementRows]) {
|
||||
expect(rows.map(row => vm.measurementValue(row, 'totalVolumeCbm'))).toEqual(['1.500', '1.431', '0.080'])
|
||||
expect(rows.map(row => vm.measurementValue(row, 'unitVolumeCbm'))).toEqual(['0.150', '0.159', '0.080'])
|
||||
expect(vm.formatMeasurementVolume(vm.measurementTotals(rows).volume)).toBe('3.011')
|
||||
expect(vm.formatMeasurementVolume(vm.measurementTotals(rows).weightVolume)).toBe('3.011')
|
||||
expect(rows.map(row => vm.measurementValue(row, 'totalVolumeCbm'))).toEqual(['1.5000', '1.4310', '0.0800', '3.6894'])
|
||||
expect(rows.map(row => vm.measurementValue(row, 'unitVolumeCbm'))).toEqual(['0.1500', '0.1590', '0.0800', '0.1677'])
|
||||
expect(vm.formatMeasurementVolume(vm.measurementTotals(rows).volume)).toBe('6.7004')
|
||||
expect(vm.formatMeasurementVolume(vm.measurementTotals(rows).weightVolume)).toBe('6.7004')
|
||||
}
|
||||
expect(vm.formatMeasurementVolume(3)).toBe('3.000')
|
||||
expect(vm.formatMeasurementVolume(0)).toBe('0.000')
|
||||
expect(vm.formatMeasurementVolume(3)).toBe('3.0000')
|
||||
expect(vm.formatMeasurementVolume(0)).toBe('0.0000')
|
||||
expect(vm.formatMeasurementVolume(null)).toBe('—')
|
||||
|
||||
// 仓库修正的外箱仍与实测尺寸分开,不能把外箱计费快照当成原始实测体积。
|
||||
@@ -162,46 +201,26 @@ describe('UnifiedOrdersView downstream fulfillment detail', () => {
|
||||
calculatedOuterLengthCm: 62, unitVolumeCbm: 0.1613, totalVolumeCbm: 1.4517,
|
||||
calculationRuleVersion: 'WAREHOUSE_OUTER_OVERRIDE_V1' }] }
|
||||
await flushPromises()
|
||||
expect(vm.measurementValue(vm.outerMeasurementRows[0]!, 'totalVolumeCbm')).toBe('1.452')
|
||||
expect(vm.measurementValue(vm.actualMeasurementRows[0]!, 'totalVolumeCbm')).toBe('1.428')
|
||||
expect(vm.measurementValue(vm.outerMeasurementRows[0]!, 'totalVolumeCbm')).toBe('1.4517')
|
||||
expect(vm.measurementValue(vm.actualMeasurementRows[0]!, 'totalVolumeCbm')).toBe('1.4283')
|
||||
|
||||
vm.measurementDetail = { items: [{ ...items[1], coefficientMode: 'ADD', calculatedOuterLengthCm: 62,
|
||||
calculatedOuterWidthCm: 52, calculatedOuterHeightCm: 52, unitVolumeCbm: 0.168, totalVolumeCbm: 1.512 }] }
|
||||
await flushPromises()
|
||||
expect(vm.measurementValue(vm.actualMeasurementRows[0]!, 'totalVolumeCbm')).toBe('1.428')
|
||||
expect(vm.measurementValue(vm.actualMeasurementRows[0]!, 'totalVolumeCbm')).toBe('1.4283')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it.each([
|
||||
{ confirmedVolume: 3.894, hasReference: true },
|
||||
{ confirmedVolume: 4.25, hasReference: true },
|
||||
{ confirmedVolume: 4.25, hasReference: false },
|
||||
])('defaults billing to the saved outer volume and sends $confirmedVolume (reference: $hasReference)', async ({ confirmedVolume, hasReference }) => {
|
||||
it.each([3.894, 4.25])('defaults billing to the saved outer volume and sends the confirmed value %s', async (confirmedVolume) => {
|
||||
const fee = { unitPrice: 13.6, chargeableQuantity: 650.298, chargeUnit: 'KG', roundingScale: 4, rateCode: 'RATE', feeStatus: 'CALCULATED' }
|
||||
vi.mocked(getLatestInboundFee).mockResolvedValue(fee as never)
|
||||
if (!hasReference) vi.mocked(getLatestInboundFee).mockRejectedValueOnce(new ApiResponseError('暂无费用快照', 'A0404', 200))
|
||||
vi.mocked(calculateInboundFee).mockResolvedValue(fee as never)
|
||||
vi.mocked(confirmInboundFee).mockResolvedValue({ ...fee, feeStatus: 'CONFIRMED' } as never)
|
||||
vi.mocked(sendInboundFee).mockResolvedValue({ ...fee, feeStatus: 'SENT' } as never)
|
||||
const wrapper = mountDetail()
|
||||
await flushPromises()
|
||||
const vm = wrapper.vm as unknown as {
|
||||
measurementDetail: Record<string, unknown>
|
||||
measurementSectionModes: { outer: string; actual: string }
|
||||
feeConfirmVolume: number
|
||||
feeConfirmWeight: number
|
||||
feeConfirmRows: { source: string; volume: number }[]
|
||||
openFeeConfirmDialog(): Promise<void>
|
||||
executeConfirmAndSendFee(): Promise<void>
|
||||
}
|
||||
vm.measurementDetail = {
|
||||
actualPackageCount: 22, actualGrossWeightKg: 570.5, actualVolumeCbm: 3.6894,
|
||||
items: [{ id: '9001', itemNo: 'B1', packageCount: 22, lengthCm: 62, widthCm: 52, heightCm: 52,
|
||||
outerLengthCm: 63, outerWidthCm: 53, outerHeightCm: 53,
|
||||
calculatedOuterLengthCm: 63, calculatedOuterWidthCm: 53, calculatedOuterHeightCm: 53,
|
||||
unitVolumeCbm: 0.177, totalVolumeCbm: 3.894, volumeWeightKg: 650.298,
|
||||
coefficientMode: 'ACTUAL', calculationRuleVersion: 'WAREHOUSE_OUTER_OVERRIDE_V1' }],
|
||||
}
|
||||
const vm = wrapper.vm as unknown as FeeConfirmVm
|
||||
vm.measurementDetail = savedOuterMeasurement()
|
||||
vm.measurementSectionModes.outer = 'ACTUAL'
|
||||
vm.measurementSectionModes.actual = 'ACTUAL'
|
||||
await vm.openFeeConfirmDialog()
|
||||
@@ -210,10 +229,13 @@ describe('UnifiedOrdersView downstream fulfillment detail', () => {
|
||||
expect(vm.feeConfirmRows.find(row => row.source === '外箱规数据')?.volume).toBeCloseTo(3.894)
|
||||
expect(vm.feeConfirmVolume).toBeCloseTo(3.894)
|
||||
expect(vm.feeConfirmWeight).toBeCloseTo(570.5)
|
||||
if (!hasReference) expect(calculateInboundFee).toHaveBeenCalledWith('6001', '7001', {
|
||||
selectedWeightKg: 570.5, selectedVolumeCbm: 3.894, weightSource: 'ACTUAL', volumeSource: 'OUTER',
|
||||
}, 'fee-calculate')
|
||||
expect(previewInboundFee).toHaveBeenCalledWith('6001', '7001', {
|
||||
selectedWeightKg: 570.5, selectedVolumeCbm: 3.894, selectedUnitPrice: null, weightSource: 'ACTUAL', volumeSource: 'OUTER',
|
||||
})
|
||||
expect(calculateInboundFee).not.toHaveBeenCalled()
|
||||
expect(getLatestInboundFee).not.toHaveBeenCalled()
|
||||
vm.feeConfirmVolume = confirmedVolume
|
||||
await vm.refreshFeeConfirmPreview()
|
||||
await vm.executeConfirmAndSendFee()
|
||||
|
||||
expect(calculateInboundFee).toHaveBeenCalledWith('6001', '7001', {
|
||||
@@ -224,6 +246,245 @@ describe('UnifiedOrdersView downstream fulfillment detail', () => {
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('refreshes the amount from current outer values instead of an older fee snapshot', async () => {
|
||||
vi.mocked(getLatestInboundFee).mockResolvedValue({ unitPrice: 13.6, chargeableQuantity: 616.1298, totalAmount: 8379.37 } as never)
|
||||
const wrapper = mountDetail()
|
||||
await flushPromises()
|
||||
const vm = wrapper.vm as unknown as FeeConfirmVm
|
||||
vm.measurementDetail = savedOuterMeasurement()
|
||||
await vm.openFeeConfirmDialog()
|
||||
|
||||
expect(vm.feeConfirmVolume).toBe(3.894)
|
||||
expect(vm.feeConfirmReference?.chargeableQuantity).toBe(650.298)
|
||||
expect(vm.feeConfirmEstimatedTotal).toBe(8844.05)
|
||||
expect(getLatestInboundFee).not.toHaveBeenCalled()
|
||||
expect(calculateInboundFee).not.toHaveBeenCalled()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('keeps saved outer totals when customer rules change without resaving the measurement', async () => {
|
||||
const wrapper = mountDetail()
|
||||
await flushPromises()
|
||||
const vm = wrapper.vm as unknown as FeeConfirmVm
|
||||
vm.measurementDetail = {
|
||||
actualPackageCount: 10, actualGrossWeightKg: 100, actualVolumeCbm: 0.81,
|
||||
items: [{ id: '9001', itemNo: 'B1', packageCount: 10, lengthCm: 50, widthCm: 40, heightCm: 30,
|
||||
outerLengthCm: null, outerWidthCm: null, outerHeightCm: null,
|
||||
calculatedOuterLengthCm: 54, calculatedOuterWidthCm: 44, calculatedOuterHeightCm: 34,
|
||||
unitVolumeCbm: 0.081, totalVolumeCbm: 0.81, volumeWeightKg: 135.27,
|
||||
coefficientMode: 'ADD', calculationRuleVersion: 'CUSTOMER_DIMENSION_LARGE_V1' }],
|
||||
}
|
||||
vm.measurementDimensionConfig = { defaultCalculationMode: 'ADD', rules: [
|
||||
[30, 20, 20], [40, 30, 30], [50, 40, 40], [60, 50, 50],
|
||||
].map(([innerLengthCm, innerWidthCm, innerHeightCm]) => ({ innerLengthCm, innerWidthCm, innerHeightCm, edgeAdditionCm: 3 })) }
|
||||
vm.measurementSectionModes.outer = 'ADD'
|
||||
await vm.openFeeConfirmDialog()
|
||||
|
||||
expect(vm.feeConfirmVolume).toBeCloseTo(0.81)
|
||||
expect(previewInboundFee).toHaveBeenCalledWith('6001', '7001', {
|
||||
selectedWeightKg: 100, selectedVolumeCbm: 0.81, selectedUnitPrice: null, weightSource: 'ACTUAL', volumeSource: 'OUTER',
|
||||
})
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('keeps the price-table reference visible when manual pricing refreshes the amount and requires a reason before sending', async () => {
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
|
||||
const wrapper = mountDetail()
|
||||
await flushPromises()
|
||||
const vm = wrapper.vm as unknown as FeeConfirmVm
|
||||
vm.measurementDetail = savedOuterMeasurement()
|
||||
await vm.openFeeConfirmDialog()
|
||||
await flushPromises()
|
||||
const referencePriceText = () => wrapper.findAll('tr')
|
||||
.find(row => row.find('th').text() === '价格表参考单价')!.get('td').text()
|
||||
expect(referencePriceText()).toBe('13.6 CNY / KG')
|
||||
|
||||
vi.mocked(previewInboundFee).mockResolvedValueOnce(feePreview({ totalAmount: 9104.17 }))
|
||||
vm.feeConfirmUnitPrice = 14
|
||||
await flushPromises()
|
||||
expect(referencePriceText()).toBe('13.6 CNY / KG')
|
||||
expect(vm.feeConfirmEstimatedTotal).toBeNull()
|
||||
expect(vm.feeConfirmPriceChanged).toBe(true)
|
||||
await vi.advanceTimersByTimeAsync(250)
|
||||
expect(referencePriceText()).toBe('13.6 CNY / KG')
|
||||
expect(vm.feeConfirmUnitPrice).toBe(14)
|
||||
expect(vm.feeConfirmEstimatedTotal).toBe(9104.17)
|
||||
expect(previewInboundFee).toHaveBeenLastCalledWith('6001', '7001', {
|
||||
selectedWeightKg: 570.5, selectedVolumeCbm: 3.894, selectedUnitPrice: 14, weightSource: 'ACTUAL', volumeSource: 'OUTER',
|
||||
})
|
||||
|
||||
await vm.executeConfirmAndSendFee()
|
||||
expect(calculateInboundFee).not.toHaveBeenCalled()
|
||||
const fee = { unitPrice: 13.6, chargeableQuantity: 650.298, additionalAmount: 0,
|
||||
rateCode: 'RATE', feeVersion: 2, feeStatus: 'CALCULATED' }
|
||||
vi.mocked(calculateInboundFee).mockResolvedValue(fee as never)
|
||||
vi.mocked(adjustPublishedInboundFee).mockResolvedValue({ ...fee, unitPrice: 14, feeVersion: 3 } as never)
|
||||
vi.mocked(confirmInboundFee).mockResolvedValue({ ...fee, unitPrice: 14, feeStatus: 'CONFIRMED' } as never)
|
||||
vi.mocked(sendInboundFee).mockResolvedValue({ ...fee, unitPrice: 14, feeStatus: 'SENT' } as never)
|
||||
vm.feeConfirmPriceReason = '客户协商调价'
|
||||
await vm.executeConfirmAndSendFee()
|
||||
expect(adjustPublishedInboundFee).toHaveBeenCalledWith('6001', '7001', {
|
||||
expectedFeeVersion: 2, chargeableQuantity: 650.298, unitPrice: 14, additionalAmount: 0,
|
||||
reason: '客户协商调价', toleranceConfirmed: true,
|
||||
}, 'fee-adjust')
|
||||
expect(sendInboundFee).toHaveBeenCalledWith('6001', '7001', 'fee-send')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('stops requiring an adjustment reason immediately when the manual price is restored to the price-table reference', async () => {
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
|
||||
const wrapper = mountDetail()
|
||||
await flushPromises()
|
||||
const vm = wrapper.vm as unknown as FeeConfirmVm
|
||||
vm.measurementDetail = savedOuterMeasurement()
|
||||
await vm.openFeeConfirmDialog()
|
||||
vm.feeConfirmUnitPrice = 14
|
||||
await vi.advanceTimersByTimeAsync(250)
|
||||
expect(vm.feeConfirmPriceChanged).toBe(true)
|
||||
vm.feeConfirmUnitPrice = 13.6
|
||||
expect(vm.feeConfirmPriceChanged).toBe(false)
|
||||
await vi.advanceTimersByTimeAsync(250)
|
||||
expect(vm.feeConfirmPriceChanged).toBe(false)
|
||||
expect(vm.feeConfirmEstimatedTotal).toBe(8844.05)
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('ignores stale preview responses and retains the manually entered unit price', async () => {
|
||||
vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] })
|
||||
const wrapper = mountDetail()
|
||||
await flushPromises()
|
||||
const vm = wrapper.vm as unknown as FeeConfirmVm
|
||||
vm.measurementDetail = savedOuterMeasurement()
|
||||
await vm.openFeeConfirmDialog()
|
||||
vm.feeConfirmUnitPrice = 16
|
||||
let resolveOld!: (value: InboundFeePreview) => void
|
||||
vi.mocked(previewInboundFee).mockImplementationOnce(() => new Promise(resolve => { resolveOld = resolve }))
|
||||
vm.feeConfirmVolume = 4
|
||||
expect(vm.feeConfirmEstimatedTotal).toBeNull()
|
||||
await vi.advanceTimersByTimeAsync(250)
|
||||
vi.mocked(previewInboundFee).mockResolvedValueOnce(feePreview({ actualVolumeCbm: 4.25, chargeableQuantity: 709.75, totalAmount: 11356 }))
|
||||
vm.feeConfirmVolume = 4.25
|
||||
await vi.advanceTimersByTimeAsync(250)
|
||||
expect(vm.feeConfirmReference?.actualVolumeCbm).toBe(4.25)
|
||||
expect(vm.feeConfirmUnitPrice).toBe(16)
|
||||
expect(vm.feeConfirmEstimatedTotal).toBe(11356)
|
||||
expect(previewInboundFee).toHaveBeenLastCalledWith('6001', '7001', {
|
||||
selectedWeightKg: 570.5, selectedVolumeCbm: 4.25, selectedUnitPrice: 16, weightSource: 'ACTUAL', volumeSource: 'OUTER',
|
||||
})
|
||||
resolveOld(feePreview({ actualVolumeCbm: 4, chargeableQuantity: 668, totalAmount: 9084.8 }))
|
||||
await flushPromises()
|
||||
expect(vm.feeConfirmReference?.actualVolumeCbm).toBe(4.25)
|
||||
expect(vm.feeConfirmEstimatedTotal).toBe(11356)
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('closes billing preview and ignores its result after switching orders', async () => {
|
||||
const wrapper = mountDetail()
|
||||
await flushPromises()
|
||||
const vm = wrapper.vm as unknown as FeeConfirmVm & { detail: Record<string, unknown>; feeConfirmOpen: boolean }
|
||||
vm.measurementDetail = savedOuterMeasurement()
|
||||
let resolvePreview!: (value: InboundFeePreview) => void
|
||||
vi.mocked(previewInboundFee).mockImplementationOnce(() => new Promise(resolve => { resolvePreview = resolve }))
|
||||
const pending = vm.openFeeConfirmDialog()
|
||||
vm.detail = { ...detail, summary: { ...detail.summary, inboundId: '7002' } }
|
||||
expect(vm.feeConfirmOpen).toBe(false)
|
||||
resolvePreview(feePreview())
|
||||
await pending
|
||||
expect(vm.feeConfirmReference).toBeNull()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('does not send when current fee preview fails', async () => {
|
||||
vi.mocked(previewInboundFee).mockRejectedValueOnce(new ApiResponseError('预览服务暂不可用', 'A0500', 200))
|
||||
const wrapper = mountDetail()
|
||||
await flushPromises()
|
||||
const vm = wrapper.vm as unknown as FeeConfirmVm
|
||||
vm.measurementDetail = savedOuterMeasurement()
|
||||
await vm.openFeeConfirmDialog()
|
||||
vm.feeConfirmUnitPrice = 13.6
|
||||
await vm.executeConfirmAndSendFee()
|
||||
|
||||
expect(vm.feeConfirmReference).toBeNull()
|
||||
expect(vm.feeConfirmEstimatedTotal).toBeNull()
|
||||
expect(calculateInboundFee).not.toHaveBeenCalled()
|
||||
expect(sendInboundFee).not.toHaveBeenCalled()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('previews manual pricing with server quantity when no price is matched', async () => {
|
||||
vi.mocked(previewInboundFee).mockResolvedValueOnce(feePreview({ priceMatched: false, unitPrice: null, totalAmount: null }))
|
||||
const wrapper = mountDetail()
|
||||
await flushPromises()
|
||||
const vm = wrapper.vm as unknown as FeeConfirmVm
|
||||
vm.measurementDetail = savedOuterMeasurement()
|
||||
await vm.openFeeConfirmDialog()
|
||||
expect(vm.feeConfirmEstimatedTotal).toBeNull()
|
||||
vi.mocked(previewInboundFee).mockResolvedValueOnce(feePreview({ priceMatched: false, unitPrice: null, totalAmount: 6502.98 }))
|
||||
vm.feeConfirmUnitPrice = 10
|
||||
await vm.refreshFeeConfirmPreview()
|
||||
expect(vm.feeConfirmEstimatedTotal).toBe(6502.98)
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('uses server money rounding after a manual unit price change', async () => {
|
||||
const wrapper = mountDetail()
|
||||
await flushPromises()
|
||||
const vm = wrapper.vm as unknown as FeeConfirmVm
|
||||
vm.measurementDetail = savedOuterMeasurement()
|
||||
await vm.openFeeConfirmDialog()
|
||||
vi.mocked(previewInboundFee).mockResolvedValueOnce(feePreview({ chargeableQuantity: 1, unitPrice: 2, totalAmount: 1.01 }))
|
||||
vm.feeConfirmUnitPrice = 1.005
|
||||
expect(vm.feeConfirmEstimatedTotal).toBeNull()
|
||||
await vm.refreshFeeConfirmPreview()
|
||||
expect(vm.feeConfirmUnitPrice).toBe(1.005)
|
||||
expect(vm.feeConfirmEstimatedTotal).toBe(1.01)
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('rejects a partially missing outer snapshot instead of billing the remaining rows', async () => {
|
||||
const wrapper = mountDetail()
|
||||
await flushPromises()
|
||||
const vm = wrapper.vm as unknown as FeeConfirmVm
|
||||
const saved = savedOuterMeasurement()
|
||||
vm.measurementDetail = { ...saved, items: [saved.items[0], { ...saved.items[0], id: '9002', calculatedOuterLengthCm: null }] }
|
||||
await vm.openFeeConfirmDialog()
|
||||
expect(previewInboundFee).not.toHaveBeenCalled()
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('saves cleared warehouse dimensions as null and keeps them blank after server readback', async () => {
|
||||
const wrapper = mountDetail()
|
||||
await flushPromises()
|
||||
const vm = wrapper.vm as unknown as FeeConfirmVm & {
|
||||
actualMeasurementRows: Record<string, unknown>[]
|
||||
outerMeasurementRows: Record<string, unknown>[]
|
||||
saveWarehouseOuterDimensions(row: Record<string, unknown>): Promise<void>
|
||||
}
|
||||
const previous = { ...savedOuterMeasurement(), measurementId: '8001', version: 3, customerFeeConfirmed: false }
|
||||
vm.measurementDetail = previous
|
||||
const cleared = { ...previous, version: 4, items: previous.items.map(row => ({ ...row,
|
||||
outerLengthCm: null, outerWidthCm: null, outerHeightCm: null,
|
||||
calculatedOuterLengthCm: 62, calculatedOuterWidthCm: 52, calculatedOuterHeightCm: 52,
|
||||
unitVolumeCbm: 0.1677, totalVolumeCbm: 3.6894, volumeWeightKg: 616.13,
|
||||
calculationRuleVersion: 'ACTUAL_DIMENSION_V1',
|
||||
})) }
|
||||
vi.mocked(updateMeasurementOuterDimensions).mockResolvedValue({ measurementId: '8001', version: 4, status: 'SUBMITTED', itemId: '9001' })
|
||||
vi.mocked(getInboundMeasurement).mockResolvedValue(cleared as never)
|
||||
const row = vm.actualMeasurementRows[0]!
|
||||
row.outerLengthCm = null
|
||||
row.outerWidthCm = undefined
|
||||
row.outerHeightCm = ''
|
||||
|
||||
await vm.saveWarehouseOuterDimensions(row)
|
||||
|
||||
expect(updateMeasurementOuterDimensions).toHaveBeenCalledWith('6001', '7001', '9001', {
|
||||
measurementId: '8001', version: 3, outerLengthCm: null, outerWidthCm: null, outerHeightCm: null,
|
||||
})
|
||||
expect(vm.actualMeasurementRows[0]).toMatchObject({ outerLengthCm: null, outerWidthCm: null, outerHeightCm: null })
|
||||
expect(vm.outerMeasurementRows[0]).toMatchObject({ lengthCm: 62, widthCm: 52, heightCm: 52, totalVolumeCbm: 3.6894 })
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('downloads one preview for cargo rows sharing the same product image', async () => {
|
||||
vi.mocked(getPlatformUnifiedOrder).mockResolvedValue({
|
||||
...detail,
|
||||
|
||||
Reference in New Issue
Block a user