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

91 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""就业信息表(需求 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