init commit
This commit is contained in:
13
app/services/finance/__init__.py
Normal file
13
app/services/finance/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""Finance service module"""
|
||||
|
||||
from app.services.finance.transaction_service import TransactionService
|
||||
from app.services.finance.budget_service import BudgetService
|
||||
from app.services.finance.goal_service import GoalService
|
||||
from app.services.finance.account_service import AccountService
|
||||
|
||||
__all__ = [
|
||||
"TransactionService",
|
||||
"BudgetService",
|
||||
"GoalService",
|
||||
"AccountService",
|
||||
]
|
||||
60
app/services/finance/account_service.py
Normal file
60
app/services/finance/account_service.py
Normal file
@@ -0,0 +1,60 @@
|
||||
"""Account service"""
|
||||
|
||||
from typing import Optional, List
|
||||
from sqlalchemy.orm import Session
|
||||
from app.db.repositories import AccountRepository
|
||||
from app.db.models import Account
|
||||
from app.schemas import AccountCreateSchema
|
||||
|
||||
|
||||
class AccountService:
|
||||
"""Service for account operations"""
|
||||
|
||||
def __init__(self, session: Session):
|
||||
self.session = session
|
||||
self.account_repo = AccountRepository(session)
|
||||
|
||||
def create_account(self, family_id: int, owner_id: int, data: AccountCreateSchema) -> Account:
|
||||
"""Create new account"""
|
||||
return self.account_repo.create(
|
||||
family_id=family_id,
|
||||
owner_id=owner_id,
|
||||
name=data.name,
|
||||
account_type=data.account_type,
|
||||
description=data.description,
|
||||
balance=data.initial_balance,
|
||||
initial_balance=data.initial_balance,
|
||||
)
|
||||
|
||||
def transfer_between_accounts(
|
||||
self, from_account_id: int, to_account_id: int, amount: float
|
||||
) -> bool:
|
||||
"""Transfer money between accounts"""
|
||||
from_account = self.account_repo.update_balance(from_account_id, -amount)
|
||||
to_account = self.account_repo.update_balance(to_account_id, amount)
|
||||
return from_account is not None and to_account is not None
|
||||
|
||||
def get_family_total_balance(self, family_id: int) -> float:
|
||||
"""Get total balance of all family accounts"""
|
||||
accounts = self.account_repo.get_family_accounts(family_id)
|
||||
return sum(acc.balance for acc in accounts)
|
||||
|
||||
def archive_account(self, account_id: int) -> Optional[Account]:
|
||||
"""Archive account (hide but keep data)"""
|
||||
return self.account_repo.archive_account(account_id)
|
||||
|
||||
def get_account_summary(self, account_id: int) -> dict:
|
||||
"""Get account summary"""
|
||||
account = self.account_repo.get_by_id(account_id)
|
||||
if not account:
|
||||
return {}
|
||||
|
||||
return {
|
||||
"account_id": account.id,
|
||||
"name": account.name,
|
||||
"type": account.account_type,
|
||||
"balance": account.balance,
|
||||
"is_active": account.is_active,
|
||||
"is_archived": account.is_archived,
|
||||
"created_at": account.created_at,
|
||||
}
|
||||
67
app/services/finance/budget_service.py
Normal file
67
app/services/finance/budget_service.py
Normal file
@@ -0,0 +1,67 @@
|
||||
"""Budget service"""
|
||||
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
from sqlalchemy.orm import Session
|
||||
from app.db.repositories import BudgetRepository, TransactionRepository, CategoryRepository
|
||||
from app.db.models import Budget, TransactionType
|
||||
from app.schemas import BudgetCreateSchema
|
||||
|
||||
|
||||
class BudgetService:
|
||||
"""Service for budget operations"""
|
||||
|
||||
def __init__(self, session: Session):
|
||||
self.session = session
|
||||
self.budget_repo = BudgetRepository(session)
|
||||
self.transaction_repo = TransactionRepository(session)
|
||||
self.category_repo = CategoryRepository(session)
|
||||
|
||||
def create_budget(self, family_id: int, data: BudgetCreateSchema) -> Budget:
|
||||
"""Create new budget"""
|
||||
return self.budget_repo.create(
|
||||
family_id=family_id,
|
||||
name=data.name,
|
||||
limit_amount=data.limit_amount,
|
||||
period=data.period,
|
||||
alert_threshold=data.alert_threshold,
|
||||
category_id=data.category_id,
|
||||
start_date=data.start_date,
|
||||
)
|
||||
|
||||
def get_budget_status(self, budget_id: int) -> dict:
|
||||
"""Get budget status with spent amount and percentage"""
|
||||
budget = self.budget_repo.get_by_id(budget_id)
|
||||
if not budget:
|
||||
return {}
|
||||
|
||||
spent_percent = (budget.spent_amount / budget.limit_amount * 100) if budget.limit_amount > 0 else 0
|
||||
remaining = budget.limit_amount - budget.spent_amount
|
||||
is_exceeded = spent_percent > 100
|
||||
is_warning = spent_percent >= budget.alert_threshold
|
||||
|
||||
return {
|
||||
"budget_id": budget.id,
|
||||
"name": budget.name,
|
||||
"limit": budget.limit_amount,
|
||||
"spent": budget.spent_amount,
|
||||
"remaining": remaining,
|
||||
"spent_percent": spent_percent,
|
||||
"is_exceeded": is_exceeded,
|
||||
"is_warning": is_warning,
|
||||
"alert_threshold": budget.alert_threshold,
|
||||
}
|
||||
|
||||
def get_family_budget_status(self, family_id: int) -> List[dict]:
|
||||
"""Get status of all budgets in family"""
|
||||
budgets = self.budget_repo.get_family_budgets(family_id)
|
||||
return [self.get_budget_status(budget.id) for budget in budgets]
|
||||
|
||||
def check_budget_exceeded(self, budget_id: int) -> bool:
|
||||
"""Check if budget limit exceeded"""
|
||||
status = self.get_budget_status(budget_id)
|
||||
return status.get("is_exceeded", False)
|
||||
|
||||
def reset_budget(self, budget_id: int) -> Optional[Budget]:
|
||||
"""Reset budget spent amount for new period"""
|
||||
return self.budget_repo.update(budget_id, spent_amount=0.0)
|
||||
64
app/services/finance/goal_service.py
Normal file
64
app/services/finance/goal_service.py
Normal file
@@ -0,0 +1,64 @@
|
||||
"""Goal service"""
|
||||
|
||||
from typing import Optional, List
|
||||
from sqlalchemy.orm import Session
|
||||
from app.db.repositories import GoalRepository
|
||||
from app.db.models import Goal
|
||||
from app.schemas import GoalCreateSchema
|
||||
|
||||
|
||||
class GoalService:
|
||||
"""Service for goal operations"""
|
||||
|
||||
def __init__(self, session: Session):
|
||||
self.session = session
|
||||
self.goal_repo = GoalRepository(session)
|
||||
|
||||
def create_goal(self, family_id: int, data: GoalCreateSchema) -> Goal:
|
||||
"""Create new savings goal"""
|
||||
return self.goal_repo.create(
|
||||
family_id=family_id,
|
||||
name=data.name,
|
||||
description=data.description,
|
||||
target_amount=data.target_amount,
|
||||
priority=data.priority,
|
||||
target_date=data.target_date,
|
||||
account_id=data.account_id,
|
||||
)
|
||||
|
||||
def add_to_goal(self, goal_id: int, amount: float) -> Optional[Goal]:
|
||||
"""Add amount to goal progress"""
|
||||
return self.goal_repo.update_progress(goal_id, amount)
|
||||
|
||||
def get_goal_progress(self, goal_id: int) -> dict:
|
||||
"""Get goal progress information"""
|
||||
goal = self.goal_repo.get_by_id(goal_id)
|
||||
if not goal:
|
||||
return {}
|
||||
|
||||
progress_percent = (goal.current_amount / goal.target_amount * 100) if goal.target_amount > 0 else 0
|
||||
|
||||
return {
|
||||
"goal_id": goal.id,
|
||||
"name": goal.name,
|
||||
"target": goal.target_amount,
|
||||
"current": goal.current_amount,
|
||||
"remaining": goal.target_amount - goal.current_amount,
|
||||
"progress_percent": progress_percent,
|
||||
"is_completed": goal.is_completed,
|
||||
"target_date": goal.target_date,
|
||||
}
|
||||
|
||||
def get_family_goals_progress(self, family_id: int) -> List[dict]:
|
||||
"""Get progress for all family goals"""
|
||||
goals = self.goal_repo.get_family_goals(family_id)
|
||||
return [self.get_goal_progress(goal.id) for goal in goals]
|
||||
|
||||
def complete_goal(self, goal_id: int) -> Optional[Goal]:
|
||||
"""Mark goal as completed"""
|
||||
from datetime import datetime
|
||||
return self.goal_repo.update(
|
||||
goal_id,
|
||||
is_completed=True,
|
||||
completed_at=datetime.utcnow()
|
||||
)
|
||||
94
app/services/finance/transaction_service.py
Normal file
94
app/services/finance/transaction_service.py
Normal file
@@ -0,0 +1,94 @@
|
||||
"""Transaction service"""
|
||||
|
||||
from typing import Optional, List
|
||||
from datetime import datetime, timedelta
|
||||
from sqlalchemy.orm import Session
|
||||
from app.db.repositories import TransactionRepository, AccountRepository, BudgetRepository
|
||||
from app.db.models import Transaction, TransactionType
|
||||
from app.schemas import TransactionCreateSchema
|
||||
|
||||
|
||||
class TransactionService:
|
||||
"""Service for transaction operations"""
|
||||
|
||||
def __init__(self, session: Session):
|
||||
self.session = session
|
||||
self.transaction_repo = TransactionRepository(session)
|
||||
self.account_repo = AccountRepository(session)
|
||||
self.budget_repo = BudgetRepository(session)
|
||||
|
||||
def create_transaction(
|
||||
self,
|
||||
family_id: int,
|
||||
user_id: int,
|
||||
account_id: int,
|
||||
data: TransactionCreateSchema,
|
||||
) -> Transaction:
|
||||
"""Create new transaction and update account balance"""
|
||||
# Create transaction
|
||||
transaction = self.transaction_repo.create(
|
||||
family_id=family_id,
|
||||
user_id=user_id,
|
||||
account_id=account_id,
|
||||
amount=data.amount,
|
||||
transaction_type=data.transaction_type,
|
||||
description=data.description,
|
||||
notes=data.notes,
|
||||
tags=data.tags,
|
||||
category_id=data.category_id,
|
||||
receipt_photo_url=data.receipt_photo_url,
|
||||
transaction_date=data.transaction_date,
|
||||
)
|
||||
|
||||
# Update account balance
|
||||
if data.transaction_type == TransactionType.EXPENSE:
|
||||
self.account_repo.update_balance(account_id, -data.amount)
|
||||
elif data.transaction_type == TransactionType.INCOME:
|
||||
self.account_repo.update_balance(account_id, data.amount)
|
||||
|
||||
# Update budget if expense
|
||||
if (
|
||||
data.transaction_type == TransactionType.EXPENSE
|
||||
and data.category_id
|
||||
):
|
||||
budget = self.budget_repo.get_category_budget(family_id, data.category_id)
|
||||
if budget:
|
||||
self.budget_repo.update_spent_amount(budget.id, data.amount)
|
||||
|
||||
return transaction
|
||||
|
||||
def get_family_summary(self, family_id: int, days: int = 30) -> dict:
|
||||
"""Get financial summary for family"""
|
||||
end_date = datetime.utcnow()
|
||||
start_date = end_date - timedelta(days=days)
|
||||
|
||||
transactions = self.transaction_repo.get_transactions_by_period(
|
||||
family_id, start_date, end_date
|
||||
)
|
||||
|
||||
income = sum(t.amount for t in transactions if t.transaction_type == TransactionType.INCOME)
|
||||
expenses = sum(t.amount for t in transactions if t.transaction_type == TransactionType.EXPENSE)
|
||||
net = income - expenses
|
||||
|
||||
return {
|
||||
"period_days": days,
|
||||
"income": income,
|
||||
"expenses": expenses,
|
||||
"net": net,
|
||||
"average_daily_expense": expenses / days if days > 0 else 0,
|
||||
"transaction_count": len(transactions),
|
||||
}
|
||||
|
||||
def delete_transaction(self, transaction_id: int) -> bool:
|
||||
"""Delete transaction and rollback balance"""
|
||||
transaction = self.transaction_repo.get_by_id(transaction_id)
|
||||
if transaction:
|
||||
# Rollback balance
|
||||
if transaction.transaction_type == TransactionType.EXPENSE:
|
||||
self.account_repo.update_balance(transaction.account_id, transaction.amount)
|
||||
elif transaction.transaction_type == TransactionType.INCOME:
|
||||
self.account_repo.update_balance(transaction.account_id, -transaction.amount)
|
||||
|
||||
# Delete transaction
|
||||
return self.transaction_repo.delete(transaction_id)
|
||||
return False
|
||||
Reference in New Issue
Block a user