""" Transaction Model SQLAlchemy model for financial transactions. Handles income and expense records with precise decimal arithmetic. """ from datetime import datetime, date from decimal import Decimal from app.extensions import db class Transaction(db.Model): """ Transaction model for financial records. Attributes: id (int): Primary key description (str): Transaction description amount (Decimal): Transaction amount (precise decimal) type (str): Transaction type ('income' or 'expense') category_id (int): Foreign key to Category transaction_date (date): Date of transaction notes (str): Optional additional notes created_at (datetime): Creation timestamp """ __tablename__ = 'transactions' # Primary key id = db.Column(db.Integer, primary_key=True) # Transaction information description = db.Column(db.String(255), nullable=False) amount = db.Column(db.Numeric(10, 2), nullable=False) # DECIMAL(10,2) for precise money handling type = db.Column(db.String(10), nullable=False) # 'income' or 'expense' notes = db.Column(db.Text) # Relationships category_id = db.Column(db.Integer, db.ForeignKey('categories.id'), nullable=True) # Dates transaction_date = db.Column(db.Date, nullable=False, default=date.today) created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False) # Database constraints __table_args__ = ( db.CheckConstraint('amount > 0', name='check_amount_positive'), db.CheckConstraint("type IN ('income', 'expense')", name='check_type_valid'), ) def __repr__(self): """String representation of Transaction.""" return f'' def to_dict(self): """Convert transaction to dictionary for JSON serialization.""" return { 'id': self.id, 'description': self.description, 'amount': float(self.amount) if self.amount else 0, 'type': self.type, 'category_id': self.category_id, 'category_name': self.category.name if self.category else None, 'transaction_date': self.transaction_date.isoformat() if self.transaction_date else None, 'notes': self.notes, 'created_at': self.created_at.isoformat() if self.created_at else None } @property def amount_decimal(self): """Get amount as Decimal object for precise calculations.""" return Decimal(str(self.amount)) if self.amount else Decimal('0') @classmethod def get_by_type(cls, transaction_type): """Get transactions by type (income/expense).""" return cls.query.filter_by(type=transaction_type).all() @classmethod def get_by_date_range(cls, start_date, end_date): """Get transactions within date range.""" return cls.query.filter( cls.transaction_date >= start_date, cls.transaction_date <= end_date ).all() @classmethod def get_by_category(cls, category_id): """Get transactions by category.""" return cls.query.filter_by(category_id=category_id).all() @classmethod def calculate_total_by_type(cls, transaction_type): """Calculate total amount for a transaction type.""" result = db.session.query(db.func.sum(cls.amount)).filter_by(type=transaction_type).scalar() return Decimal(str(result)) if result else Decimal('0') @classmethod def get_summary_stats(cls): """Get summary statistics for all transactions.""" total_income = cls.calculate_total_by_type('income') total_expenses = cls.calculate_total_by_type('expense') net_balance = total_income - total_expenses transaction_count = cls.query.count() return { 'total_income': float(total_income), 'total_expenses': float(total_expenses), 'net_balance': float(net_balance), 'transaction_count': transaction_count }