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'' for sub in subcategories ) html = f'' 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})