init commit
This commit is contained in:
145
.history/app/services/transaction_service_20251210212053.py
Normal file
145
.history/app/services/transaction_service_20251210212053.py
Normal file
@@ -0,0 +1,145 @@
|
||||
"""
|
||||
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",
|
||||
}
|
||||
Reference in New Issue
Block a user