统一后台状态列顺序、详情布局与测量计费展示
This commit is contained in:
@@ -1,8 +1,9 @@
|
||||
import { flushPromises, shallowMount } from '@vue/test-utils'
|
||||
import { 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 { downloadPlatformInboundHoldEvidence, getPlatformInboundHold, holdPlatformInbound, releasePlatformInbound, uploadPlatformInboundHoldEvidence } from '@/modules/inbounds'
|
||||
import { calculateInboundFee, confirmInboundFee, downloadPlatformInboundHoldEvidence, getLatestInboundFee, getPlatformInboundHold, holdPlatformInbound, releasePlatformInbound, sendInboundFee, uploadPlatformInboundHoldEvidence } from '@/modules/inbounds'
|
||||
import UnifiedOrdersView from './UnifiedOrdersView.vue'
|
||||
|
||||
const { routeQuery, routerPush } = vi.hoisted(() => ({ routeQuery: { tenantId: '6001', from: '' }, routerPush: vi.fn() }))
|
||||
@@ -17,6 +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(),
|
||||
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(),
|
||||
InboundFeePanel: { template: '<div />' }, InboundMeasurementDialog: { template: '<div />' }, MeasurementEvidenceThumbnail: { template: '<div />' },
|
||||
}))
|
||||
@@ -168,6 +172,58 @@ describe('UnifiedOrdersView downstream fulfillment detail', () => {
|
||||
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 }) => {
|
||||
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' }],
|
||||
}
|
||||
vm.measurementSectionModes.outer = 'ACTUAL'
|
||||
vm.measurementSectionModes.actual = 'ACTUAL'
|
||||
await vm.openFeeConfirmDialog()
|
||||
|
||||
expect(vm.feeConfirmRows.find(row => row.source === '实际测量数据')?.volume).toBeCloseTo(3.6894)
|
||||
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')
|
||||
vm.feeConfirmVolume = confirmedVolume
|
||||
await vm.executeConfirmAndSendFee()
|
||||
|
||||
expect(calculateInboundFee).toHaveBeenCalledWith('6001', '7001', {
|
||||
selectedWeightKg: 570.5, selectedVolumeCbm: confirmedVolume, weightSource: 'ACTUAL', volumeSource: 'OUTER',
|
||||
}, 'fee-calculate')
|
||||
expect(confirmInboundFee).toHaveBeenCalledWith('6001', '7001', 'fee-confirm')
|
||||
expect(sendInboundFee).toHaveBeenCalledWith('6001', '7001', 'fee-send')
|
||||
wrapper.unmount()
|
||||
})
|
||||
|
||||
it('downloads one preview for cargo rows sharing the same product image', async () => {
|
||||
vi.mocked(getPlatformUnifiedOrder).mockResolvedValue({
|
||||
...detail,
|
||||
|
||||
@@ -508,7 +508,7 @@ describe('UnifiedOrdersView contract', () => {
|
||||
expect(source).toContain('const outerTotals = measurementTotals(outerMeasurementRows.value)')
|
||||
expect(source).toContain("ElMessage.warning('外箱规数据不完整,请先保存外箱规尺寸和计算规则')")
|
||||
expect(source).toContain("weightSource: 'ACTUAL'")
|
||||
expect(source).toContain("volumeSource: 'ACTUAL'")
|
||||
expect(source).toContain("volumeSource: 'OUTER'")
|
||||
expect(source).not.toContain('测量审核通过后才能确认并发送费用')
|
||||
expect(source).not.toContain("reviewStatus !== 'APPROVED'")
|
||||
expect(source).toContain('const hasActualMeasurementData = computed(() => (measurementDetail.value?.items?.length ?? 0) > 0)')
|
||||
@@ -530,7 +530,7 @@ describe('UnifiedOrdersView contract', () => {
|
||||
expect(source).toContain('const measuredNetWeight = Number(measurementDetail.value?.actualNetWeightKg)')
|
||||
expect(source).not.toContain('const actualVolume = Number(measurementDetail.value?.actualVolumeCbm)')
|
||||
expect(source).toContain('feeConfirmWeight.value = Number(actualTotals.gross) > 0')
|
||||
expect(source).toContain('feeConfirmVolume.value = Number(actualTotals.volume) > 0')
|
||||
expect(source).toContain('feeConfirmVolume.value = Number(outerTotals.volume)')
|
||||
expect(source).toContain("{ source: '实际测量数据', count: actualTotals.count, weight: actualWeight, volume: actualVolume }")
|
||||
const sendFeeSource = source.slice(source.indexOf('async function executeConfirmAndSendFee()'), source.indexOf('function handleOrderSelectionChange'))
|
||||
expect(sendFeeSource).not.toContain('saveInboundMeasurementDraft')
|
||||
|
||||
@@ -673,8 +673,10 @@ const columns = computed<DataTableColumn[]>(() => [
|
||||
selectable: () => !batchApprovalSaving.value && !coLoadSaving.value && !exportSubmitting.value,
|
||||
} satisfies DataTableColumn] : []),
|
||||
{ key: 'inboundCode', prop: 'inboundCode', label: '入仓码', minWidth: 160, slotName: 'inboundCode' },
|
||||
{ key: 'mainStatus', prop: 'mainStatus', label: '订单主状态', minWidth: 120, slotName: 'mainStatus', afterColumn: 'inboundCode' },
|
||||
{ key: 'measurementReviewStatus', prop: 'measurementReviewStatus', label: '测量数据审核状态', minWidth: 150, slotName: 'measurementReviewStatus', afterColumn: 'inboundCode' },
|
||||
{ key: 'feeReviewStatus', prop: 'feeReviewStatus', label: '费用审核状态', minWidth: 140, slotName: 'field', defaultVisible: false, afterColumn: 'inboundCode' },
|
||||
{ key: 'orderType', label: '订单类型', minWidth: 120, slotName: 'orderType' },
|
||||
{ key: 'mainStatus', prop: 'mainStatus', label: '订单主状态', minWidth: 120, slotName: 'mainStatus' },
|
||||
{ key: 'customerName', prop: 'customerName', label: '客户', minWidth: 120, slotName: 'field' },
|
||||
{ key: 'receivingWarehouseName', prop: 'receivingWarehouseName', label: '平台收货仓', minWidth: 100, slotName: 'field' },
|
||||
{ key: 'destinationWarehouseName', prop: 'destinationWarehouseName', label: '目的仓', minWidth: 100, slotName: 'field' },
|
||||
@@ -687,13 +689,11 @@ const columns = computed<DataTableColumn[]>(() => [
|
||||
{ key: 'totalPalletCount', prop: 'totalPalletCount', label: '总托数', minWidth: 90, slotName: 'field' },
|
||||
{ key: 'requestedPickupAt', prop: 'requestedPickupAt', label: '预约提货时间', minWidth: 140, slotName: 'field' },
|
||||
{ key: 'locationCodes', prop: 'locationCodes', label: '库位位置', minWidth: 160, slotName: 'field', defaultVisible: false },
|
||||
{ key: 'measurementReviewStatus', prop: 'measurementReviewStatus', label: '测量数据审核状态', minWidth: 150, slotName: 'measurementReviewStatus' },
|
||||
{ key: 'actualGrossWeightKg', prop: 'actualGrossWeightKg', label: '实测毛重(kg)', minWidth: 120, slotName: 'field' },
|
||||
{ key: 'totalNetWeightKg', prop: 'totalNetWeightKg', label: '货物净重(kg)', minWidth: 120, slotName: 'field' },
|
||||
{ key: 'chargeableWeightKg', prop: 'chargeableWeightKg', label: '计费毛重', minWidth: 120, slotName: 'field' },
|
||||
{ key: 'chargeableVolumeCbm', prop: 'chargeableVolumeCbm', label: '计费体积(m³)', minWidth: 130, slotName: 'field' },
|
||||
{ key: 'expectedShipDate', prop: 'expectedShipDate', label: '预计出运日期', minWidth: 150, slotName: 'field' },
|
||||
{ key: 'feeReviewStatus', prop: 'feeReviewStatus', label: '费用审核状态', minWidth: 140, slotName: 'field', defaultVisible: false },
|
||||
{ key: 'skuNos', prop: 'skuNos', label: 'SKU 编码', minWidth: 180, slotName: 'field', defaultVisible: false },
|
||||
{ key: 'totalQuantity', prop: 'totalQuantity', label: '货物总数量', minWidth: 120, slotName: 'field', defaultVisible: false },
|
||||
{ key: 'expressCompany', prop: 'expressCompany', label: '快递公司', minWidth: 140, slotName: 'field', defaultVisible: false },
|
||||
@@ -1752,7 +1752,7 @@ async function openFeeConfirmDialog() {
|
||||
feeConfirmWeight.value = Number(actualTotals.gross) > 0
|
||||
? Number(actualTotals.gross)
|
||||
: measuredGrossWeight > 0 ? measuredGrossWeight : measuredNetWeight > 0 ? measuredNetWeight : null
|
||||
feeConfirmVolume.value = Number(actualTotals.volume) > 0 ? Number(actualTotals.volume) : null
|
||||
feeConfirmVolume.value = Number(outerTotals.volume)
|
||||
feeConfirmReference.value = null
|
||||
feeConfirmPriceMissing.value = false
|
||||
feeConfirmUnitPrice.value = null
|
||||
@@ -1773,7 +1773,7 @@ async function openFeeConfirmDialog() {
|
||||
selectedWeightKg: Number(feeConfirmWeight.value),
|
||||
selectedVolumeCbm: Number(feeConfirmVolume.value),
|
||||
weightSource: 'ACTUAL',
|
||||
volumeSource: 'ACTUAL',
|
||||
volumeSource: 'OUTER',
|
||||
}, createFeeActionIdempotencyKey('calculate'))
|
||||
}
|
||||
feeConfirmReference.value = snapshot
|
||||
@@ -1843,7 +1843,7 @@ async function executeConfirmAndSendFee() {
|
||||
let snapshot: InboundFeeSnapshot
|
||||
try {
|
||||
snapshot = await calculateInboundFee(tenantId, inboundId, {
|
||||
selectedWeightKg, selectedVolumeCbm, weightSource: 'ACTUAL', volumeSource: 'ACTUAL',
|
||||
selectedWeightKg, selectedVolumeCbm, weightSource: 'ACTUAL', volumeSource: 'OUTER',
|
||||
}, attempt.calculateKey)
|
||||
} catch (error) {
|
||||
if (getApiErrorDetails(error, '费用试算失败').code !== 'A0404') throw error
|
||||
|
||||
Reference in New Issue
Block a user