init commit

This commit is contained in:
2025-12-10 22:09:31 +09:00
commit b79adf1c69
361 changed files with 47414 additions and 0 deletions

View 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",
]

View 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,
}

View 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)

View 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()
)

View 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