WeasyPrint dependencies are missing: {IMPORT_ERROR}
+Showing content as HTML preview instead. You can print this page to PDF using your browser.
+An error occurred while generating the PDF:
+{str(e)}
+Showing content as HTML preview instead.
+PDF generation requires additional system dependencies that are not installed.
+Error: {str(e)}
+Please install WeasyPrint dependencies or use CSV export instead.
+ + + """ + response = HttpResponse(error_html, content_type='text/html') + response['Content-Disposition'] = 'inline; filename="pdf_not_available.html"' + return response + return MockPDFGenerator() + class GrampsClient: """Lightweight client for Gramps Web API.""" def __init__(self, base_url: str, token: str = '', username: str = '', password: str = ''): @@ -110,7 +140,7 @@ def gramps_debug_api(_request): 'has_password': bool(getattr(settings, 'GRAMPS_PASSWORD', '')), }) -from .forms import PersonForm, PaechterForm, DestinataerForm, DokumentLinkForm, FoerderungForm, LandForm, VerpachtungForm, DestinataerUnterstuetzungForm, DestinataerNotizForm +from .forms import PersonForm, PaechterForm, DestinataerForm, DokumentLinkForm, FoerderungForm, UnterstuetzungForm, UnterstuetzungMarkAsPaidForm, LandForm, DestinataerUnterstuetzungForm, DestinataerNotizForm from stiftung.models import DestinataerUnterstuetzung, DestinataerNotiz def home(request): @@ -130,7 +160,10 @@ def dokument_management(request): @api_view(['GET']) def paperless_document_redirect(_request, doc_id: int): """Redirects to the Paperless UI document URL and supports thumbnails if needed later.""" - url = getattr(settings, "PAPERLESS_API_URL", None) + from stiftung.utils.config import get_paperless_config + + config = get_paperless_config() + url = config['api_url'] if not url: return Response({'error': 'Paperless API not configured'}, status=400) base_url = url.rstrip('/api') if url.endswith('/api') else url @@ -747,7 +780,8 @@ def person_list(request): def person_detail(request, pk): person = get_object_or_404(Person, pk=pk) foerderungen = person.foerderung_set.all().order_by('-jahr', '-betrag') - verpachtungen = person.verpachtung_set.all().order_by('-pachtbeginn') + # Get new LandVerpachtungen for this person's Paechter instances + verpachtungen = LandVerpachtung.objects.filter(paechter__person=person).order_by('-pachtbeginn') context = { 'person': person, @@ -884,7 +918,8 @@ def destinataer_detail(request, pk): # Förderungen für diesen Destinatär laden foerderungen = Foerderung.objects.filter(destinataer=destinataer).order_by('-jahr', '-betrag') - # No inline form anymore + # Unterstützungen für diesen Destinatär laden + unterstuetzungen = DestinataerUnterstuetzung.objects.filter(destinataer=destinataer).order_by('-faellig_am') # Notizen laden notizen_eintraege = DestinataerNotiz.objects.filter(destinataer=destinataer).order_by('-erstellt_am') @@ -893,6 +928,7 @@ def destinataer_detail(request, pk): 'destinataer': destinataer, 'verknuepfte_dokumente': verknuepfte_dokumente, 'foerderungen': foerderungen, + 'unterstuetzungen': unterstuetzungen, 'notizen_eintraege': notizen_eintraege, } return render(request, 'stiftung/destinataer_detail.html', context) @@ -1004,15 +1040,14 @@ def paechter_list(request): # Annotate with total leased area and rent (coalesce nulls to Decimal for stable sorting) paechter = paechter.annotate( gesamt_flaeche=Coalesce( - Sum('verpachtung__verpachtete_flaeche'), + Sum('neue_verpachtungen__verpachtete_flaeche'), Value(Decimal('0.00'), output_field=DecimalField(max_digits=12, decimal_places=2)), output_field=DecimalField(max_digits=12, decimal_places=2), ), gesamt_pachtzins=Coalesce( - Sum('verpachtung__pachtzins_jaehrlich'), + Sum('neue_verpachtungen__pachtzins_pauschal'), Value(Decimal('0.00'), output_field=DecimalField(max_digits=12, decimal_places=2)), - output_field=DecimalField(max_digits=12, decimal_places=2), - ), + output_field=DecimalField(max_digits=12, decimal_places=2)), ) # Sorting @@ -1056,8 +1091,8 @@ def paechter_detail(request, pk): paechter_id=paechter.pk ).order_by('kontext', 'titel') - # Legacy Verpachtungen für diesen Pächter laden - verpachtungen = Verpachtung.objects.filter(paechter=paechter).order_by('-pachtbeginn') + # Neue LandVerpachtungen für diesen Pächter laden + verpachtungen = LandVerpachtung.objects.filter(paechter=paechter).order_by('-pachtbeginn') # Neue gepachtete Ländereien (über aktueller_paechter) gepachtete_laendereien = paechter.gepachtete_laendereien.filter(aktiv=True).order_by('gemeinde', 'gemarkung') @@ -1069,7 +1104,7 @@ def paechter_detail(request, pk): context = { 'paechter': paechter, 'verknuepfte_dokumente': verknuepfte_dokumente, - 'verpachtungen': verpachtungen, # Legacy + 'verpachtungen': verpachtungen, # Now using LandVerpachtung 'gepachtete_laendereien': gepachtete_laendereien, # Neu 'total_flaeche_neu': total_flaeche_neu, 'total_pachtzins_neu': total_pachtzins_neu, @@ -1260,16 +1295,13 @@ def land_detail(request, pk): land_id=land.pk ).order_by('kontext', 'titel') - # Legacy Verpachtungen für diese Länderei laden - verpachtungen = Verpachtung.objects.filter(land=land).order_by('-pachtbeginn') - # Neue LandVerpachtungen laden (mit related data) neue_verpachtungen = land.neue_verpachtungen.select_related('paechter').order_by('-pachtbeginn') context = { 'land': land, 'verknuepfte_dokumente': verknuepfte_dokumente, - 'verpachtungen': verpachtungen, + 'verpachtungen': neue_verpachtungen, # Using only new system now 'neue_verpachtungen': neue_verpachtungen, } return render(request, 'stiftung/land_detail.html', context) @@ -1323,7 +1355,7 @@ def verpachtung_list(request): sort = request.GET.get('sort', '') direction = request.GET.get('dir', 'asc') - verpachtungen = Verpachtung.objects.select_related('land', 'paechter').all() + verpachtungen = LandVerpachtung.objects.select_related('land', 'paechter').all() if search_query: verpachtungen = verpachtungen.filter( @@ -1364,7 +1396,7 @@ def verpachtung_list(request): # Calculate statistics for the summary cards # Get ALL verpachtungen (not filtered) for accurate statistics - all_verpachtungen = Verpachtung.objects.select_related('land', 'paechter').all() + all_verpachtungen = LandVerpachtung.objects.select_related('land', 'paechter').all() # Active verpachtungen count aktive_verpachtungen = all_verpachtungen.filter(status='aktiv').count() @@ -1406,71 +1438,89 @@ def verpachtung_list(request): return render(request, 'stiftung/verpachtung_list.html', context) @login_required -def verpachtung_detail(request, pk): - verpachtung = get_object_or_404(Verpachtung, pk=pk) +@login_required +def land_verpachtung_detail(request, pk): + """Detail view for LandVerpachtung""" + verpachtung = get_object_or_404(LandVerpachtung, pk=pk) # Alle mit dieser Verpachtung verknüpften Dokumente laden verknuepfte_dokumente = DokumentLink.objects.filter( - verpachtung_id=verpachtung.pk + land_verpachtung_id=verpachtung.pk ).order_by('kontext', 'titel') context = { 'verpachtung': verpachtung, + 'landverpachtung': verpachtung, # Template expects this variable name 'verknuepfte_dokumente': verknuepfte_dokumente, } - return render(request, 'stiftung/verpachtung_detail.html', context) + return render(request, 'stiftung/land_verpachtung_detail.html', context) @login_required -def verpachtung_create(request): - if request.method == 'POST': - form = VerpachtungForm(request.POST) - if form.is_valid(): - verpachtung = form.save() - messages.success(request, f'Verpachtung "{verpachtung}" wurde erfolgreich erstellt.') - return redirect('stiftung:verpachtung_detail', pk=verpachtung.pk) - else: - form = VerpachtungForm() +def land_verpachtung_update(request, pk): + """Update an existing LandVerpachtung by its primary key""" + verpachtung = get_object_or_404(LandVerpachtung, pk=pk) - context = {'form': form, 'title': 'Neue Verpachtung erstellen'} - return render(request, 'stiftung/verpachtung_form.html', context) + if request.method == 'POST': + # Handle the update form submission + vertragsnummer = request.POST.get('vertragsnummer') + pachtbeginn = request.POST.get('pachtbeginn') + pachtende = request.POST.get('pachtende') + pachtzins_jaehrlich = request.POST.get('pachtzins_jaehrlich') + + if vertragsnummer: + verpachtung.vertragsnummer = vertragsnummer + if pachtbeginn: + verpachtung.pachtbeginn = pachtbeginn + if pachtende: + verpachtung.pachtende = pachtende + if pachtzins_jaehrlich: + verpachtung.pachtzins_jaehrlich = pachtzins_jaehrlich + + verpachtung.save() + messages.success(request, 'Verpachtung wurde erfolgreich aktualisiert.') + return redirect('stiftung:land_verpachtung_detail', pk=verpachtung.pk) + + context = { + 'verpachtung': verpachtung, + 'landverpachtung': verpachtung, # Template expects this variable name + 'is_edit': True, + 'is_update': True, # Form template uses this flag + } + return render(request, 'stiftung/land_verpachtung_form.html', context) -@login_required -def verpachtung_update(request, pk): - verpachtung = get_object_or_404(Verpachtung, pk=pk) - if request.method == 'POST': - form = VerpachtungForm(request.POST, instance=verpachtung) - if form.is_valid(): - verpachtung = form.save() - messages.success(request, f'Verpachtung "{verpachtung}" wurde erfolgreich aktualisiert.') - return redirect('stiftung:verpachtung_list') - else: - form = VerpachtungForm(instance=verpachtung) +@login_required +def land_verpachtung_end_direct(request, pk): + """End a LandVerpachtung directly by its primary key""" + verpachtung = get_object_or_404(LandVerpachtung, pk=pk) - context = {'form': form, 'verpachtung': verpachtung, 'title': f'Verpachtung bearbeiten: {verpachtung}'} - return render(request, 'stiftung/verpachtung_form.html', context) - -@login_required -def verpachtung_delete(request, pk): - verpachtung = get_object_or_404(Verpachtung, pk=pk) if request.method == 'POST': - verpachtung.delete() - messages.success(request, f'Verpachtung "{verpachtung}" wurde erfolgreich gelöscht.') - return redirect('stiftung:verpachtung_list') + verpachtung.status = 'beendet' + verpachtung.pachtende = timezone.now().date() + verpachtung.save() + messages.success(request, 'Verpachtung wurde erfolgreich beendet.') + return redirect('stiftung:land_detail', pk=verpachtung.land.pk) - context = {'verpachtung': verpachtung} - return render(request, 'stiftung/verpachtung_confirm_delete.html', context) + context = { + 'verpachtung': verpachtung, + } + return render(request, 'stiftung/land_verpachtung_end_confirm.html', context) # Förderung Views @login_required def foerderung_list(request): """List all funding grants with filtering and pagination""" - foerderungen = Foerderung.objects.select_related('person', 'verwendungsnachweis').all() + foerderungen = Foerderung.objects.select_related('destinataer', 'verwendungsnachweis').all() + + # Check for export request - handle both GET and POST + export_format = request.POST.get('format') if request.method == 'POST' else request.GET.get('format', '') + selected_ids_param = request.POST.get('selected_entries', '') if request.method == 'POST' else request.GET.get('selected_entries', '') + selected_ids = [id for id in selected_ids_param.split(',') if id] if selected_ids_param else [] # Filtering jahr = request.GET.get('jahr') kategorie = request.GET.get('kategorie') status = request.GET.get('status') - person = request.GET.get('person') + destinataer = request.GET.get('destinataer') if jahr: foerderungen = foerderungen.filter(jahr=int(jahr)) @@ -1478,8 +1528,14 @@ def foerderung_list(request): foerderungen = foerderungen.filter(kategorie=kategorie) if status: foerderungen = foerderungen.filter(status=status) - if person: - foerderungen = foerderungen.filter(person__nachname__icontains=person) + if destinataer: + foerderungen = foerderungen.filter(destinataer__nachname__icontains=destinataer) + + # Handle exports + if export_format == 'csv': + return export_foerderungen_csv(request, foerderungen, selected_ids) + elif export_format == 'pdf': + return export_foerderungen_pdf(request, foerderungen, selected_ids) # Pagination paginator = Paginator(foerderungen, 25) @@ -1497,6 +1553,7 @@ def foerderung_list(request): context = { 'page_obj': page_obj, + 'foerderungen': foerderungen, # Add for counting 'total_betrag': total_betrag, 'avg_betrag': avg_betrag, 'kategorien': Foerderung.KATEGORIE_CHOICES, @@ -1504,7 +1561,7 @@ def foerderung_list(request): 'filter_jahr': jahr, 'filter_kategorie': kategorie, 'filter_status': status, - 'filter_person': person, + 'filter_person': destinataer, 'jahre': jahre, } return render(request, 'stiftung/foerderung_list.html', context) @@ -1529,14 +1586,20 @@ def foerderung_detail(request, pk): @login_required def foerderung_create(request): """Create a new funding grant""" + # Get destinataer from URL parameter if provided + destinataer_id = request.GET.get('destinataer') + initial = {} + if destinataer_id: + initial['destinataer'] = destinataer_id + if request.method == 'POST': form = FoerderungForm(request.POST) if form.is_valid(): foerderung = form.save() - messages.success(request, f'Förderung für {foerderung.person} wurde erfolgreich erstellt.') + messages.success(request, f'Förderung für {foerderung.destinataer} wurde erfolgreich erstellt.') return redirect('stiftung:foerderung_detail', pk=foerderung.pk) else: - form = FoerderungForm() + form = FoerderungForm(initial=initial) context = { 'form': form, @@ -1571,8 +1634,13 @@ def foerderung_delete(request, pk): foerderung = get_object_or_404(Foerderung, pk=pk) if request.method == 'POST': + # Get the recipient name before deletion + recipient_name = foerderung.destinataer.get_full_name() if foerderung.destinataer else ( + foerderung.person.get_full_name() if foerderung.person else "Unbekannter Empfänger" + ) + foerderung.delete() - messages.success(request, f'Förderung für {foerderung.person} wurde erfolgreich gelöscht.') + messages.success(request, f'Förderung für {recipient_name} wurde erfolgreich gelöscht.') return redirect('stiftung:foerderung_list') context = { @@ -1589,11 +1657,12 @@ def dokument_list(request): dokumente = DokumentLink.objects.all().order_by('-id') # Paperless-API-Konfiguration für verfügbare Dokumente - from django.conf import settings + from stiftung.utils.config import get_paperless_config import requests - url = getattr(settings, "PAPERLESS_API_URL", None) - token = getattr(settings, "PAPERLESS_API_TOKEN", None) + config = get_paperless_config() + url = config['api_url'] + token = config['api_token'] available_dokumente = [] if url and token: @@ -1634,7 +1703,7 @@ def dokument_list(request): elif isinstance(doc_tags, str): tags = [tag.strip() for tag in doc_tags.split(',')] - if any(tag in ['Stiftung_Destinatäre', 'Stiftung_Land_und_Pächter', 'Stiftung_Administration'] for tag in tags): + if any(tag in [config['destinataere_tag'], config['land_tag'], config['admin_tag']] for tag in tags): bereits_verknuepft = DokumentLink.objects.filter( paperless_document_id=doc['id'] ).exists() @@ -1765,20 +1834,18 @@ def bericht_list(request): # Get available years from data jahre = sorted(set( list(Foerderung.objects.values_list('jahr', flat=True)) + - list(Verpachtung.objects.values_list('pachtbeginn__year', flat=True)) + list(LandVerpachtung.objects.values_list('pachtbeginn__year', flat=True)) ), reverse=True) - # Statistics for overview tiles - total_persons = Person.objects.count() + # Statistics for overview tiles (removed legacy Person and Verpachtung) total_destinataere = Destinataer.objects.count() total_laendereien = Land.objects.count() - total_verpachtungen = Verpachtung.objects.count() + total_verpachtungen = LandVerpachtung.objects.count() total_foerderungen = Foerderung.objects.count() context = { 'jahre': jahre, 'title': 'Berichte', - 'total_persons': total_persons, 'total_destinataere': total_destinataere, 'total_laendereien': total_laendereien, 'total_verpachtungen': total_verpachtungen, @@ -1791,14 +1858,14 @@ def jahresbericht_generate(request, jahr): """Generate annual report for a specific year""" # Get data for the year foerderungen = Foerderung.objects.filter(jahr=jahr).select_related('person') - verpachtungen = Verpachtung.objects.filter( + verpachtungen = LandVerpachtung.objects.filter( pachtbeginn__year__lte=jahr, pachtende__year__gte=jahr ).select_related('land', 'paechter') # Calculate statistics total_foerderungen = foerderungen.aggregate(total=Sum('betrag'))['total'] or 0 - total_pachtzins = verpachtungen.aggregate(total=Sum('pachtzins_jaehrlich'))['total'] or 0 + total_pachtzins = verpachtungen.aggregate(total=Sum('pachtzins_pauschal'))['total'] or 0 context = { 'jahr': jahr, @@ -1828,14 +1895,14 @@ def jahresbericht_pdf(request, jahr): # Get data for the year foerderungen = Foerderung.objects.filter(jahr=jahr).select_related('person') - verpachtungen = Verpachtung.objects.filter( + verpachtungen = LandVerpachtung.objects.filter( pachtbeginn__year__lte=jahr, pachtende__year__gte=jahr ).select_related('land', 'paechter') # Calculate statistics total_foerderungen = foerderungen.aggregate(total=Sum('betrag'))['total'] or 0 - total_pachtzins = verpachtungen.aggregate(total=Sum('pachtzins_jaehrlich'))['total'] or 0 + total_pachtzins = verpachtungen.aggregate(total=Sum('pachtzins_pauschal'))['total'] or 0 context = { 'jahr': jahr, @@ -1860,9 +1927,7 @@ def jahresbericht_pdf(request, jahr): # Dashboard Views @login_required def dashboard(request): - # Person statistics - total_persons = Person.objects.count() - active_persons = Person.objects.filter(aktiv=True).count() + # Foerderung statistics (Person statistics removed - was legacy Verpachtung system) total_foerderungen = Foerderung.objects.aggregate(total=Sum('betrag'))['total'] or 0 # Land statistics @@ -1871,28 +1936,31 @@ def dashboard(request): total_flaeche = Land.objects.aggregate(total=Sum('groesse_qm'))['total'] or 0 # Calculate total verpachtet from active verpachtungen - total_verpachtet = Verpachtung.objects.filter(status='aktiv').aggregate( + total_verpachtet = LandVerpachtung.objects.filter(status='aktiv').aggregate( total=Sum('verpachtete_flaeche') )['total'] or 0 # Verpachtung statistics - total_verpachtungen = Verpachtung.objects.count() - active_verpachtungen = Verpachtung.objects.filter(status='aktiv').count() - total_pachtzins = Verpachtung.objects.filter(status='aktiv').aggregate( - total=Sum('pachtzins_jaehrlich') + total_verpachtungen = LandVerpachtung.objects.count() + active_verpachtungen = LandVerpachtung.objects.filter(status='aktiv').count() + total_pachtzins = LandVerpachtung.objects.filter(status='aktiv').aggregate( + total=Sum('pachtzins_pauschal') )['total'] or 0 # Recent activities recent_lands = Land.objects.order_by('-erstellt_am')[:5] - recent_verpachtungen = Verpachtung.objects.select_related('land', 'paechter').order_by('-erstellt_am')[:5] + recent_verpachtungen = LandVerpachtung.objects.select_related('land', 'paechter').order_by('-erstellt_am')[:5] # Dokumentenübersicht dokumente_uebersicht = DokumentLink.objects.all().order_by('-id')[:10] # Verfügbare Paperless-Dokumente für Dashboard available_paperless_docs = [] - url = getattr(settings, "PAPERLESS_API_URL", None) - token = getattr(settings, "PAPERLESS_API_TOKEN", None) + from stiftung.utils.config import get_paperless_config + + config = get_paperless_config() + url = config['api_url'] + token = config['api_token'] if url and token: try: @@ -1932,7 +2000,7 @@ def dashboard(request): elif isinstance(doc_tags, str): tags = [tag.strip() for tag in doc_tags.split(',')] - if any(tag in ['Stiftung_Destinatäre', 'Stiftung_Land_und_Pächter', 'Stiftung_Administration'] for tag in tags): + if any(tag in [config['destinataere_tag'], config['land_tag'], config['admin_tag']] for tag in tags): bereits_verknuepft = DokumentLink.objects.filter( paperless_document_id=doc['id'] ).exists() @@ -1956,8 +2024,7 @@ def dashboard(request): pass context = { - 'total_persons': total_persons, - 'active_persons': active_persons, + # Person statistics removed - was legacy Verpachtung system 'total_foerderungen': total_foerderungen, 'total_land': total_land, 'active_land': active_land, @@ -2007,8 +2074,11 @@ def health(_request): @api_view(['GET']) def paperless_ping(_request): - url = getattr(settings, "PAPERLESS_API_URL", None) - token = getattr(settings, "PAPERLESS_API_TOKEN", None) + from stiftung.utils.config import get_paperless_config + + config = get_paperless_config() + url = config['api_url'] + token = config['api_token'] if not url or not token: return Response({'ok': False, 'reason': 'Paperless API not configured'}, status=400) try: @@ -2025,16 +2095,17 @@ def paperless_documents(request): Optionales Polling: ?poll=1 wartet kurz und versucht erneut, damit frisch ingestete Dokumente in Paperless erscheinen, bevor die Liste zurückgegeben wird. """ - from django.conf import settings + from stiftung.utils.config import get_paperless_config - url = getattr(settings, "PAPERLESS_API_URL", None) - token = getattr(settings, "PAPERLESS_API_TOKEN", None) - required_tag = getattr(settings, "PAPERLESS_REQUIRED_TAG", "Stiftung_Destinatäre") - land_tag = getattr(settings, "PAPERLESS_LAND_TAG", "Stiftung_Land_und_Pächter") - admin_tag = getattr(settings, "PAPERLESS_ADMIN_TAG", "Stiftung_Administration") - destinaere_tag_id = getattr(settings, "PAPERLESS_DESTINATAERE_TAG_ID", "210") - land_tag_id = getattr(settings, "PAPERLESS_LAND_TAG_ID", "204") - admin_tag_id = getattr(settings, "PAPERLESS_ADMIN_TAG_ID", "216") + config = get_paperless_config() + url = config['api_url'] + token = config['api_token'] + required_tag = config['destinataere_tag'] + land_tag = config['land_tag'] + admin_tag = config['admin_tag'] + destinaere_tag_id = config['destinataere_tag_id'] + land_tag_id = config['land_tag_id'] + admin_tag_id = config['admin_tag_id'] if not url or not token: return Response({ @@ -2141,16 +2212,17 @@ def paperless_documents(request): @api_view(['GET']) def paperless_debug(request): """Debug-View für Paperless-Integration""" - from django.conf import settings + from stiftung.utils.config import get_paperless_config - url = getattr(settings, "PAPERLESS_API_URL", None) - token = getattr(settings, "PAPERLESS_API_TOKEN", None) - required_tag = getattr(settings, "PAPERLESS_REQUIRED_TAG", "Stiftung_Destinatäre") - land_tag = getattr(settings, "PAPERLESS_LAND_TAG", "Stiftung_Land_und_Pächter") - admin_tag = getattr(settings, "PAPERLESS_ADMIN_TAG", "Stiftung_Administration") - destinaere_tag_id = getattr(settings, "PAPERLESS_DESTINATAERE_TAG_ID", "210") - land_tag_id = getattr(settings, "PAPERLESS_LAND_TAG_ID", "204") - admin_tag_id = getattr(settings, "PAPERLESS_ADMIN_TAG_ID", "216") + config = get_paperless_config() + url = config['api_url'] + token = config['api_token'] + required_tag = config['destinataere_tag'] + land_tag = config['land_tag'] + admin_tag = config['admin_tag'] + destinaere_tag_id = config['destinataere_tag_id'] + land_tag_id = config['land_tag_id'] + admin_tag_id = config['admin_tag_id'] if not url or not token: return Response({'error': 'Paperless API not configured'}, status=400) @@ -2249,10 +2321,11 @@ def paperless_debug(request): @api_view(['GET']) def paperless_tags_only(request): """Holt nur die Tag-Liste aus Paperless - ohne Dokumente""" - from django.conf import settings + from stiftung.utils.config import get_paperless_config - url = getattr(settings, "PAPERLESS_API_URL", None) - token = getattr(settings, "PAPERLESS_API_TOKEN", None) + config = get_paperless_config() + url = config['api_url'] + token = config['api_token'] if not url or not token: return Response({'error': 'Paperless API not configured'}, status=400) @@ -2395,7 +2468,7 @@ def link_document_search(request): ] if category in ['all', 'verpachtung']: - # Suche nach Verpachtungen + # Suche nach Verpachtungen (using new LandVerpachtung model) verpachtung_query = Q() if query and query != 'all': verpachtung_query = ( @@ -2405,16 +2478,16 @@ def link_document_search(request): Q(land__gemarkung__icontains=query) | Q(land__gemeinde__icontains=query) | Q(land__flur__icontains=query) | Q(land__flurstueck__icontains=query) | Q(land__lfd_nr__icontains=query) | - Q(pachtzins_jaehrlich__icontains=query) | Q(notizen__icontains=query) + Q(vertragsnummer__icontains=query) | Q(pachtzins_pauschal__icontains=query) | Q(bemerkungen__icontains=query) ) - verpachtung_results = Verpachtung.objects.filter(verpachtung_query).select_related('paechter', 'land')[:25] + verpachtung_results = LandVerpachtung.objects.filter(verpachtung_query).select_related('paechter', 'land')[:25] results['verpachtung'] = [ { 'id': v.id, 'name': f"{(v.paechter.vorname + ' ') if v.paechter and v.paechter.vorname else ''}{v.paechter.nachname if v.paechter else 'Unbekannt'} → {v.land.gemarkung}, Flur {v.land.flur}", 'type': 'Verpachtung', - 'details': f"Flurstück: {v.land.flurstueck or 'N/A'} • Pachtzins: {v.pachtzins_jaehrlich or 'N/A'} • Zeitraum: {v.pachtbeginn.strftime('%Y') if v.pachtbeginn else '?'}-{v.pachtende.strftime('%Y') if v.pachtende else '?'}" + 'details': f"Vertrag: {v.vertragsnummer} • Flurstück: {v.land.flurstueck or 'N/A'} • Pachtzins: {v.pachtzins_pauschal or 'N/A'} €/Jahr • Zeitraum: {v.pachtbeginn.strftime('%Y') if v.pachtbeginn else '?'}-{v.pachtende.strftime('%Y') if v.pachtende else 'laufend'}" } for v in verpachtung_results ] @@ -2463,14 +2536,60 @@ def link_document_search(request): } for r in rentmeister_results ] + + if category in ['all', 'abrechnung']: + # Suche nach Abrechnungen + abrechnung_query = Q() + if query and query != 'all': + abrechnung_query = ( + Q(land__gemarkung__icontains=query) | Q(land__gemeinde__icontains=query) | + Q(land__flur__icontains=query) | Q(land__flurstueck__icontains=query) | + Q(land__lfd_nr__icontains=query) | Q(abrechnungsjahr__icontains=query) | + Q(bemerkungen__icontains=query) + ) + + abrechnung_results = LandAbrechnung.objects.filter(abrechnung_query).select_related('land')[:25] + results['abrechnung'] = [ + { + 'id': a.id, + 'name': f"Abrechnung {a.abrechnungsjahr} - {a.land.gemarkung}, Flur {a.land.flur}", + 'type': 'Abrechnung', + 'details': f"Flurstück: {a.land.flurstueck or 'N/A'} • Jahr: {a.abrechnungsjahr} • Grundsteuer: {a.grundsteuer_betrag or 0} € • Versicherung: {a.versicherungen_betrag or 0} €" + } + for a in abrechnung_results + ] + + if category in ['all', 'foerderung']: + # Suche nach Förderungen + foerderung_query = Q() + if query and query != 'all': + foerderung_query = ( + Q(destinataer__nachname__icontains=query) | Q(destinataer__vorname__icontains=query) | + Q(destinataer__institution__icontains=query) | Q(destinataer__email__icontains=query) | + Q(jahr__icontains=query) | Q(betrag__icontains=query) | + Q(kategorie__icontains=query) | Q(status__icontains=query) | + Q(bemerkungen__icontains=query) + ) + + foerderung_results = Foerderung.objects.filter(foerderung_query).select_related('destinataer')[:25] + results['foerderung'] = [ + { + 'id': str(f.id), # Convert UUID to string for JSON serialization + 'name': f"{f.destinataer.get_full_name() if f.destinataer else 'Unbekannt'} - {f.jahr}", + 'type': 'Förderung', + 'details': f"Betrag: {f.betrag} € • Kategorie: {f.get_kategorie_display()} • Status: {f.get_status_display()} • Jahr: {f.jahr}" + } + for f in foerderung_results + ] + return Response(results) def create_paechter_link_for_verpachtung(paperless_id, paperless_title, verpachtung_id): """Hilfsfunktion: Erstellt automatisch eine Pächter-Verknüpfung für eine Verpachtung""" try: - # Hole die Verpachtung und den zugehörigen Pächter - verpachtung = Verpachtung.objects.select_related('paechter').get(id=verpachtung_id) + # Hole die LandVerpachtung und den zugehörigen Pächter + verpachtung = LandVerpachtung.objects.select_related('paechter').get(id=verpachtung_id) if verpachtung.paechter: # Prüfe, ob bereits eine Verknüpfung für dieses Dokument und diesen Pächter existiert existing_link = DokumentLink.objects.filter( @@ -2487,7 +2606,7 @@ def create_paechter_link_for_verpachtung(paperless_id, paperless_title, verpacht paechter_id=verpachtung.paechter.id ) return True - except (Verpachtung.DoesNotExist, Exception): + except (LandVerpachtung.DoesNotExist, Exception): pass return False @@ -2511,7 +2630,7 @@ def link_document_create(request): paperless_id = payload.get('paperless_id') paperless_title = payload.get('paperless_title') paperless_url = payload.get('paperless_url') - link_type = payload.get('link_type') # 'destinataer', 'land', 'verpachtung', 'paechter' + link_type = payload.get('link_type') # 'destinataer', 'land', 'verpachtung', 'paechter', 'abrechnung' link_id = payload.get('link_id') if not all([paperless_id, paperless_title, paperless_url, link_type, link_id]): @@ -2531,11 +2650,16 @@ def link_document_create(request): elif link_type == 'land': dokument_link.land_id = link_id elif link_type == 'verpachtung': - dokument_link.verpachtung_id = link_id + # Use new LandVerpachtung field instead of legacy + dokument_link.land_verpachtung_id = link_id elif link_type == 'paechter': dokument_link.paechter_id = link_id + elif link_type == 'foerderung': + dokument_link.foerderung_id = link_id elif link_type == 'rentmeister': dokument_link.rentmeister_id = link_id + elif link_type == 'abrechnung': + dokument_link.abrechnung_id = link_id dokument_link.save() @@ -2556,6 +2680,10 @@ def link_document_create(request): from stiftung.models import Paechter entity = Paechter.objects.get(id=link_id) target_name = f"{entity.vorname} {entity.nachname}".strip() + elif link_type == 'foerderung': + from stiftung.models import Foerderung + entity = Foerderung.objects.get(id=link_id) + target_name = f"{entity.destinataer.get_full_name() if entity.destinataer else 'Unbekannt'} - {entity.jahr}" elif link_type == 'verpachtung': from stiftung.models import Verpachtung entity = Verpachtung.objects.get(id=link_id) @@ -2667,17 +2795,18 @@ def link_document_list(request): except Paechter.DoesNotExist: link_info['linked_object'] = {'type': 'Pächter', 'name': 'Gelöscht', 'details': 'Datensatz nicht mehr verfügbar'} - elif link.verpachtung_id: + elif link.land_verpachtung_id: link_info['link_type'] = 'verpachtung' try: - verp = Verpachtung.objects.select_related('paechter', 'land').get(id=link.verpachtung_id) + from stiftung.models import LandVerpachtung + verp = LandVerpachtung.objects.select_related('paechter', 'land').get(id=link.land_verpachtung_id) link_info['linked_object'] = { 'id': str(verp.id), 'type': 'Verpachtung', 'name': f"Verpachtung {(verp.paechter.vorname + ' ') if verp.paechter and verp.paechter.vorname else ''}{verp.paechter.nachname if verp.paechter else 'Unbekannt'}", - 'details': f"Land: {verp.land.gemarkung} - {verp.land.gemeinde}, Pächter: {(verp.paechter.vorname + ' ') if verp.paechter and verp.paechter.vorname else ''}{verp.paechter.nachname if verp.paechter else 'Unbekannt'}" + 'details': f"Land: {verp.land.gemarkung} - {verp.land.gemeinde}, Pächter: {(verp.paechter.vorname + ' ') if verp.paechter and verp.paechter.vorname else ''}{verp.paechter.nachname if verp.paechter else 'Unbekannt'}, Vertrag: {verp.vertragsnummer}" } - except Verpachtung.DoesNotExist: + except LandVerpachtung.DoesNotExist: link_info['linked_object'] = {'type': 'Verpachtung', 'name': 'Gelöscht', 'details': 'Datensatz nicht mehr verfügbar'} elif link.rentmeister_id: @@ -2695,6 +2824,20 @@ def link_document_list(request): except Rentmeister.DoesNotExist: link_info['linked_object'] = {'type': 'Rentmeister', 'name': 'Gelöscht', 'details': 'Datensatz nicht mehr verfügbar'} + elif link.abrechnung_id: + link_info['link_type'] = 'abrechnung' + try: + abrechnung = LandAbrechnung.objects.select_related('land').get(id=link.abrechnung_id) + link_info['linked_object'] = { + 'id': str(abrechnung.id), + 'type': 'Abrechnung', + 'name': f"Abrechnung {abrechnung.abrechnungsjahr} - {abrechnung.land.gemarkung}", + 'details': f"Land: {abrechnung.land.gemarkung} - {abrechnung.land.gemeinde}, Flur {abrechnung.land.flur}, Jahr {abrechnung.abrechnungsjahr}", + 'url': f"/laendereien/abrechnungen/{abrechnung.id}/" + } + except LandAbrechnung.DoesNotExist: + link_info['linked_object'] = {'type': 'Abrechnung', 'name': 'Gelöscht', 'details': 'Datensatz nicht mehr verfügbar'} + links_by_document[paperless_id]['links'].append(link_info) # Convert to list format for frontend @@ -2744,6 +2887,7 @@ def link_document_update(request): link.land_id = None link.verpachtung_id = None link.paechter_id = None + link.foerderung_id = None link.rentmeister_id = None link.kontext = link_type @@ -2755,6 +2899,8 @@ def link_document_update(request): link.verpachtung_id = link_target_id elif link_type == 'paechter': link.paechter_id = link_target_id + elif link_type == 'foerderung': + link.foerderung_id = link_target_id elif link_type == 'rentmeister': link.rentmeister_id = link_target_id else: @@ -3437,43 +3583,24 @@ def administration(request): def unterstuetzungen_list(request): """Liste der Destinatärunterstützungen (Administration).""" status = request.GET.get('status', '') - export = request.GET.get('format', '') - qs = DestinataerUnterstuetzung.objects.select_related('destinataer', 'konto').order_by('-faellig_am', 'destinataer__nachname') + export_format = request.POST.get('format') if request.method == 'POST' else request.GET.get('format', '') + selected_ids_param = request.POST.get('selected_entries', '') if request.method == 'POST' else request.GET.get('selected_entries', '') + selected_ids = [id for id in selected_ids_param.split(',') if id] if selected_ids_param else [] + + qs = DestinataerUnterstuetzung.objects.select_related( + 'destinataer', 'konto', 'ausgezahlt_von', 'wiederkehrend_von' + ).order_by('-faellig_am', 'destinataer__nachname') + if status: qs = qs.filter(status=status) - # CSV export - if export == 'csv': - import csv - from django.http import HttpResponse - response = HttpResponse(content_type='text/csv; charset=utf-8') - response['Content-Disposition'] = 'attachment; filename=unterstuetzungen.csv' - writer = csv.writer(response, delimiter=';') - writer.writerow(['Destinatär','Betrag','Fällig am','Status','Bank','IBAN','Kontoname','Beschreibung']) - for u in qs: - writer.writerow([ - u.destinataer.get_full_name(), - f"{u.betrag:.2f}", - u.faellig_am.strftime('%d.%m.%Y'), - u.get_status_display(), - getattr(u.konto, 'bank_name', ''), - getattr(u.konto, 'iban', ''), - str(u.konto), - u.beschreibung or '', - ]) - return response - # PDF export (simple table via WeasyPrint; graceful fallback if missing) - if export == 'pdf': - try: - from django.template.loader import render_to_string - from weasyprint import HTML - html = render_to_string('stiftung/unterstuetzungen_pdf.html', {'unterstuetzungen': qs}) - from django.http import HttpResponse - pdf = HTML(string=html).write_pdf() - resp = HttpResponse(pdf, content_type='application/pdf') - resp['Content-Disposition'] = 'inline; filename=unterstuetzungen.pdf' - return resp - except Exception: - pass + + # Enhanced CSV export with field selection + if export_format == 'csv': + return export_unterstuetzungen_csv(request, qs, selected_ids) + + # Enhanced PDF export with corporate identity + elif export_format == 'pdf': + return export_unterstuetzungen_pdf(request, qs, selected_ids) context = { 'unterstuetzungen': qs, 'status_filter': status, @@ -3481,6 +3608,357 @@ def unterstuetzungen_list(request): return render(request, 'stiftung/unterstuetzungen_list.html', context) +def export_unterstuetzungen_csv(request, queryset, selected_ids=None): + """Enhanced CSV export with field selection""" + import csv + from django.http import HttpResponse + from datetime import datetime + + # If specific entries are selected, filter to only those + if selected_ids: + queryset = queryset.filter(id__in=selected_ids) + + # Get selected fields from request (default to all if none specified) + selected_fields_param = request.POST.get('selected_fields', '') if request.method == 'POST' else request.GET.get('selected_fields', '') + selected_fields = selected_fields_param.split(',') if selected_fields_param else [] + + if not selected_fields: + # Default field set + selected_fields = [ + 'destinataer_name', 'betrag', 'faellig_am', 'status', + 'empfaenger_iban', 'empfaenger_name', 'beschreibung' + ] + + # Field definitions with headers and data extraction + field_definitions = { + # Core payment fields + 'id': ('ID', lambda u: str(u.id)), + 'betrag': ('Betrag (€)', lambda u: f"{u.betrag:.2f}"), + 'faellig_am': ('Fällig am', lambda u: u.faellig_am.strftime('%d.%m.%Y') if u.faellig_am else ''), + 'status': ('Status', lambda u: u.get_status_display()), + 'beschreibung': ('Beschreibung', lambda u: u.beschreibung or ''), + 'ausgezahlt_am': ('Ausgezahlt am', lambda u: u.ausgezahlt_am.strftime('%d.%m.%Y') if u.ausgezahlt_am else ''), + 'erstellt_am': ('Erstellt am', lambda u: u.erstellt_am.strftime('%d.%m.%Y %H:%M') if u.erstellt_am else ''), + 'aktualisiert_am': ('Aktualisiert am', lambda u: u.aktualisiert_am.strftime('%d.%m.%Y %H:%M') if u.aktualisiert_am else ''), + + # Destinataer fields + 'destinataer_name': ('Destinatär Name', lambda u: u.destinataer.get_full_name() if u.destinataer else ''), + 'destinataer_vorname': ('Vorname', lambda u: u.destinataer.vorname if u.destinataer else ''), + 'destinataer_nachname': ('Nachname', lambda u: u.destinataer.nachname if u.destinataer else ''), + 'familienzweig': ('Familienzweig', lambda u: u.destinataer.familienzweig if u.destinataer else ''), + 'geburtsdatum': ('Geburtsdatum', lambda u: u.destinataer.geburtsdatum.strftime('%d.%m.%Y') if u.destinataer and u.destinataer.geburtsdatum else ''), + 'email': ('E-Mail', lambda u: u.destinataer.email if u.destinataer else ''), + 'telefon': ('Telefon', lambda u: u.destinataer.telefon if u.destinataer else ''), + 'destinataer_iban': ('Destinatär IBAN', lambda u: u.destinataer.iban if u.destinataer else ''), + 'strasse': ('Straße', lambda u: u.destinataer.strasse if u.destinataer else ''), + 'plz': ('PLZ', lambda u: u.destinataer.plz if u.destinataer else ''), + 'ort': ('Ort', lambda u: u.destinataer.ort if u.destinataer else ''), + 'adresse': ('Adresse', lambda u: f"{u.destinataer.strasse}, {u.destinataer.plz} {u.destinataer.ort}".strip(', ') if u.destinataer else ''), + 'berufsgruppe': ('Berufsgruppe', lambda u: u.destinataer.berufsgruppe if u.destinataer else ''), + 'ausbildungsstand': ('Ausbildungsstand', lambda u: u.destinataer.ausbildungsstand if u.destinataer else ''), + 'institution': ('Institution', lambda u: u.destinataer.institution if u.destinataer else ''), + 'jaehrliches_einkommen': ('Jährliches Einkommen (€)', lambda u: f"{u.destinataer.jaehrliches_einkommen:.2f}" if u.destinataer and u.destinataer.jaehrliches_einkommen else ''), + 'haushaltsgroesse': ('Haushaltsgröße', lambda u: str(u.destinataer.haushaltsgroesse) if u.destinataer and u.destinataer.haushaltsgroesse else ''), + 'monatliche_bezuege': ('Monatliche Bezüge (€)', lambda u: f"{u.destinataer.monatliche_bezuege:.2f}" if u.destinataer and u.destinataer.monatliche_bezuege else ''), + 'vermoegen': ('Vermögen (€)', lambda u: f"{u.destinataer.vermoegen:.2f}" if u.destinataer and u.destinataer.vermoegen else ''), + + # Payment details + 'empfaenger_iban': ('Empfänger IBAN', lambda u: u.empfaenger_iban or ''), + 'empfaenger_name': ('Empfänger Name', lambda u: u.empfaenger_name or ''), + 'verwendungszweck': ('Verwendungszweck', lambda u: u.verwendungszweck or ''), + + # Account fields + 'konto_name': ('Konto', lambda u: str(u.konto) if u.konto else ''), + 'konto_bank': ('Bank', lambda u: u.konto.bank_name if u.konto else ''), + 'konto_iban': ('Konto IBAN', lambda u: u.konto.iban if u.konto else ''), + + # System fields + 'ausgezahlt_von': ('Ausgezahlt von', lambda u: u.ausgezahlt_von.get_full_name() if u.ausgezahlt_von else ''), + 'ist_wiederkehrend': ('Wiederkehrend', lambda u: 'Ja' if u.wiederkehrend_von else 'Nein'), + } + + # Create CSV response + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + filename = f'unterstuetzungen_{timestamp}.csv' + + response = HttpResponse(content_type='text/csv; charset=utf-8') + response['Content-Disposition'] = f'attachment; filename="{filename}"' + + writer = csv.writer(response, delimiter=';', quoting=csv.QUOTE_ALL) + + # Write headers + headers = [field_definitions[field][0] for field in selected_fields if field in field_definitions] + writer.writerow(headers) + + # Write data rows + for u in queryset: + row = [] + for field in selected_fields: + if field in field_definitions: + try: + value = field_definitions[field][1](u) + row.append(value) + except Exception: + row.append('') # Fallback for any errors + else: + row.append('') # Unknown field + writer.writerow(row) + + return response + + +def export_unterstuetzungen_pdf(request, queryset, selected_ids=None): + """Enhanced PDF export with corporate identity and field selection""" + # If specific entries are selected, filter to only those + if selected_ids: + queryset = queryset.filter(id__in=selected_ids) + + # Get selected fields from request (default to key fields if none specified) + selected_fields_param = request.POST.get('selected_fields', '') if request.method == 'POST' else request.GET.get('selected_fields', '') + selected_fields = selected_fields_param.split(',') if selected_fields_param else [] + + if not selected_fields: + # Default field set for PDF (fewer fields than CSV for better readability) + selected_fields = [ + 'destinataer_name', 'betrag', 'faellig_am', 'status', + 'beschreibung', 'ausgezahlt_am' + ] + + # Field definitions with display names (reuse from CSV but select PDF-appropriate subset) + field_definitions = { + # Core payment fields + 'destinataer_name': 'Destinatär', + 'betrag': 'Betrag (€)', + 'faellig_am': 'Fällig am', + 'status': 'Status', + 'beschreibung': 'Beschreibung', + 'ausgezahlt_am': 'Ausgezahlt am', + 'erstellt_am': 'Erstellt am', + 'empfaenger_iban': 'Empfänger IBAN', + 'empfaenger_name': 'Empfänger', + 'verwendungszweck': 'Verwendungszweck', + 'konto_name': 'Konto', + 'ist_wiederkehrend': 'Wiederkehrend', + } + + # Filter to only include fields that are both selected and defined + filtered_fields = {k: v for k, v in field_definitions.items() if k in selected_fields} + + # Prepare data with field extraction logic + data_for_pdf = [] + for item in queryset: + row_data = {} + for field_key in filtered_fields.keys(): + try: + if field_key == 'destinataer_name': + row_data[field_key] = item.destinataer.get_full_name() if item.destinataer else '' + elif field_key == 'betrag': + row_data[field_key] = f"€{item.betrag:.2f}" if item.betrag else '' + elif field_key == 'faellig_am': + row_data[field_key] = item.faellig_am.strftime('%d.%m.%Y') if item.faellig_am else '' + elif field_key == 'status': + row_data[field_key] = item.get_status_display() + elif field_key == 'beschreibung': + row_data[field_key] = item.beschreibung or '' + elif field_key == 'ausgezahlt_am': + row_data[field_key] = item.ausgezahlt_am.strftime('%d.%m.%Y') if item.ausgezahlt_am else '' + elif field_key == 'erstellt_am': + row_data[field_key] = item.erstellt_am.strftime('%d.%m.%Y') if item.erstellt_am else '' + elif field_key == 'empfaenger_iban': + row_data[field_key] = item.empfaenger_iban or '' + elif field_key == 'empfaenger_name': + row_data[field_key] = item.empfaenger_name or '' + elif field_key == 'verwendungszweck': + row_data[field_key] = item.verwendungszweck or '' + elif field_key == 'konto_name': + row_data[field_key] = str(item.konto) if item.konto else '' + elif field_key == 'ist_wiederkehrend': + row_data[field_key] = 'Ja' if item.wiederkehrend_von else 'Nein' + else: + # Generic field access + row_data[field_key] = getattr(item, field_key, '') or '' + except Exception: + row_data[field_key] = '' # Fallback for any errors + + data_for_pdf.append(row_data) + + # Use PDF generator + pdf_gen = get_pdf_generator() + return pdf_gen.export_data_list_pdf( + data=data_for_pdf, + fields_config=filtered_fields, + title="Unterstützungen Export", + filename_prefix="unterstuetzungen", + request_user=request.user + ) + + +def export_foerderungen_csv(request, queryset, selected_ids=None): + """Enhanced CSV export for Förderungen with field selection""" + import csv + from django.http import HttpResponse + from datetime import datetime + + # If specific entries are selected, filter to only those + if selected_ids: + queryset = queryset.filter(id__in=selected_ids) + + # Get selected fields from request (default to all if none specified) + selected_fields_param = request.POST.get('selected_fields', '') if request.method == 'POST' else request.GET.get('selected_fields', '') + selected_fields = selected_fields_param.split(',') if selected_fields_param else [] + + if not selected_fields: + # Default field set + selected_fields = [ + 'destinataer_name', 'jahr', 'betrag', 'kategorie', 'status', + 'antragsdatum', 'beschreibung' + ] + + # Field definitions with headers and data extraction + field_definitions = { + # Core fields + 'id': ('ID', lambda f: str(f.id)), + 'destinataer_name': ('Destinatär Name', lambda f: f.destinataer.get_full_name() if f.destinataer else ''), + 'jahr': ('Jahr', lambda f: str(f.jahr)), + 'betrag': ('Betrag (€)', lambda f: f"{f.betrag:.2f}"), + 'kategorie': ('Kategorie', lambda f: f.get_kategorie_display()), + 'status': ('Status', lambda f: f.get_status_display()), + 'antragsdatum': ('Antragsdatum', lambda f: f.antragsdatum.strftime('%d.%m.%Y') if f.antragsdatum else ''), + 'bewilligungsdatum': ('Bewilligungsdatum', lambda f: f.bewilligungsdatum.strftime('%d.%m.%Y') if f.bewilligungsdatum else ''), + 'auszahlungsdatum': ('Auszahlungsdatum', lambda f: f.auszahlungsdatum.strftime('%d.%m.%Y') if f.auszahlungsdatum else ''), + 'beschreibung': ('Beschreibung', lambda f: f.beschreibung or ''), + 'begruendung': ('Begründung', lambda f: f.begruendung or ''), + 'verwendungsnachweis_datum': ('Verwendungsnachweis Datum', lambda f: f.verwendungsnachweis_datum.strftime('%d.%m.%Y') if f.verwendungsnachweis_datum else ''), + 'verwendungsnachweis_status': ('Verwendungsnachweis Status', lambda f: f.get_verwendungsnachweis_status_display() if f.verwendungsnachweis_status else ''), + + # Destinataer fields + 'destinataer_vorname': ('Vorname', lambda f: f.destinataer.vorname if f.destinataer else ''), + 'destinataer_nachname': ('Nachname', lambda f: f.destinataer.nachname if f.destinataer else ''), + 'familienzweig': ('Familienzweig', lambda f: f.destinataer.familienzweig if f.destinataer else ''), + 'email': ('E-Mail', lambda f: f.destinataer.email if f.destinataer else ''), + 'telefon': ('Telefon', lambda f: f.destinataer.telefon if f.destinataer else ''), + 'adresse': ('Adresse', lambda f: f"{f.destinataer.strasse}, {f.destinataer.plz} {f.destinataer.ort}".strip(', ') if f.destinataer else ''), + 'berufsgruppe': ('Berufsgruppe', lambda f: f.destinataer.berufsgruppe if f.destinataer else ''), + 'ausbildungsstand': ('Ausbildungsstand', lambda f: f.destinataer.ausbildungsstand if f.destinataer else ''), + 'institution': ('Institution', lambda f: f.destinataer.institution if f.destinataer else ''), + + # System fields + 'erstellt_am': ('Erstellt am', lambda f: f.erstellt_am.strftime('%d.%m.%Y %H:%M') if f.erstellt_am else ''), + 'aktualisiert_am': ('Aktualisiert am', lambda f: f.aktualisiert_am.strftime('%d.%m.%Y %H:%M') if f.aktualisiert_am else ''), + } + + # Create CSV response + timestamp = datetime.now().strftime('%Y%m%d_%H%M%S') + filename = f'foerderungen_{timestamp}.csv' + + response = HttpResponse(content_type='text/csv; charset=utf-8') + response['Content-Disposition'] = f'attachment; filename="{filename}"' + + writer = csv.writer(response, delimiter=';', quoting=csv.QUOTE_ALL) + + # Write headers + headers = [field_definitions[field][0] for field in selected_fields if field in field_definitions] + writer.writerow(headers) + + # Write data rows + for f in queryset: + row = [] + for field in selected_fields: + if field in field_definitions: + try: + value = field_definitions[field][1](f) + row.append(value) + except Exception: + row.append('') # Fallback for any errors + else: + row.append('') # Unknown field + writer.writerow(row) + + return response + + +def export_foerderungen_pdf(request, queryset, selected_ids=None): + """Enhanced PDF export for Förderungen with corporate identity and field selection""" + # If specific entries are selected, filter to only those + if selected_ids: + queryset = queryset.filter(id__in=selected_ids) + + # Get selected fields from request (default to key fields if none specified) + selected_fields_param = request.POST.get('selected_fields', '') if request.method == 'POST' else request.GET.get('selected_fields', '') + selected_fields = selected_fields_param.split(',') if selected_fields_param else [] + + if not selected_fields: + # Default field set for PDF (fewer fields than CSV for better readability) + selected_fields = [ + 'destinataer_name', 'jahr', 'betrag', 'kategorie', 'status', + 'antragsdatum' + ] + + # Field definitions with display names + field_definitions = { + 'destinataer_name': 'Destinatär', + 'jahr': 'Jahr', + 'betrag': 'Betrag (€)', + 'kategorie': 'Kategorie', + 'status': 'Status', + 'antragsdatum': 'Antragsdatum', + 'bewilligungsdatum': 'Bewilligungsdatum', + 'auszahlungsdatum': 'Auszahlungsdatum', + 'beschreibung': 'Beschreibung', + 'begruendung': 'Begründung', + 'verwendungsnachweis_status': 'Verwendungsnachweis', + } + + # Filter to only include fields that are both selected and defined + filtered_fields = {k: v for k, v in field_definitions.items() if k in selected_fields} + + # Prepare data with field extraction logic + data_for_pdf = [] + for item in queryset: + row_data = {} + for field_key in filtered_fields.keys(): + try: + if field_key == 'destinataer_name': + row_data[field_key] = item.destinataer.get_full_name() if item.destinataer else '' + elif field_key == 'jahr': + row_data[field_key] = str(item.jahr) + elif field_key == 'betrag': + row_data[field_key] = f"€{item.betrag:.2f}" if item.betrag else '' + elif field_key == 'kategorie': + row_data[field_key] = item.get_kategorie_display() + elif field_key == 'status': + row_data[field_key] = item.get_status_display() + elif field_key == 'antragsdatum': + row_data[field_key] = item.antragsdatum.strftime('%d.%m.%Y') if item.antragsdatum else '' + elif field_key == 'bewilligungsdatum': + row_data[field_key] = item.bewilligungsdatum.strftime('%d.%m.%Y') if item.bewilligungsdatum else '' + elif field_key == 'auszahlungsdatum': + row_data[field_key] = item.auszahlungsdatum.strftime('%d.%m.%Y') if item.auszahlungsdatum else '' + elif field_key == 'beschreibung': + row_data[field_key] = (item.beschreibung or '')[:100] + ('...' if len(item.beschreibung or '') > 100 else '') + elif field_key == 'begruendung': + row_data[field_key] = (item.begruendung or '')[:100] + ('...' if len(item.begruendung or '') > 100 else '') + elif field_key == 'verwendungsnachweis_status': + row_data[field_key] = item.get_verwendungsnachweis_status_display() if item.verwendungsnachweis_status else '' + else: + # Generic field access + row_data[field_key] = getattr(item, field_key, '') or '' + except Exception: + row_data[field_key] = '' # Fallback for any errors + + data_for_pdf.append(row_data) + + # Use PDF generator + pdf_gen = get_pdf_generator() + return pdf_gen.export_data_list_pdf( + data=data_for_pdf, + fields_config=filtered_fields, + title="Förderungen Export", + filename_prefix="foerderungen", + request_user=request.user + ) + + @login_required def unterstuetzung_edit(request, pk): obj = get_object_or_404(DestinataerUnterstuetzung, pk=pk) @@ -3498,11 +3976,46 @@ def unterstuetzung_edit(request, pk): @login_required def unterstuetzung_delete(request, pk): obj = get_object_or_404(DestinataerUnterstuetzung, pk=pk) + + # Check if this will also delete the recurring template + will_delete_template = False + if obj.wiederkehrend_von: + andere_zahlungen = DestinataerUnterstuetzung.objects.filter( + wiederkehrend_von=obj.wiederkehrend_von + ).exclude(pk=pk).exists() + will_delete_template = not andere_zahlungen + if request.method == 'POST': + # Check if this support payment is linked to a recurring payment template + wiederkehrend_template = obj.wiederkehrend_von + + # Delete the support payment obj.delete() - messages.success(request, 'Unterstützung gelöscht.') + + # If this was generated from a recurring template and there are no other + # payments from this template, delete the template too + if wiederkehrend_template: + # Check if there are other payments from this recurring template + andere_zahlungen = DestinataerUnterstuetzung.objects.filter( + wiederkehrend_von=wiederkehrend_template + ).exists() + + # If no other payments exist from this template, delete the template too + if not andere_zahlungen: + wiederkehrend_template.delete() + messages.success(request, 'Unterstützung und wiederkehrende Zahlungsvorlage gelöscht.') + else: + messages.success(request, 'Unterstützung gelöscht.') + else: + messages.success(request, 'Unterstützung gelöscht.') + return redirect('stiftung:unterstuetzungen_list') - return render(request, 'stiftung/unterstuetzung_confirm_delete.html', {'obj': obj}) + + context = { + 'obj': obj, + 'will_delete_template': will_delete_template, + } + return render(request, 'stiftung/unterstuetzung_confirm_delete.html', context) @login_required @@ -4895,3 +5408,343 @@ def land_verpachtung_edit(request, land_pk): } return render(request, 'stiftung/land_verpachtung_form.html', context) + + +# Settings Management Views +@login_required +def app_settings(request): + """Application settings management interface""" + + # Group settings by category + categories = {} + for setting in AppConfiguration.objects.filter(is_active=True).order_by('category', 'order', 'display_name'): + if setting.category not in categories: + categories[setting.category] = [] + categories[setting.category].append(setting) + + if request.method == 'POST': + # Handle form submission + updated_count = 0 + for key, value in request.POST.items(): + if key.startswith('setting_'): + setting_key = key.replace('setting_', '') + try: + setting = AppConfiguration.objects.get(key=setting_key, is_active=True) + if not setting.is_system and setting.value != value: + setting.value = value + setting.save() + updated_count += 1 + except AppConfiguration.DoesNotExist: + continue + + if updated_count > 0: + messages.success(request, f'Successfully updated {updated_count} settings!') + else: + messages.info(request, 'No changes were made.') + + return redirect('stiftung:app_settings') + + context = { + 'categories': categories, + 'title': 'Application Settings', + } + return render(request, 'stiftung/app_settings.html', context) + + +# Unterstützungen Views (Destinataer-focused) +@login_required +def unterstuetzungen_all(request): + """List all support payments - destinataer-focused view""" + status = request.GET.get('status') + destinataer_id = request.GET.get('destinataer') + export = request.GET.get('format', '') + selected_ids = request.POST.getlist('selected_entries') if request.method == 'POST' else [] + + unterstuetzungen = DestinataerUnterstuetzung.objects.select_related( + 'destinataer', 'konto', 'ausgezahlt_von', 'wiederkehrend_von' + ).order_by('-faellig_am') + + # Filtering + if status: + unterstuetzungen = unterstuetzungen.filter(status=status) + if destinataer_id: + unterstuetzungen = unterstuetzungen.filter(destinataer_id=destinataer_id) + + # Enhanced CSV export with field selection + if export == 'csv': + return export_unterstuetzungen_csv(request, unterstuetzungen, selected_ids) + + # PDF export (simple table via WeasyPrint; graceful fallback if missing) + if export == 'pdf': + try: + from django.template.loader import render_to_string + from weasyprint import HTML + html = render_to_string('stiftung/unterstuetzungen_pdf.html', {'unterstuetzungen': unterstuetzungen}) + from django.http import HttpResponse + pdf = HTML(string=html).write_pdf() + resp = HttpResponse(pdf, content_type='application/pdf') + resp['Content-Disposition'] = 'inline; filename=unterstuetzungen.pdf' + return resp + except Exception: + pass + + # Statistics + total_betrag = unterstuetzungen.aggregate(total=Sum('betrag'))['total'] or 0 + avg_betrag = unterstuetzungen.aggregate(avg=Avg('betrag'))['avg'] or 0 + + # Available destinataer for filter + destinataer = Destinataer.objects.all().order_by('nachname', 'vorname') + + context = { + 'page_obj': unterstuetzungen, # Use directly for now (pagination can be added later) + 'unterstuetzungen': unterstuetzungen, + 'title': 'Alle Unterstützungen', + 'status_filter': status, + 'total_betrag': total_betrag, + 'avg_betrag': avg_betrag, + 'status_choices': DestinataerUnterstuetzung.STATUS_CHOICES, + 'destinataer': destinataer, + } + return render(request, 'stiftung/unterstuetzungen_all.html', context) + + +@login_required +def unterstuetzung_create(request): + """Create a new support payment""" + # Get destinataer from URL parameter if provided + destinataer_id = request.GET.get('destinataer') + initial = {} + if destinataer_id: + initial['destinataer'] = destinataer_id + # Pre-populate IBAN and name if destinataer is specified + try: + destinataer = Destinataer.objects.get(pk=destinataer_id) + if hasattr(destinataer, 'iban') and destinataer.iban: + initial['empfaenger_iban'] = destinataer.iban + initial['empfaenger_name'] = destinataer.get_full_name() + except Destinataer.DoesNotExist: + pass + + if request.method == 'POST': + form = UnterstuetzungForm(request.POST) + if form.is_valid(): + ist_wiederkehrend = form.cleaned_data.get('ist_wiederkehrend', False) + + if ist_wiederkehrend: + # Create recurring payment template + wiederkehrend = UnterstuetzungWiederkehrend.objects.create( + destinataer=form.cleaned_data['destinataer'], + konto=form.cleaned_data['konto'], + betrag=form.cleaned_data['betrag'], + intervall=form.cleaned_data['intervall'], + beschreibung=form.cleaned_data['beschreibung'], + empfaenger_iban=form.cleaned_data['empfaenger_iban'], + empfaenger_name=form.cleaned_data['empfaenger_name'], + verwendungszweck=form.cleaned_data['verwendungszweck'], + erste_zahlung_am=form.cleaned_data['faellig_am'], + letzte_zahlung_am=form.cleaned_data.get('letzte_zahlung_am'), + naechste_generierung=form.cleaned_data['faellig_am'], + erstellt_von=request.user + ) + + # Create the first payment + unterstuetzung = form.save(commit=False) + unterstuetzung.wiederkehrend_von = wiederkehrend + unterstuetzung.save() + + messages.success(request, f'Wiederkehrende Unterstützung für {unterstuetzung.destinataer} wurde erfolgreich erstellt. Die erste Zahlung ist am {unterstuetzung.faellig_am} fällig.') + else: + # Create single payment + unterstuetzung = form.save() + messages.success(request, f'Unterstützung für {unterstuetzung.destinataer} wurde erfolgreich erstellt.') + + return redirect('stiftung:unterstuetzung_detail', pk=unterstuetzung.pk) + else: + form = UnterstuetzungForm(initial=initial) + + context = { + 'form': form, + 'title': 'Neue Unterstützung erstellen', + } + return render(request, 'stiftung/unterstuetzung_form.html', context) + + +@login_required +def get_destinataer_info(request, destinataer_id): + """AJAX endpoint to get Destinataer IBAN and name information""" + try: + destinataer = Destinataer.objects.get(pk=destinataer_id) + data = { + 'success': True, + 'name': destinataer.get_full_name(), + 'iban': getattr(destinataer, 'iban', '') or '', + } + except Destinataer.DoesNotExist: + data = { + 'success': False, + 'error': 'Destinataer not found' + } + + return JsonResponse(data) + + +@login_required +def unterstuetzung_detail(request, pk): + """View support payment details""" + unterstuetzung = get_object_or_404(DestinataerUnterstuetzung, pk=pk) + + # Check if this payment can be marked as paid + can_mark_paid = unterstuetzung.can_be_marked_paid() + + context = { + 'unterstuetzung': unterstuetzung, + 'title': f'Unterstützung für {unterstuetzung.destinataer.get_full_name()}', + 'can_mark_paid': can_mark_paid, + } + return render(request, 'stiftung/unterstuetzung_detail.html', context) + + +@login_required +def unterstuetzung_mark_paid(request, pk): + """Mark a support payment as paid""" + unterstuetzung = get_object_or_404(DestinataerUnterstuetzung, pk=pk) + + if not unterstuetzung.can_be_marked_paid(): + messages.error(request, 'Diese Unterstützung kann nicht als bezahlt markiert werden.') + return redirect('stiftung:unterstuetzung_detail', pk=pk) + + if request.method == 'POST': + form = UnterstuetzungMarkAsPaidForm(request.POST) + if form.is_valid(): + unterstuetzung.status = 'ausgezahlt' + unterstuetzung.ausgezahlt_am = form.cleaned_data['ausgezahlt_am'] + unterstuetzung.ausgezahlt_von = request.user + + # Add optional note to description + bemerkung = form.cleaned_data.get('bemerkung') + if bemerkung: + if unterstuetzung.beschreibung: + unterstuetzung.beschreibung += f' | Zahlung: {bemerkung}' + else: + unterstuetzung.beschreibung = f'Zahlung: {bemerkung}' + + unterstuetzung.save() + messages.success(request, f'Unterstützung wurde als bezahlt markiert.') + return redirect('stiftung:unterstuetzung_detail', pk=pk) + else: + form = UnterstuetzungMarkAsPaidForm() + + context = { + 'form': form, + 'unterstuetzung': unterstuetzung, + 'title': f'Zahlung markieren - {unterstuetzung.destinataer.get_full_name()}', + } + return render(request, 'stiftung/unterstuetzung_mark_paid.html', context) + + +@login_required +def wiederkehrende_unterstuetzungen(request): + """List all recurring support payment templates""" + from django.db.models import Count + + # Check for cleanup request + if request.GET.get('cleanup') == '1': + # Find templates with no associated payments + verwaiste_templates = UnterstuetzungWiederkehrend.objects.annotate( + zahlung_count=Count('destinataerunterstuetzung') + ).filter(zahlung_count=0) + + if verwaiste_templates.exists(): + anzahl_geloescht = verwaiste_templates.count() + template_namen = list(verwaiste_templates.values_list('destinataer__nachname', flat=True)) + verwaiste_templates.delete() + messages.success( + request, + f'{anzahl_geloescht} verwaiste Zahlungsvorlagen bereinigt: {", ".join(template_namen[:5])}{"..." if len(template_namen) > 5 else ""}' + ) + else: + messages.info(request, 'Keine verwaisten Zahlungsvorlagen gefunden.') + + return redirect('stiftung:wiederkehrende_unterstuetzungen') + + # Get all templates with payment counts + templates = UnterstuetzungWiederkehrend.objects.select_related( + 'destinataer', 'konto' + ).annotate( + aktive_zahlungen=Count('destinataerunterstuetzung') + ).all() + + context = { + 'templates': templates, + 'title': 'Wiederkehrende Unterstützungen', + } + return render(request, 'stiftung/wiederkehrende_unterstuetzungen.html', context) + + +@login_required +def edit_help_box(request): + """Bearbeite oder erstelle eine Hilfs-Infobox""" + from .models import HelpBox + + # Nur root oder Superuser dürfen bearbeiten + if request.user.username != 'root' and not request.user.is_superuser: + messages.error(request, 'Sie haben keine Berechtigung, Hilfsboxen zu bearbeiten.') + return redirect('stiftung:dashboard') + + if request.method == 'POST': + page_key = request.POST.get('page_key') + title = request.POST.get('title') + content = request.POST.get('content') + is_active = request.POST.get('is_active') == 'on' + + if not page_key or not title or not content: + messages.error(request, 'Alle Felder sind erforderlich.') + return redirect(request.META.get('HTTP_REFERER', 'stiftung:dashboard')) + + # Hilfsbox erstellen oder aktualisieren + help_box, created = HelpBox.objects.get_or_create( + page_key=page_key, + defaults={ + 'title': title, + 'content': content, + 'is_active': is_active, + 'created_by': request.user.username, + 'updated_by': request.user.username, + } + ) + + if not created: + # Existierende Hilfsbox aktualisieren + help_box.title = title + help_box.content = content + help_box.is_active = is_active + help_box.updated_by = request.user.username + help_box.save() + + messages.success(request, f'Hilfsbox "{title}" wurde aktualisiert.') + else: + messages.success(request, f'Hilfsbox "{title}" wurde erstellt.') + + # Zurück zur vorherigen Seite + return redirect(request.META.get('HTTP_REFERER', 'stiftung:dashboard')) + + # GET Request - Zeige Admin-Übersicht der Hilfsboxen + help_boxes = HelpBox.objects.all().order_by('page_key', '-updated_at') + + # Statistiken berechnen + active_count = help_boxes.filter(is_active=True).count() + inactive_count = help_boxes.filter(is_active=False).count() + existing_pages = set(help_boxes.values_list('page_key', flat=True)) + + # Verfügbare Seiten aus dem Model holen + available_pages = HelpBox.PAGE_CHOICES + + context = { + 'help_boxes': help_boxes, + 'active_count': active_count, + 'inactive_count': inactive_count, + 'existing_pages': existing_pages, + 'available_pages': available_pages, + 'title': 'Hilfs-Infoboxen verwalten', + } + return render(request, 'stiftung/help_boxes_admin.html', context) diff --git a/app/templates/base.html b/app/templates/base.html index 4bdcf90..850001a 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -292,10 +292,32 @@ Dashboard -| {{ field_display }} | + {% endfor %} +
|---|
| + {% if field_key == 'betrag' or field_key == 'pachtzins' or 'betrag' in field_key %} + + {% if item|lookup:field_key %}€{{ item|lookup:field_key|floatformat:2 }}{% else %}-{% endif %} + + {% elif field_key == 'status' %} + {% with status_value=item|lookup:field_key %} + {% if status_value %} + {{ status_value|capfirst }} + {% else %}-{% endif %} + {% endwith %} + {% elif field_key|slice:"-5:" == '_date' or field_key|slice:"-6:" == '_datum' or 'datum' in field_key or 'date' in field_key %} + {% with date_value=item|lookup:field_key %} + {% if date_value %}{{ date_value|date:"d.m.Y" }}{% else %}-{% endif %} + {% endwith %} + {% else %} + {% with field_value=item|lookup:field_key %} + {{ field_value|default:"-"|truncatechars:50 }} + {% endwith %} + {% endif %} + | + {% endfor %} +
Keine Daten zum Anzeigen verfügbar.
+| Fällig am | +Betrag | +Status | +Beschreibung | +Aktionen | +
|---|---|---|---|---|
| {{ unterstuetzung.faellig_am|date:"d.m.Y" }} | +€{{ unterstuetzung.betrag|floatformat:2 }} | ++ {% if unterstuetzung.status == 'ausgezahlt' %} + Ausgezahlt + {% elif unterstuetzung.status == 'in_bearbeitung' %} + In Bearbeitung + {% elif unterstuetzung.status == 'geplant' %} + Geplant + {% else %} + {{ unterstuetzung.get_status_display }} + {% endif %} + | +{{ unterstuetzung.beschreibung|truncatechars:40 }} | ++ + + + | +
Voraussetzungen für Unterstützung
-Die Stiftung kann grundsätzlich laufende Leistungen nur an Abkömmlinge der Geschwister des Stifters Hendrik van Hees und seiner Ehefrau oder im Einzelfall an weitere Personen erbringen, die als Alleinstehende(r) oder Haushaltsvorstand keine höheren Bezüge als 2.245,00 € (5× Regelsatz 563,00 €) haben und deren Vermögen 15.500 € nicht übersteigt (§53 AO). Die Sätze erhöhen sich bei weiteren Haushaltsangehörigen.
-Dieser Text ist redaktionell anpassbar (Template).
-- Sind Sie sicher, dass Sie die Förderung für {{ foerderung.person.get_full_name }} + Sind Sie sicher, dass Sie die Förderung für + + {% if foerderung.destinataer %} + {{ foerderung.destinataer.get_full_name }} + {% elif foerderung.person %} + {{ foerderung.person.get_full_name }} (Legacy) + {% else %} + Unbekannter Empfänger + {% endif %} + ({{ foerderung.jahr }}, €{{ foerderung.betrag|floatformat:2 }}) löschen möchten?
diff --git a/app/templates/stiftung/foerderung_detail.html b/app/templates/stiftung/foerderung_detail.html index db95b33..489f17f 100644 --- a/app/templates/stiftung/foerderung_detail.html +++ b/app/templates/stiftung/foerderung_detail.html @@ -26,9 +26,13 @@+ {% if foerderung.destinataer %} - {{ foerderung.person.get_full_name }} + {{ foerderung.destinataer.get_full_name }} + {% else %} + Keine Person zugeordnet + {% endif %}
| + + | Person | Jahr | Betrag | @@ -120,9 +285,16 @@ {% for foerderung in page_obj %}||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| + + | ++ {% if foerderung.destinataer and foerderung.destinataer.pk %} - {{ foerderung.person.get_full_name }} + {{ foerderung.destinataer.get_full_name }} + {% else %} + Keine Person zugeordnet + {% endif %} | {{ foerderung.jahr }} | €{{ foerderung.betrag|floatformat:2 }} | @@ -200,4 +372,80 @@ + + {% endblock %} diff --git a/app/templates/stiftung/help_box.html b/app/templates/stiftung/help_box.html new file mode 100644 index 0000000..2ce0213 --- /dev/null +++ b/app/templates/stiftung/help_box.html @@ -0,0 +1,147 @@ +{% load static %} +{% if help_obj %} +
| Seite | +Titel | +Status | +Zuletzt geändert | +Geändert von | +Aktionen | +
|---|---|---|---|---|---|
| + {{ help_box.get_page_key_display }} + | +
+ {{ help_box.title }}
+ + {{ help_box.content|truncatechars:80 }} + |
+ + {% if help_box.is_active %} + Aktiv + {% else %} + Inaktiv + {% endif %} + | ++ {{ help_box.updated_at|date:"d.m.Y H:i" }} + | ++ {{ help_box.updated_by|default:"-" }} + | ++ + + + + + + | +
**fett** → fett*kursiv* → kursiv`code` → code[Link](url) → Link- Liste → Aufzählung1. Nummer → NummeriertOrganisieren Sie Pachtverträge und deren Verwaltung effizient.
- + Öffnen @@ -143,7 +143,7 @@ Pächter - + Verpachtungen diff --git a/app/templates/stiftung/land_detail.html b/app/templates/stiftung/land_detail.html index 489888b..54d6f84 100644 --- a/app/templates/stiftung/land_detail.html +++ b/app/templates/stiftung/land_detail.html @@ -391,6 +391,7 @@| - | @@ -734,6 +762,58 @@ + {% elif land.aktueller_paechter %} + +
| Pächter | +Zeitraum | +Fläche | +Pachtzins | +Status | +Aktionen | +
|---|---|---|---|---|---|
| + + {{ land.aktueller_paechter.get_full_name }} + + | ++ {% if land.pachtbeginn %}{{ land.pachtbeginn|date:"d.m.Y" }}{% else %}N/A{% endif %} - + {% if land.pachtende %}{{ land.pachtende|date:"d.m.Y" }}{% else %}Unbefristet{% endif %} + | ++ {{ land.verp_flaeche_aktuell|default_if_none:land.groesse_qm|floatformat:0 }} qm + | ++ {% if land.pachtzins_aktuell %}€{{ land.pachtzins_aktuell|floatformat:2 }}/Jahr{% else %}N/A{% endif %} + | ++ Legacy System + | ++ + | +
| Titel | +Kontext | +Aktionen | +
|---|---|---|
|
+ {{ doc.titel|default:"Ohne Titel" }}
+ + Paperless-ID: {{ doc.paperless_document_id }} + |
+ {{ doc.get_kontext_display }} | ++ + + + | +
Keine Dokumente verknüpft.
+ {% endif %} +{{ landverpachtung.bemerkungen|linebreaks }}
+- Sind Sie sicher, dass Sie die Verpachtung {{ verpachtung.vertragsnummer }} löschen möchten? + Sind Sie sicher, dass Sie die Verpachtung {{ verpachtung.vertragsnummer }} beenden möchten?
- Länderei: {{ verpachtung.land.gemeinde }} - {{ verpachtung.land.gemarkung }}
- Pächter: {{ verpachtung.paechter.get_full_name }}
- Zeitraum: {{ verpachtung.pachtbeginn|date:"d.m.Y" }} - {{ verpachtung.pachtende|date:"d.m.Y" }}
- Fläche: {{ verpachtung.verpachtete_flaeche|floatformat:2 }} qm
- Jährlicher Pachtzins: €{{ verpachtung.pachtzins_jaehrlich|floatformat:2 }}
+ Vertragsnummer: {{ verpachtung.vertragsnummer }}
+ Pächter: {{ verpachtung.paechter }}
+ Länderei: {{ verpachtung.land }}
+ Pachtbeginn: {{ verpachtung.pachtbeginn|date:"d.m.Y" }}
+ Verpachtete Fläche: {{ verpachtung.verpachtete_flaeche_qm|floatformat:2 }} qm
+ Pachtzins: {{ verpachtung.pachtzins_euro_pro_qm|floatformat:4 }} €/qm
- Diese Aktion kann nicht rückgängig gemacht werden. Alle zugehörigen Daten werden permanent gelöscht. + Die Verpachtung wird auf "beendet" gesetzt und das Pachtende auf das heutige Datum gesetzt. Diese Aktion kann rückgängig gemacht werden, indem der Status und das Pachtende manuell geändert werden.
Länderei: {{ land }} {% if is_edit and land.aktueller_paechter %} | Aktueller Pächter: {{ land.aktueller_paechter.get_full_name }}{% endif %} + {% if is_update %} | Bearbeitung von {{ landverpachtung.vertragsnummer }}{% endif %}
{{ obj.destinataer.get_full_name }}, €{{ obj.betrag|floatformat:2 }}, fällig am {{ obj.faellig_am|date:"d.m.Y" }}
- -+ Sind Sie sicher, dass Sie die Unterstützung für {{ obj.destinataer.get_full_name }} + (€{{ obj.betrag|floatformat:2 }}, fällig am {{ obj.faellig_am|date:"d.m.Y" }}) löschen möchten? +
++ Diese Aktion kann nicht rückgängig gemacht werden. Alle zugehörigen Daten werden permanent gelöscht. +
+ + +€{{ unterstuetzung.betrag|floatformat:2 }}
+{{ unterstuetzung.faellig_am|date:"d.m.Y" }}
+{{ unterstuetzung.konto }}
+{{ unterstuetzung.get_status_display }}
+{{ unterstuetzung.empfaenger_iban|default:"Nicht angegeben" }}
+{{ unterstuetzung.empfaenger_name|default:"Nicht angegeben" }}
+{{ unterstuetzung.verwendungszweck }}
+{{ unterstuetzung.beschreibung }}
+Diese Zahlung wurde automatisch aus einer wiederkehrenden Vorlage generiert.
+{{ unterstuetzung.erstellt_am|date:"d.m.Y H:i" }}
+{{ unterstuetzung.aktualisiert_am|date:"d.m.Y H:i" }}
+