feat: 增强风险规则生成引擎与预算中心页面
后端拆分风险规则生成为解释器、语义分析、本体对齐等子模块, 优化模板执行和流程图生成,完善员工种子数据和导入逻辑,增强 报销单权限策略和草稿持久化,前端新增预算中心视图和趋势图 组件,重构审计页面和风险规则测试对话框交互,完善文档中心 和报销创建页面细节,补充单元测试覆盖。
This commit is contained in:
83
server/src/app/services/expense_claim_application_handoff.py
Normal file
83
server/src/app/services/expense_claim_application_handoff.py
Normal file
@@ -0,0 +1,83 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import UTC, datetime
|
||||
from decimal import Decimal
|
||||
from typing import Any
|
||||
|
||||
from app.models.financial_record import ExpenseClaim
|
||||
|
||||
|
||||
APPLICATION_REIMBURSEMENT_TYPE_MAP = {
|
||||
"travel_application": "travel",
|
||||
"purchase_application": "office",
|
||||
"meeting_application": "meeting",
|
||||
"expense_application": "other",
|
||||
"application": "other",
|
||||
}
|
||||
|
||||
|
||||
class ExpenseClaimApplicationHandoffMixin:
|
||||
@staticmethod
|
||||
def _resolve_reimbursement_type_from_application(expense_type: str | None) -> str:
|
||||
normalized = str(expense_type or "").strip().lower()
|
||||
if normalized in APPLICATION_REIMBURSEMENT_TYPE_MAP:
|
||||
return APPLICATION_REIMBURSEMENT_TYPE_MAP[normalized]
|
||||
if normalized.endswith("_application"):
|
||||
return normalized.removesuffix("_application") or "other"
|
||||
return normalized or "other"
|
||||
|
||||
def _create_reimbursement_draft_from_application(
|
||||
self,
|
||||
*,
|
||||
application_claim: ExpenseClaim,
|
||||
approval_flag: dict[str, Any],
|
||||
operator: str,
|
||||
) -> ExpenseClaim:
|
||||
occurred_at = application_claim.occurred_at or datetime.now(UTC)
|
||||
created_at = datetime.now(UTC)
|
||||
draft_claim = ExpenseClaim(
|
||||
claim_no=self._generate_claim_no(occurred_at),
|
||||
employee_id=application_claim.employee_id,
|
||||
employee_name=application_claim.employee_name,
|
||||
department_id=application_claim.department_id,
|
||||
department_name=application_claim.department_name,
|
||||
project_code=application_claim.project_code,
|
||||
expense_type=self._resolve_reimbursement_type_from_application(application_claim.expense_type),
|
||||
reason=application_claim.reason,
|
||||
location=application_claim.location,
|
||||
amount=application_claim.amount or Decimal("0.00"),
|
||||
currency=application_claim.currency or "CNY",
|
||||
invoice_count=0,
|
||||
occurred_at=occurred_at,
|
||||
submitted_at=None,
|
||||
status="draft",
|
||||
approval_stage="待提交",
|
||||
risk_flags_json=[
|
||||
{
|
||||
"source": "application_handoff",
|
||||
"event_type": "expense_application_to_reimbursement_draft",
|
||||
"handoff_event_id": str(uuid.uuid4()),
|
||||
"severity": "info",
|
||||
"label": "申请转报销草稿",
|
||||
"message": (
|
||||
f"费用申请 {application_claim.claim_no} 已由 {operator} 确认审核,"
|
||||
"系统已生成报销草稿。"
|
||||
),
|
||||
"application_claim_id": application_claim.id,
|
||||
"application_claim_no": application_claim.claim_no,
|
||||
"application_budget_amount": str(application_claim.amount or Decimal("0.00")),
|
||||
"application_approval_event_id": str(approval_flag.get("approval_event_id") or ""),
|
||||
"leader_opinion": str(approval_flag.get("opinion") or "").strip(),
|
||||
"created_at": created_at.isoformat(),
|
||||
}
|
||||
],
|
||||
)
|
||||
self.db.add(draft_claim)
|
||||
self.db.flush()
|
||||
|
||||
approval_flag["generated_draft_claim_id"] = draft_claim.id
|
||||
approval_flag["generated_draft_claim_no"] = draft_claim.claim_no
|
||||
approval_flag["handoff_event_type"] = "expense_application_to_reimbursement_draft"
|
||||
approval_flag["handoff_message"] = f"已生成报销草稿 {draft_claim.claim_no}。"
|
||||
return draft_claim
|
||||
Reference in New Issue
Block a user