- V7 四表:performance_ledgers(append-only 版本行,uk(receipt_id,calc_version) 库级防重兜底)/monthly_settlements(月份 uk,通过即锁定终态)/ settlement_items(按人固化可负工资,uk 三列)/adjustment_orders(TZ 单号) - registry +1:approval.adjustment.enabled(19 项,BOOL 首组扩至三开关) - formula 补核算纯函数:clamp_weight_r(从预览内联提取共用)/monthly_salary 三口径/probation_annual_note/manager_team_share(#12 暂口径:基数不含 经理个人户业绩,聚合集中一处答复后只改) - 迁移 downgrade 只保留 drop_table+alter 回滚+种子行反向 DELETE; performance_ledgers 表名复数(test_models 红线断言) - 断言同步:EXPECTED_TABLES 四新表/registry 19/BOOL 三开关/conftest 清理四新表与 ParamValue 边界 22 - schema_sync+全量单测 460 passed Co-Authored-By: Claude Code <noreply@anthropic.com>
174 lines
7.4 KiB
Python
174 lines
7.4 KiB
Python
"""绩效核算台账模型(M3 ④:每笔回款仅核算一次的固化点)。
|
||
|
||
**append-only 版本行**:重算 = 插入 calc_version+1 的新行,旧行仅把
|
||
is_active 翻 0 —— 这是全表唯一的受控 UPDATE(模型注释与 test_models
|
||
均已声明例外),行内容永不改写;历史版本可回溯(重算授权留痕的需求口径)。
|
||
|
||
防重三层:服务层 CAS(UPDATE ... WHERE settlement_status=0,rowcount=0
|
||
即 400)+ FOR UPDATE 行锁 + 库级 uk(receipt_id, calc_version) 兜底。
|
||
|
||
经理聚合行(calc_type=2,receipt_id 为 NULL):月度批处理时按「本团队
|
||
业务员绩效 ×12.5% + 全公司 ×5%」落一行;其唯一性由月结生成的
|
||
settlement_month 串行化 + 服务层保证(MySQL 无部分唯一索引,docstring
|
||
声明即可)。
|
||
|
||
#12 策略点(待业务答复):经理个人户业绩暂不计入聚合基数——聚合集中
|
||
在 settlement_service 的聚合函数,答复后只改该函数。
|
||
"""
|
||
|
||
from datetime import date
|
||
from decimal import Decimal
|
||
|
||
from sqlalchemy import (
|
||
JSON,
|
||
Date,
|
||
ForeignKey,
|
||
Index,
|
||
Numeric,
|
||
String,
|
||
UniqueConstraint,
|
||
)
|
||
from sqlalchemy.orm import Mapped, mapped_column
|
||
|
||
from app.models.base import BIGINT_UNSIGNED, TINYINT_TYPE, Base, TimestampMixin, mysql_table_args
|
||
|
||
# 核算类型(performance_ledgers.calc_type)
|
||
CALC_TYPE_RECEIPT = 1 # 单笔回款核算
|
||
CALC_TYPE_MANAGER_AGGREGATE = 2 # 经理团队聚合(月度批处理)
|
||
|
||
|
||
class PerformanceLedger(Base, TimestampMixin):
|
||
"""绩效核算台账表:核算事实的固化点,报表 2.4.1 的数据源。"""
|
||
|
||
__tablename__ = "performance_ledgers"
|
||
__table_args__ = mysql_table_args(
|
||
"绩效核算台账表(append-only 版本行;重算插新版本旧行仅翻 is_active)",
|
||
# 库级防重兜底:同一回款同版本仅一行(服务层 CAS+行锁之外的第二道
|
||
# 闸);聚合行 receipt_id 为 NULL,MySQL 唯一索引对 NULL 不去重,
|
||
# 天然不约束聚合行
|
||
UniqueConstraint(
|
||
"receipt_id", "calc_version", name="uk_performance_ledgers_receipt_id_calc_version"
|
||
),
|
||
Index("idx_performance_ledgers_month_active", "settlement_month", "is_active"),
|
||
Index(
|
||
"idx_performance_ledgers_month_personnel_type",
|
||
"settlement_month",
|
||
"personnel_id",
|
||
"calc_type",
|
||
),
|
||
Index("idx_performance_ledgers_personnel_id", "personnel_id"),
|
||
Index("idx_performance_ledgers_business_id", "business_id"),
|
||
)
|
||
|
||
id: Mapped[int] = mapped_column(
|
||
BIGINT_UNSIGNED, primary_key=True, autoincrement=True, comment="主键ID"
|
||
)
|
||
receipt_id: Mapped[int | None] = mapped_column(
|
||
BIGINT_UNSIGNED,
|
||
ForeignKey("receipts.id", ondelete="RESTRICT"),
|
||
comment="回款ID(receipts.id;聚合行可为 NULL)",
|
||
)
|
||
calc_version: Mapped[int] = mapped_column(
|
||
TINYINT_TYPE,
|
||
nullable=False,
|
||
server_default="1",
|
||
comment="核算版本(重算+1,旧行 is_active=0)",
|
||
)
|
||
is_active: Mapped[int] = mapped_column(
|
||
TINYINT_TYPE,
|
||
nullable=False,
|
||
server_default="1",
|
||
comment="生效标记:1当前生效 0已被重算替代(重算受控 UPDATE 例外)",
|
||
)
|
||
calc_type: Mapped[int] = mapped_column(
|
||
TINYINT_TYPE,
|
||
nullable=False,
|
||
server_default="1",
|
||
comment="核算类型:1单笔回款 2经理团队聚合(月度)",
|
||
)
|
||
business_id: Mapped[int] = mapped_column(
|
||
BIGINT_UNSIGNED,
|
||
comment="业务ID(固化无外键;聚合行为该经理名下首行业务ID,报表下钻锚点)",
|
||
)
|
||
business_no: Mapped[str] = mapped_column(
|
||
String(32), nullable=False, comment="业务编号(冗余固化,列表免联查)"
|
||
)
|
||
personnel_id: Mapped[int] = mapped_column(
|
||
BIGINT_UNSIGNED,
|
||
ForeignKey("personnel.id", ondelete="RESTRICT"),
|
||
nullable=False,
|
||
comment="归属营销人员ID(personnel.id;核算归属锚点)",
|
||
)
|
||
personnel_name: Mapped[str] = mapped_column(
|
||
String(64), nullable=False, comment="归属营销人员姓名(冗余固化)"
|
||
)
|
||
role_code: Mapped[str] = mapped_column(
|
||
String(32), nullable=False, comment="岗位编码(business_roles.role_code,计提公式锚点)"
|
||
)
|
||
customer_type: Mapped[str] = mapped_column(
|
||
String(32), nullable=False, comment="客户分类:personal/manager_personal/enterprise"
|
||
)
|
||
product_category: Mapped[str] = mapped_column(
|
||
String(32),
|
||
nullable=False,
|
||
comment="产品类别:municipal_water/rail_transit/mixed(混合业务,行级明细入 snapshot_json)",
|
||
)
|
||
ship_date: Mapped[date | None] = mapped_column(Date, comment="发货日期(回款天数基准)")
|
||
receipt_date: Mapped[date] = mapped_column(
|
||
Date, nullable=False, comment="回款日期(参数版本匹配日,裁决 #11)"
|
||
)
|
||
payment_days: Mapped[int | None] = mapped_column(
|
||
comment="回款天数(发货日首日计第 1 天;未指定批次为 NULL;先款后货照实负值)"
|
||
)
|
||
rate_text: Mapped[str] = mapped_column(
|
||
String(32),
|
||
nullable=False,
|
||
comment="计提比例原文(如 4/360,防 Fraction 截断;聚合行存 12.5%/5% 结构说明)",
|
||
)
|
||
converted_profit: Mapped[Decimal | None] = mapped_column(
|
||
Numeric(14, 2), comment="折算毛利润额(可负;混合业务为各行汇总)"
|
||
)
|
||
performance_amount: Mapped[Decimal | None] = mapped_column(
|
||
Numeric(14, 2), comment="绩效金额(可负,裁决:负绩效照算不归零)"
|
||
)
|
||
barter_applied: Mapped[int] = mapped_column(
|
||
TINYINT_TYPE,
|
||
nullable=False,
|
||
server_default="0",
|
||
comment="以物抵顶折算:1已按 80% 折算 0否",
|
||
)
|
||
weight_r: Mapped[int | None] = mapped_column(comment="支付权重 R(回款占销售额百分比向下取整)")
|
||
payment_weight: Mapped[Decimal | None] = mapped_column(
|
||
Numeric(7, 6), comment="支付权重系数 R×R(展示用)"
|
||
)
|
||
payable_amount: Mapped[Decimal | None] = mapped_column(
|
||
Numeric(14, 2), comment="当期应付绩效(绩效 × 支付权重;聚合行为当月汇总)"
|
||
)
|
||
settlement_month: Mapped[str] = mapped_column(
|
||
String(7), nullable=False, comment="归属月(回款日期所在月,YYYY-MM;月结与报表维度)"
|
||
)
|
||
is_probation: Mapped[int] = mapped_column(
|
||
TINYINT_TYPE,
|
||
nullable=False,
|
||
server_default="0",
|
||
comment="试用期:1照算不发放 0否",
|
||
)
|
||
missing_cost: Mapped[int] = mapped_column(
|
||
TINYINT_TYPE,
|
||
nullable=False,
|
||
server_default="0",
|
||
comment="成本缺失:1有明细行缺成本(该行毛利按 0 计)0否",
|
||
)
|
||
snapshot_json: Mapped[dict | None] = mapped_column(
|
||
JSON, comment="计算快照(所用参数版本与行级折算明细,重算可回溯)"
|
||
)
|
||
settlement_id: Mapped[int | None] = mapped_column(
|
||
BIGINT_UNSIGNED,
|
||
ForeignKey("monthly_settlements.id", ondelete="RESTRICT"),
|
||
comment="所属月结ID(月结生成时回填;NULL=未入结)",
|
||
)
|
||
calc_reason: Mapped[str | None] = mapped_column(
|
||
String(255), comment="核算/重算原因(重算必填留痕)"
|
||
)
|
||
remark: Mapped[str | None] = mapped_column(String(255), comment="备注")
|