from datetime import datetime, timezone, time, date as dt_date from sqlmodel import SQLModel, Field, Relationship from sqlalchemy import Column, DateTime, text, Numeric, func from enum import Enum from sqlalchemy.dialects.postgresql import JSONB from typing import Optional from decimal import Decimal class ActivityCategory(str, Enum): prayer = "prayer" meal = "meal" habit = "habit" exercise = "exercise" productivity = "productivity" class TransactionCategory(str, Enum): INCOME = "income" EXPENSE = "expense" class User(SQLModel, table=True): __tablename__ = "users" id: int = Field(default=None, primary_key=True) phone_number: str = Field(index=True, unique=True) email: str = Field(index=True, unique=True) hashed_password: str name: Optional[str] = None timezone: str = "Asia/Karachi" created_at: datetime = Field( default_factory=lambda: datetime.now(timezone.utc), sa_column=Column(DateTime(timezone=True), server_default=text("CURRENT_TIMESTAMP"), nullable=False) ) updated_at: datetime = Field( default_factory=lambda: datetime.now(timezone.utc), sa_column=Column( DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), # SQLAlchemy will now handle this dynamically nullable=False )) activity_entries: list["ActivityEntry"] = Relationship(back_populates="user") goals: list["Goal"] = Relationship(back_populates="user") day_logs: list["DayLog"] = Relationship(back_populates="user") wallet: "Wallet" = Relationship(back_populates="user") transactions: list["Transaction"] = Relationship(back_populates="user") class Goal(SQLModel, table=True): __tablename__ = "goals" id: int = Field(default=None, primary_key=True) user_id: int = Field( foreign_key="users.id", index=True ) date: dt_date = Field(default_factory=dt_date.today, index=True) title: str = Field(index=True) description: Optional[str] = None total_productivity_hours: Optional[float] = Field(default=0.0) active_status: Optional[bool] = Field(default=None) activity_entries: list["ActivityEntry"] = Relationship(back_populates="goal") user: "User" = Relationship(back_populates="goals") class DayLog(SQLModel, table=True): __tablename__ = "day_logs" id: int = Field(default=None, primary_key=True) user_id: int = Field( foreign_key="users.id", index=True ) date: dt_date = Field(default_factory=dt_date.today, index=True) bed_time: Optional[time] = None sleep_time: Optional[time] = None wake_time: Optional[time] = None sleep_duration_hours: Optional[float] = None total_productivity_min: Optional[float] = Field(default=0.0) total_calories: Optional[int] = None notes: Optional[str] = None user: "User" = Relationship(back_populates="day_logs") activity_entries: list["ActivityEntry"] = Relationship( back_populates="day_log" ) transactions: list["Transaction"] = Relationship(back_populates="day_log") class ActivityEntry(SQLModel, table=True): __tablename__ = "activity_entries" id: int = Field(default=None, primary_key=True) user_id: int = Field( foreign_key="users.id", index=True ) day_log_id: int = Field( foreign_key="day_logs.id", index=True ) goal_id: Optional[int] = Field( default=None, foreign_key="goals.id", index=True ) goal_name: Optional[str] = None date: dt_date = Field(default_factory=dt_date.today, index=True) category: ActivityCategory = Field(index=True) activity_type_name: str = Field(index=True) note: Optional[str] = None #for dynamic fields like location, quantity_text, what_i_did, result_text, jamaat, #sets, reps, on_time, duration_min, occurred_at, active_status etc. details: dict = Field( default_factory=dict, sa_column=Column(JSONB, server_default='{}') ) user: "User" = Relationship(back_populates="activity_entries") goal: Optional["Goal"] = Relationship(back_populates="activity_entries") day_log: "DayLog" = Relationship( back_populates="activity_entries" ) class Wallet(SQLModel, table=True): """Stores the running total/balance for the user.""" __tablename__ = "wallets" id: int = Field(default=None, primary_key=True) user_id: int = Field(foreign_key="users.id", index=True, unique=True) # Using Numeric/Decimal for precise money math balance: Decimal = Field(default=Decimal("0.00"), sa_column=Column(Numeric(10, 2))) updated_at: datetime = Field( default_factory=lambda: datetime.now(timezone.utc), sa_column=Column(DateTime(timezone=True), server_default=text("CURRENT_TIMESTAMP"), onupdate=text("CURRENT_TIMESTAMP")) ) user: "User" = Relationship(back_populates="wallet") transactions: list["Transaction"] = Relationship(back_populates="wallet") class Transaction(SQLModel, table=True): """Records every addition (income) and subtraction (expense).""" __tablename__ = "transactions" id: int = Field(default=None, primary_key=True) user_id: int = Field(foreign_key="users.id", index=True) day_log_id: int = Field(foreign_key="day_logs.id", index=True) wallet_id: int = Field(foreign_key="wallets.id", index=True) date: dt_date = Field(default_factory=dt_date.today, index=True) category: TransactionCategory = Field(index=True) amount: Decimal = Field(sa_column=Column(Numeric(10, 2))) note: Optional[str] = None # JSONB for the dynamic fields (source, type, litre, dayMode, what_eat) details: dict = Field( default_factory=dict, sa_column=Column(JSONB, server_default='{}') ) # ADDED: Relationships to map back to your other tables user: "User" = Relationship(back_populates="transactions") day_log: "DayLog" = Relationship(back_populates="transactions") wallet: "Wallet" = Relationship(back_populates="transactions")