from datetime import datetime from decimal import Decimal from sqlalchemy import String, Boolean, Text, Numeric, ForeignKey, DateTime, func, ARRAY from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base class OperationType(Base): __tablename__ = "operation_types" id: Mapped[int] = mapped_column(primary_key=True) name: Mapped[str] = mapped_column(String(50), unique=True, nullable=False) # income | expense | transfer description: Mapped[str | None] = mapped_column(Text) operations: Mapped[list["Operation"]] = relationship("Operation", back_populates="operation_type") class Operation(Base): __tablename__ = "operations" id: Mapped[int] = mapped_column(primary_key=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False, index=True) account_id: Mapped[int] = mapped_column(ForeignKey("accounts.id"), nullable=False, index=True) category_id: Mapped[int | None] = mapped_column(ForeignKey("categories.id"), nullable=True) operation_type_id: Mapped[int] = mapped_column(ForeignKey("operation_types.id"), nullable=False) amount: Mapped[Decimal] = mapped_column(Numeric(20, 2), nullable=False) currency_id: Mapped[int] = mapped_column(ForeignKey("currencies.id"), nullable=False) amount_converted: Mapped[Decimal | None] = mapped_column(Numeric(20, 2)) # in account currency exchange_rate: Mapped[Decimal | None] = mapped_column(Numeric(20, 6)) date: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, index=True) description: Mapped[str | None] = mapped_column(Text) location_id: Mapped[int | None] = mapped_column(ForeignKey("locations.id"), nullable=True) is_planned: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now() ) user: Mapped["User"] = relationship("User", back_populates="operations") account: Mapped["Account"] = relationship("Account", back_populates="operations") category: Mapped["Category | None"] = relationship("Category", back_populates="operations") operation_type: Mapped["OperationType"] = relationship("OperationType", back_populates="operations") currency: Mapped["Currency"] = relationship("Currency", back_populates="operations") location: Mapped["Location | None"] = relationship("Location", back_populates="operations") relations_from: Mapped[list["OperationRelation"]] = relationship( "OperationRelation", foreign_keys="OperationRelation.operation_from_id", back_populates="operation_from" ) relations_to: Mapped[list["OperationRelation"]] = relationship( "OperationRelation", foreign_keys="OperationRelation.operation_to_id", back_populates="operation_to" ) calendar_events: Mapped[list] = relationship("CalendarEvent", back_populates="related_operation") class OperationRelation(Base): """Links two operations — e.g., the debit and credit legs of a transfer.""" __tablename__ = "operation_relations" id: Mapped[int] = mapped_column(primary_key=True) operation_from_id: Mapped[int] = mapped_column(ForeignKey("operations.id"), nullable=False) operation_to_id: Mapped[int] = mapped_column(ForeignKey("operations.id"), nullable=False) relation_type: Mapped[str] = mapped_column(String(50), nullable=False) # transfer | split | correction created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), server_default=func.now()) operation_from: Mapped["Operation"] = relationship( "Operation", foreign_keys=[operation_from_id], back_populates="relations_from" ) operation_to: Mapped["Operation"] = relationship( "Operation", foreign_keys=[operation_to_id], back_populates="relations_to" )