feat: 完善 Transaction 和 Reconciliation API 客户端

Transaction API:
- 新增 deposit() 充值方法
- 新增 withdraw() 提现方法
- 修正 transfer() 端点路径为 /transactions/transfer
- 添加 DepositRequest 和 WithdrawRequest 类型
- 标记需要后端扩展的方法

Reconciliation API:
- 新增 approveAdjustment() 审批补录方法
- 新增 rejectAdjustment() 拒绝补录方法
- 新增 getPendingAdjustments() 获取待审批列表
- 修正 runReconciliation() 端点路径为 /reconciliation/run
- 完善类型定义 (ReconciliationBatch, ManualAdjustment 等)
- 添加状态标签映射

现在前端 API 覆盖率:
- Transaction: 100% (5/5 端点)
- Reconciliation: 100% (8/8 端点)
This commit is contained in:
tangweijie 2026-01-05 18:17:11 +08:00
parent ae2653f9dd
commit 4086cc00de
2 changed files with 427 additions and 122 deletions

View File

@ -1,131 +1,312 @@
// 对账相关API服务 /**
* API
*
*/
import { apiClient } from './client' import { apiClient } from './client'
import type { PageResponse } from '@/types/api'
/**
*
*/
export interface ReconciliationBatch { export interface ReconciliationBatch {
id: number id: number
batchNo: string batch_no: string
reconDate: string physical_account_id: number
status: string recon_date: string
totalTransactions: number total_count: number
matchedCount: number matched_count: number
mismatchedCount: number mismatch_count: number
bankTotal: number status: ReconciliationStatus
ledgerTotal: number bank_total?: string
inTransitNet: number transit_net?: string
threeAccountBalanced: boolean ledger_total?: string
difference: number three_account_balanced?: boolean
createdAt: string created_at: string
completedAt?: string completed_at?: string
} }
/**
*
*/
export type ReconciliationStatus = 'processing' | 'completed' | 'need_review'
/**
*
*/
export interface ReconciliationItem { export interface ReconciliationItem {
id: number id: number
batchId: number batch_id: number
transactionId?: number system_txn_no?: string
bankRefNo?: string bank_ref_no?: string
txnType: string system_amount?: string
amount: number bank_amount?: string
bankAmount?: number diff_amount: string
ledgerAmount?: number status: ReconciliationItemStatus
matchStatus: 'matched' | 'mismatched' | 'bank_only' | 'ledger_only' remark?: string
matchTime?: string created_at: string
remarks?: string
} }
/**
*
*/
export type ReconciliationItemStatus =
| 'matched' // 已匹配
| 'system_unreached' // 系统未达
| 'bank_unreached' // 银行未达
| 'amount_mismatch' // 金额不匹配
| 'adjusted' // 已调整
/**
*
*/
export interface ThreeAccountResult { export interface ThreeAccountResult {
bankBalance: number physical_account_id: number
transitNet: number bank_balance: string
ledgerTotal: number transit_net: string
isBalanced: boolean ledger_total: string
difference: number expected_total: string
difference: string
is_balanced: boolean
verified_at: string
}
/**
*
*/
export interface ManualAdjustment {
id: number
adjustment_no: string
related_txn_no?: string
reconciliation_item_id?: number
adjustment_type: AdjustmentType
account_id: number
amount: string
reason: string
operator: string
approver?: string
status: AdjustmentStatus
created_at: string
approved_at?: string
}
/**
*
*/
export type AdjustmentType = 'add' | 'reverse' | 'modify'
/**
*
*/
export type AdjustmentStatus = 'pending' | 'approved' | 'rejected'
/**
*
*/
export interface CreateReconciliationRequest {
physical_account_id: number
recon_date: string
}
/**
*
*/
export interface CreateAdjustmentRequest {
related_txn_no?: string
reconciliation_item_id?: number
adjustment_type: AdjustmentType
account_id: number
amount: string
reason: string
} }
export class ReconciliationAPI { export class ReconciliationAPI {
// 获取对账批次列表 // ==================== 核心对账功能 (已对接后端) ====================
static async getBatches(params?: {
status?: string /**
startDate?: string *
endDate?: string * 对应后端: POST /api/v1/reconciliation/run
page?: number */
pageSize?: number static async runReconciliation(data: CreateReconciliationRequest): Promise<ReconciliationBatch> {
}): Promise<{ return apiClient.post('/reconciliation/run', data)
items: ReconciliationBatch[]
total: number
}> {
return apiClient.get('/reconciliation/batches', params)
} }
// 获取对账批次详情 /**
*
* 对应后端: GET /api/v1/reconciliation/batches/:id
*/
static async getBatch(id: number): Promise<ReconciliationBatch> { static async getBatch(id: number): Promise<ReconciliationBatch> {
return apiClient.get(`/reconciliation/batches/${id}`) return apiClient.get(`/reconciliation/batches/${id}`)
} }
// 创建对账批次 /**
static async createBatch(data: { *
reconDate: string * 对应后端: GET /api/v1/reconciliation/batches/:id/items
accountIds?: number[] */
}): Promise<ReconciliationBatch> {
return apiClient.post('/reconciliation/batches', data)
}
// 执行对账
static async executeBatch(id: number): Promise<ReconciliationBatch> {
return apiClient.post(`/reconciliation/batches/${id}/execute`)
}
// 获取对账明细
static async getBatchItems(batchId: number, params?: { static async getBatchItems(batchId: number, params?: {
matchStatus?: string status?: ReconciliationItemStatus
page?: number page?: number
pageSize?: number page_size?: number
}): Promise<{ }): Promise<PageResponse<ReconciliationItem>> {
items: ReconciliationItem[] return apiClient.get(`/reconciliation/batches/${batchId}/items`, { params })
total: number
}> {
return apiClient.get(`/reconciliation/batches/${batchId}/items`, params)
} }
// 三账校验 /**
*
* 对应后端: GET /api/v1/reconciliation/three-account/:account_id
*/
static async verifyThreeAccounts(accountId: number): Promise<ThreeAccountResult> { static async verifyThreeAccounts(accountId: number): Promise<ThreeAccountResult> {
return apiClient.get(`/reconciliation/three-account/${accountId}`) return apiClient.get(`/reconciliation/three-account/${accountId}`)
} }
// 手工调整 /**
static async createAdjustment(data: { *
batchId: number * 对应后端: POST /api/v1/reconciliation/adjustments
transactionId?: number */
adjustmentType: string static async createAdjustment(data: CreateAdjustmentRequest): Promise<ManualAdjustment> {
amount: number
reason: string
operator: string
}): Promise<ReconciliationItem> {
return apiClient.post('/reconciliation/adjustments', data) return apiClient.post('/reconciliation/adjustments', data)
} }
/**
*
* 对应后端: POST /api/v1/reconciliation/adjustments/:id/approve
*/
static async approveAdjustment(id: number, approver: string): Promise<ManualAdjustment> {
return apiClient.post(`/reconciliation/adjustments/${id}/approve`, { approver })
}
/**
*
* 对应后端: POST /api/v1/reconciliation/adjustments/:id/reject
*/
static async rejectAdjustment(id: number, approver: string, reason: string): Promise<ManualAdjustment> {
return apiClient.post(`/reconciliation/adjustments/${id}/reject`, { approver, reason })
}
/**
*
* 对应后端: GET /api/v1/reconciliation/adjustments/pending
*/
static async getPendingAdjustments(): Promise<ManualAdjustment[]> {
return apiClient.get('/reconciliation/adjustments/pending')
}
// ==================== 扩展功能 (需要后端支持) ====================
/**
* ()
* @todo
*/
static async getBatches(params?: {
status?: ReconciliationStatus
physical_account_id?: number
start_date?: string
end_date?: string
page?: number
page_size?: number
}): Promise<PageResponse<ReconciliationBatch>> {
return apiClient.get('/reconciliation/batches', { params })
}
// 获取调整记录 /**
static async getAdjustments(batchId: number): Promise<ReconciliationItem[]> { * ()
* @todo
*/
static async getAdjustments(batchId: number): Promise<ManualAdjustment[]> {
return apiClient.get(`/reconciliation/batches/${batchId}/adjustments`) return apiClient.get(`/reconciliation/batches/${batchId}/adjustments`)
} }
// 对账统计 /**
* ()
* @todo
*/
static async getStats(params: { static async getStats(params: {
startDate?: string start_date?: string
endDate?: string end_date?: string
accountId?: number physical_account_id?: number
}): Promise<{ }): Promise<{
totalBatches: number total_batches: number
successRate: number success_rate: number
totalDiscrepancy: number total_discrepancy: string
largestDiscrepancy: number largest_discrepancy: string
}> { }> {
return apiClient.get('/reconciliation/stats', params) return apiClient.get('/reconciliation/stats', { params })
} }
// 导出对账报告 /**
* ()
* @todo
*/
static async exportReport(batchId: number, format: 'excel' | 'pdf' = 'excel'): Promise<Blob> { static async exportReport(batchId: number, format: 'excel' | 'pdf' = 'excel'): Promise<Blob> {
return apiClient.get(`/reconciliation/batches/${batchId}/export`, return apiClient.get(`/reconciliation/batches/${batchId}/export`, {
{ format }, params: { format },
{ responseType: 'blob' } responseType: 'blob'
) })
}
// ==================== 兼容旧方法 ====================
/**
* @deprecated 使 runReconciliation
*/
static async createBatch(data: {
reconDate: string
accountIds?: number[]
}): Promise<ReconciliationBatch> {
return this.runReconciliation({
physical_account_id: data.accountIds?.[0] || 0,
recon_date: data.reconDate,
})
}
/**
* @deprecated 使 runReconciliation
*/
static async executeBatch(id: number): Promise<ReconciliationBatch> {
// 获取批次信息后重新执行对账
const batch = await this.getBatch(id)
return this.runReconciliation({
physical_account_id: batch.physical_account_id,
recon_date: batch.recon_date,
})
} }
} }
/**
*
*/
export const ReconciliationStatusLabels: Record<ReconciliationStatus, string> = {
processing: '处理中',
completed: '已完成',
need_review: '需要审核',
}
/**
*
*/
export const ReconciliationItemStatusLabels: Record<ReconciliationItemStatus, string> = {
matched: '已匹配',
system_unreached: '系统未达',
bank_unreached: '银行未达',
amount_mismatch: '金额不匹配',
adjusted: '已调整',
}
/**
*
*/
export const AdjustmentTypeLabels: Record<AdjustmentType, string> = {
add: '补录',
reverse: '冲销',
modify: '修改',
}
/**
*
*/
export const AdjustmentStatusLabels: Record<AdjustmentStatus, string> = {
pending: '待审批',
approved: '已审批',
rejected: '已拒绝',
}
export default ReconciliationAPI

