feat: 新增风险图谱算法与系统仪表盘及操作反馈体系
后端新增风险图谱算法模块、风险观察与反馈服务、规则 DSL 校验器和可解释性引擎,完善系统仪表盘和财务仪表盘统计, 优化 agent 运行和编排执行链路,清理旧开发文档,前端新增 系统趋势、负载热力图等多种仪表盘图表组件,完善操作反馈 对话框和工作台日期选择器,优化报销创建和审批详情交互, 补充单元测试覆盖。
This commit is contained in:
155
server/tests/test_finance_dashboard_service.py
Normal file
155
server/tests/test_finance_dashboard_service.py
Normal file
@@ -0,0 +1,155 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import Session, sessionmaker
|
||||
from sqlalchemy.pool import StaticPool
|
||||
|
||||
from app.db.base import Base
|
||||
from app.models.budget import BudgetAllocation, BudgetTransaction
|
||||
from app.models.financial_record import ExpenseClaim
|
||||
from app.models.risk_observation import RiskObservation
|
||||
from app.services.finance_dashboard import FinanceDashboardService
|
||||
|
||||
|
||||
def build_session() -> Session:
|
||||
engine = create_engine(
|
||||
"sqlite+pysqlite:///:memory:",
|
||||
connect_args={"check_same_thread": False},
|
||||
poolclass=StaticPool,
|
||||
)
|
||||
Base.metadata.create_all(bind=engine)
|
||||
session_factory = sessionmaker(bind=engine, autoflush=False, autocommit=False)
|
||||
return session_factory()
|
||||
|
||||
|
||||
def test_finance_dashboard_service_aggregates_claim_risk_and_budget_data() -> None:
|
||||
now = datetime.now(UTC)
|
||||
|
||||
with build_session() as db:
|
||||
db.add_all(
|
||||
[
|
||||
ExpenseClaim(
|
||||
claim_no="CLM-DASH-001",
|
||||
employee_name="陈雨晴",
|
||||
department_name="财务部",
|
||||
expense_type="travel",
|
||||
reason="项目差旅",
|
||||
location="广州",
|
||||
amount=Decimal("1200.00"),
|
||||
invoice_count=2,
|
||||
occurred_at=now - timedelta(hours=4),
|
||||
submitted_at=now - timedelta(hours=3),
|
||||
status="submitted",
|
||||
approval_stage="finance_review",
|
||||
risk_flags_json=[],
|
||||
hermes_risk_flag=False,
|
||||
created_at=now - timedelta(hours=4),
|
||||
updated_at=now - timedelta(hours=3),
|
||||
),
|
||||
ExpenseClaim(
|
||||
claim_no="CLM-DASH-002",
|
||||
employee_name="顾成宇",
|
||||
department_name="研发中心",
|
||||
expense_type="meal",
|
||||
reason="客户招待",
|
||||
location="深圳",
|
||||
amount=Decimal("800.00"),
|
||||
invoice_count=1,
|
||||
occurred_at=now - timedelta(days=1, hours=2),
|
||||
submitted_at=now - timedelta(days=1, hours=1),
|
||||
status="paid",
|
||||
approval_stage="payment",
|
||||
risk_flags_json=[{"label": "招待费超标"}],
|
||||
hermes_risk_flag=False,
|
||||
created_at=now - timedelta(days=1, hours=2),
|
||||
updated_at=now - timedelta(days=1),
|
||||
),
|
||||
ExpenseClaim(
|
||||
claim_no="CLM-DASH-003",
|
||||
employee_name="李文静",
|
||||
department_name="行政部",
|
||||
expense_type="office",
|
||||
reason="办公用品",
|
||||
location="珠海",
|
||||
amount=Decimal("5000.00"),
|
||||
invoice_count=3,
|
||||
occurred_at=now - timedelta(hours=1),
|
||||
submitted_at=None,
|
||||
status="draft",
|
||||
approval_stage=None,
|
||||
risk_flags_json=[],
|
||||
hermes_risk_flag=False,
|
||||
created_at=now - timedelta(hours=1),
|
||||
updated_at=now - timedelta(hours=1),
|
||||
),
|
||||
]
|
||||
)
|
||||
db.add(
|
||||
RiskObservation(
|
||||
observation_key="risk-dashboard-001",
|
||||
subject_type="expense_claim",
|
||||
subject_key="CLM-DASH-002",
|
||||
subject_label="CLM-DASH-002",
|
||||
claim_no="CLM-DASH-002",
|
||||
risk_type="policy",
|
||||
risk_signal="amount_outlier",
|
||||
title="金额异常",
|
||||
risk_level="high",
|
||||
status="pending_review",
|
||||
created_at=now - timedelta(hours=2),
|
||||
updated_at=now - timedelta(hours=2),
|
||||
)
|
||||
)
|
||||
allocation = BudgetAllocation(
|
||||
budget_no="BUD-DASH-001",
|
||||
fiscal_year=now.year,
|
||||
period_type="year",
|
||||
period_key=f"{now.year}",
|
||||
department_name="财务部",
|
||||
subject_code="travel",
|
||||
subject_name="差旅费",
|
||||
original_amount=Decimal("10000.00"),
|
||||
adjusted_amount=Decimal("0.00"),
|
||||
status="active",
|
||||
warning_threshold=Decimal("80.00"),
|
||||
control_action="warn",
|
||||
)
|
||||
db.add(allocation)
|
||||
db.flush()
|
||||
db.add(
|
||||
BudgetTransaction(
|
||||
transaction_no="BTX-DASH-001",
|
||||
allocation_id=allocation.id,
|
||||
source_type="expense_claim",
|
||||
source_id="CLM-DASH-002",
|
||||
source_no="CLM-DASH-002",
|
||||
transaction_type="consume",
|
||||
amount=Decimal("4000.00"),
|
||||
before_available_amount=Decimal("10000.00"),
|
||||
after_available_amount=Decimal("6000.00"),
|
||||
operator="finance",
|
||||
reason="测试消耗",
|
||||
created_at=now - timedelta(hours=1),
|
||||
)
|
||||
)
|
||||
db.commit()
|
||||
|
||||
dashboard = FinanceDashboardService(db).build_dashboard(
|
||||
range_key="近10日",
|
||||
trend_range="近7天",
|
||||
department_range="本月",
|
||||
)
|
||||
|
||||
assert dashboard.has_real_data is True
|
||||
assert dashboard.totals["pendingCount"] == 1
|
||||
assert dashboard.totals["pendingAmount"] == 1200.0
|
||||
assert dashboard.totals["riskCount"] == 1
|
||||
assert dashboard.trend["applications"][-1] >= 1
|
||||
assert dashboard.spend_by_category[0]["value"] == 1200.0
|
||||
assert dashboard.department_ranking[0]["name"] == "财务部"
|
||||
assert dashboard.department_ranking[0]["amount"] == 1200.0
|
||||
assert dashboard.budget_summary["ratio"] == 40.0
|
||||
assert dashboard.budget_summary["used"] == "¥4,000"
|
||||
Reference in New Issue
Block a user