Files
WealthySmart/backend/app/models/models.py
Carlos Escalante 4d468036c6 Add user settings endpoint and exchange rate fallback APIs
Backend now stores user settings (dashboard config) in a JSONB column and
exposes CRUD via /settings/. Exchange rate service gains multiple fallback
providers (ExchangeRate-API, currency-api, FloatRates) so USD/CRC rates
stay available when BCCR is down.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-22 14:45:20 -06:00

223 lines
5.0 KiB
Python

import enum
from datetime import datetime
from typing import Optional
from sqlalchemy import Column
from sqlalchemy.dialects.postgresql import JSONB
from sqlmodel import Field, Relationship, SQLModel
class TransactionType(str, enum.Enum):
COMPRA = "COMPRA"
DEVOLUCION = "DEVOLUCION"
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(JSONB, 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