feat(migration): V6 系统设置/二次分配/审批表与角色参数种子

- 新增 system_settings(公司信息单行)/ business_secondary_allocations(二次
  分配明细,比例不强制合计满额,分摊计算待澄清另批)/ approval_orders+
  approval_steps(三级审批状态机,开关关闭自动通过留痕)
- roles 种子新增营销后勤/财务/领导三职能角色;内置保护改 BUILTIN_ROLE_CODES
  白名单(roles 表无 is_builtin 列,不加列)
- PARAM_REGISTRY 首例 BOOL:两个审批开关(16→18 项,种子双写)
- 双轨同步:V6 导出 SQL + init-dev.sql(表/索引/种子/版本号 79bcc5aa98d4)
- 测试同步:EXPECTED_TABLES 四新表、角色种子断言 5 角色、目录断言 18 项
  八分组、BOOL 校验边界;迁移 downgrade 修 1553(只保留 drop_table)并补
  行级种子反向清理

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
liuwenxinandClaude Code committed 2026-10-15 17:25:23 +08:00
1 parent 94c49138bf
commit acf87fdfdc
14 files changed
+918 -25

No files matched your search

@@ -86,9 +86,10 @@ def _snapshot_value(client: TestClient, admin: dict, param_code: str, *, as_of:
class TestCatalog:
"""参数目录:种子完整性与时点化回溯。"""
def test_目录共16项且覆盖七个分组(self, client: TestClient, admin: dict) -> None:
def test_目录共18项且覆盖八个分组(self, client: TestClient, admin: dict) -> None:
"""V6 起含审批开关组(16 计提项 + 2 BOOL 开关)。"""
items = client.get(_PARAMS, headers=admin["headers"]).json()["data"]
assert len(items) == 16
assert len(items) == 18
groups = {item["param_group"] for item in items}
assert groups == {
"业务员个人户",
@@ -98,6 +99,7 @@ class TestCatalog:
"支付环节",
"产品阈值",
"月工资口径",
"审批开关",
}
def test_企业户比例按到账日回溯_2千分3千分双种子(self, client: TestClient, admin: dict) -> None:
@@ -129,7 +129,7 @@ class TestSchemaSync:
class TestSeedData:
"""字典数据与版本标记。"""
@pytest.mark.parametrize("role_code", ["admin", "user"])
@pytest.mark.parametrize("role_code", ["admin", "user", "marketing_ops", "finance", "leader"])
def test_角色初始化数据在两处都存在(self, role_code: str) -> None:
migration_sql = "\n".join(
path.read_text(encoding="utf-8") for path in sorted(MIGRATION_DIR.glob("V*.sql"))
+4
View File
@@ -20,6 +20,10 @@ EXPECTED_TABLES = {
"login_logs",
"operation_logs",
"business_roles",
"system_settings",
"approval_orders",
"approval_steps",
"business_secondary_allocations",
"param_definitions",
"param_change_batches",
"param_values",
+22 -3
View File
@@ -18,7 +18,7 @@ from app.services.param_registry import (
validate_and_normalize,
)
# 与 db/migration/V3 迁移种子保持一致的 16 项编码
# 与 db/migration 迁移种子保持一致的 18 项编码(V3 十六项 + V6 审批开关两项)
EXPECTED_CODES = {
"salesperson.rate.start",
"salesperson.rate.slope",
@@ -36,14 +36,16 @@ EXPECTED_CODES = {
"salary.basic.mode",
"probation.annual.threshold",
"probation.salary.basic",
"approval.secondary_allocation.enabled",
"approval.monthly_settlement.enabled",
}
class TestRegistry:
"""注册表元数据完整性(与迁移种子同源)。"""
def test_注册表共16项(self) -> None:
assert len(PARAM_REGISTRY) == 16
def test_注册表共18项(self) -> None:
assert len(PARAM_REGISTRY) == 18
def test_编码集合与迁移种子一致(self) -> None:
assert set(PARAM_REGISTRY) == EXPECTED_CODES
@@ -61,10 +63,27 @@ class TestRegistry:
continue
if spec.value_type in {"DECIMAL", "INT"}:
parse_fraction(spec.default_value) # 不可解析会抛 BadRequestError
elif spec.value_type == "BOOL":
assert spec.default_value in {"0", "1"}, f"{spec.code} 默认值非 0/1"
elif spec.value_type == "ENUM":
assert spec.options, f"{spec.code} 为 ENUM 但无选项"
assert spec.default_value in spec.options
def test_BOOL开关_仅接受0与1(self) -> None:
"""V6 首例 BOOL 注册的两个审批开关:合法值与非法值边界。"""
for code in (
"approval.secondary_allocation.enabled",
"approval.monthly_settlement.enabled",
):
spec = PARAM_REGISTRY[code]
assert spec.value_type == "BOOL"
assert validate_and_normalize(spec, "0") == "0"
assert validate_and_normalize(spec, "1") == "1"
with pytest.raises(BadRequestError):
validate_and_normalize(spec, "true")
with pytest.raises(BadRequestError):
validate_and_normalize(spec, "2")
def test_取值域上下限可解析(self) -> None:
for spec in PARAM_REGISTRY.values():
if spec.value_min is not None: