修复采购审核竞态并整理管理页面展示

This commit is contained in:
2026-10-30 15:42:37 +08:00
parent 8cc8b4fb94
commit 4abd6ffd5d
23 changed files with 791 additions and 75 deletions
@@ -4,6 +4,8 @@ import { ApiResponseError } from '@/shared/api/http'
import { getLoadingAssociationSummary, getOverseasInboundCodeRelationship } from '@/shared/api/inboundCodeRelations'
import { downloadPlatformProductImage, getPlatformUnifiedOrder, listPlatformUnifiedOrders } from '@/modules/unified-orders/api/unifiedOrders'
import { adjustPublishedInboundFee, calculateInboundFee, confirmInboundFee, downloadPlatformInboundHoldEvidence, getInboundMeasurement, getLatestInboundFee, getPlatformInboundHold, holdPlatformInbound, previewInboundFee, releasePlatformInbound, sendInboundFee, updateMeasurementOuterDimensions, uploadPlatformInboundHoldEvidence, type InboundFeePreview } from '@/modules/inbounds'
import { listCustomerChargeRules } from '@/modules/fba-pricing'
import { getCustomerDimensionConfig } from '@/modules/dimension-config'
import UnifiedOrdersView from './UnifiedOrdersView.vue'
const { routeQuery, routerPush } = vi.hoisted(() => ({ routeQuery: { tenantId: '6001', from: '' }, routerPush: vi.fn() }))
@@ -16,6 +18,8 @@ vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({
vi.mock('@/modules/iam', async importOriginal => ({
...await importOriginal<typeof import('@/modules/iam')>(), listPlatformTenants: vi.fn().mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 0 }) }))
vi.mock('@/modules/warehouses', () => ({ listAllPlatformWarehouses: vi.fn().mockResolvedValue([]) }))
vi.mock('@/modules/fba-pricing', () => ({ listCustomerChargeRules: vi.fn() }))
vi.mock('@/modules/dimension-config', () => ({ getCustomerDimensionConfig: vi.fn() }))
vi.mock('@/modules/inbounds', () => ({
downloadWeightImage: vi.fn(), getInboundMeasurement: vi.fn(),
updateMeasurementOuterDimensions: vi.fn(),
@@ -117,6 +121,8 @@ describe('UnifiedOrdersView downstream fulfillment detail', () => {
afterEach(() => vi.useRealTimers())
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(getCustomerDimensionConfig).mockReset().mockResolvedValue({ tenantId: '6001', defaultCalculationMode: 'ACTUAL', rules: [] })
vi.mocked(listCustomerChargeRules).mockReset().mockResolvedValue([])
vi.mocked(previewInboundFee).mockReset().mockResolvedValue(feePreview())
routeQuery.from = ''
canPermission.mockImplementation(() => true)
@@ -160,6 +166,55 @@ describe('UnifiedOrdersView downstream fulfillment detail', () => {
wrapper.unmount()
})
it.each([
{ defaultMode: 'ADD' as const, outerDimensions: [66, 56, 56], outerUnitVolume: 0.207, outerVolume: 4.554 },
{ defaultMode: 'MULTIPLY' as const, outerDimensions: [77.5, 65, 65], outerUnitVolume: 0.3275, outerVolume: 7.205 },
])('keeps declared dimensions and volume original when the customer default is $defaultMode', async ({ defaultMode, outerDimensions, outerUnitVolume, outerVolume }) => {
const cargo = { itemNo: 1, lengthCm: 60, widthCm: 50, heightCm: 50, packageCount: 22, grossWeightKg: 200, volumeCbm: 3.3 }
vi.mocked(getPlatformUnifiedOrder).mockResolvedValue({ ...detail, order: { ...detail.order, cargoes: [cargo] } } as never)
vi.mocked(getCustomerDimensionConfig).mockResolvedValue({
tenantId: '6001', defaultCalculationMode: defaultMode,
rules: [{ id: 'dimension-1', boxType: 'EXTRA_LARGE', boxTypeName: '特大箱',
innerLengthCm: 60, innerWidthCm: 50, innerHeightCm: 50, edgeAdditionCm: 2,
outerLengthCm: 64, outerWidthCm: 54, outerHeightCm: 54, volumeCbm: 0.187,
remark: null, version: 1, createdAt: null, updatedAt: null }],
})
vi.mocked(listCustomerChargeRules).mockResolvedValue([{
id: 'rule-1', tenantId: '6001', ruleCode: 'DEFAULT', ruleVersion: 1, customerId: '6001', customerName: '测试客户',
businessType: 'FBA', chargeMode: 'KG', taxMode: 'TAX_INCLUDED', conversionFactor: 167,
lengthFactor: 1.25, widthFactor: 1.25, heightFactor: 1.25, defaultRule: true,
roundingScale: 4, roundingMode: 'CEILING', currency: 'CNY', effectiveFrom: '2026-01-01',
effectiveTo: null, status: 'ACTIVE', remark: null,
}])
const measurement = savedOuterMeasurement()
vi.mocked(getInboundMeasurement).mockResolvedValue({ ...measurement, items: [{ ...measurement.items[0],
outerLengthCm: null, outerWidthCm: null, outerHeightCm: null,
calculatedOuterLengthCm: outerDimensions[0], calculatedOuterWidthCm: outerDimensions[1], calculatedOuterHeightCm: outerDimensions[2],
coefficientMode: defaultMode, calculationRuleVersion: 'CUSTOMER_DIMENSION_V1',
unitVolumeCbm: outerUnitVolume, totalVolumeCbm: outerVolume, volumeWeightKg: outerVolume * 167,
}] } as never)
const wrapper = mountDetail()
await flushPromises()
const vm = wrapper.vm as unknown as {
customerMeasurementRows: Record<string, unknown>[]
outerMeasurementRows: Record<string, unknown>[]
actualMeasurementRows: Record<string, unknown>[]
measurementSectionModes: { customer: string; outer: string; actual: string }
feeConfirmRows: { source: string; count: number; weight: number; volume: number }[]
loadMeasurementDetails(): Promise<void>
}
await vm.loadMeasurementDetails()
expect(vm.customerMeasurementRows[0]).toMatchObject({ lengthCm: 60, widthCm: 50, heightCm: 50, unitVolumeCbm: 0.15, totalVolumeCbm: 3.3 })
const customerReference = vm.feeConfirmRows.find(row => row.source === '客户下单数据')!
expect(customerReference).toMatchObject({ count: 22, volume: 3.3 })
expect(customerReference.weight).toBeCloseTo(200)
expect(vm.measurementSectionModes).toEqual({ customer: 'ACTUAL', outer: defaultMode, actual: 'ACTUAL' })
expect(vm.outerMeasurementRows[0]).toMatchObject({ lengthCm: outerDimensions[0], widthCm: outerDimensions[1], heightCm: outerDimensions[2], totalVolumeCbm: outerVolume })
expect(vm.actualMeasurementRows[0]).toMatchObject({ lengthCm: 62, widthCm: 52, heightCm: 52, totalVolumeCbm: 3.6894 })
expect(cargo).toEqual({ itemNo: 1, lengthCm: 60, widthCm: 50, heightCm: 50, packageCount: 22, grossWeightKg: 200, volumeCbm: 3.3 })
wrapper.unmount()
})
it('shows actual and outer unit volumes and totals consistently with four decimal places', async () => {
const wrapper = mountDetail()
await flushPromises()
@@ -246,6 +301,41 @@ describe('UnifiedOrdersView downstream fulfillment detail', () => {
wrapper.unmount()
})
it('keeps confirm-and-send available after sending until the customer confirms, without reloading the page', async () => {
const fee = { unitPrice: 13.6, chargeableQuantity: 650.298, chargeUnit: 'KG', roundingScale: 4, rateCode: 'RATE', feeStatus: 'CALCULATED' }
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 FeeConfirmVm & {
activeDetailTab: string
automaticFee: { feeStatus: string } | null
}
vm.activeDetailTab = 'measurement'
vm.measurementDetail = { ...savedOuterMeasurement(), customerFeeConfirmed: false }
await flushPromises()
expect(wrapper.find('[data-testid="confirm-send-fee-button"]').exists()).toBe(true)
await vm.openFeeConfirmDialog()
await vm.executeConfirmAndSendFee()
await flushPromises()
expect(vm.automaticFee?.feeStatus).toBe('SENT')
expect(wrapper.find('[data-testid="confirm-send-fee-button"]').exists()).toBe(true)
await wrapper.get('[data-testid="confirm-send-fee-button"]').trigger('click')
await flushPromises()
await vm.executeConfirmAndSendFee()
await flushPromises()
expect(sendInboundFee).toHaveBeenCalledTimes(2)
expect(wrapper.find('[data-testid="confirm-send-fee-button"]').exists()).toBe(true)
vm.measurementDetail = { ...vm.measurementDetail, customerFeeConfirmed: true }
await flushPromises()
expect(wrapper.find('[data-testid="confirm-send-fee-button"]').exists()).toBe(false)
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()
@@ -257,6 +347,8 @@ describe('UnifiedOrdersView downstream fulfillment detail', () => {
expect(vm.feeConfirmVolume).toBe(3.894)
expect(vm.feeConfirmReference?.chargeableQuantity).toBe(650.298)
expect(vm.feeConfirmEstimatedTotal).toBe(8844.05)
const referenceRow = wrapper.findAll('tr').find(row => row.text().startsWith('参考计费量'))!
expect(referenceRow.findAll('td')[0]!.text()).toBe('650.3 KG')
expect(getLatestInboundFee).not.toHaveBeenCalled()
expect(calculateInboundFee).not.toHaveBeenCalled()
wrapper.unmount()