feat: Add complete Verpachtung management system
- Add LandVerpachtung model with Land and Paechter relationships - Implement full CRUD operations for Verpachtungen - Add responsive Bootstrap templates with JavaScript calculations - Integrate document linking functionality similar to other entities - Add navigation links and URL patterns - Include CSV import support for Paechter data - Fix template encoding issues for proper UTF-8 support - Enhance administration interface with Verpachtung CSV import This implements the complete Verpachtung management feature requested, allowing users to manage land lease agreements with proper relationships to properties (Laenderei) and tenants (Paechter), following the same patterns as Foerderung/Destinataer relationships.
This commit is contained in:
@@ -6,7 +6,7 @@ from django.utils import timezone
|
||||
|
||||
from .models import (BankTransaction, Destinataer, DestinataerNotiz,
|
||||
DestinataerUnterstuetzung, DokumentLink, Foerderung, Land,
|
||||
LandAbrechnung, Paechter, Person, Rentmeister,
|
||||
LandAbrechnung, LandVerpachtung, Paechter, Person, Rentmeister,
|
||||
StiftungsKonto, UnterstuetzungWiederkehrend,
|
||||
Verwaltungskosten)
|
||||
|
||||
@@ -534,6 +534,53 @@ class LandForm(forms.ModelForm):
|
||||
}
|
||||
|
||||
|
||||
class LandVerpachtungForm(forms.ModelForm):
|
||||
"""Form für das Erstellen und Bearbeiten von Verpachtungen"""
|
||||
|
||||
class Meta:
|
||||
model = LandVerpachtung
|
||||
fields = [
|
||||
'land',
|
||||
'paechter',
|
||||
'vertragsnummer',
|
||||
'pachtbeginn',
|
||||
'pachtende',
|
||||
'verlaengerung_klausel',
|
||||
'verpachtete_flaeche',
|
||||
'pachtzins_pauschal',
|
||||
'pachtzins_pro_ha',
|
||||
'zahlungsweise',
|
||||
'ust_option',
|
||||
'ust_satz',
|
||||
'grundsteuer_umlage',
|
||||
'versicherungen_umlage',
|
||||
'verbandsbeitraege_umlage',
|
||||
'jagdpacht_anteil_umlage',
|
||||
'status',
|
||||
'bemerkungen'
|
||||
]
|
||||
widgets = {
|
||||
'land': forms.Select(attrs={'class': 'form-select'}),
|
||||
'paechter': forms.Select(attrs={'class': 'form-select'}),
|
||||
'vertragsnummer': forms.TextInput(attrs={'class': 'form-control'}),
|
||||
'pachtbeginn': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||
'pachtende': forms.DateInput(attrs={'class': 'form-control', 'type': 'date'}),
|
||||
'verlaengerung_klausel': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'verpachtete_flaeche': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
||||
'pachtzins_pauschal': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
||||
'pachtzins_pro_ha': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
||||
'zahlungsweise': forms.Select(attrs={'class': 'form-select'}),
|
||||
'ust_option': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'ust_satz': forms.NumberInput(attrs={'class': 'form-control', 'step': '0.01'}),
|
||||
'grundsteuer_umlage': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'versicherungen_umlage': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'verbandsbeitraege_umlage': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'jagdpacht_anteil_umlage': forms.CheckboxInput(attrs={'class': 'form-check-input'}),
|
||||
'status': forms.Select(attrs={'class': 'form-select'}),
|
||||
'bemerkungen': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
|
||||
}
|
||||
|
||||
|
||||
class LandAbrechnungForm(forms.ModelForm):
|
||||
"""Form für das Erstellen und Bearbeiten von Landabrechnungen"""
|
||||
|
||||
|
||||
@@ -109,6 +109,20 @@ urlpatterns = [
|
||||
views.land_verpachtung_end_direct,
|
||||
name="land_verpachtung_end_direct",
|
||||
),
|
||||
# Verpachtung URLs (Management Overview)
|
||||
path("verpachtungen/", views.verpachtung_list, name="verpachtung_list"),
|
||||
path("verpachtungen/<uuid:pk>/", views.verpachtung_detail, name="verpachtung_detail"),
|
||||
path("verpachtungen/neu/", views.verpachtung_create, name="verpachtung_create"),
|
||||
path(
|
||||
"verpachtungen/<uuid:pk>/bearbeiten/",
|
||||
views.verpachtung_update,
|
||||
name="verpachtung_update",
|
||||
),
|
||||
path(
|
||||
"verpachtungen/<uuid:pk>/loeschen/",
|
||||
views.verpachtung_delete,
|
||||
name="verpachtung_delete",
|
||||
),
|
||||
# Förderung URLs
|
||||
path("foerderungen/", views.foerderung_list, name="foerderung_list"),
|
||||
path("foerderungen/<uuid:pk>/", views.foerderung_detail, name="foerderung_detail"),
|
||||
|
||||
@@ -1689,7 +1689,7 @@ def verpachtung_list(request):
|
||||
"beginn": ["pachtbeginn"],
|
||||
"ende": ["pachtende"],
|
||||
"flaeche": ["verpachtete_flaeche"],
|
||||
"pachtzins": ["pachtzins_jaehrlich"],
|
||||
"pachtzins": ["pachtzins_pauschal"],
|
||||
"status": ["status"],
|
||||
}
|
||||
if sort in sort_map:
|
||||
@@ -1723,7 +1723,7 @@ def verpachtung_list(request):
|
||||
|
||||
# Total annual rent (only active verpachtungen)
|
||||
jaehrlicher_pachtzins_result = all_verpachtungen.filter(status="aktiv").aggregate(
|
||||
total=Sum("pachtzins_jaehrlich")
|
||||
total=Sum("pachtzins_pauschal")
|
||||
)
|
||||
jaehrlicher_pachtzins = (
|
||||
jaehrlicher_pachtzins_result["total"]
|
||||
@@ -1787,7 +1787,7 @@ def land_verpachtung_update(request, pk):
|
||||
vertragsnummer = request.POST.get("vertragsnummer")
|
||||
pachtbeginn = request.POST.get("pachtbeginn")
|
||||
pachtende = request.POST.get("pachtende")
|
||||
pachtzins_jaehrlich = request.POST.get("pachtzins_jaehrlich")
|
||||
pachtzins_pauschal = request.POST.get("pachtzins_pauschal")
|
||||
|
||||
if vertragsnummer:
|
||||
verpachtung.vertragsnummer = vertragsnummer
|
||||
@@ -1795,8 +1795,8 @@ def land_verpachtung_update(request, pk):
|
||||
verpachtung.pachtbeginn = pachtbeginn
|
||||
if pachtende:
|
||||
verpachtung.pachtende = pachtende
|
||||
if pachtzins_jaehrlich:
|
||||
verpachtung.pachtzins_jaehrlich = pachtzins_jaehrlich
|
||||
if pachtzins_pauschal:
|
||||
verpachtung.pachtzins_pauschal = pachtzins_pauschal
|
||||
|
||||
verpachtung.save()
|
||||
messages.success(request, "Verpachtung wurde erfolgreich aktualisiert.")
|
||||
@@ -2130,7 +2130,11 @@ def dokument_create(request):
|
||||
)
|
||||
|
||||
# Zurück zur verknüpften Entität leiten
|
||||
if dokument.verpachtung_id:
|
||||
if dokument.land_verpachtung_id:
|
||||
return redirect(
|
||||
"stiftung:verpachtung_detail", pk=dokument.land_verpachtung_id
|
||||
)
|
||||
elif dokument.verpachtung_id:
|
||||
return redirect(
|
||||
"stiftung:verpachtung_detail", pk=dokument.verpachtung_id
|
||||
)
|
||||
@@ -2149,6 +2153,8 @@ def dokument_create(request):
|
||||
else:
|
||||
# Initial-Werte aus GET-Parametern setzen
|
||||
initial_data = {}
|
||||
if request.GET.get("land_verpachtung_id"):
|
||||
initial_data["land_verpachtung_id"] = request.GET.get("land_verpachtung_id")
|
||||
if request.GET.get("verpachtung"):
|
||||
initial_data["verpachtung_id"] = request.GET.get("verpachtung")
|
||||
if request.GET.get("land"):
|
||||
@@ -5435,7 +5441,7 @@ def verpachtung_export(request, pk):
|
||||
else None
|
||||
),
|
||||
"pachtzins_pro_qm": str(verpachtung.pachtzins_pro_qm),
|
||||
"pachtzins_jaehrlich": str(verpachtung.pachtzins_jaehrlich),
|
||||
"pachtzins_jaehrlich": str(verpachtung.pachtzins_pauschal),
|
||||
"verpachtete_flaeche": str(verpachtung.verpachtete_flaeche),
|
||||
"status": verpachtung.get_status_display(),
|
||||
"verwendungsnachweis": (
|
||||
@@ -6914,3 +6920,126 @@ def edit_help_box(request):
|
||||
"title": "Hilfs-Infoboxen verwalten",
|
||||
}
|
||||
return render(request, "stiftung/help_boxes_admin.html", context)
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Verpachtung Management Views (Standalone CRUD)
|
||||
# =============================================================================
|
||||
|
||||
@login_required
|
||||
def verpachtung_detail(request, pk):
|
||||
"""Standalone detail view for verpachtung"""
|
||||
verpachtung = get_object_or_404(LandVerpachtung, pk=pk)
|
||||
|
||||
# Alle mit dieser Verpachtung verknüpften Dokumente laden
|
||||
verknuepfte_dokumente = DokumentLink.objects.filter(
|
||||
land_verpachtung_id=verpachtung.pk
|
||||
).order_by("kontext", "titel")
|
||||
|
||||
context = {
|
||||
"verpachtung": verpachtung,
|
||||
"landverpachtung": verpachtung, # Template compatibility
|
||||
"verknuepfte_dokumente": verknuepfte_dokumente,
|
||||
"title": f"Verpachtung {verpachtung.vertragsnummer}",
|
||||
}
|
||||
return render(request, "stiftung/verpachtung_detail.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def verpachtung_create(request):
|
||||
"""Standalone create view for verpachtung"""
|
||||
from .forms import LandVerpachtungForm
|
||||
from datetime import datetime as dt
|
||||
|
||||
if request.method == 'POST':
|
||||
form = LandVerpachtungForm(request.POST, request.FILES)
|
||||
if form.is_valid():
|
||||
verpachtung = form.save()
|
||||
|
||||
# Update the Land model to reflect this verpachtung
|
||||
land = verpachtung.land
|
||||
land.aktueller_paechter = verpachtung.paechter
|
||||
land.paechter_name = verpachtung.paechter.get_full_name()
|
||||
land.paechter_anschrift = f"{verpachtung.paechter.strasse or ''}\n{verpachtung.paechter.plz or ''} {verpachtung.paechter.ort or ''}".strip()
|
||||
land.pachtbeginn = verpachtung.pachtbeginn
|
||||
land.pachtende = verpachtung.pachtende
|
||||
land.pachtzins_pauschal = verpachtung.pachtzins_pauschal
|
||||
land.zahlungsweise = verpachtung.zahlungsweise
|
||||
land.ust_option = verpachtung.ust_option
|
||||
land.verpachtete_gesamtflaeche = verpachtung.verpachtete_flaeche
|
||||
land.verp_flaeche_aktuell = verpachtung.verpachtete_flaeche
|
||||
land.save()
|
||||
|
||||
# Create automatic abrechnung
|
||||
current_year = dt.now().year
|
||||
expected_annual_rent = verpachtung.pachtzins_pauschal if verpachtung.pachtzins_pauschal else 0
|
||||
|
||||
abrechnung, created = LandAbrechnung.objects.get_or_create(
|
||||
land=land,
|
||||
abrechnungsjahr=current_year,
|
||||
defaults={
|
||||
"pacht_vereinnahmt": expected_annual_rent,
|
||||
"umlagen_vereinnahmt": 0,
|
||||
"grundsteuer_betrag": 0,
|
||||
"versicherungen_betrag": 0,
|
||||
},
|
||||
)
|
||||
|
||||
if not created and expected_annual_rent > abrechnung.pacht_vereinnahmt:
|
||||
abrechnung.pacht_vereinnahmt = expected_annual_rent
|
||||
abrechnung.save()
|
||||
|
||||
success_msg = f'Verpachtung "{verpachtung.vertragsnummer}" wurde erfolgreich erstellt.'
|
||||
if created:
|
||||
success_msg += f" Abrechnung für {current_year} wurde automatisch angelegt"
|
||||
if expected_annual_rent > 0:
|
||||
success_msg += f" (Erwartete Jahrespacht: {expected_annual_rent}€)"
|
||||
success_msg += "."
|
||||
elif expected_annual_rent > 0:
|
||||
success_msg += f" Erwartete Jahrespacht in Abrechnung {current_year} wurde aktualisiert ({expected_annual_rent}€)."
|
||||
|
||||
messages.success(request, success_msg)
|
||||
return redirect('stiftung:verpachtung_detail', pk=verpachtung.pk)
|
||||
else:
|
||||
form = LandVerpachtungForm()
|
||||
|
||||
# Get available Länder and Pächter for the template
|
||||
laender_list = Land.objects.all().order_by('lfd_nr')
|
||||
paechter_list = Paechter.objects.filter(aktiv=True).order_by('nachname', 'vorname')
|
||||
|
||||
context = {
|
||||
'form': form,
|
||||
'title': 'Neue Verpachtung erstellen',
|
||||
'laender_list': laender_list,
|
||||
'paechter_list': paechter_list,
|
||||
'current_year': dt.now().year,
|
||||
'is_edit': False,
|
||||
}
|
||||
return render(request, 'stiftung/verpachtung_form.html', context)
|
||||
|
||||
|
||||
@login_required
|
||||
def verpachtung_update(request, pk):
|
||||
"""Standalone update view for verpachtung"""
|
||||
return land_verpachtung_update(request, pk)
|
||||
|
||||
|
||||
@login_required
|
||||
def verpachtung_delete(request, pk):
|
||||
"""Standalone delete view for verpachtung"""
|
||||
verpachtung = get_object_or_404(LandVerpachtung, pk=pk)
|
||||
|
||||
if request.method == 'POST':
|
||||
vertragsnummer = verpachtung.vertragsnummer
|
||||
verpachtung.delete()
|
||||
messages.success(
|
||||
request,
|
||||
f'Verpachtung "{vertragsnummer}" wurde erfolgreich gelöscht.'
|
||||
)
|
||||
return redirect('stiftung:verpachtung_list')
|
||||
|
||||
context = {
|
||||
'verpachtung': verpachtung,
|
||||
'title': f'Verpachtung {verpachtung.vertragsnummer} löschen',
|
||||
}
|
||||
return render(request, 'stiftung/verpachtung_confirm_delete.html', context)
|
||||
|
||||
Reference in New Issue
Block a user