Merge origin/release/test and preserve local frontend changes
This commit is contained in:
@@ -1,24 +1,54 @@
|
||||
import ElementPlus from 'element-plus'
|
||||
import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils'
|
||||
import { enableAutoUnmount, flushPromises, mount, type VueWrapper } from '@vue/test-utils'
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
import { getLastMileOrder } from '../api/lastMileOrders'
|
||||
import { createLastMileOrderFee, getLastMileOrder } from '../api/lastMileOrders'
|
||||
import LastMileOrderDetailView from './LastMileOrderDetailView.vue'
|
||||
import AppDialog from '@/shared/components/AppDialog.vue'
|
||||
import OperationErrorToast from '@/shared/components/OperationErrorToast.vue'
|
||||
import { ApiResponseError } from '@/shared/api/http'
|
||||
import { listSuppliers, type Supplier } from '@/modules/suppliers'
|
||||
import { getPlatformWarehouse } from '@/modules/warehouses'
|
||||
import { getWarehouseTransfer } from '../api/warehouseTransfers'
|
||||
|
||||
const can = vi.hoisted(() => vi.fn(() => false))
|
||||
const push = vi.hoisted(() => vi.fn())
|
||||
vi.mock('@/modules/warehouses', () => ({ getPlatformWarehouse: vi.fn() }))
|
||||
vi.mock('vue-router', () => ({ useRoute: () => ({ params: { orderId: 'internal-order-id' } }), useRouter: () => ({ push }) }))
|
||||
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => false }) }))
|
||||
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can }) }))
|
||||
vi.mock('@/modules/suppliers', () => ({ listSuppliers: vi.fn() }))
|
||||
vi.mock('../api/lastMileOrders', () => ({ getLastMileOrder: vi.fn(), createLastMileOrderFee: vi.fn() }))
|
||||
vi.mock('../api/warehouseTransfers', () => ({ getWarehouseTransfer: vi.fn() }))
|
||||
const feeSupplier: Supplier = { id: '9223372036854775001', tenantId: '0', name: '测试供应商', category: 'TRUCKING', truckingScope: 'OVERSEAS', carrierCodes: [], region: '', legalRepresentative: '', contactName: '', phone: '', email: null, address: null, businessLicenseNo: null, licenseFileId: null, status: 'ACTIVE', settlementPeriod: 'PER_TRANSACTION', remark: null, createdBy: null, createdAt: '', updatedBy: null, updatedAt: '', deleted: false, version: 1 }
|
||||
async function openFeeEditor() {
|
||||
can.mockReturnValue(true)
|
||||
const wrapper = mount(LastMileOrderDetailView, { attachTo: document.body, global: { plugins: [ElementPlus] } })
|
||||
await flushPromises()
|
||||
await wrapper.get('#tab-fees').trigger('click'); await flushPromises()
|
||||
await wrapper.findAll('button').find(button => button.text() === '新增费用')!.trigger('click'); await flushPromises()
|
||||
const dialog = wrapper.findComponent(AppDialog)
|
||||
return { wrapper, dialog }
|
||||
}
|
||||
async function fillFee(dialog: VueWrapper, direction: 'RECEIVABLE' | 'PAYABLE') {
|
||||
const selects = dialog.findAllComponents({ name: 'ElSelect' })
|
||||
selects[0]!.vm.$emit('update:modelValue', 'STORAGE_FEE'); selects[0]!.vm.$emit('change', 'STORAGE_FEE')
|
||||
selects[1]!.vm.$emit('update:modelValue', direction); await flushPromises()
|
||||
if (direction === 'PAYABLE') dialog.findAllComponents({ name: 'ElSelect' }).find(select => select.props('placeholder') === '请选择供应商')!.vm.$emit('update:modelValue', feeSupplier.id)
|
||||
dialog.findAllComponents({ name: 'ElSelect' }).find(select => select.props('placeholder') === '请选择币种')!.vm.$emit('update:modelValue', direction === 'PAYABLE' ? 'CNY' : 'USD')
|
||||
const numbers = dialog.findAllComponents({ name: 'ElInputNumber' })
|
||||
numbers[0]!.vm.$emit('update:modelValue', 80); numbers[1]!.vm.$emit('update:modelValue', 2)
|
||||
const dates = dialog.findAllComponents({ name: 'ElDatePicker' })
|
||||
dates[0]!.vm.$emit('update:modelValue', '2026-10-20'); dates[1]!.vm.$emit('update:modelValue', '2026-10-01'); dates[2]!.vm.$emit('update:modelValue', '2026-10-20')
|
||||
await flushPromises()
|
||||
await dialog.get('input[placeholder="如 箱、托、天、m³"]').setValue('托')
|
||||
}
|
||||
|
||||
enableAutoUnmount(afterEach)
|
||||
|
||||
describe('LastMileOrderDetailView domestic detail alignment', () => {
|
||||
it('counts mixed parcel and box receipts by their physical unit instead of calling both parcels', async () => {
|
||||
vi.mocked(getLastMileOrder).mockResolvedValue({ order: { id: 'return-tail', customerId: 'C1', orderType: 'RETURN_RELABEL', orderNo: 'RT-1' }, cargo: [], fees: [], statusHistory: [], operationHistory: [],
|
||||
returnReceipt: { order: { returnNo: 'RT-1', expectedQuantity: 8, receivedQuantity: 8 }, receipts: [{ receiptNo: 'P1', unitType: 'PARCEL' }, { receiptNo: 'B1', unitType: 'BOX', length: 30, width: 20, height: 10, weight: 2 }],
|
||||
cartons: [{ receiptNo: 'P1', cartonMark: 'PK1', items: [] }, { receiptNo: 'B1', cartonMark: 'RC1', items: [] }] } })
|
||||
returnReceipt: { order: { returnNo: 'RT-1', expectedQuantity: 8, receivedQuantity: 8 }, receipts: [{ receiptNo: 'P1', unitType: 'PARCEL', receivedQuantity: 1, boxCount: 0, signedAt: '2026-10-20T10:00:00' }, { receiptNo: 'B1', unitType: 'BOX', receivedQuantity: 1, boxCount: 1, signedAt: '2026-10-20T10:00:00', length: 30, width: 20, height: 10, weight: 2 }],
|
||||
cartons: [{ receiptNo: 'P1', cartonMark: 'PK1', countStatus: 'COUNTED', items: [] }, { receiptNo: 'B1', cartonMark: 'RC1', countStatus: 'COUNTED', items: [] }] } })
|
||||
const wrapper = mount(LastMileOrderDetailView, { global: { plugins: [ElementPlus] } }); await flushPromises()
|
||||
await wrapper.get('#tab-return-receipt').trigger('click'); await flushPromises()
|
||||
expect(wrapper.text()).toContain('签收实物明细(1 包裹 · 1 箱)')
|
||||
@@ -100,6 +130,9 @@ describe('LastMileOrderDetailView domestic detail alignment', () => {
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
can.mockReset().mockReturnValue(false)
|
||||
vi.mocked(listSuppliers).mockReset().mockResolvedValue({ records: [feeSupplier], page: 1, size: 200, total: 1, pages: 1 })
|
||||
vi.mocked(createLastMileOrderFee).mockReset().mockResolvedValue({ id: 'fee-test', sourceFeeId: 'source-fee-test', sourceVersion: 1, customerId: 'customer-1', feeItemCode: 'STORAGE_FEE', feeItemName: '仓租', direction: 'RECEIVABLE', currency: 'CNY', amount: 80, originalCurrency: 'USD', originalAmount: 80, exchangeRate: 1, exchangeRateMonth: '2026-10', reviewStatus: 'PENDING', publicationStatus: 'PENDING', sourceStatus: 'AVAILABLE', businessDate: '2026-10-20', createdAt: '', updatedAt: '' })
|
||||
push.mockReset()
|
||||
vi.mocked(getWarehouseTransfer).mockReset()
|
||||
vi.mocked(getPlatformWarehouse).mockReset()
|
||||
@@ -215,4 +248,37 @@ describe('LastMileOrderDetailView domestic detail alignment', () => {
|
||||
expect(rows[1].text()).toContain('处理中')
|
||||
expect(rows[1].text()).toContain('5')
|
||||
})
|
||||
it.each(['RECEIVABLE', 'PAYABLE'] as const)('keeps %s fee fields and exact payload through fullscreen editing', async direction => {
|
||||
const { dialog } = await openFeeEditor()
|
||||
expect(dialog.props('title')).toContain('FL-WH-001')
|
||||
expect(dialog.props('width')).toContain('720px')
|
||||
await fillFee(dialog, direction)
|
||||
await dialog.get('button[aria-label="展开全屏"]').trigger('click')
|
||||
await dialog.findAll('button').find(button => button.text() === '保存费用')!.trigger('click'); await flushPromises()
|
||||
const payable = direction === 'PAYABLE'
|
||||
expect(createLastMileOrderFee).toHaveBeenCalledWith('internal-order-id', {
|
||||
feeItemCode: 'STORAGE_FEE', feeItemName: '仓租', direction, currency: payable ? 'CNY' : 'USD', amount: 80, businessDate: '2026-10-20',
|
||||
supplierId: payable ? feeSupplier.id : undefined, supplierName: payable ? feeSupplier.name : undefined, supplierCategory: payable ? feeSupplier.category : undefined,
|
||||
settlementMode: payable ? 'ON_ACCOUNT' : undefined, chargeableQuantity: 2, quantityUnit: '托', unitPrice: 40, periodStart: '2026-10-01', periodEnd: '2026-10-20',
|
||||
})
|
||||
})
|
||||
it('keeps fee required-field validation before a request', async () => {
|
||||
const { dialog } = await openFeeEditor()
|
||||
await dialog.findAll('button').find(button => button.text() === '保存费用')!.trigger('click'); await flushPromises()
|
||||
expect(createLastMileOrderFee).not.toHaveBeenCalled()
|
||||
})
|
||||
it('locks fee controls and preserves failed values and diagnostic context', async () => {
|
||||
let reject!: (reason: unknown) => void
|
||||
vi.mocked(createLastMileOrderFee).mockReturnValue(new Promise((_, no) => { reject = no }))
|
||||
const { wrapper, dialog } = await openFeeEditor()
|
||||
await fillFee(dialog, 'RECEIVABLE')
|
||||
await dialog.findAll('button').find(button => button.text() === '保存费用')!.trigger('click'); await flushPromises()
|
||||
expect(dialog.findComponent({ name: 'ElDialog' }).props()).toMatchObject({ showClose: false, closeOnPressEscape: false })
|
||||
expect(dialog.get('input[placeholder="如 箱、托、天、m³"]').attributes('disabled')).toBeDefined()
|
||||
reject(new ApiResponseError('费用保存失败', 'TEST_REJECTED', 409, null, 'fee-test-request')); await flushPromises()
|
||||
expect(dialog.props('modelValue')).toBe(true)
|
||||
expect(dialog.findAllComponents({ name: 'ElInputNumber' })[0]!.props('modelValue')).toBe(80)
|
||||
expect(wrapper.findComponent(OperationErrorToast).props('details')).toMatchObject({ requestId: 'fee-test-request' })
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user