# api/employment.py # 就业模块接口:就业开放登记、offer登记(状态联动)、多条件查询、修改、删除 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.employment_dao import EmploymentDAO from dao.students_dao import StudentDAO from database import get_db from model.user import User from scheme.employment import ( EmploymentOpen, OfferAdd, EmploymentUpdate, OfferResponse, EmploymentResponse, EmploymentListResponse, ) router = APIRouter() def _can_manage_student_employment(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 def _check_student_manage(db: Session, user: User, stu_id: int): """公共校验:学生存在 + 操作者有权管理该学生就业信息""" student = StudentDAO.get_active(db, stu_id) if student is None: raise HTTPException(status_code=404, detail="学生不存在,或已被软删除") if not _can_manage_student_employment(db, user, student): raise HTTPException(status_code=403, detail="教师只能管理自己所带班级学生的就业信息") return student def _to_response(db_base) -> EmploymentResponse: resp = EmploymentResponse.model_validate(db_base) resp.offers = [OfferResponse.model_validate(o) for o in (db_base.offers or [])] return resp # ---------- 登记就业开放 ---------- @router.post("/open", response_model=EmploymentResponse, summary="登记就业开放(学生状态联动→进入就业)") async def open_employment( body: EmploymentOpen, db: Session = Depends(get_db), current_user: User = Depends(require_roles("admin", "teacher")), ): _check_student_manage(db, current_user, body.stu_id) existing = EmploymentDAO.get_base(db, body.stu_id) if existing: raise HTTPException(status_code=409, detail="该学生已登记就业开放,请勿重复登记") db_base = EmploymentDAO.open_employment(db, body.stu_id, body.employment_open_time) return _to_response(db_base) # ---------- 登记 offer ---------- @router.post("/offer", response_model=EmploymentResponse, summary="登记offer(学生状态联动→已就业)") async def add_offer( body: OfferAdd, db: Session = Depends(get_db), current_user: User = Depends(require_roles("admin", "teacher")), ): _check_student_manage(db, current_user, body.stu_id) db_base = EmploymentDAO.get_base(db, body.stu_id) if db_base is None: raise HTTPException(status_code=404, detail="该学生尚未登记就业开放,请先调用 /employment/open") if body.offer_time < db_base.employment_open_time: raise HTTPException(status_code=400, detail="offer下发时间不能早于就业开放时间") EmploymentDAO.add_offer(db, body.stu_id, body.offer_time, body.company_name, body.salary) return _to_response(EmploymentDAO.get_base(db, body.stu_id)) # ---------- 查询学生就业信息 ---------- @router.get("/students/{stu_id}", response_model=EmploymentResponse, summary="查询学生就业信息") async def get_student_employment( stu_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user), ): if current_user.role == "student" and current_user.stu_id != stu_id: raise HTTPException(status_code=403, detail="学生角色只能查看自己的就业信息") db_base = EmploymentDAO.get_by_student(db, stu_id) if db_base is None: raise HTTPException(status_code=404, detail="该学生暂无就业信息") return _to_response(db_base) # ---------- 查询班级就业信息 ---------- @router.get("/class/{class_id}", response_model=EmploymentListResponse, summary="查询班级学生就业信息") async def get_class_employment( class_id: int, skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=200), db: Session = Depends(get_db), current_user: User = Depends(require_roles("admin", "teacher")), ): total, items = EmploymentDAO.get_by_class(db, class_id, skip=skip, limit=limit) return EmploymentListResponse(total=total, items=[_to_response(b) for b in items]) # ---------- 多条件查询就业信息 ---------- @router.get("/total_query", response_model=EmploymentListResponse, summary="多条件查询就业信息(学号/公司/薪资范围)") async def query_employment( skip: int = Query(0, ge=0), limit: int = Query(100, ge=1, le=200), stu_id: int = Query(None, ge=1, description="按学号精确查询"), company_name: str = Query(None, description="按公司名称模糊查询"), salary_min: float = Query(None, ge=0, description="薪资下限"), salary_max: float = Query(None, ge=0, description="薪资上限"), db: Session = Depends(get_db), current_user: User = Depends(require_roles("admin", "teacher")), ): total, items = EmploymentDAO.get_all( db, skip=skip, limit=limit, stu_id=stu_id, company_name=company_name, salary_min=salary_min, salary_max=salary_max, ) return EmploymentListResponse(total=total, items=[_to_response(b) for b in items]) # ---------- 修改就业基础信息 ---------- @router.put("/update", response_model=EmploymentResponse, summary="修改学生就业基础信息") async def update_employment( body: EmploymentUpdate, stu_id: int = Query(..., ge=1, description="学生编号"), db: Session = Depends(get_db), current_user: User = Depends(require_roles("admin", "teacher")), ): _check_student_manage(db, current_user, stu_id) update_data = body.model_dump(exclude_unset=True, exclude_none=True) if not update_data: raise HTTPException(status_code=400, detail="至少提供一个要修改的字段") db_base = EmploymentDAO.update_base(db, stu_id, update_data) if db_base is None: raise HTTPException(status_code=404, detail="该学生暂无就业信息") return _to_response(db_base) # ---------- 删除就业信息 ---------- @router.delete("/delete", status_code=204, summary="删除学生就业信息(逻辑删除,状态回退为在读)") async def delete_employment( stu_id: int = Query(..., ge=1, description="学生编号"), db: Session = Depends(get_db), current_user: User = Depends(require_roles("admin", "teacher")), ): _check_student_manage(db, current_user, stu_id) success = EmploymentDAO.delete_light(db, stu_id) if not success: raise HTTPException(status_code=404, detail="该学生暂无就业信息") return None