# api/stu_api.py # 学生信息管理模块:接口层只负责 HTTP 出入参与响应组装,查库/校验/事务交给 dao.stu_dao # 业务异常(NotFoundError / ConflictError)由 main.py 注册的全局处理器统一转成状态码 from typing import Optional from fastapi import APIRouter, Depends, Query from sqlalchemy.orm import Session from dao import stu_dao from database import get_db from schemas.stu_schema import StuInfoCreate, StuInfoUpdate, StuInfoOut router = APIRouter() def _to_detail(stu) -> dict: """把学生实体组装成详情响应:基础字段 + 班级 / 顾问 / 成绩 / 就业信息。""" data = StuInfoOut.model_validate(stu).model_dump() # 班级信息 data["cls"] = None if stu.cls: data["cls"] = { "id": stu.cls.id, "cls_start_date": stu.cls.cls_start_date.isoformat() if stu.cls.cls_start_date else None, } # 顾问信息 data["advisor"] = {"id": stu.adv.id, "name": stu.adv.name} if stu.adv else None # 成绩列表(过滤已删除) data["scores"] = [ { "exam_attempt": s.exam_attempt, "exam_score": s.exam_score, "score_level": s.score_level, } for s in stu.score if not s.is_deleted ] # 就业信息(一对一) data["employ"] = None if stu.stu_emp_mgmt and not stu.stu_emp_mgmt.is_deleted: e = stu.stu_emp_mgmt data["employ"] = { "emp_company": e.emp_company, "salary": e.salary, "emp_open_time": e.emp_open_time.isoformat() if e.emp_open_time else None, "send_offer_time": e.send_offer_time.isoformat() if e.send_offer_time else None, } return data @router.get("", summary="分页查询学生列表") def list_students( page: int = Query(1, ge=1, description="页码"), size: int = Query(10, ge=1, le=100, description="每页数量"), name: Optional[str] = Query(None, description="按姓名模糊查询"), cls_id: Optional[str] = Query(None, description="按班级编号过滤"), state: Optional[str] = Query(None, description="按状态过滤"), db: Session = Depends(get_db), ): """查询未删除的学生,支持分页和按姓名/班级/状态过滤。""" total, items = stu_dao.list_students( db, page=page, size=size, name=name, cls_id=cls_id, state=state ) return { "total": total, "page": page, "size": size, "items": [StuInfoOut.model_validate(x) for x in items], } @router.get("/{stu_id}", summary="查询单个学生详情") def get_student(stu_id: str, db: Session = Depends(get_db)): """查询学生详情,并附带班级、顾问、成绩与就业信息。""" return _to_detail(stu_dao.assert_stu_alive(db, stu_id, "学生不存在或已删除")) @router.post("", response_model=StuInfoOut, status_code=201, summary="新增学生") def create_student(payload: StuInfoCreate, db: Session = Depends(get_db)): """新增学生(学号唯一、班级与顾问必须存在,由 DAO 校验)。""" return stu_dao.create_student(db, payload.model_dump()) @router.put("/{stu_id}", response_model=StuInfoOut, summary="更新学生信息") def update_student(stu_id: str, payload: StuInfoUpdate, db: Session = Depends(get_db)): """按传入字段局部更新学生信息。""" return stu_dao.update_student(db, stu_id, payload.model_dump(exclude_unset=True)) @router.delete("/{stu_id}", summary="删除学生(软删除)") def delete_student(stu_id: str, db: Session = Depends(get_db)): """软删除学生:仅置 is_deleted 标记,数据保留。""" stu_dao.soft_delete_student(db, stu_id) return {"message": "删除成功", "id": stu_id}