Files

161 lines
6.7 KiB
Python

# 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