- 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)
171 lines
4.3 KiB
Python
171 lines
4.3 KiB
Python
from datetime import date
|
|
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.monthly.services import update_budget
|
|
from app.monthly.models import Income
|
|
|
|
|
|
def _render_income_table(year, month, editing_id=None):
|
|
incomes = Income.objects.filter(
|
|
date__year=year,
|
|
date__month=month,
|
|
)
|
|
total = incomes.aggregate(total=Sum("amount"))["total"] or Decimal("0")
|
|
return render_to_string(
|
|
"pages/monthly/partials/income_table.html",
|
|
{
|
|
"incomes": incomes,
|
|
"total": total,
|
|
"year": year,
|
|
"month": month,
|
|
"today": date.today().isoformat(),
|
|
"editing_id": editing_id,
|
|
},
|
|
)
|
|
|
|
|
|
@liveview_handler("add_income")
|
|
def add_income(consumer, content):
|
|
form = content.get("form", {})
|
|
year = int(form.get("year", 0))
|
|
month = int(form.get("month", 0))
|
|
concept = form.get("income_concept", "").strip()
|
|
amount_raw = form.get("income_amount", "").replace(",", ".")
|
|
date_raw = form.get("income_date", "")
|
|
|
|
if not concept or not amount_raw or not date_raw or not year or not month:
|
|
return
|
|
|
|
try:
|
|
amount = Decimal(amount_raw)
|
|
income_date = date.fromisoformat(date_raw)
|
|
except (InvalidOperation, ValueError):
|
|
return
|
|
|
|
Income.objects.create(
|
|
date=income_date,
|
|
concept=concept,
|
|
amount=amount,
|
|
)
|
|
|
|
html = _render_income_table(year, month)
|
|
send(consumer, {"target": "#income-table", "html": html})
|
|
update_budget(consumer, year, month)
|
|
|
|
|
|
@liveview_handler("delete_income")
|
|
def delete_income(consumer, content):
|
|
data = content.get("data", {})
|
|
income_id = data.get("data_id", "")
|
|
year = int(data.get("data_year", 0))
|
|
month = int(data.get("data_month", 0))
|
|
|
|
if not income_id or not year or not month:
|
|
return
|
|
|
|
Income.objects.filter(id=income_id).delete()
|
|
|
|
html = _render_income_table(year, month)
|
|
send(consumer, {"target": "#income-table", "html": html})
|
|
update_budget(consumer, year, month)
|
|
|
|
|
|
@liveview_handler("edit_income")
|
|
def edit_income(consumer, content):
|
|
data = content.get("data", {})
|
|
income_id = data.get("data_id", "")
|
|
year = int(data.get("data_year", 0))
|
|
month = int(data.get("data_month", 0))
|
|
|
|
if not income_id:
|
|
return
|
|
|
|
try:
|
|
income = Income.objects.get(id=income_id)
|
|
except Income.DoesNotExist:
|
|
return
|
|
|
|
html = _render_income_table(year, month, editing_id=income.id)
|
|
send(consumer, {"target": "#income-table", "html": html})
|
|
|
|
|
|
@liveview_handler("cancel_edit_income")
|
|
def cancel_edit_income(consumer, content):
|
|
data = content.get("data", {})
|
|
year = int(data.get("data_year", 0))
|
|
month = int(data.get("data_month", 0))
|
|
|
|
if not year or not month:
|
|
return
|
|
|
|
html = _render_income_table(year, month)
|
|
send(consumer, {"target": "#income-table", "html": html})
|
|
|
|
|
|
@liveview_handler("save_income")
|
|
def save_income(consumer, content):
|
|
form = content.get("form", {})
|
|
income_id = form.get("income_id", "")
|
|
year = int(form.get("year", 0))
|
|
month = int(form.get("month", 0))
|
|
concept = form.get("income_concept", "").strip()
|
|
amount_raw = form.get("income_amount", "").replace(",", ".")
|
|
date_raw = form.get("income_date", "")
|
|
|
|
if not income_id or not concept or not amount_raw or not date_raw:
|
|
return
|
|
|
|
try:
|
|
amount = Decimal(amount_raw)
|
|
income_date = date.fromisoformat(date_raw)
|
|
income = Income.objects.get(id=income_id)
|
|
except (InvalidOperation, ValueError, Income.DoesNotExist):
|
|
return
|
|
|
|
income.date = income_date
|
|
income.concept = concept
|
|
income.amount = amount
|
|
income.save()
|
|
|
|
html = _render_income_table(year, month)
|
|
send(consumer, {"target": "#income-table", "html": html})
|
|
update_budget(consumer, year, month)
|
|
|
|
|
|
@liveview_handler("clone_income")
|
|
def clone_income(consumer, content):
|
|
form = content.get("form", {})
|
|
source = form.get("clone_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_incomes = Income.objects.filter(
|
|
date__year=src_year,
|
|
date__month=src_month,
|
|
)
|
|
|
|
today = date.today()
|
|
for inc in source_incomes:
|
|
Income.objects.create(
|
|
date=today,
|
|
concept=inc.concept,
|
|
amount=inc.amount,
|
|
)
|
|
|
|
html = _render_income_table(year, month)
|
|
send(consumer, {"target": "#income-table", "html": html})
|
|
update_budget(consumer, year, month)
|