mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-07-08 22:05:11 +02:00
changes
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class MonthlyOverviewConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "apps.monthly_overview"
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
@@ -0,0 +1,27 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("monthly/", views.index, name="monthly_index"),
|
||||
path(
|
||||
"monthly/<int:month>/<int:year>/transactions/list/",
|
||||
views.transactions_list,
|
||||
name="monthly_transactions_list",
|
||||
),
|
||||
path(
|
||||
"monthly/<int:month>/<int:year>/",
|
||||
views.monthly_overview,
|
||||
name="monthly_overview",
|
||||
),
|
||||
path(
|
||||
"monthly/<int:month>/<int:year>/summary/",
|
||||
views.monthly_summary,
|
||||
name="monthly_summary",
|
||||
),
|
||||
path(
|
||||
"available_dates/",
|
||||
views.month_year_picker,
|
||||
name="available_dates",
|
||||
),
|
||||
]
|
||||
@@ -0,0 +1,2 @@
|
||||
from .ui import *
|
||||
from .main import *
|
||||
@@ -0,0 +1,309 @@
|
||||
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)
|
||||
|
||||
f = TransactionsFilter(request.GET)
|
||||
transactions_filtered = (
|
||||
f.qs.filter()
|
||||
.filter(
|
||||
reference_date__year=year,
|
||||
reference_date__month=month,
|
||||
)
|
||||
.annotate(
|
||||
date_order=Case(
|
||||
When(date=tomorrow, then=Value(0)),
|
||||
When(date=today, then=Value(1)),
|
||||
When(date=yesterday, then=Value(2)),
|
||||
default=Value(3),
|
||||
output_field=IntegerField(),
|
||||
)
|
||||
)
|
||||
.order_by("date_order", "date", "id")
|
||||
.prefetch_related(
|
||||
"account",
|
||||
"category",
|
||||
"tags",
|
||||
"account__exchange_currency",
|
||||
"account__currency",
|
||||
)
|
||||
)
|
||||
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},
|
||||
)
|
||||
@@ -0,0 +1,71 @@
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from django.db.models import (
|
||||
Count,
|
||||
)
|
||||
from django.db.models.functions import ExtractYear, ExtractMonth
|
||||
from django.shortcuts import render
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.transactions.models import Transaction
|
||||
|
||||
|
||||
def month_year_picker(request):
|
||||
# Get current month and year from request or use current date
|
||||
current_date = timezone.localdate(timezone.now())
|
||||
current_month = int(request.GET.get("month", current_date.month))
|
||||
current_year = int(request.GET.get("year", current_date.year))
|
||||
|
||||
# Set start and end dates
|
||||
start_date = timezone.datetime(current_year - 1, 1, 1).date()
|
||||
end_date = timezone.datetime(current_year + 1, 12, 31).date()
|
||||
|
||||
# Get years from transactions
|
||||
transaction_years = Transaction.objects.dates("reference_date", "year", order="ASC")
|
||||
|
||||
# Extend start_date and end_date if necessary
|
||||
if transaction_years:
|
||||
start_date = min(start_date, transaction_years.first().replace(month=1, day=1))
|
||||
end_date = max(end_date, transaction_years.last().replace(month=12, day=31))
|
||||
|
||||
# Generate all months between start_date and end_date
|
||||
all_months = []
|
||||
current_month_date = start_date
|
||||
while current_month_date <= end_date:
|
||||
all_months.append(current_month_date)
|
||||
current_month_date += relativedelta(months=1)
|
||||
|
||||
# Get transaction counts for each month
|
||||
transaction_counts = (
|
||||
Transaction.objects.annotate(
|
||||
year=ExtractYear("reference_date"), month=ExtractMonth("reference_date")
|
||||
)
|
||||
.values("year", "month")
|
||||
.annotate(transaction_count=Count("id"))
|
||||
.order_by("year", "month")
|
||||
)
|
||||
|
||||
# Create a dictionary for quick lookup
|
||||
count_dict = {
|
||||
(item["year"], item["month"]): item["transaction_count"]
|
||||
for item in transaction_counts
|
||||
}
|
||||
|
||||
# Create the final result
|
||||
result = [
|
||||
{
|
||||
"year": date.year,
|
||||
"month": date.month,
|
||||
"transaction_count": count_dict.get((date.year, date.month), 0),
|
||||
}
|
||||
for date in all_months
|
||||
]
|
||||
|
||||
return render(
|
||||
request,
|
||||
"monthly_overview/fragments/month_year_picker.html",
|
||||
{
|
||||
"month_year_data": result,
|
||||
"current_month": current_month,
|
||||
"current_year": current_year,
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user