feat(后端): 添加授权体系、系统监控与试用期计提

按 2026-11-02 需求补齐计划交付后端能力:
- 角色授权:权限目录化,接口统一准入校验,未登记默认拒绝;
  管理员判断改为按数据库角色重新判定,防止权限码持有者绕过
- 系统监控:单进程采集资源、请求与异常指标,分钟级聚合,
  阈值配置化;敏感配置增加 repr 保护,Redis 密码 URL 转义
- 试用期计提:按回款日判定并写入快照,年度结清与调整单联动;
  人员模型补试用期、离职日期与工资版本历史字段
- 计提模板:公式骨架受控,页面维护名称与参数绑定
- T+ 采集:自动化配置表与进度查询

内置角色禁停用保护对齐模块注释;核算规则版本提为常量并
补充批量核算失败堆栈日志。

影响范围:后端全部模块、数据库迁移 V22-V28
验证:uv run ruff check 通过;922 个单元测试全部通过;
集成测试因本机 MySQL 数据卷被其他容器锁定暂未运行
This commit is contained in:
liuwenxin committed 2026-11-03 11:20:55 +08:00
1 parent 02fb06e0af
commit 1461a8d113
149 files changed
+8736 -368

No files matched your search

+50 -11
View File
@@ -6,11 +6,12 @@
"""
from fractions import Fraction
from types import SimpleNamespace
import pytest
from app.core.exceptions import BadRequestError
from app.services.calc_templates import TEMPLATES, get_template, required_params
from app.services.calc_templates import FORMULAS, TEMPLATES, get_formula
from app.services.param_registry import (
PARAM_REGISTRY,
get_spec,
@@ -22,6 +23,8 @@ from app.services.param_registry import (
# + V7 调整单开关;V8 撤试用期统一工资参数 #16、增经理个人户基数开关 #12;
# V10 增工作台预警规则三项 #10)
EXPECTED_CODES = {
"coefficient.personal.first", "coefficient.personal.start", "coefficient.personal.slope",
"coefficient.personal.floor", "coefficient.personal.boundary",
"salesperson.rate.start",
"salesperson.rate.slope",
"salesperson.rate.floor",
@@ -47,11 +50,44 @@ EXPECTED_CODES = {
}
def test_目录说明使用当前规则且不改写已有参数值(monkeypatch):
"""旧数据库的说明可更新,历史生效值和日期必须保持原样。"""
from datetime import date
from app.services import param_service
spec = get_spec("approval.monthly_settlement.enabled")
definition = SimpleNamespace(
param_code=spec.code,
param_name=spec.name,
param_group=spec.group,
value_type=spec.value_type,
unit=spec.unit,
dimension_type=spec.dimension_type,
default_value=spec.default_value,
value_min=spec.value_min,
value_max=spec.value_max,
description="旧说明:关闭后自动记录通过痕迹",
)
monkeypatch.setattr(param_service.param_repository, "list_definitions", lambda *a, **k: [definition])
monkeypatch.setattr(
param_service.param_repository,
"resolve_values",
lambda *a: [(spec.code, "", "1", date(2026, 1, 1))],
)
item = param_service.get_catalog(None, as_of=date(2026, 11, 2))[0]
assert item.description == spec.description
assert item.values[0].value_text == "1"
assert item.values[0].effective_date == date(2026, 1, 1)
class TestRegistry:
"""注册表元数据完整性(与迁移种子同源)。"""
def test_注册表共22项(self) -> None:
assert len(PARAM_REGISTRY) == 22
def test_注册表共27项(self) -> None:
assert len(PARAM_REGISTRY) == 27
def test_编码集合与迁移种子一致(self) -> None:
assert set(PARAM_REGISTRY) == EXPECTED_CODES
@@ -161,20 +197,23 @@ class TestValidateAndNormalize:
class TestTemplates:
"""公式模板注册表:有限集合,声明参数必须真实存在。"""
"""公式模板:骨架受控,内置注册表的参数绑定必须真实存在。"""
def test_共五个模板(self) -> None:
def test_共五个内置模板且五个骨架(self) -> None:
assert len(TEMPLATES) == 5
assert len(FORMULAS) == 5
def test_模板声明参数均在注册表内(self) -> None:
def test_内置模板参数绑定均在注册表内且覆盖骨架角色(self) -> None:
for tpl in TEMPLATES.values():
for code in tpl.params:
spec = get_formula(tpl.formula_type)
assert set(tpl.params) == set(spec.roles), tpl.code
for code in tpl.params.values():
assert code in PARAM_REGISTRY, f"{tpl.code} 声明了未注册参数 {code}"
def test_取模板声明参数(self) -> None:
params = required_params("TPL_SALESPERSON_DECAY")
assert "salesperson.rate.slope" in params
params = TEMPLATES["TPL_SALESPERSON_DECAY"].params
assert params["slope"] == "salesperson.rate.slope"
def test_未知模板抛ValueError_属代码缺陷(self) -> None:
def test_未知骨架抛ValueError(self) -> None:
with pytest.raises(ValueError):
get_template("TPL_NOT_EXIST")
get_formula("NOT_EXIST")