1
1
Files
financeplanner/financeplanner/views.py

178 lines
5.9 KiB
Python

from datetime import datetime
from decimal import Decimal
from math import floor
from dateutil.relativedelta import relativedelta
from django.contrib.auth.decorators import login_required
from django.db.models import Q
from django.shortcuts import render
from financeplanner.utils import format_date
def _get_relevant_balances(user, start, end):
balances = user.balances.filter(date__range=(start, end)).order_by("date")
return list(balances)
def _get_relevant_transactions(user, start, end):
transactions = user.transactions.filter(
Q(booking_date__range=(start, end)) |
Q(booking_date__lte=end, recurring_months__isnull=False, not_recurring_after__gte=start)
).order_by("booking_date")
return list(transactions)
def _calculate_actual_transactions(start, end, transaction_list):
result = []
for trans in transaction_list:
current_calc_trans = {}
if trans.recurring_months:
current_date = trans.booking_date
step = 1
limit = min(trans.not_recurring_after, end) if trans.not_recurring_after else end
while current_date < limit:
if current_date >= start:
result.append((
current_date,
f"{trans.subject} #{step}",
trans.amount,
))
current_date += relativedelta(months=trans.recurring_months)
step += 1
else:
current_calc_trans[trans.booking_date] = trans.amount
result.append((
trans.booking_date,
trans.subject,
trans.amount,
))
return result
def _calculate_daily_stats(start, end, balance_list, actual_transactions):
stats = {}
current_date = start
current_amount = None
while current_date < end:
current_stats = {
"balance": None,
"transactions": [],
"amount": None,
}
relevant_balances = [bal for bal in balance_list if bal.date == current_date]
if relevant_balances:
assert len(relevant_balances) == 1, f"balances should be unique for user and date, " \
f"but here are {relevant_balances}"
amount = relevant_balances[0].amount
current_stats["balance"] = relevant_balances[0].amount
current_amount = amount
relevant_transactions = [tra for tra in actual_transactions if tra[0] == current_date]
for tra in relevant_transactions:
subject, amount = tra[1], tra[2]
current_stats["transactions"].append({
"subject": subject,
"amount": amount,
})
if current_amount is not None:
current_amount -= amount
current_stats["amount"] = current_amount
stats[current_date] = current_stats
current_date += relativedelta(days=1)
return stats
def _floor_to_first_two_places(dec):
if dec < 100:
return 100
exp = floor(dec.log10()) - 1
factor = 10 ** exp
return factor * int(dec / factor)
def _calculate_scale(stats):
amounts = [stat["amount"] for stat in stats.values() if stat["amount"] is not None]
if not amounts:
return 100
max_amount = max(amounts)
return _floor_to_first_two_places(max_amount * Decimal("1.3"))
def _build_graph_data(start, end, balance_list, actual_transactions):
"""
result has the format:
{
<date>: {
"balance": <decimal>,
"transactions": [
{
"subject": <str>,
"amount": <decimal>,
},
...
],
"amount": <decimal>,
"div_percentage": <int>,
"div_opacity_class": <str>,
"div_color_class": <str>,
},
...
}
"""
today = datetime.now().date()
stats = _calculate_daily_stats(start, end, balance_list, actual_transactions)
amount_limit = _calculate_scale(stats)
for date in stats.keys():
amount = stats[date]["amount"]
if amount is None:
stats[date]["div_percentage"] = None
else:
percentage = int((amount / amount_limit) * 100)
stats[date]["div_percentage"] = percentage
if percentage >= 60:
stats[date]["div_color_class"] = "green"
elif percentage >= 45:
stats[date]["div_color_class"] = "olive"
elif percentage >= 30:
stats[date]["div_color_class"] = "yellow"
elif percentage >= 15:
stats[date]["div_color_class"] = "orange"
else:
stats[date]["div_color_class"] = "red"
if date == today:
stats[date]["div_opacity_class"] = "highlighted"
elif stats[date]["balance"] or stats[date]["transactions"]:
stats[date]["div_opacity_class"] = "strong"
else:
stats[date]["div_opacity_class"] = ""
return stats
@login_required
def index(request):
user = request.user
today = datetime.now().date()
range_start = today - relativedelta(days=30)
range_end = today + relativedelta(days=50)
balance_list = _get_relevant_balances(user, range_start, range_end)
transaction_list = _get_relevant_transactions(user, range_start, range_end)
actual_transactions = _calculate_actual_transactions(range_start, range_end, transaction_list)
graph_data = _build_graph_data(range_start, range_end, balance_list, actual_transactions)
context = {
"range_start": format_date(range_start),
"range_end": format_date(range_end),
"today": today,
"balance_list": balance_list,
"transaction_list": transaction_list,
"actual_transactions": actual_transactions,
"graph_data": graph_data,
}
return render(request, "financeplanner/index.html", context)