feat: 新增员工行为画像算法与费用风险标签体系
后端新增员工行为画像算法模块,支持标签规则引擎和评分计算, 完善员工模型、银行信息、序列化和导入逻辑,优化报销审批流 和工作流常量,增强 Hermes 同步和知识同步能力,前端新增费 用画像详情弹窗、雷达图和风险卡片组件,完善登录页和工作台 样式,优化文档中心和归档中心交互,补充单元测试。
This commit is contained in:
@@ -7,10 +7,13 @@ from typing import Any
|
||||
from app.api.deps import CurrentUserContext
|
||||
from app.services.expense_claim_workflow_constants import (
|
||||
APPROVAL_DONE_STAGE,
|
||||
ARCHIVE_ACCOUNTING_STAGE,
|
||||
BUDGET_MANAGER_APPROVAL_STAGE,
|
||||
DIRECT_MANAGER_APPROVAL_STAGE,
|
||||
FINANCE_APPROVAL_STAGE,
|
||||
PAYMENT_PAID_STAGE,
|
||||
PAYMENT_PAID_STATUS,
|
||||
PAYMENT_PENDING_STAGE,
|
||||
PAYMENT_PENDING_STATUS,
|
||||
)
|
||||
|
||||
|
||||
@@ -67,9 +70,9 @@ class ExpenseClaimApprovalFlowMixin:
|
||||
approval_source = "finance_approval"
|
||||
event_type = "expense_claim_finance_approval"
|
||||
label = "财务审核通过"
|
||||
next_status = "approved"
|
||||
next_stage = ARCHIVE_ACCOUNTING_STAGE
|
||||
default_message = "{operator} 已完成财务审核,进入归档入账。"
|
||||
next_status = PAYMENT_PENDING_STATUS
|
||||
next_stage = PAYMENT_PENDING_STAGE
|
||||
default_message = "{operator} 已完成财务审核,进入待付款。"
|
||||
else:
|
||||
raise ValueError("当前节点不支持审批通过。")
|
||||
|
||||
@@ -160,6 +163,65 @@ class ExpenseClaimApprovalFlowMixin:
|
||||
|
||||
return claim
|
||||
|
||||
def mark_claim_paid(
|
||||
self,
|
||||
claim_id: str,
|
||||
current_user: CurrentUserContext,
|
||||
):
|
||||
claim = self.get_claim(claim_id, current_user)
|
||||
if claim is None:
|
||||
return None
|
||||
|
||||
normalized_status = str(claim.status or "").strip().lower()
|
||||
if normalized_status == PAYMENT_PAID_STATUS:
|
||||
raise ValueError("该报销单已付款,无需重复确认。")
|
||||
if normalized_status != PAYMENT_PENDING_STATUS:
|
||||
raise ValueError("只有待付款状态的报销单可以确认已付款。")
|
||||
if not self._access_policy.can_mark_claim_paid(current_user, claim):
|
||||
raise ValueError("只有财务人员或高级财务人员可以确认付款,且不能处理本人单据。")
|
||||
|
||||
before_json = self._serialize_claim(claim)
|
||||
operator = self._access_policy.resolve_current_user_display_name(current_user)
|
||||
previous_stage = str(claim.approval_stage or "").strip()
|
||||
payment_flag = {
|
||||
"source": "payment",
|
||||
"event_type": "expense_claim_payment_completed",
|
||||
"payment_event_id": str(uuid.uuid4()),
|
||||
"severity": "info",
|
||||
"label": "付款完成",
|
||||
"message": f"{operator} 已确认付款,报销单进入已付款。",
|
||||
"operator": operator,
|
||||
"operator_username": current_user.username,
|
||||
"operator_role_codes": [
|
||||
str(item).strip().lower()
|
||||
for item in current_user.role_codes
|
||||
if str(item).strip()
|
||||
],
|
||||
"previous_status": str(claim.status or "").strip(),
|
||||
"previous_approval_stage": previous_stage,
|
||||
"next_status": PAYMENT_PAID_STATUS,
|
||||
"next_approval_stage": PAYMENT_PAID_STAGE,
|
||||
"created_at": datetime.now(UTC).isoformat(),
|
||||
}
|
||||
|
||||
claim.status = PAYMENT_PAID_STATUS
|
||||
claim.approval_stage = PAYMENT_PAID_STAGE
|
||||
claim.risk_flags_json = [*list(claim.risk_flags_json or []), payment_flag]
|
||||
|
||||
self.db.commit()
|
||||
self.db.refresh(claim)
|
||||
|
||||
self.audit_service.log_action(
|
||||
actor=operator,
|
||||
action="expense_claim.mark_paid",
|
||||
resource_type="expense_claim",
|
||||
resource_id=claim.id,
|
||||
before_json=before_json,
|
||||
after_json=self._serialize_claim(claim),
|
||||
)
|
||||
|
||||
return claim
|
||||
|
||||
@staticmethod
|
||||
def _resolve_latest_approval_opinion(claim, *, source: str) -> str:
|
||||
for flag in reversed(list(claim.risk_flags_json or [])):
|
||||
|
||||
Reference in New Issue
Block a user