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('/admin') @login_required def admin(): if not current_user.is_admin: return render_template('404.html'), 404 return render_template('admin.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 (all currencies - show user's preferred currency) current_month_expenses = Expense.query.filter( Expense.user_id == current_user.id, Expense.date >= current_month_start ).all() current_month_total = sum(exp.amount for exp in current_month_expenses) # Previous month total prev_month_expenses = Expense.query.filter( Expense.user_id == current_user.id, Expense.date >= prev_month_start, Expense.date < current_month_start ).all() prev_month_total = sum(exp.amount for exp in prev_month_expenses) # 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 for entire current year (all currencies) current_year_start = now.replace(month=1, day=1, hour=0, minute=0, second=0, microsecond=0) category_stats = db.session.query( Category.id, Category.name, Category.color, func.sum(Expense.amount).label('total'), func.count(Expense.id).label('count') ).join(Expense).filter( Expense.user_id == current_user.id, Expense.date >= current_year_start ).group_by(Category.id).order_by(Category.display_order, Category.created_at).all() # Monthly breakdown (all 12 months of current year) monthly_data = [] for month_num in range(1, 13): month_start = now.replace(month=month_num, day=1, hour=0, minute=0, second=0, microsecond=0) if month_num == 12: month_end = now.replace(year=now.year+1, month=1, day=1, hour=0, minute=0, second=0, microsecond=0) else: month_end = now.replace(month=month_num+1, day=1, hour=0, minute=0, second=0, microsecond=0) month_expenses = Expense.query.filter( Expense.user_id == current_user.id, Expense.date >= month_start, Expense.date < month_end ).all() month_total = sum(exp.amount for exp in month_expenses) 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': [ {'id': stat[0], 'name': stat[1], 'color': stat[2], 'total': float(stat[3]), 'count': stat[4]} 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 (all currencies) total_spent = sum(exp.amount for exp in expenses) # 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 ).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 (all currencies) category_totals = {} for exp in expenses: 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 calculation based on monthly budget if current_user.monthly_budget and current_user.monthly_budget > 0: savings_amount = current_user.monthly_budget - total_spent savings_rate = (savings_amount / current_user.monthly_budget) * 100 savings_rate = max(0, min(100, savings_rate)) # Clamp between 0-100% else: savings_rate = 0 # 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_expenses = Expense.query.filter( Expense.user_id == current_user.id, Expense.date >= day_start, Expense.date < day_end ).all() day_total = sum(exp.amount for exp in day_expenses) daily_trend.insert(0, { 'date': day_date.strftime('%d %b'), 'amount': float(day_total) }) # Monthly comparison (all 12 months of current year, all currencies) monthly_comparison = [] current_year = now.year for month in range(1, 13): month_start = datetime(current_year, month, 1) if month == 12: month_end = datetime(current_year + 1, 1, 1) else: month_end = datetime(current_year, month + 1, 1) month_expenses = Expense.query.filter( Expense.user_id == current_user.id, Expense.date >= month_start, Expense.date < month_end ).all() month_total = sum(exp.amount for exp in month_expenses) 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 }) @bp.route('/api/smart-recommendations') @login_required def smart_recommendations(): """ Generate smart financial recommendations based on user spending patterns Security: Only returns recommendations for current_user """ now = datetime.utcnow() # Get data for last 30 and 60 days for comparison period_30 = now - timedelta(days=30) period_60 = now - timedelta(days=60) period_30_start = period_60 # Current period expenses (all currencies) current_expenses = Expense.query.filter( Expense.user_id == current_user.id, Expense.date >= period_30 ).all() # Previous period expenses (all currencies) previous_expenses = Expense.query.filter( Expense.user_id == current_user.id, Expense.date >= period_60, Expense.date < period_30 ).all() current_total = sum(exp.amount for exp in current_expenses) previous_total = sum(exp.amount for exp in previous_expenses) # Category analysis current_by_category = defaultdict(float) previous_by_category = defaultdict(float) for exp in current_expenses: current_by_category[exp.category.name] += exp.amount for exp in previous_expenses: previous_by_category[exp.category.name] += exp.amount recommendations = [] # Recommendation 1: Budget vs Spending if current_user.monthly_budget and current_user.monthly_budget > 0: budget_used_percent = (current_total / current_user.monthly_budget) * 100 remaining = current_user.monthly_budget - current_total if budget_used_percent > 90: recommendations.append({ 'type': 'warning', 'icon': 'warning', 'color': 'red', 'title': 'Budget Alert' if current_user.language == 'en' else 'Alertă Buget', 'description': f'You\'ve used {budget_used_percent:.1f}% of your monthly budget. Only {abs(remaining):.2f} {current_user.currency} remaining.' if current_user.language == 'en' else f'Ai folosit {budget_used_percent:.1f}% din bugetul lunar. Mai rămân doar {abs(remaining):.2f} {current_user.currency}.' }) elif budget_used_percent < 70 and remaining > 0: recommendations.append({ 'type': 'success', 'icon': 'trending_up', 'color': 'green', 'title': 'Great Savings Opportunity' if current_user.language == 'en' else 'Oportunitate de Economisire', 'description': f'You have {remaining:.2f} {current_user.currency} remaining from your budget. Consider saving or investing it.' if current_user.language == 'en' else f'Mai ai {remaining:.2f} {current_user.currency} din buget. Consideră să economisești sau să investești.' }) # Recommendation 2: Category spending changes for category_name, current_amount in current_by_category.items(): if category_name in previous_by_category: previous_amount = previous_by_category[category_name] if previous_amount > 0: change_percent = ((current_amount - previous_amount) / previous_amount) * 100 if change_percent > 50: # 50% increase recommendations.append({ 'type': 'warning', 'icon': 'trending_up', 'color': 'yellow', 'title': f'{category_name} Spending Up' if current_user.language == 'en' else f'Cheltuieli {category_name} în Creștere', 'description': f'Your {category_name} spending increased by {change_percent:.0f}%. Review recent transactions.' if current_user.language == 'en' else f'Cheltuielile pentru {category_name} au crescut cu {change_percent:.0f}%. Revizuiește tranzacțiile recente.' }) elif change_percent < -30: # 30% decrease recommendations.append({ 'type': 'success', 'icon': 'trending_down', 'color': 'green', 'title': f'{category_name} Savings' if current_user.language == 'en' else f'Economii {category_name}', 'description': f'Great job! You reduced {category_name} spending by {abs(change_percent):.0f}%.' if current_user.language == 'en' else f'Foarte bine! Ai redus cheltuielile pentru {category_name} cu {abs(change_percent):.0f}%.' }) # Recommendation 3: Unusual transactions if current_expenses: category_averages = {} for category_name, amount in current_by_category.items(): count = sum(1 for exp in current_expenses if exp.category.name == category_name) category_averages[category_name] = amount / count if count > 0 else 0 for exp in current_expenses[-10:]: # Check last 10 transactions category_avg = category_averages.get(exp.category.name, 0) if category_avg > 0 and exp.amount > category_avg * 2: # 200% of average recommendations.append({ 'type': 'info', 'icon': 'info', 'color': 'blue', 'title': 'Unusual Transaction' if current_user.language == 'en' else 'Tranzacție Neobișnuită', 'description': f'A transaction of {exp.amount:.2f} {current_user.currency} in {exp.category.name} is higher than usual.' if current_user.language == 'en' else f'O tranzacție de {exp.amount:.2f} {current_user.currency} în {exp.category.name} este mai mare decât de obicei.' }) break # Only show one unusual transaction warning # Limit to top 3 recommendations recommendations = recommendations[:3] # If no recommendations, add a positive message if not recommendations: recommendations.append({ 'type': 'success', 'icon': 'check_circle', 'color': 'green', 'title': 'Spending on Track' if current_user.language == 'en' else 'Cheltuieli sub Control', 'description': 'Your spending patterns look healthy. Keep up the good work!' if current_user.language == 'en' else 'Obiceiurile tale de cheltuieli arată bine. Continuă așa!' }) return jsonify({ 'success': True, 'recommendations': recommendations })