from __future__ import annotations import uuid from datetime import datetime from typing import Any from sqlalchemy import CheckConstraint, DateTime, Index, String, UniqueConstraint, func from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.types import JSON from app.db.base_class import Base class ApprovalActionLedger(Base): """审批类写动作的持久化幂等账本。""" __tablename__ = "approval_action_ledgers" __table_args__ = ( CheckConstraint( "action IN ('approve', 'return', 'pay')", name="ck_approval_action_ledger_action", ), CheckConstraint( "(completed_at IS NULL AND result_status IS NULL " "AND result_approval_stage IS NULL) OR " "(completed_at IS NOT NULL AND result_status IS NOT NULL " "AND result_approval_stage IS NOT NULL)", name="ck_approval_action_ledger_completion", ), UniqueConstraint( "tenant_id", "actor_id", "request_id", name="uq_approval_action_ledger_request", ), Index( "ix_approval_action_ledger_claim_action", "tenant_id", "claim_id", "action", ), ) id: Mapped[str] = mapped_column( String(36), primary_key=True, default=lambda: str(uuid.uuid4()), ) tenant_id: Mapped[str] = mapped_column(String(64), nullable=False) actor_id: Mapped[str] = mapped_column(String(120), nullable=False) request_id: Mapped[str] = mapped_column(String(120), nullable=False) claim_id: Mapped[str] = mapped_column(String(36), nullable=False) action: Mapped[str] = mapped_column(String(20), nullable=False) payload_fingerprint: Mapped[str] = mapped_column(String(80), nullable=False) expected_status: Mapped[str] = mapped_column(String(30), nullable=False) expected_approval_stage: Mapped[str] = mapped_column(String(50), nullable=False) result_status: Mapped[str | None] = mapped_column(String(30), nullable=True) result_approval_stage: Mapped[str | None] = mapped_column(String(50), nullable=True) response_json: Mapped[dict[str, Any] | None] = mapped_column(JSON, nullable=True) completed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), )