77 lines
3.6 KiB
HTML
77 lines
3.6 KiB
HTML
{% extends "expenses/base.html" %}
|
||
|
||
{% block title %}Reparto por bloques{% endblock %}
|
||
|
||
{% block content %}
|
||
<h1>Reparto por bloques</h1>
|
||
|
||
<p>Qué porcentaje de tus ingresos debería ir a cada bloque. Los bloques de gasto son los tipos que asignas a tus categorías; el ahorro son los traspasos a tus cuentas de ahorro.</p>
|
||
|
||
{# Arranca con el reparto guardado; el script de abajo lo recalcula al teclear. #}
|
||
<p id="budgetTotals" aria-live="polite">Asignado: {{ assigned|floatformat:"-2" }} %. Sin asignar: {{ unassigned|floatformat:"-2" }} %.</p>
|
||
|
||
{% if proposal %}
|
||
<p class="muted">
|
||
Junto a cada bloque tienes lo que fue de verdad del {{ proposal.date_from|date:"d/m/Y" }} al {{ proposal.date_to|date:"d/m/Y" }}, sobre tus ingresos de ese periodo.
|
||
{% if proposal.unclassified %}El {{ proposal.unclassified }} % se fue en gastos sin tipo, que no cuentan en ningún bloque.{% endif %}
|
||
</p>
|
||
{% else %}
|
||
<p class="muted">No hay ingresos en los últimos 12 meses completos, así que no se puede calcular una propuesta.</p>
|
||
{% endif %}
|
||
|
||
<form method="post" action="{% url 'budget_blocks' %}" class="app-form" id="budgetForm">
|
||
{% csrf_token %}
|
||
{% include "expenses/_form_fields.html" %}
|
||
|
||
<div class="form-actions">
|
||
<button type="submit" class="btn btn-primary">Guardar</button>
|
||
{% if proposal %}
|
||
<a class="btn btn-secondary" href="{% url 'budget_blocks' %}?propuesta=1">Usar valores calculados</a>
|
||
{% endif %}
|
||
<a class="btn btn-secondary" href="{% url 'settings_index' %}">Volver</a>
|
||
</div>
|
||
{% if proposal_total > 100 %}
|
||
<p class="form-errors">Los valores calculados suman un {{ proposal_total }} %: en ese periodo gastaste más de lo que ingresaste. Puedes cargarlos, pero tendrás que bajar algún bloque hasta que la suma no pase de 100 para poder guardar.</p>
|
||
{% endif %}
|
||
</form>
|
||
|
||
<script>
|
||
// Suma en vivo de los cinco bloques. Es solo comodidad: quien decide si
|
||
// se puede guardar es la validación del servidor.
|
||
(function () {
|
||
const form = document.getElementById('budgetForm');
|
||
const totals = document.getElementById('budgetTotals');
|
||
const names = [{% for field in form %}"{{ field.html_name }}",{% endfor %}];
|
||
|
||
function format(cents) {
|
||
return (cents / 100).toLocaleString('es-ES', { maximumFractionDigits: 2 });
|
||
}
|
||
|
||
function update() {
|
||
// En céntimos enteros y redondeados: sin el redondeo,
|
||
// 18,1 × 100 + 81,9 × 100 da 10000,000000000002 y avisaría
|
||
// de que pasa de 100.
|
||
let cents = 0;
|
||
names.forEach(function (name) {
|
||
const value = parseFloat(form.elements[name].value);
|
||
if (!isNaN(value)) {
|
||
cents += Math.round(value * 100);
|
||
}
|
||
});
|
||
|
||
if (cents > 10000) {
|
||
totals.textContent = 'Asignado: ' + format(cents) + ' %. Pasa de 100 por '
|
||
+ format(cents - 10000) + ' %: así no se podrá guardar.';
|
||
totals.className = 'form-errors';
|
||
} else {
|
||
totals.textContent = 'Asignado: ' + format(cents) + ' %. Sin asignar: '
|
||
+ format(10000 - cents) + ' %.';
|
||
totals.className = '';
|
||
}
|
||
}
|
||
|
||
form.addEventListener('input', update);
|
||
})();
|
||
</script>
|
||
{% endblock %}
|