# api/scores.py # 成绩模块接口:录入(含 60 分红线预警)、修改、删除、查询 from fastapi import APIRouter, Depends, HTTPException, Query from sqlalchemy.orm import Session from core.deps import get_current_user, require_roles, get_teacher_class_ids from dao.scores_dao import ScoreDAO from dao.students_dao import StudentDAO from database import get_db from model.user import User from scheme.scores import ( ScoreAdd, ScoreUpdate, ScoreDelete, ScoreResponse, ScoreListResponse, ScoreAddResponse, ) router = APIRouter() def _can_manage_student_scores(db: Session, user: User, student) -> bool: """教师只能管理自己所带班级学生的成绩;管理员不受限""" if user.role == "admin": return True if user.role == "teacher": return student.class_id in get_teacher_class_ids(db, user.teacher_id) return False # ---------- 录入成绩 ---------- @router.post("/add", response_model=ScoreAddResponse, summary="录入成绩(低于60分触发预警)") async def add_score( score_data: ScoreAdd, db: Session = Depends(get_db), current_user: User = Depends(require_roles("admin", "teacher")), ): # 外键校验:学生必须存在 student = StudentDAO.get_active(db, score_data.stu_id) if student is None: raise HTTPException(status_code=404, detail="学生不存在,或已被软删除") if not _can_manage_student_scores(db, current_user, student): raise HTTPException(status_code=403, detail="教师只能管理自己所带班级学生的成绩") # 复合主键冲突校验 existing = ScoreDAO.get_one(db, score_data.stu_id, score_data.exam_id, include_deleted=True) if existing: raise HTTPException(status_code=409, detail="该学生此考核序次的成绩已存在,请使用修改接口") db_score = ScoreDAO.add_score(db, score_data) item = ScoreDAO.build_score_item(db_score, stu_name=student.stu_name) warning = None if item["is_warning"]: warning = f"预警:学生 {student.stu_name}(学号 {student.stu_id})第 {score_data.exam_id} 次考核成绩 {score_data.score} 分低于 {ScoreDAO.WARNING_LINE} 分红线,需要多加关注!" return ScoreAddResponse(score=ScoreResponse(**item), warning=warning) # ---------- 查询学生成绩 ---------- @router.get("/query", response_model=ScoreListResponse, summary="查询成绩(按学号或考核序次)") async def get_scores( stu_id: int = Query(None, ge=1, description="按学号查询(学生角色只能查自己)"), exam_id: int = Query(None, ge=1, description="按考核序次查询"), skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=200), db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): # 学生角色只能查自己的成绩 if current_user.role == "student": stu_id = current_user.stu_id if stu_id is None and exam_id is None: raise HTTPException(status_code=400, detail="stu_id 与 exam_id 至少提供一个") if stu_id is not None: rows = ScoreDAO.get_by_student(db, stu_id) student = StudentDAO.get_active(db, stu_id) stu_name = student.stu_name if student else None items = [ScoreResponse(**ScoreDAO.build_score_item(s, stu_name)) for s in rows] return ScoreListResponse(total=len(items), items=items) total, rows = ScoreDAO.get_by_exam(db, exam_id, skip=skip, limit=limit) items = [] for s in rows: student = StudentDAO.get_active(db, s.stu_id) items.append( ScoreResponse(**ScoreDAO.build_score_item(s, student.stu_name if student else None)) ) return ScoreListResponse(total=total, items=items) # ---------- 修改成绩 ---------- @router.put("/update", response_model=ScoreResponse, summary="修改指定学生的某次成绩") async def update_score( score_data: ScoreUpdate, stu_id: int = Query(..., ge=1, description="学生编号"), exam_id: int = Query(..., ge=1, description="考核序次"), db: Session = Depends(get_db), current_user: User = Depends(require_roles("admin", "teacher")), ): student = StudentDAO.get_active(db, stu_id) if student is None: raise HTTPException(status_code=404, detail="学生不存在,或已被软删除") if not _can_manage_student_scores(db, current_user, student): raise HTTPException(status_code=403, detail="教师只能管理自己所带班级学生的成绩") db_score = ScoreDAO.update_score(db, stu_id, exam_id, score_data.score) if db_score is None: raise HTTPException(status_code=404, detail="该学生此考核序次的成绩不存在") return ScoreResponse(**ScoreDAO.build_score_item(db_score, student.stu_name)) # ---------- 删除成绩 ---------- @router.post("/delete", status_code=204, summary="删除指定学生的某次成绩(逻辑删除)") async def delete_score( body: ScoreDelete, db: Session = Depends(get_db), current_user: User = Depends(require_roles("admin", "teacher")), ): student = StudentDAO.get_active(db, body.stu_id) if student is None: raise HTTPException(status_code=404, detail="学生不存在,或已被软删除") if not _can_manage_student_scores(db, current_user, student): raise HTTPException(status_code=403, detail="教师只能管理自己所带班级学生的成绩") success = ScoreDAO.delete_score(db, body.stu_id, body.exam_id) if not success: raise HTTPException(status_code=404, detail="该成绩不存在,或已被删除") return None