feat: 扩展风险规则体系、审批动态路由与预算中心列表化改造
- 新增 25+ 条风险规则(预算/报销/申请/通用类),完善风险规则模拟与反馈发布机制 - 引入费用审批动态路由、平台风险分级、预审与风险阶段管理 - 预算中心列表化改造,优化票据夹仪表盘与数字员工工作看板 - 新增 Hermes 风险线索收集器、Agent 链路追踪中心 - 扩展数字员工能力库(18 个领域 Skill)与交通费用自动预估 - 完善报销申请快速预览、权限控制与前端测试覆盖
This commit is contained in:
@@ -4,7 +4,7 @@ from datetime import UTC, datetime, timedelta
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import or_, select
|
||||
from sqlalchemy import func, or_, select
|
||||
from sqlalchemy.orm import Session, selectinload
|
||||
|
||||
from app.algorithem.employee_behavior_profile import (
|
||||
@@ -102,7 +102,8 @@ class EmployeeBehaviorProfileService(EmployeeBehaviorProfileMetricHelpers):
|
||||
commit: bool = True,
|
||||
) -> list[EmployeeBehaviorProfileSnapshot]:
|
||||
self.ensure_storage_ready()
|
||||
employee = self.db.get(Employee, employee_id)
|
||||
requested_employee_id = str(employee_id or "").strip()
|
||||
employee = self._resolve_employee_by_identifier(requested_employee_id)
|
||||
if employee is None:
|
||||
return []
|
||||
|
||||
@@ -161,10 +162,11 @@ class EmployeeBehaviorProfileService(EmployeeBehaviorProfileMetricHelpers):
|
||||
expense_type_scope: str = "overall",
|
||||
) -> EmployeeProfileLatestRead:
|
||||
self.ensure_storage_ready()
|
||||
employee = self.db.get(Employee, employee_id)
|
||||
requested_employee_id = str(employee_id or "").strip()
|
||||
employee = self._resolve_employee_by_identifier(requested_employee_id)
|
||||
if employee is None:
|
||||
return EmployeeProfileLatestRead(
|
||||
employee_id=employee_id,
|
||||
employee_id=requested_employee_id,
|
||||
scene=scene,
|
||||
window_days=window_days,
|
||||
expense_type_scope=expense_type_scope,
|
||||
@@ -172,22 +174,23 @@ class EmployeeBehaviorProfileService(EmployeeBehaviorProfileMetricHelpers):
|
||||
)
|
||||
|
||||
resolved_scope = self._resolve_scope_from_claim(claim_id, expense_type_scope)
|
||||
resolved_employee_id = employee.id
|
||||
rows = self._load_latest_snapshots(
|
||||
employee_id=employee_id,
|
||||
employee_id=resolved_employee_id,
|
||||
window_days=window_days,
|
||||
expense_type_scope=resolved_scope,
|
||||
scene=scene,
|
||||
)
|
||||
if not rows and claim_id:
|
||||
self.refresh_employee_profiles(
|
||||
employee_id=employee_id,
|
||||
employee_id=resolved_employee_id,
|
||||
window_days=(window_days,),
|
||||
expense_type_scope=resolved_scope,
|
||||
source_task_type="api_on_demand",
|
||||
claim_id=claim_id,
|
||||
)
|
||||
rows = self._load_latest_snapshots(
|
||||
employee_id=employee_id,
|
||||
employee_id=resolved_employee_id,
|
||||
window_days=window_days,
|
||||
expense_type_scope=resolved_scope,
|
||||
scene=scene,
|
||||
@@ -201,6 +204,31 @@ class EmployeeBehaviorProfileService(EmployeeBehaviorProfileMetricHelpers):
|
||||
expense_type_scope=resolved_scope,
|
||||
)
|
||||
|
||||
def _resolve_employee_by_identifier(self, identifier: str) -> Employee | None:
|
||||
normalized = str(identifier or "").strip()
|
||||
if not normalized:
|
||||
return None
|
||||
|
||||
employee = self.db.get(Employee, normalized)
|
||||
if employee is not None:
|
||||
return employee
|
||||
|
||||
normalized_email = normalized.lower()
|
||||
conditions = [
|
||||
Employee.name == normalized,
|
||||
Employee.employee_no == normalized,
|
||||
]
|
||||
if "@" in normalized_email:
|
||||
conditions.append(func.lower(Employee.email) == normalized_email)
|
||||
|
||||
stmt = (
|
||||
select(Employee)
|
||||
.where(or_(*conditions))
|
||||
.order_by(Employee.created_at.asc())
|
||||
.limit(1)
|
||||
)
|
||||
return self.db.scalars(stmt).first()
|
||||
|
||||
def _build_window_context(
|
||||
self,
|
||||
*,
|
||||
|
||||
Reference in New Issue
Block a user