84 lines
3.5 KiB
Python
84 lines
3.5 KiB
Python
|
|
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
|