313 lines
13 KiB
Python
313 lines
13 KiB
Python
"""
|
|
银行流水API路由
|
|
"""
|
|
from typing import Any, List, Optional
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
|
from sqlalchemy import select, func, and_, or_
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from loguru import logger
|
|
|
|
from app.database import get_async_session
|
|
from app.models.bank_transaction import BankTransaction
|
|
from app.schemas.bank import (
|
|
BankTransactionCreate,
|
|
BankTransactionUpdate,
|
|
BankTransactionResponse,
|
|
BankTransactionListResponse,
|
|
)
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("", response_model=BankTransactionListResponse)
|
|
async def list_bank_transactions(
|
|
page: int = Query(1, ge=1, description="页码"),
|
|
size: int = Query(10, ge=1, le=100, description="每页数量"),
|
|
transaction_id: str = Query(None, description="流水ID"),
|
|
account_no: str = Query(None, description="账号"),
|
|
transaction_type: str = Query(None, description="交易类型"),
|
|
is_suspicious: bool = Query(None, description="是否可疑交易"),
|
|
db: AsyncSession = Depends(get_async_session),
|
|
):
|
|
"""
|
|
获取银行流水列表(分页查询)
|
|
"""
|
|
logger.info(f"获取银行流水列表: page={page}, size={size}")
|
|
|
|
# 构建查询
|
|
query = select(BankTransaction)
|
|
|
|
# 添加过滤条件
|
|
conditions = []
|
|
if transaction_id:
|
|
conditions.append(BankTransaction.transaction_id.ilike(f"%{transaction_id}%"))
|
|
if account_no:
|
|
conditions.append(BankTransaction.account_no == account_no)
|
|
if transaction_type:
|
|
conditions.append(BankTransaction.transaction_type == transaction_type)
|
|
if is_suspicious is not None:
|
|
conditions.append(BankTransaction.is_suspicious == is_suspicious)
|
|
|
|
if conditions:
|
|
query = query.where(and_(*conditions))
|
|
|
|
# 获取总数
|
|
count_query = select(func.count()).select_from(BankTransaction)
|
|
if conditions:
|
|
count_query = count_query.where(and_(*conditions))
|
|
|
|
total_result = await db.execute(count_query)
|
|
total = total_result.scalar()
|
|
|
|
# 分页
|
|
query = query.offset((page - 1) * size).limit(size)
|
|
|
|
# 执行查询
|
|
result = await db.execute(query)
|
|
records = result.scalars().all()
|
|
|
|
# 转换为响应格式
|
|
response_records = []
|
|
for transaction in records:
|
|
response_records.append({
|
|
"id": transaction.id,
|
|
"transaction_id": transaction.transaction_id,
|
|
"account_no": transaction.account_no,
|
|
"account_name": transaction.account_name,
|
|
"bank_name": transaction.bank_name,
|
|
"transaction_date": transaction.transaction_date.isoformat() if transaction.transaction_date else None,
|
|
"transaction_time": transaction.transaction_time.isoformat() if transaction.transaction_time else None,
|
|
"transaction_type": transaction.transaction_type,
|
|
"transaction_amount": transaction.transaction_amount,
|
|
"balance": transaction.balance,
|
|
"counterparty_account_no": transaction.counterparty_account_no,
|
|
"counterparty_account_name": transaction.counterparty_account_name,
|
|
"counterparty_bank_name": transaction.counterparty_bank_name,
|
|
"voucher_no": transaction.voucher_no,
|
|
"transaction_purpose": transaction.transaction_purpose,
|
|
"is_cross_border": transaction.is_cross_border,
|
|
"currency": transaction.currency,
|
|
"amount_cny": transaction.amount_cny,
|
|
"exchange_rate": transaction.exchange_rate,
|
|
"is_large_amount": transaction.is_large_amount,
|
|
"is_suspicious": transaction.is_suspicious,
|
|
"suspicious_reason": transaction.suspicious_reason,
|
|
"is_reconciled": transaction.is_reconciled,
|
|
"reconciled_time": transaction.reconciled_time.isoformat() if transaction.reconciled_time else None,
|
|
})
|
|
|
|
return BankTransactionListResponse(
|
|
records=response_records,
|
|
total=total,
|
|
page=page,
|
|
size=size,
|
|
)
|
|
|
|
|
|
@router.get("/{transaction_id}", response_model=BankTransactionResponse)
|
|
async def get_bank_transaction(transaction_id: str, db: AsyncSession = Depends(get_async_session)):
|
|
"""
|
|
根据ID获取银行流水详细信息
|
|
"""
|
|
logger.info(f"获取银行流水详情: {transaction_id}")
|
|
|
|
query = select(BankTransaction).where(BankTransaction.transaction_id == transaction_id)
|
|
result = await db.execute(query)
|
|
transaction = result.scalar_one_or_none()
|
|
|
|
if not transaction:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"银行流水不存在: {transaction_id}",
|
|
)
|
|
|
|
# 转换为响应格式
|
|
response = {
|
|
"id": transaction.id,
|
|
"transaction_id": transaction.transaction_id,
|
|
"account_no": transaction.account_no,
|
|
"account_name": transaction.account_name,
|
|
"bank_name": transaction.bank_name,
|
|
"transaction_date": transaction.transaction_date.isoformat() if transaction.transaction_date else None,
|
|
"transaction_time": transaction.transaction_time.isoformat() if transaction.transaction_time else None,
|
|
"transaction_type": transaction.transaction_type,
|
|
"transaction_amount": transaction.transaction_amount,
|
|
"balance": transaction.balance,
|
|
"counterparty_account_no": transaction.counterparty_account_no,
|
|
"counterparty_account_name": transaction.counterparty_account_name,
|
|
"counterparty_bank_name": transaction.counterparty_bank_name,
|
|
"voucher_no": transaction.voucher_no,
|
|
"transaction_purpose": transaction.transaction_purpose,
|
|
"is_cross_border": transaction.is_cross_border,
|
|
"currency": transaction.currency,
|
|
"amount_cny": transaction.amount_cny,
|
|
"exchange_rate": transaction.exchange_rate,
|
|
"is_large_amount": transaction.is_large_amount,
|
|
"is_suspicious": transaction.is_suspicious,
|
|
"suspicious_reason": transaction.suspicious_reason,
|
|
"is_reconciled": transaction.is_reconciled,
|
|
"reconciled_time": transaction.reconciled_time.isoformat() if transaction.reconciled_time else None,
|
|
}
|
|
|
|
return response
|
|
|
|
|
|
@router.post("", response_model=BankTransactionResponse, status_code=status.HTTP_201_CREATED)
|
|
async def create_bank_transaction(transaction: BankTransactionCreate, db: AsyncSession = Depends(get_async_session)):
|
|
"""
|
|
创建新的银行流水
|
|
"""
|
|
logger.info(f"创建银行流水: {transaction.account_no}")
|
|
|
|
# 生成流水ID
|
|
query = select(func.count()).select_from(BankTransaction)
|
|
result = await db.execute(query)
|
|
count = result.scalar()
|
|
transaction_id = f"BTX{2024}{(count + 1):06d}"
|
|
|
|
# 创建新流水
|
|
new_transaction = BankTransaction(
|
|
transaction_id=transaction_id,
|
|
account_no=transaction.account_no,
|
|
account_name=transaction.account_name,
|
|
bank_name=transaction.bank_name,
|
|
transaction_date=transaction.transaction_date,
|
|
transaction_time=transaction.transaction_time,
|
|
transaction_type=transaction.transaction_type,
|
|
transaction_amount=transaction.transaction_amount,
|
|
balance=transaction.balance,
|
|
counterparty_account_no=transaction.counterparty_account_no,
|
|
counterparty_account_name=transaction.counterparty_account_name,
|
|
counterparty_bank_name=transaction.counterparty_bank_name,
|
|
voucher_no=transaction.voucher_no,
|
|
transaction_purpose=transaction.transaction_purpose,
|
|
is_cross_border=transaction.is_cross_border,
|
|
currency=transaction.currency or "CNY",
|
|
amount_cny=transaction.amount_cny,
|
|
exchange_rate=transaction.exchange_rate or 1.0,
|
|
is_large_amount=transaction.is_large_amount,
|
|
is_suspicious=transaction.is_suspicious,
|
|
suspicious_reason=transaction.suspicious_reason,
|
|
is_reconciled=transaction.is_reconciled,
|
|
reconciled_time=transaction.reconciled_time,
|
|
)
|
|
|
|
db.add(new_transaction)
|
|
await db.commit()
|
|
await db.refresh(new_transaction)
|
|
|
|
# 转换为响应格式
|
|
response = {
|
|
"id": new_transaction.id,
|
|
"transaction_id": new_transaction.transaction_id,
|
|
"account_no": new_transaction.account_no,
|
|
"account_name": new_transaction.account_name,
|
|
"bank_name": new_transaction.bank_name,
|
|
"transaction_date": new_transaction.transaction_date.isoformat() if new_transaction.transaction_date else None,
|
|
"transaction_time": new_transaction.transaction_time.isoformat() if new_transaction.transaction_time else None,
|
|
"transaction_type": new_transaction.transaction_type,
|
|
"transaction_amount": new_transaction.transaction_amount,
|
|
"balance": new_transaction.balance,
|
|
"counterparty_account_no": new_transaction.counterparty_account_no,
|
|
"counterparty_account_name": new_transaction.counterparty_account_name,
|
|
"counterparty_bank_name": new_transaction.counterparty_bank_name,
|
|
"voucher_no": new_transaction.voucher_no,
|
|
"transaction_purpose": new_transaction.transaction_purpose,
|
|
"is_cross_border": new_transaction.is_cross_border,
|
|
"currency": new_transaction.currency,
|
|
"amount_cny": new_transaction.amount_cny,
|
|
"exchange_rate": new_transaction.exchange_rate,
|
|
"is_large_amount": new_transaction.is_large_amount,
|
|
"is_suspicious": new_transaction.is_suspicious,
|
|
"suspicious_reason": new_transaction.suspicious_reason,
|
|
"is_reconciled": new_transaction.is_reconciled,
|
|
"reconciled_time": new_transaction.reconciled_time.isoformat() if new_transaction.reconciled_time else None,
|
|
}
|
|
|
|
return response
|
|
|
|
|
|
@router.put("/{transaction_id}", response_model=BankTransactionResponse)
|
|
async def update_bank_transaction(
|
|
transaction_id: str,
|
|
transaction_update: BankTransactionUpdate,
|
|
db: AsyncSession = Depends(get_async_session),
|
|
):
|
|
"""
|
|
更新银行流水
|
|
"""
|
|
logger.info(f"更新银行流水: {transaction_id}")
|
|
|
|
query = select(BankTransaction).where(BankTransaction.transaction_id == transaction_id)
|
|
result = await db.execute(query)
|
|
transaction = result.scalar_one_or_none()
|
|
|
|
if not transaction:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"银行流水不存在: {transaction_id}",
|
|
)
|
|
|
|
# 更新字段
|
|
update_data = transaction_update.model_dump(exclude_unset=True)
|
|
for key, value in update_data.items():
|
|
if hasattr(transaction, key):
|
|
setattr(transaction, key, value)
|
|
|
|
await db.commit()
|
|
await db.refresh(transaction)
|
|
|
|
# 转换为响应格式
|
|
response = {
|
|
"id": transaction.id,
|
|
"transaction_id": transaction.transaction_id,
|
|
"account_no": transaction.account_no,
|
|
"account_name": transaction.account_name,
|
|
"bank_name": transaction.bank_name,
|
|
"transaction_date": transaction.transaction_date.isoformat() if transaction.transaction_date else None,
|
|
"transaction_time": transaction.transaction_time.isoformat() if transaction.transaction_time else None,
|
|
"transaction_type": transaction.transaction_type,
|
|
"transaction_amount": transaction.transaction_amount,
|
|
"balance": transaction.balance,
|
|
"counterparty_account_no": transaction.counterparty_account_no,
|
|
"counterparty_account_name": transaction.counterparty_account_name,
|
|
"counterparty_bank_name": transaction.counterparty_bank_name,
|
|
"voucher_no": transaction.voucher_no,
|
|
"transaction_purpose": transaction.transaction_purpose,
|
|
"is_cross_border": transaction.is_cross_border,
|
|
"currency": transaction.currency,
|
|
"amount_cny": transaction.amount_cny,
|
|
"exchange_rate": transaction.exchange_rate,
|
|
"is_large_amount": transaction.is_large_amount,
|
|
"is_suspicious": transaction.is_suspicious,
|
|
"suspicious_reason": transaction.suspicious_reason,
|
|
"is_reconciled": transaction.is_reconciled,
|
|
"reconciled_time": transaction.reconciled_time.isoformat() if transaction.reconciled_time else None,
|
|
}
|
|
|
|
return response
|
|
|
|
|
|
@router.delete("/{transaction_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
async def delete_bank_transaction(transaction_id: str, db: AsyncSession = Depends(get_async_session)):
|
|
"""
|
|
删除银行流水
|
|
"""
|
|
logger.info(f"删除银行流水: {transaction_id}")
|
|
|
|
query = select(BankTransaction).where(BankTransaction.transaction_id == transaction_id)
|
|
result = await db.execute(query)
|
|
transaction = result.scalar_one_or_none()
|
|
|
|
if not transaction:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail=f"银行流水不存在: {transaction_id}",
|
|
)
|
|
|
|
# 删除
|
|
await db.delete(transaction)
|
|
await db.commit()
|
|
|
|
return None
|