"""计提公式模板骨架注册与解析。 一期骨架仍是代码内受控集合:页面可维护模板名称、说明、参数绑定(JSON), 但不允许自由表达式,避免注入与算错。数据库定义优先;迁移前的旧库用 `SYSTEM_TEMPLATES` 兜底解析,保证读写与历史行为一致。 """ from dataclasses import dataclass from sqlalchemy.orm import Session @dataclass(frozen=True, slots=True) class FormulaSpec: """公式骨架元数据:参数角色 → 模板参数映射键。 role_labels 把英文角色键翻译成业务可读的中文标签(模板表单展示), role_hints 进一步解释该槽位承载的口径(placeholder 提示)。 """ code: str name: str roles: tuple[str, ...] role_labels: dict[str, str] role_hints: dict[str, str] description: str # 骨架类型(formula_type)。新增骨架需同步执行引擎分派点。 FORMULAS: dict[str, FormulaSpec] = { "linear_decay": FormulaSpec( code="linear_decay", name="天数线性递减", roles=("start", "slope", "floor", "boundary"), role_labels={"start": "起始比例", "slope": "日递减斜率", "floor": "下限比例", "boundary": "递减截止天数"}, role_hints={"start": "回款第 1 天适用的计提比例", "slope": "每延后一天递减的幅度", "floor": "超过截止天数后固定使用的比例", "boundary": "按日递减的天数上限"}, description="按回款天数线性递减,超过截止天数取下限比例", ), "fixed_profit": FormulaSpec( code="fixed_profit", name="折算毛利固定比例", roles=("rate",), role_labels={"rate": "计提比例"}, role_hints={"rate": "折算毛利润额乘以的固定比例"}, description="折算毛利润额 × 固定比例", ), "fixed_sales": FormulaSpec( code="fixed_sales", name="销售额固定比例", roles=("rate",), role_labels={"rate": "计提比例"}, role_hints={"rate": "本人销售额乘以的固定比例"}, description="本人销售额 × 固定比例", ), "manager_share": FormulaSpec( code="manager_share", name="经理团队分成", roles=("team", "company"), role_labels={"team": "团队分成比例", "company": "全公司分成比例"}, role_hints={"team": "本团队业务员绩效乘以的比例", "company": "全公司业务员绩效乘以的比例"}, description="本团队绩效 × 团队比例 + 全公司绩效 × 公司比例", ), "prepay_fixed": FormulaSpec( code="prepay_fixed", name="预付款固定比例", roles=("rate",), role_labels={"rate": "计提比例"}, role_hints={"rate": "预付回款额乘以的固定比例"}, description="预付回款额 × 固定比例", ), } @dataclass(frozen=True, slots=True) class TemplateDefinition: """模板定义解析结果:数据库行或旧库兜底系统模板。""" template_code: str template_name: str formula_type: str params: dict[str, str] description: str | None is_system: bool status: int def get_formula(code: str) -> FormulaSpec: """取骨架元数据;未知编码属配置错误,调用方转用户错误。""" spec = FORMULAS.get(code) if spec is None: raise ValueError(f"公式骨架未注册:{code}") return spec def system_definition(code: str) -> TemplateDefinition | None: """旧库兜底:迁移未应用时按内置注册表合成定义,行为与历史一致。""" spec = TEMPLATES.get(code) if spec is None: return None return TemplateDefinition( template_code=spec.code, template_name=spec.name, formula_type=spec.formula_type, params=dict(spec.params), description=spec.description, is_system=True, status=1, ) def resolve_definition(session: Session, code: str) -> TemplateDefinition: """按编码解析模板定义:数据库优先,旧库兜底系统模板。""" from app.repositories import calc_template_repository row = calc_template_repository.get_by_code(session, code) if row is not None: return TemplateDefinition( template_code=row.template_code, template_name=row.template_name, formula_type=row.formula_type, params=dict(row.params_json or {}), description=row.description, is_system=row.is_system == 1, status=row.status, ) fallback = system_definition(code) if fallback is None: raise ValueError(f"计提公式模板未定义:{code}") return fallback def formula_type_of(session: Session, code: str) -> str: """执行分派用:模板 → 公式骨架类型。""" return resolve_definition(session, code).formula_type # 模板参数角色到参数编码的内置映射(与 V28 种子一致;旧库兜底用)。 _TPL_SALESPERSON = { "start": "salesperson.rate.start", "slope": "salesperson.rate.slope", "floor": "salesperson.rate.floor", "boundary": "salesperson.days.boundary", } @dataclass(frozen=True, slots=True) class TemplateSpec: """旧库兜底模板元数据(历史编码锚点)。""" code: str name: str formula_type: str params: dict[str, str] description: str TEMPLATES: dict[str, TemplateSpec] = { "TPL_SALESPERSON_DECAY": TemplateSpec( code="TPL_SALESPERSON_DECAY", name="业务员个人户·天数线性递减", formula_type="linear_decay", params=_TPL_SALESPERSON, description="分段:≤分界天数 = 起始 − 天数×斜率;>分界天数 = 下限", ), "TPL_MANAGER_SHARE": TemplateSpec( code="TPL_MANAGER_SHARE", name="营销经理·团队与全公司分成", formula_type="manager_share", params={"team": "manager.share.team", "company": "manager.share.company"}, description="聚合:本团队业务员绩效×团队比例 + 全公司业务员绩效×公司比例", ), "TPL_ENTERPRISE_FIXED": TemplateSpec( code="TPL_ENTERPRISE_FIXED", name="企业户·固定计提", formula_type="fixed_profit", params={"rate": "enterprise.rate"}, description="乘法:折算毛利润额 × 计提比例", ), "TPL_INFO_SALES_FIXED": TemplateSpec( code="TPL_INFO_SALES_FIXED", name="信息员·销售额固定比例", formula_type="fixed_sales", params={"rate": "info.rate"}, description="乘法:本人销售额 × 计提比例", ), "TPL_PREPAY_FIXED": TemplateSpec( code="TPL_PREPAY_FIXED", name="预付款·固定计提", formula_type="prepay_fixed", params={"rate": "prepay.rate"}, description="乘法:预付回款额 × 计提比例", ), }