""" Transaction Service - Core business logic Handles transaction creation, approval, reversal with audit trail """ from datetime import datetime from typing import Optional, Dict, Any from decimal import Decimal import logging from sqlalchemy.orm import Session from app.db.models import Transaction, Account, Family, User from app.security.rbac import RBACEngine, Permission, UserContext logger = logging.getLogger(__name__) class TransactionService: """Manages financial transactions with approval workflow""" APPROVAL_THRESHOLD = 500.0 def __init__(self, db: Session): self.db = db async def create_transaction( self, user_context: UserContext, family_id: int, from_account_id: Optional[int], to_account_id: Optional[int], amount: Decimal, category_id: Optional[int] = None, description: str = "", requires_approval: bool = False, ) -> Dict[str, Any]: """Create new transaction""" RBACEngine.check_permission(user_context, Permission.CREATE_TRANSACTION) RBACEngine.check_family_access(user_context, family_id) if amount <= 0: raise ValueError("Amount must be positive") needs_approval = requires_approval or (float(amount) > self.APPROVAL_THRESHOLD and user_context.role.value != "owner") tx_status = "pending_approval" if needs_approval else "executed" transaction = Transaction( family_id=family_id, created_by_id=user_context.user_id, from_account_id=from_account_id, to_account_id=to_account_id, amount=float(amount), category_id=category_id, description=description, created_at=datetime.utcnow(), ) self.db.add(transaction) self.db.commit() logger.info(f"Transaction created: {transaction.id}") return { "id": transaction.id, "status": tx_status, "amount": float(amount), "requires_approval": needs_approval, } async def confirm_transaction( self, user_context: UserContext, transaction_id: int, family_id: int, ) -> Dict[str, Any]: """Approve pending transaction""" RBACEngine.check_permission(user_context, Permission.APPROVE_TRANSACTION) RBACEngine.check_family_access(user_context, family_id) tx = self.db.query(Transaction).filter_by( id=transaction_id, family_id=family_id, ).first() if not tx: raise ValueError(f"Transaction {transaction_id} not found") tx.status = "executed" tx.approved_by_id = user_context.user_id tx.approved_at = datetime.utcnow() self.db.commit() logger.info(f"Transaction {transaction_id} approved") return { "id": tx.id, "status": "executed", } async def reverse_transaction( self, user_context: UserContext, transaction_id: int, family_id: int, ) -> Dict[str, Any]: """Reverse transaction by creating compensation""" RBACEngine.check_permission(user_context, Permission.REVERSE_TRANSACTION) RBACEngine.check_family_access(user_context, family_id) original = self.db.query(Transaction).filter_by( id=transaction_id, family_id=family_id, ).first() if not original: raise ValueError(f"Transaction {transaction_id} not found") if original.status == "reversed": raise ValueError("Transaction already reversed") reversal = Transaction( family_id=family_id, created_by_id=user_context.user_id, from_account_id=original.to_account_id, to_account_id=original.from_account_id, amount=original.amount, category_id=original.category_id, description=f"Reversal of transaction #{original.id}", status="executed", created_at=datetime.utcnow(), ) original.status = "reversed" original.reversed_at = datetime.utcnow() original.reversed_by_id = user_context.user_id self.db.add(reversal) self.db.commit() logger.info(f"Transaction {transaction_id} reversed, created {reversal.id}") return { "original_id": original.id, "reversal_id": reversal.id, "status": "reversed", }