Compare commits
No commits in common. "b4508a13db8531e78aa4d20cf9a1670b07266c7e" and "e5aa66369a92e1ed0fb0058aa1fabb2aa3178a33" have entirely different histories.
b4508a13db
...
e5aa66369a
@ -97,31 +97,6 @@ a.danger:visited,
|
|||||||
color: var(--color-danger-accent);
|
color: var(--color-danger-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Escala tipográfica. Los márgenes van en rem a propósito: los del navegador
|
|
||||||
van en em y escalan con el tamaño de fuente, dejando huecos irregulares
|
|
||||||
entre secciones. */
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
font-size: 1.625rem;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.2;
|
|
||||||
margin: 0 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.25;
|
|
||||||
margin: 1.5rem 0 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
line-height: 1.3;
|
|
||||||
margin: 1rem 0 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ========================= */
|
/* ========================= */
|
||||||
/* Topbar / navigation */
|
/* Topbar / navigation */
|
||||||
/* ========================= */
|
/* ========================= */
|
||||||
@ -298,7 +273,7 @@ h3 {
|
|||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-context h1 {
|
.dashboard-context h2 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -923,7 +898,7 @@ tr:hover {
|
|||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-card h2 {
|
.settings-card h3 {
|
||||||
margin: 0 0 0.25rem 0;
|
margin: 0 0 0.25rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Editar categoría</h1>
|
<h2>Editar categoría</h2>
|
||||||
|
|
||||||
<form method="post" class="app-form">
|
<form method="post" class="app-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<h1>Mis categorías</h1>
|
<h1>Mis categorías</h1>
|
||||||
|
|
||||||
<h2>Nueva categoría</h2>
|
<h3>Nueva categoría</h3>
|
||||||
<form method="post" class="app-form">
|
<form method="post" class="app-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% include "expenses/_form_fields.html" %}
|
{% include "expenses/_form_fields.html" %}
|
||||||
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<h2>Listado</h2>
|
<h3>Listado</h3>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
|
|||||||
@ -7,13 +7,13 @@
|
|||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<section class="dashboard-context">
|
<section class="dashboard-context">
|
||||||
<h1>
|
<h2>
|
||||||
{% if selected_account_obj %}
|
{% if selected_account_obj %}
|
||||||
{{ selected_account_obj.name }}
|
{{ selected_account_obj.name }}
|
||||||
{% else %}
|
{% else %}
|
||||||
Todas las cuentas
|
Todas las cuentas
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</h1>
|
</h2>
|
||||||
|
|
||||||
<p class="muted">
|
<p class="muted">
|
||||||
{% if selected_month %}
|
{% if selected_month %}
|
||||||
@ -95,6 +95,34 @@
|
|||||||
<button type="submit" class="btn btn-primary">Aplicar Filtros</button>
|
<button type="submit" class="btn btn-primary">Aplicar Filtros</button>
|
||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
<script>
|
||||||
|
const yearSelect = document.getElementById('yearSelect');
|
||||||
|
const monthSelect = document.getElementById('monthSelect');
|
||||||
|
const hiddenPeriod = document.getElementById('hiddenPeriod');
|
||||||
|
|
||||||
|
function clearPeriodPreset() {
|
||||||
|
if (hiddenPeriod) {
|
||||||
|
hiddenPeriod.value = ""
|
||||||
|
}
|
||||||
|
document.querySelectorAll('.preset').forEach(btn => btn.classList.remove('active'));
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
<!-- <script>
|
||||||
|
document.querySelectorAll('.dashboard-presets a').forEach(link => {
|
||||||
|
link.addEventListener('click', e => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const period = link.dataset.period;
|
||||||
|
const isActive = link.classList.contains('active');
|
||||||
|
|
||||||
|
if (isActive) {
|
||||||
|
window.location.href = link.href;
|
||||||
|
} else {
|
||||||
|
window.location.href = `${link.href}?period=${period}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script> -->
|
||||||
|
|
||||||
<!-- ========================= -->
|
<!-- ========================= -->
|
||||||
<!-- KPIs -->
|
<!-- KPIs -->
|
||||||
@ -137,7 +165,7 @@
|
|||||||
|
|
||||||
{% if compare_enabled %}
|
{% if compare_enabled %}
|
||||||
<section class="comparison">
|
<section class="comparison">
|
||||||
<h2>Resumen comparativo</h2>
|
<h3>Resumen comparativo</h3>
|
||||||
<p>
|
<p>
|
||||||
Gastos periodo actual: <strong>{{ kpi_total|floatformat:2 }} €</strong><br>
|
Gastos periodo actual: <strong>{{ kpi_total|floatformat:2 }} €</strong><br>
|
||||||
Gastos periodo anterior: <strong>{{ kpi_previous_total|floatformat:2 }} €</strong><br>
|
Gastos periodo anterior: <strong>{{ kpi_previous_total|floatformat:2 }} €</strong><br>
|
||||||
@ -150,7 +178,7 @@
|
|||||||
</small>
|
</small>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Desglose de cambios por categoría</h2>
|
<h3>Desglose de cambios por categoría</h3>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
@ -184,21 +212,21 @@
|
|||||||
|
|
||||||
<div class="charts-container">
|
<div class="charts-container">
|
||||||
<section>
|
<section>
|
||||||
<h2>Evolución de Gastos ({% if chart_type == 'day' %}Por día {% else %} Por Meses{% endif %})</h2>
|
<h3>Evolución de Gastos ({% if chart_type == 'day' %}Por día {% else %} Por Meses{% endif %})</h3>
|
||||||
<div class="chart-box">
|
<div class="chart-box">
|
||||||
<canvas id="mainChart"></canvas>
|
<canvas id="mainChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>Distribución por Categorías</h2>
|
<h3>Distribución por Categorías</h3>
|
||||||
<div class="chart-box">
|
<div class="chart-box">
|
||||||
<canvas id="categoryChart"></canvas>
|
<canvas id="categoryChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>Gastos Recientes</h2>
|
<h3>Gastos Recientes</h3>
|
||||||
<div class="table-wrap">
|
<div class="table-wrap">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
@ -262,11 +290,11 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<h2>Evolución anual por cuenta ({{ selected_year }})</h2>
|
<h3>Evolución anual por cuenta ({{ selected_year }})</h3>
|
||||||
<div class="dashboard-grid">
|
<div class="dashboard-grid">
|
||||||
{% for acc in accounts_charts %}
|
{% for acc in accounts_charts %}
|
||||||
<div class="card card-chart">
|
<div class="card card-chart">
|
||||||
<h3>{{ acc.name }}</h3>
|
<h4>{{ acc.name }}</h4>
|
||||||
<p><strong>Saldo actual:</strong> {{ acc.current_balance|floatformat:2 }}€</p>
|
<p><strong>Saldo actual:</strong> {{ acc.current_balance|floatformat:2 }}€</p>
|
||||||
<div class="canvas-wrapper">
|
<div class="canvas-wrapper">
|
||||||
<canvas id="accountChart{{ acc.id }}"></canvas>
|
<canvas id="accountChart{{ acc.id }}"></canvas>
|
||||||
@ -365,11 +393,28 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
{% if compare_enabled %}
|
||||||
|
<section class="comparison">
|
||||||
|
<h3>Comparativa</h3>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Diferencia:
|
||||||
|
<strong class="{% if kpi_trend == 'up' %}positive{% endif %}">
|
||||||
|
{% if kpi_trend == "up" %}+{% endif %}
|
||||||
|
{{ kpi_difference_abs|floatformat:2 }} €
|
||||||
|
</strong>
|
||||||
|
{% if kpi_percentage %}
|
||||||
|
({{ kpi_percentage|floatformat:1 }}%)
|
||||||
|
{% endif %}
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
<!-- ========================= -->
|
<!-- ========================= -->
|
||||||
<!-- Goals -->
|
<!-- Goals -->
|
||||||
<!-- ========================= -->
|
<!-- ========================= -->
|
||||||
|
|
||||||
<h2>Objetivos</h2>
|
<h3>Objetivos</h3>
|
||||||
{% if goals %}
|
{% if goals %}
|
||||||
<div class="goals-widget">
|
<div class="goals-widget">
|
||||||
{% for goal in goals %}
|
{% for goal in goals %}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<h1>Iniciar sesión</h1>
|
<h2>Iniciar sesión</h2>
|
||||||
|
|
||||||
<form method="post" class="app-form">
|
<form method="post" class="app-form">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
|
|
||||||
<h1>Recuperar contraseña</h1>
|
<h2>Recuperar contraseña</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Esta aplicación actualmente no envía correos de recuperación automáticos.
|
Esta aplicación actualmente no envía correos de recuperación automáticos.
|
||||||
|
|||||||
@ -5,23 +5,23 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>Configuración</h1>
|
<h2>Configuración</h2>
|
||||||
|
|
||||||
<p>Gestiona las categorías, etiquetas y objetivos de tu cuenta</p>
|
<p>Gestiona las categorías, etiquetas y objetivos de tu cuenta</p>
|
||||||
|
|
||||||
<div class="settings-grid">
|
<div class="settings-grid">
|
||||||
<a class="settings-card" href="{% url 'category_list' %}">
|
<a class="settings-card" href="{% url 'category_list' %}">
|
||||||
<h2>Categorías</h2>
|
<h3>Categorías</h3>
|
||||||
<p>Organiza tus gastos por tipo.</p>
|
<p>Organiza tus gastos por tipo.</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a class="settings-card" href="{% url 'tag_list' %}">
|
<a class="settings-card" href="{% url 'tag_list' %}">
|
||||||
<h2>Etiquetas</h2>
|
<h3>Etiquetas</h3>
|
||||||
<p>Añade etiquetas libres a tus gastos.</p>
|
<p>Añade etiquetas libres a tus gastos.</p>
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<a class="settings-card" href="{% url 'goal_list' %}">
|
<a class="settings-card" href="{% url 'goal_list' %}">
|
||||||
<h2>Objetivos</h2>
|
<h3>Objetivos</h3>
|
||||||
<p>Define y controla tus metas de gasto.</p>
|
<p>Define y controla tus metas de gasto.</p>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import pytest
|
|||||||
from datetime import date
|
from datetime import date
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from expenses.models import Account, Expense, Category, Income
|
from expenses.models import Expense, Category
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
@ -80,146 +80,3 @@ def test_dashboard_filters_by_year(auth_client, user, account, category):
|
|||||||
assert len(chart_data) == 12
|
assert len(chart_data) == 12
|
||||||
assert chart_data[4] == 20.0 # May = month 5 -> index 4
|
assert chart_data[4] == 20.0 # May = month 5 -> index 4
|
||||||
assert sum(chart_data) == 20.0 # only the 2024 expense contributes
|
assert sum(chart_data) == 20.0 # only the 2024 expense contributes
|
||||||
|
|
||||||
|
|
||||||
def test_dashboard_preset_this_month(auth_client):
|
|
||||||
today = date.today()
|
|
||||||
|
|
||||||
response = auth_client.get(reverse('dashboard'), {'period': 'this_month'})
|
|
||||||
|
|
||||||
assert response.context['selected_year'] == today.year
|
|
||||||
assert response.context['selected_month'] == today.month
|
|
||||||
|
|
||||||
|
|
||||||
def test_dashboard_preset_last_month(auth_client):
|
|
||||||
today = date.today()
|
|
||||||
expected_month = today.month - 1 or 12
|
|
||||||
expected_year = today.year if today.month > 1 else today.year - 1
|
|
||||||
|
|
||||||
response = auth_client.get(reverse('dashboard'), {'period': 'last_month'})
|
|
||||||
|
|
||||||
assert response.context['selected_year'] == expected_year
|
|
||||||
assert response.context['selected_month'] == expected_month
|
|
||||||
|
|
||||||
|
|
||||||
def test_dashboard_preset_this_year_has_no_month(auth_client):
|
|
||||||
today = date.today()
|
|
||||||
|
|
||||||
response = auth_client.get(reverse('dashboard'), {'period': 'this_year'})
|
|
||||||
|
|
||||||
assert response.context['selected_year'] == today.year
|
|
||||||
assert response.context['selected_month'] is None
|
|
||||||
|
|
||||||
|
|
||||||
def test_dashboard_filters_by_account(auth_client, user, account, category):
|
|
||||||
other = Account.objects.create(
|
|
||||||
owner=user, name="Otra", initial_balance=Decimal("100"), active=True
|
|
||||||
)
|
|
||||||
Expense.objects.create(
|
|
||||||
owner=user, account=account, category=category,
|
|
||||||
amount=Decimal("10"), date=date(2024, 3, 1),
|
|
||||||
)
|
|
||||||
Expense.objects.create(
|
|
||||||
owner=user, account=other, category=category,
|
|
||||||
amount=Decimal("25"), date=date(2024, 3, 1),
|
|
||||||
)
|
|
||||||
|
|
||||||
response = auth_client.get(
|
|
||||||
reverse('dashboard'),
|
|
||||||
{'year': 2024, 'account': other.id},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.context['kpi_total'] == Decimal('25')
|
|
||||||
assert response.context['kpi_count'] == 1
|
|
||||||
assert response.context['kpi_balance'] == Decimal('75') # 100 - 25
|
|
||||||
|
|
||||||
|
|
||||||
def test_dashboard_without_compare_keeps_comparison_defaults(auth_client):
|
|
||||||
response = auth_client.get(reverse('dashboard'), {'year': 2024})
|
|
||||||
|
|
||||||
assert response.context['compare_enabled'] is False
|
|
||||||
assert response.context['kpi_previous_total'] == 0
|
|
||||||
assert response.context['kpi_difference'] == 0
|
|
||||||
assert response.context['kpi_percentage'] == 0
|
|
||||||
assert response.context['kpi_trend'] == 'equal'
|
|
||||||
assert response.context['category_comparison'] == []
|
|
||||||
|
|
||||||
|
|
||||||
def test_dashboard_compare_fills_difference_and_trend(auth_client, user, account, category):
|
|
||||||
Expense.objects.create(
|
|
||||||
owner=user, account=account, category=category,
|
|
||||||
amount=Decimal("20"), date=date(2024, 2, 15),
|
|
||||||
)
|
|
||||||
Expense.objects.create(
|
|
||||||
owner=user, account=account, category=category,
|
|
||||||
amount=Decimal("30"), date=date(2024, 3, 10),
|
|
||||||
)
|
|
||||||
|
|
||||||
response = auth_client.get(
|
|
||||||
reverse('dashboard'),
|
|
||||||
{'year': 2024, 'month': 3, 'compare': '1'},
|
|
||||||
)
|
|
||||||
|
|
||||||
assert response.context['compare_enabled'] is True
|
|
||||||
assert response.context['kpi_previous_total'] == Decimal('20')
|
|
||||||
assert response.context['kpi_difference'] == Decimal('10')
|
|
||||||
assert response.context['kpi_trend'] == 'up'
|
|
||||||
assert float(response.context['kpi_percentage']) == 50.0
|
|
||||||
|
|
||||||
rows = {row['category']: row for row in response.context['category_comparison']}
|
|
||||||
assert rows[category.name]['current'] == 30.0
|
|
||||||
assert rows[category.name]['previous'] == 20.0
|
|
||||||
assert rows[category.name]['difference'] == 10.0
|
|
||||||
|
|
||||||
|
|
||||||
def test_dashboard_chart_type_is_day_when_a_month_is_selected(auth_client):
|
|
||||||
response = auth_client.get(reverse('dashboard'), {'year': 2024, 'month': 3})
|
|
||||||
|
|
||||||
assert response.context['chart_type'] == 'day'
|
|
||||||
|
|
||||||
|
|
||||||
def test_dashboard_chart_type_is_month_for_the_whole_year(auth_client):
|
|
||||||
response = auth_client.get(reverse('dashboard'), {'year': 2024})
|
|
||||||
|
|
||||||
assert response.context['chart_type'] == 'month'
|
|
||||||
|
|
||||||
|
|
||||||
def test_dashboard_category_chart_keeps_only_top_ten(auth_client, user, account):
|
|
||||||
for i in range(12):
|
|
||||||
cat = Category.objects.create(name=f"Cat {i:02d}", owner=user)
|
|
||||||
Expense.objects.create(
|
|
||||||
owner=user, account=account, category=cat,
|
|
||||||
amount=Decimal(str(i + 1)), date=date(2024, 6, 1),
|
|
||||||
)
|
|
||||||
|
|
||||||
response = auth_client.get(reverse('dashboard'), {'year': 2024})
|
|
||||||
|
|
||||||
assert len(response.context['by_category_chart']) == 10
|
|
||||||
assert len(list(response.context['by_category'])) == 12
|
|
||||||
|
|
||||||
|
|
||||||
def test_dashboard_kpi_balance_matches_sum_of_current_balances(auth_client, user, account, category):
|
|
||||||
savings = Account.objects.create(
|
|
||||||
owner=user, name="Ahorro", initial_balance=Decimal("500"), active=True
|
|
||||||
)
|
|
||||||
Account.objects.create(
|
|
||||||
owner=user, name="Cerrada", initial_balance=Decimal("999"), active=False
|
|
||||||
)
|
|
||||||
|
|
||||||
Expense.objects.create(
|
|
||||||
owner=user, account=account, category=category,
|
|
||||||
amount=Decimal("40"), date=date(2024, 4, 1),
|
|
||||||
)
|
|
||||||
Income.objects.create(
|
|
||||||
owner=user, account=savings, name="Nomina",
|
|
||||||
amount=Decimal("60"), date=date(2024, 4, 2),
|
|
||||||
)
|
|
||||||
|
|
||||||
response = auth_client.get(reverse('dashboard'), {'year': 2024})
|
|
||||||
|
|
||||||
expected = sum(
|
|
||||||
acc.current_balance()
|
|
||||||
for acc in Account.objects.filter(owner=user, active=True)
|
|
||||||
)
|
|
||||||
assert expected == Decimal('520') # (0 - 40) + (500 + 60), sin la inactiva
|
|
||||||
assert response.context['kpi_balance'] == expected
|
|
||||||
|
|||||||
@ -112,67 +112,6 @@ def _account_balances(accounts):
|
|||||||
return account_balances, negative_accounts, total_balance
|
return account_balances, negative_accounts, total_balance
|
||||||
|
|
||||||
|
|
||||||
def _resolve_period(request, today):
|
|
||||||
"""Traduce los parámetros de la URL en el periodo mirado por el dashboard.
|
|
||||||
|
|
||||||
Devuelve (period, selected_year, selected_month), donde selected_month es
|
|
||||||
None cuando se está mirando el año completo."""
|
|
||||||
period = request.GET.get("period", "")
|
|
||||||
|
|
||||||
if period == "this_month":
|
|
||||||
return period, today.year, today.month
|
|
||||||
|
|
||||||
if period == "last_month":
|
|
||||||
selected_year, selected_month = sub_months(today.year, today.month, 1)
|
|
||||||
return period, selected_year, selected_month
|
|
||||||
|
|
||||||
if period == "this_year":
|
|
||||||
return period, today.year, None
|
|
||||||
|
|
||||||
selected_year = _get_int(request.GET.get("year")) or today.year
|
|
||||||
selected_month = _get_int(request.GET.get("month")) or None
|
|
||||||
return period, selected_year, selected_month
|
|
||||||
|
|
||||||
|
|
||||||
def _build_account_charts(accounts, balance_by_account, selected_year, today):
|
|
||||||
"""Serie mensual de saldo de cada cuenta para las gráficas del dashboard.
|
|
||||||
|
|
||||||
En el año en curso la serie se corta en el mes actual y ese último punto
|
|
||||||
se sustituye por el saldo real de la cuenta, que llega ya calculado en
|
|
||||||
balance_by_account para no repetir current_balance() por cuenta."""
|
|
||||||
accounts_charts = []
|
|
||||||
|
|
||||||
for acc in accounts:
|
|
||||||
try:
|
|
||||||
monthly_data = acc.monthly_balance(selected_year)
|
|
||||||
m_balance = [float(row["balance"]) for row in monthly_data]
|
|
||||||
except Exception:
|
|
||||||
logger.exception(
|
|
||||||
"Error calculando monthly_balance para la cuenta %s (año %s)",
|
|
||||||
acc.id, selected_year
|
|
||||||
)
|
|
||||||
m_balance = [0] * 12
|
|
||||||
|
|
||||||
current_balance = balance_by_account[acc.id]
|
|
||||||
|
|
||||||
if selected_year == today.year:
|
|
||||||
current_month_index = today.month - 1
|
|
||||||
|
|
||||||
if current_month_index < len(m_balance):
|
|
||||||
m_balance[current_month_index] = float(current_balance)
|
|
||||||
|
|
||||||
m_balance = m_balance[:today.month]
|
|
||||||
|
|
||||||
accounts_charts.append({
|
|
||||||
"id": acc.id,
|
|
||||||
"name": acc.name,
|
|
||||||
"data": m_balance,
|
|
||||||
"current_balance": current_balance,
|
|
||||||
})
|
|
||||||
|
|
||||||
return accounts_charts
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def home(request):
|
def home(request):
|
||||||
today = date.today()
|
today = date.today()
|
||||||
@ -468,26 +407,33 @@ def expense_delete(request, pk):
|
|||||||
def dashboard(request):
|
def dashboard(request):
|
||||||
today = date.today()
|
today = date.today()
|
||||||
|
|
||||||
|
period = request.GET.get("period", "")
|
||||||
account_id = _get_int(request.GET.get("account"))
|
account_id = _get_int(request.GET.get("account"))
|
||||||
compare_enabled = request.GET.get("compare") == "1"
|
compare_enabled = request.GET.get("compare") == "1"
|
||||||
|
|
||||||
# Time presets
|
# Time presets
|
||||||
period, selected_year, selected_month = _resolve_period(request, today)
|
if period == "this_month":
|
||||||
|
selected_year, selected_month = today.year, today.month
|
||||||
|
elif period == "last_month":
|
||||||
|
selected_year, selected_month = sub_months(today.year, today.month, 1)
|
||||||
|
elif period == "this_year":
|
||||||
|
selected_year, selected_month = today.year, None
|
||||||
|
else:
|
||||||
|
selected_year = _get_int(request.GET.get("year")) or _get_int(today.year)
|
||||||
|
selected_month = _get_int(request.GET.get("month"))
|
||||||
|
selected_month = _get_int(selected_month) if selected_month else None
|
||||||
|
|
||||||
# Accounts
|
# Accounts
|
||||||
accounts = list(Account.objects.filter(owner=request.user, active=True))
|
accounts = Account.objects.filter(owner=request.user, active=True)
|
||||||
selected_account_obj = None
|
selected_account_obj = None
|
||||||
if account_id:
|
if account_id:
|
||||||
selected_account_obj = next((a for a in accounts if a.id == account_id), None)
|
selected_account_obj = accounts.filter(id=account_id).first()
|
||||||
|
|
||||||
# Calculate the KPI
|
# Calculate the KPI
|
||||||
account_balances, _, total_balance = _account_balances(accounts)
|
|
||||||
balance_by_account = {row["account"].id: row["balance"] for row in account_balances}
|
|
||||||
|
|
||||||
if selected_account_obj:
|
if selected_account_obj:
|
||||||
kpi_balance = balance_by_account[selected_account_obj.id]
|
kpi_balance = selected_account_obj.current_balance()
|
||||||
else:
|
else:
|
||||||
kpi_balance = total_balance
|
kpi_balance = sum(account.current_balance() for account in accounts)
|
||||||
|
|
||||||
# Filter by base expenses
|
# Filter by base expenses
|
||||||
expenses = Expense.objects.filter(owner=request.user)
|
expenses = Expense.objects.filter(owner=request.user)
|
||||||
@ -615,9 +561,32 @@ def dashboard(request):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Anual evolution by accounts
|
# Anual evolution by accounts
|
||||||
accounts_charts = _build_account_charts(
|
accounts_charts = []
|
||||||
accounts, balance_by_account, selected_year, today
|
for acc in accounts:
|
||||||
)
|
try:
|
||||||
|
monthly_data = acc.monthly_balance(selected_year)
|
||||||
|
m_balance = [float(row["balance"]) for row in monthly_data]
|
||||||
|
except Exception:
|
||||||
|
logger.exception(
|
||||||
|
"Error calculando monthly_balance para la cuenta %s (año %s)",
|
||||||
|
acc.id, selected_year
|
||||||
|
)
|
||||||
|
m_balance = [0] * 12
|
||||||
|
|
||||||
|
if selected_year == today.year:
|
||||||
|
current_month_index = today.month - 1
|
||||||
|
|
||||||
|
if current_month_index < len(m_balance):
|
||||||
|
m_balance[current_month_index] = float(acc.current_balance())
|
||||||
|
|
||||||
|
m_balance = m_balance[:today.month]
|
||||||
|
|
||||||
|
accounts_charts.append({
|
||||||
|
"id": acc.id,
|
||||||
|
"name": acc.name,
|
||||||
|
"data": m_balance,
|
||||||
|
"current_balance": acc.current_balance(),
|
||||||
|
})
|
||||||
|
|
||||||
# Goals
|
# Goals
|
||||||
goals = Goal.objects.filter(owner=request.user)
|
goals = Goal.objects.filter(owner=request.user)
|
||||||
@ -636,7 +605,6 @@ def dashboard(request):
|
|||||||
"by_category_chart": by_category_chart,
|
"by_category_chart": by_category_chart,
|
||||||
"chart_labels": chart_labels,
|
"chart_labels": chart_labels,
|
||||||
"chart_data": chart_totals,
|
"chart_data": chart_totals,
|
||||||
"chart_type": chart_type,
|
|
||||||
"year_list": year_list,
|
"year_list": year_list,
|
||||||
"months": list(range(1, 13)),
|
"months": list(range(1, 13)),
|
||||||
"selected_year": selected_year,
|
"selected_year": selected_year,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user