2026-05-30 15:46:51 +08:00
|
|
|
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
|
2026-06-03 09:25:23 +08:00
|
|
|
from app.models.agent_run import AgentRun
|
2026-05-30 15:46:51 +08:00
|
|
|
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
|
2026-06-03 09:25:23 +08:00
|
|
|
from app.services.finance_dashboard_snapshot import FinanceDashboardSnapshotService
|
2026-05-30 15:46:51 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
|
2026-06-02 16:22:59 +08:00
|
|
|
def test_finance_dashboard_service_aggregates_claim_budget_and_payment_data() -> None:
|
2026-05-30 15:46:51 +08:00
|
|
|
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),
|
|
|
|
|
),
|
2026-06-02 16:22:59 +08:00
|
|
|
ExpenseClaim(
|
|
|
|
|
claim_no="AP-DASH-ADMIN-001",
|
|
|
|
|
employee_name="admin",
|
|
|
|
|
department_name="Finance",
|
|
|
|
|
expense_type="travel_application",
|
|
|
|
|
reason="admin pre-approval should not enter reimbursement metrics",
|
|
|
|
|
location="Shanghai",
|
|
|
|
|
amount=Decimal("999999.00"),
|
|
|
|
|
invoice_count=1,
|
|
|
|
|
occurred_at=now - timedelta(minutes=20),
|
|
|
|
|
submitted_at=now - timedelta(minutes=10),
|
|
|
|
|
status="paid",
|
|
|
|
|
approval_stage="payment",
|
|
|
|
|
risk_flags_json=[],
|
|
|
|
|
hermes_risk_flag=False,
|
|
|
|
|
created_at=now - timedelta(minutes=20),
|
|
|
|
|
updated_at=now - timedelta(minutes=10),
|
|
|
|
|
),
|
2026-05-30 15:46:51 +08:00
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
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
|
2026-06-02 16:22:59 +08:00
|
|
|
assert dashboard.totals["reimbursementCount"] == 2
|
|
|
|
|
assert dashboard.totals["reimbursementAmount"] == 2000.0
|
|
|
|
|
assert dashboard.totals["pendingPaymentAmount"] == 0.0
|
2026-06-03 09:25:23 +08:00
|
|
|
assert sum(dashboard.trend["applications"]) >= 1
|
2026-06-02 16:22:59 +08:00
|
|
|
assert "AP-DASH-ADMIN-001" not in str(dashboard.trend)
|
2026-05-30 15:46:51 +08:00
|
|
|
assert dashboard.spend_by_category[0]["value"] == 1200.0
|
|
|
|
|
assert dashboard.department_ranking[0]["name"] == "财务部"
|
|
|
|
|
assert dashboard.department_ranking[0]["amount"] == 1200.0
|
2026-06-03 09:25:23 +08:00
|
|
|
assert dashboard.department_ranking[0]["employeeCount"] == 1
|
|
|
|
|
assert dashboard.department_employee_mix[0]["name"] == "财务部 · 陈雨晴"
|
|
|
|
|
assert dashboard.department_employee_mix[0]["amount"] == 1200.0
|
2026-06-02 16:22:59 +08:00
|
|
|
assert dashboard.employee_ranking[0]["name"] == "陈雨晴"
|
2026-06-03 09:25:23 +08:00
|
|
|
assert dashboard.employee_ranking[0]["count"] == 1
|
2026-06-02 16:22:59 +08:00
|
|
|
assert dashboard.top_claims[0]["claimNo"] == "CLM-DASH-001"
|
|
|
|
|
assert "AP-DASH-ADMIN-001" not in str(dashboard.top_claims)
|
2026-05-30 15:46:51 +08:00
|
|
|
assert dashboard.budget_summary["ratio"] == 40.0
|
|
|
|
|
assert dashboard.budget_summary["used"] == "¥4,000"
|
2026-06-02 16:22:59 +08:00
|
|
|
metric_labels = {item["label"] for item in dashboard.budget_metrics}
|
|
|
|
|
assert {"预算池数量", "总预算", "已用预算", "可用预算", "预警预算池"}.issubset(
|
|
|
|
|
metric_labels
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_finance_dashboard_uses_financial_terms_instead_of_approval_terms() -> None:
|
|
|
|
|
now = datetime.now(UTC)
|
|
|
|
|
|
|
|
|
|
with build_session() as db:
|
|
|
|
|
db.add_all(
|
|
|
|
|
[
|
|
|
|
|
ExpenseClaim(
|
|
|
|
|
claim_no="CLM-DASH-LABEL-001",
|
|
|
|
|
employee_name="林嘉宁",
|
|
|
|
|
department_name="市场部",
|
|
|
|
|
expense_type="travel_application",
|
|
|
|
|
reason="客户拜访差旅",
|
|
|
|
|
location="上海",
|
|
|
|
|
amount=Decimal("700.00"),
|
|
|
|
|
invoice_count=1,
|
|
|
|
|
occurred_at=now - timedelta(hours=2),
|
|
|
|
|
submitted_at=now - timedelta(hours=1),
|
|
|
|
|
status="submitted",
|
|
|
|
|
approval_stage="finance_review",
|
|
|
|
|
risk_flags_json=[{"type": "budget_pressure"}],
|
|
|
|
|
hermes_risk_flag=False,
|
|
|
|
|
created_at=now - timedelta(hours=2),
|
|
|
|
|
updated_at=now - timedelta(hours=1),
|
|
|
|
|
),
|
|
|
|
|
ExpenseClaim(
|
|
|
|
|
claim_no="CLM-DASH-LABEL-002",
|
|
|
|
|
employee_name="周思远",
|
|
|
|
|
department_name="财务部",
|
|
|
|
|
expense_type="meal",
|
|
|
|
|
reason="客户沟通",
|
|
|
|
|
location="杭州",
|
|
|
|
|
amount=Decimal("300.00"),
|
|
|
|
|
invoice_count=1,
|
|
|
|
|
occurred_at=now - timedelta(days=1),
|
|
|
|
|
submitted_at=now - timedelta(days=1),
|
|
|
|
|
status="paid",
|
|
|
|
|
approval_stage="payment",
|
|
|
|
|
risk_flags_json=[],
|
|
|
|
|
hermes_risk_flag=False,
|
|
|
|
|
created_at=now - timedelta(days=1),
|
|
|
|
|
updated_at=now - timedelta(days=1),
|
|
|
|
|
),
|
|
|
|
|
ExpenseClaim(
|
|
|
|
|
claim_no="CLM-DASH-LABEL-003",
|
|
|
|
|
employee_name="reimbursement-user",
|
2026-06-03 09:25:23 +08:00
|
|
|
department_name="Market",
|
2026-06-02 16:22:59 +08:00
|
|
|
expense_type="travel",
|
|
|
|
|
reason="real travel reimbursement",
|
|
|
|
|
location="Shanghai",
|
|
|
|
|
amount=Decimal("700.00"),
|
|
|
|
|
invoice_count=1,
|
|
|
|
|
occurred_at=now - timedelta(hours=2),
|
|
|
|
|
submitted_at=now - timedelta(hours=1),
|
|
|
|
|
status="submitted",
|
|
|
|
|
approval_stage="finance_review",
|
|
|
|
|
risk_flags_json=[],
|
|
|
|
|
hermes_risk_flag=False,
|
|
|
|
|
created_at=now - timedelta(hours=2),
|
|
|
|
|
updated_at=now - timedelta(hours=1),
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
db.add_all(
|
|
|
|
|
[
|
|
|
|
|
RiskObservation(
|
|
|
|
|
observation_key="risk-dashboard-label-001",
|
|
|
|
|
subject_type="expense_claim",
|
|
|
|
|
subject_key="CLM-DASH-LABEL-001",
|
|
|
|
|
subject_label="CLM-DASH-LABEL-001",
|
|
|
|
|
claim_no="CLM-DASH-LABEL-001",
|
|
|
|
|
risk_type="policy",
|
|
|
|
|
risk_signal="missing_material",
|
|
|
|
|
title="材料不完整",
|
|
|
|
|
risk_level="medium",
|
|
|
|
|
status="pending_review",
|
|
|
|
|
created_at=now - timedelta(minutes=30),
|
|
|
|
|
updated_at=now - timedelta(minutes=30),
|
|
|
|
|
),
|
|
|
|
|
RiskObservation(
|
|
|
|
|
observation_key="risk-dashboard-label-002",
|
|
|
|
|
subject_type="expense_claim",
|
|
|
|
|
subject_key="CLM-DASH-LABEL-001",
|
|
|
|
|
subject_label="CLM-DASH-LABEL-001",
|
|
|
|
|
claim_no="CLM-DASH-LABEL-001",
|
|
|
|
|
risk_type="budget",
|
|
|
|
|
risk_signal="budget_pressure",
|
|
|
|
|
title="预算压力偏高",
|
|
|
|
|
risk_level="high",
|
|
|
|
|
status="pending_review",
|
|
|
|
|
created_at=now - timedelta(minutes=20),
|
|
|
|
|
updated_at=now - timedelta(minutes=20),
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
allocation = BudgetAllocation(
|
|
|
|
|
budget_no="BUD-DASH-LABEL-001",
|
|
|
|
|
fiscal_year=now.year,
|
|
|
|
|
period_type="year",
|
|
|
|
|
period_key=f"{now.year}",
|
|
|
|
|
department_name="市场部",
|
|
|
|
|
subject_code="travel",
|
|
|
|
|
subject_name="差旅费",
|
|
|
|
|
original_amount=Decimal("1000.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-LABEL-001",
|
|
|
|
|
allocation_id=allocation.id,
|
|
|
|
|
source_type="expense_claim",
|
|
|
|
|
source_id="CLM-DASH-LABEL-003",
|
|
|
|
|
source_no="CLM-DASH-LABEL-003",
|
|
|
|
|
transaction_type="consume",
|
|
|
|
|
amount=Decimal("1250.00"),
|
|
|
|
|
before_available_amount=Decimal("1000.00"),
|
|
|
|
|
after_available_amount=Decimal("-250.00"),
|
|
|
|
|
operator="finance",
|
|
|
|
|
reason="测试超支",
|
|
|
|
|
created_at=now - timedelta(minutes=10),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
dashboard = FinanceDashboardService(db).build_dashboard(
|
|
|
|
|
range_key="近10日",
|
|
|
|
|
trend_range="近7天",
|
|
|
|
|
department_range="本月",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
spend_names = {item["name"] for item in dashboard.spend_by_category}
|
|
|
|
|
focus_names = {item["name"] for item in dashboard.bottlenecks}
|
|
|
|
|
|
|
|
|
|
assert "差旅" in spend_names
|
|
|
|
|
assert "travel_application" not in str(dashboard.spend_by_category)
|
|
|
|
|
assert "风险" not in str(dashboard.exception_mix)
|
|
|
|
|
assert "异常" not in str(dashboard.exception_mix)
|
|
|
|
|
assert "missing material" not in str(dashboard.exception_mix).lower()
|
|
|
|
|
assert "budget pressure" not in str(dashboard.exception_mix).lower()
|
|
|
|
|
assert dashboard.trend["claimCount"][-1] == 1
|
|
|
|
|
assert dashboard.trend["claimAmount"][-1] == 700.0
|
2026-06-03 15:46:56 +08:00
|
|
|
assert sum(series["data"][-1] for series in dashboard.trend["categoryAmountSeries"]) == 700.0
|
|
|
|
|
assert "travel_application" not in str(dashboard.trend["categoryAmountSeries"])
|
2026-06-02 16:22:59 +08:00
|
|
|
assert dashboard.trend["applications"] == dashboard.trend["claimCount"]
|
2026-06-03 09:25:23 +08:00
|
|
|
assert dashboard.department_ranking[0]["name"] == "Market"
|
2026-06-02 16:22:59 +08:00
|
|
|
assert dashboard.department_ranking[0]["amount"] == 700.0
|
|
|
|
|
assert {"预算超支", "待付款", "高额单据"}.issubset(focus_names)
|
|
|
|
|
assert "风险金额" not in focus_names
|
|
|
|
|
assert "材料待补" not in focus_names
|
|
|
|
|
assert all(item["role"] != "审批节点" for item in dashboard.bottlenecks)
|
|
|
|
|
assert len(dashboard.budget_metrics) == 6
|
2026-06-03 09:25:23 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_finance_dashboard_ranking_range_supports_year_and_all_scope() -> None:
|
|
|
|
|
now = datetime.now(UTC)
|
|
|
|
|
previous_year_time = now - timedelta(days=420)
|
|
|
|
|
|
|
|
|
|
with build_session() as db:
|
|
|
|
|
db.add_all(
|
|
|
|
|
[
|
|
|
|
|
ExpenseClaim(
|
|
|
|
|
claim_no="CLM-RANGE-CURRENT-001",
|
|
|
|
|
employee_name="王明",
|
|
|
|
|
department_name="销售部",
|
|
|
|
|
expense_type="travel",
|
|
|
|
|
reason="本年差旅",
|
|
|
|
|
location="北京",
|
|
|
|
|
amount=Decimal("1000.00"),
|
|
|
|
|
invoice_count=1,
|
|
|
|
|
occurred_at=now - timedelta(days=5),
|
|
|
|
|
submitted_at=now - timedelta(days=5),
|
|
|
|
|
status="paid",
|
|
|
|
|
approval_stage="payment",
|
|
|
|
|
risk_flags_json=[],
|
|
|
|
|
hermes_risk_flag=False,
|
|
|
|
|
created_at=now - timedelta(days=5),
|
|
|
|
|
updated_at=now - timedelta(days=4),
|
|
|
|
|
),
|
|
|
|
|
ExpenseClaim(
|
|
|
|
|
claim_no="CLM-RANGE-CURRENT-002",
|
|
|
|
|
employee_name="赵琳",
|
|
|
|
|
department_name="销售部",
|
|
|
|
|
expense_type="meal",
|
|
|
|
|
reason="本年招待",
|
|
|
|
|
location="上海",
|
|
|
|
|
amount=Decimal("500.00"),
|
|
|
|
|
invoice_count=1,
|
|
|
|
|
occurred_at=now - timedelta(days=8),
|
|
|
|
|
submitted_at=now - timedelta(days=8),
|
|
|
|
|
status="paid",
|
|
|
|
|
approval_stage="payment",
|
|
|
|
|
risk_flags_json=[],
|
|
|
|
|
hermes_risk_flag=False,
|
|
|
|
|
created_at=now - timedelta(days=8),
|
|
|
|
|
updated_at=now - timedelta(days=7),
|
|
|
|
|
),
|
|
|
|
|
ExpenseClaim(
|
|
|
|
|
claim_no="CLM-RANGE-OLD-001",
|
|
|
|
|
employee_name="钱远",
|
|
|
|
|
department_name="销售部",
|
|
|
|
|
expense_type="office",
|
|
|
|
|
reason="历史办公",
|
|
|
|
|
location="广州",
|
|
|
|
|
amount=Decimal("9000.00"),
|
|
|
|
|
invoice_count=1,
|
|
|
|
|
occurred_at=previous_year_time,
|
|
|
|
|
submitted_at=previous_year_time,
|
|
|
|
|
status="paid",
|
|
|
|
|
approval_stage="payment",
|
|
|
|
|
risk_flags_json=[],
|
|
|
|
|
hermes_risk_flag=False,
|
|
|
|
|
created_at=previous_year_time,
|
|
|
|
|
updated_at=previous_year_time,
|
|
|
|
|
),
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
year_dashboard = FinanceDashboardService(db).build_dashboard(
|
|
|
|
|
range_key="近10日",
|
|
|
|
|
trend_range="近7天",
|
|
|
|
|
department_range="本年",
|
|
|
|
|
)
|
|
|
|
|
all_dashboard = FinanceDashboardService(db).build_dashboard(
|
|
|
|
|
range_key="近10日",
|
|
|
|
|
trend_range="近7天",
|
|
|
|
|
department_range="全部",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
assert year_dashboard.department_ranking[0]["amount"] == 1500.0
|
|
|
|
|
assert year_dashboard.department_ranking[0]["employeeCount"] == 2
|
|
|
|
|
assert "CLM-RANGE-OLD-001" not in str(year_dashboard.top_claims)
|
|
|
|
|
assert {item["employee"] for item in year_dashboard.department_employee_mix} == {
|
|
|
|
|
"王明",
|
|
|
|
|
"赵琳",
|
|
|
|
|
}
|
|
|
|
|
assert all_dashboard.department_ranking[0]["amount"] == 10500.0
|
|
|
|
|
assert all_dashboard.department_ranking[0]["employeeCount"] == 3
|
|
|
|
|
assert all_dashboard.top_claims[0]["claimNo"] == "CLM-RANGE-OLD-001"
|
|
|
|
|
assert all_dashboard.department_employee_mix[0]["employee"] == "钱远"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_finance_dashboard_snapshot_service_persists_digital_employee_snapshot() -> None:
|
|
|
|
|
now = datetime.now(UTC)
|
|
|
|
|
|
|
|
|
|
with build_session() as db:
|
|
|
|
|
db.add(
|
|
|
|
|
ExpenseClaim(
|
|
|
|
|
claim_no="CLM-SNAPSHOT-001",
|
|
|
|
|
employee_name="snapshot-user",
|
|
|
|
|
department_name="Finance",
|
|
|
|
|
expense_type="travel",
|
|
|
|
|
reason="snapshot test",
|
|
|
|
|
location="Shanghai",
|
|
|
|
|
amount=Decimal("880.00"),
|
|
|
|
|
invoice_count=1,
|
|
|
|
|
occurred_at=now - timedelta(hours=1),
|
|
|
|
|
submitted_at=now - timedelta(minutes=50),
|
|
|
|
|
status="paid",
|
|
|
|
|
approval_stage="payment",
|
|
|
|
|
risk_flags_json=[],
|
|
|
|
|
hermes_risk_flag=False,
|
|
|
|
|
created_at=now - timedelta(hours=1),
|
|
|
|
|
updated_at=now - timedelta(minutes=40),
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
service = FinanceDashboardSnapshotService(db)
|
|
|
|
|
first = service.build_dashboard(
|
|
|
|
|
range_key="近30日",
|
|
|
|
|
trend_range="近12天",
|
|
|
|
|
department_range="本月",
|
|
|
|
|
)
|
|
|
|
|
second = service.build_dashboard(
|
|
|
|
|
range_key="近30日",
|
|
|
|
|
trend_range="近12天",
|
|
|
|
|
department_range="本月",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
runs = [
|
|
|
|
|
run
|
|
|
|
|
for run in db.query(AgentRun).filter(AgentRun.agent == "hermes").all()
|
|
|
|
|
if (run.route_json or {}).get("task_type") == "finance_dashboard_snapshot"
|
|
|
|
|
]
|
|
|
|
|
assert first.totals["reimbursementCount"] == 1
|
|
|
|
|
assert second.generated_at == first.generated_at
|
|
|
|
|
assert len(runs) == 1
|
|
|
|
|
assert runs[0].status == "succeeded"
|
|
|
|
|
assert runs[0].route_json["task_type"] == "finance_dashboard_snapshot"
|
|
|
|
|
assert runs[0].route_json["snapshot_payload"]["totals"]["reimbursementAmount"] == 880.0
|