hku-class/backend/app/api/fund.py
2026-04-17 22:41:50 +08:00

127 lines
5.0 KiB
Python

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import require_role
from app.db.database import get_db
from app.db.models import FundRecord, User
from app.schemas.fund import FundRecordCreate, FundRecordUpdate, FundRecordOut, FundStatistics
from app.schemas.common import PageResponse
from app.services.fund_service import (
create_fund_record, update_fund_record, delete_fund_record,
get_fund_record_by_id, list_fund_records, get_fund_statistics,
)
router = APIRouter(prefix="/api/fund", tags=["fund"])
def record_to_out(record: FundRecord) -> FundRecordOut:
return FundRecordOut(
id=record.id,
class_id=record.class_id,
type=record.type,
amount=record.amount,
category=record.category,
description=record.description,
record_date=record.record_date,
recorder_id=record.recorder_id,
recorder_name=record.recorder.name if record.recorder else "Unknown",
created_at=record.created_at,
updated_at=record.updated_at,
)
@router.get("/statistics", response_model=FundStatistics)
async def get_statistics(
class_id: int | None = None,
user: User = Depends(require_role("super_admin", "class_admin", "student")),
db: AsyncSession = Depends(get_db),
):
effective_class_id = class_id if user.role == "super_admin" and class_id else user.class_id
if effective_class_id is None:
return FundStatistics(
total_income=0, total_expense=0, balance=0,
income_by_category=[], expense_by_category=[]
)
return await get_fund_statistics(db, effective_class_id)
@router.get("/", response_model=PageResponse[FundRecordOut])
async def get_fund_records(
page: int = 1,
page_size: int = 20,
type: str | None = None,
category: str | None = None,
class_id: int | None = None,
user: User = Depends(require_role("super_admin", "class_admin", "student")),
db: AsyncSession = Depends(get_db),
):
effective_class_id = class_id if user.role == "super_admin" and class_id else user.class_id
if effective_class_id is None:
return PageResponse(items=[], total=0, page=page, page_size=page_size, total_pages=0)
records, total = await list_fund_records(db, effective_class_id, page, page_size, type, category)
total_pages = (total + page_size - 1) // page_size
items = [record_to_out(r) for r in records]
return PageResponse(items=items, total=total, page=page, page_size=page_size, total_pages=total_pages)
@router.post("/", response_model=FundRecordOut)
async def create_new_record(
data: FundRecordCreate,
class_id: int | None = None,
user: User = Depends(require_role("super_admin", "class_admin")),
db: AsyncSession = Depends(get_db),
):
effective_class_id = class_id if user.role == "super_admin" and class_id else user.class_id
if effective_class_id is None:
raise HTTPException(status_code=400, detail="No class specified")
if data.type not in ("income", "expense"):
raise HTTPException(status_code=400, detail="Type must be 'income' or 'expense'")
if data.amount <= 0:
raise HTTPException(status_code=400, detail="Amount must be positive")
record = await create_fund_record(db, effective_class_id, user.id, data)
# Reload with recorder relationship
record = await get_fund_record_by_id(db, record.id)
return record_to_out(record)
@router.put("/{record_id}", response_model=FundRecordOut)
async def update_existing_record(
record_id: int,
data: FundRecordUpdate,
user: User = Depends(require_role("super_admin", "class_admin")),
db: AsyncSession = Depends(get_db),
):
record = await get_fund_record_by_id(db, record_id)
if record is None:
raise HTTPException(status_code=404, detail="Record not found")
if user.role != "super_admin" and record.class_id != user.class_id:
raise HTTPException(status_code=403, detail="Access denied")
if data.type is not None and data.type not in ("income", "expense"):
raise HTTPException(status_code=400, detail="Type must be 'income' or 'expense'")
if data.amount is not None and data.amount <= 0:
raise HTTPException(status_code=400, detail="Amount must be positive")
updated = await update_fund_record(db, record, data)
# Reload with recorder relationship
updated = await get_fund_record_by_id(db, updated.id)
return record_to_out(updated)
@router.delete("/{record_id}")
async def delete_existing_record(
record_id: int,
user: User = Depends(require_role("super_admin", "class_admin")),
db: AsyncSession = Depends(get_db),
):
record = await get_fund_record_by_id(db, record_id)
if record is None:
raise HTTPException(status_code=404, detail="Record not found")
if user.role != "super_admin" and record.class_id != user.class_id:
raise HTTPException(status_code=403, detail="Access denied")
await delete_fund_record(db, record)
return {"message": "Record deleted"}