"""计提参数模型:定义、变更批次与版本条目。 存储设计(M1 评审定稿): - 参数值按**生效日期时点化**存储(逐项版本条目,append-only),核算按业务 日期回溯取值(对照表 #11:按回款到账日匹配参数版本),禁读「当前值」算历史 - 「版本」由变更批次承载:一次保存 = 一个批次 = 一条变更记录;条目逐项留痕 (旧值/新值),满足 #28 版本管理与留痕(需规 2.3.1 四) - 值一律为 Fraction 可解析字符串(如 "4/360",禁 0.011111),解析不经 float - **不设 (param_id, dimension_value, effective_date) 唯一键**:同日多次修正 是合法留痕,解析取 (effective_date, id) 最新一条;唯一键会把同日二次变更 逼成覆盖写、丢失中间值 - 批次与条目均为 append-only(仅 create_time、无 update_time,同 login_logs 先例):带 ON UPDATE 的列会让「未改也被刷新」造成留痕假象 """ from datetime import date, datetime from sqlalchemy import Date, DateTime, ForeignKey, Index, Integer, String, text from sqlalchemy.orm import Mapped, mapped_column from app.models.base import BIGINT_UNSIGNED, TINYINT_TYPE, Base, TimestampMixin, mysql_table_args class ParamDefinition(Base, TimestampMixin): """计提参数定义表。目录元数据(类型/单位/维度/取值域),随迁移种子初始化。""" __tablename__ = "param_definitions" __table_args__ = mysql_table_args("计提参数定义表") id: Mapped[int] = mapped_column( BIGINT_UNSIGNED, primary_key=True, autoincrement=True, comment="主键ID" ) param_code: Mapped[str] = mapped_column( String(64), nullable=False, unique=True, comment="参数编码,如 salesperson.rate.slope" ) param_name: Mapped[str] = mapped_column(String(64), nullable=False, comment="参数名称") param_group: Mapped[str] = mapped_column( String(32), nullable=False, comment="参数分组:业务员个人户/经理分成/企业户/信息员/支付环节/产品阈值/月工资口径", ) value_type: Mapped[str] = mapped_column( String(16), nullable=False, comment="值类型:DECIMAL/INT/BOOL/ENUM/TEXT" ) unit: Mapped[str | None] = mapped_column(String(16), comment="展示单位:‰/%/元/天,无单位为空") dimension_type: Mapped[str] = mapped_column( String(32), nullable=False, server_default="NONE", comment="维度类型:NONE/PRODUCT_CATEGORY/BUSINESS_ROLE", ) options_text: Mapped[str | None] = mapped_column( String(255), comment="枚举选项(竖线分隔),仅 ENUM 使用" ) default_value: Mapped[str | None] = mapped_column( String(64), comment="系统默认值(Fraction 可解析字符串)" ) value_min: Mapped[str | None] = mapped_column(String(64), comment="取值下限(含),校验用") value_max: Mapped[str | None] = mapped_column(String(64), comment="取值上限(含),校验用") description: Mapped[str | None] = mapped_column(String(255), comment="口径说明(配置页展示)") sort_order: Mapped[int] = mapped_column( Integer, nullable=False, server_default="0", comment="显示排序" ) status: Mapped[int] = mapped_column( TINYINT_TYPE, nullable=False, server_default="1", comment="状态:1启用 0停用" ) class ParamChangeBatch(Base): """参数变更批次表(append-only)。 一次提交 = 一个批次;版本号(如 P20261012-01)供对比与追溯引用。 未生效批次可整批撤回(删批次与条目);已生效条目禁改禁删。 """ __tablename__ = "param_change_batches" __table_args__ = mysql_table_args("参数变更批次表") id: Mapped[int] = mapped_column( BIGINT_UNSIGNED, primary_key=True, autoincrement=True, comment="主键ID" ) version_no: Mapped[str] = mapped_column( String(32), nullable=False, unique=True, comment="版本号,如 P20261012-01" ) effective_date: Mapped[date] = mapped_column( Date, nullable=False, comment="生效日期(业务日期口径)" ) change_type: Mapped[int] = mapped_column( TINYINT_TYPE, nullable=False, comment="生效方式:1立即生效 2指定日期生效" ) remark: Mapped[str | None] = mapped_column(String(255), comment="变更说明") operator_id: Mapped[int | None] = mapped_column( BIGINT_UNSIGNED, ForeignKey("users.id", ondelete="SET NULL"), comment="操作人ID(账号删除后置空)", ) operator_name: Mapped[str] = mapped_column( String(64), nullable=False, comment="操作人用户名(冗余固化)" ) item_count: Mapped[int] = mapped_column( Integer, nullable=False, server_default="0", comment="变更参数项数" ) create_time: Mapped[datetime] = mapped_column( DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP"), comment="创建时间" ) class ParamValue(Base): """参数版本条目表(append-only)。 解析规则:按 (param_id, dimension_value) 取 effective_date <= 业务日期 的 ORDER BY effective_date DESC, id DESC 首条(同日多次变更取最新)。 dimension_value 用空串而非 NULL:MySQL 组合索引对 NULL 不去重,空串语义更稳。 """ __tablename__ = "param_values" __table_args__ = mysql_table_args( "参数版本条目表(append-only,按生效日期时点化取值)", Index( "idx_param_values_param_id_dimension_value_effective_date", "param_id", "dimension_value", "effective_date", ), Index("idx_param_values_effective_date", "effective_date"), Index("idx_param_values_batch_id", "batch_id"), ) id: Mapped[int] = mapped_column( BIGINT_UNSIGNED, primary_key=True, autoincrement=True, comment="主键ID" ) param_id: Mapped[int] = mapped_column( BIGINT_UNSIGNED, ForeignKey("param_definitions.id", ondelete="RESTRICT"), nullable=False, comment="参数定义ID(param_definitions.id)", ) dimension_value: Mapped[str] = mapped_column( String(32), nullable=False, server_default=text("''"), comment="维度取值:NONE维度为空串;产品类别/岗位编码为具体值", ) effective_date: Mapped[date] = mapped_column( Date, nullable=False, comment="生效日期(按业务日期时点化取值)" ) value_text: Mapped[str] = mapped_column( String(64), nullable=False, comment="参数值(Fraction 可解析字符串/枚举码/开关)" ) old_value_text: Mapped[str | None] = mapped_column(String(64), comment="变更前值(留痕)") batch_id: Mapped[int] = mapped_column( BIGINT_UNSIGNED, ForeignKey("param_change_batches.id", ondelete="RESTRICT"), nullable=False, comment="变更批次ID(param_change_batches.id)", ) create_time: Mapped[datetime] = mapped_column( DateTime, nullable=False, server_default=text("CURRENT_TIMESTAMP"), comment="创建时间" )