import enum from datetime import date, datetime from typing import Optional from sqlalchemy import JSON, Column, UniqueConstraint from sqlmodel import Field, Relationship, SQLModel class RecurringItemType(str, enum.Enum): INCOME = "INCOME" EXPENSE = "EXPENSE" SAVINGS = "SAVINGS" class RecurringFrequency(str, enum.Enum): WEEKLY = "WEEKLY" MONTHLY = "MONTHLY" QUARTERLY = "QUARTERLY" BIANNUAL = "BIANNUAL" YEARLY = "YEARLY" class TransactionType(str, enum.Enum): COMPRA = "COMPRA" DEVOLUCION = "DEVOLUCION" DEPOSITO = "DEPOSITO" class TransactionSource(str, enum.Enum): CREDIT_CARD = "CREDIT_CARD" CASH = "CASH" TRANSFER = "TRANSFER" class Currency(str, enum.Enum): CRC = "CRC" USD = "USD" BTC = "BTC" XMR = "XMR" class Bank(str, enum.Enum): BAC = "BAC" BCR = "BCR" DAVIVIENDA = "DAVIVIENDA" FCL = "FCL" ROP = "ROP" VOL = "VOL" MEMP = "MEMP" MPAT = "MPAT" MORTGAGE = "MORTGAGE" class AccountType(str, enum.Enum): BANK = "BANK" PENSION = "PENSION" CRYPTO = "CRYPTO" SAVINGS = "SAVINGS" LIABILITY = "LIABILITY" # --- Category --- class CategoryBase(SQLModel): name: str = Field(index=True, unique=True) icon: str = "tag" auto_match_patterns: Optional[str] = Field( default=None, description="Comma-separated merchant substrings for auto-matching", ) class Category(CategoryBase, table=True): id: Optional[int] = Field(default=None, primary_key=True) transactions: list["Transaction"] = Relationship(back_populates="category") class CategoryCreate(CategoryBase): pass class CategoryRead(CategoryBase): id: int class CategoryUpdate(SQLModel): name: Optional[str] = None icon: Optional[str] = None auto_match_patterns: Optional[str] = None # --- Account --- class AccountBase(SQLModel): bank: Bank currency: Currency label: str balance: float = 0.0 account_type: AccountType = AccountType.BANK next_payment: Optional[float] = None class Account(AccountBase, table=True): id: Optional[int] = Field(default=None, primary_key=True) updated_at: datetime = Field(default_factory=datetime.utcnow) class AccountCreate(AccountBase): pass class AccountRead(AccountBase): id: int updated_at: datetime class AccountUpdate(SQLModel): balance: Optional[float] = None label: Optional[str] = None next_payment: Optional[float] = None # --- Transaction --- class TransactionBase(SQLModel): amount: float currency: Currency = Currency.CRC merchant: str city: Optional[str] = None date: datetime card_type: Optional[str] = None card_last4: Optional[str] = None authorization_code: Optional[str] = None reference: Optional[str] = Field(default=None, index=True) transaction_type: TransactionType = TransactionType.COMPRA source: TransactionSource = TransactionSource.CREDIT_CARD bank: Bank = Bank.BAC notes: Optional[str] = None category_id: Optional[int] = Field(default=None, foreign_key="category.id") class Transaction(TransactionBase, table=True): id: Optional[int] = Field(default=None, primary_key=True) created_at: datetime = Field(default_factory=datetime.utcnow) category: Optional[Category] = Relationship(back_populates="transactions") class TransactionCreate(TransactionBase): pass class TransactionRead(TransactionBase): id: int created_at: datetime category: Optional[CategoryRead] = None class TransactionUpdate(SQLModel): amount: Optional[float] = None currency: Optional[Currency] = None merchant: Optional[str] = None city: Optional[str] = None date: Optional[datetime] = None transaction_type: Optional[TransactionType] = None source: Optional[TransactionSource] = None notes: Optional[str] = None category_id: Optional[int] = None # --- Exchange Rate --- class ExchangeRate(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) date: datetime buy_rate: float sell_rate: float fetched_at: datetime = Field(default_factory=datetime.utcnow) class ExchangeRateRead(SQLModel): buy_rate: float sell_rate: float date: datetime fetched_at: datetime # --- API Token --- class APIToken(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) name: str token_hash: str = Field(index=True) created_at: datetime = Field(default_factory=datetime.utcnow) expires_at: Optional[datetime] = None is_active: bool = True class APITokenCreate(SQLModel): name: str expires_days: Optional[int] = None class APITokenRead(SQLModel): id: int name: str created_at: datetime expires_at: Optional[datetime] is_active: bool # --- User Settings --- class UserSettings(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) key: str = Field(index=True, unique=True, default="default") data: dict = Field( default_factory=dict, sa_column=Column(JSON, nullable=False, server_default="{}"), ) updated_at: datetime = Field(default_factory=datetime.utcnow) class UserSettingsRead(SQLModel): key: str data: dict updated_at: datetime class UserSettingsUpdate(SQLModel): data: dict # --- Recurring Item --- class RecurringItemBase(SQLModel): name: str amount: float currency: Currency = Currency.CRC item_type: RecurringItemType frequency: RecurringFrequency = RecurringFrequency.MONTHLY day_of_month: Optional[int] = None month_of_year: Optional[int] = None override_amounts: Optional[dict] = Field( default=None, sa_column=Column(JSON, nullable=True), ) category_id: Optional[int] = Field(default=None, foreign_key="category.id") is_active: bool = True notes: Optional[str] = None class RecurringItem(RecurringItemBase, table=True): id: Optional[int] = Field(default=None, primary_key=True) created_at: datetime = Field(default_factory=datetime.utcnow) category: Optional[Category] = Relationship() class RecurringItemCreate(RecurringItemBase): pass class RecurringItemRead(RecurringItemBase): id: int created_at: datetime category: Optional[CategoryRead] = None class RecurringItemUpdate(SQLModel): name: Optional[str] = None amount: Optional[float] = None currency: Optional[Currency] = None item_type: Optional[RecurringItemType] = None frequency: Optional[RecurringFrequency] = None day_of_month: Optional[int] = None month_of_year: Optional[int] = None override_amounts: Optional[dict] = None category_id: Optional[int] = None is_active: Optional[bool] = None notes: Optional[str] = None # --- Push Subscription --- class PushSubscription(SQLModel, table=True): id: Optional[int] = Field(default=None, primary_key=True) endpoint: str = Field(unique=True) p256dh: str auth: str created_at: datetime = Field(default_factory=datetime.utcnow) class PushSubscriptionCreate(SQLModel): endpoint: str keys: dict # {"p256dh": "...", "auth": "..."} # --- Pension Snapshot --- class PensionSnapshotBase(SQLModel): fund: Bank contract_number: str period_start: date period_end: date saldo_anterior: float aportes: float rendimientos: float retiros: float traslados: float comision: float correccion: float bonificacion: float saldo_final: float source_filename: str class PensionSnapshot(PensionSnapshotBase, table=True): __table_args__ = ( UniqueConstraint("fund", "period_start", "period_end"), ) id: Optional[int] = Field(default=None, primary_key=True) created_at: datetime = Field(default_factory=datetime.utcnow) class PensionSnapshotRead(PensionSnapshotBase): id: int created_at: datetime