from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, func from datetime import datetime, timedelta from app.core.database import get_db from app.models.models import User, Debt, RepaymentPlan, RepaymentRecord, PlanStatus, DebtStatus from app.schemas.schemas import RepaymentRecordCreate, RepaymentRecordResponse, DashboardSummary from app.core.deps import get_current_user router = APIRouter(prefix="/repayments", tags=["还款管理"]) def _naive(dt: datetime) -> datetime: if dt and dt.tzinfo: return dt.replace(tzinfo=None) return dt @router.post("", response_model=RepaymentRecordResponse, status_code=201) async def create_repayment( req: RepaymentRecordCreate, user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): result = await db.execute(select(Debt).where(Debt.id == req.debt_id)) debt = result.scalar_one_or_none() if not debt: raise HTTPException(status_code=404, detail="债务不存在") if debt.user_id != user.id and user.role != "admin": raise HTTPException(status_code=403, detail="无权为此债务还款") record = RepaymentRecord( debt_id=req.debt_id, plan_id=req.plan_id, amount=req.amount, paid_date=_naive(req.paid_date), notes=req.notes, ) db.add(record) if req.plan_id: plan_result = await db.execute(select(RepaymentPlan).where(RepaymentPlan.id == req.plan_id)) plan = plan_result.scalar_one_or_none() if plan: plan.status = PlanStatus.PAID.value total_paid_result = await db.execute( select(func.coalesce(func.sum(RepaymentRecord.amount), 0)).where(RepaymentRecord.debt_id == req.debt_id) ) total_paid = total_paid_result.scalar() + req.amount if total_paid >= debt.amount: debt.status = DebtStatus.PAID.value await db.commit() await db.refresh(record) return record @router.get("/dashboard", response_model=DashboardSummary) async def get_dashboard( user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): debts_result = await db.execute(select(Debt).where(Debt.status == DebtStatus.ACTIVE.value)) debts = debts_result.scalars().all() total_debt = sum(d.amount for d in debts) total_paid = 0 for d in debts: pr = await db.execute(select(func.coalesce(func.sum(RepaymentRecord.amount), 0)).where(RepaymentRecord.debt_id == d.id)) total_paid += pr.scalar() active_count = sum(1 for d in debts if d.status == DebtStatus.ACTIVE.value) overdue_count = sum(1 for d in debts if d.status == DebtStatus.OVERDUE.value) paid_all = await db.execute(select(Debt).where(Debt.status == DebtStatus.PAID.value)) paid_count = len(paid_all.scalars().all()) now = datetime.utcnow() upcoming = [] for d in debts: if d.status != DebtStatus.ACTIVE.value: continue plans_result = await db.execute( select(RepaymentPlan).where( RepaymentPlan.debt_id == d.id, RepaymentPlan.status == PlanStatus.PENDING.value, RepaymentPlan.due_date <= now + timedelta(days=7), ).order_by(RepaymentPlan.due_date) ) for p in plans_result.scalars().all(): upcoming.append({ "debt_id": d.id, "creditor": d.creditor_name, "due_date": p.due_date.isoformat(), "amount": p.total_due, "overdue": p.due_date < now, }) upcoming.sort(key=lambda x: x["due_date"]) return DashboardSummary( total_debt=total_debt, total_paid=total_paid, total_remaining=total_debt - total_paid, active_count=active_count, overdue_count=overdue_count, paid_count=paid_count, upcoming_payments=upcoming[:10], )