This commit is contained in:
2026-08-19 09:14:35 +08:00
parent c188482c24
commit 80cfca5056
36 changed files with 854 additions and 1044 deletions
@@ -11,7 +11,7 @@ import { createPlatformExportTask } from '@/modules/downloads'
import { ApiResponseError } from '@/shared/api/http'
import { showExportTaskCreatedDialog } from '@/shared/components/exportTaskCreatedDialog'
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
import { createReconciliationStatement, getFinanceFeeSource, listFinanceFeeSources, reviewFinanceFeeSource, updateFinanceFeeSourceSettlementInclusion } from '../api/finance'
import { adjustFinanceFeeSource, createReconciliationStatement, editFinanceFeeSource, getFinanceFeeSource, listFinanceFeeSources, reviewFinanceFeeSource, submitFinanceFeeSource, voidFinanceFeeSource } from '../api/finance'
import type { FinanceFeeSourceDetail, FinanceFeeSourceSummary } from '../types'
import FinanceCustomerArapView from './FinanceCustomerArapView.vue'
@@ -22,7 +22,7 @@ vi.mock('../api/finance', () => ({
listFinanceFeeSources: vi.fn(),
getFinanceFeeSource: vi.fn(),
reviewFinanceFeeSource: vi.fn(),
updateFinanceFeeSourceSettlementInclusion: vi.fn(),
editFinanceFeeSource: vi.fn(), submitFinanceFeeSource: vi.fn(), adjustFinanceFeeSource: vi.fn(), voidFinanceFeeSource: vi.fn(),
createReconciliationStatement: vi.fn(),
}))
@@ -44,14 +44,14 @@ const detail: FinanceFeeSourceDetail = {
poolItemId: '42', feeSourceId: '42', direction: 'RECEIVABLE', sourceType: 'INBOUND_FEE', sourceId: '81', sourceFeeId: '81', sourceVersion: 3,
sourceSystem: 'FIRSTLEG', sourceBusinessId: 'IB-91',
customerId: '10001', customerName: '客户A', supplierId: null, supplierName: '', orderId: null, inboundId: '91', inboundCode: 'IB-91', containerId: null, containerNo: '',
businessDate: '2026-08-11', feeItemName: '运费', quantity: 2, unitPrice: 50, amount: 100, currency: 'CNY', reviewStatus: 'PENDING', settlementIncluded: false,
businessDate: '2026-08-11', feeItemName: '运费', quantity: 2, unitPrice: 50, amount: 100, currency: 'CNY', reviewStatus: 'PENDING',
sourceStatus: 'AVAILABLE', status: 'AVAILABLE', statementGenerated: false, operationVersion: 3, createdAt: '2026-08-11', operationLogs: [],
}
const feeSummary: FinanceFeeSourceSummary = {
poolItemId: '42', feeSourceId: '42', sourceSystem: 'FIRSTLEG', sourceType: 'INBOUND_FEE', sourceBusinessId: 'IB-91',
sourceId: '81', sourceVersion: 3, customerId: '10001', customerName: '客户A', direction: 'RECEIVABLE', supplierId: null,
supplierName: '', orderId: null, inboundId: '91', inboundCode: 'IB-91', containerId: null, containerNo: '', businessDate: '2026-08-11',
feeItemName: '运费', quantity: 2, unitPrice: 50, amount: 100, currency: 'CNY', reviewStatus: 'PENDING', settlementIncluded: false,
feeItemName: '运费', quantity: 2, unitPrice: 50, amount: 100, currency: 'CNY', reviewStatus: 'PENDING',
sourceStatus: 'AVAILABLE', statementGenerated: false, status: 'AVAILABLE', createdAt: '2026-08-11',
}
@@ -92,7 +92,10 @@ describe('FinanceCustomerArapView G2 operations', () => {
vi.mocked(listFinanceFeeSources).mockReset().mockResolvedValue({ records: [feeSummary], total: 1, pages: 1 })
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(editFinanceFeeSource).mockReset().mockResolvedValue({ ...detail, reviewStatus: 'DRAFT', operationVersion: 4 })
vi.mocked(submitFinanceFeeSource).mockReset().mockResolvedValue({ ...detail, reviewStatus: 'PENDING', operationVersion: 4 })
vi.mocked(adjustFinanceFeeSource).mockReset().mockResolvedValue({ ...detail, reviewStatus: 'DRAFT', operationVersion: 4 })
vi.mocked(voidFinanceFeeSource).mockReset().mockResolvedValue({ ...detail, sourceStatus: 'VOIDED', status: 'VOIDED', operationVersion: 4 })
vi.mocked(createReconciliationStatement).mockReset().mockResolvedValue({} as never)
vi.mocked(createPlatformExportTask).mockReset().mockResolvedValue({ id: 'exp-3', taskNo: 'EXP-3', taskName: '客户应收应付费用来源', status: 'PENDING' } as never)
vi.mocked(showExportTaskCreatedDialog).mockReset().mockResolvedValue(false)
@@ -109,7 +112,7 @@ describe('FinanceCustomerArapView G2 operations', () => {
await vm.openDetail({ id: 'RECEIVABLE:42', feeSourceId: '42' })
expect(getFinanceFeeSource).toHaveBeenCalledWith('42')
expect(reviewFinanceFeeSource).not.toHaveBeenCalled()
expect(updateFinanceFeeSourceSettlementInclusion).not.toHaveBeenCalled()
expect(editFinanceFeeSource).not.toHaveBeenCalled()
})
it('loads all customers by default and keeps explicit customer filtering', async () => {
@@ -136,17 +139,14 @@ describe('FinanceCustomerArapView G2 operations', () => {
expect(wrapper.text()).toContain('客户B')
})
it('loads detail and submits review and settlement changes with the returned version', async () => {
const wrapper = await mountView(['platform:finance:customer-arap:list', 'platform:finance:fee-source:review', 'platform:finance:fee-source:settlement'])
it('loads detail and submits review with the returned version', async () => {
const wrapper = await mountView(['platform:finance:customer-arap:list', 'platform:finance:fee-source:review'])
await loadRows(wrapper)
const vm = wrapper.vm as unknown as {
openDetail(row: Record<string, unknown>): Promise<void>
openReview(row: Record<string, unknown>): Promise<void>
submitReview(): Promise<void>
openInclusion(row: Record<string, unknown>): Promise<void>
submitInclusion(): Promise<void>
reviewForm: { action: 'APPROVE' | 'REJECT'; reason: string }
inclusionForm: { included: boolean; reason: string }
}
const row = { id: 'RECEIVABLE:42', feeSourceId: '42' }
@@ -158,9 +158,6 @@ describe('FinanceCustomerArapView G2 operations', () => {
await vm.openReview(row); vm.reviewForm.action = 'APPROVE'; vm.reviewForm.reason = '核对无误'; await vm.submitReview()
expect(reviewFinanceFeeSource).toHaveBeenCalledWith('42', { action: 'APPROVE', reason: '核对无误', expectedVersion: 3 })
await vm.openInclusion(row); vm.inclusionForm.included = true; vm.inclusionForm.reason = '本期结算'; await vm.submitInclusion()
expect(updateFinanceFeeSourceSettlementInclusion).toHaveBeenCalledWith('42', { included: true, reason: '本期结算', expectedVersion: 3 })
})
it('renders request and trace ids when detail loading fails', async () => {
@@ -211,8 +208,8 @@ describe('FinanceCustomerArapView G2 operations', () => {
it('renders all permitted fee-source row operations as small plain primary buttons with icons', () => {
const page = readFileSync(resolve(process.cwd(), 'src/modules/finance/pages/FinanceCustomerArapView.vue'), 'utf8')
expect(page).toContain('<el-button v-if="canViewSource" size="small" plain type="primary" @click="openDetail(row)"><Eye :size="15" />详情</el-button>')
expect(page).toContain('<el-button v-if="canReviewSource" size="small" plain type="primary" @click="openReview(row)"><FileCheck2 :size="15" />审核</el-button>')
expect(page).toContain('<el-button v-if="canSettleSource" size="small" plain type="primary" @click="openInclusion(row)"><Link2 :size="15" />参与结算</el-button>')
expect(page).toContain("@click=\"openMutation(row, 'SUBMIT')\"")
expect(page).toContain("@click=\"openMutation(row, 'ADJUST')\"")
expect(page).not.toMatch(/<template #actions[\s\S]*?<el-button[^>]*\blink\b/)
expect(page).toContain('white-space: nowrap')
})
@@ -263,7 +260,7 @@ describe('FinanceCustomerArapView G2 operations', () => {
generationForm: { mode: 'BY_CUTOFF' | 'SELECTED'; customerId: string; period: string[]; remark: string }
submitGeneration(): Promise<void>
}
vm.selectedRows = [{ id: 'RECEIVABLE:42', feeSourceId: '42', customerId: '10001', direction: 'RECEIVABLE', reviewStatus: 'APPROVED', settlementIncluded: true, statementGenerated: false, amount: 100 }]
vm.selectedRows = [{ id: 'RECEIVABLE:42', feeSourceId: '42', customerId: '10001', direction: 'RECEIVABLE', reviewStatus: 'APPROVED', status: 'AVAILABLE', 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: '八月费用' })
@@ -273,14 +270,14 @@ describe('FinanceCustomerArapView G2 operations', () => {
const wrapper = await mountView(['platform:finance:customer-arap:list', 'platform:finance:customer-arap:export', 'platform:download:list'])
const vm = wrapper.vm as unknown as { filters: Record<string, unknown>; exportFeeSources(): Promise<void> }
Object.assign(vm.filters, {
customerId: '10001', direction: 'RECEIVABLE', reviewStatus: 'APPROVED', settlementIncluded: 'true',
customerId: '10001', direction: 'RECEIVABLE', reviewStatus: 'APPROVED',
statementGenerated: 'false', businessRange: ['2026-08-01', '2026-08-31'], keyword: ' 运费 ',
})
await vm.exportFeeSources()
expect(createPlatformExportTask).toHaveBeenCalledWith({
exportType: 'FINANCE_FEE_SOURCE_LIST', format: 'XLSX', taskName: '客户应收应付费用来源',
parameters: {
customerId: '10001', direction: 'RECEIVABLE', reviewStatus: 'APPROVED', settlementIncluded: true,
customerId: '10001', direction: 'RECEIVABLE', reviewStatus: 'APPROVED',
statementGenerated: false, businessDateFrom: '2026-08-01', businessDateTo: '2026-08-31', keyword: '运费',
},
})