优化
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
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'
|
||||
@@ -24,23 +25,28 @@ function memoryStorage(): Storage {
|
||||
const statement: FinanceStatementReadModel = {
|
||||
id: '501', customerId: '10001', billNo: 'REC-501', customerName: '客户A', billDate: '2026-08-31', dueDate: '',
|
||||
totalAmount: 300, receivedAmount: 100, outstandingAmount: 200, currency: 'CNY', status: 'ISSUED', version: 3,
|
||||
createdBy: '创建人A', createdAt: '2026-08-01 09:00:00', updatedBy: '更新人B', updatedAt: '2026-08-20 11:00:00',
|
||||
createdBy: '7', createdByName: '创建人A', createdAt: '2026-08-01 09:00:00', updatedBy: '8', updatedByName: '更新人B', updatedAt: '2026-08-20 11:00:00',
|
||||
}
|
||||
const detail: FinanceReconciliationDetail = {
|
||||
...statement, paidAmount: 100, periodStart: '2026-08-01', periodEnd: '2026-08-31', creditDays: 30, settled: false, remark: '八月账单',
|
||||
createdBy: '创建人A', createdAt: '2026-08-01 09:00:00', updatedBy: '更新人B', updatedAt: '2026-08-20 11:00:00',
|
||||
createdBy: '7', createdByName: '创建人A', createdAt: '2026-08-01 09:00:00', updatedBy: '8', updatedByName: '更新人B', updatedAt: '2026-08-20 11:00:00',
|
||||
lines: [{ statementLineId: '601', sourceFeeId: '81', sourceFeeVersion: 2, sourceHash: 'hash-81', orderId: '11', inboundId: '21', containerId: '31', feeItemName: '运费', sourceAmount: 320, amount: 300, adjustmentReason: '优惠调整' }],
|
||||
paymentHistory: [{ transactionId: '701', transactionType: 'RECEIPT', transactionDate: '2026-08-20', amount: 100, method: 'BANK_TRANSFER', referenceNo: 'PAY-701', createdBy: '财务员', occurredAt: '2026-08-20 10:00:00' }],
|
||||
revisions: [],
|
||||
paymentHistory: [{ transactionId: '701', transactionType: 'RECEIPT', transactionDate: '2026-08-20', amount: 100, method: 'BANK_TRANSFER', referenceNo: 'PAY-701', createdBy: '9', createdByName: '财务员', occurredAt: '2026-08-20 10:00:00' }],
|
||||
revisions: [{ revisionNo: 1, action: 'ADJUST', beforeAmount: 320, afterAmount: 300, reason: '优惠调整', operatorId: '10', operatorName: '调整人', occurredAt: '2026-08-20 09:00:00' }],
|
||||
}
|
||||
|
||||
async function mountPage(permissions = ['platform:finance:customer-statement:list']) {
|
||||
async function mountPage(permissions = ['platform:finance:customer-statement:list'], initialPath = '/finance/customer-statements') {
|
||||
const pinia = createPinia(); setActivePinia(pinia)
|
||||
const auth = useAuthStore(); auth.token = 'token'; auth.permissions = permissions
|
||||
useAccessPreviewStore().initializeAfterLogin()
|
||||
const wrapper = mount(FinanceCustomerStatementsView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
|
||||
const router = createRouter({ history: createMemoryHistory(), routes: [
|
||||
{ path: '/finance/customer-statements', name: 'finance-customer-statements', component: FinanceCustomerStatementsView },
|
||||
{ path: '/finance/customer-statements/:statementId', name: 'finance-customer-statement-detail', component: FinanceCustomerStatementsView },
|
||||
] })
|
||||
await router.push(initialPath); await router.isReady()
|
||||
const wrapper = mount(FinanceCustomerStatementsView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus, router] } })
|
||||
await flushPromises()
|
||||
return wrapper
|
||||
return { wrapper, router }
|
||||
}
|
||||
|
||||
enableAutoUnmount(afterEach)
|
||||
@@ -65,10 +71,12 @@ describe('FinanceCustomerStatementsView', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('renders the main table and linked detail/payment tables on the same page', async () => {
|
||||
const wrapper = await mountPage()
|
||||
it('opens statement details on a dedicated route instead of below the list', async () => {
|
||||
const { wrapper, router } = await mountPage()
|
||||
await (wrapper.vm as unknown as { openDetail(row: FinanceStatementReadModel): Promise<void> }).openDetail(statement)
|
||||
await flushPromises()
|
||||
expect(router.currentRoute.value.name).toBe('finance-customer-statement-detail')
|
||||
expect(router.currentRoute.value.params.statementId).toBe('501')
|
||||
expect(document.body.textContent).toContain('账单明细表')
|
||||
expect(document.body.textContent).toContain('账单支付历史表')
|
||||
expect(document.body.textContent).toContain('REC-501')
|
||||
@@ -78,9 +86,12 @@ describe('FinanceCustomerStatementsView', () => {
|
||||
expect(document.body.textContent).not.toContain('hash-81')
|
||||
expect(document.body.textContent).toContain('优惠调整')
|
||||
expect(document.body.textContent).toContain('创建人A')
|
||||
expect(document.body.textContent).toContain('调整人')
|
||||
expect(document.body.textContent).toContain('2026-08-20 11:00')
|
||||
expect(document.querySelector('[aria-label="客户对账单详情"]')).toBeTruthy()
|
||||
expect(document.querySelector('[aria-label="所选对账单摘要"]')?.textContent).toContain('200.00 CNY')
|
||||
expect(document.querySelector('[aria-label="客户对账单列表"]')).toBeNull()
|
||||
expect(document.querySelector('[aria-label="所选对账单联动明细"]')).toBeNull()
|
||||
expect(document.querySelector('[aria-label="对账单摘要"]')?.textContent).toContain('200.00 CNY')
|
||||
expect(document.querySelector('.statement-detail-head')?.textContent).toContain('REC-501')
|
||||
expect(document.querySelector('[aria-label="客户对账单账单明细"]')).toBeTruthy()
|
||||
expect(document.querySelector('.detail-table-scroll [aria-label="客户对账单账单明细"]')).toBeTruthy()
|
||||
@@ -88,15 +99,14 @@ describe('FinanceCustomerStatementsView', () => {
|
||||
})
|
||||
|
||||
it('keeps the payment history in the desktop detail layout without duplicating a card container', async () => {
|
||||
const wrapper = await mountPage()
|
||||
const { wrapper } = await mountPage()
|
||||
const vm = wrapper.vm as unknown as { openDetail(row: FinanceStatementReadModel): Promise<void>; activeDetailTab: 'lines' | 'payments' }
|
||||
await vm.openDetail(statement)
|
||||
vm.activeDetailTab = 'payments'
|
||||
await flushPromises()
|
||||
|
||||
expect(document.querySelector('[aria-label="客户对账单支付历史"]')?.textContent).toContain('PAY-701')
|
||||
expect(document.querySelectorAll('.statement-detail-panel .detail-page-layout')).toHaveLength(1)
|
||||
expect(document.querySelectorAll('.statement-detail-panel .surface-panel')).toHaveLength(0)
|
||||
expect(document.querySelectorAll('.statement-detail-page')).toHaveLength(1)
|
||||
})
|
||||
|
||||
it('resolves customer names from the active customer list and never exposes internal customer or operator ids', async () => {
|
||||
@@ -105,26 +115,26 @@ describe('FinanceCustomerStatementsView', () => {
|
||||
vi.mocked(listPlatformTenants).mockResolvedValue({ records: [{ id: internalCustomerId, tenantName: '虫虫跨境科技', status: 'ACTIVE' } as never], page: 1, size: 200, total: 1, pages: 1 })
|
||||
const internalStatement = {
|
||||
...statement, customerId: internalCustomerId, customerName: `客户${internalCustomerId}`,
|
||||
createdBy: internalOperatorId, createdAt: '2026-08-14T03:27:34Z', updatedBy: internalOperatorId, updatedAt: '2026-08-14T04:28:35Z',
|
||||
createdBy: internalOperatorId, createdByName: '', createdAt: '2026-08-14T03:27:34Z', updatedBy: internalOperatorId, updatedByName: '', updatedAt: '2026-08-14T04:28:35Z',
|
||||
}
|
||||
const internalDetail = {
|
||||
...detail, ...internalStatement,
|
||||
paymentHistory: [{ ...detail.paymentHistory[0]!, createdBy: internalOperatorId, occurredAt: '2026-08-14T04:28:35Z' }],
|
||||
paymentHistory: [{ ...detail.paymentHistory[0]!, createdBy: internalOperatorId, createdByName: '', occurredAt: '2026-08-14T04:28:35Z' }],
|
||||
revisions: [{ ...detail.revisions[0]!, operatorId: internalOperatorId, operatorName: '' }],
|
||||
}
|
||||
vi.mocked(listFinanceStatements).mockResolvedValue([internalStatement])
|
||||
vi.mocked(getReconciliationStatement).mockResolvedValue(internalDetail)
|
||||
|
||||
const wrapper = await mountPage(['platform:finance:customer-statement:list', 'platform:customer:list', 'platform:finance:reconciliation:adjust'])
|
||||
const { wrapper } = await mountPage(['platform:finance:customer-statement:list', 'platform:customer:list', 'platform:finance:reconciliation:adjust'])
|
||||
await (wrapper.vm as unknown as { openDetail(row: FinanceStatementReadModel): Promise<void> }).openDetail(internalStatement)
|
||||
await flushPromises()
|
||||
|
||||
const text = document.body.textContent || ''
|
||||
expect(text).toContain('虫虫跨境科技')
|
||||
expect(document.querySelector('[aria-label="客户对账单列表"]')?.textContent).toContain('虫虫跨境科技')
|
||||
expect(document.querySelector('[aria-label="所选对账单联动明细"]')?.textContent).toContain('虫虫跨境科技')
|
||||
expect(document.querySelector('[aria-label="客户对账单详情"]')?.textContent).toContain('虫虫跨境科技')
|
||||
expect(text).not.toContain(`客户${internalCustomerId}`)
|
||||
expect(text).not.toContain(internalOperatorId)
|
||||
expect(text).toContain('操作人信息缺失')
|
||||
expect(text).toContain('名称数据缺失')
|
||||
expect(text).toContain('2026-08-14 03:27')
|
||||
expect(text).not.toContain('2026-08-14T03:27:34Z')
|
||||
|
||||
@@ -135,7 +145,7 @@ describe('FinanceCustomerStatementsView', () => {
|
||||
})
|
||||
|
||||
it('filters hydrated statements by status, bill number, container, period, settlement and payment date', async () => {
|
||||
const wrapper = await mountPage()
|
||||
const { wrapper } = await mountPage()
|
||||
const vm = wrapper.vm as unknown as { load(model: Record<string, unknown>): Promise<void>; filters: Record<string, unknown>; filteredRows: FinanceStatementReadModel[] }
|
||||
await vm.load({
|
||||
status: 'ISSUED', billNo: '501', customerId: '', containerNo: 'CONT-31',
|
||||
@@ -151,7 +161,7 @@ describe('FinanceCustomerStatementsView', () => {
|
||||
|
||||
it('hydrates and exports the currently filtered real statement rows as CSV', async () => {
|
||||
const clicked = vi.spyOn(HTMLAnchorElement.prototype, 'click').mockImplementation(() => {})
|
||||
const wrapper = await mountPage()
|
||||
const { wrapper } = await mountPage()
|
||||
await (wrapper.vm as unknown as { exportStatements(): Promise<void> }).exportStatements()
|
||||
expect(getReconciliationStatement).toHaveBeenCalledWith('501')
|
||||
expect(URL.createObjectURL).toHaveBeenCalledWith(expect.any(Blob))
|
||||
@@ -168,7 +178,7 @@ describe('FinanceCustomerStatementsView', () => {
|
||||
})
|
||||
|
||||
it('uses statement list permission for fee-source details without exposing technical trace fields', async () => {
|
||||
const wrapper = await mountPage(['platform:finance:customer-statement:list'])
|
||||
const { wrapper } = await mountPage(['platform:finance:customer-statement:list'])
|
||||
await (wrapper.vm as unknown as { openDetail(row: FinanceStatementReadModel): Promise<void> }).openDetail(statement)
|
||||
await flushPromises()
|
||||
expect(getFinanceFeeSource).toHaveBeenCalledWith('81')
|
||||
@@ -177,19 +187,20 @@ describe('FinanceCustomerStatementsView', () => {
|
||||
})
|
||||
|
||||
it('surfaces detail hydration failures instead of silently returning an empty filtered list', async () => {
|
||||
const wrapper = await mountPage()
|
||||
const { wrapper } = await mountPage()
|
||||
vi.mocked(getReconciliationStatement).mockRejectedValueOnce(new Error('detail unavailable'))
|
||||
await (wrapper.vm as unknown as { load(model: Record<string, unknown>): Promise<void> }).load({ status: 'ISSUED' })
|
||||
await flushPromises()
|
||||
expect(document.body.textContent).toContain('筛选字段加载失败')
|
||||
})
|
||||
|
||||
it('keeps the failed selected row visible and exposes a detail retry state', async () => {
|
||||
const wrapper = await mountPage()
|
||||
it('keeps the dedicated detail route visible and exposes a retry state', async () => {
|
||||
vi.mocked(getReconciliationStatement).mockRejectedValueOnce(new Error('detail unavailable'))
|
||||
await (wrapper.vm as unknown as { openDetail(row: FinanceStatementReadModel): Promise<void> }).openDetail(statement)
|
||||
const { router } = await mountPage(['platform:finance:customer-statement:list'], '/finance/customer-statements/501')
|
||||
await flushPromises()
|
||||
expect(document.body.textContent).toContain('已选择 1 项')
|
||||
expect(router.currentRoute.value.name).toBe('finance-customer-statement-detail')
|
||||
expect(document.body.textContent).toContain('客户对账单详情')
|
||||
expect(document.body.textContent).toContain('返回客户对账单')
|
||||
expect(document.body.textContent).toContain('明细加载失败')
|
||||
expect(document.body.textContent).toContain('重试')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user