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