289 lines
9.4 KiB
Python
289 lines
9.4 KiB
Python
from flask import Blueprint, render_template, request, jsonify
|
|
from flask_login import login_required, current_user
|
|
from app import db
|
|
from app.models import Expense, Category
|
|
from sqlalchemy import func, extract
|
|
from datetime import datetime, timedelta
|
|
from collections import defaultdict
|
|
|
|
bp = Blueprint('main', __name__)
|
|
|
|
@bp.route('/')
|
|
def index():
|
|
if current_user.is_authenticated:
|
|
return render_template('dashboard.html')
|
|
return render_template('landing.html')
|
|
|
|
|
|
@bp.route('/dashboard')
|
|
@login_required
|
|
def dashboard():
|
|
return render_template('dashboard.html')
|
|
|
|
|
|
@bp.route('/transactions')
|
|
@login_required
|
|
def transactions():
|
|
return render_template('transactions.html')
|
|
|
|
|
|
@bp.route('/reports')
|
|
@login_required
|
|
def reports():
|
|
return render_template('reports.html')
|
|
|
|
|
|
@bp.route('/settings')
|
|
@login_required
|
|
def settings():
|
|
return render_template('settings.html')
|
|
|
|
|
|
@bp.route('/documents')
|
|
@login_required
|
|
def documents():
|
|
return render_template('documents.html')
|
|
|
|
|
|
@bp.route('/api/dashboard-stats')
|
|
@login_required
|
|
def dashboard_stats():
|
|
now = datetime.utcnow()
|
|
|
|
# Current month stats
|
|
current_month_start = now.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
|
|
|
# Previous month stats
|
|
if now.month == 1:
|
|
prev_month_start = now.replace(year=now.year-1, month=12, day=1)
|
|
else:
|
|
prev_month_start = current_month_start.replace(month=current_month_start.month-1)
|
|
|
|
# Total spent this month
|
|
current_month_total = db.session.query(func.sum(Expense.amount)).filter(
|
|
Expense.user_id == current_user.id,
|
|
Expense.date >= current_month_start,
|
|
Expense.currency == current_user.currency
|
|
).scalar() or 0
|
|
|
|
# Previous month total
|
|
prev_month_total = db.session.query(func.sum(Expense.amount)).filter(
|
|
Expense.user_id == current_user.id,
|
|
Expense.date >= prev_month_start,
|
|
Expense.date < current_month_start,
|
|
Expense.currency == current_user.currency
|
|
).scalar() or 0
|
|
|
|
# Calculate percentage change
|
|
if prev_month_total > 0:
|
|
percent_change = ((current_month_total - prev_month_total) / prev_month_total) * 100
|
|
else:
|
|
percent_change = 100 if current_month_total > 0 else 0
|
|
|
|
# Active categories
|
|
active_categories = Category.query.filter_by(user_id=current_user.id).count()
|
|
|
|
# Total transactions this month
|
|
total_transactions = Expense.query.filter(
|
|
Expense.user_id == current_user.id,
|
|
Expense.date >= current_month_start
|
|
).count()
|
|
|
|
# Category breakdown
|
|
category_stats = db.session.query(
|
|
Category.name,
|
|
Category.color,
|
|
func.sum(Expense.amount).label('total')
|
|
).join(Expense).filter(
|
|
Expense.user_id == current_user.id,
|
|
Expense.date >= current_month_start,
|
|
Expense.currency == current_user.currency
|
|
).group_by(Category.id).all()
|
|
|
|
# Monthly breakdown (last 6 months)
|
|
monthly_data = []
|
|
for i in range(5, -1, -1):
|
|
month_date = now - timedelta(days=30*i)
|
|
month_start = month_date.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
|
if month_date.month == 12:
|
|
month_end = month_date.replace(year=month_date.year+1, month=1, day=1)
|
|
else:
|
|
month_end = month_date.replace(month=month_date.month+1, day=1)
|
|
|
|
month_total = db.session.query(func.sum(Expense.amount)).filter(
|
|
Expense.user_id == current_user.id,
|
|
Expense.date >= month_start,
|
|
Expense.date < month_end,
|
|
Expense.currency == current_user.currency
|
|
).scalar() or 0
|
|
|
|
monthly_data.append({
|
|
'month': month_start.strftime('%b'),
|
|
'total': float(month_total)
|
|
})
|
|
|
|
return jsonify({
|
|
'total_spent': float(current_month_total),
|
|
'percent_change': round(percent_change, 1),
|
|
'active_categories': active_categories,
|
|
'total_transactions': total_transactions,
|
|
'currency': current_user.currency,
|
|
'category_breakdown': [
|
|
{'name': stat[0], 'color': stat[1], 'amount': float(stat[2])}
|
|
for stat in category_stats
|
|
],
|
|
'monthly_data': monthly_data
|
|
})
|
|
|
|
|
|
@bp.route('/api/recent-transactions')
|
|
@login_required
|
|
def recent_transactions():
|
|
limit = request.args.get('limit', 10, type=int)
|
|
|
|
expenses = Expense.query.filter_by(user_id=current_user.id)\
|
|
.order_by(Expense.date.desc())\
|
|
.limit(limit)\
|
|
.all()
|
|
|
|
return jsonify({
|
|
'transactions': [expense.to_dict() for expense in expenses]
|
|
})
|
|
|
|
|
|
@bp.route('/api/reports-stats')
|
|
@login_required
|
|
def reports_stats():
|
|
"""
|
|
Generate comprehensive financial reports
|
|
Security: Only returns data for current_user (enforced by user_id filter)
|
|
"""
|
|
period = request.args.get('period', '30') # days
|
|
category_filter = request.args.get('category_id', type=int)
|
|
|
|
try:
|
|
days = int(period)
|
|
except ValueError:
|
|
days = 30
|
|
|
|
now = datetime.utcnow()
|
|
period_start = now - timedelta(days=days)
|
|
|
|
# Query with security filter
|
|
query = Expense.query.filter(
|
|
Expense.user_id == current_user.id,
|
|
Expense.date >= period_start
|
|
)
|
|
|
|
if category_filter:
|
|
query = query.filter_by(category_id=category_filter)
|
|
|
|
expenses = query.all()
|
|
|
|
# Total spent in period
|
|
total_spent = sum(exp.amount for exp in expenses if exp.currency == current_user.currency)
|
|
|
|
# Previous period comparison
|
|
prev_period_start = period_start - timedelta(days=days)
|
|
prev_expenses = Expense.query.filter(
|
|
Expense.user_id == current_user.id,
|
|
Expense.date >= prev_period_start,
|
|
Expense.date < period_start,
|
|
Expense.currency == current_user.currency
|
|
).all()
|
|
prev_total = sum(exp.amount for exp in prev_expenses)
|
|
|
|
percent_change = 0
|
|
if prev_total > 0:
|
|
percent_change = ((total_spent - prev_total) / prev_total) * 100
|
|
elif total_spent > 0:
|
|
percent_change = 100
|
|
|
|
# Top category
|
|
category_totals = {}
|
|
for exp in expenses:
|
|
if exp.currency == current_user.currency:
|
|
cat_name = exp.category.name
|
|
category_totals[cat_name] = category_totals.get(cat_name, 0) + exp.amount
|
|
|
|
top_category = max(category_totals.items(), key=lambda x: x[1]) if category_totals else ('None', 0)
|
|
|
|
# Average daily spending
|
|
avg_daily = total_spent / days if days > 0 else 0
|
|
prev_avg_daily = prev_total / days if days > 0 else 0
|
|
avg_change = 0
|
|
if prev_avg_daily > 0:
|
|
avg_change = ((avg_daily - prev_avg_daily) / prev_avg_daily) * 100
|
|
elif avg_daily > 0:
|
|
avg_change = 100
|
|
|
|
# Savings rate (placeholder - would need income data)
|
|
savings_rate = 18.5 # Placeholder
|
|
|
|
# Category breakdown for pie chart
|
|
category_breakdown = []
|
|
for cat_name, amount in sorted(category_totals.items(), key=lambda x: x[1], reverse=True):
|
|
category = Category.query.filter_by(user_id=current_user.id, name=cat_name).first()
|
|
if category:
|
|
percentage = (amount / total_spent * 100) if total_spent > 0 else 0
|
|
category_breakdown.append({
|
|
'name': cat_name,
|
|
'color': category.color,
|
|
'amount': float(amount),
|
|
'percentage': round(percentage, 1)
|
|
})
|
|
|
|
# Daily spending trend (last 30 days)
|
|
daily_trend = []
|
|
for i in range(min(30, days)):
|
|
day_date = now - timedelta(days=i)
|
|
day_start = day_date.replace(hour=0, minute=0, second=0, microsecond=0)
|
|
day_end = day_start + timedelta(days=1)
|
|
|
|
day_total = db.session.query(func.sum(Expense.amount)).filter(
|
|
Expense.user_id == current_user.id,
|
|
Expense.date >= day_start,
|
|
Expense.date < day_end,
|
|
Expense.currency == current_user.currency
|
|
).scalar() or 0
|
|
|
|
daily_trend.insert(0, {
|
|
'date': day_date.strftime('%d %b'),
|
|
'amount': float(day_total)
|
|
})
|
|
|
|
# Monthly comparison (last 6 months)
|
|
monthly_comparison = []
|
|
for i in range(5, -1, -1):
|
|
month_date = now - timedelta(days=30*i)
|
|
month_start = month_date.replace(day=1, hour=0, minute=0, second=0, microsecond=0)
|
|
if month_date.month == 12:
|
|
month_end = month_date.replace(year=month_date.year+1, month=1, day=1)
|
|
else:
|
|
month_end = month_date.replace(month=month_date.month+1, day=1)
|
|
|
|
month_total = db.session.query(func.sum(Expense.amount)).filter(
|
|
Expense.user_id == current_user.id,
|
|
Expense.date >= month_start,
|
|
Expense.date < month_end,
|
|
Expense.currency == current_user.currency
|
|
).scalar() or 0
|
|
|
|
monthly_comparison.append({
|
|
'month': month_start.strftime('%b'),
|
|
'amount': float(month_total)
|
|
})
|
|
|
|
return jsonify({
|
|
'total_spent': float(total_spent),
|
|
'percent_change': round(percent_change, 1),
|
|
'top_category': {'name': top_category[0], 'amount': float(top_category[1])},
|
|
'avg_daily': float(avg_daily),
|
|
'avg_daily_change': round(avg_change, 1),
|
|
'savings_rate': savings_rate,
|
|
'category_breakdown': category_breakdown,
|
|
'daily_trend': daily_trend,
|
|
'monthly_comparison': monthly_comparison,
|
|
'currency': current_user.currency,
|
|
'period_days': days
|
|
})
|