就业模块合并

This commit is contained in:
pzc
2026-09-13 22:46:47 +08:00
parent 5156da5a7c
commit 109f9c7862
8 changed files with 203 additions and 25 deletions
+56
View File
@@ -0,0 +1,56 @@
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session
from dao.wl_emp_dao import EmpDAO
from database import get_db
from scheme.wl_emp_scheme import EmpCreate, EmpOut, EmpUpdate
router = APIRouter(prefix="/emp", tags=["就业管理"])
# 查询就业列表
@router.get("/", response_model=list[EmpOut])
def query_list(
skip: int = 0,
limit: int = 100,
company_name: Optional[str] = None,
db: Session = Depends(get_db),
):
return EmpDAO.list_emps(db, skip, limit, company_name)
# 根据学号查询就业信息
@router.get("/{stu_no}", response_model=EmpOut)
def query_one(stu_no: str, db: Session = Depends(get_db)):
emp = EmpDAO.get_emp(db, stu_no)
if not emp:
raise HTTPException(status_code=404, detail="就业信息不存在")
return emp
# 新增就业信息
@router.post("/", response_model=EmpOut, status_code=201)
def add_emp(body: EmpCreate, db: Session = Depends(get_db)):
try:
return EmpDAO.create_emp(db, body)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
# 修改就业信息
@router.put("/{stu_no}", response_model=EmpOut)
def edit_emp(stu_no: str, body: EmpUpdate, db: Session = Depends(get_db)):
emp = EmpDAO.update_emp(db, stu_no, body)
if not emp:
raise HTTPException(status_code=404, detail="就业信息不存在")
return emp
# 逻辑删除就业信息
@router.delete("/{stu_no}")
def remove_emp(stu_no: str, db: Session = Depends(get_db)):
if not EmpDAO.delete_emp(db, stu_no):
raise HTTPException(status_code=404, detail="就业信息不存在")
return {"code": 200, "msg": "删除成功"}
+99
View File
@@ -0,0 +1,99 @@
from sqlalchemy.orm import Session
from model.wl_emp_model import Emp
from model.wl_student_model import Student
from scheme.wl_emp_scheme import EmpCreate, EmpUpdate
class EmpDAO:
@staticmethod
def get_student(db: Session, stu_no: str):
"""按学号查未删除的学生"""
return db.query(Student).filter(
Student.stu_no == stu_no,
Student.is_deleted == 0,
).first()
@staticmethod
def list_emps(db: Session, skip: int = 0, limit: int = 100, company_name: str = None):
"""查询就业列表,顺带把学生姓名带上"""
q = db.query(Emp).filter(Emp.is_deleted == 0)
if company_name:
q = q.filter(Emp.company_name.like(f"%{company_name}%"))
emp_list = q.offset(skip).limit(limit).all()
for e in emp_list:
stu = db.query(Student).filter(
Student.stu_no == e.stu_no,
Student.is_deleted == 0,
).first()
e.stu_name = stu.stu_name if stu else None
return emp_list
@staticmethod
def get_emp(db: Session, stu_no: str):
"""按学号查就业信息"""
emp = db.query(Emp).filter(
Emp.stu_no == stu_no,
Emp.is_deleted == 0,
).first()
if emp:
stu = EmpDAO.get_student(db, stu_no)
emp.stu_name = stu.stu_name if stu else None
return emp
@staticmethod
def create_emp(db: Session, emp_data: EmpCreate):
"""新增就业信息;如果这个学生之前被删过,就把那条记录复活"""
# stu_no 是主键,已删除的记录也占着这个主键,所以查的时候要连已删除的一起查
exist = db.query(Emp).filter(Emp.stu_no == emp_data.stu_no).first()
if exist and exist.is_deleted == 0:
raise ValueError("该学生的就业信息已存在")
# exclude_none:没传的字段不写进去,交给数据库默认值
data = emp_data.model_dump(exclude_none=True)
stu = EmpDAO.get_student(db, emp_data.stu_no)
if exist:
# 复活旧记录
for k, v in data.items():
setattr(exist, k, v)
exist.is_deleted = 0
db.commit()
db.refresh(exist)
exist.stu_name = stu.stu_name if stu else None
return exist
db_emp = Emp(**data)
db.add(db_emp)
db.commit()
db.refresh(db_emp)
db_emp.stu_name = stu.stu_name if stu else None
return db_emp
@staticmethod
def update_emp(db: Session, stu_no: str, emp_data: EmpUpdate):
"""修改就业信息"""
db_emp = EmpDAO.get_emp(db, stu_no)
if not db_emp:
return None
update_dict = emp_data.model_dump(exclude_unset=True)
for k, v in update_dict.items():
setattr(db_emp, k, v)
db.commit()
db.refresh(db_emp)
return db_emp
@staticmethod
def delete_emp(db: Session, stu_no: str):
"""逻辑删除,修改 is_deleted=1"""
db_emp = EmpDAO.get_emp(db, stu_no)
if not db_emp:
return None
db_emp.is_deleted = 1
db.commit()
return db_emp
+5 -3
View File
@@ -136,7 +136,7 @@ class StatisticDao:
Emp.company_name Emp.company_name
).select_from(Student) \ ).select_from(Student) \
.join(Student.emp)) \ .join(Student.emp)) \
.filter(Student.is_deleted==0) \ .filter(Student.is_deleted==0, Emp.is_deleted==0) \
.order_by(Emp.salary.desc())\ .order_by(Emp.salary.desc())\
.limit(rank) .limit(rank)
@@ -151,11 +151,12 @@ class StatisticDao:
0, 0,
).label('emp_total_time') ).label('emp_total_time')
# 逻辑删除条件写在 ON 里,写进 where 会变成 INNER JOIN,未就业学生会被滤掉
q=db.query( q=db.query(
Student.stu_name, Student.stu_name,
emp_total_time, emp_total_time,
).select_from(Student) \ ).select_from(Student) \
.outerjoin(Student.emp) \ .outerjoin(Emp, and_(Student.stu_no == Emp.stu_no, Emp.is_deleted == 0)) \
.filter(Student.is_deleted==0) .filter(Student.is_deleted==0)
return q.all() return q.all()
@@ -188,7 +189,8 @@ class StatisticDao:
.outerjoin( .outerjoin(
Emp, Emp,
and_( and_(
Emp.stu_id == Student.stu_id, Emp.stu_no == Student.stu_no,
Emp.is_deleted == 0,
Emp.emp_open_time.isnot(None), Emp.emp_open_time.isnot(None),
Emp.offer_time.isnot(None), Emp.offer_time.isnot(None),
), ),
+6 -3
View File
@@ -1,9 +1,11 @@
#项目初始化入口 #项目初始化入口
from fastapi import FastAPI from fastapi import FastAPI
# 周学灵、熊浩钦、圣国伟、刘盼、彭少的路由 # 周学灵、熊浩钦、圣国伟、刘盼、彭少、赵康宁的路由
from api import wl_student_api,wl_class_api,wl_score_api,wl_teacher_api,statistics from api import wl_student_api,wl_class_api,wl_score_api,wl_teacher_api,statistics,wl_emp_api
from database import Base, engine from database import Base, engine
from model import wl_student_model,wl_class_model,wl_advisor_model,wl_score_model,wl_teacher_model # wl_emp_model 必须显式导入:否则 wl_emp 表只能靠 statistics 的传递导入进 metadata,
# 一旦统计路由被拿掉,create_all 会漏建 wl_emp,Student.emp 关系随即报错
from model import wl_student_model,wl_class_model,wl_advisor_model,wl_score_model,wl_teacher_model,wl_emp_model
Base.metadata.drop_all(engine) Base.metadata.drop_all(engine)
Base.metadata.create_all(bind=engine) Base.metadata.create_all(bind=engine)
@@ -14,6 +16,7 @@ app.include_router(wl_class_api.router)
app.include_router(wl_score_api.router) app.include_router(wl_score_api.router)
app.include_router(wl_teacher_api.router) app.include_router(wl_teacher_api.router)
app.include_router(statistics.router) app.include_router(statistics.router)
app.include_router(wl_emp_api.router)
if __name__ == "__main__": if __name__ == "__main__":
+8 -6
View File
@@ -1,5 +1,5 @@
from sqlalchemy import * from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, func
from sqlalchemy.orm import * from sqlalchemy.orm import relationship
from database import Base from database import Base
@@ -7,15 +7,17 @@ from database import Base
class Emp(Base): class Emp(Base):
__tablename__ = "wl_emp" __tablename__ = "wl_emp"
stu_id = Column(Integer,ForeignKey('wl_student.stu_id'), primary_key=True, autoincrement=True) # 学号做主键,同时做外键指向学生表,长度跟 Student.stu_no 保持一致
stu_no = Column(String(20), ForeignKey('wl_student.stu_no'), primary_key=True)
emp_open_time = Column(DateTime, default="2026-11-30 18:18:18") emp_open_time = Column(DateTime, default="2026-11-30 18:18:18")
offer_time = Column(DateTime, default=func.now()) offer_time = Column(DateTime, default=func.now())
company_name = Column(String(100), nullable=False) company_name = Column(String(100), nullable=False)
salary = Column(Integer, nullable=False, default=15000) salary = Column(Integer, nullable=False, default=15000)
# Student.emp 用的是 back_populates="student",这个反向属性必须保留,否则 mapper 配置失败
student = relationship("Student", back_populates="emp") student = relationship("Student", back_populates="emp")
def __repr__(self): # 逻辑删除:0 正常 1 已删除
pass is_deleted = Column(Integer, default=0, comment='逻辑删除: 0正常 1删除')
def __repr__(self):
return f"<Emp(stu_no={self.stu_no}, company='{self.company_name}')>"
+1 -1
View File
@@ -24,7 +24,7 @@ class Student(Base):
class_ = relationship("Class_", back_populates="students") class_ = relationship("Class_", back_populates="students")
scores=relationship("Score",back_populates="student") scores=relationship("Score",back_populates="student")
emp=relationship("Emp",back_populates="student") emp = relationship("Emp", back_populates="student")
# advisor = relationship("Advisor", back_populates="students") # advisor = relationship("Advisor", back_populates="students")
is_deleted = Column(Integer, default=0, comment='逻辑删除: 0正常 1删除') is_deleted = Column(Integer, default=0, comment='逻辑删除: 0正常 1删除')
+28
View File
@@ -0,0 +1,28 @@
from pydantic import BaseModel, Field, ConfigDict
from datetime import datetime
from typing import Optional
# ---------- 请求模型 ----------
class EmpCreate(BaseModel):
stu_no: str = Field(..., max_length=20, description="学号")
emp_open_time: Optional[datetime] = Field(None, description="不传用表默认值")
offer_time: Optional[datetime] = Field(None, description="不传用数据库当前时间")
company_name: str = Field(..., max_length=100, description="公司名称")
salary: Optional[int] = Field(None, description="不传用默认值 15000")
class EmpUpdate(BaseModel):
emp_open_time: Optional[datetime] = None
offer_time: Optional[datetime] = None
company_name: Optional[str] = Field(None, max_length=100)
salary: Optional[int] = None
# ---------- 响应模型 ----------
class EmpOut(BaseModel):
stu_no: str
stu_name: Optional[str] = None
emp_open_time: Optional[datetime] = None
offer_time: Optional[datetime] = None
company_name: Optional[str] = None
salary: Optional[int] = None
model_config = ConfigDict(from_attributes=True)
-12
View File
@@ -30,18 +30,6 @@ class ScoreResp(ScoreCreate):
class Config: class Config:
from_attributes = True from_attributes = True
# ----------------就业----------------
class EmploymentCreate(BaseModel):
stu_id: int
emp_open_time: date
offer_time: Optional[date] = None
company_name: Optional[str] = None
salary: Optional[float] = None
class EmploymentResp(EmploymentCreate):
class Config:
from_attributes = True
# ----------------班级---------------- # ----------------班级----------------
class ClassCreate(BaseModel): class ClassCreate(BaseModel):
start_time: Optional[date] = None start_time: Optional[date] = None