PayrollManagementSystem/backend/app/models/base.py

118 lines
4.4 KiB
Python
Raw Permalink 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.

"""ORM 基类、命名约定与公共列。
命名约定按 PROJECT_STANDARDS 红线设置,使 Alembic autogenerate 产出的
约束名自动符合规范(唯一键 `uk_表_字段`、索引 `idx_表_字段`),
无需在每张表上手工指定 —— 手工指定迟早会漏。
"""
from datetime import datetime
from sqlalchemy import DateTime, MetaData, String, text
from sqlalchemy.dialects.mysql import BIGINT, TINYINT
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
# 约束命名模板。`column_0_name` 取约束涉及的第一列,
# 对单列唯一键/索引即为字段名,符合规范的 uk_表_字段 / idx_表_字段
NAMING_CONVENTION = {
"ix": "idx_%(table_name)s_%(column_0_name)s",
"uq": "uk_%(table_name)s_%(column_0_name)s",
"ck": "ck_%(table_name)s_%(constraint_name)s",
"fk": "fk_%(table_name)s_%(column_0_name)s",
"pk": "pk_%(table_name)s",
}
# 主键与外键统一使用无符号 BIGINT(规范模板为 BIGINT UNSIGNED)。
# 两边类型必须完全一致,否则 MySQL 拒绝建立外键约束。
BIGINT_UNSIGNED = BIGINT(unsigned=True)
# 状态类字段统一用 TINYINT(0/1 或少量枚举值)
TINYINT_TYPE = TINYINT
__all__ = [
"BIGINT_UNSIGNED",
"TINYINT_TYPE",
"Base",
"TPlusSyncMixin",
"TimestampMixin",
"mysql_table_args",
]
class Base(DeclarativeBase):
"""全部 ORM 模型的基类。"""
metadata = MetaData(naming_convention=NAMING_CONVENTION)
class TimestampMixin:
"""创建时间 / 更新时间公共列。
取默认值交给数据库而非应用层:即使有人直接用 SQL 改数据,时间戳依然准确;
应用层赋值则会被绕过。
`update_time` 必须用 `text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP")`
而非 `server_onupdate=func.current_timestamp()` —— 后者只是告诉 ORM
「这个值由服务端维护」,**不会生成 ON UPDATE 子句**,结果是更新时间永远不刷新。
"""
create_time: Mapped[datetime] = mapped_column(
DateTime,
nullable=False,
server_default=text("CURRENT_TIMESTAMP"),
comment="创建时间",
)
update_time: Mapped[datetime] = mapped_column(
DateTime,
nullable=False,
server_default=text("CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP"),
comment="更新时间",
)
class TPlusSyncMixin:
"""T+(用友)数据同步预留列,六张业务主表共用。
对照表 #26:T+ 对接的字段映射与幂等键语义未定(IT 牵头),故四列全部
可空、不设唯一键 —— 键语义未定就锁死唯一约束,对接时大概率返工;
source_no 先建普通索引承担查询与去重,IT 定键后以小迁移升级唯一约束。
同步守则(M4 同步模块必须遵守):同步只写 T+ 侧字段与本组列,
**不覆盖人工维护字段**(岗位/工资标准/归属/客户分类/业务状态等,
各表注释已标注)。同步频率:一周一次定时 + 手动触发(对照表 #10)。
"""
source_no: Mapped[str | None] = mapped_column(
String(64),
index=True,
comment="T+ 原始单据号(同步幂等去重锚点;唯一键待 IT 定键后升级)",
)
t_plus_id: Mapped[str | None] = mapped_column(String(64), comment="T+ 系统内部标识")
sync_batch_no: Mapped[str | None] = mapped_column(
String(32), comment="同步批次号(同步模块写入)"
)
synced_at: Mapped[datetime | None] = mapped_column(DateTime, comment="最近同步时间")
source_updated_at: Mapped[datetime | None] = mapped_column(
DateTime, comment="T+ 源数据更新时间(未确认时可为空)"
)
def mysql_table_args(comment: str, *extra: object) -> tuple[object, ...]:
"""构造 MySQL 表的公共参数,返回可直接赋给 `__table_args__` 的元组。
显式指定引擎、字符集与排序规则:compose 已把服务端排序规则覆盖为
`utf8mb4_general_ci`,若某处按 MySQL 8.0 默认的 `utf8mb4_0900_ai_ci` 建表,
跨表 JOIN 会报 `Illegal mix of collations`。
:param comment: 表注释(规范红线要求中文 COMMENT)
:param extra: 额外的约束对象(UniqueConstraint / Index 等)
"""
return (
*extra,
{
"mysql_engine": "InnoDB",
"mysql_charset": "utf8mb4",
"mysql_collate": "utf8mb4_general_ci",
"comment": comment,
},
)