Files
Personal-Finance-Management/app/routes/reports.py
T
2026-05-31 16:55:43 -04:00

220 lines
8.0 KiB
Python

from flask import (Blueprint, render_template, request, redirect, url_for,
Response, flash, send_file)
from flask_login import login_required, current_user
from app.models.transaction import Transaction
from app.services.report_service import (
monthly_report, quarterly_report, yearly_report,
net_worth_history, category_trends, tax_year_summary,
take_net_worth_snapshot,
)
from app.services.export_service import (
transactions_to_csv, transactions_to_excel,
report_to_pdf, build_report_html,
)
from datetime import date
import io
reports_bp = Blueprint('reports', __name__, url_prefix='/reports')
_CUR_YEAR = date.today().year
_CUR_MONTH = date.today().month
@reports_bp.route('/')
@login_required
def index():
today = date.today()
# Default: current month summary
report = monthly_report(today.year, today.month)
nw_history = net_worth_history()
cat_trend = category_trends(6)
years = list(range(_CUR_YEAR, _CUR_YEAR - 5, -1))
return render_template('reports/index.html',
report=report,
nw_history=nw_history,
cat_trend=cat_trend,
years=years,
current_year=_CUR_YEAR,
current_month=_CUR_MONTH,
report_type='monthly',
selected_year=today.year,
selected_month=today.month,
selected_quarter=None)
@reports_bp.route('/monthly')
@login_required
def monthly():
year = request.args.get('year', _CUR_YEAR, type=int)
month = request.args.get('month', _CUR_MONTH, type=int)
report = monthly_report(year, month)
nw_history = net_worth_history()
cat_trend = category_trends(6)
years = list(range(_CUR_YEAR, _CUR_YEAR - 5, -1))
return render_template('reports/index.html',
report=report,
nw_history=nw_history,
cat_trend=cat_trend,
years=years,
current_year=_CUR_YEAR,
current_month=_CUR_MONTH,
report_type='monthly',
selected_year=year,
selected_month=month,
selected_quarter=None)
@reports_bp.route('/quarterly')
@login_required
def quarterly():
year = request.args.get('year', _CUR_YEAR, type=int)
quarter = request.args.get('quarter', ((_CUR_MONTH - 1) // 3) + 1, type=int)
report = quarterly_report(year, quarter)
nw_history = net_worth_history()
cat_trend = category_trends(6)
years = list(range(_CUR_YEAR, _CUR_YEAR - 5, -1))
return render_template('reports/index.html',
report=report,
nw_history=nw_history,
cat_trend=cat_trend,
years=years,
current_year=_CUR_YEAR,
current_month=_CUR_MONTH,
report_type='quarterly',
selected_year=year,
selected_month=None,
selected_quarter=quarter)
@reports_bp.route('/yearly')
@login_required
def yearly():
year = request.args.get('year', _CUR_YEAR, type=int)
report = yearly_report(year)
nw_history = net_worth_history()
cat_trend = category_trends(12)
years = list(range(_CUR_YEAR, _CUR_YEAR - 5, -1))
return render_template('reports/index.html',
report=report,
nw_history=nw_history,
cat_trend=cat_trend,
years=years,
current_year=_CUR_YEAR,
current_month=_CUR_MONTH,
report_type='yearly',
selected_year=year,
selected_month=None,
selected_quarter=None)
@reports_bp.route('/tax')
@login_required
def tax():
year = request.args.get('year', _CUR_YEAR, type=int)
report = tax_year_summary(year)
years = list(range(_CUR_YEAR, _CUR_YEAR - 5, -1))
return render_template('reports/tax.html',
report=report,
years=years,
selected_year=year)
# ── Exports ───────────────────────────────────────────────────────────────────
@reports_bp.route('/export/csv')
@login_required
def export_csv():
year = request.args.get('year', _CUR_YEAR, type=int)
month = request.args.get('month', type=int)
txn_type = request.args.get('type', 'all')
query = Transaction.query.filter(
Transaction.date >= date(year, month if month else 1, 1),
Transaction.date <= date(year, month if month else 12,
31 if not month else
__import__('calendar').monthrange(year, month)[1]),
)
if txn_type != 'all':
query = query.filter(Transaction.transaction_type == txn_type)
transactions = query.order_by(Transaction.date.desc()).all()
csv_data = transactions_to_csv(transactions)
label = f'{year}-{month:02d}' if month else str(year)
filename = f'pfm_transactions_{label}.csv'
return Response(
csv_data,
mimetype='text/csv',
headers={'Content-Disposition': f'attachment; filename="{filename}"'}
)
@reports_bp.route('/export/excel')
@login_required
def export_excel():
year = request.args.get('year', _CUR_YEAR, type=int)
month = request.args.get('month', type=int)
query = Transaction.query.filter(
Transaction.date >= date(year, month if month else 1, 1),
Transaction.date <= date(year, month if month else 12,
31 if not month else
__import__('calendar').monthrange(year, month)[1]),
Transaction.transaction_type.in_(['income', 'expense']),
)
transactions = query.order_by(Transaction.date.desc()).all()
label = f'{year}-{month:02d}' if month else str(year)
period_label = f'{year} Month {month}' if month else str(year)
excel_bytes = transactions_to_excel(transactions, period_label)
filename = f'pfm_transactions_{label}.xlsx'
return send_file(
io.BytesIO(excel_bytes),
mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
as_attachment=True,
download_name=filename,
)
@reports_bp.route('/export/pdf')
@login_required
def export_pdf():
report_type = request.args.get('type', 'monthly')
year = request.args.get('year', _CUR_YEAR, type=int)
month = request.args.get('month', _CUR_MONTH, type=int)
quarter = request.args.get('quarter', 1, type=int)
if report_type == 'yearly':
report = yearly_report(year)
elif report_type == 'quarterly':
report = quarterly_report(year, quarter)
else:
report = monthly_report(year, month)
symbol = current_user.currency_symbol or '$'
html = build_report_html(report, report_type, symbol)
pdf = report_to_pdf(html)
if pdf is None:
flash('PDF generation failed. WeasyPrint may not be installed.', 'danger')
return redirect(url_for('reports.index'))
filename = f'pfm_report_{report["period"].replace(" ", "_")}.pdf'
return send_file(
io.BytesIO(pdf),
mimetype='application/pdf',
as_attachment=True,
download_name=filename,
)
@reports_bp.route('/snapshot', methods=['POST'])
@login_required
def manual_snapshot():
"""Manually trigger a net worth snapshot."""
snap = take_net_worth_snapshot()
flash(f'Net worth snapshot saved: {snap.snapshot_date.strftime("%B %d, %Y")}', 'success')
return redirect(url_for('reports.index'))