Files
test/app/dao/employment_dao.py
T
2026-09-21 19:03:31 +08:00

111 lines
4.0 KiB
Python

"""就业 DAO(需求 2.3)。"""
from __future__ import annotations
from sqlalchemy import Select, func, or_, select
from sqlalchemy.orm import Session
from app.dao.base_dao import BaseDao
from app.model import Clazz, Employment, Student
class EmploymentDao(BaseDao[Employment]):
model = Employment
@classmethod
def build_stmt(
cls,
stu_id: int | None = None,
class_id: int | None = None,
company: str | None = None,
salary_min: float | None = None,
salary_max: float | None = None,
has_offer: bool | None = None,
has_open: bool | None = None,
keyword: str | None = None,
order_by: str = "id",
order: str = "desc",
) -> Select:
stmt = select(Employment).join(Student, Student.id == Employment.stu_id).where(
Employment.alive(), Student.alive()
)
if stu_id:
stmt = stmt.where(Employment.stu_id == stu_id)
if class_id:
stmt = stmt.where(Employment.class_id == class_id)
if company:
stmt = stmt.where(Employment.company.like(f"%{company.strip()}%"))
if salary_min is not None:
stmt = stmt.where(Employment.salary >= salary_min)
if salary_max is not None:
stmt = stmt.where(Employment.salary <= salary_max)
if has_offer is True:
stmt = stmt.where(Employment.offer_date.is_not(None))
elif has_offer is False:
stmt = stmt.where(Employment.offer_date.is_(None))
if has_open is True:
stmt = stmt.where(Employment.open_date.is_not(None))
elif has_open is False:
stmt = stmt.where(Employment.open_date.is_(None))
if keyword:
like = f"%{keyword.strip()}%"
stmt = stmt.where(
or_(
Student.name.like(like),
Student.stu_no.like(like),
Employment.company.like(like),
Employment.city.like(like),
)
)
sortable = {
"id": Employment.id,
"salary": Employment.salary,
"open_date": Employment.open_date,
"offer_date": Employment.offer_date,
"company": Employment.company,
"student_name": Student.name,
"class_name": Clazz.name,
}
column = sortable.get(order_by or "id", Employment.id)
stmt = stmt.order_by(column.desc() if (order or "desc").lower() == "desc" else column.asc())
return stmt
@classmethod
def get_by_stu_id(cls, db: Session, stu_id: int, with_deleted: bool = False) -> Employment | None:
stmt = select(Employment).where(Employment.stu_id == stu_id)
if not with_deleted:
stmt = stmt.where(Employment.alive())
return db.scalars(stmt).unique().first()
@classmethod
def employed_count_by_class(cls, db: Session) -> dict[int, int]:
"""已就业(拿到 offer)人数按班级汇总。"""
stmt = (
select(Employment.class_id, func.count(Employment.id))
.where(Employment.alive(), Employment.offer_date.is_not(None))
.group_by(Employment.class_id)
)
return {row[0]: row[1] for row in db.execute(stmt).all()}
@classmethod
def salary_stats(cls, db: Session, high_line: float) -> dict[str, float | int | None]:
stmt = select(
func.count(Employment.id),
func.avg(Employment.salary),
func.max(Employment.salary),
).where(Employment.alive(), Employment.salary.is_not(None))
total, avg, mx = db.execute(stmt).first()
high = db.scalar(
select(func.count(Employment.id)).where(
Employment.alive(), Employment.salary >= high_line
)
)
return {
"count": int(total or 0),
"avg": float(avg) if avg is not None else None,
"max": float(mx) if mx is not None else None,
"high": int(high or 0),
}