feat(finance): manage customer credit limits

This commit is contained in:
2026-09-05 11:54:55 +08:00
parent fb4cab6a0e
commit 92b24cc9a6
5 changed files with 92 additions and 24 deletions
@@ -10,7 +10,7 @@ import { useAuthStore } from '@/core/auth/auth'
import { createPlatformExportTask } from '@/modules/downloads'
import { listPlatformTenants, type PlatformTenantResource } from '@/modules/iam'
import { showExportTaskCreatedDialog } from '@/shared/components/exportTaskCreatedDialog'
import { adjustFinanceAccount, downloadFinanceRechargeVoucher, listFinanceAccountLedger, listFinanceAccounts, listFinanceRechargeRequests, reviewFinanceRechargeRequest } from '../api/finance'
import { adjustFinanceAccount, downloadFinanceRechargeVoucher, listFinanceAccountLedger, listFinanceAccounts, listFinanceRechargeRequests, reviewFinanceRechargeRequest, updateFinanceCreditLimit } from '../api/finance'
import FinanceCustomerAccountsView from './FinanceCustomerAccountsView.vue'
vi.mock('../api/finance', () => ({
@@ -19,6 +19,7 @@ vi.mock('../api/finance', () => ({
listFinanceRechargeRequests: vi.fn(),
reviewFinanceRechargeRequest: vi.fn(),
adjustFinanceAccount: vi.fn(),
updateFinanceCreditLimit: vi.fn(),
downloadFinanceRechargeVoucher: vi.fn(),
getFinanceCustomerTerms: vi.fn(),
updateFinanceCustomerTerms: vi.fn(),
@@ -37,7 +38,7 @@ function memoryStorage(): Storage {
const accountFixture = {
customerId: '10001', customerCode: 'C-001', customerName: '客户A', contactName: '张三', contactPhone: '13800000000',
outstandingReceivable: 100, pendingBilling: 20, receivedInPeriod: 30, totalCost: 90, paidCost: 50, outstandingPayable: 40,
actualBalance: '126580.50', frozenBalance: '8500.00', availableBalance: '118080.50', accountVersion: 7, pendingRechargeCount: 1,
actualBalance: '126580.50', frozenBalance: '8500.00', availableBalance: '138080.50', creditLimit: '20000.00', creditUsed: '0.00', availableCredit: '20000.00', accountVersion: 7, pendingRechargeCount: 1,
status: 'ACTIVE', lastChangedAt: '2026-08-13 10:00:00',
}
@@ -65,8 +66,7 @@ describe('FinanceCustomerAccountsView delivery', () => {
expect(page).toContain('返回客户账户')
expect(page).toContain('<DetailPageLayout')
expect(page).toContain('class="account-metrics"')
for (const metric of ['实际余额', '冻结金额', '可用余额']) expect(page).toContain(metric)
expect(page.match(/class="account-metric /g)).toHaveLength(3)
for (const metric of ['实际余额', '冻结金额', '总可用余额', '授信额度', '已用授信', '可用授信']) expect(page).toContain(metric)
expect(page).toContain('class="ledger-query"')
expect(page).toContain('@search="ledgerPage = 1; void loadLedger($event)"')
expect(page).toContain('class="ledger-table"')
@@ -84,7 +84,7 @@ describe('FinanceCustomerAccountsView delivery', () => {
it('keeps term editing aligned with the account summary and nearby actions', () => {
for (const marker of ['class="term-section"', 'class="term-editor"', '账期天数', '#footer', '保存账期']) expect(page).toContain(marker)
expect(page).toContain('grid-template-columns: repeat(3, minmax(0, 1fr))')
for (const column of ['实际余额(CNY)', '冻结金额(CNY)', '可用余额(CNY)', '待审核充值']) expect(page).toContain(column)
for (const column of ['实际余额(CNY)', '冻结金额(CNY)', '总可用余额(CNY)', '授信额度(CNY)', '已用授信(CNY)', '待审核充值']) expect(page).toContain(column)
expect(page).toContain('grid-template-columns: 120px minmax(0, 220px)')
expect(page).toContain('justify-content: start')
expect(page).toContain('class="surface-panel finance-account-detail__surface"')
@@ -106,7 +106,7 @@ describe('FinanceCustomerAccountsView delivery', () => {
})
it('renders every account row operation as a small plain primary button with an icon', () => {
expect(page).toContain("{ key: 'actions', label: '操作', width: 310, fixed: 'left', slotName: 'actions' }")
expect(page).toContain("{ key: 'actions', label: '操作', width: 390, fixed: 'left', slotName: 'actions' }")
expect(page).toContain('<el-button size="small" plain type="primary" @click="openAccount(row, false)"><Eye :size="15" />详情</el-button>')
expect(page).toContain("openAdjustment(row as FinanceAccountSummary, 'RECHARGE')")
expect(page).toContain("openAdjustment(row as FinanceAccountSummary, 'DEDUCTION')")
@@ -117,7 +117,7 @@ describe('FinanceCustomerAccountsView delivery', () => {
})
it('shows authoritative CNY wallet balances and independently permitted actions', () => {
for (const marker of ['实际余额', '冻结金额', '可用余额', '手工充值', '手工扣款', 'platform:finance:customer-account:adjust', 'platform:finance:recharge:review']) expect(page).toContain(marker)
for (const marker of ['实际余额', '冻结金额', '总可用余额', '授信额度', '已用授信', '可用授信', '手工充值', '手工扣款', '设置授信', 'platform:finance:customer-account:adjust', 'platform:finance:recharge:review']) expect(page).toContain(marker)
expect(page).not.toContain('operatingFundsSummary')
expect(page).not.toContain('adjustmentAccount?.actualBalance -')
})
@@ -149,6 +149,7 @@ describe('FinanceCustomerAccountsView delivery', () => {
vi.mocked(listFinanceRechargeRequests).mockReset().mockResolvedValue({ records: [{ requestId: '91', requestNo: 'RC-91', amount: '500.00', paymentMethod: 'WECHAT', voucherFileId: '81', voucherFileName: 'receipt.png', status: 'PENDING', version: 3, submittedByName: '客户A', reviewedByName: '', reviewRemark: '', createdAt: '2026-09-04T10:00:00', reviewedAt: '' }], total: 1, pages: 1 })
vi.mocked(reviewFinanceRechargeRequest).mockReset().mockResolvedValue({ requestId: '91', requestNo: 'RC-91', amount: '500.00', paymentMethod: 'WECHAT', voucherFileId: '81', voucherFileName: 'receipt.png', status: 'APPROVED', version: 4, submittedByName: '客户A', reviewedByName: '财务A', reviewRemark: '', createdAt: '2026-09-04T10:00:00', reviewedAt: '2026-09-05T10:00:00' })
vi.mocked(adjustFinanceAccount).mockReset().mockResolvedValue({ ...accountFixture, actualBalance: '127080.50', availableBalance: '118580.50', accountVersion: 8 })
vi.mocked(updateFinanceCreditLimit).mockReset().mockResolvedValue({ ledgerId: '102', businessNo: 'AC-102', changeType: 'CREDIT_LIMIT', amount: '20000.00', actualBalanceAfter: '126580.50', frozenBalanceAfter: '8500.00', availableBalanceAfter: '138080.50', creditLimitAfter: '20000.00', creditUsedAfter: '0.00', availableCreditAfter: '20000.00', sourceType: 'CREDIT_LIMIT_ADJUSTMENT', sourceNo: '', occurredAt: '', occurredDate: '2026-09-05', paymentMethod: '', referenceNo: '', operatorName: '财务A', remark: '', createdAt: '2026-09-05T10:00:00' })
vi.mocked(downloadFinanceRechargeVoucher).mockReset().mockResolvedValue(new Blob(['image'], { type: 'image/png' }))
vi.mocked(listPlatformTenants).mockReset().mockResolvedValue({ records: [{ id: '10001', tenantName: '客户A', status: 'ACTIVE' } as PlatformTenantResource], page: 1, size: 200, total: 1, pages: 1 })
vi.mocked(createPlatformExportTask).mockReset().mockResolvedValue({ id: 'exp-1', taskNo: 'EXP-1', taskName: '客户账户', status: 'PENDING' } as never)
@@ -172,10 +173,13 @@ describe('FinanceCustomerAccountsView delivery', () => {
expect(wrapper.text()).toContain('张三')
expect(wrapper.text()).toContain('13800000000')
const metrics = wrapper.find('.account-metrics')
expect(metrics.findAll('.account-metric')).toHaveLength(3)
expect(metrics.findAll('.account-metric')).toHaveLength(6)
expect(metrics.text()).toContain('实际余额¥126580.50CNY')
expect(metrics.text()).toContain('冻结金额¥8500.00CNY')
expect(metrics.text()).toContain('可用余额¥118080.50CNY')
expect(metrics.text()).toContain('总可用余额¥138080.50CNY')
expect(metrics.text()).toContain('授信额度¥20000.00CNY')
expect(metrics.text()).toContain('已用授信¥0.00CNY')
expect(metrics.text()).toContain('可用授信¥20000.00CNY')
for (const auxiliary of ['待开账金额', '本期已收', '总成本', '已付成本', '已收客户款', '客户线下支付']) expect(metrics.text()).not.toContain(auxiliary)
expect(listFinanceAccounts).toHaveBeenCalledWith({ page: 1, size: 1, customerId: '10001' })
expect(listFinanceAccountLedger).toHaveBeenCalledWith('10001', expect.objectContaining({ page: 1, size: 20 }))