mirror of
https://github.com/escalante29/WealthySmart.git
synced 2026-05-19 09:28:47 +02:00
All checks were successful
Deploy to VPS / deploy (push) Successful in 56s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
359 lines
8.3 KiB
Python
359 lines
8.3 KiB
Python
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"
|
|
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
|
|
|
|
|
|
# --- Balance Override ---
|
|
|
|
|
|
class BalanceOverride(SQLModel, table=True):
|
|
__table_args__ = (UniqueConstraint("year", "month"),)
|
|
id: Optional[int] = Field(default=None, primary_key=True)
|
|
year: int
|
|
month: int
|
|
override_balance: float
|
|
created_at: datetime = Field(default_factory=datetime.utcnow)
|
|
updated_at: datetime = Field(default_factory=datetime.utcnow)
|
|
|
|
|
|
class BalanceOverrideCreate(SQLModel):
|
|
override_balance: float
|
|
|
|
|
|
class BalanceOverrideRead(SQLModel):
|
|
id: int
|
|
year: int
|
|
month: int
|
|
override_balance: float
|
|
updated_at: datetime
|