feat(approval): add safe risk disposition workflow

This commit is contained in:
caoxiaozhu
2026-07-16 15:34:58 +08:00
parent ee88a36baf
commit 4940ebc419
64 changed files with 6602 additions and 631 deletions

View File

@@ -4,7 +4,10 @@ from datetime import UTC, datetime
from typing import Any
from app.models.financial_record import ExpenseClaim
from app.services.expense_claim_risk_stage import risk_business_stage_for_claim, with_risk_business_stage
from app.services.expense_claim_risk_stage import (
risk_business_stage_for_claim,
with_risk_business_stage,
)
from app.services.expense_claim_workflow_constants import (
BUDGET_MANAGER_APPROVAL_STAGE,
DIRECT_MANAGER_APPROVAL_STAGE,
@@ -13,18 +16,17 @@ from app.services.expense_claim_workflow_constants import (
class ExpenseClaimWorkflowRepairMixin:
def _repair_duplicate_budget_approval_stages(self, claims: list[ExpenseClaim]) -> None:
repaired_claims = [
def _repair_duplicate_budget_approval_stages(
self,
claims: list[ExpenseClaim],
) -> list[ExpenseClaim]:
"""只修改当前事务中的对象,不在底层辅助方法里提交事务。"""
return [
claim
for claim in claims
if claim is not None and self._repair_duplicate_budget_approval_stage(claim)
]
if not repaired_claims:
return
self.db.commit()
for claim in repaired_claims:
self.db.refresh(claim)
def _repair_duplicate_budget_approval_stage(self, claim: ExpenseClaim) -> bool:
if self._is_expense_application_claim(claim):
@@ -54,7 +56,8 @@ class ExpenseClaimWorkflowRepairMixin:
if isinstance(flag, dict)
and str(flag.get("source") or "").strip() == "manual_approval"
and str(flag.get("event_type") or "").strip() == "expense_claim_approval"
and str(flag.get("previous_approval_stage") or "").strip() == DIRECT_MANAGER_APPROVAL_STAGE
and str(flag.get("previous_approval_stage") or "").strip()
== DIRECT_MANAGER_APPROVAL_STAGE
and str(flag.get("next_approval_stage") or "").strip() == BUDGET_MANAGER_APPROVAL_STAGE
]
for flag in reversed(flags):
@@ -74,11 +77,14 @@ class ExpenseClaimWorkflowRepairMixin:
return any(
isinstance(flag, dict)
and str(flag.get("source") or "").strip() == "approval_flow_repair"
and str(flag.get("event_type") or "").strip() == "duplicate_budget_approval_stage_repaired"
and str(flag.get("event_type") or "").strip()
== "duplicate_budget_approval_stage_repaired"
for flag in list(claim.risk_flags_json or [])
)
def _build_duplicate_budget_stage_repair_flag(self, approval_event: dict[str, Any]) -> dict[str, Any]:
def _build_duplicate_budget_stage_repair_flag(
self, approval_event: dict[str, Any]
) -> dict[str, Any]:
return with_risk_business_stage(
{
"source": "approval_flow_repair",