PayrollManagementSystem/backend/app/services/calc_templates.py
liuwenxin b3de35c2ec fix(核算): 对齐抵顶口径并完善界面细节
回款确认时间收敛到秒级;抵顶回款不再自动折算并保留标记。补齐公式模板角色标签与提示、指导成本登记入口及基础布局、分页、设置等响应式细节。同步忽略本地排查产物。
2026-11-06 16:27:45 +08:00

194 lines
6.8 KiB
Python
Raw 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.

"""计提公式模板骨架注册与解析。
一期骨架仍是代码内受控集合:页面可维护模板名称、说明、参数绑定(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="乘法:预付回款额 × 计提比例",
),
}