import ElementPlus from 'element-plus' import { createPinia, setActivePinia } from 'pinia' import { createMemoryHistory, createRouter } from 'vue-router' import { enableAutoUnmount, flushPromises, mount } from '@vue/test-utils' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { useAccessPreviewStore } from '@/core/access/accessPreview' import { useAuthStore } from '@/core/auth/auth' import { createPlatformExportTask } from '@/modules/downloads' import { listPlatformTenants } from '@/modules/iam' import { showExportTaskCreatedDialog } from '@/shared/components/exportTaskCreatedDialog' import { getFinanceSettlement, listFinancePayableSettlements, listFinanceReceivableSettlements } from '../api/finance' import type { FinanceSettlementDetail } from '../types' import FinanceFormalSettlementPage from './FinanceFormalSettlementPage.vue' vi.mock('@/modules/downloads', () => ({ createPlatformExportTask: vi.fn() })) vi.mock('@/modules/iam', () => ({ listPlatformTenants: vi.fn() })) vi.mock('@/modules/suppliers', () => ({ categoryLabels: {}, listSuppliers: vi.fn() })) vi.mock('@/shared/components/exportTaskCreatedDialog', () => ({ showExportTaskCreatedDialog: vi.fn() })) vi.mock('../api/finance', async (importOriginal) => ({ ...await importOriginal(), getFinanceSettlement: vi.fn(), listFinancePayableSettlements: vi.fn(), listFinanceReceivableSettlements: vi.fn(), })) class ResizeObserverStub implements ResizeObserver { observe() {} unobserve() {} disconnect() {} } function createMemoryStorage(): Storage { const values = new Map() return { get length() { return values.size }, clear: () => values.clear(), getItem: (key) => values.get(key) ?? null, key: (index) => [...values.keys()][index] ?? null, removeItem: (key) => values.delete(key), setItem: (key, value) => values.set(key, value), } } function piniaWithPermissions(permissions: string[]) { const pinia = createPinia() setActivePinia(pinia) const auth = useAuthStore() auth.token = 'token' auth.permissions = permissions useAccessPreviewStore().initializeAfterLogin() return pinia } function testRouter() { return createRouter({ history: createMemoryHistory(), routes: [ { path: '/', name: 'settlements', component: { template: '
' } }, { path: '/downloads', name: 'download-center', component: { template: '
' } }, ], }) } const detail: FinanceSettlementDetail = { id: '701', settlementNo: 'AR-701', customerId: '10001', reconciliationStatementId: '501', direction: 'RECEIVABLE', amount: 300, paidAmount: 0, outstandingAmount: 300, currency: 'CNY', status: 'ISSUED', version: 0, lines: [], transactions: [], revisions: [], } enableAutoUnmount(afterEach) describe('formal settlement download-center exports', () => { beforeEach(() => { vi.stubGlobal('ResizeObserver', ResizeObserverStub) vi.stubGlobal('localStorage', createMemoryStorage()) vi.stubGlobal('sessionStorage', createMemoryStorage()) vi.mocked(listPlatformTenants).mockReset().mockResolvedValue({ records: [], page: 1, size: 200, total: 0, pages: 1 }) vi.mocked(listFinanceReceivableSettlements).mockReset().mockResolvedValue([]) vi.mocked(listFinancePayableSettlements).mockReset().mockResolvedValue([]) vi.mocked(getFinanceSettlement).mockReset().mockResolvedValue(detail) vi.mocked(createPlatformExportTask).mockReset().mockResolvedValue({ taskName: '应收结算单列表', taskNo: 'EXP-001' } as never) vi.mocked(showExportTaskCreatedDialog).mockReset().mockResolvedValue(false) }) it('creates an AR list task with current filters and no paging or direction parameters', async () => { const router = testRouter() await router.push('/'); await router.isReady() const pinia = piniaWithPermissions(['platform:finance:settlement-ar:list', 'platform:finance:settlement-ar:export', 'platform:download:list']) const wrapper = mount(FinanceFormalSettlementPage, { props: { kind: 'receivable' }, global: { plugins: [pinia, ElementPlus, router] } }) await flushPromises() const vm = wrapper.vm as unknown as { filters: Record; exportRows(): Promise } vm.filters = { customerId: '10001', status: 'ISSUED', settlementNo: ' AR-001 ', supplierName: '' } await vm.exportRows() expect(createPlatformExportTask).toHaveBeenCalledWith({ exportType: 'FINANCE_RECEIVABLE_SETTLEMENT_LIST', format: 'XLSX', taskName: '应收结算单列表', parameters: { customerId: '10001', status: 'ISSUED', settlementNo: 'AR-001' }, }) expect(createPlatformExportTask).not.toHaveBeenCalledWith(expect.objectContaining({ parameters: expect.objectContaining({ direction: expect.anything(), page: expect.anything(), pageSize: expect.anything() }) })) }) it('creates an AP list task with the supplier filter', async () => { const router = testRouter() await router.push('/'); await router.isReady() const pinia = piniaWithPermissions(['platform:finance:settlement-ap:list', 'platform:finance:settlement-ap:export', 'platform:download:list']) const wrapper = mount(FinanceFormalSettlementPage, { props: { kind: 'payable' }, global: { plugins: [pinia, ElementPlus, router] } }) await flushPromises() const vm = wrapper.vm as unknown as { filters: Record; exportRows(): Promise } vm.filters = { customerId: '10002', status: 'PARTIALLY_PAID', settlementNo: 'AP-002', supplierName: '承运供应商' } await vm.exportRows() expect(createPlatformExportTask).toHaveBeenCalledWith({ exportType: 'FINANCE_PAYABLE_SETTLEMENT_LIST', format: 'XLSX', taskName: '应付结算单列表', parameters: { customerId: '10002', status: 'PARTIALLY_PAID', settlementNo: 'AP-002', supplierName: '承运供应商' }, }) }) it('exports a detail by settlement business key and opens the download center on confirmation', async () => { vi.mocked(createPlatformExportTask).mockResolvedValueOnce({ taskName: '应收结算单详情', taskNo: 'EXP-DETAIL-1' } as never) vi.mocked(showExportTaskCreatedDialog).mockResolvedValueOnce(true) const router = testRouter() await router.push('/'); await router.isReady() const pinia = piniaWithPermissions(['platform:finance:settlement-ar:list', 'platform:finance:settlement-ar:export', 'platform:download:list']) const wrapper = mount(FinanceFormalSettlementPage, { props: { kind: 'receivable', settlementId: '701' }, global: { plugins: [pinia, ElementPlus, router] } }) await flushPromises() const vm = wrapper.vm as unknown as { exportDetail(): Promise } await vm.exportDetail() expect(createPlatformExportTask).toHaveBeenCalledWith({ exportType: 'FINANCE_RECEIVABLE_SETTLEMENT_DETAIL', format: 'XLSX', taskName: '应收结算单详情', parameters: { settlementId: '701' }, }) expect(showExportTaskCreatedDialog).toHaveBeenCalledWith({ taskName: '应收结算单详情', taskNo: 'EXP-DETAIL-1', dense: true }) expect(router.currentRoute.value.name).toBe('download-center') }) it('uses the payable detail type without a direction parameter', async () => { vi.mocked(getFinanceSettlement).mockResolvedValueOnce({ ...detail, id: '801', settlementNo: 'AP-801', direction: 'PAYABLE' }) vi.mocked(createPlatformExportTask).mockResolvedValueOnce({ taskName: '应付结算单详情', taskNo: 'EXP-DETAIL-2' } as never) const router = testRouter() await router.push('/'); await router.isReady() const pinia = piniaWithPermissions(['platform:finance:settlement-ap:list', 'platform:finance:settlement-ap:export', 'platform:download:list']) const wrapper = mount(FinanceFormalSettlementPage, { props: { kind: 'payable', settlementId: '801' }, global: { plugins: [pinia, ElementPlus, router] } }) await flushPromises() await (wrapper.vm as unknown as { exportDetail(): Promise }).exportDetail() expect(createPlatformExportTask).toHaveBeenCalledWith({ exportType: 'FINANCE_PAYABLE_SETTLEMENT_DETAIL', format: 'XLSX', taskName: '应付结算单详情', parameters: { settlementId: '801' }, }) }) it('hides export actions unless both export and download-list permissions are present', async () => { const router = testRouter() await router.push('/'); await router.isReady() const pinia = piniaWithPermissions(['platform:finance:settlement-ar:list', 'platform:download:list']) const wrapper = mount(FinanceFormalSettlementPage, { props: { kind: 'receivable' }, global: { plugins: [pinia, ElementPlus, router] } }) await flushPromises() expect(wrapper.find('.settlement-list-actions').text()).not.toContain('导出') }) })