Files
WYGIWYH/app/apps/yearly_overview/views.py

395 lines
13 KiB
Python

from datetime import date
from decimal import Decimal
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, redirect
from django.utils import timezone
from django.db.models import Sum, F, Q, Value, CharField, DecimalField
from django.db.models.functions import TruncMonth, Coalesce
from django.db.models.expressions import Case, When
from django.db.models.functions import Concat
from apps.transactions.models import Transaction
from apps.currencies.utils.convert import convert
from apps.currencies.models import Currency
@login_required
def index_by_currency(request):
now = timezone.localdate(timezone.now())
return redirect(to="yearly_overview_currency", year=now.year)
@login_required
def index_by_account(request):
now = timezone.localdate(timezone.now())
return redirect(to="yearly_overview_account", year=now.year)
@login_required
def yearly_overview_by_currency(request, year: int):
next_year = year + 1
previous_year = year - 1
transactions = Transaction.objects.filter(
reference_date__year=year, account__is_archived=False
)
monthly_data = (
transactions.annotate(month=TruncMonth("reference_date"))
.values(
"month",
"account__currency__code",
"account__currency__prefix",
"account__currency__suffix",
"account__currency__decimal_places",
)
.annotate(
income_paid=Coalesce(
Sum(
Case(
When(
type=Transaction.Type.INCOME, is_paid=True, then=F("amount")
),
default=Value(Decimal("0")),
output_field=DecimalField(),
)
),
Value(Decimal("0")),
output_field=DecimalField(),
),
expense_paid=Coalesce(
Sum(
Case(
When(
type=Transaction.Type.EXPENSE,
is_paid=True,
then=F("amount"),
),
default=Value(Decimal("0")),
output_field=DecimalField(),
)
),
Value(Decimal("0")),
output_field=DecimalField(),
),
income_unpaid=Coalesce(
Sum(
Case(
When(
type=Transaction.Type.INCOME,
is_paid=False,
then=F("amount"),
),
default=Value(Decimal("0")),
output_field=DecimalField(),
)
),
Value(Decimal("0")),
output_field=DecimalField(),
),
expense_unpaid=Coalesce(
Sum(
Case(
When(
type=Transaction.Type.EXPENSE,
is_paid=False,
then=F("amount"),
),
default=Value(Decimal("0")),
output_field=DecimalField(),
)
),
Value(Decimal("0")),
output_field=DecimalField(),
),
)
.annotate(
balance_unpaid=F("income_unpaid") - F("expense_unpaid"),
balance_paid=F("income_paid") - F("expense_paid"),
balance_total=F("income_paid")
+ F("income_unpaid")
- F("expense_paid")
- F("expense_unpaid"),
)
.order_by("month", "account__currency__code")
)
# Create a list of all months in the year
all_months = [date(year, month, 1) for month in range(1, 13)]
# Create a dictionary to store the final result
result = {
month: {
"income_paid": [],
"expense_paid": [],
"income_unpaid": [],
"expense_unpaid": [],
"balance_unpaid": [],
"balance_paid": [],
"balance_total": [],
}
for month in all_months
}
# Fill in the data
for entry in monthly_data:
month = entry["month"]
currency_code = entry["account__currency__code"]
prefix = entry["account__currency__prefix"]
suffix = entry["account__currency__suffix"]
decimal_places = entry["account__currency__decimal_places"]
for field in [
"income_paid",
"expense_paid",
"income_unpaid",
"expense_unpaid",
"balance_unpaid",
"balance_paid",
"balance_total",
]:
if entry[field] != 0:
result[month][field].append(
{
"code": currency_code,
"prefix": prefix,
"suffix": suffix,
"decimal_places": decimal_places,
"amount": entry[field],
}
)
# Fill in missing months with empty lists
for month in all_months:
if not any(result[month].values()):
result[month] = {
"income_paid": [],
"expense_paid": [],
"income_unpaid": [],
"expense_unpaid": [],
"balance_unpaid": [],
"balance_paid": [],
"balance_total": [],
}
return render(
request,
"yearly_overview/pages/overview_by_currency.html",
context={
"year": year,
"next_year": next_year,
"previous_year": previous_year,
"totals": result,
},
)
@login_required
def yearly_overview_by_account(request, year: int):
next_year = year + 1
previous_year = year - 1
transactions = Transaction.objects.filter(
reference_date__year=year, account__is_archived=False
)
monthly_data = (
transactions.annotate(month=TruncMonth("reference_date"))
.select_related(
"account__currency",
"account__exchange_currency",
)
.values(
"month",
"account__id",
"account__name",
"account__currency",
"account__exchange_currency",
"account__currency__code",
"account__currency__prefix",
"account__currency__suffix",
"account__currency__decimal_places",
"account__exchange_currency__code",
"account__exchange_currency__prefix",
"account__exchange_currency__suffix",
"account__exchange_currency__decimal_places",
)
.annotate(
income_paid=Coalesce(
Sum(
Case(
When(
type=Transaction.Type.INCOME, is_paid=True, then=F("amount")
),
default=Value(Decimal("0")),
output_field=DecimalField(),
)
),
Value(Decimal("0")),
output_field=DecimalField(),
),
expense_paid=Coalesce(
Sum(
Case(
When(
type=Transaction.Type.EXPENSE,
is_paid=True,
then=F("amount"),
),
default=Value(Decimal("0")),
output_field=DecimalField(),
)
),
Value(Decimal("0")),
output_field=DecimalField(),
),
income_unpaid=Coalesce(
Sum(
Case(
When(
type=Transaction.Type.INCOME,
is_paid=False,
then=F("amount"),
),
default=Value(Decimal("0")),
output_field=DecimalField(),
)
),
Value(Decimal("0")),
output_field=DecimalField(),
),
expense_unpaid=Coalesce(
Sum(
Case(
When(
type=Transaction.Type.EXPENSE,
is_paid=False,
then=F("amount"),
),
default=Value(Decimal("0")),
output_field=DecimalField(),
)
),
Value(Decimal("0")),
output_field=DecimalField(),
),
)
.annotate(
balance_unpaid=F("income_unpaid") - F("expense_unpaid"),
balance_paid=F("income_paid") - F("expense_paid"),
balance_total=F("income_paid")
+ F("income_unpaid")
- F("expense_paid")
- F("expense_unpaid"),
)
.order_by("month", "account__name")
)
all_months = [date(year, month, 1) for month in range(1, 13)]
# Get all accounts with their currencies
accounts = (
transactions.values(
"account__id",
"account__name",
"account__group__name",
"account__currency__code",
"account__currency__prefix",
"account__currency__suffix",
"account__currency__decimal_places",
"account__exchange_currency__code",
"account__exchange_currency__prefix",
"account__exchange_currency__suffix",
"account__exchange_currency__decimal_places",
)
.distinct()
.order_by("account__name")
)
# Get Currency objects for conversion
currencies = {currency.id: currency for currency in Currency.objects.all()}
result = {
month: {
account["account__id"]: {
"name": account["account__name"],
"group": account["account__group__name"],
"currency": {
"code": account["account__currency__code"],
"prefix": account["account__currency__prefix"],
"suffix": account["account__currency__suffix"],
"decimal_places": account["account__currency__decimal_places"],
},
"exchange_currency": (
{
"code": account["account__exchange_currency__code"],
"prefix": account["account__exchange_currency__prefix"],
"suffix": account["account__exchange_currency__suffix"],
"decimal_places": account[
"account__exchange_currency__decimal_places"
],
}
if account["account__exchange_currency__code"]
else None
),
"income_paid": Decimal("0"),
"expense_paid": Decimal("0"),
"income_unpaid": Decimal("0"),
"expense_unpaid": Decimal("0"),
"balance_unpaid": Decimal("0"),
"balance_paid": Decimal("0"),
"balance_total": Decimal("0"),
}
for account in accounts
}
for month in all_months
}
# Fill in the data
for entry in monthly_data:
month = entry["month"]
account_id = entry["account__id"]
for field in [
"income_paid",
"expense_paid",
"income_unpaid",
"expense_unpaid",
"balance_unpaid",
"balance_paid",
"balance_total",
]:
result[month][account_id][field] = entry[field]
if result[month][account_id]["exchange_currency"]:
from_currency = currencies[entry["account__currency"]]
to_currency = currencies[entry["account__exchange_currency"]]
if entry[field] > 0 or entry[field] < 0:
converted_amount, prefix, suffix, decimal_places = convert(
amount=entry[field],
from_currency=from_currency,
to_currency=to_currency,
)
if isinstance(converted_amount, Decimal):
print(converted_amount)
result[month][account_id][
f"exchange_{field}"
] = converted_amount
else:
result[month][account_id][f"exchange_{field}"] = Decimal(0)
return render(
request,
"yearly_overview/pages/overview_by_account.html",
context={
"year": year,
"next_year": next_year,
"previous_year": previous_year,
"totals": result,
},
)