feat(dashboard): 工作台四区块实时聚合与风险预警参数

- 后端 /api/v1/dashboard 单端点返回四区块:经营总览(绩效/应付/人数/团队数/月结状态/近 6 个月趋势)、待办(待审批 + 回款未指定批次 + 同步显式降级)、风险预警(回款逾期/审批超时,阈值参数化)、最近动态(操作日志)
- dashboard_service 实时派生(不落表、不引调度器);参数快照一次解析;总览只做 SQL 聚合,不复用报表的重查询
- V10 迁移新增预警参数三项(warning.enabled / warning.receipt.overdue.days=90 / warning.approval.timeout.days=3),参数注册表 19→22,init-dev 与 V10 SQL 双轨同步
- 测试:test_dashboard 5 例(权限文案/总览对账+非法月份 400/待办三类/预警命中与参数关闭/动态来源);全量 852 passed
- 依赖闭包并入:core/month.py 月份校验、api/v1/base/business_roles.py(router 已挂载的岗位只读端点)、test_month_validation
- 修复:alembic --sql 在 Windows 按控制台 GBK 输出,V10 SQL 以 UTF-8 重新导出(README 已记)

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
liuwenxinandClaude Code committed 2026-10-19 18:44:15 +08:00
1 parent b7cfdfa50a
commit 6ff202d637
18 files changed
+1262 -25

No files matched your search

@@ -0,0 +1,55 @@
"""月份校验单元测试。
守的口径:报表、月结、调整单入口都必须拦住 `2026-13`、`0000-01` 这类
非法月份。只校验长度会让 ValueError 一路传到 `calendar.monthrange`,
最终变成 500 而不是参数错误 400。
"""
import pytest
from pydantic import ValidationError
from app.core.month import parse_month, validate_month
from app.schemas.report import DepartmentSummaryQuery, PersonalDetailQuery
from app.schemas.settlement import AdjustmentCreateIn, SettlementGenerateIn
class Test月份校验:
"""合法月份通过,非法月份在入口即拒绝。"""
@pytest.mark.parametrize("month", ["2026-01", "2026-12", "2000-06"])
def test_合法月份_通过(self, month: str):
assert validate_month(month) == month
year, mon = parse_month(month)
assert (year, mon) == (int(month[:4]), int(month[5:7]))
@pytest.mark.parametrize(
"month",
["2026-13", "2026-00", "2026-1", "2026-1a", "0000-01", "abc", "2026/10"],
)
def test_非法月份_抛ValueError(self, month: str):
with pytest.raises(ValueError):
validate_month(month)
@pytest.mark.parametrize("month", ["2026-13", "2026-00", "0000-01"])
def test_月结与调整单_schema拒绝非法月份(self, month: str):
with pytest.raises(ValidationError):
SettlementGenerateIn(month=month)
with pytest.raises(ValidationError):
AdjustmentCreateIn(
settlement_month=month,
personnel_id=1,
direction=1,
amount="100.00",
reason="测试",
)
@pytest.mark.parametrize("month", ["2026-13", "2026-00", "0000-01"])
def test_报表查询_schema拒绝非法月份(self, month: str):
with pytest.raises(ValidationError):
DepartmentSummaryQuery(settlement_month=month)
with pytest.raises(ValidationError):
PersonalDetailQuery(settlement_month=month)
def test_报表可选月份_空串规范为None(self):
"""axios 未剔除空串时不能误报格式错误。"""
assert PersonalDetailQuery(settlement_month="").settlement_month is None
+8 -4
View File
@@ -18,8 +18,9 @@ from app.services.param_registry import (
validate_and_normalize,
)
# 与 db/migration 迁移种子保持一致的 19 项编码(V3 十六项 + V6 审批开关两项
# + V7 调整单开关;V8 撤试用期统一工资参数 #16、增经理个人户基数开关 #12)
# 与 db/migration 迁移种子保持一致的 22 项编码(V3 十六项 + V6 审批开关两项
# + V7 调整单开关;V8 撤试用期统一工资参数 #16、增经理个人户基数开关 #12;
# V10 增工作台预警规则三项 #10)
EXPECTED_CODES = {
"salesperson.rate.start",
"salesperson.rate.slope",
@@ -40,14 +41,17 @@ EXPECTED_CODES = {
"approval.monthly_settlement.enabled",
"approval.adjustment.enabled",
"manager.personal.included",
"warning.enabled",
"warning.receipt.overdue.days",
"warning.approval.timeout.days",
}
class TestRegistry:
"""注册表元数据完整性(与迁移种子同源)。"""
def test_注册表共19项(self) -> None:
assert len(PARAM_REGISTRY) == 19
def test_注册表共22项(self) -> None:
assert len(PARAM_REGISTRY) == 22
def test_编码集合与迁移种子一致(self) -> None:
assert set(PARAM_REGISTRY) == EXPECTED_CODES