From 4be6be203e9c174f12d0680f75f428dda5cfe435 Mon Sep 17 00:00:00 2001 From: Stiftung Development Date: Mon, 15 Sep 2025 21:18:01 +0200 Subject: [PATCH] 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. --- DEVELOPMENT_WORKFLOW.md | 131 ++++++ README.md | 49 +- app/stiftung/forms.py | 49 +- app/stiftung/urls.py | 14 + app/stiftung/views.py | 143 +++++- app/templates/base.html | 7 + app/templates/stiftung/administration.html | 6 + app/templates/stiftung/dokument_form.html | 214 ++++----- .../stiftung/verpachtung_confirm_delete.html | 81 ++++ .../stiftung/verpachtung_detail.html | 378 ++++++++++++++++ app/templates/stiftung/verpachtung_form.html | 421 ++++++++++++++++++ app/templates/stiftung/verpachtung_list.html | 244 ++++++++++ compose.dev.yml | 82 ++++ compose.yml | 51 ++- 14 files changed, 1743 insertions(+), 127 deletions(-) create mode 100644 DEVELOPMENT_WORKFLOW.md create mode 100644 app/templates/stiftung/verpachtung_confirm_delete.html create mode 100644 app/templates/stiftung/verpachtung_detail.html create mode 100644 app/templates/stiftung/verpachtung_form.html create mode 100644 app/templates/stiftung/verpachtung_list.html create mode 100644 compose.dev.yml diff --git a/DEVELOPMENT_WORKFLOW.md b/DEVELOPMENT_WORKFLOW.md new file mode 100644 index 0000000..1b77f59 --- /dev/null +++ b/DEVELOPMENT_WORKFLOW.md @@ -0,0 +1,131 @@ +# 🚀 Development Workflow Guide + +## 🔄 Complete Development Process + +### Step 1: Make Changes Safely (Local Development) + +```bash +# 1. Start development environment (isolated from production) +docker-compose -f compose.dev.yml up -d + +# 2. Make your code changes in the /app directory +# - Edit Python files, templates, models, etc. +# - Changes automatically reload in development server + +# 3. Test your changes +# - Access: http://localhost:8081 (Django app) +# - Access: http://localhost:8082 (Paperless) +# - Database: localhost:5433 (completely separate from production) + +# 4. Run tests if needed +docker-compose -f compose.dev.yml exec web python manage.py test + +# 5. When done testing, stop development +docker-compose -f compose.dev.yml down +``` + +### Step 2: Commit Changes (Git Version Control) + +```bash +# 1. Stage your changes +git add . + +# 2. Commit with descriptive message +git commit -m "Add new feature: enhanced user management" + +# 3. Push to GitHub (but NOT to main branch yet) +git push origin main +``` + +### Step 3: Deploy to Production (Automated) + +When you push to the `main` branch, GitHub Actions automatically: + +1. **Builds** the Docker images +2. **Runs tests** (if any fail, deployment stops) +3. **Deploys** to production server +4. **Preserves** production database and secrets +5. **Updates** only the code, not the data + +## 🛡️ Environment Separation Details + +### Development Environment (`compose.dev.yml`) +- **Database**: `stiftung_dev` (port 5433) +- **Data**: Completely isolated test data +- **Secrets**: Hardcoded safe values +- **Purpose**: Safe testing without any risk + +### Production Environment (`compose.yml`) +- **Database**: `stiftung` (internal to server) +- **Data**: Real user data (Destinatäre, Förderungen, etc.) +- **Secrets**: Secure tokens from server's .env file +- **Purpose**: Live system for real users + +## 🔒 Safety Guarantees + +### What CAN'T Break Production: +✅ Code changes in development +✅ Database changes in development +✅ Testing new features locally +✅ Experimenting with different configurations + +### What COULD Affect Production: +⚠️ Pushing broken code to main branch +⚠️ Database migration issues (rare) +⚠️ Configuration changes in compose.yml + +## 🧪 Testing New Features + +### For Small Changes: +1. Edit code in `/app` directory +2. Test in development (localhost:8081) +3. If good → commit and push + +### For Database Changes: +1. Create migration: `docker-compose -f compose.dev.yml exec web python manage.py makemigrations` +2. Test migration: `docker-compose -f compose.dev.yml exec web python manage.py migrate` +3. Verify in development environment +4. If good → commit and push (migration will run automatically in production) + +### For Major Features: +1. Create feature branch: `git checkout -b feature/new-feature` +2. Develop and test extensively in development environment +3. Create Pull Request on GitHub +4. Review and merge to main when ready + +## 🚀 Deployment Process + +```mermaid +graph LR + A[Local Changes] --> B[Development Testing] + B --> C[Git Commit] + C --> D[Push to GitHub] + D --> E[GitHub Actions] + E --> F[Automatic Production Deployment] + F --> G[Live on vhtv-stiftung.de] +``` + +## 🆘 Emergency Procedures + +### If Something Breaks in Production: +1. **Rollback**: `git revert ` and push +2. **Quick Fix**: Make fix, test locally, commit and push +3. **Check Logs**: GitHub Actions shows deployment logs + +### If Development Environment Has Issues: +1. **Reset**: `docker-compose -f compose.dev.yml down -v` +2. **Rebuild**: `docker-compose -f compose.dev.yml up -d --build` +3. **Re-migrate**: `docker-compose -f compose.dev.yml exec web python manage.py migrate` + +## 📊 Current Status Check + +```bash +# Check what's running +docker ps + +# Development environment status +docker-compose -f compose.dev.yml ps + +# View logs +docker-compose -f compose.dev.yml logs web +``` \ No newline at end of file diff --git a/README.md b/README.md index 37baa7b..b8255cf 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,32 @@ # Stiftung Application -A comprehensive Django-based foundation management system with modern features and automated deployment. +A## 🏃‍♂️ Quick Start + +### Development Environment + +For local development, use the development Docker Compose configuration: + +```bash +# Start development environment (uses hardcoded dev settings) +docker-compose -f compose.dev.yml up -d + +# Run migrations +docker-compose -f compose.dev.yml exec web python manage.py migrate + +# Create superuser +docker-compose -f compose.dev.yml exec web python manage.py createsuperuser + +# Access applications +# - Django: http://localhost:8081 +# - Paperless: http://localhost:8082 +# - Database: localhost:5433 +``` + +### Production Deployment + +Production deployment uses the main `compose.yml` and is handled automatically via GitHub Actions. The production environment reads configuration from the server's `.env` file (not included in the repository for security). + +## 🏗️ Architecturengo-based foundation management system with modern features and automated deployment. ## 🌟 Features @@ -27,21 +53,30 @@ A comprehensive Django-based foundation management system with modern features a ```bash # Clone repository -git clone https://github.com/yourusername/stiftung-starter.git -cd stiftung-starter +git clone https://github.com/remmerinio/stiftung-management-system.git +cd stiftung-management-system -# Start development environment -docker-compose up -d +# Start development environment (isolated from production) +docker-compose -f compose.dev.yml up -d # Create superuser -docker-compose exec web python manage.py createsuperuser +docker-compose -f compose.dev.yml exec web python manage.py createsuperuser # Access application -open http://localhost:8000 +open http://localhost:8081 # Django app +open http://localhost:8080 # Paperless-ngx (admin/admin123) ``` ### Production Deployment +Production uses automated deployment via GitHub Actions. The production environment has its own `.env` file with secure credentials that is not stored in Git. + +**For Production:** +- Environment variables are configured on the server +- HTTPS is enabled with Let's Encrypt certificates +- Database users are manually configured for security +- Automated deployments preserve manual configurations + See [deployment documentation](deploy-production/MIGRATION_PLAN.md) for complete setup instructions. ## 📁 Project Structure diff --git a/app/stiftung/forms.py b/app/stiftung/forms.py index 9be429d..9bede74 100644 --- a/app/stiftung/forms.py +++ b/app/stiftung/forms.py @@ -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""" diff --git a/app/stiftung/urls.py b/app/stiftung/urls.py index c87a323..645d04f 100644 --- a/app/stiftung/urls.py +++ b/app/stiftung/urls.py @@ -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//", views.verpachtung_detail, name="verpachtung_detail"), + path("verpachtungen/neu/", views.verpachtung_create, name="verpachtung_create"), + path( + "verpachtungen//bearbeiten/", + views.verpachtung_update, + name="verpachtung_update", + ), + path( + "verpachtungen//loeschen/", + views.verpachtung_delete, + name="verpachtung_delete", + ), # Förderung URLs path("foerderungen/", views.foerderung_list, name="foerderung_list"), path("foerderungen//", views.foerderung_detail, name="foerderung_detail"), diff --git a/app/stiftung/views.py b/app/stiftung/views.py index 2106c77..bcc6769 100644 --- a/app/stiftung/views.py +++ b/app/stiftung/views.py @@ -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) diff --git a/app/templates/base.html b/app/templates/base.html index bc925a8..a63f724 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -331,6 +331,13 @@ Neue Länderei
  • +
  • + Alle Verpachtungen +
  • +
  • + Neue Verpachtung +
  • +
  • Abrechnungen
  • diff --git a/app/templates/stiftung/administration.html b/app/templates/stiftung/administration.html index 537db30..b4fd4c9 100644 --- a/app/templates/stiftung/administration.html +++ b/app/templates/stiftung/administration.html @@ -229,6 +229,12 @@ Backup & Restore +
    diff --git a/app/templates/stiftung/dokument_form.html b/app/templates/stiftung/dokument_form.html index 76b3c71..7c0222f 100644 --- a/app/templates/stiftung/dokument_form.html +++ b/app/templates/stiftung/dokument_form.html @@ -1,125 +1,129 @@ -{% extends 'base.html' %} +{% extends 'base.html' %} {% load static %} -{% block title %}{{ title }} - {{ block.super }}{% endblock %} +{% block title %}{{ title }} - Stiftung Management{% endblock %} {% block content %} - -
    -
    -
    -
    -

    - {{ title }} -

    -
    -
    -
    - {% csrf_token %} - -
    -
    - - {{ form.paperless_document_id }} - {% if form.paperless_document_id.errors %} -
    - {{ form.paperless_document_id.errors.0 }} -
    - {% endif %} - - Die Dokument-ID aus Paperless (z.B. aus der URL: /documents/12345/) - -
    - -
    - - {{ form.kontext }} - {% if form.kontext.errors %} -
    - {{ form.kontext.errors.0 }} -
    - {% endif %} +
    +
    +
    +
    +

    + {{ title }} +

    +
    +
    + + {% csrf_token %} + + + {% if form.land_verpachtung_id.value %} +
    + + Verknüpfung: Dieses Dokument wird mit einer Verpachtung verknüpft. +
    + {% elif form.verpachtung_id.value %} +
    + + Verknüpfung: Dieses Dokument wird mit einer Verpachtung (Legacy) verknüpft. +
    + {% elif form.land_id.value %} +
    + + Verknüpfung: Dieses Dokument wird mit einer Länderei verknüpft. +
    + {% elif form.paechter_id.value %} +
    + + Verknüpfung: Dieses Dokument wird mit einem Pächter verknüpft. +
    + {% elif form.destinataer_id.value %} +
    + + Verknüpfung: Dieses Dokument wird mit einem Destinatär verknüpft. +
    + {% elif form.foerderung_id.value %} +
    + + Verknüpfung: Dieses Dokument wird mit einer Förderung verknüpft. +
    + {% endif %} + +
    +
    + + {{ form.paperless_document_id }} + {% if form.paperless_document_id.errors %} +
    + {{ form.paperless_document_id.errors.0 }}
    + {% endif %} + + Die Dokument-ID aus Paperless (z.B. aus der URL: /documents/12345/) +
    -
    -
    + + Zurück zur Liste + + +
    +
    - +
    {% endblock %} {% block extra_css %} @@ -132,4 +136,4 @@ box-shadow: 0 0 0 0.25rem rgba(13, 110, 253, 0.25); } -{% endblock %} +{% endblock %} diff --git a/app/templates/stiftung/verpachtung_confirm_delete.html b/app/templates/stiftung/verpachtung_confirm_delete.html new file mode 100644 index 0000000..b320fab --- /dev/null +++ b/app/templates/stiftung/verpachtung_confirm_delete.html @@ -0,0 +1,81 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %}{{ title }} - Stiftungsverwaltung{% endblock %} + +{% block content %} + +
    +
    +
    +
    +

    + Verpachtung löschen +

    +
    +
    +
    +
    + Warnung! +
    +

    + Sind Sie sicher, dass Sie die Verpachtung + {{ verpachtung.vertragsnummer }} + löschen möchten? +

    +
    + +
    +
    +
    + Verpachtungsdetails +
    +
    +
    +

    Vertragsnummer:
    {{ verpachtung.vertragsnummer }}

    +

    Land:
    {{ verpachtung.land.bezeichnung }}

    +

    Pächter:
    {{ verpachtung.paechter.get_full_name }}

    +
    +
    +

    Pachtzeit:
    + {{ verpachtung.pachtbeginn|date:"d.m.Y" }} + {% if verpachtung.pachtende %} - {{ verpachtung.pachtende|date:"d.m.Y" }}{% else %} (unbefristet){% endif %} +

    +

    Pachtzins:
    €{{ verpachtung.pachtzins_pauschal|floatformat:2 }} / Jahr

    +

    Status:
    {{ verpachtung.get_status_display }}

    +
    +
    +
    +
    + +
    +
    Wichtige Hinweise:
    +
      +
    • Alle verknüpften Dokumente bleiben erhalten
    • +
    • Abrechnungsdaten werden entsprechend aktualisiert
    • +
    • Diese Aktion kann nicht rückgängig gemacht werden
    • +
    • Bitte stellen Sie sicher, dass alle Daten gesichert sind
    • +
    +
    + +

    + Diese Aktion kann nicht rückgängig gemacht werden. Alle zugehörigen Daten werden permanent gelöscht. +

    + +
    + {% csrf_token %} +
    + + Abbrechen + + +
    +
    +
    +
    +
    +
    + +{% endblock %} \ No newline at end of file diff --git a/app/templates/stiftung/verpachtung_detail.html b/app/templates/stiftung/verpachtung_detail.html new file mode 100644 index 0000000..b61e8ef --- /dev/null +++ b/app/templates/stiftung/verpachtung_detail.html @@ -0,0 +1,378 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %}{{ title }} - Stiftungsverwaltung{% endblock %} + +{% block content %} + +
    +
    + +
    +

    + + Verpachtung: {{ verpachtung.vertragsnummer }} +

    + +
    + + +
    +
    + {% if verpachtung.status == 'aktiv' %} + {% if verpachtung.is_aktiv %} + + Aktiv + + {% else %} + + Inaktiv + + {% endif %} + {% elif verpachtung.status == 'beendet' %} + + Beendet + + {% elif verpachtung.status == 'gekuendigt' %} + + Gekündigt + + {% elif verpachtung.status == 'verlaengert' %} + + Verlängert + + {% endif %} + + {% if verpachtung.verlaengerung_klausel %} + + Auto-Verlängerung + + {% endif %} + + {% if verpachtung.ust_option %} + + USt-Option + + {% endif %} +
    +
    + +
    + +
    +
    +
    +
    + Grunddaten +
    +
    +
    +
    +
    +

    Vertragsnummer:
    {{ verpachtung.vertragsnummer }}

    +

    Status:
    {{ verpachtung.get_status_display }}

    +
    +
    +

    Erstellt:
    {{ verpachtung.erstellt_am|date:"d.m.Y H:i" }}

    +

    Aktualisiert:
    {{ verpachtung.aktualisiert_am|date:"d.m.Y H:i" }}

    +
    +
    +
    +
    + + +
    +
    +
    + Länderei +
    +
    +
    +
    + + {{ verpachtung.land.bezeichnung }} + +
    +
    +
    + {% if verpachtung.land.flur %} +

    Flur: {{ verpachtung.land.flur }}

    + {% endif %} + {% if verpachtung.land.flurstueck %} +

    Flurstück: {{ verpachtung.land.flurstueck }}

    + {% endif %} +
    +
    +

    Gesamtfläche:
    {{ verpachtung.land.groesse_qm|floatformat:0 }} qm ({{ verpachtung.land.groesse_hektar|floatformat:2 }} ha)

    +
    +
    + {% if verpachtung.land.lage %} +

    Lage:
    {{ verpachtung.land.lage }}

    + {% endif %} +
    +
    +
    + + +
    +
    +
    +
    + Pächter +
    +
    +
    +
    + + {{ verpachtung.paechter.get_full_name }} + +
    +
    +
    + {% if verpachtung.paechter.email %} +

    E-Mail:
    + {{ verpachtung.paechter.email }} +

    + {% endif %} + {% if verpachtung.paechter.telefon %} +

    Telefon:
    {{ verpachtung.paechter.telefon }}

    + {% endif %} +
    +
    + {% if verpachtung.paechter.strasse or verpachtung.paechter.plz or verpachtung.paechter.ort %} +

    Adresse:
    + {% if verpachtung.paechter.strasse %}{{ verpachtung.paechter.strasse }}
    {% endif %} + {% if verpachtung.paechter.plz %}{{ verpachtung.paechter.plz }} {% endif %}{{ verpachtung.paechter.ort }} +

    + {% endif %} +
    +
    +

    Typ: {{ verpachtung.paechter.get_personentyp_display }}

    +
    +
    + + +
    +
    +
    + Pachtzeit +
    +
    +
    +
    +
    +

    Pachtbeginn:
    {{ verpachtung.pachtbeginn|date:"d.m.Y" }}

    + {% if verpachtung.pachtende %} +

    Pachtende:
    {{ verpachtung.pachtende|date:"d.m.Y" }}

    + {% else %} +

    Pachtende:
    Unbefristet

    + {% endif %} +
    +
    +

    Auto-Verlängerung:
    + {% if verpachtung.verlaengerung_klausel %} + Ja + {% else %} + Nein + {% endif %} +

    + {% if verpachtung.get_restlaufzeit_tage %} +

    Restlaufzeit:
    {{ verpachtung.get_restlaufzeit_tage }} Tage

    + {% endif %} +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    +
    + Pachtzins +
    +
    +
    +
    +
    +

    Pachtzins pauschal:
    + €{{ verpachtung.pachtzins_pauschal|floatformat:2 }} / Jahr +

    + {% if verpachtung.pachtzins_pro_ha %} +

    Pachtzins pro ha:
    €{{ verpachtung.pachtzins_pro_ha|floatformat:2 }} / ha

    + {% endif %} +
    +
    +

    Zahlungsweise:
    {{ verpachtung.get_zahlungsweise_display }}

    +

    Verpachtete Fläche:
    + {{ verpachtung.verpachtete_flaeche|floatformat:0 }} qm
    + ({{ verpachtung.verpachtete_flaeche_hektar|floatformat:2 }} ha) +

    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + Steuern & Umlagen +
    +
    +
    +
    +
    +

    USt-Option:
    + {% if verpachtung.ust_option %} + Ja ({{ verpachtung.ust_satz }}%) + {% if verpachtung.ust_pacht_betrag %} +
    USt-Betrag: €{{ verpachtung.ust_pacht_betrag|floatformat:2 }} + {% endif %} + {% else %} + Nein + {% endif %} +

    +
    +
    +

    Umlagefähig:

    +
      +
    • + + Grundsteuer +
    • +
    • + + Versicherungen +
    • +
    • + + Verbandsbeiträge +
    • +
    • + + Jagdpachtanteile +
    • +
    +
    +
    +
    +
    +
    +
    + + + {% if verpachtung.bemerkungen %} +
    +
    +
    +
    +
    + Bemerkungen +
    +
    +
    +

    {{ verpachtung.bemerkungen|linebreaks }}

    +
    +
    +
    +
    + {% endif %} + + + {% if verknuepfte_dokumente %} +
    +
    +
    +
    +
    + Verknüpfte Dokumente ({{ verknuepfte_dokumente.count }}) +
    +
    +
    +
    + {% for dokument in verknuepfte_dokumente %} +
    +
    +
    +
    +
    +
    {{ dokument.titel }}
    + {{ dokument.kontext }} +
    +
    + + + +
    +
    +
    +
    +
    + {% endfor %} +
    +
    +
    +
    +
    + {% endif %} + + +
    +
    +
    +
    +
    + Dokumente verwalten +
    + + Dokument verknüpfen + +
    +
    + {% if verknuepfte_dokumente %} +

    {{ verknuepfte_dokumente.count }} Dokument{{ verknuepfte_dokumente.count|pluralize:"e" }} verknüpft

    + {% else %} +

    + + Noch keine Dokumente mit dieser Verpachtung verknüpft. + Klicken Sie auf "Dokument verknüpfen", um Dokumente aus dem Paperless-System zu verknüpfen. +

    + {% endif %} +
    +
    +
    +
    + + + +
    +
    + +{% endblock %} \ No newline at end of file diff --git a/app/templates/stiftung/verpachtung_form.html b/app/templates/stiftung/verpachtung_form.html new file mode 100644 index 0000000..d468eb2 --- /dev/null +++ b/app/templates/stiftung/verpachtung_form.html @@ -0,0 +1,421 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %}{{ title }} - Stiftungsverwaltung{% endblock %} + +{% block content %} + + +
    +
    +

    + + {{ title }} +

    +

    + {% if form.instance.land %}Länderei: {{ form.instance.land }}{% endif %} + {% if form.instance.paechter %} | Pächter: {{ form.instance.paechter.get_full_name }}{% endif %} +

    +
    + +
    + +
    + {% csrf_token %} + +
    + +
    +
    +
    +
    + Verpachtungsdetails +
    +
    +
    + +
    +
    +
    + + {{ form.land }} + {% if form.land.errors %} +
    + {{ form.land.errors.0 }} +
    + {% endif %} +
    +
    +
    +
    + + {{ form.paechter }} + {% if form.paechter.errors %} +
    + {{ form.paechter.errors.0 }} +
    + {% endif %} +
    +
    +
    + + +
    +
    +
    + + {{ form.vertragsnummer }} + {% if form.vertragsnummer.errors %} +
    + {{ form.vertragsnummer.errors.0 }} +
    + {% endif %} +
    +
    +
    +
    + + {{ form.status }} + {% if form.status.errors %} +
    + {{ form.status.errors.0 }} +
    + {% endif %} +
    +
    +
    + + +
    +
    +
    + + {{ form.pachtbeginn }} + {% if form.pachtbeginn.errors %} +
    + {{ form.pachtbeginn.errors.0 }} +
    + {% endif %} +
    +
    +
    +
    + + {{ form.pachtende }} + {% if form.pachtende.errors %} +
    + {{ form.pachtende.errors.0 }} +
    + {% endif %} +
    Leer lassen für unbefristete Verpachtung
    +
    +
    +
    + + +
    +
    +
    +
    + {{ form.verlaengerung_klausel }} + +
    + {% if form.verlaengerung_klausel.errors %} +
    + {{ form.verlaengerung_klausel.errors.0 }} +
    + {% endif %} +
    +
    +
    + + +
    +
    +
    + + {{ form.verpachtete_flaeche }} + {% if form.verpachtete_flaeche.errors %} +
    + {{ form.verpachtete_flaeche.errors.0 }} +
    + {% endif %} +
    + 0,00 ha +
    +
    +
    +
    + + +
    +
    +
    + + {{ form.pachtzins_pauschal }} + {% if form.pachtzins_pauschal.errors %} +
    + {{ form.pachtzins_pauschal.errors.0 }} +
    + {% endif %} +
    +
    +
    +
    + + {{ form.pachtzins_pro_ha }} + {% if form.pachtzins_pro_ha.errors %} +
    + {{ form.pachtzins_pro_ha.errors.0 }} +
    + {% endif %} +
    Wird automatisch berechnet
    +
    +
    +
    + + +
    +
    +
    + + {{ form.zahlungsweise }} + {% if form.zahlungsweise.errors %} +
    + {{ form.zahlungsweise.errors.0 }} +
    + {% endif %} +
    +
    +
    + + +
    +
    +
    +
    + {{ form.ust_option }} + +
    + {% if form.ust_option.errors %} +
    + {{ form.ust_option.errors.0 }} +
    + {% endif %} +
    +
    +
    +
    + + {{ form.ust_satz }} + {% if form.ust_satz.errors %} +
    + {{ form.ust_satz.errors.0 }} +
    + {% endif %} +
    +
    +
    + + +
    + Umlagefähige Kosten +
    +
    +
    +
    +
    + {{ form.grundsteuer_umlage }} + +
    +
    +
    +
    + {{ form.versicherungen_umlage }} + +
    +
    +
    +
    +
    +
    + {{ form.verbandsbeitraege_umlage }} + +
    +
    +
    +
    + {{ form.jagdpacht_anteil_umlage }} + +
    +
    +
    +
    + + +
    +
    +
    + + {{ form.bemerkungen }} + {% if form.bemerkungen.errors %} +
    + {{ form.bemerkungen.errors.0 }} +
    + {% endif %} +
    +
    +
    +
    +
    +
    + + +
    + +
    +
    +
    + Aktionen +
    +
    +
    + + + Abbrechen + +
    +
    + + {% if form.instance.pk %} + +
    +
    +
    + Dokumente verknüpfen +
    +
    +
    +

    Dokumente können nach dem Speichern der Verpachtung verknüpft werden.

    + + Neues Dokument + +
    +
    + {% else %} + +
    +
    +
    + Hinweis +
    +
    +
    +

    + Nach dem Erstellen der Verpachtung können Dokumente verknüpft und weitere Details bearbeitet werden. +

    +
    +
    + {% endif %} +
    +
    +
    + + + +{% endblock %} \ No newline at end of file diff --git a/app/templates/stiftung/verpachtung_list.html b/app/templates/stiftung/verpachtung_list.html new file mode 100644 index 0000000..99ea32e --- /dev/null +++ b/app/templates/stiftung/verpachtung_list.html @@ -0,0 +1,244 @@ +{% extends 'base.html' %} +{% load static %} + +{% block title %}{{ title }} - Stiftungsverwaltung{% endblock %} + +{% block content %} + +
    +
    + +
    +

    + + {{ title }} +

    + + Neue Verpachtung + +
    + + +
    +
    +
    + Filter und Suche +
    +
    +
    +
    +
    + + +
    +
    + + +
    +
    + + +
    +
    + + + Zurücksetzen + +
    +
    +
    +
    + + +
    +
    +
    + Verpachtungen ({{ page_obj.paginator.count }}) +
    +
    +
    + {% if page_obj %} +
    + + + + + + + + + + + + + + + {% for verpachtung in page_obj %} + + + + + + + + + + + {% endfor %} + +
    VertragsnummerLandPächterPachtzeitFlächePachtzinsStatusAktionen
    + + {{ verpachtung.vertragsnummer }} + + + + {{ verpachtung.land.bezeichnung }} + + {% if verpachtung.land.flur %} +
    Flur {{ verpachtung.land.flur }} + {% endif %} +
    + + {{ verpachtung.paechter.get_full_name }} + + {% if verpachtung.paechter.ort %} +
    {{ verpachtung.paechter.ort }} + {% endif %} +
    + {{ verpachtung.pachtbeginn|date:"d.m.Y" }} + {% if verpachtung.pachtende %} +
    bis {{ verpachtung.pachtende|date:"d.m.Y" }} + {% else %} +
    Unbefristet + {% endif %} + {% if verpachtung.verlaengerung_klausel %} +
    Auto-Verlängerung + {% endif %} +
    + {{ verpachtung.verpachtete_flaeche|floatformat:0 }} qm +
    ({{ verpachtung.verpachtete_flaeche_hektar|floatformat:2 }} ha) +
    + €{{ verpachtung.pachtzins_pauschal|floatformat:2 }} + {% if verpachtung.pachtzins_pro_ha %} +
    €{{ verpachtung.pachtzins_pro_ha|floatformat:2 }}/ha + {% endif %} + {% if verpachtung.ust_option %} +
    + {{ verpachtung.ust_satz }}% USt + {% endif %} +
    + {% if verpachtung.status == 'aktiv' %} + {% if verpachtung.is_aktiv %} + Aktiv + {% else %} + Inaktiv + {% endif %} + {% elif verpachtung.status == 'beendet' %} + Beendet + {% elif verpachtung.status == 'gekuendigt' %} + Gekündigt + {% elif verpachtung.status == 'verlaengert' %} + Verlängert + {% else %} + {{ verpachtung.get_status_display }} + {% endif %} + + +
    +
    + + + {% if page_obj.has_other_pages %} + + {% endif %} + + {% else %} +
    + +
    Keine Verpachtungen gefunden
    +

    + {% if search_query or status_filter or year_filter %} + Keine Verpachtungen entsprechen den angegebenen Filterkriterien. + {% else %} + Es sind noch keine Verpachtungen erfasst. + {% endif %} +

    + + Erste Verpachtung erstellen + +
    + {% endif %} +
    +
    +
    +
    + +{% endblock %} \ No newline at end of file diff --git a/compose.dev.yml b/compose.dev.yml new file mode 100644 index 0000000..ad8f802 --- /dev/null +++ b/compose.dev.yml @@ -0,0 +1,82 @@ +services: + db: + image: postgres:16-alpine + environment: + POSTGRES_DB: stiftung_dev + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres_dev + volumes: + - dbdata_dev:/var/lib/postgresql/data + ports: + - "5433:5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres -d stiftung_dev"] + interval: 10s + timeout: 5s + retries: 5 + + redis: + image: redis:7-alpine + ports: + - "6380:6379" + + web: + build: ./app + depends_on: + db: + condition: service_healthy + redis: + condition: service_started + environment: + - POSTGRES_DB=stiftung_dev + - POSTGRES_USER=postgres + - POSTGRES_PASSWORD=postgres_dev + - DB_HOST=db + - DB_PORT=5432 + - DJANGO_SECRET_KEY=dev-secret-key-not-for-production + - DJANGO_DEBUG=1 + - DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1 + - LANGUAGE_CODE=de + - TIME_ZONE=Europe/Berlin + - REDIS_URL=redis://redis:6379/0 + - PAPERLESS_API_URL=http://paperless:8000 + - PAPERLESS_API_TOKEN=dev-token-will-be-set-after-paperless-setup + ports: + - "8081:8000" + volumes: + - ./app:/app + command: ["python", "manage.py", "runserver", "0.0.0.0:8000"] + + paperless: + image: ghcr.io/paperless-ngx/paperless-ngx:latest + ports: + - "8082:8000" + environment: + - PAPERLESS_REDIS=redis://redis:6379 + - PAPERLESS_DBHOST=db + - PAPERLESS_DBPORT=5432 + - PAPERLESS_DBNAME=stiftung_dev + - PAPERLESS_DBUSER=postgres + - PAPERLESS_DBPASS=postgres_dev + - PAPERLESS_SECRET_KEY=dev-paperless-secret-key + - PAPERLESS_URL=http://localhost:8082 + - PAPERLESS_ALLOWED_HOSTS=localhost,127.0.0.1 + - PAPERLESS_CORS_ALLOWED_HOSTS=http://localhost:8082,http://localhost:8081 + - PAPERLESS_ADMIN_USER=admin + - PAPERLESS_ADMIN_PASSWORD=admin123 + - PAPERLESS_ADMIN_MAIL=admin@localhost + volumes: + - paperless_data_dev:/usr/src/paperless/data + - paperless_media_dev:/usr/src/paperless/media + - paperless_export_dev:/usr/src/paperless/export + - paperless_consume_dev:/usr/src/paperless/consume + depends_on: + - db + - redis + +volumes: + dbdata_dev: + paperless_data_dev: + paperless_media_dev: + paperless_export_dev: + paperless_consume_dev: diff --git a/compose.yml b/compose.yml index a3891cd..d89ebde 100644 --- a/compose.yml +++ b/compose.yml @@ -1,3 +1,11 @@ +# Production Docker Compose Configuration +# This file is used for production deployment via GitHub Actions +# For local development, use: docker-compose -f compose.dev.yml up +# +# IMPORTANT: This configuration requires ALL environment variables to be +# provided via the production server's .env file. No fallback values are +# included for security reasons. + services: db: image: postgres:16-alpine @@ -8,7 +16,7 @@ services: volumes: - dbdata:/var/lib/postgresql/data healthcheck: - test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] + test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"] interval: 10s timeout: 5s retries: 5 @@ -23,7 +31,20 @@ services: condition: service_healthy redis: condition: service_started - env_file: ./app/.env + environment: + - POSTGRES_DB=${POSTGRES_DB} + - POSTGRES_USER=${POSTGRES_USER} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - DB_HOST=${DB_HOST} + - DB_PORT=${DB_PORT} + - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY} + - DJANGO_DEBUG=${DJANGO_DEBUG} + - DJANGO_ALLOWED_HOSTS=${DJANGO_ALLOWED_HOSTS} + - LANGUAGE_CODE=${LANGUAGE_CODE} + - TIME_ZONE=${TIME_ZONE} + - REDIS_URL=${REDIS_URL} + - PAPERLESS_API_URL=${PAPERLESS_API_URL} + - PAPERLESS_API_TOKEN=${PAPERLESS_API_TOKEN} ports: - "8081:8000" volumes: @@ -32,7 +53,15 @@ services: worker: build: ./app - env_file: ./app/.env + environment: + - POSTGRES_DB=${POSTGRES_DB} + - POSTGRES_USER=${POSTGRES_USER} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - DB_HOST=${DB_HOST} + - DB_PORT=${DB_PORT} + - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY} + - DJANGO_DEBUG=${DJANGO_DEBUG} + - REDIS_URL=${REDIS_URL} depends_on: - redis - db @@ -40,7 +69,15 @@ services: beat: build: ./app - env_file: ./app/.env + environment: + - POSTGRES_DB=${POSTGRES_DB} + - POSTGRES_USER=${POSTGRES_USER} + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - DB_HOST=${DB_HOST} + - DB_PORT=${DB_PORT} + - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY} + - DJANGO_DEBUG=${DJANGO_DEBUG} + - REDIS_URL=${REDIS_URL} depends_on: - redis - db @@ -76,9 +113,9 @@ services: - PAPERLESS_STATIC_URL=/paperless/static/ - PAPERLESS_LOGIN_REDIRECT_URL=/paperless/ - PAPERLESS_LOGOUT_REDIRECT_URL=/paperless/ - - PAPERLESS_ADMIN_USER=${PAPERLESS_ADMIN_USER:-admin} - - PAPERLESS_ADMIN_PASSWORD=${PAPERLESS_ADMIN_PASSWORD:-admin123} - - PAPERLESS_ADMIN_MAIL=${PAPERLESS_ADMIN_MAIL:-admin@vhtv-stiftung.de} + - PAPERLESS_ADMIN_USER=${PAPERLESS_ADMIN_USER} + - PAPERLESS_ADMIN_PASSWORD=${PAPERLESS_ADMIN_PASSWORD} + - PAPERLESS_ADMIN_MAIL=${PAPERLESS_ADMIN_MAIL} volumes: - paperless_data:/usr/src/paperless/data - paperless_media:/usr/src/paperless/media