Files
HOAPro/backend/backend/hoa_app/models.py
2025-07-31 11:27:50 -04:00

149 lines
6.3 KiB
Python

from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey, Enum, Boolean
from sqlalchemy.orm import relationship, declarative_base
import enum
from datetime import datetime
from .logging_config import setup_logging, DEBUG_LOGGING
import logging
Base = declarative_base()
setup_logging()
models_logger = logging.getLogger("hoa_app.models")
class RoleEnum(str, enum.Enum):
admin = "admin"
user = "user"
class Role(Base):
__tablename__ = "roles"
id = Column(Integer, primary_key=True, index=True)
name = Column(Enum(RoleEnum), unique=True, nullable=False)
users = relationship("User", back_populates="role")
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True, nullable=False)
password_hash = Column(String, nullable=False)
role_id = Column(Integer, ForeignKey("roles.id"))
created_at = Column(DateTime, default=datetime.utcnow)
role = relationship("Role", back_populates="users")
class Bucket(Base):
__tablename__ = "buckets"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, nullable=False)
description = Column(String)
accounts = relationship("Account", back_populates="bucket")
class AccountType(Base):
__tablename__ = "account_types"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, nullable=False)
accounts = relationship("Account", back_populates="account_type")
def __init__(self, **kwargs):
super().__init__(**kwargs)
if DEBUG_LOGGING:
models_logger.debug(f"AccountType created: {self.name}")
class Account(Base):
__tablename__ = "accounts"
id = Column(Integer, primary_key=True, index=True)
bucket_id = Column(Integer, ForeignKey("buckets.id"))
account_type_id = Column(Integer, ForeignKey("account_types.id"))
name = Column(String, nullable=False)
institution_name = Column(String, nullable=False)
interest_rate = Column(Float, default=0.0)
maturity_date = Column(DateTime, nullable=True)
balance = Column(Float, default=0.0)
last_validated_at = Column(DateTime, default=datetime.utcnow)
bucket = relationship("Bucket", back_populates="accounts")
account_type = relationship("AccountType", back_populates="accounts")
transactions = relationship("Transaction", back_populates="account", foreign_keys="[Transaction.account_id]")
cash_flows = relationship("CashFlow", back_populates="account")
balance_history = relationship("AccountBalanceHistory", back_populates="account", cascade="all, delete-orphan")
def __init__(self, **kwargs):
super().__init__(**kwargs)
if DEBUG_LOGGING:
models_logger.debug(f"Account created: {self.name} at {self.institution_name}")
def set_balance(self, new_balance):
if self.balance != new_balance:
if DEBUG_LOGGING:
models_logger.debug(f"Balance update for account {self.id}: {self.balance} -> {new_balance}")
self.balance = new_balance
self.last_validated_at = datetime.utcnow()
if DEBUG_LOGGING:
models_logger.debug(f"last_validated_at updated for account {self.id}: {self.last_validated_at}")
class TransactionTypeEnum(str, enum.Enum):
deposit = "deposit"
withdrawal = "withdrawal"
transfer = "transfer"
class Transaction(Base):
__tablename__ = "transactions"
id = Column(Integer, primary_key=True, index=True)
account_id = Column(Integer, ForeignKey("accounts.id"))
type = Column(Enum(TransactionTypeEnum), nullable=False)
amount = Column(Float, nullable=False)
date = Column(DateTime, default=datetime.utcnow)
description = Column(String)
related_account_id = Column(Integer, ForeignKey("accounts.id"), nullable=True)
reconciled = Column(Boolean, default=False) # NEW FIELD
account = relationship("Account", back_populates="transactions", foreign_keys=[account_id])
related_account = relationship("Account", foreign_keys=[related_account_id])
class CashFlowTypeEnum(str, enum.Enum):
inflow = "inflow"
outflow = "outflow"
class CashFlow(Base):
__tablename__ = "cash_flows"
id = Column(Integer, primary_key=True, index=True)
account_id = Column(Integer, ForeignKey("accounts.id"))
type = Column(Enum(CashFlowTypeEnum), nullable=False)
estimate_actual = Column(Boolean, default=True) # True=estimate, False=actual
amount = Column(Float, nullable=False)
date = Column(DateTime, nullable=False)
description = Column(String)
account = relationship("Account", back_populates="cash_flows")
class AccountBalanceHistory(Base):
__tablename__ = "account_balance_history"
id = Column(Integer, primary_key=True, index=True)
account_id = Column(Integer, ForeignKey("accounts.id"), nullable=False, index=True)
balance = Column(Float, nullable=False)
date = Column(DateTime, nullable=False, index=True)
account = relationship("Account", back_populates="balance_history")
class CashFlowCategoryTypeEnum(str, enum.Enum):
income = "income"
expense = "expense"
class CashFlowFundingTypeEnum(str, enum.Enum):
operating = "Operating"
reserve = "Reserve"
class CashFlowCategory(Base):
__tablename__ = "cash_flow_categories"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, nullable=False)
category_type = Column(Enum(CashFlowCategoryTypeEnum), nullable=False)
funding_type = Column(Enum(CashFlowFundingTypeEnum), nullable=False)
entries = relationship("CashFlowEntry", back_populates="category")
class CashFlowEntry(Base):
__tablename__ = "cash_flow_entries"
id = Column(Integer, primary_key=True, index=True)
category_id = Column(Integer, ForeignKey("cash_flow_categories.id"), nullable=False)
year = Column(Integer, nullable=False)
month = Column(Integer, nullable=False)
projected_amount = Column(Float, nullable=True)
actual_amount = Column(Float, nullable=True)
created_by = Column(Integer, ForeignKey("users.id"), nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
category = relationship("CashFlowCategory", back_populates="entries")
user = relationship("User")