This commit is contained in:
2026-08-13 11:57:44 +08:00
parent 7beca3f985
commit 835ac0ad12
87 changed files with 3721 additions and 295 deletions
@@ -1,12 +1,13 @@
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 { afterAll, afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { useAccessPreviewStore } from '@/core/access/accessPreview'
import { useAuthStore } from '@/core/auth/auth'
import { ApiResponseError } from '@/shared/api/http'
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
import { getFinanceFeeSource, listFinanceFeePool, reviewFinanceFeeSource, updateFinanceFeeSourceSettlementInclusion } from '../api/finance'
import { createReconciliationStatement, getFinanceFeeSource, listFinanceFeePool, reviewFinanceFeeSource, updateFinanceFeeSourceSettlementInclusion } from '../api/finance'
import type { FinanceFeeSourceDetail } from '../types'
import FinanceCustomerArapView from './FinanceCustomerArapView.vue'
@@ -16,6 +17,7 @@ vi.mock('../api/finance', () => ({
getFinanceFeeSource: vi.fn(),
reviewFinanceFeeSource: vi.fn(),
updateFinanceFeeSourceSettlementInclusion: vi.fn(),
createReconciliationStatement: vi.fn(),
}))
class ResizeObserverStub implements ResizeObserver {
@@ -39,13 +41,18 @@ const detail: FinanceFeeSourceDetail = {
sourceStatus: 'AVAILABLE', status: 'AVAILABLE', statementGenerated: false, operationVersion: 3, createdAt: '2026-08-11', operationLogs: [],
}
async function mountView(permissions: string[]) {
async function mountView(permissions: string[], initialPath = '/finance/customer-arap') {
const pinia = createPinia(); setActivePinia(pinia)
const auth = useAuthStore(); auth.token = 'token'; auth.permissions = permissions
useAccessPreviewStore().initializeAfterLogin()
const wrapper = mount(FinanceCustomerArapView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
const router = createRouter({ history: createMemoryHistory(), routes: [
{ path: '/finance/customer-arap', name: 'finance-customer-arap', component: FinanceCustomerArapView },
{ path: '/finance/customer-arap/:feeSourceId', name: 'finance-customer-arap-detail', component: FinanceCustomerArapView },
] })
await router.push(initialPath); await router.isReady()
const wrapper = mount(FinanceCustomerArapView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus, router] } })
await flushPromises()
return wrapper
return Object.assign(wrapper, { router })
}
async function loadRows(wrapper: Awaited<ReturnType<typeof mountView>>) {
@@ -69,23 +76,25 @@ describe('FinanceCustomerArapView G2 operations', () => {
vi.mocked(getFinanceFeeSource).mockReset().mockResolvedValue(detail)
vi.mocked(reviewFinanceFeeSource).mockReset().mockResolvedValue({ ...detail, reviewStatus: 'APPROVED', operationVersion: 4 })
vi.mocked(updateFinanceFeeSourceSettlementInclusion).mockReset().mockResolvedValue({ ...detail, reviewStatus: 'APPROVED', settlementIncluded: true, operationVersion: 4 })
vi.mocked(createReconciliationStatement).mockReset().mockResolvedValue({} as never)
})
it('does not expose or call G2 operations without their permissions', async () => {
it('shows detail from the list permission but keeps G2 operations independent', async () => {
const wrapper = await mountView(['platform:finance:bill:view'])
await loadRows(wrapper)
const buttonLabels = wrapper.findAll('button').map((button) => button.text())
expect(buttonLabels).not.toContain('详情')
expect(buttonLabels).not.toContain('审核')
expect(buttonLabels).not.toContain('结算')
expect(getFinanceFeeSource).not.toHaveBeenCalled()
const vm = wrapper.vm as unknown as { openDetail(row: Record<string, unknown>): Promise<void> }
await vm.openDetail({ id: 'RECEIVABLE:42', feeSourceId: '42' })
expect(getFinanceFeeSource).toHaveBeenCalledWith('42')
expect(reviewFinanceFeeSource).not.toHaveBeenCalled()
expect(updateFinanceFeeSourceSettlementInclusion).not.toHaveBeenCalled()
})
it('loads all customers by default and keeps explicit customer filtering', async () => {
const wrapper = await mountView(['platform:finance:bill:view'])
const wrapper = await mountView(['platform:finance:bill:view', 'platform:customer:list'])
expect(listFinanceFeePool).toHaveBeenCalledWith(undefined, undefined)
const vm = wrapper.vm as unknown as { load(model: Record<string, string>): Promise<void> }
await vm.load({ customerId: '10001', direction: 'RECEIVABLE', status: '', keyword: '' })
@@ -146,4 +155,26 @@ describe('FinanceCustomerArapView G2 operations', () => {
expect(document.body.textContent).toContain('req-g2-error')
expect(document.body.textContent).toContain('trace-g2-error')
})
it('restores the independent detail route from its fee source id', async () => {
const wrapper = await mountView(['platform:finance:bill:view'], '/finance/customer-arap/42')
expect(getFinanceFeeSource).toHaveBeenCalledWith('42')
expect(wrapper.text()).toContain('费用来源详情')
expect(wrapper.text()).toContain('IB-91')
expect(wrapper.router.currentRoute.value.name).toBe('finance-customer-arap-detail')
})
it('creates a reconciliation draft from selected eligible receivables', async () => {
const wrapper = await mountView(['platform:finance:bill:view', 'platform:finance:reconciliation:create'])
await loadRows(wrapper)
const vm = wrapper.vm as unknown as {
selectedRows: Array<Record<string, unknown>>
generationForm: { mode: 'BY_CUTOFF' | 'SELECTED'; customerId: string; period: string[]; remark: string }
submitGeneration(): Promise<void>
}
vm.selectedRows = [{ id: 'RECEIVABLE:42', feeSourceId: '42', direction: 'RECEIVABLE', reviewStatus: 'APPROVED', settlementIncluded: true, statementGenerated: false, amount: 100 }]
Object.assign(vm.generationForm, { mode: 'SELECTED', customerId: '10001', period: ['2026-08-01', '2026-08-31'], remark: '八月费用' })
await vm.submitGeneration()
expect(createReconciliationStatement).toHaveBeenCalledWith({ customerId: '10001', currency: 'CNY', generationMode: 'SELECTED', feeSourceIds: ['42'], periodStart: '2026-08-01', periodEnd: '2026-08-31', remark: '八月费用' })
})
})