06/05 Optimize app: Dashboard adds Reconcile button and separate Cashes and Investment

This commit is contained in:
2026-06-05 13:20:17 -04:00
parent 3a113c89c4
commit 452374365c
2 changed files with 234 additions and 7 deletions
+81
View File
@@ -81,6 +81,20 @@ def index():
total_liabilities = get_total_liabilities()
net_worth = total_assets - total_liabilities
total_cash = float(db.session.query(
func.coalesce(func.sum(Account.balance), 0)
).filter(
Account.is_active == True,
Account.account_type.in_(['checking', 'savings', 'cash']),
).scalar())
total_investments = float(db.session.query(
func.coalesce(func.sum(Account.balance), 0)
).filter(
Account.is_active == True,
Account.account_type.in_(['investment', 'crypto']),
).scalar())
# ── Top expense categories ────────────────────────
top_categories = db.session.query(
Category.name,
@@ -158,6 +172,8 @@ def index():
total_assets=total_assets,
total_liabilities=total_liabilities,
net_worth=net_worth,
total_cash=total_cash,
total_investments=total_investments,
top_categories=top_categories,
chart_months=chart_months,
chart_income=chart_income,
@@ -169,6 +185,71 @@ def index():
schwab_warning=schwab_warning)
@dashboard_bp.route('/api/reconcile')
@login_required
def reconcile_api():
"""
Return income/expense totals for the period excluding any categories
whose name contains 'transfer' (case-insensitive).
These are internal account moves that inflate both sides artificially.
"""
period = request.args.get('period', 'this_month')
date_from, date_to, _ = _parse_date_range(period)
# Find all transfer-like category IDs
transfer_cats = Category.query.filter(
Category.name.ilike('%transfer%')
).all()
transfer_ids = [c.id for c in transfer_cats]
def _sum(txn_type):
q = db.session.query(
func.coalesce(func.sum(Transaction.amount), 0)
).filter(
Transaction.transaction_type == txn_type,
Transaction.date >= date_from,
Transaction.date <= date_to,
)
if transfer_ids:
q = q.filter(
db.or_(
Transaction.category_id.notin_(transfer_ids),
Transaction.category_id.is_(None),
)
)
return float(q.scalar())
def _sum_transfer(txn_type):
if not transfer_ids:
return 0.0
return float(db.session.query(
func.coalesce(func.sum(Transaction.amount), 0)
).filter(
Transaction.transaction_type == txn_type,
Transaction.date >= date_from,
Transaction.date <= date_to,
Transaction.category_id.in_(transfer_ids),
).scalar())
income = _sum('income')
expense = _sum('expense')
net = income - expense
savings = round(net / income * 100, 1) if income else 0
excluded_income = _sum_transfer('income')
excluded_expense = _sum_transfer('expense')
return jsonify({
'income': income,
'expense': expense,
'net_cash_flow': net,
'savings_rate': savings,
'excluded_income': excluded_income,
'excluded_expense': excluded_expense,
'transfer_categories': [c.name for c in transfer_cats],
})
@dashboard_bp.route('/api/fx-history')
@login_required
def fx_history_api():