diff --git a/src/modules/finance/api/finance.test.ts b/src/modules/finance/api/finance.test.ts index 9cc5e3c0..075bc006 100644 --- a/src/modules/finance/api/finance.test.ts +++ b/src/modules/finance/api/finance.test.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { http } from '@/shared/api/http' -import { actionReconciliationStatement, adjustFinanceAccount, adjustFinanceBill, createFinanceCost, createFinanceReceipt, createFinanceSettlementPayment, createPayableSettlement, createReceivableSettlement, createReconciliationStatement, downloadFinanceRechargeVoucher, editFinanceFeeSource, getFinanceBill, getFinanceCustomerAccount, getFinanceCustomerAccountSummary, getFinanceCustomerTerms, getFinanceFeeSource, getFinanceSettlement, getReceivableStatistics, getReconciliationStatement, listFinanceAccountLedger, listFinanceAccounts, listFinanceBillRevisions, listFinanceFeePool, listFinanceFeeSources, listFinancePayableSettlements, listFinanceReceivableSettlements, listFinanceRechargeRequests, listFinanceStatements, reviewFinanceFeeSource, reviewFinanceRechargeRequest, updateFinanceBill, updateFinanceCustomerTerms, uploadFinanceVoucher, voidFinanceBill } from './finance' +import { actionReconciliationStatement, adjustFinanceAccount, adjustFinanceBill, createFinanceCost, createFinanceReceipt, createFinanceSettlementPayment, createPayableSettlement, createReceivableSettlement, createReconciliationStatement, downloadFinanceRechargeVoucher, editFinanceFeeSource, getFinanceBill, getFinanceCustomerAccount, getFinanceCustomerAccountSummary, getFinanceCustomerTerms, getFinanceFeeSource, getFinanceSettlement, getReceivableStatistics, getReconciliationStatement, listFinanceAccountLedger, listFinanceAccounts, listFinanceBillRevisions, listFinanceFeePool, listFinanceFeeSources, listFinancePayableSettlements, listFinanceReceivableSettlements, listFinanceRechargeRequests, listFinanceStatements, reviewFinanceFeeSource, reviewFinanceRechargeRequest, updateFinanceBill, updateFinanceCreditLimit, updateFinanceCustomerTerms, uploadFinanceVoucher, voidFinanceBill } from './finance' describe('finance api', () => { beforeEach(() => { @@ -79,12 +79,12 @@ describe('finance api', () => { }) it('uses the formal G1 account list path and fixed page DTO', async () => { - const get = vi.spyOn(http, 'get').mockResolvedValue({ code: '200', message: 'success', requestId: 'req-g1', traceId: 'trace-g1', data: [{ customerId: 10001, customerCode: 'C-10001', customerName: '客户A', contactName: '张三', contactPhone: '13800000000', outstandingReceivable: 300, pendingBilling: 50, receivedInPeriod: 100, totalCost: 260, paidCost: 180, outstandingPayable: 80, status: 'ACTIVE', lastChangedAt: '2026-08-11T10:00:00', actualBalance: '126580.50', frozenBalance: '8500.00', availableBalance: '118080.50', accountVersion: 7, pendingRechargeCount: 2 }], page: { page: 2, pageSize: 20, total: 21 } }) + const get = vi.spyOn(http, 'get').mockResolvedValue({ code: '200', message: 'success', requestId: 'req-g1', traceId: 'trace-g1', data: [{ customerId: 10001, customerCode: 'C-10001', customerName: '客户A', contactName: '张三', contactPhone: '13800000000', outstandingReceivable: 300, pendingBilling: 50, receivedInPeriod: 100, totalCost: 260, paidCost: 180, outstandingPayable: 80, status: 'ACTIVE', lastChangedAt: '2026-08-11T10:00:00', actualBalance: '126580.50', frozenBalance: '8500.00', availableBalance: '138080.50', creditLimit: '20000.00', creditUsed: '0.00', availableCredit: '20000.00', accountVersion: 7, pendingRechargeCount: 2 }], page: { page: 2, pageSize: 20, total: 21 } }) const result = await listFinanceAccounts({ page: 2, size: 20, customerId: '10001', status: 'ACTIVE' }) expect(get).toHaveBeenCalledWith('/platform/finance/v1/accounts', { params: { page: 2, pageSize: 20, customerId: '10001', status: 'ACTIVE' } }) - expect(result).toEqual({ records: [{ customerId: '10001', customerCode: 'C-10001', customerName: '客户A', contactName: '张三', contactPhone: '13800000000', outstandingReceivable: 300, pendingBilling: 50, receivedInPeriod: 100, totalCost: 260, paidCost: 180, outstandingPayable: 80, status: 'ACTIVE', lastChangedAt: '2026-08-11T10:00:00', actualBalance: '126580.50', frozenBalance: '8500.00', availableBalance: '118080.50', accountVersion: 7, pendingRechargeCount: 2 }], total: 21, pages: 2 }) + expect(result).toEqual({ records: [{ customerId: '10001', customerCode: 'C-10001', customerName: '客户A', contactName: '张三', contactPhone: '13800000000', outstandingReceivable: 300, pendingBilling: 50, receivedInPeriod: 100, totalCost: 260, paidCost: 180, outstandingPayable: 80, status: 'ACTIVE', lastChangedAt: '2026-08-11T10:00:00', actualBalance: '126580.50', frozenBalance: '8500.00', availableBalance: '138080.50', creditLimit: '20000.00', creditUsed: '0.00', availableCredit: '20000.00', accountVersion: 7, pendingRechargeCount: 2 }], total: 21, pages: 2 }) }) it('uses the formal G1 account ledger path and fixed page DTO', async () => { @@ -113,6 +113,15 @@ describe('finance api', () => { expect(ledger).toMatchObject({ ledgerId: 101, businessNo: 'AC-101', changeType: 'DEDUCTION', amount: '-100.00' }) }) + it('updates the customer credit limit with account version and idempotency', async () => { + const put = vi.spyOn(http, 'put').mockResolvedValue({ code: '200', message: 'ok', data: { ledgerId: 102, businessNo: 'AC-102', changeType: 'CREDIT_LIMIT', amount: '20000.00', creditLimitAfter: '20000.00', creditUsedAfter: '0.00', availableCreditAfter: '20000.00' } }) + + const result = await updateFinanceCreditLimit('10001', { creditLimit: '20000.00', remark: '初始化授信', expectedAccountVersion: 7 }) + + expect(put).toHaveBeenCalledWith('/platform/finance/v1/accounts/10001/credit-limit', { creditLimit: '20000.00', remark: '初始化授信', expectedAccountVersion: 7 }, { headers: { 'Idempotency-Key': expect.any(String) } }) + expect(result).toMatchObject({ ledgerId: 102, changeType: 'CREDIT_LIMIT', creditLimitAfter: '20000.00', availableCreditAfter: '20000.00' }) + }) + it('downloads a recharge voucher as an authenticated blob', async () => { const blob = new Blob(['image'], { type: 'image/png' }) const get = vi.spyOn(http, 'get').mockResolvedValue(blob) diff --git a/src/modules/finance/api/finance.ts b/src/modules/finance/api/finance.ts index 7371f2c2..82171a66 100644 --- a/src/modules/finance/api/finance.ts +++ b/src/modules/finance/api/finance.ts @@ -140,7 +140,8 @@ function normalizeFinanceAccount(item: RawResource): FinanceAccountSummary { pendingBilling: numberValue(item.pendingBilling), receivedInPeriod: numberValue(item.receivedInPeriod), totalCost: numberValue(item.totalCost), paidCost: numberValue(item.paidCost), outstandingPayable: numberValue(item.outstandingPayable), status: stringValue(item.status), lastChangedAt: stringValue(item.lastChangedAt), actualBalance: stringValue(item.actualBalance), frozenBalance: stringValue(item.frozenBalance), - availableBalance: stringValue(item.availableBalance), accountVersion: numberValue(item.accountVersion), pendingRechargeCount: numberValue(item.pendingRechargeCount), + availableBalance: stringValue(item.availableBalance), creditLimit: stringValue(item.creditLimit), creditUsed: stringValue(item.creditUsed), + availableCredit: stringValue(item.availableCredit), accountVersion: numberValue(item.accountVersion), pendingRechargeCount: numberValue(item.pendingRechargeCount), } } @@ -156,6 +157,8 @@ export async function listFinanceAccountLedger(customerId: string, params: { pag businessNo: nullableStringValue(item.businessNo), changeType: nullableStringValue(item.changeType), amount: nullableStringValue(item.amount), actualBalanceAfter: nullableStringValue(item.actualBalanceAfter), frozenBalanceAfter: nullableStringValue(item.frozenBalanceAfter), availableBalanceAfter: nullableStringValue(item.availableBalanceAfter), sourceType: nullableStringValue(item.sourceType), + creditLimitAfter: nullableStringValue(item.creditLimitAfter), creditUsedAfter: nullableStringValue(item.creditUsedAfter), + availableCreditAfter: nullableStringValue(item.availableCreditAfter), sourceNo: nullableStringValue(item.sourceNo), occurredDate: nullableStringValue(item.occurredDate), paymentMethod: nullableStringValue(item.paymentMethod), referenceNo: nullableStringValue(item.referenceNo), createdAt: nullableStringValue(item.createdAt), @@ -198,6 +201,11 @@ export async function adjustFinanceAccount(customerId: string, payload: FinanceA return unwrapApiResponse(response) } +export async function updateFinanceCreditLimit(customerId: string, payload: { creditLimit: string; remark?: string; expectedAccountVersion: number }) { + const response = await http.put, ApiEnvelope>(`${basePath}/accounts/${customerId}/credit-limit`, payload, mutationHeaders('credit-limit')) + return unwrapApiResponse(response) +} + export async function listFinanceFeeSources(params: { page?: number size?: number diff --git a/src/modules/finance/pages/FinanceCustomerAccountsView.test.ts b/src/modules/finance/pages/FinanceCustomerAccountsView.test.ts index 0f1be3c0..1d326cb8 100644 --- a/src/modules/finance/pages/FinanceCustomerAccountsView.test.ts +++ b/src/modules/finance/pages/FinanceCustomerAccountsView.test.ts @@ -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('