from decimal import Decimal, InvalidOperation from django.template.loader import render_to_string from liveview import liveview_handler, send from app.yearly.models import PlannedExpense MONTH_LABELS = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December", ] def _render_planned_tables(year, editing_id=None): months_data = [] for m in range(1, 13): items = PlannedExpense.objects.filter(year=year, month=m) total = sum(i.amount for i in items) months_data.append( { "month": m, "label": MONTH_LABELS[m - 1], "items": items, "total": total, } ) grand_total = sum(md["total"] for md in months_data) # Clone years available_years = ( PlannedExpense.objects.exclude(year=year) .values_list("year", flat=True) .distinct() .order_by("-year") ) return render_to_string( "pages/yearly/partials/planned_tables.html", { "months_data": months_data, "year": year, "grand_total": grand_total, "editing_id": editing_id, "clone_years": list(available_years), }, ) @liveview_handler("add_planned_expense") def add_planned_expense(consumer, content): form = content.get("form", {}) year = int(form.get("year", 0)) month = int(form.get("pe_month", 0)) concept = form.get("pe_concept", "").strip() amount_raw = form.get("pe_amount", "").replace(",", ".") if not concept or not amount_raw or not year or not month: return try: amount = Decimal(amount_raw) except (InvalidOperation, ValueError): return PlannedExpense.objects.create( year=year, month=month, concept=concept, amount=amount ) html = _render_planned_tables(year) send(consumer, {"target": "#planned-expenses", "html": html}) @liveview_handler("edit_planned_expense") def edit_planned_expense(consumer, content): data = content.get("data", {}) pe_id = data.get("data_id", "") year = int(data.get("data_year", 0)) if not pe_id or not year: return html = _render_planned_tables(year, editing_id=int(pe_id)) send(consumer, {"target": "#planned-expenses", "html": html}) @liveview_handler("save_planned_expense") def save_planned_expense(consumer, content): form = content.get("form", {}) pe_id = form.get("pe_id", "") year = int(form.get("year", 0)) month = int(form.get("pe_month", 0)) concept = form.get("pe_concept", "").strip() amount_raw = form.get("pe_amount", "").replace(",", ".") if not pe_id or not concept or not amount_raw or not year or not month: return try: amount = Decimal(amount_raw) pe = PlannedExpense.objects.get(id=pe_id) pe.month = month pe.concept = concept pe.amount = amount pe.save() except (InvalidOperation, ValueError, PlannedExpense.DoesNotExist): return html = _render_planned_tables(year) send(consumer, {"target": "#planned-expenses", "html": html}) @liveview_handler("cancel_edit_planned_expense") def cancel_edit_planned_expense(consumer, content): data = content.get("data", {}) year = int(data.get("data_year", 0)) if not year: return html = _render_planned_tables(year) send(consumer, {"target": "#planned-expenses", "html": html}) @liveview_handler("delete_planned_expense") def delete_planned_expense(consumer, content): data = content.get("data", {}) pe_id = data.get("data_id", "") year = int(data.get("data_year", 0)) if not pe_id or not year: return PlannedExpense.objects.filter(id=pe_id).delete() html = _render_planned_tables(year) send(consumer, {"target": "#planned-expenses", "html": html}) @liveview_handler("clone_planned_expenses") def clone_planned_expenses(consumer, content): form = content.get("form", {}) source_year = int(form.get("clone_pe_source", 0)) year = int(form.get("year", 0)) if not source_year or not year: return source_items = PlannedExpense.objects.filter(year=source_year) for item in source_items: PlannedExpense.objects.create( year=year, month=item.month, concept=item.concept, amount=item.amount ) html = _render_planned_tables(year) send(consumer, {"target": "#planned-expenses", "html": html})