Files
test/app/model/employment.py
T

91 lines
4.1 KiB
Python
Raw Normal View History

2026-09-21 19:03:31 +08:00
"""就业信息表(需求 2.3)。
关于「学生姓名、学生班级属于冗余字段」的处理
--------------------------------------------------------------------------
需求点名要自行设计表结构来平衡查询效率与一致性。这里的取舍:
* **学生姓名不落库。** 它是 student.name 的投影,1:1 关系,
JOIN 一次的成本可以忽略;而一旦落库,改名就会两处不一致,收益为零。查询接口
统一 `JOIN student` 取名字。
* **班级落库(class_id 而非 class_name)。** 因为"按班级筛就业、按班级算就业率/
平均就业时长"是高频统计场景,每次 JOIN student 再 JOIN clazz 成本更高;
而且存 id 不存名字,班级改名不会导致脏数据。
代价是学生转班时要同步——由 ``EmploymentService.sync_class_id()`` 在学生
接口里兜住,不给"记得改"留口子。
* **就业时长不落库**,= offer_date - open_date,用 SQL DATEDIFF 或 Python 现算。
状态联动(需求 2.3 可选扩展,这里是必做):
登记就业 -> 有 open_date 则学生 status=2(进入就业),
-> 有 offer_date 则学生 status=3(已就业)
删除就业记录 -> 学生状态回退(3 -> 2 -> 1),不允许跨级跳。
"""
from __future__ import annotations
from datetime import date
from decimal import Decimal
from sqlalchemy import Date, ForeignKey, Numeric, String, Text
from sqlalchemy.orm import Mapped, mapped_column, relationship
from app.model.base import Base, SoftDeleteMixin, TimestampMixin
class Employment(Base, TimestampMixin, SoftDeleteMixin):
__tablename__ = "employment"
__table_args__ = {"comment": "学生就业信息表"}
id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True)
stu_id: Mapped[int] = mapped_column(
ForeignKey("student.id"), unique=True, nullable=False, index=True,
comment="学生ID(一学生一条就业记录)",
)
class_id: Mapped[int | None] = mapped_column(
ForeignKey("clazz.id"), index=True,
comment="班级ID(冗余,用于按班统计,转班时由 service 同步)",
)
open_date: Mapped[date | None] = mapped_column(Date, comment="就业开放时间")
offer_date: Mapped[date | None] = mapped_column(Date, comment="offer 下发时间")
company: Mapped[str | None] = mapped_column(String(80), index=True, comment="就业公司名称")
salary: Mapped[Decimal | None] = mapped_column(Numeric(10, 2), index=True, comment="就业薪资(元/月)")
position: Mapped[str | None] = mapped_column(String(50), comment="岗位名称")
city: Mapped[str | None] = mapped_column(String(30), comment="就业城市")
remark: Mapped[str | None] = mapped_column(Text, comment="备注")
student: Mapped["Student"] = relationship(back_populates="employment", lazy="joined") # noqa: F821
klass: Mapped["Clazz"] = relationship(lazy="joined", foreign_keys=[class_id]) # noqa: F821
# ---------------- 出参派生字段 ----------------
@property
def student_name(self) -> str | None:
return self.student.name if self.student else None
@property
def stu_no(self) -> str | None:
return self.student.stu_no if self.student else None
@property
def student_status(self) -> int | None:
return self.student.status if self.student else None
@property
def student_status_text(self) -> str | None:
return self.student.status_text if self.student else None
@property
def class_name(self) -> str | None:
# 冗余字段只存了 id,名字实时取,保证与班级表一致
return self.klass.name if self.klass and self.klass.is_del == 0 else None
@property
def duration_days(self) -> int | None:
"""就业时长 = offer 下发时间 - 就业开放时间(天)。"""
if self.open_date and self.offer_date:
return (self.offer_date - self.open_date).days
return None
@property
def salary_wan(self) -> float | None:
return round(float(self.salary) / 10000, 2) if self.salary is not None else None