hku-class/backend/app/api/fund.py
2026-04-27 09:21:20 +08:00

129 lines
5.0 KiB
Python

from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import ensure_class_permission, require_role, resolve_class_id_for_user
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", "teacher", "student")),
db: AsyncSession = Depends(get_db),
):
effective_class_id = resolve_class_id_for_user(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=[]
)
ensure_class_permission(user, "class_view", effective_class_id)
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", "teacher", "student")),
db: AsyncSession = Depends(get_db),
):
effective_class_id = resolve_class_id_for_user(user, class_id)
if effective_class_id is None:
return PageResponse(items=[], total=0, page=page, page_size=page_size, total_pages=0)
ensure_class_permission(user, "class_view", effective_class_id)
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", "teacher", "student")),
db: AsyncSession = Depends(get_db),
):
effective_class_id = resolve_class_id_for_user(user, class_id)
if effective_class_id is None:
raise HTTPException(status_code=400, detail="No class specified")
ensure_class_permission(user, "fund_manage", effective_class_id)
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", "teacher", "student")),
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")
ensure_class_permission(user, "fund_manage", record.class_id)
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", "teacher", "student")),
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")
ensure_class_permission(user, "fund_manage", record.class_id)
await delete_fund_record(db, record)
return {"message": "Record deleted"}