143 lines
5.6 KiB
Python
143 lines
5.6 KiB
Python
"""学生模块出入参(需求 2.1)。"""
|
|||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
from datetime import date
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
from pydantic import BaseModel, Field, field_validator, model_validator
|
||
|
|
|
||
|
|
from app.model.constants import Education
|
||
|
|
from app.schema.common import GENDER_VALIDATOR, ORMBase, STRIP_VALIDATOR
|
||
|
|
|
||
|
|
|
||
|
|
class _StudentBase(BaseModel):
|
||
|
|
name: str = Field(..., min_length=1, max_length=30, description="姓名")
|
||
|
|
gender: int = Field(1, description="性别 1=男 2=女(也接受 男/女)")
|
||
|
|
|
||
|
|
birth_date: str | None = Field(None, description="出生日期 YYYY-MM-DD")
|
||
|
|
age: int | None = Field(
|
||
|
|
None, ge=10, le=80,
|
||
|
|
description="年龄。只在拿不到生日时用:填了 age 没填 birth_date,系统按当年推算一个占位生日",
|
||
|
|
)
|
||
|
|
|
||
|
|
native_place: str | None = Field(None, max_length=60, description="籍贯")
|
||
|
|
graduate_school: str | None = Field(None, max_length=80, description="毕业院校")
|
||
|
|
major: str | None = Field(None, max_length=60, description="专业")
|
||
|
|
education: str | None = Field(None, max_length=20, description="学历,如 大专/本科", examples=[Education.BACHELOR.value])
|
||
|
|
|
||
|
|
enroll_date: str | None = Field(None, description="入学时间 YYYY-MM-DD")
|
||
|
|
graduate_date: str | None = Field(None, description="毕业时间 YYYY-MM-DD")
|
||
|
|
|
||
|
|
phone: str | None = Field(None, max_length=20, description="联系电话")
|
||
|
|
id_card: str | None = Field(None, max_length=30, description="身份证号")
|
||
|
|
|
||
|
|
class_id: int | None = Field(None, description="所属班级ID")
|
||
|
|
advisor_id: int | None = Field(None, description="顾问ID")
|
||
|
|
|
||
|
|
remark: str | None = Field(None, description="备注")
|
||
|
|
|
||
|
|
_v_gender = GENDER_VALIDATOR
|
||
|
|
_v_strip = STRIP_VALIDATOR
|
||
|
|
|
||
|
|
@field_validator("birth_date", "enroll_date", "graduate_date", mode="before")
|
||
|
|
@classmethod
|
||
|
|
def _date_like(cls, v): # noqa: ANN001, ANN201
|
||
|
|
"""把 date/datetime 统一成 str,真正的解析放在 API 层用 parse_date。"""
|
||
|
|
if v is None or isinstance(v, str):
|
||
|
|
return v
|
||
|
|
if isinstance(v, date):
|
||
|
|
return v.isoformat()
|
||
|
|
return v
|
||
|
|
|
||
|
|
|
||
|
|
class StudentCreate(_StudentBase):
|
||
|
|
stu_no: str | None = Field(None, max_length=30, description="学号。不传则按规则自动生成")
|
||
|
|
status: int | None = Field(None, ge=1, le=3, description="状态 1=在读 2=进入就业 3=已就业,默认在读")
|
||
|
|
|
||
|
|
@model_validator(mode="after")
|
||
|
|
def _check_age_source(self):
|
||
|
|
if self.birth_date is None and self.age is None:
|
||
|
|
raise ValueError("birth_date 和 age 至少要填一个(用于年龄统计)")
|
||
|
|
return self
|
||
|
|
|
||
|
|
|
||
|
|
class StudentUpdate(BaseModel):
|
||
|
|
"""全部字段可选,只更新传了的字段。"""
|
||
|
|
|
||
|
|
name: str | None = Field(None, min_length=1, max_length=30, description="姓名")
|
||
|
|
gender: int | None = Field(None, description="性别 1=男 2=女")
|
||
|
|
birth_date: str | None = Field(None, description="出生日期 YYYY-MM-DD")
|
||
|
|
age: int | None = Field(None, description="年龄(会折算成占位生日)")
|
||
|
|
|
||
|
|
native_place: str | None = Field(None, max_length=60, description="籍贯")
|
||
|
|
graduate_school: str | None = Field(None, max_length=80, description="毕业院校")
|
||
|
|
major: str | None = Field(None, max_length=60, description="专业")
|
||
|
|
education: str | None = Field(None, max_length=20, description="学历")
|
||
|
|
|
||
|
|
enroll_date: str | None = Field(None, description="入学时间 YYYY-MM-DD")
|
||
|
|
graduate_date: str | None = Field(None, description="毕业时间 YYYY-MM-DD")
|
||
|
|
|
||
|
|
phone: str | None = Field(None, max_length=20, description="联系电话")
|
||
|
|
id_card: str | None = Field(None, max_length=30, description="身份证号")
|
||
|
|
|
||
|
|
class_id: int | None = Field(None, description="所属班级ID(转班会同步就业表的冗余班级)")
|
||
|
|
advisor_id: int | None = Field(None, description="顾问ID")
|
||
|
|
|
||
|
|
remark: str | None = Field(None, description="备注")
|
||
|
|
|
||
|
|
_v_gender = GENDER_VALIDATOR
|
||
|
|
_v_strip = STRIP_VALIDATOR
|
||
|
|
|
||
|
|
|
||
|
|
class StudentBrief(ORMBase):
|
||
|
|
id: int
|
||
|
|
stu_no: str
|
||
|
|
name: str
|
||
|
|
gender_text: str | None = None
|
||
|
|
class_name: str | None = None
|
||
|
|
status_text: str
|
||
|
|
|
||
|
|
|
||
|
|
class StudentOut(ORMBase):
|
||
|
|
id: int
|
||
|
|
stu_no: str = Field(..., description="学号")
|
||
|
|
name: str
|
||
|
|
gender: int
|
||
|
|
gender_text: str | None = Field(None, description="性别文案")
|
||
|
|
age: int | None = Field(None, description="年龄(按出生日期实时计算)")
|
||
|
|
birth_date: date | None = None
|
||
|
|
birth_date_estimated: int = Field(0, description="生日是否为推算值 1=是")
|
||
|
|
|
||
|
|
native_place: str | None = None
|
||
|
|
graduate_school: str | None = None
|
||
|
|
major: str | None = None
|
||
|
|
education: str | None = None
|
||
|
|
|
||
|
|
enroll_date: date | None = None
|
||
|
|
graduate_date: date | None = None
|
||
|
|
|
||
|
|
phone: str | None = None
|
||
|
|
id_card: str | None = None
|
||
|
|
|
||
|
|
class_id: int | None = None
|
||
|
|
class_no: str | None = Field(None, description="班级编号")
|
||
|
|
class_name: str | None = Field(None, description="班级名称(JOIN 得出)")
|
||
|
|
advisor_id: int | None = None
|
||
|
|
advisor_name: str | None = None
|
||
|
|
|
||
|
|
status: int
|
||
|
|
status_text: str
|
||
|
|
remark: str | None = None
|
||
|
|
|
||
|
|
score_count: int = Field(0, description="已录入的考核次数")
|
||
|
|
avg_score: float | None = Field(None, description="平均分")
|
||
|
|
fail_count: int = Field(0, description="低于红线的次数")
|
||
|
|
|
||
|
|
|
||
|
|
class StudentImportResult(BaseModel):
|
||
|
|
total: int = Field(0, description="文件里的数据行数")
|
||
|
|
success: int = Field(0, description="成功导入")
|
||
|
|
failed: int = Field(0, description="失败行数")
|
||
|
|
errors: list[dict[str, Any]] = Field(default_factory=list, description="失败明细:行号 + 原因")
|