- Yearly view: replace N*12 queries with annotate/ExtractMonth (6 queries) - Fixed expenses: bulk_create instead of get_or_create loop - Use DB aggregate(Sum) instead of Python sum() for totals - Remove 10 unused CRUD views, 13 URLs, 6 templates, 3 forms - Fix overly broad Exception catch in save_expense - Move update_budget to app/monthly/services.py (no cross-handler imports)
156 lines
4.4 KiB
Python
156 lines
4.4 KiB
Python
from collections import OrderedDict
|
|
from datetime import timedelta
|
|
from decimal import Decimal, InvalidOperation
|
|
|
|
from django.template.loader import render_to_string
|
|
from django.utils import timezone
|
|
from liveview import liveview_handler, send
|
|
|
|
from app.expenses.models import Category, Expense, Subcategory
|
|
|
|
|
|
def _build_week_data(offset):
|
|
today = timezone.localdate()
|
|
monday = today - timedelta(days=today.weekday()) + timedelta(weeks=offset)
|
|
sunday = monday + timedelta(days=6)
|
|
expenses = Expense.objects.filter(
|
|
created_at__date__gte=monday,
|
|
created_at__date__lte=sunday,
|
|
).select_related("category", "subcategory")
|
|
|
|
categories_data = OrderedDict()
|
|
total = Decimal("0")
|
|
for expense in expenses:
|
|
cat = expense.category
|
|
if cat.id not in categories_data:
|
|
categories_data[cat.id] = {
|
|
"category": cat,
|
|
"expenses": [],
|
|
"subcategory_totals": OrderedDict(),
|
|
"total": Decimal("0"),
|
|
}
|
|
entry = categories_data[cat.id]
|
|
entry["expenses"].append(expense)
|
|
entry["total"] += expense.amount
|
|
total += expense.amount
|
|
|
|
sub = expense.subcategory
|
|
if sub.id not in entry["subcategory_totals"]:
|
|
entry["subcategory_totals"][sub.id] = {
|
|
"subcategory": sub,
|
|
"total": Decimal("0"),
|
|
}
|
|
entry["subcategory_totals"][sub.id]["total"] += expense.amount
|
|
|
|
return {
|
|
"categories_data": categories_data.values(),
|
|
"total": total,
|
|
"monday": monday,
|
|
"sunday": sunday,
|
|
"offset": offset,
|
|
"prev_offset": offset - 1,
|
|
"next_offset": offset + 1,
|
|
"is_current_week": offset == 0,
|
|
}
|
|
|
|
|
|
def _render_week(offset, editing_id=None):
|
|
context = _build_week_data(offset)
|
|
context["editing_id"] = editing_id
|
|
if editing_id:
|
|
try:
|
|
expense = Expense.objects.get(id=editing_id)
|
|
context["editing_expense"] = expense
|
|
context["user_categories"] = Category.objects.all()
|
|
context["user_subcategories"] = Subcategory.objects.filter(
|
|
category=expense.category
|
|
)
|
|
except Expense.DoesNotExist:
|
|
pass
|
|
return render_to_string("pages/expenses/partials/week_content.html", context)
|
|
|
|
|
|
@liveview_handler("delete_expense")
|
|
def delete_expense(consumer, content):
|
|
data = content.get("data", {})
|
|
expense_id = data.get("data_id", "")
|
|
offset = int(data.get("data_offset", 0))
|
|
|
|
if not expense_id:
|
|
return
|
|
|
|
Expense.objects.filter(id=expense_id).delete()
|
|
html = _render_week(offset)
|
|
send(consumer, {"target": "#week-content", "html": html})
|
|
|
|
|
|
@liveview_handler("edit_expense")
|
|
def edit_expense(consumer, content):
|
|
data = content.get("data", {})
|
|
expense_id = data.get("data_id", "")
|
|
offset = int(data.get("data_offset", 0))
|
|
|
|
if not expense_id:
|
|
return
|
|
|
|
html = _render_week(offset, editing_id=int(expense_id))
|
|
send(consumer, {"target": "#week-content", "html": html})
|
|
|
|
|
|
@liveview_handler("save_expense")
|
|
def save_expense(consumer, content):
|
|
form = content.get("form", {})
|
|
expense_id = form.get("expense_id", "")
|
|
offset = int(form.get("offset", 0))
|
|
concept = form.get("expense_concept", "").strip()
|
|
amount_raw = form.get("expense_amount", "").replace(",", ".")
|
|
category_id = form.get("expense_category", "")
|
|
subcategory_id = form.get("expense_subcategory", "")
|
|
|
|
if not expense_id or not concept or not amount_raw:
|
|
return
|
|
|
|
try:
|
|
expense = Expense.objects.get(id=expense_id)
|
|
expense.concept = concept
|
|
expense.amount = Decimal(amount_raw)
|
|
if category_id:
|
|
expense.category_id = int(category_id)
|
|
if subcategory_id:
|
|
expense.subcategory_id = int(subcategory_id)
|
|
expense.save()
|
|
except (Expense.DoesNotExist, ValueError, InvalidOperation):
|
|
return
|
|
|
|
html = _render_week(offset)
|
|
send(consumer, {"target": "#week-content", "html": html})
|
|
|
|
|
|
@liveview_handler("update_week_subcategories")
|
|
def update_week_subcategories(consumer, content):
|
|
form = content.get("form", {})
|
|
category_id = form.get("expense_category", "")
|
|
|
|
if not category_id:
|
|
return
|
|
|
|
try:
|
|
subcategories = Subcategory.objects.filter(category_id=int(category_id))
|
|
except (ValueError, Category.DoesNotExist):
|
|
return
|
|
|
|
options = "".join(
|
|
f'<option value="{sub.id}">{sub.name}</option>' for sub in subcategories
|
|
)
|
|
html = f'<select name="expense_subcategory" class="select select-bordered select-sm flex-1 min-w-[7rem]">{options}</select>'
|
|
send(consumer, {"target": "#week-subcategory-wrapper", "html": html})
|
|
|
|
|
|
@liveview_handler("cancel_edit_expense")
|
|
def cancel_edit_expense(consumer, content):
|
|
data = content.get("data", {})
|
|
offset = int(data.get("data_offset", 0))
|
|
|
|
html = _render_week(offset)
|
|
send(consumer, {"target": "#week-content", "html": html})
|