|
@@ -11,15 +11,19 @@ from django.conf import settings
|
|
|
from django.utils.timezone import localtime, now, make_aware, is_naive
|
|
from django.utils.timezone import localtime, now, make_aware, is_naive
|
|
|
from calendar import monthrange
|
|
from calendar import monthrange
|
|
|
from django.utils import timezone
|
|
from django.utils import timezone
|
|
|
-from django.db.models import Sum
|
|
|
|
|
|
|
+from django.db.models import Q
|
|
|
from collections import defaultdict
|
|
from collections import defaultdict
|
|
|
|
|
+from django.template.loader import get_template
|
|
|
|
|
+from django.http import HttpResponse
|
|
|
|
|
+from weasyprint import HTML
|
|
|
|
|
+import tempfile
|
|
|
|
|
|
|
|
import logging
|
|
import logging
|
|
|
import re
|
|
import re
|
|
|
|
|
|
|
|
from accounts import models
|
|
from accounts import models
|
|
|
from .models import Movimiento, Cuenta, MovimientoFrecuente, Categoria, TransaccionRecurrente
|
|
from .models import Movimiento, Cuenta, MovimientoFrecuente, Categoria, TransaccionRecurrente
|
|
|
-from .forms import MovimientoForm, FiltroForm, CuentaForm, FiltroMesAnioForm, FiltroCuentaForm
|
|
|
|
|
|
|
+from .forms import MovimientoForm, FiltroForm, CuentaForm, FiltroMesAnioForm, FiltroCuentaForm, InformeCuentaForm
|
|
|
from .recurrentes import procesar_transacciones_recurrentes
|
|
from .recurrentes import procesar_transacciones_recurrentes
|
|
|
|
|
|
|
|
|
|
|
|
@@ -452,3 +456,148 @@ def resumen_saldos_mensuales(request):
|
|
|
'meses': meses_ordenados,
|
|
'meses': meses_ordenados,
|
|
|
'saldos_mensuales': saldos_mensuales,
|
|
'saldos_mensuales': saldos_mensuales,
|
|
|
})
|
|
})
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+# Informes
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@login_required
|
|
|
|
|
+def informe_cuenta(request, cuenta_id):
|
|
|
|
|
+ cuenta = get_object_or_404(Cuenta, id=cuenta_id, usuario=request.user)
|
|
|
|
|
+
|
|
|
|
|
+ movimientos = Movimiento.objects.filter(
|
|
|
|
|
+ Q(cuenta_origen=cuenta) | Q(cuenta_destino=cuenta)
|
|
|
|
|
+ ).order_by('fecha')
|
|
|
|
|
+
|
|
|
|
|
+ saldo = 0
|
|
|
|
|
+ historial = []
|
|
|
|
|
+
|
|
|
|
|
+ for mov in movimientos:
|
|
|
|
|
+ if mov.cuenta_origen == cuenta:
|
|
|
|
|
+ saldo -= mov.monto
|
|
|
|
|
+ tipo = 'Salida'
|
|
|
|
|
+ elif mov.cuenta_destino == cuenta:
|
|
|
|
|
+ saldo += mov.monto
|
|
|
|
|
+ tipo = 'Entrada'
|
|
|
|
|
+ else:
|
|
|
|
|
+ tipo = 'Otro' # Por seguridad, aunque no debería ocurrir
|
|
|
|
|
+
|
|
|
|
|
+ historial.append({
|
|
|
|
|
+ 'fecha': mov.fecha,
|
|
|
|
|
+ 'descripcion': mov.descripcion,
|
|
|
|
|
+ 'monto': mov.monto,
|
|
|
|
|
+ 'tipo': tipo,
|
|
|
|
|
+ 'saldo_despues': saldo,
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ context = {
|
|
|
|
|
+ 'cuenta': cuenta,
|
|
|
|
|
+ 'historial': historial,
|
|
|
|
|
+ }
|
|
|
|
|
+ return render(request, 'informe_cuenta.html', context)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@login_required
|
|
|
|
|
+def informe_cuenta_pdf(request, cuenta_id):
|
|
|
|
|
+ cuenta = get_object_or_404(Cuenta, id=cuenta_id, usuario=request.user)
|
|
|
|
|
+
|
|
|
|
|
+ movimientos = Movimiento.objects.filter(
|
|
|
|
|
+ Q(cuenta_origen=cuenta) | Q(cuenta_destino=cuenta)
|
|
|
|
|
+ ).order_by('fecha')
|
|
|
|
|
+
|
|
|
|
|
+ saldo = 0
|
|
|
|
|
+ historial = []
|
|
|
|
|
+
|
|
|
|
|
+ for mov in movimientos:
|
|
|
|
|
+ if mov.cuenta_origen == cuenta:
|
|
|
|
|
+ saldo -= mov.monto
|
|
|
|
|
+ tipo = 'Salida'
|
|
|
|
|
+ elif mov.cuenta_destino == cuenta:
|
|
|
|
|
+ saldo += mov.monto
|
|
|
|
|
+ tipo = 'Entrada'
|
|
|
|
|
+ else:
|
|
|
|
|
+ tipo = 'Otro'
|
|
|
|
|
+
|
|
|
|
|
+ historial.append({
|
|
|
|
|
+ 'fecha': mov.fecha,
|
|
|
|
|
+ 'descripcion': mov.descripcion,
|
|
|
|
|
+ 'monto': mov.monto,
|
|
|
|
|
+ 'tipo': tipo,
|
|
|
|
|
+ 'saldo_despues': saldo,
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ template = get_template('informe_cuenta_pdf.html')
|
|
|
|
|
+ html = template.render({'cuenta': cuenta, 'historial': historial})
|
|
|
|
|
+
|
|
|
|
|
+ with tempfile.NamedTemporaryFile(delete=True) as output:
|
|
|
|
|
+ HTML(string=html).write_pdf(output.name)
|
|
|
|
|
+
|
|
|
|
|
+ output.seek(0)
|
|
|
|
|
+ response = HttpResponse(output.read(), content_type='application/pdf')
|
|
|
|
|
+ response['Content-Disposition'] = 'inline; filename=informe_{cuenta.nombre}.pdf '
|
|
|
|
|
+ return response
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@login_required
|
|
|
|
|
+def informe_cuenta_form(request):
|
|
|
|
|
+ if request.method == 'POST':
|
|
|
|
|
+ cuenta_id = request.GET.get('cuenta_id')
|
|
|
|
|
+
|
|
|
|
|
+ form = InformeCuentaForm(request.GET or None, cuenta_id=cuenta_id)
|
|
|
|
|
+
|
|
|
|
|
+ if form.is_valid():
|
|
|
|
|
+ cuenta = form.cleaned_data['cuenta']
|
|
|
|
|
+ fecha_inicio = form.cleaned_data['fecha_inicio']
|
|
|
|
|
+ fecha_fin = form.cleaned_data['fecha_fin']
|
|
|
|
|
+ return redirect('informe_cuenta_pdf_rango', cuenta.id, fecha_inicio.isoformat(), fecha_fin.isoformat())
|
|
|
|
|
+ else:
|
|
|
|
|
+ form = InformeCuentaForm(user=request.user)
|
|
|
|
|
+
|
|
|
|
|
+ return render(request, 'informe_cuenta_form.html', {'form': form})
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
+@login_required
|
|
|
|
|
+def informe_cuenta_pdf_rango(request, cuenta_id, fecha_inicio, fecha_fin):
|
|
|
|
|
+ cuenta = get_object_or_404(Cuenta, id=cuenta_id, usuario=request.user)
|
|
|
|
|
+ fi = datetime.fromisoformat(fecha_inicio)
|
|
|
|
|
+ ff = datetime.fromisoformat(fecha_fin)
|
|
|
|
|
+
|
|
|
|
|
+ movimientos = Movimiento.objects.filter(
|
|
|
|
|
+ Q(cuenta_origen=cuenta) | Q(cuenta_destino=cuenta),
|
|
|
|
|
+ fecha__date__range=(fi.date(), ff.date())
|
|
|
|
|
+ ).order_by('fecha')
|
|
|
|
|
+
|
|
|
|
|
+ saldo = 0
|
|
|
|
|
+ historial = []
|
|
|
|
|
+
|
|
|
|
|
+ for mov in movimientos:
|
|
|
|
|
+ if mov.cuenta_origen == cuenta:
|
|
|
|
|
+ saldo -= mov.monto
|
|
|
|
|
+ tipo = 'Salida'
|
|
|
|
|
+ elif mov.cuenta_destino == cuenta:
|
|
|
|
|
+ saldo += mov.monto
|
|
|
|
|
+ tipo = 'Entrada'
|
|
|
|
|
+ else:
|
|
|
|
|
+ tipo = 'Otro'
|
|
|
|
|
+
|
|
|
|
|
+ historial.append({
|
|
|
|
|
+ 'fecha': mov.fecha,
|
|
|
|
|
+ 'descripcion': mov.descripcion,
|
|
|
|
|
+ 'monto': mov.monto,
|
|
|
|
|
+ 'tipo': tipo,
|
|
|
|
|
+ 'saldo_despues': saldo,
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ template = get_template('informe_cuenta_pdf.html')
|
|
|
|
|
+ html = template.render({
|
|
|
|
|
+ 'cuenta': cuenta,
|
|
|
|
|
+ 'historial': historial,
|
|
|
|
|
+ 'rango': f"{fi.strftime('%d/%m/%Y')} – {ff.strftime('%d/%m/%Y')}",
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ with tempfile.NamedTemporaryFile(delete=True) as output:
|
|
|
|
|
+ HTML(string=html).write_pdf(output.name)
|
|
|
|
|
+ output.seek(0)
|
|
|
|
|
+ response = HttpResponse(output.read(), content_type='application/pdf')
|
|
|
|
|
+ response['Content-Disposition'] = f'filename=informe_{cuenta.nombre}.pdf'
|
|
|
|
|
+ return response
|