Modificaciones en filtrado y creada opción para marcar que un gasto/ingreso es un traspaso de cuentas #34
242
expenses_manager/expenses/tests/test_transfers.py
Normal file
242
expenses_manager/expenses/tests/test_transfers.py
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
"""Traspasos entre cuentas (is_transfer).
|
||||||
|
|
||||||
|
Un traspaso no es gasto ni ingreso real, pero sí mueve dinero entre cuentas.
|
||||||
|
De ahí los dos bloques de este fichero, que comprueban lo contrario el uno del
|
||||||
|
otro: los análisis excluyen los traspasos, los saldos los incluyen.
|
||||||
|
"""
|
||||||
|
import pytest
|
||||||
|
from datetime import date
|
||||||
|
from decimal import Decimal
|
||||||
|
from django.urls import reverse
|
||||||
|
from expenses.models import Account, Category, Expense, Goal, Income
|
||||||
|
|
||||||
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def savings(user):
|
||||||
|
return Account.objects.create(
|
||||||
|
owner=user, name="Ahorro", initial_balance=Decimal("0"), active=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def make_expense(user, account, category, amount, on, is_transfer=False):
|
||||||
|
return Expense.objects.create(
|
||||||
|
owner=user,
|
||||||
|
account=account,
|
||||||
|
category=category,
|
||||||
|
amount=Decimal(amount),
|
||||||
|
date=on,
|
||||||
|
is_transfer=is_transfer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def make_income(user, account, amount, on, name="Ingreso", is_transfer=False):
|
||||||
|
return Income.objects.create(
|
||||||
|
owner=user,
|
||||||
|
account=account,
|
||||||
|
name=name,
|
||||||
|
amount=Decimal(amount),
|
||||||
|
date=on,
|
||||||
|
is_transfer=is_transfer,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Análisis: los traspasos NO cuentan
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_dashboard_kpi_total_ignores_transfer(auth_client, user, account, category):
|
||||||
|
make_expense(user, account, category, "100", date(2024, 5, 10))
|
||||||
|
make_expense(user, account, category, "400", date(2024, 5, 11), is_transfer=True)
|
||||||
|
|
||||||
|
response = auth_client.get(reverse("dashboard"), {"year": 2024})
|
||||||
|
|
||||||
|
assert response.context["kpi_total"] == Decimal("100")
|
||||||
|
assert response.context["kpi_count"] == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_dashboard_by_category_ignores_transfer(auth_client, user, account, category):
|
||||||
|
other = Category.objects.create(owner=user, name="Traspasos")
|
||||||
|
make_expense(user, account, category, "100", date(2024, 5, 10))
|
||||||
|
make_expense(user, account, other, "400", date(2024, 5, 11), is_transfer=True)
|
||||||
|
|
||||||
|
response = auth_client.get(reverse("dashboard"), {"year": 2024})
|
||||||
|
|
||||||
|
rows = {row["category__name"]: row["total"] for row in response.context["by_category"]}
|
||||||
|
assert rows == {category.name: Decimal("100")}
|
||||||
|
|
||||||
|
|
||||||
|
def test_dashboard_comparison_ignores_transfer(auth_client, user, account, category):
|
||||||
|
make_expense(user, account, category, "30", date(2024, 3, 10))
|
||||||
|
make_expense(user, account, category, "20", date(2024, 2, 15))
|
||||||
|
make_expense(user, account, category, "500", date(2024, 2, 16), is_transfer=True)
|
||||||
|
|
||||||
|
response = auth_client.get(
|
||||||
|
reverse("dashboard"), {"year": 2024, "month": 3, "compare": "1"}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.context["kpi_previous_total"] == Decimal("20")
|
||||||
|
assert response.context["kpi_difference"] == Decimal("10")
|
||||||
|
|
||||||
|
|
||||||
|
def test_home_kpi_total_ignores_transfer(auth_client, user, account, category):
|
||||||
|
today = date.today()
|
||||||
|
make_expense(user, account, category, "100", today)
|
||||||
|
make_expense(user, account, category, "400", today, is_transfer=True)
|
||||||
|
|
||||||
|
response = auth_client.get(reverse("home"))
|
||||||
|
|
||||||
|
assert response.context["kpi_total"] == Decimal("100")
|
||||||
|
assert response.context["kpi_count"] == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_home_previous_month_comparison_ignores_transfer(
|
||||||
|
auth_client, user, account, category
|
||||||
|
):
|
||||||
|
today = date.today()
|
||||||
|
# Día 1 para que reste un mes sin caer en un día inexistente.
|
||||||
|
first = date(today.year, today.month, 1)
|
||||||
|
prev = date(first.year - 1, 12, 1) if first.month == 1 else date(
|
||||||
|
first.year, first.month - 1, 1
|
||||||
|
)
|
||||||
|
|
||||||
|
make_expense(user, account, category, "10", prev)
|
||||||
|
make_expense(user, account, category, "900", prev, is_transfer=True)
|
||||||
|
|
||||||
|
response = auth_client.get(reverse("home"))
|
||||||
|
|
||||||
|
assert response.context["prev_total"] == Decimal("10")
|
||||||
|
|
||||||
|
|
||||||
|
def test_expense_list_total_ignores_transfer_but_row_is_listed(
|
||||||
|
auth_client, user, account, category
|
||||||
|
):
|
||||||
|
make_expense(user, account, category, "100", date(2024, 5, 10))
|
||||||
|
transfer = make_expense(
|
||||||
|
user, account, category, "400", date(2024, 5, 11), is_transfer=True
|
||||||
|
)
|
||||||
|
|
||||||
|
response = auth_client.get(reverse("expense_list"), {"year": 2024})
|
||||||
|
|
||||||
|
assert response.context["kpi_total"] == Decimal("100")
|
||||||
|
assert response.context["kpi_count"] == 1
|
||||||
|
# El traspaso sigue en la tabla aunque no sume en el total.
|
||||||
|
assert transfer in list(response.context["page_obj"])
|
||||||
|
|
||||||
|
|
||||||
|
def test_budget_goal_progress_ignores_transfer(user, account, category):
|
||||||
|
today = date.today()
|
||||||
|
make_expense(user, account, category, "50", today)
|
||||||
|
make_expense(user, account, category, "300", today, is_transfer=True)
|
||||||
|
|
||||||
|
goal = Goal.objects.create(
|
||||||
|
owner=user,
|
||||||
|
name="Presupuesto mensual",
|
||||||
|
target_amount=Decimal("200"),
|
||||||
|
kind=Goal.KIND_BUDGET,
|
||||||
|
category=category,
|
||||||
|
period=Goal.PERIOD_MONTH,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert goal.progress == Decimal("50")
|
||||||
|
assert not goal.is_exceeded()
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Saldos: los traspasos SÍ cuentan
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_transfer_expense_still_lowers_source_balance(user, account, category):
|
||||||
|
account.initial_balance = Decimal("1000")
|
||||||
|
account.save()
|
||||||
|
make_expense(user, account, category, "400", date(2024, 5, 11), is_transfer=True)
|
||||||
|
|
||||||
|
assert account.current_balance() == Decimal("600")
|
||||||
|
|
||||||
|
|
||||||
|
def test_transfer_income_still_raises_target_balance(user, savings):
|
||||||
|
make_income(user, savings, "400", date(2024, 5, 11), is_transfer=True)
|
||||||
|
|
||||||
|
assert savings.current_balance() == Decimal("400")
|
||||||
|
|
||||||
|
|
||||||
|
def test_transfer_keeps_total_balance_unchanged(auth_client, user, account, category, savings):
|
||||||
|
account.initial_balance = Decimal("1000")
|
||||||
|
account.save()
|
||||||
|
make_expense(user, account, category, "400", date(2024, 5, 11), is_transfer=True)
|
||||||
|
make_income(user, savings, "400", date(2024, 5, 11), is_transfer=True)
|
||||||
|
|
||||||
|
response = auth_client.get(reverse("home"))
|
||||||
|
|
||||||
|
# El dinero cambia de sitio, el patrimonio no se mueve.
|
||||||
|
assert response.context["total_balance"] == Decimal("1000")
|
||||||
|
|
||||||
|
|
||||||
|
def test_saving_goal_progress_counts_the_transfer(user, savings):
|
||||||
|
"""El punto más delicado: un traspaso a la cuenta de ahorro ES el progreso."""
|
||||||
|
make_income(user, savings, "400", date(2024, 5, 11), is_transfer=True)
|
||||||
|
|
||||||
|
goal = Goal.objects.create(
|
||||||
|
owner=user,
|
||||||
|
name="Fondo de emergencia",
|
||||||
|
target_amount=Decimal("1000"),
|
||||||
|
kind=Goal.KIND_SAVING,
|
||||||
|
account=savings,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert goal.progress == Decimal("400")
|
||||||
|
|
||||||
|
|
||||||
|
def test_account_list_balance_counts_the_transfer(auth_client, user, account, category):
|
||||||
|
account.initial_balance = Decimal("1000")
|
||||||
|
account.save()
|
||||||
|
make_expense(user, account, category, "400", date(2024, 5, 11), is_transfer=True)
|
||||||
|
|
||||||
|
response = auth_client.get(reverse("account_list"))
|
||||||
|
|
||||||
|
rows = {row["account"].id: row["balance"] for row in response.context["account_rows"]}
|
||||||
|
assert rows[account.id] == Decimal("600")
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
# Formularios
|
||||||
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def test_expense_form_can_mark_a_transfer(auth_client, user, account, category):
|
||||||
|
response = auth_client.post(
|
||||||
|
reverse("expense_create"),
|
||||||
|
{
|
||||||
|
"date": "2024-05-11",
|
||||||
|
"amount": "400",
|
||||||
|
"description": "",
|
||||||
|
"category": category.id,
|
||||||
|
"account": account.id,
|
||||||
|
"is_transfer": "on",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert Expense.objects.get(owner=user).is_transfer is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_income_form_can_mark_a_transfer(auth_client, user, account):
|
||||||
|
response = auth_client.post(
|
||||||
|
reverse("income_create"),
|
||||||
|
{
|
||||||
|
"account": account.id,
|
||||||
|
"name": "Traspaso desde nómina",
|
||||||
|
"amount": "400",
|
||||||
|
"date": "2024-05-11",
|
||||||
|
"is_transfer": "on",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 302
|
||||||
|
assert Income.objects.get(owner=user).is_transfer is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_expense_defaults_to_not_a_transfer(user, account, category):
|
||||||
|
expense = make_expense(user, account, category, "10", date(2024, 5, 11))
|
||||||
|
|
||||||
|
assert expense.is_transfer is False
|
||||||
Loading…
Reference in New Issue
Block a user