按 2026-11-02 需求补齐计划交付后端能力: - 角色授权:权限目录化,接口统一准入校验,未登记默认拒绝; 管理员判断改为按数据库角色重新判定,防止权限码持有者绕过 - 系统监控:单进程采集资源、请求与异常指标,分钟级聚合, 阈值配置化;敏感配置增加 repr 保护,Redis 密码 URL 转义 - 试用期计提:按回款日判定并写入快照,年度结清与调整单联动; 人员模型补试用期、离职日期与工资版本历史字段 - 计提模板:公式骨架受控,页面维护名称与参数绑定 - T+ 采集:自动化配置表与进度查询 内置角色禁停用保护对齐模块注释;核算规则版本提为常量并 补充批量核算失败堆栈日志。 影响范围:后端全部模块、数据库迁移 V22-V28 验证:uv run ruff check 通过;922 个单元测试全部通过; 集成测试因本机 MySQL 数据卷被其他容器锁定暂未运行
57 lines
2.4 KiB
Python
57 lines
2.4 KiB
Python
"""正式核算与试算共用计提规则;比例保持 Fraction,产品覆盖存于参数版本。"""
|
||
|
||
from fractions import Fraction
|
||
|
||
from app.services import formula
|
||
|
||
PERSONAL_FIELDS = ("first", "start", "slope", "floor", "boundary")
|
||
BASE_CODES = {
|
||
"first": "salesperson.rate.start",
|
||
"start": "salesperson.rate.start",
|
||
"slope": "salesperson.rate.slope",
|
||
"floor": "salesperson.rate.floor",
|
||
"boundary": "salesperson.days.boundary",
|
||
}
|
||
|
||
|
||
def personal_values(snapshot, category):
|
||
"""未维护产品专属项时,继续使用同日期的通用参数,不复制初始值。"""
|
||
result = {}
|
||
for field in PERSONAL_FIELDS:
|
||
code = f"coefficient.personal.{field}"
|
||
overrides = snapshot._values.get(code, {})
|
||
result[field] = overrides.get(
|
||
category, overrides.get("", snapshot.get(BASE_CODES[field], role_code="salesperson"))
|
||
)
|
||
return result
|
||
|
||
|
||
def decide(params, *, category, customer_type, role_code, days, prepayment=False):
|
||
"""返回纯比例和计算基数类型。经理个人户排除优先于预收/企业户。"""
|
||
if role_code == "sales_manager" or customer_type == "manager_personal":
|
||
return Fraction(0), "converted", "经理本人/经理个人户不单笔计提"
|
||
if role_code == "info_officer":
|
||
return Fraction(str(params.get("info", "1"))) / 1000, "sales", "信息员按销售额计提"
|
||
if prepayment:
|
||
personal = params.get("personal", {}).get(category, params)
|
||
maximum = max(
|
||
Fraction(str(personal.get(key, personal["start"])))
|
||
for key in ("first", "start", "floor")
|
||
)
|
||
return maximum / 1000, "converted", "预收发货后按最高比例计提"
|
||
if customer_type == "enterprise":
|
||
value = params.get("enterprise_by_category", {}).get(category, params["enterprise"])
|
||
return Fraction(str(value)) / 1000, "converted", "企业户按产品分类固定计提"
|
||
values = params.get("personal", {}).get(category, params)
|
||
if days <= 1:
|
||
rate = Fraction(str(values.get("first", values["start"]))) / 1000
|
||
else:
|
||
rate = formula.salesperson_rate(
|
||
days,
|
||
start=Fraction(str(values["start"])),
|
||
slope=Fraction(str(values["slope"])),
|
||
floor=Fraction(str(values["floor"])),
|
||
boundary_days=int(Fraction(str(values.get("boundary", 360)))),
|
||
)
|
||
return rate, "converted", "个人户按产品分类及回款阶段计提"
|