feat: 数字员工财务报告体系与定时提醒及看板快照调度
- 新增数字员工财务报告生成、邮件投递与渲染调度器 - 引入员工画像扫描调度与定时提醒任务 - 完善财务看板快照、排行口径与部门人员占比计算 - 优化数字员工工作看板仪表盘与技能目录 - 增强前端总览页图表、工作台摘要与顶部导航栏交互 - 新增差旅申请规划推动提醒与报销创建会话状态管理 - 补充财务报告、看板调度、数字员工工作记录测试覆盖
This commit is contained in:
@@ -8,10 +8,12 @@ from sqlalchemy.orm import Session, sessionmaker
|
||||
from sqlalchemy.pool import StaticPool
|
||||
|
||||
from app.db.base import Base
|
||||
from app.models.agent_run import AgentRun
|
||||
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
|
||||
from app.services.finance_dashboard_snapshot import FinanceDashboardSnapshotService
|
||||
|
||||
|
||||
def build_session() -> Session:
|
||||
@@ -165,12 +167,16 @@ def test_finance_dashboard_service_aggregates_claim_budget_and_payment_data() ->
|
||||
assert dashboard.totals["reimbursementCount"] == 2
|
||||
assert dashboard.totals["reimbursementAmount"] == 2000.0
|
||||
assert dashboard.totals["pendingPaymentAmount"] == 0.0
|
||||
assert dashboard.trend["applications"][-1] >= 1
|
||||
assert sum(dashboard.trend["applications"]) >= 1
|
||||
assert "AP-DASH-ADMIN-001" not in str(dashboard.trend)
|
||||
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.department_ranking[0]["employeeCount"] == 1
|
||||
assert dashboard.department_employee_mix[0]["name"] == "财务部 · 陈雨晴"
|
||||
assert dashboard.department_employee_mix[0]["amount"] == 1200.0
|
||||
assert dashboard.employee_ranking[0]["name"] == "陈雨晴"
|
||||
assert dashboard.employee_ranking[0]["count"] == 1
|
||||
assert dashboard.top_claims[0]["claimNo"] == "CLM-DASH-001"
|
||||
assert "AP-DASH-ADMIN-001" not in str(dashboard.top_claims)
|
||||
assert dashboard.budget_summary["ratio"] == 40.0
|
||||
@@ -226,7 +232,7 @@ def test_finance_dashboard_uses_financial_terms_instead_of_approval_terms() -> N
|
||||
ExpenseClaim(
|
||||
claim_no="CLM-DASH-LABEL-003",
|
||||
employee_name="reimbursement-user",
|
||||
department_name="甯傚満閮?,
|
||||
department_name="Market",
|
||||
expense_type="travel",
|
||||
reason="real travel reimbursement",
|
||||
location="Shanghai",
|
||||
@@ -327,10 +333,150 @@ def test_finance_dashboard_uses_financial_terms_instead_of_approval_terms() -> N
|
||||
assert dashboard.trend["claimCount"][-1] == 1
|
||||
assert dashboard.trend["claimAmount"][-1] == 700.0
|
||||
assert dashboard.trend["applications"] == dashboard.trend["claimCount"]
|
||||
assert dashboard.department_ranking[0]["name"] == "市场部"
|
||||
assert dashboard.department_ranking[0]["name"] == "Market"
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user