from decimal import Decimal, InvalidOperation from django.db.models import Sum from django.template.loader import render_to_string from liveview import liveview_handler, send from app.expenses.models import FixedExpenseConcept from app.expenses.templatetags.money import format_money from app.monthly.models import MonthlyFixedExpense from app.monthly.services import update_budget def _ensure_entries_exist(year, month): """Create missing MonthlyFixedExpense entries in bulk.""" concepts = FixedExpenseConcept.objects.all() existing_ids = set( MonthlyFixedExpense.objects.filter(year=year, month=month).values_list( "concept_id", flat=True ) ) to_create = [ MonthlyFixedExpense(year=year, month=month, concept=c) for c in concepts if c.id not in existing_ids ] if to_create: MonthlyFixedExpense.objects.bulk_create(to_create) def _render_fixed_expenses_table(year, month): _ensure_entries_exist(year, month) entries = MonthlyFixedExpense.objects.filter(year=year, month=month).select_related( "concept" ) total = entries.aggregate(total=Sum("amount"))["total"] or Decimal("0") return render_to_string( "pages/monthly/partials/fixed_expenses_table.html", {"entries": entries, "total": total, "year": year, "month": month}, ) @liveview_handler("save_monthly_fixed_expense") def save_monthly_fixed_expense(consumer, content): form = content.get("form", {}) entry_id = form.get("entry_id", "") year = int(form.get("year", 0)) month = int(form.get("month", 0)) amount_raw = form.get("fe_amount", "").replace(",", ".") if not entry_id or not year or not month: return try: amount = Decimal(amount_raw) if amount_raw else Decimal("0") entry = MonthlyFixedExpense.objects.get(id=entry_id) entry.amount = amount entry.save() except (InvalidOperation, ValueError, MonthlyFixedExpense.DoesNotExist): return total = MonthlyFixedExpense.objects.filter(year=year, month=month).aggregate( total=Sum("amount") )["total"] or Decimal("0") send(consumer, {"target": "#fe-total", "html": f"{format_money(total)} €"}) update_budget(consumer, year, month) @liveview_handler("clone_fixed_expenses") def clone_fixed_expenses(consumer, content): form = content.get("form", {}) source = form.get("clone_fe_source", "") year = int(form.get("year", 0)) month = int(form.get("month", 0)) if not source or not year or not month: return try: src_year, src_month = source.split("-") src_year, src_month = int(src_year), int(src_month) except ValueError: return source_entries = MonthlyFixedExpense.objects.filter( year=src_year, month=src_month ).select_related("concept") _ensure_entries_exist(year, month) for src in source_entries: MonthlyFixedExpense.objects.filter( year=year, month=month, concept=src.concept ).update(amount=src.amount) html = _render_fixed_expenses_table(year, month) send(consumer, {"target": "#fixed-expenses-table", "html": html}) update_budget(consumer, year, month)