05/31 Phase 2

This commit is contained in:
2026-05-31 11:17:53 -04:00
parent 1c34dcbd28
commit 9f2a0acc38
18 changed files with 1817 additions and 391 deletions
+85
View File
@@ -0,0 +1,85 @@
"""
Account Service — balance calculated from transactions.
Balance = sum of income - sum of expenses for an account,
plus any incoming transfers minus outgoing transfers.
"""
from decimal import Decimal
from sqlalchemy import func
from app.extensions import db
from app.models.account import Account
from app.models.transaction import Transaction
def calc_balance(account_id):
"""Recalculate and persist the balance for a given account."""
# Income credited to this account
income = db.session.query(
func.coalesce(func.sum(Transaction.amount), 0)
).filter(
Transaction.account_id == account_id,
Transaction.transaction_type == 'income'
).scalar()
# Expenses debited from this account
expense = db.session.query(
func.coalesce(func.sum(Transaction.amount), 0)
).filter(
Transaction.account_id == account_id,
Transaction.transaction_type == 'expense'
).scalar()
# Transfers out (this account is source)
transfer_out = db.session.query(
func.coalesce(func.sum(Transaction.amount), 0)
).filter(
Transaction.account_id == account_id,
Transaction.transaction_type == 'transfer'
).scalar()
# Transfers in (this account is destination)
transfer_in = db.session.query(
func.coalesce(func.sum(Transaction.amount), 0)
).filter(
Transaction.to_account_id == account_id,
Transaction.transaction_type == 'transfer'
).scalar()
balance = Decimal(str(income)) - Decimal(str(expense)) \
- Decimal(str(transfer_out)) + Decimal(str(transfer_in))
account = db.session.get(Account, account_id)
if account:
account.balance = balance
db.session.commit()
return balance
def recalc_all():
"""Recalculate balances for all accounts."""
for account in Account.query.filter_by(is_active=True).all():
calc_balance(account.id)
def get_total_assets():
"""Sum of all positive-balance accounts (non-credit)."""
result = db.session.query(
func.coalesce(func.sum(Account.balance), 0)
).filter(
Account.is_active == True,
Account.account_type != 'credit_card',
Account.balance > 0
).scalar()
return float(result)
def get_total_liabilities():
"""Sum of credit card balances (negative = owed)."""
result = db.session.query(
func.coalesce(func.sum(Account.balance), 0)
).filter(
Account.is_active == True,
Account.account_type == 'credit_card',
Account.balance < 0
).scalar()
return abs(float(result))