mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-03-18 23:44:03 +01:00
463 lines
15 KiB
Python
463 lines
15 KiB
Python
from datetime import date
|
|
from decimal import Decimal
|
|
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.db.models import Sum, F, Value, DecimalField, Q
|
|
from django.db.models.expressions import Case, When
|
|
from django.db.models.functions import TruncMonth, Coalesce, TruncYear
|
|
from django.http import Http404
|
|
from django.shortcuts import render, redirect
|
|
from django.utils import timezone
|
|
|
|
from apps.accounts.models import Account
|
|
from apps.currencies.models import Currency
|
|
from apps.currencies.utils.convert import convert
|
|
from apps.transactions.models import Transaction
|
|
from apps.common.decorators.htmx import only_htmx
|
|
|
|
|
|
@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 index_yearly_overview_by_currency(request, year: int):
|
|
next_year = year + 1
|
|
previous_year = year - 1
|
|
|
|
month_options = range(1, 13)
|
|
currency_options = Currency.objects.filter(
|
|
accounts__transactions__date__year=year
|
|
).distinct()
|
|
|
|
return render(
|
|
request,
|
|
"yearly_overview/pages/overview_by_currency.html",
|
|
context={
|
|
"year": year,
|
|
"next_year": next_year,
|
|
"previous_year": previous_year,
|
|
"months": month_options,
|
|
"currencies": currency_options,
|
|
},
|
|
)
|
|
|
|
|
|
@only_htmx
|
|
@login_required
|
|
def yearly_overview_by_currency(request, year: int):
|
|
month = request.GET.get("month")
|
|
currency = request.GET.get("currency")
|
|
|
|
# Base query filter
|
|
filter_params = {"reference_date__year": year, "account__is_archived": False}
|
|
|
|
# Add month filter if provided
|
|
if month:
|
|
month = int(month)
|
|
if not 1 <= month <= 12:
|
|
raise Http404("Invalid month")
|
|
filter_params["reference_date__month"] = month
|
|
|
|
# Add currency filter if provided
|
|
if currency:
|
|
filter_params["account__currency_id"] = int(currency)
|
|
|
|
transactions = Transaction.objects.filter(**filter_params).exclude(
|
|
Q(category__mute=True) & ~Q(category=None)
|
|
)
|
|
|
|
if month:
|
|
date_trunc = TruncMonth("reference_date")
|
|
else:
|
|
date_trunc = TruncYear("reference_date")
|
|
|
|
monthly_data = (
|
|
transactions.annotate(month=date_trunc)
|
|
.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 dictionary to store the final result
|
|
result = {
|
|
"income_paid": [],
|
|
"expense_paid": [],
|
|
"income_unpaid": [],
|
|
"expense_unpaid": [],
|
|
"balance_unpaid": [],
|
|
"balance_paid": [],
|
|
"balance_total": [],
|
|
}
|
|
|
|
# Fill in the data
|
|
for entry in monthly_data:
|
|
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[field].append(
|
|
{
|
|
"code": currency_code,
|
|
"prefix": prefix,
|
|
"suffix": suffix,
|
|
"decimal_places": decimal_places,
|
|
"amount": entry[field],
|
|
}
|
|
)
|
|
|
|
return render(
|
|
request,
|
|
"yearly_overview/fragments/currency_data.html",
|
|
context={
|
|
"year": year,
|
|
"totals": result,
|
|
},
|
|
)
|
|
|
|
|
|
@login_required
|
|
def index_yearly_overview_by_account(request, year: int):
|
|
next_year = year + 1
|
|
previous_year = year - 1
|
|
|
|
month_options = range(1, 13)
|
|
account_options = (
|
|
Account.objects.filter(is_archived=False, transactions__date__year=year)
|
|
.select_related("group")
|
|
.distinct()
|
|
.order_by("group__name", "name", "id")
|
|
)
|
|
|
|
return render(
|
|
request,
|
|
"yearly_overview/pages/overview_by_account.html",
|
|
context={
|
|
"year": year,
|
|
"next_year": next_year,
|
|
"previous_year": previous_year,
|
|
"months": month_options,
|
|
"accounts": account_options,
|
|
},
|
|
)
|
|
|
|
|
|
@only_htmx
|
|
@login_required
|
|
def yearly_overview_by_account(request, year: int):
|
|
month = request.GET.get("month")
|
|
account = request.GET.get("account")
|
|
|
|
# Base query filter
|
|
filter_params = {"reference_date__year": year, "account__is_archived": False}
|
|
|
|
# Add month filter if provided
|
|
if month:
|
|
month = int(month)
|
|
if not 1 <= month <= 12:
|
|
raise Http404("Invalid month")
|
|
filter_params["reference_date__month"] = month
|
|
|
|
# Add account filter if provided
|
|
if account:
|
|
filter_params["account_id"] = int(account)
|
|
|
|
transactions = Transaction.objects.filter(**filter_params)
|
|
|
|
# Use TruncYear if no month specified, otherwise use TruncMonth
|
|
date_trunc = TruncMonth("reference_date") if month else TruncYear("reference_date")
|
|
|
|
monthly_data = (
|
|
transactions.annotate(month=date_trunc)
|
|
.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")
|
|
)
|
|
|
|
# Determine which dates to include
|
|
if month:
|
|
all_months = [date(year, month, 1)]
|
|
else:
|
|
all_months = [date(year, 1, 1)] # Just one entry for the whole year
|
|
|
|
# Get all accounts with their currencies (filtered by account if specified)
|
|
accounts_filter = {}
|
|
if account:
|
|
accounts_filter["account__id"] = int(account)
|
|
|
|
accounts = (
|
|
transactions.filter(**accounts_filter)
|
|
.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__group__name", "account__name", "account__id")
|
|
)
|
|
|
|
# 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):
|
|
result[month][account_id][
|
|
f"exchange_{field}"
|
|
] = converted_amount
|
|
else:
|
|
result[month][account_id][f"exchange_{field}"] = Decimal(0)
|
|
|
|
return render(
|
|
request,
|
|
"yearly_overview/fragments/account_data.html",
|
|
context={"year": year, "totals": result, "single": True if account else False},
|
|
)
|