"""银行账户管理与交易查询后台路由。""" from fastapi import APIRouter, Depends, HTTPException, Path, Query import sqlalchemy as sa from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.dependencies import get_admin_user, get_db from app.models.bank_account import BankAccount from app.models.bank_transaction import BankTransaction from app.models.user import User from app.services.bank.service import query_transactions_with_log from app.utils.id_gen import generate_id router = APIRouter(prefix="/admin/bank", tags=["admin-bank"]) # ============================================================ # 银行账户 CRUD # ============================================================ @router.get("/accounts") async def list_accounts( admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): """列出所有银行账户。""" result = await db.execute(select(BankAccount).order_by(BankAccount.is_default.desc(), BankAccount.created_at.desc())) accounts = result.scalars().all() return {"items": [ { "id": a.id, "account_name": a.account_name, "bank_name": a.bank_name, "account_no": a.account_no, "is_active": a.is_active, "is_default": a.is_default, "description": a.description, "created_at": a.created_at.isoformat() if a.created_at else None, "updated_at": a.updated_at.isoformat() if a.updated_at else None, } for a in accounts ]} @router.post("/accounts") async def create_account( body: dict, admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): """新增银行账户。""" account_name = (body.get("account_name") or "").strip() bank_name = (body.get("bank_name") or "").strip() account_no = (body.get("account_no") or "").strip() if not account_name or not bank_name or not account_no: raise HTTPException(status_code=400, detail="账户名称、开户银行、银行账号不能为空") # 检查账号唯一性 existing = await db.execute(select(BankAccount).where(BankAccount.account_no == account_no)) if existing.scalar_one_or_none(): raise HTTPException(status_code=409, detail="该银行账号已存在") is_default = bool(body.get("is_default", False)) # 如果设为默认,取消其他默认 if is_default: await db.execute( BankAccount.__table__.update().where(BankAccount.is_default.is_(True)).values(is_default=False) ) account = BankAccount( id=generate_id(), account_name=account_name, bank_name=bank_name, account_no=account_no, is_active=bool(body.get("is_active", True)), is_default=is_default, description=body.get("description"), ) db.add(account) await db.commit() return {"id": account.id, "message": "创建成功"} @router.put("/accounts/{account_id}") async def update_account( account_id: str = Path(..., description="账户 ID"), body: dict = ..., admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): """编辑银行账户。""" result = await db.execute(select(BankAccount).where(BankAccount.id == account_id)) account = result.scalar_one_or_none() if account is None: raise HTTPException(status_code=404, detail="账户不存在") if "account_name" in body: account.account_name = str(body["account_name"]).strip() if "bank_name" in body: account.bank_name = str(body["bank_name"]).strip() if "account_no" in body: new_no = str(body["account_no"]).strip() if new_no != account.account_no: existing = await db.execute(select(BankAccount).where(BankAccount.account_no == new_no)) if existing.scalar_one_or_none(): raise HTTPException(status_code=409, detail="该银行账号已存在") account.account_no = new_no if "is_active" in body: account.is_active = bool(body["is_active"]) if "description" in body: account.description = body.get("description") if body.get("is_default"): await db.execute( BankAccount.__table__.update() .where(BankAccount.is_default.is_(True)) .where(BankAccount.id != account_id) .values(is_default=False) ) account.is_default = True await db.commit() return {"message": "更新成功"} @router.delete("/accounts/{account_id}") async def delete_account( account_id: str = Path(..., description="账户 ID"), admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): """删除银行账户。""" result = await db.execute(select(BankAccount).where(BankAccount.id == account_id)) account = result.scalar_one_or_none() if account is None: raise HTTPException(status_code=404, detail="账户不存在") await db.delete(account) await db.commit() return {"message": "删除成功"} # ============================================================ # 银行交易查询 # ============================================================ @router.get("/transactions") async def list_transactions( account_id: str = Query(..., description="银行账户 ID"), start_date: str = Query(..., description="开始日期 (YYYY-MM-DD)"), end_date: str = Query(..., description="结束日期 (YYYY-MM-DD)"), dc_flag: int | None = Query(None, description="借贷方向: 0-借/出金, 1-贷/入金, 不传返回全部"), page: int = Query(1, ge=1, description="页码"), page_size: int = Query(20, ge=1, le=100, description="每页数量"), admin: User = Depends(get_admin_user), db: AsyncSession = Depends(get_db), ): """查询本地已同步的银行交易流水。""" result = await db.execute(select(BankAccount).where(BankAccount.id == account_id)) account = result.scalar_one_or_none() if account is None: raise HTTPException(status_code=404, detail="银行账户不存在") # 构建查询条件 conditions = [BankTransaction.account_id == account_id] if start_date: conditions.append(BankTransaction.transaction_time >= start_date) if end_date: conditions.append(BankTransaction.transaction_time < end_date + " 23:59:59") if dc_flag is not None: direction = "DR" if dc_flag == 0 else "CR" conditions.append(BankTransaction.balance_direction == direction) # 查总数 count_result = await db.execute( select(sa.func.count()).where(*conditions) ) total = count_result.scalar() or 0 # 查分页数据 offset = (page - 1) * page_size data_result = await db.execute( select(BankTransaction) .where(*conditions) .order_by(BankTransaction.transaction_time.desc()) .offset(offset) .limit(page_size) ) transactions = data_result.scalars().all() return { "items": [ { "id": t.id, "account_id": t.account_id, "account_no": t.account_no, "transaction_no": t.transaction_no, "transaction_time": t.transaction_time.isoformat() if t.transaction_time else None, "transaction_amount": t.transaction_amount, "balance_direction": t.balance_direction, "balance_after": t.balance_after, "counterparty_name": t.counterparty_name, "counterparty_account": t.counterparty_account, "counterparty_bank": t.counterparty_bank, "remark": t.remark, "digest_code": t.digest_code, "sync_batch": t.sync_batch, "is_synced": t.is_synced, "created_at": t.created_at.isoformat() if t.created_at else None, } for t in transactions ], "total": total, }