Saca de home() dos ayudantes reutilizables: - _account_balances(): calcula el saldo de cada cuenta con dos queries agregadas en lugar de llamar a account.current_balance() en bucle, que disparaba un N+1. - _category_tree(): aplana las categorias en preorden con su profundidad, para poder indentarlas en la plantilla. Con ellos, account_list pasa a mostrar el saldo calculado de cada cuenta (antes usaba current_balance() desde la plantilla) y category_list muestra un arbol indentado en vez de una columna "Categoria padre" que obligaba a reconstruir la jerarquia mentalmente. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
102 lines
4.7 KiB
Python
102 lines
4.7 KiB
Python
import pytest
|
|
from datetime import date
|
|
from decimal import Decimal
|
|
from django.urls import reverse
|
|
from expenses.models import Account, Expense, Income, Category
|
|
|
|
pytestmark = pytest.mark.django_db
|
|
|
|
|
|
def test_current_balance_reflects_income_and_expenses(user, category):
|
|
acc = Account.objects.create(owner=user, name="Cuenta", initial_balance=Decimal("100"))
|
|
Income.objects.create(owner=user, account=acc, name="Nomina", amount=Decimal("50"), date=date.today())
|
|
Expense.objects.create(owner=user, account=acc, category=category, amount=Decimal("30"), date=date.today())
|
|
|
|
assert acc.current_balance() == Decimal("120")
|
|
|
|
|
|
def test_monthly_balance_returns_12_rows_in_order(user):
|
|
acc = Account.objects.create(owner=user, name="Cuenta", initial_balance=Decimal("100"))
|
|
|
|
data = acc.monthly_balance(2020)
|
|
|
|
assert len(data) == 12
|
|
assert [row["month"] for row in data] == list(range(1, 13))
|
|
assert all(row["balance"] == 100.0 for row in data)
|
|
|
|
|
|
def test_monthly_balance_is_cumulative_across_months(user, category):
|
|
acc = Account.objects.create(owner=user, name="Cuenta", initial_balance=Decimal("0"))
|
|
year = date.today().year - 1 # avoid the current-month live-balance patch
|
|
|
|
Expense.objects.create(owner=user, account=acc, category=category, amount=Decimal("40"), date=date(year, 3, 1))
|
|
Income.objects.create(owner=user, account=acc, name="Extra", amount=Decimal("100"), date=date(year, 6, 1))
|
|
|
|
data = acc.monthly_balance(year)
|
|
|
|
assert data[2]["balance"] == -40.0 # month 3
|
|
assert data[5]["balance"] == 60.0 # month 6, cumulative
|
|
assert data[11]["balance"] == 60.0 # carries forward to December
|
|
|
|
|
|
def test_monthly_balance_respects_year_param_and_excludes_other_years(user, category):
|
|
acc = Account.objects.create(owner=user, name="Cuenta", initial_balance=Decimal("0"))
|
|
|
|
Expense.objects.create(owner=user, account=acc, category=category, amount=Decimal("50"), date=date(2023, 6, 15))
|
|
Income.objects.create(owner=user, account=acc, name="Extra", amount=Decimal("30"), date=date(2024, 3, 10))
|
|
|
|
data_2023 = acc.monthly_balance(2023)
|
|
assert data_2023[5]["balance"] == -50.0 # month 6
|
|
assert data_2023[11]["balance"] == -50.0
|
|
|
|
data_2024 = acc.monthly_balance(2024)
|
|
assert data_2024[1]["balance"] == -50.0 # month 2: prior year's expense folded into start
|
|
assert data_2024[2]["balance"] == -20.0 # month 3: 2024 income applied
|
|
|
|
|
|
def test_monthly_balance_current_month_patched_with_live_balance(user, category):
|
|
today = date.today()
|
|
acc = Account.objects.create(owner=user, name="Cuenta", initial_balance=Decimal("100"))
|
|
Expense.objects.create(owner=user, account=acc, category=category, amount=Decimal("30"), date=today)
|
|
|
|
data = acc.monthly_balance(today.year)
|
|
|
|
assert data[today.month - 1]["balance"] == float(acc.current_balance())
|
|
|
|
|
|
def test_monthly_net_returns_income_minus_expense_per_month(user, category):
|
|
acc = Account.objects.create(owner=user, name="Cuenta", initial_balance=Decimal("0"))
|
|
|
|
Income.objects.create(owner=user, account=acc, name="Nomina", amount=Decimal("100"), date=date(2024, 4, 5))
|
|
Expense.objects.create(owner=user, account=acc, category=category, amount=Decimal("40"), date=date(2024, 4, 20))
|
|
|
|
data = acc.monthly_net(2024)
|
|
|
|
assert len(data) == 12
|
|
assert data[3]["net"] == 60.0 # month 4
|
|
assert data[0]["net"] == 0.0
|
|
|
|
|
|
def test_balance_until_only_counts_entries_on_or_before_date(user, category):
|
|
acc = Account.objects.create(owner=user, name="Cuenta", initial_balance=Decimal("0"))
|
|
|
|
Expense.objects.create(owner=user, account=acc, category=category, amount=Decimal("20"), date=date(2024, 1, 10))
|
|
Expense.objects.create(owner=user, account=acc, category=category, amount=Decimal("5"), date=date(2024, 1, 15))
|
|
Income.objects.create(owner=user, account=acc, name="Later", amount=Decimal("50"), date=date(2024, 1, 20))
|
|
|
|
assert acc.balance_until(date(2024, 1, 15)) == Decimal("-25")
|
|
|
|
|
|
def test_account_list_shows_all_accounts_with_computed_balances(auth_client, user, category, django_assert_max_num_queries):
|
|
active = Account.objects.create(owner=user, name="Activa", initial_balance=Decimal("100"))
|
|
Account.objects.create(owner=user, name="Inactiva", initial_balance=Decimal("50"), active=False)
|
|
Income.objects.create(owner=user, account=active, name="Nomina", amount=Decimal("20"), date=date.today())
|
|
Expense.objects.create(owner=user, account=active, category=category, amount=Decimal("5"), date=date.today())
|
|
|
|
with django_assert_max_num_queries(10):
|
|
response = auth_client.get(reverse('account_list'))
|
|
|
|
assert response.status_code == 200
|
|
rows = {row["account"].name: row["balance"] for row in response.context["account_rows"]}
|
|
assert rows == {"Activa": Decimal("115"), "Inactiva": Decimal("50")}
|