feat: 扩展风险规则体系、审批动态路由与预算中心列表化改造
- 新增 25+ 条风险规则(预算/报销/申请/通用类),完善风险规则模拟与反馈发布机制 - 引入费用审批动态路由、平台风险分级、预审与风险阶段管理 - 预算中心列表化改造,优化票据夹仪表盘与数字员工工作看板 - 新增 Hermes 风险线索收集器、Agent 链路追踪中心 - 扩展数字员工能力库(18 个领域 Skill)与交通费用自动预估 - 完善报销申请快速预览、权限控制与前端测试覆盖
This commit is contained in:
@@ -36,6 +36,7 @@ from app.services.document_intelligence import build_document_insight
|
||||
from app.services.document_numbering import is_application_claim_no
|
||||
from app.services.expense_claim_access_policy import ExpenseClaimAccessPolicy
|
||||
from app.services.expense_claim_approval_flow import ExpenseClaimApprovalFlowMixin
|
||||
from app.services.expense_claim_approval_routing import ExpenseClaimApprovalRoutingMixin
|
||||
from app.services.expense_claim_attachment_presentation import ExpenseClaimAttachmentPresentation
|
||||
from app.services.expense_claim_attachment_storage import ExpenseClaimAttachmentStorage
|
||||
from app.services.expense_claim_application_handoff import ExpenseClaimApplicationHandoffMixin
|
||||
@@ -49,8 +50,10 @@ from app.services.expense_claim_draft_flow import ExpenseClaimDraftFlowMixin
|
||||
from app.services.expense_claim_draft_persistence import ExpenseClaimDraftPersistenceMixin
|
||||
from app.services.expense_claim_errors import ExpenseClaimSubmissionBlockedError
|
||||
from app.services.expense_claim_pagination import ExpenseClaimPaginationMixin
|
||||
from app.services.expense_claim_pre_review import ExpenseClaimPreReviewMixin
|
||||
from app.services.expense_claim_ontology_resolvers import ExpenseClaimOntologyResolverMixin
|
||||
from app.services.expense_claim_read_model import ExpenseClaimReadModelMixin
|
||||
from app.services.expense_claim_risk_stage import with_risk_business_stage
|
||||
from app.services.expense_claim_review_preview import ExpenseClaimReviewPreviewMixin
|
||||
from app.services.expense_claim_constants import (
|
||||
EXPENSE_TYPE_LABELS,
|
||||
@@ -131,7 +134,9 @@ from app.services.ocr import OcrService
|
||||
class ExpenseClaimService(
|
||||
ExpenseClaimPaginationMixin,
|
||||
ExpenseClaimApprovalFlowMixin,
|
||||
ExpenseClaimApprovalRoutingMixin,
|
||||
ExpenseClaimApplicationHandoffMixin,
|
||||
ExpenseClaimPreReviewMixin,
|
||||
ExpenseClaimBudgetFlowMixin,
|
||||
ExpenseClaimAttachmentOperationsMixin,
|
||||
ExpenseClaimReviewPreviewMixin,
|
||||
@@ -197,7 +202,7 @@ class ExpenseClaimService(
|
||||
.order_by(ExpenseClaim.created_at.desc(), ExpenseClaim.occurred_at.desc())
|
||||
)
|
||||
stmt = self._access_policy.apply_claim_scope(stmt, current_user)
|
||||
return list(self.db.scalars(stmt).all())
|
||||
return self._access_policy.attach_budget_approval_snapshots(list(self.db.scalars(stmt).all()))
|
||||
|
||||
def list_approval_claims(self, current_user: CurrentUserContext) -> list[ExpenseClaim]:
|
||||
stmt = (
|
||||
@@ -210,7 +215,7 @@ class ExpenseClaimService(
|
||||
.order_by(ExpenseClaim.submitted_at.desc(), ExpenseClaim.created_at.desc())
|
||||
)
|
||||
stmt = self._access_policy.apply_approval_claim_scope(stmt, current_user)
|
||||
return list(self.db.scalars(stmt).all())
|
||||
return self._access_policy.attach_budget_approval_snapshots(list(self.db.scalars(stmt).all()))
|
||||
|
||||
def list_archived_claims(self, current_user: CurrentUserContext) -> list[ExpenseClaim]:
|
||||
stmt = (
|
||||
@@ -236,7 +241,7 @@ class ExpenseClaimService(
|
||||
.where(ExpenseClaim.id == claim_id)
|
||||
)
|
||||
stmt = self._access_policy.apply_claim_scope(stmt, current_user, include_approval_scope=True)
|
||||
return self.db.scalar(stmt)
|
||||
return self._access_policy.attach_budget_approval_snapshot(self.db.scalar(stmt))
|
||||
|
||||
def can_view_budget_analysis(self, current_user: CurrentUserContext, claim: ExpenseClaim | None = None) -> bool:
|
||||
if claim is None:
|
||||
@@ -468,27 +473,44 @@ class ExpenseClaimService(
|
||||
for flag in list(claim.risk_flags_json or [])
|
||||
if not (
|
||||
isinstance(flag, dict)
|
||||
and str(flag.get("source") or "").strip() in {"submission_review", "attachment_analysis"}
|
||||
and str(flag.get("source") or "").strip()
|
||||
in {"submission_review", "attachment_analysis"}
|
||||
)
|
||||
]
|
||||
submit_flag = {
|
||||
"source": "application_submission",
|
||||
"event_type": "expense_application_submission",
|
||||
"severity": "info",
|
||||
"label": "申请提交",
|
||||
"message": "费用申请已提交至直属领导审批,请等待审核结果。",
|
||||
"previous_status": str(claim.status or "").strip(),
|
||||
"previous_approval_stage": str(claim.approval_stage or "").strip(),
|
||||
"next_status": "submitted",
|
||||
"next_approval_stage": "直属领导审批",
|
||||
"created_at": submitted_at.isoformat(),
|
||||
}
|
||||
platform_review = self.evaluate_platform_risk_rules(
|
||||
claim,
|
||||
business_stage="expense_application",
|
||||
)
|
||||
platform_flags = list(platform_review.get("flags") or [])
|
||||
submit_flag = with_risk_business_stage(
|
||||
{
|
||||
"source": "application_submission",
|
||||
"event_type": "expense_application_submission",
|
||||
"severity": "info",
|
||||
"label": "申请提交",
|
||||
"message": "费用申请已提交至直属领导审批,请等待审核结果。",
|
||||
"previous_status": str(claim.status or "").strip(),
|
||||
"previous_approval_stage": str(claim.approval_stage or "").strip(),
|
||||
"next_status": "submitted",
|
||||
"next_approval_stage": "直属领导审批",
|
||||
"created_at": submitted_at.isoformat(),
|
||||
},
|
||||
"expense_application",
|
||||
)
|
||||
claim.status = "submitted"
|
||||
claim.approval_stage = "直属领导审批"
|
||||
claim.risk_flags_json = self._append_budget_flags([*preserved_flags, submit_flag], budget_flags)
|
||||
claim.risk_flags_json = self._append_budget_flags(
|
||||
[*preserved_flags, submit_flag, *platform_flags],
|
||||
budget_flags,
|
||||
business_stage="expense_application",
|
||||
)
|
||||
claim.submitted_at = submitted_at
|
||||
else:
|
||||
claim.risk_flags_json = self._append_budget_flags(claim.risk_flags_json, budget_flags)
|
||||
claim.risk_flags_json = self._append_budget_flags(
|
||||
claim.risk_flags_json,
|
||||
budget_flags,
|
||||
business_stage="reimbursement",
|
||||
)
|
||||
review_result = self._run_ai_submission_review(claim)
|
||||
|
||||
claim.status = str(review_result.get("status") or "supplement")
|
||||
@@ -681,6 +703,7 @@ class ExpenseClaimService(
|
||||
claim.risk_flags_json = self._append_budget_flags(
|
||||
[*list(claim.risk_flags_json or []), return_flag],
|
||||
budget_flags,
|
||||
business_stage="expense_application" if is_application_claim else "reimbursement",
|
||||
)
|
||||
|
||||
self.db.commit()
|
||||
@@ -717,10 +740,6 @@ class ExpenseClaimService(
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user