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