Files
admin-web/src/modules/container-loading/components/ContainerLastLegFeesPanel.test.ts
T
2026-10-20 15:48:27 +08:00

152 lines
9.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { mount, flushPromises } from '@vue/test-utils'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import ElementPlus, { ElFormItem, ElSelect } from 'element-plus'
import AppDialog from '@/shared/components/AppDialog.vue'
import ContainerLastLegFeesPanel from './ContainerLastLegFeesPanel.vue'
const mocks = vi.hoisted(() => ({ upload: vi.fn(), metadata: vi.fn(), download: vi.fn(), create: vi.fn(), fees: vi.fn() }))
vi.mock('@/core/access/accessPreview', () => ({ useAccessPreviewStore: () => ({ can: () => true }) }))
vi.mock('@/modules/finance', () => ({ getFinancePaymentVoucher: mocks.metadata, downloadFinancePaymentVoucher: mocks.download }))
vi.mock('@/modules/loading-lists', () => ({ uploadContainerFeePaymentVoucher: mocks.upload, listContainerFeeSupplierOptions: async () => [{ id: '21', supplierName: '供应商甲', category: 'TERMINAL_DRAYAGE' }] }))
vi.mock('../api/overseasContainerFees', () => ({
listOverseasContainerFees: mocks.fees, listOverseasContainerCustomers: async () => [{ customerId: '11', customerName: '客户甲' }],
createOverseasContainerFee: mocks.create, adjustOverseasContainerFee: vi.fn(), reviewOverseasContainerFee: vi.fn(), submitOverseasContainerFee: vi.fn(), voidOverseasContainerFee: vi.fn(),
}))
async function setup(readonly = false, realDialog = false, containerNo: string | null = 'CONT-99') {
const wrapper = mount(ContainerLastLegFeesPanel, { props: { containerBookingId: '99', cabinetNo: 'CAB-99', containerNo, readonly }, global: { plugins: [ElementPlus], stubs: realDialog ? {} : {
AppDialog: { props: ['modelValue', 'title', 'confirmDisabled'], template: '<section v-if="modelValue"><h3>{{ title }}</h3><slot /><button :disabled="confirmDisabled" @click="$emit(\'confirm\')">保存</button></section>' },
} } })
await flushPromises()
return wrapper
}
type FormState = { form: { customerId: string; settlementMode: string; direction: string; amount: number | null; businessDate: string; supplierId: string; paidAt: string; paymentReferenceNo: string }; dialogOpen: boolean }
async function upload(wrapper: Awaited<ReturnType<typeof setup>>, name: string) {
const input = wrapper.get('input[type="file"]')
Object.defineProperty(input.element, 'files', { configurable: true, value: [new File(['image'], name, { type: 'image/png' })] })
await input.trigger('change')
await flushPromises()
}
describe('尾程付款凭证', () => {
beforeEach(() => {
vi.clearAllMocks()
mocks.fees.mockResolvedValue([])
vi.stubGlobal('ResizeObserver', class { observe() {} unobserve() {} disconnect() {} })
URL.createObjectURL = vi.fn(() => 'blob:voucher')
URL.revokeObjectURL = vi.fn()
mocks.metadata.mockResolvedValue({ fileId: '91', fileName: '付款.png', contentType: 'image/png' })
mocks.download.mockResolvedValue(new Blob(['image']))
})
it('新增连续表切换付款字段和全屏后保留输入,保存仍使用真实集装箱号', async () => {
const wrapper = await setup(false, true)
await wrapper.findAll('button').find(button => button.text() === '新增费用')!.trigger('click')
await flushPromises()
const state = (wrapper.vm.$ as unknown as { setupState: FormState }).setupState
const dialog = wrapper.findAllComponents(AppDialog).find(item => item.props('title').startsWith('新增尾程费用'))!
expect(dialog.props('title')).toBe('新增尾程费用 · CAB-99')
const labels = () => dialog.findAllComponents(ElFormItem).map(item => item.props('label'))
expect(labels()).toHaveLength(7)
state.form.settlementMode = 'DIRECT_PAID'
state.form.amount = 120.25
state.form.supplierId = '21'
state.form.businessDate = '2026-10-19'
state.form.paidAt = '2026-10-19T09:00:00'
state.form.paymentReferenceNo = ' PAY-001 '
await flushPromises()
expect(labels()).toHaveLength(11)
await dialog.get('[aria-label="展开全屏"]').trigger('click')
expect(dialog.find('.el-dialog.is-fullscreen').exists()).toBe(true)
expect(state.form.amount).toBe(120.25)
await dialog.get('[aria-label="退出全屏"]').trigger('click')
expect(state.form.paymentReferenceNo).toBe(' PAY-001 ')
dialog.vm.$emit('confirm')
await flushPromises()
expect(mocks.create).toHaveBeenCalledWith(expect.objectContaining({
customerId: '11', containerBookingId: '99', containerNo: 'CONT-99', businessNo: 'CONT-99',
amount: 120.25, supplierId: '21', settlementMode: 'DIRECT_PAID',
paidAt: '2026-10-19T09:00:00', paymentReferenceNo: 'PAY-001', currency: 'CNY',
}), expect.stringContaining('container-fee-create-'))
wrapper.unmount()
})
it('调整的所属客户显示只读名称,应收费用隐藏供应商和付款字段', async () => {
mocks.fees.mockResolvedValue([{ sourceFeeId: '81', sourceVersion: 7, customerId: '11', customerName: '客户甲', feeItemCode: 'PORT_FEE', amount: 10, businessDate: '2026-10-19', direction: 'RECEIVABLE', reviewStatus: 'APPROVED', sourceStatus: 'ACTIVE', paymentVoucherFileIds: [] }])
const wrapper = await setup(false, true)
await wrapper.findAll('button').find(button => button.text() === '调整')!.trigger('click')
await flushPromises()
const dialog = wrapper.findAllComponents(AppDialog).find(item => item.props('title').startsWith('调整尾程费用'))!
const fields = dialog.findAllComponents(ElFormItem)
expect(fields.map(item => item.props('label'))).toEqual(['客户', '费用项目', '费用方向', '业务日期', '金额(CNY)'])
const customer = fields[0]!
expect(customer.text()).toContain('客户甲')
expect(customer.findComponent(ElSelect).exists()).toBe(false)
wrapper.unmount()
})
it('只有柜次号仍不能维护费用,保留补录真实集装箱号门禁', async () => {
const wrapper = await setup(false, true, null)
expect(wrapper.findAll('button').find(button => button.text() === '新增费用')!.attributes('disabled')).toBeDefined()
expect(wrapper.text()).toContain('尚未补录真实集装箱号')
expect(mocks.create).not.toHaveBeenCalled()
wrapper.unmount()
})
it('作废待财务确认时保留有效来源,停止调整和重复作废', async () => {
mocks.fees.mockResolvedValue([{ sourceFeeId: '81', customerId: '11', amount: 10, reviewStatus: 'APPROVED', sourceStatus: 'ACTIVE', voidStatus: 'PENDING_FINANCE', paymentVoucherFileIds: [] }])
const wrapper = await setup()
expect(wrapper.text()).toContain('待财务确认')
expect(wrapper.text()).not.toContain('已作废')
expect(wrapper.findAll('button').some(button => ['调整', '作废'].includes(button.text()))).toBe(false)
wrapper.unmount()
})
it('显示上传文件名,允许再次上传替换和移除', async () => {
const wrapper = await setup()
await wrapper.findAll('button').find(button => button.text() === '新增费用')!.trigger('click')
const state = (wrapper.vm.$ as unknown as { setupState: FormState }).setupState
state.form.settlementMode = 'DIRECT_PAID'
await flushPromises()
mocks.upload.mockResolvedValueOnce({ fileId: '91', fileName: '付款.png' }).mockResolvedValueOnce({ fileId: '92', fileName: '新凭证.png' })
await upload(wrapper, '付款.png')
expect(mocks.upload).toHaveBeenCalledWith('11', expect.any(File))
expect(wrapper.text()).toContain('付款.png')
await upload(wrapper, '新凭证.png')
expect(wrapper.text()).toContain('新凭证.png')
expect(wrapper.text()).not.toContain('付款.png')
await wrapper.findAll('button').find(b => b.text() === '移除')!.trigger('click')
expect(wrapper.text()).not.toContain('新凭证.png')
wrapper.unmount()
})
it('上传中禁止保存,客户切换后忽略旧客户上传结果', async () => {
const wrapper = await setup()
await wrapper.findAll('button').find(button => button.text() === '新增费用')!.trigger('click')
const state = (wrapper.vm.$ as unknown as { setupState: FormState }).setupState
state.form.settlementMode = 'DIRECT_PAID'
await flushPromises()
let complete!: (file: { fileId: string; fileName: string }) => void
mocks.upload.mockReturnValue(new Promise(resolve => { complete = resolve }))
await upload(wrapper, '旧客户.png')
expect(wrapper.findAll('button').find(b => b.text() === '保存')!.attributes('disabled')).toBeDefined()
state.form.customerId = '12'
complete({ fileId: '91', fileName: '旧客户.png' })
await flushPromises()
expect(wrapper.text()).not.toContain('旧客户.png')
wrapper.unmount()
})
it('只读列表能查看已保存的待审核凭证,并在关闭时释放预览资源', async () => {
mocks.fees.mockResolvedValue([{ sourceFeeId: '81', customerId: '11', amount: 10, reviewStatus: 'PENDING', paymentVoucherFileIds: ['91'] }])
const wrapper = await setup(true)
await wrapper.findAll('button').find(b => b.text() === '付款凭证 1')!.trigger('click')
await flushPromises()
expect(mocks.metadata).toHaveBeenCalledWith('11', '91')
expect(mocks.download).toHaveBeenCalledWith('11', '91')
expect(wrapper.get('img').attributes('src')).toBe('blob:voucher')
expect(wrapper.get('a[download]').attributes('download')).toBe('付款.png')
wrapper.unmount()
expect(URL.revokeObjectURL).toHaveBeenCalledWith('blob:voucher')
})
})