import enum import uuid from datetime import date, datetime from typing import TYPE_CHECKING from sqlalchemy import ( CheckConstraint, Date, DateTime, Enum, ForeignKey, Index, Integer, String, Uuid, func, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base if TYPE_CHECKING: from app.models.category import Category class TransactionType(str, enum.Enum): income = "income" expense = "expense" class Transaction(Base): __tablename__ = "transactions" __table_args__ = ( CheckConstraint("amount_cents > 0", name="ck_transactions_amount_positive"), Index("ix_transactions_user_date", "user_id", "transaction_date", "deleted_at"), Index("ix_transactions_user_category", "user_id", "category_id", "deleted_at"), ) id: Mapped[uuid.UUID] = mapped_column(Uuid, primary_key=True, default=uuid.uuid4) user_id: Mapped[uuid.UUID] = mapped_column( Uuid, ForeignKey("users.id", ondelete="CASCADE"), nullable=False ) category_id: Mapped[uuid.UUID] = mapped_column( Uuid, ForeignKey("categories.id"), nullable=False ) amount_cents: Mapped[int] = mapped_column(Integer, nullable=False) type: Mapped[TransactionType] = mapped_column( Enum(TransactionType, name="transactiontype", native_enum=False, length=10), nullable=False, ) description: Mapped[str | None] = mapped_column(String(255), nullable=True) transaction_date: Mapped[date] = mapped_column(Date, nullable=False) deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=False), nullable=True) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=False), nullable=False, server_default=func.now() ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=False), nullable=False, server_default=func.now() ) # Relationships — use selectinload() explicitly; lazy="raise" prevents accidental N+1 category: Mapped["Category"] = relationship("Category", lazy="raise")