Files
PayrollManagementSystem/backend/app/services/calc_templates.py
T
liuwenxinandClaude Code 5ed5665a54 feat(system): 计提公式模板注册表与精确分数计算函数
- formula.py 八个纯函数:回款天数(首日计1)、业务员分段递减(360=6‰/361=4‰)、折算毛利润额化简式(防除零)、乘法方向绩效、以物抵顶/R×R/起付
- param_registry.py:16 项参数元数据(与迁移种子同源)+ Fraction 精确解析(4/360 禁截断)+ 类型/取值域/枚举校验
- calc_templates.py:5 个公式模板注册表(岗位绑定的受控集合)
- 新增 43 项单元用例:精确分数断言(899/90000、27 天、9/25),畸形值拒绝

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-12 16:22:32 +08:00

73 lines
2.6 KiB
Python

"""计提公式模板注册表(代码内有限集合)。
一期不引入规则引擎的落地方式:公式骨架固定(本表枚举),变化的是参数值
(系统设置维护)。岗位角色(business_roles.calc_template)绑定模板编码,
新增岗位 = 选模板 + 配参数,无需改代码(需求 #28)。
新增公式形态(如未来「按产品数量计提」)需要新增模板 —— 一期接受,
岗位表单中模板为带说明的受控下拉,避免误以为可自由写公式。
"""
from dataclasses import dataclass
@dataclass(frozen=True, slots=True)
class TemplateSpec:
"""公式模板元数据。"""
code: str
name: str
params: tuple[str, ...]
description: str
TEMPLATES: dict[str, TemplateSpec] = {
"TPL_SALESPERSON_DECAY": TemplateSpec(
code="TPL_SALESPERSON_DECAY",
name="业务员个人户·天数线性递减",
params=(
"salesperson.rate.start",
"salesperson.rate.slope",
"salesperson.rate.floor",
"salesperson.days.boundary",
),
description="分段:≤分界天数 = 起始 − 天数×斜率;>分界天数 = 下限",
),
"TPL_MANAGER_SHARE": TemplateSpec(
code="TPL_MANAGER_SHARE",
name="营销经理·团队与全公司分成",
params=("manager.share.team", "manager.share.company"),
description="聚合:∑本团队业务员绩效×12.5% + ∑全公司业务员绩效×5%",
),
"TPL_ENTERPRISE_FIXED": TemplateSpec(
code="TPL_ENTERPRISE_FIXED",
name="企业户·固定计提",
params=("enterprise.rate",),
description="乘法:折算毛利润额 × 计提比例",
),
"TPL_INFO_SALES_FIXED": TemplateSpec(
code="TPL_INFO_SALES_FIXED",
name="信息员·销售额固定比例",
params=("info.rate",),
description="乘法:本人销售额 × 计提比例",
),
"TPL_PREPAY_FIXED": TemplateSpec(
code="TPL_PREPAY_FIXED",
name="预付款·固定计提",
params=("prepay.rate",),
description="乘法:回款额 × 计提比例",
),
}
def get_template(code: str) -> TemplateSpec:
"""取模板元数据。未注册编码属代码缺陷(500),不是用户错误。"""
tpl = TEMPLATES.get(code)
if tpl is None:
raise ValueError(f"公式模板编码未注册:{code}")
return tpl
def required_params(code: str) -> tuple[str, ...]:
"""模板声明的参数编码列表(岗位表单展示与校验用)。"""
return get_template(code).params