from __future__ import annotations from datetime import datetime from typing import Any import uuid from sqlalchemy import BigInteger, DateTime, ForeignKey, Index, JSON, String, Text, UniqueConstraint from sqlalchemy.orm import Mapped, mapped_column from govoplan_core.db.base import Base, TimestampMixin def new_uuid() -> str: return str(uuid.uuid4()) class PaymentObligation(Base, TimestampMixin): __tablename__ = "payment_obligations" __table_args__ = ( UniqueConstraint("tenant_id", "payment_id", name="uq_payment_obligation"), UniqueConstraint( "tenant_id", "source_module", "idempotency_key", name="uq_payment_request_idempotency", ), UniqueConstraint( "tenant_id", "payment_reference", name="uq_payment_reference" ), Index( "ix_payment_obligation_source", "tenant_id", "source_module", "source_resource_type", "source_resource_id", ), Index( "ix_payment_obligation_state", "tenant_id", "status", "requested_at", ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) payment_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) payment_reference: Mapped[str] = mapped_column( String(32), nullable=False, index=True ) source_module: Mapped[str] = mapped_column(String(120), nullable=False, index=True) source_resource_type: Mapped[str] = mapped_column( String(120), nullable=False, index=True ) source_resource_id: Mapped[str] = mapped_column( String(255), nullable=False, index=True ) amount_minor: Mapped[int] = mapped_column(BigInteger, nullable=False) currency: Mapped[str] = mapped_column(String(3), nullable=False, index=True) subject: Mapped[str] = mapped_column(Text, nullable=False) status: Mapped[str] = mapped_column(String(30), nullable=False, index=True) idempotency_key: Mapped[str] = mapped_column(String(255), nullable=False) request_sha256: Mapped[str] = mapped_column(String(64), nullable=False) requested_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, index=True ) requested_by_ref: Mapped[str] = mapped_column( String(255), nullable=False, index=True ) due_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, index=True ) settled_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, index=True ) context_refs: Mapped[dict[str, str]] = mapped_column( JSON, default=dict, nullable=False ) details: Mapped[dict[str, Any]] = mapped_column( "metadata", JSON, default=dict, nullable=False ) class PaymentReconciliation(Base, TimestampMixin): __tablename__ = "payment_reconciliations" __table_args__ = ( UniqueConstraint( "tenant_id", "reconciliation_id", name="uq_payment_reconciliation" ), UniqueConstraint( "tenant_id", "payment_row_id", "idempotency_key", name="uq_payment_reconciliation_idempotency", ), Index( "ix_payment_reconciliation_payment", "tenant_id", "payment_row_id", "recorded_at", ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) reconciliation_id: Mapped[str] = mapped_column( String(36), nullable=False, index=True ) payment_row_id: Mapped[str] = mapped_column( ForeignKey("payment_obligations.id", ondelete="RESTRICT"), nullable=False, index=True, ) mode: Mapped[str] = mapped_column(String(30), nullable=False, index=True) amount_minor: Mapped[int] = mapped_column(BigInteger, nullable=False) currency: Mapped[str] = mapped_column(String(3), nullable=False) transaction_reference: Mapped[str] = mapped_column( String(255), nullable=False, index=True ) evidence_ref: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) idempotency_key: Mapped[str] = mapped_column(String(255), nullable=False) request_sha256: Mapped[str] = mapped_column(String(64), nullable=False) received_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, index=True ) recorded_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, index=True ) recorded_by_ref: Mapped[str] = mapped_column( String(255), nullable=False, index=True ) details: Mapped[dict[str, Any]] = mapped_column( "metadata", JSON, default=dict, nullable=False ) class PaymentEvent(Base, TimestampMixin): __tablename__ = "payment_events" __table_args__ = ( UniqueConstraint("tenant_id", "event_id", name="uq_payment_event"), Index( "ix_payment_event_stream", "tenant_id", "payment_row_id", "occurred_at", ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) event_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) payment_row_id: Mapped[str] = mapped_column( ForeignKey("payment_obligations.id", ondelete="RESTRICT"), nullable=False, index=True, ) event_type: Mapped[str] = mapped_column(String(120), nullable=False, index=True) status: Mapped[str] = mapped_column(String(30), nullable=False, index=True) occurred_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, index=True ) actor_ref: Mapped[str] = mapped_column(String(255), nullable=False, index=True) payload: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False) __all__ = ["PaymentEvent", "PaymentObligation", "PaymentReconciliation"]