# views/dashboard.py # Vision 2026 – Phase 1: Dashboard Cockpit import csv import io import json import os import time from datetime import datetime, timedelta, date from decimal import Decimal import qrcode import qrcode.image.svg import requests from django.conf import settings from django.contrib import messages from django.contrib.auth.decorators import login_required from django.core.paginator import Paginator from django.db.models import (Avg, Count, DecimalField, F, IntegerField, Q, Sum, Value) from django.db.models.functions import Cast, Coalesce, NullIf, Replace from django.http import HttpResponse, JsonResponse from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.utils import timezone from django.views.decorators.csrf import csrf_exempt from django_otp.decorators import otp_required from django_otp.plugins.otp_totp.models import TOTPDevice from django_otp.plugins.otp_static.models import StaticDevice, StaticToken from django_otp.util import random_hex from rest_framework.decorators import api_view from rest_framework.response import Response from stiftung.models import (AppConfiguration, AuditLog, BackupJob, BankTransaction, BriefVorlage, CSVImport, Destinataer, DestinataerEmailEingang, DestinataerNotiz, DestinataerUnterstuetzung, DokumentLink, Foerderung, GeschichteBild, GeschichteSeite, Land, LandAbrechnung, LandVerpachtung, Paechter, Person, Rentmeister, StiftungsKalenderEintrag, StiftungsKonto, UnterstuetzungWiederkehrend, Veranstaltung, Veranstaltungsteilnehmer, Verwaltungskosten, VierteljahresNachweis) from stiftung.forms import ( DestinataerForm, DestinataerUnterstuetzungForm, DestinataerNotizForm, FoerderungForm, GeschichteBildForm, GeschichteSeiteForm, LandForm, LandVerpachtungForm, LandAbrechnungForm, PaechterForm, DokumentLinkForm, RentmeisterForm, StiftungsKontoForm, VerwaltungskostenForm, BankTransactionForm, BankImportForm, UnterstuetzungForm, UnterstuetzungWiederkehrendForm, UnterstuetzungMarkAsPaidForm, VierteljahresNachweisForm, UserCreationForm, UserUpdateForm, PasswordChangeForm, UserPermissionForm, TwoFactorSetupForm, TwoFactorVerifyForm, TwoFactorDisableForm, BackupTokenRegenerateForm, PersonForm, VeranstaltungForm, VeranstaltungsteilnehmerForm, ) @login_required def home(request): """Vision 2026 Dashboard Cockpit""" from stiftung.services.calendar_service import StiftungsKalenderService today = timezone.now().date() current_quarter = (today.month - 1) // 3 + 1 current_year = today.year # ── Calendar events ── calendar_service = StiftungsKalenderService() end_date = today + timedelta(days=14) all_events = calendar_service.get_all_events(today, end_date) upcoming_events = [e for e in all_events if not getattr(e, 'overdue', False)] overdue_events = [e for e in all_events if getattr(e, 'overdue', False)] # ── Stats ── destinataer_count = Destinataer.objects.count() paechter_count = Paechter.objects.count() land_count = Land.objects.count() # Active Foerderungen (not rejected/cancelled, current or future year) foerderung_active = Foerderung.objects.filter( jahr__gte=current_year, status__in=['beantragt', 'genehmigt'], ).count() # ── Overdue Nachweise (current quarter) ── overdue_nachweise = VierteljahresNachweis.objects.filter( quartal=current_quarter, jahr=current_year, status__in=['offen', 'teilweise', 'nachbesserung'], ).select_related('destinataer').order_by('jahr', 'quartal')[:10] # ── Pending payments (not yet paid out) ── pending_payments = DestinataerUnterstuetzung.objects.filter( status__in=['geplant', 'faellig', 'in_bearbeitung'], ).select_related('destinataer').order_by('faellig_am')[:10] pending_payment_total = DestinataerUnterstuetzung.objects.filter( status__in=['geplant', 'faellig', 'in_bearbeitung'], ).aggregate(total=Coalesce(Sum('betrag'), Decimal('0')))['total'] # ── New emails ── new_emails = DestinataerEmailEingang.objects.filter( status='neu', ).order_by('-eingangsdatum')[:5] new_email_count = DestinataerEmailEingang.objects.filter(status='neu').count() # ── Expiring leases (next 90 days) ── lease_cutoff = today + timedelta(days=90) expiring_leases = LandVerpachtung.objects.filter( pachtende__lte=lease_cutoff, pachtende__gte=today, ).select_related('paechter', 'land').order_by('pachtende')[:5] # ── Recent audit log ── recent_audit = AuditLog.objects.order_by('-timestamp')[:5] context = { "title": "Dashboard", # Stats "destinataer_count": destinataer_count, "paechter_count": paechter_count, "land_count": land_count, "foerderung_active": foerderung_active, # Calendar "upcoming_events": upcoming_events[:5], "overdue_events": overdue_events[:3], "today": today, # Action items "overdue_nachweise": overdue_nachweise, "pending_payments": pending_payments, "pending_payment_total": pending_payment_total, "new_emails": new_emails, "new_email_count": new_email_count, "expiring_leases": expiring_leases, "recent_audit": recent_audit, "current_quarter": current_quarter, "current_year": current_year, } return render(request, "stiftung/home.html", context) @api_view(["GET"]) def health_check(request): """Simple health check endpoint for deployment monitoring""" return JsonResponse( { "status": "healthy", "timestamp": timezone.now().isoformat(), "service": "stiftung-web", } ) # CSV Import Views @api_view(["GET"]) def health(_request): return Response({"status": "ok"})