mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-03-28 20:22:01 +01:00
316 lines
10 KiB
Python
316 lines
10 KiB
Python
from decimal import Decimal
|
|
|
|
from django.contrib.auth.decorators import login_required
|
|
from django.db.models import (
|
|
Case,
|
|
When,
|
|
Value,
|
|
IntegerField,
|
|
Sum,
|
|
Q,
|
|
)
|
|
from django.shortcuts import render, redirect
|
|
from django.utils import timezone
|
|
from django.views.decorators.http import require_http_methods
|
|
|
|
from apps.common.decorators.htmx import only_htmx
|
|
from apps.common.functions.dates import remaining_days_in_month
|
|
from apps.transactions.filters import TransactionsFilter
|
|
from apps.transactions.models import Transaction
|
|
|
|
|
|
@login_required
|
|
def index(request):
|
|
now = timezone.localdate(timezone.now())
|
|
|
|
return redirect(to="monthly_overview", month=now.month, year=now.year)
|
|
|
|
|
|
@login_required
|
|
@require_http_methods(["GET"])
|
|
def monthly_overview(request, month: int, year: int):
|
|
transactions = (
|
|
Transaction.objects.all()
|
|
.filter(
|
|
reference_date__year=year,
|
|
reference_date__month=month,
|
|
)
|
|
.order_by("date", "id")
|
|
.select_related()
|
|
)
|
|
|
|
if month < 1 or month > 12:
|
|
from django.http import Http404
|
|
|
|
raise Http404("Month is out of range")
|
|
|
|
next_month = 1 if month == 12 else month + 1
|
|
next_year = year + 1 if next_month == 1 and month == 12 else year
|
|
|
|
previous_month = 12 if month == 1 else month - 1
|
|
previous_year = year - 1 if previous_month == 12 and month == 1 else year
|
|
|
|
f = TransactionsFilter(request.GET, queryset=transactions)
|
|
|
|
return render(
|
|
request,
|
|
"monthly_overview/pages/overview.html",
|
|
context={
|
|
"month": month,
|
|
"year": year,
|
|
"next_month": next_month,
|
|
"next_year": next_year,
|
|
"previous_month": previous_month,
|
|
"previous_year": previous_year,
|
|
"filter": f,
|
|
},
|
|
)
|
|
|
|
|
|
@only_htmx
|
|
@login_required
|
|
@require_http_methods(["GET"])
|
|
def transactions_list(request, month: int, year: int):
|
|
today = timezone.localdate(timezone.now())
|
|
yesterday = today - timezone.timedelta(days=1)
|
|
tomorrow = today + timezone.timedelta(days=1)
|
|
last_7_days = today - timezone.timedelta(days=7)
|
|
next_7_days = today + timezone.timedelta(days=7)
|
|
|
|
# TO-DO Improve date-order
|
|
f = TransactionsFilter(request.GET)
|
|
transactions_filtered = (
|
|
f.qs.filter()
|
|
.filter(
|
|
reference_date__year=year,
|
|
reference_date__month=month,
|
|
)
|
|
.annotate(
|
|
date_order=Case(
|
|
When(date__lte=next_7_days, date__gte=tomorrow, then=Value(0)),
|
|
When(date=tomorrow, then=Value(1)),
|
|
When(date=today, then=Value(2)),
|
|
When(date=yesterday, then=Value(3)),
|
|
When(date__gte=last_7_days, date__lte=today, then=Value(4)),
|
|
default=Value(5),
|
|
output_field=IntegerField(),
|
|
)
|
|
)
|
|
.order_by("date_order", "date", "id")
|
|
.prefetch_related(
|
|
"account",
|
|
"category",
|
|
"tags",
|
|
"account__exchange_currency",
|
|
"account__currency",
|
|
"installment_plan",
|
|
)
|
|
)
|
|
return render(
|
|
request,
|
|
"monthly_overview/fragments/list.html",
|
|
context={"transactions": transactions_filtered},
|
|
)
|
|
|
|
|
|
@only_htmx
|
|
@login_required
|
|
@require_http_methods(["GET"])
|
|
def monthly_summary(request, month: int, year: int):
|
|
# Helper function to calculate sums for different transaction types
|
|
def calculate_sum(transaction_type, is_paid):
|
|
return (
|
|
base_queryset.filter(type=transaction_type, is_paid=is_paid)
|
|
.values(
|
|
"account__currency__name",
|
|
"account__currency__suffix",
|
|
"account__currency__prefix",
|
|
"account__currency__decimal_places",
|
|
)
|
|
.annotate(total=Sum("amount"))
|
|
.order_by("account__currency__name")
|
|
)
|
|
|
|
# Helper function to format currency sums
|
|
def format_currency_sum(queryset):
|
|
return [
|
|
{
|
|
"currency": item["account__currency__name"],
|
|
"suffix": item["account__currency__suffix"],
|
|
"prefix": item["account__currency__prefix"],
|
|
"decimal_places": item["account__currency__decimal_places"],
|
|
"amount": item["total"],
|
|
}
|
|
for item in queryset
|
|
]
|
|
|
|
# Calculate totals
|
|
def calculate_total(income, expenses):
|
|
totals = {}
|
|
|
|
# Process income
|
|
for item in income:
|
|
currency = item["account__currency__name"]
|
|
totals[currency] = totals.get(currency, Decimal("0")) + item["total"]
|
|
|
|
# Subtract expenses
|
|
for item in expenses:
|
|
currency = item["account__currency__name"]
|
|
totals[currency] = totals.get(currency, Decimal("0")) - item["total"]
|
|
|
|
return [
|
|
{
|
|
"currency": currency,
|
|
"suffix": next(
|
|
(
|
|
item["account__currency__suffix"]
|
|
for item in list(income) + list(expenses)
|
|
if item["account__currency__name"] == currency
|
|
),
|
|
"",
|
|
),
|
|
"prefix": next(
|
|
(
|
|
item["account__currency__prefix"]
|
|
for item in list(income) + list(expenses)
|
|
if item["account__currency__name"] == currency
|
|
),
|
|
"",
|
|
),
|
|
"decimal_places": next(
|
|
(
|
|
item["account__currency__decimal_places"]
|
|
for item in list(income) + list(expenses)
|
|
if item["account__currency__name"] == currency
|
|
),
|
|
2,
|
|
),
|
|
"amount": amount,
|
|
}
|
|
for currency, amount in totals.items()
|
|
]
|
|
|
|
# Calculate total final
|
|
def sum_totals(total1, total2):
|
|
totals = {}
|
|
for item in total1 + total2:
|
|
currency = item["currency"]
|
|
totals[currency] = totals.get(currency, Decimal("0")) + item["amount"]
|
|
return [
|
|
{
|
|
"currency": currency,
|
|
"suffix": next(
|
|
item["suffix"]
|
|
for item in total1 + total2
|
|
if item["currency"] == currency
|
|
),
|
|
"prefix": next(
|
|
item["prefix"]
|
|
for item in total1 + total2
|
|
if item["currency"] == currency
|
|
),
|
|
"decimal_places": next(
|
|
item["decimal_places"]
|
|
for item in total1 + total2
|
|
if item["currency"] == currency
|
|
),
|
|
"amount": amount,
|
|
}
|
|
for currency, amount in totals.items()
|
|
]
|
|
|
|
# Base queryset with all required filters
|
|
base_queryset = Transaction.objects.filter(
|
|
reference_date__year=year, reference_date__month=month, account__is_asset=False
|
|
).exclude(Q(category__mute=True) & ~Q(category=None))
|
|
|
|
# Calculate sums for different transaction types
|
|
paid_income = calculate_sum(Transaction.Type.INCOME, True)
|
|
projected_income = calculate_sum(Transaction.Type.INCOME, False)
|
|
paid_expenses = calculate_sum(Transaction.Type.EXPENSE, True)
|
|
projected_expenses = calculate_sum(Transaction.Type.EXPENSE, False)
|
|
|
|
total_current = calculate_total(paid_income, paid_expenses)
|
|
total_projected = calculate_total(projected_income, projected_expenses)
|
|
|
|
total_final = sum_totals(total_current, total_projected)
|
|
|
|
# Calculate daily spending allowance
|
|
remaining_days = remaining_days_in_month(
|
|
month=month, year=year, current_date=timezone.localdate(timezone.now())
|
|
)
|
|
if (
|
|
timezone.localdate(timezone.now()).month == month
|
|
and timezone.localdate(timezone.now()).year == year
|
|
):
|
|
daily_spending_allowance = [
|
|
{
|
|
"currency": item["currency"],
|
|
"suffix": item["suffix"],
|
|
"prefix": item["prefix"],
|
|
"decimal_places": item["decimal_places"],
|
|
"amount": (
|
|
amount
|
|
if (amount := item["amount"] / remaining_days) > 0
|
|
else Decimal("0")
|
|
),
|
|
}
|
|
for item in total_final
|
|
]
|
|
else:
|
|
daily_spending_allowance = []
|
|
|
|
# Construct the response dictionary
|
|
data = {
|
|
"paid_income": format_currency_sum(paid_income),
|
|
"projected_income": format_currency_sum(projected_income),
|
|
"paid_expenses": format_currency_sum(paid_expenses),
|
|
"projected_expenses": format_currency_sum(projected_expenses),
|
|
"total_current": total_current,
|
|
"total_projected": total_projected,
|
|
"total_final": total_final,
|
|
"daily_spending_allowance": daily_spending_allowance,
|
|
}
|
|
|
|
# account_summary = (
|
|
# Account.objects.annotate(
|
|
# balance=Coalesce(
|
|
# Sum(
|
|
# Case(
|
|
# When(
|
|
# transaction__type=Transaction.Type.INCOME,
|
|
# transaction__is_paid=True,
|
|
# transaction__reference_date__year=year,
|
|
# transaction__reference_date__month=month,
|
|
# then=F("transaction__amount"),
|
|
# ),
|
|
# When(
|
|
# transaction__type=Transaction.Type.EXPENSE,
|
|
# transaction__is_paid=True,
|
|
# transaction__reference_date__year=year,
|
|
# transaction__reference_date__month=month,
|
|
# then=-F("transaction__amount"),
|
|
# ),
|
|
# output_field=DecimalField(),
|
|
# )
|
|
# ),
|
|
# Decimal(0),
|
|
# )
|
|
# )
|
|
# .values(
|
|
# "id",
|
|
# "name",
|
|
# "balance",
|
|
# "currency__prefix",
|
|
# "currency__suffix",
|
|
# "currency__decimal_places",
|
|
# )
|
|
# .order_by("id")
|
|
# )
|
|
|
|
return render(
|
|
request,
|
|
"monthly_overview/fragments/monthly_summary.html",
|
|
context={"totals": data},
|
|
)
|