feat(ai): add expense application feedback ledger

This commit is contained in:
caoxiaozhu
2026-07-14 11:10:55 +08:00
parent 5ed34c2b8f
commit a662cfe6c3
24 changed files with 2159 additions and 34 deletions

View File

@@ -8,6 +8,7 @@ from sqlalchemy import or_, select
from sqlalchemy.exc import IntegrityError
from app.api.deps import CurrentUserContext
from app.models.expense_case import BusinessEvent
from app.models.financial_record import ExpenseClaim
from app.schemas.reimbursement import TravelReimbursementCalculatorRequest
from app.schemas.user_agent import (
@@ -28,6 +29,7 @@ from app.services.document_numbering import (
from app.services.expense_application_draft_events import (
ExpenseApplicationDraftEventService,
)
from app.services.expense_application_learning import ExpenseApplicationLearningService
from app.services.expense_claim_access_policy import ExpenseClaimAccessPolicy
from app.services.expense_claim_risk_stage import with_risk_business_stage
from app.services.travel_reimbursement_calculator import TravelReimbursementCalculatorService
@@ -635,6 +637,27 @@ class UserAgentApplicationSlotMixin:
class UserAgentApplicationPersistenceMixin:
def _record_expense_application_learning(
self,
payload: UserAgentRequest,
facts: dict[str, str],
claim: ExpenseClaim,
trusted_current_user: CurrentUserContext | None,
*,
action_type: str,
business_event: BusinessEvent,
) -> None:
if trusted_current_user is None:
return
ExpenseApplicationLearningService(self.db).record_action(
payload,
facts,
claim,
trusted_current_user,
action_type=action_type,
business_event=business_event,
)
@staticmethod
def _resolve_application_edit_claim_id(context_json: dict[str, object]) -> str:
if not isinstance(context_json, dict):
@@ -703,6 +726,7 @@ class UserAgentApplicationPersistenceMixin:
claim: ExpenseClaim,
*,
submit: bool,
learning_current_user: CurrentUserContext | None = None,
) -> ExpenseClaim:
current_user = self._build_application_current_user(payload)
previous_status = str(claim.status or "").strip()
@@ -732,7 +756,7 @@ class UserAgentApplicationPersistenceMixin:
claim.approval_stage = "待提交"
claim.submitted_at = None
try:
ExpenseApplicationDraftEventService(self.db).record(
_, draft_event = ExpenseApplicationDraftEventService(self.db).record(
payload,
claim,
current_user,
@@ -740,6 +764,14 @@ class UserAgentApplicationPersistenceMixin:
previous_status=previous_status,
previous_approval_stage=previous_approval_stage,
)
self._record_expense_application_learning(
payload,
facts,
claim,
learning_current_user,
action_type="save_draft",
business_event=draft_event,
)
self.db.commit()
self.db.refresh(claim)
return claim
@@ -749,10 +781,28 @@ class UserAgentApplicationPersistenceMixin:
from app.services.expense_claims import ExpenseClaimService
submitted = ExpenseClaimService(self.db).submit_claim(claim.id, current_user)
if submitted is None:
raise ValueError("未找到可修改的申请单。")
return submitted
try:
submitted = ExpenseClaimService(self.db).submit_claim(
claim.id,
current_user,
correlation_id=payload.run_id,
before_commit=(
lambda event: self._record_expense_application_learning(
payload,
facts,
claim,
learning_current_user,
action_type="submit",
business_event=event,
)
),
)
if submitted is None:
raise ValueError("未找到可修改的申请单。")
return submitted
except Exception:
self.db.rollback()
raise
def _create_expense_application_record(
self,
@@ -760,6 +810,7 @@ class UserAgentApplicationPersistenceMixin:
facts: dict[str, str],
*,
submit: bool,
learning_current_user: CurrentUserContext | None = None,
) -> ExpenseClaim:
current_user = self._build_application_current_user(payload)
access_policy = ExpenseClaimAccessPolicy(self.db)
@@ -821,19 +872,41 @@ class UserAgentApplicationPersistenceMixin:
return existing
raise
if not submit:
draft_event_service.record(
_, draft_event = draft_event_service.record(
payload,
claim,
current_user,
event_type="claim_draft_created",
)
self._record_expense_application_learning(
payload,
facts,
claim,
learning_current_user,
action_type="save_draft",
business_event=draft_event,
)
self.db.commit()
self.db.refresh(claim)
return claim
from app.services.expense_claims import ExpenseClaimService
submitted = ExpenseClaimService(self.db).submit_claim(claim.id, current_user)
submitted = ExpenseClaimService(self.db).submit_claim(
claim.id,
current_user,
correlation_id=payload.run_id,
before_commit=(
lambda event: self._record_expense_application_learning(
payload,
facts,
claim,
learning_current_user,
action_type="submit",
business_event=event,
)
),
)
if submitted is None:
raise ValueError("未找到可提交的申请单。")
return submitted
@@ -1212,6 +1285,7 @@ class UserAgentApplicationMixin(UserAgentApplicationSlotMixin, UserAgentApplicat
payload: UserAgentRequest,
*,
risk_flags: list[str],
learning_current_user: CurrentUserContext | None = None,
) -> UserAgentResponse:
facts = self._resolve_expense_application_facts(payload)
step = self._resolve_expense_application_step(payload, facts)
@@ -1224,6 +1298,7 @@ class UserAgentApplicationMixin(UserAgentApplicationSlotMixin, UserAgentApplicat
facts,
editable_claim,
submit=step == "submitted",
learning_current_user=learning_current_user,
)
facts["application_edit_mode"] = "true"
elif step == "submitted":
@@ -1236,12 +1311,14 @@ class UserAgentApplicationMixin(UserAgentApplicationSlotMixin, UserAgentApplicat
payload,
facts,
submit=True,
learning_current_user=learning_current_user,
)
else:
application_claim = self._create_expense_application_record(
payload,
facts,
submit=False,
learning_current_user=learning_current_user,
)
if application_claim is not None:
facts["application_no"] = application_claim.claim_no