View File

@ -1,4 +1,7 @@
// 交易相关API服务 /**
* API
*
*/
import { apiClient } from './client' import { apiClient } from './client'
import type { import type {
SystemTransaction, SystemTransaction,
@ -9,54 +12,166 @@ import type {
TransferRequest, TransferRequest,
TransferResponse TransferResponse
} from '@/types/transaction' } from '@/types/transaction'
import type { PageResponse } from '@/types/api'
/**
*
*/
export interface DepositRequest {
account_id: number
amount: string
source_key?: string
remark?: string
}
/**
*
*/
export interface WithdrawRequest {
account_id: number
amount: string
remark?: string
}
export class TransactionAPI { export class TransactionAPI {
// 获取交易详情 // ==================== 核心交易操作 ====================
/**
*
* 对应后端: POST /api/v1/transactions/transfer
*/
static async transfer(data: TransferRequest): Promise<SystemTransaction> {
return apiClient.post('/transactions/transfer', data)
}
/**
* ()
* 对应后端: POST /api/v1/transactions/deposit
*/
static async deposit(data: DepositRequest): Promise<SystemTransaction> {
return apiClient.post('/transactions/deposit', data)
}
/**
* ()
* 对应后端: POST /api/v1/transactions/withdraw
*/
static async withdraw(data: WithdrawRequest): Promise<SystemTransaction> {
return apiClient.post('/transactions/withdraw', data)
}
// ==================== 交易查询 ====================
/**
*
* 对应后端: GET /api/v1/transactions/:id
*/
static async getTransaction(id: number): Promise<SystemTransaction> { static async getTransaction(id: number): Promise<SystemTransaction> {
return apiClient.get(`/transactions/${id}`) return apiClient.get(`/transactions/${id}`)
} }
// 根据交易号获取交易 /**
static async getTransactionByNo(txnNo: string): Promise<SystemTransaction> { *
return apiClient.get(`/transactions/by-no/${txnNo}`) * 对应后端: GET /api/v1/transactions
*/
static async getTransactions(query: TransactionQuery = {}): Promise<PageResponse<SystemTransaction>> {
return apiClient.get('/transactions', { params: query })
} }
// 获取交易列表 // ==================== 兼容旧方法 (保持向后兼容) ====================
/**
* @deprecated 使 getTransactions
*/
static async getTransactionList(query: TransactionQuery = {}): Promise<TransactionListResponse> { static async getTransactionList(query: TransactionQuery = {}): Promise<TransactionListResponse> {
return apiClient.get('/transactions', query) return apiClient.get('/transactions', { params: query })
} }
// 创建交易 /**
* ()
* 注意: 当前后端未提供此端点使 getTransactions + filter
*/
static async getTransactionByNo(txnNo: string): Promise<SystemTransaction> {
const result = await this.getTransactions({ txn_no: txnNo })
if (result.data && result.data.length > 0) {
return result.data[0]
}
throw new Error('交易不存在')
}
// ==================== 扩展功能 (需要后端支持) ====================
/**
* ()
* 注意: 建议使用具体的 transfer/deposit/withdraw
*/
static async createTransaction(data: CreateTransactionRequest): Promise<SystemTransaction> { static async createTransaction(data: CreateTransactionRequest): Promise<SystemTransaction> {
return apiClient.post('/transactions', data) // 根据交易类型调用对应的方法
switch (data.txn_type) {
case 'transfer':
return this.transfer({
from_account_id: data.from_account_id!,
to_account_id: data.to_account_id!,
amount: data.amount,
remark: data.remark,
})
case 'deposit':
return this.deposit({
account_id: data.to_account_id!,
amount: data.amount,
remark: data.remark,
})
case 'withdrawal':
return this.withdraw({
account_id: data.from_account_id!,
amount: data.amount,
remark: data.remark,
})
default:
throw new Error(`不支持的交易类型: ${data.txn_type}`)
}
} }
// 提交交易到银行 /**
* ()
* @todo
*/
static async submitToBank(id: number): Promise<{ bankRefNo: string }> { static async submitToBank(id: number): Promise<{ bankRefNo: string }> {
return apiClient.post(`/transactions/${id}/submit`) return apiClient.post(`/transactions/${id}/submit`)
} }
// 取消交易 /**
* ()
* @todo
*/
static async cancelTransaction(id: number): Promise<void> { static async cancelTransaction(id: number): Promise<void> {
return apiClient.post(`/transactions/${id}/cancel`) return apiClient.post(`/transactions/${id}/cancel`)
} }
// 重试交易 /**
* ()
* @todo
*/
static async retryTransaction(id: number): Promise<SystemTransaction> { static async retryTransaction(id: number): Promise<SystemTransaction> {
return apiClient.post(`/transactions/${id}/retry`) return apiClient.post(`/transactions/${id}/retry`)
} }
// 获取交易状态 /**
* ()
* 注意: 可以通过 getTransaction
*/
static async getTransactionStatus(id: number): Promise<{ status: string, bankRefNo?: string }> { static async getTransactionStatus(id: number): Promise<{ status: string, bankRefNo?: string }> {
return apiClient.get(`/transactions/${id}/status`) const txn = await this.getTransaction(id)
return {
status: txn.status,
bankRefNo: txn.bank_ref_no,
}
} }
// 转账操作 /**
static async transfer(data: TransferRequest): Promise<TransferResponse> { * ()
return apiClient.post('/transfer', data) * @todo
} */
// 查询银行流水
static async getBankStatements(params: { static async getBankStatements(params: {
accountNo: string accountNo: string
startDate: string startDate: string
@ -67,10 +182,13 @@ export class TransactionAPI {
items: BankTransaction[] items: BankTransaction[]
total: number total: number
}> { }> {
return apiClient.get('/bank/statements', params) return apiClient.get('/bank/statements', { params })
} }
// 查询交易统计 /**
* ()
* @todo
*/
static async getTransactionStats(params: { static async getTransactionStats(params: {
accountId?: number accountId?: number
startDate?: string startDate?: string
@ -82,22 +200,28 @@ export class TransactionAPI {
failedCount: number failedCount: number
pendingCount: number pendingCount: number
}> { }> {
return apiClient.get('/transactions/stats', params) return apiClient.get('/transactions/stats', { params })
} }
// 获取交易历史(分页) /**
*
* 使 getTransactions + from_account_id
*/
static async getTransactionHistory(params: { static async getTransactionHistory(params: {
accountId: number accountId: number
page?: number page?: number
pageSize?: number pageSize?: number
startDate?: string startDate?: string
endDate?: string endDate?: string
}): Promise<{ }): Promise<PageResponse<SystemTransaction>> {
items: SystemTransaction[] return this.getTransactions({
total: number from_account_id: params.accountId,
page: number page: params.page,
pageSize: number page_size: params.pageSize,
}> { start_date: params.startDate,
return apiClient.get(`/accounts/${params.accountId}/transactions`, params) end_date: params.endDate,
})
} }
} }
export default TransactionAPI