Compare commits
2 Commits
4b21f553c3
...
28621d2774
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
28621d2774 | ||
|
|
f8f9dc3319 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -138,4 +138,7 @@ dev-debug.log
|
|||||||
|
|
||||||
# Task files
|
# Task files
|
||||||
# tasks.json
|
# tasks.json
|
||||||
# tasks/
|
# tasks/
|
||||||
|
|
||||||
|
# Claude Code local config
|
||||||
|
.claude/
|
||||||
|
|||||||
30
agents/dog/AGENTS.md
Normal file
30
agents/dog/AGENTS.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Bürohund (Office Dog)
|
||||||
|
|
||||||
|
Du bist der Bürohund der Stiftung — ein freundlicher, verspielter virtueller Hund, der für gute Stimmung im Team sorgt.
|
||||||
|
|
||||||
|
## Persönlichkeit
|
||||||
|
|
||||||
|
- Enthusiastisch und freundlich
|
||||||
|
- Aufmunternd und positiv
|
||||||
|
- Nutze gelegentlich Hunde-Metaphern (Schwanzwedeln, Bellen vor Freude, etc.)
|
||||||
|
- Halte dich kurz — du bist ein Hund, kein Essayist
|
||||||
|
|
||||||
|
## Aufgaben
|
||||||
|
|
||||||
|
Wenn dir eine Aufgabe zugewiesen wird:
|
||||||
|
|
||||||
|
1. Lies die Aufgabe und den Kontext
|
||||||
|
2. Schreibe eine kurze, aufmunternde Nachricht als Kommentar an den zuständigen Agenten
|
||||||
|
3. Markiere die Aufgabe als erledigt
|
||||||
|
|
||||||
|
Typische Aktionen:
|
||||||
|
- Ermutigende Kommentare auf Aufgaben anderer Agenten hinterlassen
|
||||||
|
- Positive Stimmung verbreiten
|
||||||
|
- Teamgeist stärken
|
||||||
|
|
||||||
|
## Stil
|
||||||
|
|
||||||
|
- Schreibe auf Deutsch
|
||||||
|
- Benutze Emojis sparsam aber passend (ein Hunde-Emoji hier und da ist ok)
|
||||||
|
- Sei authentisch-verspielt, nicht nervig
|
||||||
|
- Halte Nachrichten auf 2-3 Sätze
|
||||||
@@ -162,7 +162,32 @@ Nutze für die Prüfung möglichst dieses Raster:
|
|||||||
- Protokollentwurf
|
- Protokollentwurf
|
||||||
- Wenn eine Frage unklar ist, nenne zuerst die Annahmen, auf denen deine Antwort beruht.
|
- Wenn eine Frage unklar ist, nenne zuerst die Annahmen, auf denen deine Antwort beruht.
|
||||||
|
|
||||||
### 11. KLARE GRENZEN
|
### 11. DATENZUGRIFF
|
||||||
|
|
||||||
|
#### REST API (`/api/v1/`)
|
||||||
|
Die Stiftungsverwaltung bietet Read-Only API-Endpunkte. Authentifizierung über Token (`Authorization: Token <token>`).
|
||||||
|
|
||||||
|
| Endpunkt | Daten |
|
||||||
|
|---|---|
|
||||||
|
| `/api/v1/destinataere/` | Destinatäre mit Unterstützungen |
|
||||||
|
| `/api/v1/laendereien/` | Ländereien mit Verpachtungen |
|
||||||
|
| `/api/v1/paechter/` | Pächter mit Verträgen |
|
||||||
|
| `/api/v1/foerderungen/` | Förderungen mit Status |
|
||||||
|
| `/api/v1/konten/` | Stiftungskonten |
|
||||||
|
| `/api/v1/verpachtungen/` | Pachtverträge |
|
||||||
|
| `/api/v1/verwaltungskosten/` | Verwaltungskosten |
|
||||||
|
| `/api/v1/kalender/` | Termine und Fristen |
|
||||||
|
| `/api/v1/transaktionen/` | Banktransaktionen |
|
||||||
|
|
||||||
|
#### Wissensbasis (`knowledge/`)
|
||||||
|
Stabile Stiftungsinformationen als Markdown-Dateien:
|
||||||
|
- `knowledge/satzung.md` — Stiftungssatzung und Zwecke
|
||||||
|
- `knowledge/richtlinien.md` — Förderrichtlinien
|
||||||
|
- `knowledge/verfahren.md` — Verwaltungsverfahren und Abläufe
|
||||||
|
- `knowledge/kontakte.md` — Wichtige Kontakte
|
||||||
|
- `knowledge/historie.md` — Stiftungsgeschichte
|
||||||
|
|
||||||
|
### KLARE GRENZEN
|
||||||
- Du handelst nicht selbst gegenüber Banken, Behörden oder Vertragspartnern.
|
- Du handelst nicht selbst gegenüber Banken, Behörden oder Vertragspartnern.
|
||||||
- Du gibst keine finalen rechtlichen Freigaben.
|
- Du gibst keine finalen rechtlichen Freigaben.
|
||||||
- Du bestätigst keine Gemeinnützigkeitskonformität mit Verbindlichkeit.
|
- Du bestätigst keine Gemeinnützigkeitskonformität mit Verbindlichkeit.
|
||||||
|
|||||||
@@ -53,6 +53,33 @@ Du bist Systemadministrator einer gemeinnützigen deutschen Familienstiftung. Du
|
|||||||
- Erfinde keine Fakten. Benenne Unsicherheiten und offene Punkte klar.
|
- Erfinde keine Fakten. Benenne Unsicherheiten und offene Punkte klar.
|
||||||
- Eskaliere bei Unklarheiten oder potenziellen Risiken.
|
- Eskaliere bei Unklarheiten oder potenziellen Risiken.
|
||||||
|
|
||||||
|
## Datenzugriff
|
||||||
|
|
||||||
|
### REST API (`/api/v1/`)
|
||||||
|
Die Stiftungsverwaltung bietet Read-Only API-Endpunkte für alle Kernmodelle. Authentifizierung über Token (`Authorization: Token <token>`).
|
||||||
|
|
||||||
|
| Endpunkt | Daten |
|
||||||
|
|---|---|
|
||||||
|
| `/api/v1/destinataere/` | Destinatäre mit Unterstützungen |
|
||||||
|
| `/api/v1/laendereien/` | Ländereien mit Verpachtungen |
|
||||||
|
| `/api/v1/paechter/` | Pächter mit Verträgen |
|
||||||
|
| `/api/v1/foerderungen/` | Förderungen mit Status |
|
||||||
|
| `/api/v1/konten/` | Stiftungskonten |
|
||||||
|
| `/api/v1/verpachtungen/` | Pachtverträge |
|
||||||
|
| `/api/v1/verwaltungskosten/` | Verwaltungskosten |
|
||||||
|
| `/api/v1/kalender/` | Termine und Fristen |
|
||||||
|
| `/api/v1/transaktionen/` | Banktransaktionen |
|
||||||
|
|
||||||
|
Token erstellen: `python manage.py create_agent_token <username>`
|
||||||
|
|
||||||
|
### Wissensbasis (`knowledge/`)
|
||||||
|
Stabile Stiftungsinformationen als Markdown-Dateien:
|
||||||
|
- `knowledge/satzung.md` — Stiftungssatzung und Zwecke
|
||||||
|
- `knowledge/richtlinien.md` — Förderrichtlinien
|
||||||
|
- `knowledge/verfahren.md` — Verwaltungsverfahren und Abläufe
|
||||||
|
- `knowledge/kontakte.md` — Wichtige Kontakte
|
||||||
|
- `knowledge/historie.md` — Stiftungsgeschichte
|
||||||
|
|
||||||
## Grenzen
|
## Grenzen
|
||||||
|
|
||||||
- Du triffst keine eigenständigen Entscheidungen über Architektur oder Technologieauswahl ohne Rücksprache.
|
- Du triffst keine eigenständigen Entscheidungen über Architektur oder Technologieauswahl ohne Rücksprache.
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ INSTALLED_APPS = [
|
|||||||
"django.contrib.staticfiles",
|
"django.contrib.staticfiles",
|
||||||
"django.contrib.humanize",
|
"django.contrib.humanize",
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
|
"rest_framework.authtoken",
|
||||||
"django_otp",
|
"django_otp",
|
||||||
"django_otp.plugins.otp_totp",
|
"django_otp.plugins.otp_totp",
|
||||||
"django_otp.plugins.otp_static",
|
"django_otp.plugins.otp_static",
|
||||||
@@ -99,6 +100,16 @@ STATICFILES_DIRS = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
|
|
||||||
|
REST_FRAMEWORK = {
|
||||||
|
"DEFAULT_AUTHENTICATION_CLASSES": [
|
||||||
|
"rest_framework.authentication.SessionAuthentication",
|
||||||
|
"rest_framework.authentication.TokenAuthentication",
|
||||||
|
],
|
||||||
|
"DEFAULT_PERMISSION_CLASSES": [
|
||||||
|
"rest_framework.permissions.IsAuthenticated",
|
||||||
|
],
|
||||||
|
}
|
||||||
MEDIA_URL = "/media/"
|
MEDIA_URL = "/media/"
|
||||||
MEDIA_ROOT = BASE_DIR / "media"
|
MEDIA_ROOT = BASE_DIR / "media"
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from django.urls import include, path
|
|||||||
from stiftung.views import home
|
from stiftung.views import home
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
|
path("api/v1/", include("stiftung.api_urls")),
|
||||||
path("", include("stiftung.urls")),
|
path("", include("stiftung.urls")),
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
# Authentication URLs
|
# Authentication URLs
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ celery==5.3.6
|
|||||||
redis==5.0.7
|
redis==5.0.7
|
||||||
djangorestframework==3.15.2
|
djangorestframework==3.15.2
|
||||||
weasyprint==62.3
|
weasyprint==62.3
|
||||||
|
pydyf==0.11.0
|
||||||
python-dotenv==1.0.1
|
python-dotenv==1.0.1
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
gunicorn==22.0.0
|
gunicorn==22.0.0
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ from .models import (AppConfiguration, AuditLog, BackupJob, BankTransaction,
|
|||||||
DestinataerUnterstuetzung,
|
DestinataerUnterstuetzung,
|
||||||
DokumentLink, Foerderung, Land, LandVerpachtung, Paechter, Person,
|
DokumentLink, Foerderung, Land, LandVerpachtung, Paechter, Person,
|
||||||
Rentmeister, StiftungsKonto, UnterstuetzungWiederkehrend,
|
Rentmeister, StiftungsKonto, UnterstuetzungWiederkehrend,
|
||||||
|
Veranstaltung, Veranstaltungsteilnehmer,
|
||||||
Verwaltungskosten, VierteljahresNachweis)
|
Verwaltungskosten, VierteljahresNachweis)
|
||||||
|
|
||||||
|
|
||||||
@@ -1228,6 +1229,109 @@ class DestinataerEmailEingangAdmin(admin.ModelAdmin):
|
|||||||
mark_verarbeitet.short_description = "Als verarbeitet markieren"
|
mark_verarbeitet.short_description = "Als verarbeitet markieren"
|
||||||
|
|
||||||
|
|
||||||
|
class VeranstaltungsteilnehmerInline(admin.TabularInline):
|
||||||
|
model = Veranstaltungsteilnehmer
|
||||||
|
extra = 1
|
||||||
|
fields = [
|
||||||
|
"anrede", "vorname", "nachname", "strasse", "plz", "ort",
|
||||||
|
"email", "rsvp_status", "bemerkungen",
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Veranstaltung)
|
||||||
|
class VeranstaltungAdmin(admin.ModelAdmin):
|
||||||
|
list_display = [
|
||||||
|
"titel", "datum", "uhrzeit", "ort", "status",
|
||||||
|
"get_teilnehmer_count", "get_zugesagte_count", "budget_pro_person",
|
||||||
|
]
|
||||||
|
list_filter = ["status", "datum"]
|
||||||
|
search_fields = ["titel", "ort", "beschreibung"]
|
||||||
|
ordering = ["-datum"]
|
||||||
|
readonly_fields = ["id", "erstellt_am", "aktualisiert_am", "serienbrief_link"]
|
||||||
|
inlines = [VeranstaltungsteilnehmerInline]
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
("Grunddaten", {"fields": ("titel", "datum", "uhrzeit", "status")}),
|
||||||
|
("Veranstaltungsort", {"fields": ("ort", "adresse")}),
|
||||||
|
("Details", {"fields": ("beschreibung", "budget_pro_person")}),
|
||||||
|
(
|
||||||
|
"Serienbrief",
|
||||||
|
{
|
||||||
|
"fields": (
|
||||||
|
"betreff", "briefvorlage",
|
||||||
|
"unterschrift_1_name", "unterschrift_1_titel",
|
||||||
|
"unterschrift_2_name", "unterschrift_2_titel",
|
||||||
|
"serienbrief_link",
|
||||||
|
),
|
||||||
|
"description": (
|
||||||
|
"Betreff leer = 'Einladung zum [Titel]'. "
|
||||||
|
"Platzhalter in der Vorlage: {{ anrede }}, {{ vorname }}, "
|
||||||
|
"{{ nachname }}, {{ strasse }}, {{ plz }}, {{ ort_teilnehmer }}, "
|
||||||
|
"{{ datum }}, {{ uhrzeit }}, {{ veranstaltungsort }}, {{ gasthaus_adresse }}"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
("System", {"fields": ("id", "erstellt_am", "aktualisiert_am"), "classes": ("collapse",)}),
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_teilnehmer_count(self, obj):
|
||||||
|
return obj.get_teilnehmer_count()
|
||||||
|
get_teilnehmer_count.short_description = "Teilnehmer gesamt"
|
||||||
|
|
||||||
|
def get_zugesagte_count(self, obj):
|
||||||
|
return obj.get_zugesagte_count()
|
||||||
|
get_zugesagte_count.short_description = "Zugesagt"
|
||||||
|
|
||||||
|
def serienbrief_link(self, obj):
|
||||||
|
if obj.pk:
|
||||||
|
from django.urls import reverse as url_reverse
|
||||||
|
url = url_reverse("stiftung:veranstaltung_serienbrief_pdf", args=[obj.pk])
|
||||||
|
return format_html(
|
||||||
|
'<a href="{}" target="_blank" class="button">Serienbrief-PDF generieren</a>', url
|
||||||
|
)
|
||||||
|
return "–"
|
||||||
|
serienbrief_link.short_description = "Serienbrief"
|
||||||
|
|
||||||
|
actions = ["generate_serienbrief"]
|
||||||
|
|
||||||
|
def generate_serienbrief(self, request, queryset):
|
||||||
|
if queryset.count() != 1:
|
||||||
|
self.message_user(
|
||||||
|
request,
|
||||||
|
"Bitte genau eine Veranstaltung auswählen.",
|
||||||
|
level="error",
|
||||||
|
)
|
||||||
|
return
|
||||||
|
from django.urls import reverse as url_reverse
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
veranstaltung = queryset.first()
|
||||||
|
url = url_reverse("stiftung:veranstaltung_serienbrief_pdf", args=[veranstaltung.pk])
|
||||||
|
return redirect(url)
|
||||||
|
generate_serienbrief.short_description = "Serienbrief-PDF generieren"
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Veranstaltungsteilnehmer)
|
||||||
|
class VeranstaltungsteilnehmerAdmin(admin.ModelAdmin):
|
||||||
|
list_display = [
|
||||||
|
"nachname", "vorname", "anrede", "veranstaltung", "rsvp_status", "ort",
|
||||||
|
]
|
||||||
|
list_filter = ["rsvp_status", "veranstaltung", "anrede"]
|
||||||
|
search_fields = ["vorname", "nachname", "ort", "email"]
|
||||||
|
ordering = ["veranstaltung", "nachname", "vorname"]
|
||||||
|
readonly_fields = ["id", "erstellt_am"]
|
||||||
|
|
||||||
|
fieldsets = (
|
||||||
|
("Zuordnung", {"fields": ("veranstaltung", "paechter", "destinataer")}),
|
||||||
|
(
|
||||||
|
"Persönliche Daten",
|
||||||
|
{"fields": ("anrede", "vorname", "nachname", "email")},
|
||||||
|
),
|
||||||
|
("Adresse", {"fields": ("strasse", "plz", "ort")}),
|
||||||
|
("RSVP", {"fields": ("rsvp_status", "bemerkungen")}),
|
||||||
|
("System", {"fields": ("id", "erstellt_am"), "classes": ("collapse",)}),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
# Customize admin site
|
# Customize admin site
|
||||||
admin.site.site_header = "Stiftungsverwaltung Administration"
|
admin.site.site_header = "Stiftungsverwaltung Administration"
|
||||||
admin.site.site_title = "Stiftungsverwaltung Admin"
|
admin.site.site_title = "Stiftungsverwaltung Admin"
|
||||||
|
|||||||
110
app/stiftung/api_serializers.py
Normal file
110
app/stiftung/api_serializers.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
from rest_framework import serializers
|
||||||
|
|
||||||
|
from .models import (
|
||||||
|
BankTransaction,
|
||||||
|
Destinataer,
|
||||||
|
DestinataerUnterstuetzung,
|
||||||
|
Foerderung,
|
||||||
|
Land,
|
||||||
|
LandVerpachtung,
|
||||||
|
Paechter,
|
||||||
|
StiftungsKalenderEintrag,
|
||||||
|
StiftungsKonto,
|
||||||
|
Veranstaltung,
|
||||||
|
Veranstaltungsteilnehmer,
|
||||||
|
Verwaltungskosten,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DestinataerUnterstuetzungSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = DestinataerUnterstuetzung
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class DestinataerSerializer(serializers.ModelSerializer):
|
||||||
|
unterstuetzungen = DestinataerUnterstuetzungSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Destinataer
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class LandVerpachtungSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = LandVerpachtung
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class LandSerializer(serializers.ModelSerializer):
|
||||||
|
aktive_verpachtungen = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Land
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
def get_aktive_verpachtungen(self, obj):
|
||||||
|
qs = obj.neue_verpachtungen.filter(status="aktiv")
|
||||||
|
return LandVerpachtungSerializer(qs, many=True).data
|
||||||
|
|
||||||
|
|
||||||
|
class PaechterSerializer(serializers.ModelSerializer):
|
||||||
|
aktive_verpachtungen = serializers.SerializerMethodField()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Paechter
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
def get_aktive_verpachtungen(self, obj):
|
||||||
|
qs = obj.neue_verpachtungen.filter(status="aktiv")
|
||||||
|
return LandVerpachtungSerializer(qs, many=True).data
|
||||||
|
|
||||||
|
|
||||||
|
class FoerderungSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Foerderung
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class StiftungsKontoSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = StiftungsKonto
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class VerwaltungskostenSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Verwaltungskosten
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class StiftungsKalenderEintragSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = StiftungsKalenderEintrag
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class BankTransactionSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = BankTransaction
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class VeranstaltungsteilnehmerSerializer(serializers.ModelSerializer):
|
||||||
|
class Meta:
|
||||||
|
model = Veranstaltungsteilnehmer
|
||||||
|
fields = "__all__"
|
||||||
|
|
||||||
|
|
||||||
|
class VeranstaltungSerializer(serializers.ModelSerializer):
|
||||||
|
teilnehmer = VeranstaltungsteilnehmerSerializer(many=True, read_only=True)
|
||||||
|
teilnehmer_count = serializers.IntegerField(
|
||||||
|
source="get_teilnehmer_count", read_only=True
|
||||||
|
)
|
||||||
|
zugesagte_count = serializers.IntegerField(
|
||||||
|
source="get_zugesagte_count", read_only=True
|
||||||
|
)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = Veranstaltung
|
||||||
|
fields = "__all__"
|
||||||
28
app/stiftung/api_urls.py
Normal file
28
app/stiftung/api_urls.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from rest_framework.routers import DefaultRouter
|
||||||
|
|
||||||
|
from .api_views import (
|
||||||
|
BankTransactionViewSet,
|
||||||
|
DestinataerViewSet,
|
||||||
|
FoerderungViewSet,
|
||||||
|
LandVerpachtungViewSet,
|
||||||
|
LandViewSet,
|
||||||
|
PaechterViewSet,
|
||||||
|
StiftungsKalenderEintragViewSet,
|
||||||
|
StiftungsKontoViewSet,
|
||||||
|
VeranstaltungViewSet,
|
||||||
|
VerwaltungskostenViewSet,
|
||||||
|
)
|
||||||
|
|
||||||
|
router = DefaultRouter()
|
||||||
|
router.register(r"destinataere", DestinataerViewSet)
|
||||||
|
router.register(r"laendereien", LandViewSet)
|
||||||
|
router.register(r"paechter", PaechterViewSet)
|
||||||
|
router.register(r"foerderungen", FoerderungViewSet)
|
||||||
|
router.register(r"konten", StiftungsKontoViewSet)
|
||||||
|
router.register(r"verpachtungen", LandVerpachtungViewSet)
|
||||||
|
router.register(r"verwaltungskosten", VerwaltungskostenViewSet)
|
||||||
|
router.register(r"kalender", StiftungsKalenderEintragViewSet)
|
||||||
|
router.register(r"transaktionen", BankTransactionViewSet)
|
||||||
|
router.register(r"veranstaltungen", VeranstaltungViewSet)
|
||||||
|
|
||||||
|
urlpatterns = router.urls
|
||||||
76
app/stiftung/api_views.py
Normal file
76
app/stiftung/api_views.py
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
from rest_framework.viewsets import ReadOnlyModelViewSet
|
||||||
|
|
||||||
|
from .api_serializers import (
|
||||||
|
BankTransactionSerializer,
|
||||||
|
DestinataerSerializer,
|
||||||
|
FoerderungSerializer,
|
||||||
|
LandSerializer,
|
||||||
|
LandVerpachtungSerializer,
|
||||||
|
PaechterSerializer,
|
||||||
|
StiftungsKalenderEintragSerializer,
|
||||||
|
StiftungsKontoSerializer,
|
||||||
|
VeranstaltungSerializer,
|
||||||
|
VerwaltungskostenSerializer,
|
||||||
|
)
|
||||||
|
from .models import (
|
||||||
|
BankTransaction,
|
||||||
|
Destinataer,
|
||||||
|
Foerderung,
|
||||||
|
Land,
|
||||||
|
LandVerpachtung,
|
||||||
|
Paechter,
|
||||||
|
StiftungsKalenderEintrag,
|
||||||
|
StiftungsKonto,
|
||||||
|
Veranstaltung,
|
||||||
|
Verwaltungskosten,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class DestinataerViewSet(ReadOnlyModelViewSet):
|
||||||
|
queryset = Destinataer.objects.all()
|
||||||
|
serializer_class = DestinataerSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class LandViewSet(ReadOnlyModelViewSet):
|
||||||
|
queryset = Land.objects.all()
|
||||||
|
serializer_class = LandSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class PaechterViewSet(ReadOnlyModelViewSet):
|
||||||
|
queryset = Paechter.objects.all()
|
||||||
|
serializer_class = PaechterSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class FoerderungViewSet(ReadOnlyModelViewSet):
|
||||||
|
queryset = Foerderung.objects.all()
|
||||||
|
serializer_class = FoerderungSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class StiftungsKontoViewSet(ReadOnlyModelViewSet):
|
||||||
|
queryset = StiftungsKonto.objects.all()
|
||||||
|
serializer_class = StiftungsKontoSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class LandVerpachtungViewSet(ReadOnlyModelViewSet):
|
||||||
|
queryset = LandVerpachtung.objects.all()
|
||||||
|
serializer_class = LandVerpachtungSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class VerwaltungskostenViewSet(ReadOnlyModelViewSet):
|
||||||
|
queryset = Verwaltungskosten.objects.all()
|
||||||
|
serializer_class = VerwaltungskostenSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class StiftungsKalenderEintragViewSet(ReadOnlyModelViewSet):
|
||||||
|
queryset = StiftungsKalenderEintrag.objects.all()
|
||||||
|
serializer_class = StiftungsKalenderEintragSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class BankTransactionViewSet(ReadOnlyModelViewSet):
|
||||||
|
queryset = BankTransaction.objects.all()
|
||||||
|
serializer_class = BankTransactionSerializer
|
||||||
|
|
||||||
|
|
||||||
|
class VeranstaltungViewSet(ReadOnlyModelViewSet):
|
||||||
|
queryset = Veranstaltung.objects.all()
|
||||||
|
serializer_class = VeranstaltungSerializer
|
||||||
28
app/stiftung/management/commands/create_agent_token.py
Normal file
28
app/stiftung/management/commands/create_agent_token.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
from django.contrib.auth import get_user_model
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
from rest_framework.authtoken.models import Token
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Erstellt oder gibt ein API-Token für einen Django-User aus (für Agent-Zugriff)"
|
||||||
|
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
parser.add_argument("username", type=str, help="Django-Username")
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
User = get_user_model()
|
||||||
|
username = options["username"]
|
||||||
|
|
||||||
|
try:
|
||||||
|
user = User.objects.get(username=username)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
raise CommandError(f'User "{username}" nicht gefunden.')
|
||||||
|
|
||||||
|
token, created = Token.objects.get_or_create(user=user)
|
||||||
|
|
||||||
|
if created:
|
||||||
|
self.stdout.write(self.style.SUCCESS(f"Neues Token erstellt für {username}:"))
|
||||||
|
else:
|
||||||
|
self.stdout.write(self.style.WARNING(f"Bestehendes Token für {username}:"))
|
||||||
|
|
||||||
|
self.stdout.write(token.key)
|
||||||
61
app/stiftung/migrations/0044_veranstaltungsmodul.py
Normal file
61
app/stiftung/migrations/0044_veranstaltungsmodul.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2026-03-10 21:47
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
import uuid
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('stiftung', '0043_destinataer_email_eingang'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Veranstaltung',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('titel', models.CharField(max_length=200, verbose_name='Titel')),
|
||||||
|
('datum', models.DateField(verbose_name='Datum')),
|
||||||
|
('uhrzeit', models.TimeField(blank=True, null=True, verbose_name='Uhrzeit')),
|
||||||
|
('ort', models.CharField(max_length=200, verbose_name='Ort / Gasthaus')),
|
||||||
|
('adresse', models.TextField(blank=True, verbose_name='Adresse Gasthaus')),
|
||||||
|
('beschreibung', models.TextField(blank=True, verbose_name='Beschreibung / Zweck')),
|
||||||
|
('status', models.CharField(choices=[('geplant', 'Geplant'), ('einladungen_versendet', 'Einladungen versendet'), ('abgeschlossen', 'Abgeschlossen'), ('abgesagt', 'Abgesagt')], default='geplant', max_length=30, verbose_name='Status')),
|
||||||
|
('budget_pro_person', models.DecimalField(blank=True, decimal_places=2, help_text='Geschätztes Budget je Teilnehmer in €', max_digits=8, null=True, verbose_name='Budget pro Person (€)')),
|
||||||
|
('briefvorlage', models.TextField(blank=True, help_text='HTML/Text-Template für Serienbrief. Platzhalter: {{ anrede }}, {{ vorname }}, {{ nachname }}, {{ strasse }}, {{ plz }}, {{ ort }}, {{ datum }}, {{ uhrzeit }}, {{ veranstaltungsort }}, {{ gasthaus_adresse }}', verbose_name='Briefvorlage')),
|
||||||
|
('erstellt_am', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('aktualisiert_am', models.DateTimeField(auto_now=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Veranstaltung',
|
||||||
|
'verbose_name_plural': 'Veranstaltungen',
|
||||||
|
'ordering': ['-datum'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Veranstaltungsteilnehmer',
|
||||||
|
fields=[
|
||||||
|
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
|
||||||
|
('anrede', models.CharField(blank=True, choices=[('Herr', 'Herr'), ('Frau', 'Frau'), ('', 'Keine Anrede')], max_length=10, verbose_name='Anrede')),
|
||||||
|
('vorname', models.CharField(max_length=100, verbose_name='Vorname')),
|
||||||
|
('nachname', models.CharField(max_length=100, verbose_name='Nachname')),
|
||||||
|
('strasse', models.CharField(blank=True, max_length=200, verbose_name='Straße')),
|
||||||
|
('plz', models.CharField(blank=True, max_length=10, verbose_name='PLZ')),
|
||||||
|
('ort', models.CharField(blank=True, max_length=100, verbose_name='Ort')),
|
||||||
|
('email', models.EmailField(blank=True, help_text='Optional, für späteren E-Mail-Versand', max_length=254, verbose_name='E-Mail')),
|
||||||
|
('rsvp_status', models.CharField(choices=[('eingeladen', 'Eingeladen'), ('zugesagt', 'Zugesagt'), ('abgesagt', 'Abgesagt'), ('keine_rueckmeldung', 'Keine Rückmeldung')], default='eingeladen', max_length=20, verbose_name='RSVP-Status')),
|
||||||
|
('bemerkungen', models.TextField(blank=True, verbose_name='Bemerkungen')),
|
||||||
|
('erstellt_am', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('destinataer', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stiftung.destinataer', verbose_name='Destinatär (optional)')),
|
||||||
|
('paechter', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='stiftung.paechter', verbose_name='Pächter (optional)')),
|
||||||
|
('veranstaltung', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='teilnehmer', to='stiftung.veranstaltung', verbose_name='Veranstaltung')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Veranstaltungsteilnehmer',
|
||||||
|
'verbose_name_plural': 'Veranstaltungsteilnehmer',
|
||||||
|
'ordering': ['nachname', 'vorname'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
# Generated by Django 5.0.6 on 2026-03-10 22:31
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('stiftung', '0044_veranstaltungsmodul'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='veranstaltung',
|
||||||
|
name='betreff',
|
||||||
|
field=models.CharField(blank=True, help_text='Betreffzeile des Serienbriefs. Leer = Standardbetreff.', max_length=300, verbose_name='Betreff'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='veranstaltung',
|
||||||
|
name='unterschrift_1_name',
|
||||||
|
field=models.CharField(blank=True, default='Katrin Kleinpaß', max_length=100, verbose_name='Unterschrift 1 – Name'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='veranstaltung',
|
||||||
|
name='unterschrift_1_titel',
|
||||||
|
field=models.CharField(blank=True, default='Rentmeisterin', max_length=100, verbose_name='Unterschrift 1 – Titel'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='veranstaltung',
|
||||||
|
name='unterschrift_2_name',
|
||||||
|
field=models.CharField(blank=True, default='Jan Remmer Siebels', max_length=100, verbose_name='Unterschrift 2 – Name'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='veranstaltung',
|
||||||
|
name='unterschrift_2_titel',
|
||||||
|
field=models.CharField(blank=True, default='Rentmeister', max_length=100, verbose_name='Unterschrift 2 – Titel'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='vierteljahresnachweis',
|
||||||
|
name='faelligkeitsdatum',
|
||||||
|
field=models.DateField(blank=True, help_text='Veraltet - wird durch studiennachweis_faelligkeitsdatum und zahlung_faelligkeitsdatum ersetzt', null=True, verbose_name='Fälligkeitsdatum'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -671,7 +671,7 @@ class Land(models.Model):
|
|||||||
|
|
||||||
def get_verpachtungsgrad_neu(self):
|
def get_verpachtungsgrad_neu(self):
|
||||||
"""Berechnet den Verpachtungsgrad basierend auf neuen Verpachtungen"""
|
"""Berechnet den Verpachtungsgrad basierend auf neuen Verpachtungen"""
|
||||||
if self.groesse_qm > 0:
|
if self.groesse_qm and self.groesse_qm > 0:
|
||||||
return (self.get_verpachtete_flaeche_aktuell() / self.groesse_qm) * 100
|
return (self.get_verpachtete_flaeche_aktuell() / self.groesse_qm) * 100
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@@ -3279,3 +3279,178 @@ class DestinataerEmailEingang(models.Model):
|
|||||||
f"{base}/documents/{doc_id}/"
|
f"{base}/documents/{doc_id}/"
|
||||||
for doc_id in (self.paperless_dokument_ids or [])
|
for doc_id in (self.paperless_dokument_ids or [])
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class Veranstaltung(models.Model):
|
||||||
|
"""Veranstaltungen der Stiftung, z.B. Stiftungsessen mit Rechnungslegung"""
|
||||||
|
|
||||||
|
STATUS_CHOICES = [
|
||||||
|
("geplant", "Geplant"),
|
||||||
|
("einladungen_versendet", "Einladungen versendet"),
|
||||||
|
("abgeschlossen", "Abgeschlossen"),
|
||||||
|
("abgesagt", "Abgesagt"),
|
||||||
|
]
|
||||||
|
|
||||||
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
|
titel = models.CharField(max_length=200, verbose_name="Titel")
|
||||||
|
datum = models.DateField(verbose_name="Datum")
|
||||||
|
uhrzeit = models.TimeField(null=True, blank=True, verbose_name="Uhrzeit")
|
||||||
|
ort = models.CharField(max_length=200, verbose_name="Ort / Gasthaus")
|
||||||
|
adresse = models.TextField(blank=True, verbose_name="Adresse Gasthaus")
|
||||||
|
beschreibung = models.TextField(blank=True, verbose_name="Beschreibung / Zweck")
|
||||||
|
status = models.CharField(
|
||||||
|
max_length=30,
|
||||||
|
choices=STATUS_CHOICES,
|
||||||
|
default="geplant",
|
||||||
|
verbose_name="Status",
|
||||||
|
)
|
||||||
|
budget_pro_person = models.DecimalField(
|
||||||
|
max_digits=8,
|
||||||
|
decimal_places=2,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="Budget pro Person (€)",
|
||||||
|
help_text="Geschätztes Budget je Teilnehmer in €",
|
||||||
|
)
|
||||||
|
briefvorlage = models.TextField(
|
||||||
|
blank=True,
|
||||||
|
verbose_name="Briefvorlage",
|
||||||
|
help_text=(
|
||||||
|
"HTML/Text-Template für Serienbrief. Platzhalter: "
|
||||||
|
"{{ anrede }}, {{ vorname }}, {{ nachname }}, {{ strasse }}, "
|
||||||
|
"{{ plz }}, {{ ort }}, {{ datum }}, {{ uhrzeit }}, "
|
||||||
|
"{{ veranstaltungsort }}, {{ gasthaus_adresse }}"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
betreff = models.CharField(
|
||||||
|
max_length=300,
|
||||||
|
blank=True,
|
||||||
|
verbose_name="Betreff",
|
||||||
|
help_text="Betreffzeile des Serienbriefs. Leer = Standardbetreff.",
|
||||||
|
)
|
||||||
|
unterschrift_1_name = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
blank=True,
|
||||||
|
default="Katrin Kleinpaß",
|
||||||
|
verbose_name="Unterschrift 1 – Name",
|
||||||
|
)
|
||||||
|
unterschrift_1_titel = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
blank=True,
|
||||||
|
default="Rentmeisterin",
|
||||||
|
verbose_name="Unterschrift 1 – Titel",
|
||||||
|
)
|
||||||
|
unterschrift_2_name = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
blank=True,
|
||||||
|
default="Jan Remmer Siebels",
|
||||||
|
verbose_name="Unterschrift 2 – Name",
|
||||||
|
)
|
||||||
|
unterschrift_2_titel = models.CharField(
|
||||||
|
max_length=100,
|
||||||
|
blank=True,
|
||||||
|
default="Rentmeister",
|
||||||
|
verbose_name="Unterschrift 2 – Titel",
|
||||||
|
)
|
||||||
|
|
||||||
|
erstellt_am = models.DateTimeField(auto_now_add=True)
|
||||||
|
aktualisiert_am = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Veranstaltung"
|
||||||
|
verbose_name_plural = "Veranstaltungen"
|
||||||
|
ordering = ["-datum"]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.titel} ({self.datum})"
|
||||||
|
|
||||||
|
def get_teilnehmer_count(self):
|
||||||
|
return self.teilnehmer.count()
|
||||||
|
|
||||||
|
def get_zugesagte_count(self):
|
||||||
|
return self.teilnehmer.filter(rsvp_status="zugesagt").count()
|
||||||
|
|
||||||
|
def get_abgesagte_count(self):
|
||||||
|
return self.teilnehmer.filter(rsvp_status="abgesagt").count()
|
||||||
|
|
||||||
|
def get_keine_rueckmeldung_count(self):
|
||||||
|
return self.teilnehmer.filter(rsvp_status="keine_rueckmeldung").count()
|
||||||
|
|
||||||
|
|
||||||
|
class Veranstaltungsteilnehmer(models.Model):
|
||||||
|
"""Teilnehmer einer Veranstaltung – primär freie Eingabe für Familienmitglieder"""
|
||||||
|
|
||||||
|
ANREDE_CHOICES = [
|
||||||
|
("Herr", "Herr"),
|
||||||
|
("Frau", "Frau"),
|
||||||
|
("", "Keine Anrede"),
|
||||||
|
]
|
||||||
|
|
||||||
|
RSVP_CHOICES = [
|
||||||
|
("eingeladen", "Eingeladen"),
|
||||||
|
("zugesagt", "Zugesagt"),
|
||||||
|
("abgesagt", "Abgesagt"),
|
||||||
|
("keine_rueckmeldung", "Keine Rückmeldung"),
|
||||||
|
]
|
||||||
|
|
||||||
|
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||||
|
veranstaltung = models.ForeignKey(
|
||||||
|
Veranstaltung,
|
||||||
|
on_delete=models.CASCADE,
|
||||||
|
related_name="teilnehmer",
|
||||||
|
verbose_name="Veranstaltung",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Optionale Verknüpfung zu bestehenden Datensätzen
|
||||||
|
paechter = models.ForeignKey(
|
||||||
|
"Paechter",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
verbose_name="Pächter (optional)",
|
||||||
|
)
|
||||||
|
destinataer = models.ForeignKey(
|
||||||
|
"Destinataer",
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
verbose_name="Destinatär (optional)",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Freie Felder (Pflichtfelder für Serienbrief)
|
||||||
|
anrede = models.CharField(
|
||||||
|
max_length=10, choices=ANREDE_CHOICES, blank=True, verbose_name="Anrede"
|
||||||
|
)
|
||||||
|
vorname = models.CharField(max_length=100, verbose_name="Vorname")
|
||||||
|
nachname = models.CharField(max_length=100, verbose_name="Nachname")
|
||||||
|
strasse = models.CharField(max_length=200, blank=True, verbose_name="Straße")
|
||||||
|
plz = models.CharField(max_length=10, blank=True, verbose_name="PLZ")
|
||||||
|
ort = models.CharField(max_length=100, blank=True, verbose_name="Ort")
|
||||||
|
email = models.EmailField(
|
||||||
|
blank=True, verbose_name="E-Mail", help_text="Optional, für späteren E-Mail-Versand"
|
||||||
|
)
|
||||||
|
|
||||||
|
rsvp_status = models.CharField(
|
||||||
|
max_length=20,
|
||||||
|
choices=RSVP_CHOICES,
|
||||||
|
default="eingeladen",
|
||||||
|
verbose_name="RSVP-Status",
|
||||||
|
)
|
||||||
|
bemerkungen = models.TextField(blank=True, verbose_name="Bemerkungen")
|
||||||
|
|
||||||
|
erstellt_am = models.DateTimeField(auto_now_add=True)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
verbose_name = "Veranstaltungsteilnehmer"
|
||||||
|
verbose_name_plural = "Veranstaltungsteilnehmer"
|
||||||
|
ordering = ["nachname", "vorname"]
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return f"{self.anrede} {self.vorname} {self.nachname}".strip()
|
||||||
|
|
||||||
|
def get_full_name(self):
|
||||||
|
return f"{self.vorname} {self.nachname}".strip()
|
||||||
|
|
||||||
|
def get_full_address(self):
|
||||||
|
parts = [self.strasse, f"{self.plz} {self.ort}".strip()]
|
||||||
|
return ", ".join(p for p in parts if p)
|
||||||
|
|||||||
@@ -359,6 +359,14 @@ urlpatterns = [
|
|||||||
views.paperless_document_redirect,
|
views.paperless_document_redirect,
|
||||||
name="paperless_document_redirect",
|
name="paperless_document_redirect",
|
||||||
),
|
),
|
||||||
|
# Veranstaltungsmodul
|
||||||
|
path("veranstaltungen/", views.veranstaltung_list, name="veranstaltung_list"),
|
||||||
|
path("veranstaltungen/<uuid:pk>/", views.veranstaltung_detail, name="veranstaltung_detail"),
|
||||||
|
path(
|
||||||
|
"veranstaltungen/<uuid:pk>/serienbrief/",
|
||||||
|
views.veranstaltung_serienbrief_pdf,
|
||||||
|
name="veranstaltung_serienbrief_pdf",
|
||||||
|
),
|
||||||
# Gramps integration (probe)
|
# Gramps integration (probe)
|
||||||
path("api/gramps/search/", views.gramps_search_api, name="gramps_search_api"),
|
path("api/gramps/search/", views.gramps_search_api, name="gramps_search_api"),
|
||||||
path("api/gramps/debug/", views.gramps_debug_api, name="gramps_debug_api"),
|
path("api/gramps/debug/", views.gramps_debug_api, name="gramps_debug_api"),
|
||||||
|
|||||||
@@ -31,7 +31,8 @@ from .models import (AppConfiguration, CSVImport, Destinataer,
|
|||||||
DestinataerEmailEingang, DestinataerUnterstuetzung,
|
DestinataerEmailEingang, DestinataerUnterstuetzung,
|
||||||
DokumentLink, Foerderung, Land,
|
DokumentLink, Foerderung, Land,
|
||||||
LandAbrechnung, LandVerpachtung, Paechter, Person,
|
LandAbrechnung, LandVerpachtung, Paechter, Person,
|
||||||
StiftungsKonto, UnterstuetzungWiederkehrend, VierteljahresNachweis)
|
StiftungsKonto, UnterstuetzungWiederkehrend, Veranstaltung,
|
||||||
|
Veranstaltungsteilnehmer, VierteljahresNachweis)
|
||||||
|
|
||||||
|
|
||||||
def get_pdf_generator():
|
def get_pdf_generator():
|
||||||
@@ -279,7 +280,7 @@ def paperless_document_redirect(_request, doc_id: int):
|
|||||||
return Response({"error": "Paperless API not configured"}, status=400)
|
return Response({"error": "Paperless API not configured"}, status=400)
|
||||||
|
|
||||||
# Remove /api suffix if present, then construct the document URL
|
# Remove /api suffix if present, then construct the document URL
|
||||||
base_url = url.rstrip("/api") if url.endswith("/api") else url
|
base_url = url[:-4] if url.endswith("/api") else url
|
||||||
|
|
||||||
# For external Paperless (already includes /paperless/ in base URL)
|
# For external Paperless (already includes /paperless/ in base URL)
|
||||||
return redirect(f"{base_url}/documents/{doc_id}/details/")
|
return redirect(f"{base_url}/documents/{doc_id}/details/")
|
||||||
@@ -1930,7 +1931,6 @@ def verpachtung_list(request):
|
|||||||
return render(request, "stiftung/verpachtung_list.html", context)
|
return render(request, "stiftung/verpachtung_list.html", context)
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
@login_required
|
@login_required
|
||||||
def land_verpachtung_detail(request, pk):
|
def land_verpachtung_detail(request, pk):
|
||||||
"""Detail view for LandVerpachtung"""
|
"""Detail view for LandVerpachtung"""
|
||||||
@@ -2197,7 +2197,7 @@ def dokument_list(request):
|
|||||||
available_dokumente = []
|
available_dokumente = []
|
||||||
if url and token:
|
if url and token:
|
||||||
try:
|
try:
|
||||||
base_url = url.rstrip("/api") if url.endswith("/api") else url
|
base_url = url[:-4] if url.endswith("/api") else url
|
||||||
headers = {"Authorization": f"Token {token}"}
|
headers = {"Authorization": f"Token {token}"}
|
||||||
|
|
||||||
# Alle verfügbaren Dokumente abrufen (mit Paginierung)
|
# Alle verfügbaren Dokumente abrufen (mit Paginierung)
|
||||||
@@ -2553,7 +2553,7 @@ def paperless_ping(_request):
|
|||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
# Entferne /api vom Ende der URL falls vorhanden
|
# Entferne /api vom Ende der URL falls vorhanden
|
||||||
base_url = url.rstrip("/api") if url.endswith("/api") else url
|
base_url = url[:-4] if url.endswith("/api") else url
|
||||||
r = requests.get(
|
r = requests.get(
|
||||||
f"{base_url}/api/tags/",
|
f"{base_url}/api/tags/",
|
||||||
headers={"Authorization": f"Token {token}"},
|
headers={"Authorization": f"Token {token}"},
|
||||||
@@ -2598,7 +2598,7 @@ def paperless_documents(request):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Entferne /api vom Ende der URL falls vorhanden
|
# Entferne /api vom Ende der URL falls vorhanden
|
||||||
base_url = url.rstrip("/api") if url.endswith("/api") else url
|
base_url = url[:-4] if url.endswith("/api") else url
|
||||||
headers = {"Authorization": f"Token {token}"}
|
headers = {"Authorization": f"Token {token}"}
|
||||||
|
|
||||||
def fetch_tagged():
|
def fetch_tagged():
|
||||||
@@ -2735,7 +2735,7 @@ def paperless_debug(request):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Entferne /api vom Ende der URL falls vorhanden
|
# Entferne /api vom Ende der URL falls vorhanden
|
||||||
base_url = url.rstrip("/api") if url.endswith("/api") else url
|
base_url = url[:-4] if url.endswith("/api") else url
|
||||||
|
|
||||||
headers = {"Authorization": f"Token {token}"}
|
headers = {"Authorization": f"Token {token}"}
|
||||||
|
|
||||||
@@ -2857,7 +2857,7 @@ def paperless_tags_only(request):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
# Entferne /api vom Ende der URL falls vorhanden
|
# Entferne /api vom Ende der URL falls vorhanden
|
||||||
base_url = url.rstrip("/api") if url.endswith("/api") else url
|
base_url = url[:-4] if url.endswith("/api") else url
|
||||||
|
|
||||||
# Alle Tags abrufen (mit großer page_size)
|
# Alle Tags abrufen (mit großer page_size)
|
||||||
headers = {"Authorization": f"Token {token}"}
|
headers = {"Authorization": f"Token {token}"}
|
||||||
@@ -4147,7 +4147,6 @@ def verwaltungskosten_create(request):
|
|||||||
rentmeister = Rentmeister.objects.get(pk=rentmeister_id)
|
rentmeister = Rentmeister.objects.get(pk=rentmeister_id)
|
||||||
initial_data["rentmeister"] = rentmeister
|
initial_data["rentmeister"] = rentmeister
|
||||||
redirect_url = "stiftung:rentmeister_detail"
|
redirect_url = "stiftung:rentmeister_detail"
|
||||||
redirect_args = [rentmeister_id]
|
|
||||||
except Rentmeister.DoesNotExist:
|
except Rentmeister.DoesNotExist:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -8440,13 +8439,6 @@ def kalender_admin(request):
|
|||||||
}
|
}
|
||||||
|
|
||||||
return render(request, 'stiftung/kalender/admin.html', context)
|
return render(request, 'stiftung/kalender/admin.html', context)
|
||||||
|
|
||||||
context = {
|
|
||||||
'title': f'Löschen: {event.titel}',
|
|
||||||
'event': event,
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(request, 'stiftung/kalender/delete.html', context)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@@ -8642,3 +8634,54 @@ def email_eingang_poll_trigger(request):
|
|||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
messages.error(request, f"Fehler beim Starten des Tasks: {exc}")
|
messages.error(request, f"Fehler beim Starten des Tasks: {exc}")
|
||||||
return redirect("email_eingang_list")
|
return redirect("email_eingang_list")
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Veranstaltungsmodul
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def veranstaltung_list(request):
|
||||||
|
"""Liste aller Veranstaltungen"""
|
||||||
|
veranstaltungen = Veranstaltung.objects.all()
|
||||||
|
return render(request, "stiftung/veranstaltung/list.html", {"veranstaltungen": veranstaltungen})
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def veranstaltung_detail(request, pk):
|
||||||
|
"""Detail-Ansicht einer Veranstaltung mit RSVP-Übersicht"""
|
||||||
|
veranstaltung = get_object_or_404(Veranstaltung, pk=pk)
|
||||||
|
teilnehmer = veranstaltung.teilnehmer.all()
|
||||||
|
context = {
|
||||||
|
"veranstaltung": veranstaltung,
|
||||||
|
"teilnehmer": teilnehmer,
|
||||||
|
"zugesagte": teilnehmer.filter(rsvp_status="zugesagt"),
|
||||||
|
"abgesagte": teilnehmer.filter(rsvp_status="abgesagt"),
|
||||||
|
"keine_rueckmeldung": teilnehmer.filter(rsvp_status="keine_rueckmeldung"),
|
||||||
|
"eingeladen": teilnehmer.filter(rsvp_status="eingeladen"),
|
||||||
|
}
|
||||||
|
return render(request, "stiftung/veranstaltung/detail.html", context)
|
||||||
|
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
def veranstaltung_serienbrief_pdf(request, pk):
|
||||||
|
"""Generiert Serienbrief-PDF für alle Teilnehmer einer Veranstaltung"""
|
||||||
|
from weasyprint import HTML
|
||||||
|
from django.template.loader import render_to_string
|
||||||
|
|
||||||
|
veranstaltung = get_object_or_404(Veranstaltung, pk=pk)
|
||||||
|
teilnehmer = veranstaltung.teilnehmer.all().order_by("nachname", "vorname")
|
||||||
|
|
||||||
|
# Render HTML for all letters
|
||||||
|
html_string = render_to_string(
|
||||||
|
"stiftung/veranstaltung/serienbrief_pdf.html",
|
||||||
|
{
|
||||||
|
"veranstaltung": veranstaltung,
|
||||||
|
"teilnehmer": teilnehmer,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
pdf = HTML(string=html_string).write_pdf()
|
||||||
|
filename = f"einladungen_{veranstaltung.datum}_{veranstaltung.titel[:30].replace(' ', '_')}.pdf"
|
||||||
|
response = HttpResponse(pdf, content_type="application/pdf")
|
||||||
|
response["Content-Disposition"] = f'attachment; filename="{filename}"'
|
||||||
|
return response
|
||||||
|
|||||||
@@ -25,9 +25,9 @@
|
|||||||
--orange-dark: #e8590c;
|
--orange-dark: #e8590c;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Global Typography - More Compact */
|
/* Global Typography */
|
||||||
html {
|
html {
|
||||||
font-size: 14px; /* Reduced from default 16px */
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -69,26 +69,6 @@
|
|||||||
padding-right: 0.5rem;
|
padding-right: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Compact margins */
|
|
||||||
.mb-1 { margin-bottom: 0.25rem !important; }
|
|
||||||
.mb-2 { margin-bottom: 0.4rem !important; }
|
|
||||||
.mb-3 { margin-bottom: 0.75rem !important; }
|
|
||||||
.mb-4 { margin-bottom: 1rem !important; }
|
|
||||||
.mb-5 { margin-bottom: 1.5rem !important; }
|
|
||||||
|
|
||||||
.mt-1 { margin-top: 0.25rem !important; }
|
|
||||||
.mt-2 { margin-top: 0.4rem !important; }
|
|
||||||
.mt-3 { margin-top: 0.75rem !important; }
|
|
||||||
.mt-4 { margin-top: 1rem !important; }
|
|
||||||
.mt-5 { margin-top: 1.5rem !important; }
|
|
||||||
|
|
||||||
.py-1 { padding-top: 0.25rem !important; padding-bottom: 0.25rem !important; }
|
|
||||||
.py-2 { padding-top: 0.4rem !important; padding-bottom: 0.4rem !important; }
|
|
||||||
.py-3 { padding-top: 0.75rem !important; padding-bottom: 0.75rem !important; }
|
|
||||||
|
|
||||||
.px-1 { padding-left: 0.25rem !important; padding-right: 0.25rem !important; }
|
|
||||||
.px-2 { padding-left: 0.4rem !important; padding-right: 0.4rem !important; }
|
|
||||||
.px-3 { padding-left: 0.75rem !important; padding-right: 0.75rem !important; }
|
|
||||||
|
|
||||||
.border-left-primary {
|
.border-left-primary {
|
||||||
border-left: 0.25rem solid var(--racing-green) !important;
|
border-left: 0.25rem solid var(--racing-green) !important;
|
||||||
@@ -209,19 +189,19 @@
|
|||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tables - More Compact */
|
/* Tables */
|
||||||
.table {
|
.table {
|
||||||
font-size: 0.8rem;
|
font-size: 0.85rem;
|
||||||
margin-bottom: 0.75rem;
|
margin-bottom: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table th {
|
.table th {
|
||||||
border-top: none;
|
border-top: none;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: var(--racing-green-dark);
|
color: var(--racing-green-dark);
|
||||||
background-color: var(--grey-light);
|
background-color: var(--grey-light);
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
font-size: 0.75rem;
|
font-size: 0.8rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.025em;
|
letter-spacing: 0.025em;
|
||||||
}
|
}
|
||||||
@@ -375,47 +355,61 @@
|
|||||||
color: var(--orange-dark);
|
color: var(--orange-dark);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive adjustments for very compact design */
|
/* Responsive adjustments */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
html {
|
html {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-brand {
|
.navbar-brand {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-nav .nav-link {
|
.navbar-nav .nav-link {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
padding: 0.25rem 0.375rem !important;
|
padding: 0.25rem 0.375rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-body {
|
.card-body {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table {
|
.table {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table th,
|
.table th,
|
||||||
.table td {
|
.table td {
|
||||||
padding: 0.375rem;
|
padding: 0.375rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) and (max-width: 1200px) {
|
||||||
|
html {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container, .container-lg {
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (min-width: 1400px) {
|
@media (min-width: 1400px) {
|
||||||
.container-lg {
|
.container-lg {
|
||||||
max-width: 1400px;
|
max-width: 1600px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -638,6 +632,23 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<!-- Veranstaltungen -->
|
||||||
|
<li class="nav-item dropdown">
|
||||||
|
<a class="nav-link dropdown-toggle" href="#" id="veranstaltungenDropdown" role="button" data-bs-toggle="dropdown" aria-expanded="false">
|
||||||
|
<i class="fas fa-glass-cheers me-1"></i>Veranstaltungen
|
||||||
|
</a>
|
||||||
|
<ul class="dropdown-menu" aria-labelledby="veranstaltungenDropdown">
|
||||||
|
<li><a class="dropdown-item" href="{% url 'stiftung:veranstaltung_list' %}">
|
||||||
|
<i class="fas fa-list me-2"></i>Alle Veranstaltungen
|
||||||
|
</a></li>
|
||||||
|
<li><hr class="dropdown-divider"></li>
|
||||||
|
<li><h6 class="dropdown-header">Aktionen</h6></li>
|
||||||
|
<li><a class="dropdown-item" href="{% url 'stiftung:veranstaltung_list' %}">
|
||||||
|
<i class="fas fa-envelope-open-text me-2"></i>Serienbrief drucken
|
||||||
|
</a></li>
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
|
||||||
<!-- Geschichte -->
|
<!-- Geschichte -->
|
||||||
<li class="nav-item">
|
<li class="nav-item">
|
||||||
<a class="nav-link" href="{% url 'stiftung:geschichte_list' %}">
|
<a class="nav-link" href="{% url 'stiftung:geschichte_list' %}">
|
||||||
|
|||||||
@@ -25,6 +25,10 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="col-md-4 text-end">
|
<div class="col-md-4 text-end">
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
|
<a href="https://www.tim-online.nrw.de/tim-online2/?WFS_gemarkung={{ land.gemarkung|urlencode }}&WFS_flur={{ land.flur|urlencode }}&WFS_flurstueck={{ land.flurstueck|urlencode }}"
|
||||||
|
class="btn btn-outline-success" title="TIM-Online NRW öffnen" target="_blank" rel="noopener">
|
||||||
|
<i class="fas fa-map-marked-alt me-2"></i>TIM-Online
|
||||||
|
</a>
|
||||||
<a href="{% url 'stiftung:land_update' land.pk %}" class="btn btn-warning">
|
<a href="{% url 'stiftung:land_update' land.pk %}" class="btn btn-warning">
|
||||||
<i class="fas fa-edit me-2"></i>Bearbeiten
|
<i class="fas fa-edit me-2"></i>Bearbeiten
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -90,7 +90,7 @@
|
|||||||
<div class="small text-muted">Grünland</div>
|
<div class="small text-muted">Grünland</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="height: 200px;">
|
<div style="min-height: 200px;">
|
||||||
<canvas id="usageChart"></canvas>
|
<canvas id="usageChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -110,7 +110,7 @@
|
|||||||
<div class="h6 mb-0">{{ stats.total_plots }}</div>
|
<div class="h6 mb-0">{{ stats.total_plots }}</div>
|
||||||
<div class="small text-muted">Grundstücke gesamt</div>
|
<div class="small text-muted">Grundstücke gesamt</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="height: 200px;">
|
<div style="min-height: 200px;">
|
||||||
<canvas id="sizesChart"></canvas>
|
<canvas id="sizesChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -136,7 +136,7 @@
|
|||||||
<div class="small text-muted">Verfügbar</div>
|
<div class="small text-muted">Verfügbar</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div style="height: 200px;">
|
<div style="min-height: 200px;">
|
||||||
<canvas id="verpachtungChart"></canvas>
|
<canvas id="verpachtungChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -245,15 +245,19 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="btn-group" role="group">
|
<div class="btn-group" role="group">
|
||||||
<a href="{% url 'stiftung:land_detail' land.pk %}"
|
<a href="{% url 'stiftung:land_detail' land.pk %}"
|
||||||
class="btn btn-sm btn-outline-primary" title="Anzeigen">
|
class="btn btn-sm btn-outline-primary" title="Anzeigen">
|
||||||
<i class="fas fa-eye"></i>
|
<i class="fas fa-eye"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'stiftung:land_update' land.pk %}"
|
<a href="https://www.tim-online.nrw.de/tim-online2/?WFS_gemarkung={{ land.gemarkung|urlencode }}&WFS_flur={{ land.flur|urlencode }}&WFS_flurstueck={{ land.flurstueck|urlencode }}"
|
||||||
|
class="btn btn-sm btn-outline-success" title="TIM-Online NRW" target="_blank" rel="noopener">
|
||||||
|
<i class="fas fa-map-marked-alt"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'stiftung:land_update' land.pk %}"
|
||||||
class="btn btn-sm btn-outline-warning" title="Bearbeiten">
|
class="btn btn-sm btn-outline-warning" title="Bearbeiten">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
</a>
|
</a>
|
||||||
<a href="{% url 'stiftung:land_delete' land.pk %}"
|
<a href="{% url 'stiftung:land_delete' land.pk %}"
|
||||||
class="btn btn-sm btn-outline-danger" title="Löschen">
|
class="btn btn-sm btn-outline-danger" title="Löschen">
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
199
app/templates/stiftung/veranstaltung/detail.html
Normal file
199
app/templates/stiftung/veranstaltung/detail.html
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}{{ veranstaltung.titel }} – Stiftungsverwaltung{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid py-4">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="d-flex justify-content-between align-items-start mb-4">
|
||||||
|
<div>
|
||||||
|
<nav aria-label="breadcrumb">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item"><a href="{% url 'stiftung:veranstaltung_list' %}">Veranstaltungen</a></li>
|
||||||
|
<li class="breadcrumb-item active">{{ veranstaltung.titel }}</li>
|
||||||
|
</ol>
|
||||||
|
</nav>
|
||||||
|
<h1 class="h3 mb-1">{{ veranstaltung.titel }}</h1>
|
||||||
|
<p class="text-muted mb-0">
|
||||||
|
{{ veranstaltung.datum|date:"l, d. F Y" }}{% if veranstaltung.uhrzeit %}, {{ veranstaltung.uhrzeit|time:"H:i" }} Uhr{% endif %}
|
||||||
|
· {{ veranstaltung.ort }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex gap-2">
|
||||||
|
<a href="{% url 'admin:stiftung_veranstaltung_change' veranstaltung.pk %}" class="btn btn-outline-secondary">
|
||||||
|
<i class="fas fa-edit me-1"></i>Bearbeiten
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'stiftung:veranstaltung_serienbrief_pdf' veranstaltung.pk %}" class="btn btn-success">
|
||||||
|
<i class="fas fa-file-pdf me-1"></i>Serienbrief-PDF
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row g-4">
|
||||||
|
<!-- Veranstaltungsdetails -->
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card shadow-sm h-100">
|
||||||
|
<div class="card-header bg-dark text-white">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>Details
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<dl class="row mb-0">
|
||||||
|
<dt class="col-sm-5">Status</dt>
|
||||||
|
<dd class="col-sm-7">
|
||||||
|
{% if veranstaltung.status == "geplant" %}
|
||||||
|
<span class="badge bg-secondary">Geplant</span>
|
||||||
|
{% elif veranstaltung.status == "einladungen_versendet" %}
|
||||||
|
<span class="badge bg-primary">Einladungen versendet</span>
|
||||||
|
{% elif veranstaltung.status == "abgeschlossen" %}
|
||||||
|
<span class="badge bg-success">Abgeschlossen</span>
|
||||||
|
{% elif veranstaltung.status == "abgesagt" %}
|
||||||
|
<span class="badge bg-danger">Abgesagt</span>
|
||||||
|
{% endif %}
|
||||||
|
</dd>
|
||||||
|
<dt class="col-sm-5">Gasthaus</dt>
|
||||||
|
<dd class="col-sm-7">{{ veranstaltung.ort }}</dd>
|
||||||
|
{% if veranstaltung.adresse %}
|
||||||
|
<dt class="col-sm-5">Adresse</dt>
|
||||||
|
<dd class="col-sm-7">{{ veranstaltung.adresse }}</dd>
|
||||||
|
{% endif %}
|
||||||
|
{% if veranstaltung.budget_pro_person %}
|
||||||
|
<dt class="col-sm-5">Budget/Person</dt>
|
||||||
|
<dd class="col-sm-7">{{ veranstaltung.budget_pro_person }} €</dd>
|
||||||
|
{% endif %}
|
||||||
|
{% if veranstaltung.beschreibung %}
|
||||||
|
<dt class="col-sm-5">Beschreibung</dt>
|
||||||
|
<dd class="col-sm-7">{{ veranstaltung.beschreibung }}</dd>
|
||||||
|
{% endif %}
|
||||||
|
</dl>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- RSVP-Statistik -->
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card shadow-sm h-100">
|
||||||
|
<div class="card-header bg-dark text-white">
|
||||||
|
<i class="fas fa-chart-pie me-2"></i>RSVP-Übersicht
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="row text-center g-3">
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="border rounded p-3">
|
||||||
|
<div class="fs-2 fw-bold text-primary">{{ teilnehmer.count }}</div>
|
||||||
|
<div class="small text-muted">Eingeladen gesamt</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="border rounded p-3">
|
||||||
|
<div class="fs-2 fw-bold text-success">{{ zugesagte.count }}</div>
|
||||||
|
<div class="small text-muted">Zugesagt</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="border rounded p-3">
|
||||||
|
<div class="fs-2 fw-bold text-danger">{{ abgesagte.count }}</div>
|
||||||
|
<div class="small text-muted">Abgesagt</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-6">
|
||||||
|
<div class="border rounded p-3">
|
||||||
|
<div class="fs-2 fw-bold text-warning">{{ keine_rueckmeldung.count }}</div>
|
||||||
|
<div class="small text-muted">Keine Rückmeldung</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if eingeladen.count %}
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="border rounded p-3">
|
||||||
|
<div class="fs-2 fw-bold text-secondary">{{ eingeladen.count }}</div>
|
||||||
|
<div class="small text-muted">Nur eingeladen (noch kein RSVP)</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Schnellaktionen -->
|
||||||
|
<div class="col-lg-4">
|
||||||
|
<div class="card shadow-sm h-100">
|
||||||
|
<div class="card-header bg-dark text-white">
|
||||||
|
<i class="fas fa-tools me-2"></i>Aktionen
|
||||||
|
</div>
|
||||||
|
<div class="card-body d-flex flex-column gap-2">
|
||||||
|
<a href="{% url 'stiftung:veranstaltung_serienbrief_pdf' veranstaltung.pk %}"
|
||||||
|
class="btn btn-success w-100">
|
||||||
|
<i class="fas fa-file-pdf me-2"></i>Serienbrief-PDF (alle Teilnehmer)
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'admin:stiftung_veranstaltungsteilnehmer_add' %}?veranstaltung={{ veranstaltung.pk }}"
|
||||||
|
class="btn btn-outline-primary w-100">
|
||||||
|
<i class="fas fa-user-plus me-2"></i>Teilnehmer hinzufügen
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'admin:stiftung_veranstaltung_change' veranstaltung.pk %}"
|
||||||
|
class="btn btn-outline-secondary w-100">
|
||||||
|
<i class="fas fa-edit me-2"></i>Veranstaltung bearbeiten
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Teilnehmerliste -->
|
||||||
|
<div class="card shadow-sm mt-4">
|
||||||
|
<div class="card-header bg-dark text-white d-flex justify-content-between align-items-center">
|
||||||
|
<span><i class="fas fa-users me-2"></i>Teilnehmerliste ({{ teilnehmer.count }})</span>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
{% if teilnehmer %}
|
||||||
|
<table class="table table-hover mb-0">
|
||||||
|
<thead class="table-light">
|
||||||
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>Adresse</th>
|
||||||
|
<th>E-Mail</th>
|
||||||
|
<th>RSVP</th>
|
||||||
|
<th>Bemerkungen</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for t in teilnehmer %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
{{ t.anrede }} {{ t.vorname }} {{ t.nachname }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% if t.strasse %}{{ t.strasse }},{% endif %}
|
||||||
|
{% if t.plz %}{{ t.plz }}{% endif %}
|
||||||
|
{% if t.ort %}{{ t.ort }}{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{% if t.email %}<a href="mailto:{{ t.email }}">{{ t.email }}</a>{% else %}–{% endif %}</td>
|
||||||
|
<td>
|
||||||
|
{% if t.rsvp_status == "eingeladen" %}
|
||||||
|
<span class="badge bg-secondary">Eingeladen</span>
|
||||||
|
{% elif t.rsvp_status == "zugesagt" %}
|
||||||
|
<span class="badge bg-success">Zugesagt</span>
|
||||||
|
{% elif t.rsvp_status == "abgesagt" %}
|
||||||
|
<span class="badge bg-danger">Abgesagt</span>
|
||||||
|
{% elif t.rsvp_status == "keine_rueckmeldung" %}
|
||||||
|
<span class="badge bg-warning text-dark">Keine Rückmeldung</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ t.bemerkungen|default:"–" }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
{% else %}
|
||||||
|
<div class="p-4 text-center text-muted">
|
||||||
|
<i class="fas fa-users fa-2x mb-2"></i>
|
||||||
|
<p>Noch keine Teilnehmer eingetragen.</p>
|
||||||
|
<a href="{% url 'admin:stiftung_veranstaltungsteilnehmer_add' %}?veranstaltung={{ veranstaltung.pk }}"
|
||||||
|
class="btn btn-primary">
|
||||||
|
<i class="fas fa-user-plus me-1"></i>Ersten Teilnehmer hinzufügen
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
75
app/templates/stiftung/veranstaltung/list.html
Normal file
75
app/templates/stiftung/veranstaltung/list.html
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
|
||||||
|
{% block title %}Veranstaltungen – Stiftungsverwaltung{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
<div class="container-fluid py-4">
|
||||||
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||||
|
<h1 class="h3 mb-0">
|
||||||
|
<i class="fas fa-calendar-alt me-2"></i>Veranstaltungen
|
||||||
|
</h1>
|
||||||
|
<a href="{% url 'admin:stiftung_veranstaltung_add' %}" class="btn btn-primary">
|
||||||
|
<i class="fas fa-plus me-1"></i>Neue Veranstaltung
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if veranstaltungen %}
|
||||||
|
<div class="card shadow-sm">
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<table class="table table-hover mb-0">
|
||||||
|
<thead class="table-dark">
|
||||||
|
<tr>
|
||||||
|
<th>Titel</th>
|
||||||
|
<th>Datum</th>
|
||||||
|
<th>Ort / Gasthaus</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Teilnehmer</th>
|
||||||
|
<th>Zugesagt</th>
|
||||||
|
<th>Aktionen</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% for v in veranstaltungen %}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'stiftung:veranstaltung_detail' v.pk %}">
|
||||||
|
<strong>{{ v.titel }}</strong>
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
<td>{{ v.datum|date:"d.m.Y" }}{% if v.uhrzeit %}, {{ v.uhrzeit|time:"H:i" }} Uhr{% endif %}</td>
|
||||||
|
<td>{{ v.ort }}</td>
|
||||||
|
<td>
|
||||||
|
{% if v.status == "geplant" %}
|
||||||
|
<span class="badge bg-secondary">Geplant</span>
|
||||||
|
{% elif v.status == "einladungen_versendet" %}
|
||||||
|
<span class="badge bg-primary">Einladungen versendet</span>
|
||||||
|
{% elif v.status == "abgeschlossen" %}
|
||||||
|
<span class="badge bg-success">Abgeschlossen</span>
|
||||||
|
{% elif v.status == "abgesagt" %}
|
||||||
|
<span class="badge bg-danger">Abgesagt</span>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td>{{ v.get_teilnehmer_count }}</td>
|
||||||
|
<td>{{ v.get_zugesagte_count }}</td>
|
||||||
|
<td>
|
||||||
|
<a href="{% url 'stiftung:veranstaltung_detail' v.pk %}" class="btn btn-sm btn-outline-secondary me-1">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
</a>
|
||||||
|
<a href="{% url 'stiftung:veranstaltung_serienbrief_pdf' v.pk %}" class="btn btn-sm btn-outline-success">
|
||||||
|
<i class="fas fa-file-pdf"></i> Serienbrief
|
||||||
|
</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="alert alert-info">
|
||||||
|
<i class="fas fa-info-circle me-2"></i>Noch keine Veranstaltungen angelegt.
|
||||||
|
<a href="{% url 'admin:stiftung_veranstaltung_add' %}">Jetzt erste Veranstaltung erstellen.</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
197
app/templates/stiftung/veranstaltung/serienbrief_pdf.html
Normal file
197
app/templates/stiftung/veranstaltung/serienbrief_pdf.html
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>Einladungen – {{ veranstaltung.titel }}</title>
|
||||||
|
<style>
|
||||||
|
@page {
|
||||||
|
size: A4;
|
||||||
|
margin: 2cm 2.5cm 2cm 2.5cm;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: "Times New Roman", Times, serif;
|
||||||
|
font-size: 10pt;
|
||||||
|
line-height: 1.35;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.letter {
|
||||||
|
page-break-after: always;
|
||||||
|
}
|
||||||
|
|
||||||
|
.letter:last-child {
|
||||||
|
page-break-after: avoid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Absenderzeile (klein, über Adressfeld) */
|
||||||
|
.absender-zeile {
|
||||||
|
font-size: 7.5pt;
|
||||||
|
border-bottom: 1px solid #000;
|
||||||
|
margin-bottom: 3pt;
|
||||||
|
padding-bottom: 1pt;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Empfängeradresse */
|
||||||
|
.empfaenger {
|
||||||
|
min-height: 35mm;
|
||||||
|
margin-bottom: 5mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empfaenger p {
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Datum und Ort */
|
||||||
|
.datum-zeile {
|
||||||
|
text-align: right;
|
||||||
|
margin-bottom: 4mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Betreff */
|
||||||
|
.betreff {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 4mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Brieftext */
|
||||||
|
.brieftext p {
|
||||||
|
margin: 0 0 3mm 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Veranstaltungsblock (eingerückt) */
|
||||||
|
.veranstaltungs-block {
|
||||||
|
margin: 4mm 0 4mm 10mm;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Unterschrift */
|
||||||
|
.unterschrift {
|
||||||
|
margin-top: 10mm;
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unterschrift-person {
|
||||||
|
display: inline-block;
|
||||||
|
width: 45%;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unterschrift-linie {
|
||||||
|
border-top: 1px solid #000;
|
||||||
|
margin-bottom: 2mm;
|
||||||
|
width: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stiftungsname-header {
|
||||||
|
font-size: 12pt;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: 1mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
.stiftungsadresse {
|
||||||
|
font-size: 8.5pt;
|
||||||
|
color: #444;
|
||||||
|
margin-bottom: 5mm;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
{% for t in teilnehmer %}
|
||||||
|
<div class="letter">
|
||||||
|
|
||||||
|
<!-- Stiftungskopf -->
|
||||||
|
<div class="stiftungsname-header">van Hees-Theyssen-Vogel'sche Stiftung</div>
|
||||||
|
<div class="stiftungsadresse">
|
||||||
|
Raesfelder Str. 3 · 46499 Hamminkeln
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Empfänger -->
|
||||||
|
<div class="empfaenger">
|
||||||
|
<div class="absender-zeile">van Hees-Theyssen-Vogel'sche Stiftung · Raesfelder Str. 3 · 46499 Hamminkeln</div>
|
||||||
|
<p>{{ t.anrede }} {{ t.vorname }} {{ t.nachname }}</p>
|
||||||
|
{% if t.strasse %}<p>{{ t.strasse }}</p>{% endif %}
|
||||||
|
{% if t.plz or t.ort %}<p>{{ t.plz }} {{ t.ort }}</p>{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Datum -->
|
||||||
|
<div class="datum-zeile">
|
||||||
|
Hamminkeln, den {{ veranstaltung.datum|date:"j. F Y" }}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Betreff -->
|
||||||
|
<div class="betreff">
|
||||||
|
{% if veranstaltung.betreff %}{{ veranstaltung.betreff }}{% else %}Einladung zum {{ veranstaltung.titel }}{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Anrede -->
|
||||||
|
<div class="brieftext">
|
||||||
|
<p>
|
||||||
|
Sehr geehrte{% if t.anrede == "Herr" %}r Herr{% elif t.anrede == "Frau" %} Frau{% else %}
|
||||||
|
{{ t.anrede }}{% endif %} {{ t.nachname }},
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Entweder freie Briefvorlage oder Standardtext -->
|
||||||
|
{% if veranstaltung.briefvorlage %}
|
||||||
|
{{ veranstaltung.briefvorlage|safe }}
|
||||||
|
{% else %}
|
||||||
|
<p>
|
||||||
|
wir laden Sie herzlich ein, an der jährlichen Vorstellung der Rechnungslegung
|
||||||
|
der van Hees-Theyssen-Vogel'schen Stiftung teilzunehmen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Die Veranstaltung findet statt am:</p>
|
||||||
|
|
||||||
|
<div class="veranstaltungs-block">
|
||||||
|
{{ veranstaltung.datum|date:"l, j. F Y" }}{% if veranstaltung.uhrzeit %}, {{ veranstaltung.uhrzeit|time:"H:i" }} Uhr{% endif %}<br>
|
||||||
|
{{ veranstaltung.ort }}<br>
|
||||||
|
{% if veranstaltung.adresse %}{{ veranstaltung.adresse }}{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Am Abend werden wir Ihnen einen Überblick über das abgelaufene Wirtschaftsjahr 2025
|
||||||
|
der Stiftung geben und gemeinsam das Abendessen genießen. Es bietet sich die
|
||||||
|
Gelegenheit zum persönlichen Austausch.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Bitte teilen Sie uns bis zum <strong>4. April 2026</strong> mit, ob Sie an der
|
||||||
|
Veranstaltung teilnehmen werden. Eine Rückmeldung per Post an die oben genannte
|
||||||
|
Adresse ist erbeten.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>Wir freuen uns auf Ihr Kommen.</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<p>Mit freundlichen Grüßen</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Unterschriften -->
|
||||||
|
<div class="unterschrift">
|
||||||
|
{% if veranstaltung.unterschrift_1_name %}
|
||||||
|
<div class="unterschrift-person">
|
||||||
|
<div class="unterschrift-linie"></div>
|
||||||
|
{{ veranstaltung.unterschrift_1_name }}<br>
|
||||||
|
{{ veranstaltung.unterschrift_1_titel }}<br>
|
||||||
|
van Hees-Theyssen-Vogel'sche Stiftung
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if veranstaltung.unterschrift_2_name %}
|
||||||
|
<div class="unterschrift-person">
|
||||||
|
<div class="unterschrift-linie"></div>
|
||||||
|
{{ veranstaltung.unterschrift_2_name }}<br>
|
||||||
|
{{ veranstaltung.unterschrift_2_titel }}<br>
|
||||||
|
van Hees-Theyssen-Vogel'sche Stiftung
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -7,6 +7,7 @@ services:
|
|||||||
POSTGRES_PASSWORD: postgres_dev
|
POSTGRES_PASSWORD: postgres_dev
|
||||||
volumes:
|
volumes:
|
||||||
- dbdata_dev:/var/lib/postgresql/data
|
- dbdata_dev:/var/lib/postgresql/data
|
||||||
|
- ./scripts/init-paperless-db.sh:/docker-entrypoint-initdb.d/init-paperless-db.sh
|
||||||
ports:
|
ports:
|
||||||
- "5433:5432"
|
- "5433:5432"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
@@ -40,7 +41,7 @@ services:
|
|||||||
- TIME_ZONE=Europe/Berlin
|
- TIME_ZONE=Europe/Berlin
|
||||||
- REDIS_URL=redis://redis:6379/0
|
- REDIS_URL=redis://redis:6379/0
|
||||||
- PAPERLESS_API_URL=http://paperless:8000
|
- PAPERLESS_API_URL=http://paperless:8000
|
||||||
- PAPERLESS_API_TOKEN=d477152aca264ea00620910ac09a06f0a4faaecc
|
- PAPERLESS_API_TOKEN=1972509e25810d9ae7497c1c79ecfea9e942f18d
|
||||||
- PAPERLESS_REQUIRED_TAG=Stiftung_Destinatäre
|
- PAPERLESS_REQUIRED_TAG=Stiftung_Destinatäre
|
||||||
- PAPERLESS_LAND_TAG=Stiftung_Land_und_Pächter
|
- PAPERLESS_LAND_TAG=Stiftung_Land_und_Pächter
|
||||||
- PAPERLESS_ADMIN_TAG=Stiftung_Administration
|
- PAPERLESS_ADMIN_TAG=Stiftung_Administration
|
||||||
@@ -64,12 +65,12 @@ services:
|
|||||||
- PAPERLESS_REDIS=redis://redis:6379
|
- PAPERLESS_REDIS=redis://redis:6379
|
||||||
- PAPERLESS_DBHOST=db
|
- PAPERLESS_DBHOST=db
|
||||||
- PAPERLESS_DBPORT=5432
|
- PAPERLESS_DBPORT=5432
|
||||||
- PAPERLESS_DBNAME=stiftung_dev
|
- PAPERLESS_DBNAME=paperless_dev
|
||||||
- PAPERLESS_DBUSER=postgres
|
- PAPERLESS_DBUSER=postgres
|
||||||
- PAPERLESS_DBPASS=postgres_dev
|
- PAPERLESS_DBPASS=postgres_dev
|
||||||
- PAPERLESS_SECRET_KEY=dev-paperless-secret-key
|
- PAPERLESS_SECRET_KEY=dev-paperless-secret-key
|
||||||
- PAPERLESS_URL=http://localhost:8082
|
- PAPERLESS_URL=http://localhost:8082
|
||||||
- PAPERLESS_ALLOWED_HOSTS=localhost,127.0.0.1
|
- PAPERLESS_ALLOWED_HOSTS=localhost,127.0.0.1,paperless
|
||||||
- PAPERLESS_CORS_ALLOWED_HOSTS=http://localhost:8082,http://localhost:8081
|
- PAPERLESS_CORS_ALLOWED_HOSTS=http://localhost:8082,http://localhost:8081
|
||||||
- PAPERLESS_ADMIN_USER=admin
|
- PAPERLESS_ADMIN_USER=admin
|
||||||
- PAPERLESS_ADMIN_PASSWORD=admin123
|
- PAPERLESS_ADMIN_PASSWORD=admin123
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ services:
|
|||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
volumes:
|
volumes:
|
||||||
- dbdata:/var/lib/postgresql/data
|
- dbdata:/var/lib/postgresql/data
|
||||||
|
- ./scripts/init-paperless-db.sh:/docker-entrypoint-initdb.d/init-paperless-db.sh
|
||||||
healthcheck:
|
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
|
interval: 10s
|
||||||
@@ -157,12 +158,12 @@ services:
|
|||||||
- PAPERLESS_REDIS=redis://redis:6379
|
- PAPERLESS_REDIS=redis://redis:6379
|
||||||
- PAPERLESS_DBHOST=db
|
- PAPERLESS_DBHOST=db
|
||||||
- PAPERLESS_DBPORT=5432
|
- PAPERLESS_DBPORT=5432
|
||||||
- PAPERLESS_DBNAME=${POSTGRES_DB}
|
- PAPERLESS_DBNAME=${PAPERLESS_DBNAME:-paperless}
|
||||||
- PAPERLESS_DBUSER=${POSTGRES_USER}
|
- PAPERLESS_DBUSER=${POSTGRES_USER}
|
||||||
- PAPERLESS_DBPASS=${POSTGRES_PASSWORD}
|
- PAPERLESS_DBPASS=${POSTGRES_PASSWORD}
|
||||||
- PAPERLESS_SECRET_KEY=${PAPERLESS_SECRET_KEY}
|
- PAPERLESS_SECRET_KEY=${PAPERLESS_SECRET_KEY}
|
||||||
- PAPERLESS_URL=https://vhtv-stiftung.de
|
- PAPERLESS_URL=https://vhtv-stiftung.de
|
||||||
- PAPERLESS_ALLOWED_HOSTS=vhtv-stiftung.de,localhost
|
- PAPERLESS_ALLOWED_HOSTS=vhtv-stiftung.de,localhost,paperless
|
||||||
- PAPERLESS_CORS_ALLOWED_HOSTS=https://vhtv-stiftung.de
|
- PAPERLESS_CORS_ALLOWED_HOSTS=https://vhtv-stiftung.de
|
||||||
- PAPERLESS_FORCE_SCRIPT_NAME=/paperless
|
- PAPERLESS_FORCE_SCRIPT_NAME=/paperless
|
||||||
- PAPERLESS_STATIC_URL=/paperless/static/
|
- PAPERLESS_STATIC_URL=/paperless/static/
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ services:
|
|||||||
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
|
||||||
volumes:
|
volumes:
|
||||||
- dbdata:/var/lib/postgresql/data
|
- dbdata:/var/lib/postgresql/data
|
||||||
|
- ./scripts/init-paperless-db.sh:/docker-entrypoint-initdb.d/init-paperless-db.sh
|
||||||
healthcheck:
|
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
|
interval: 10s
|
||||||
@@ -66,6 +67,15 @@ services:
|
|||||||
- DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
|
- DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
|
||||||
- DJANGO_DEBUG=${DJANGO_DEBUG}
|
- DJANGO_DEBUG=${DJANGO_DEBUG}
|
||||||
- REDIS_URL=${REDIS_URL}
|
- REDIS_URL=${REDIS_URL}
|
||||||
|
- IMAP_HOST=${IMAP_HOST}
|
||||||
|
- IMAP_PORT=${IMAP_PORT}
|
||||||
|
- IMAP_USER=${IMAP_USER}
|
||||||
|
- IMAP_PASSWORD=${IMAP_PASSWORD}
|
||||||
|
- IMAP_FOLDER=${IMAP_FOLDER}
|
||||||
|
- IMAP_USE_SSL=${IMAP_USE_SSL}
|
||||||
|
- PAPERLESS_API_URL=${PAPERLESS_API_URL}
|
||||||
|
- PAPERLESS_API_TOKEN=${PAPERLESS_API_TOKEN}
|
||||||
|
- PAPERLESS_DESTINATAERE_TAG_ID=${PAPERLESS_DESTINATAERE_TAG_ID}
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
- db
|
- db
|
||||||
@@ -83,6 +93,15 @@ services:
|
|||||||
- DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
|
- DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
|
||||||
- DJANGO_DEBUG=${DJANGO_DEBUG}
|
- DJANGO_DEBUG=${DJANGO_DEBUG}
|
||||||
- REDIS_URL=${REDIS_URL}
|
- REDIS_URL=${REDIS_URL}
|
||||||
|
- IMAP_HOST=${IMAP_HOST}
|
||||||
|
- IMAP_PORT=${IMAP_PORT}
|
||||||
|
- IMAP_USER=${IMAP_USER}
|
||||||
|
- IMAP_PASSWORD=${IMAP_PASSWORD}
|
||||||
|
- IMAP_FOLDER=${IMAP_FOLDER}
|
||||||
|
- IMAP_USE_SSL=${IMAP_USE_SSL}
|
||||||
|
- PAPERLESS_API_URL=${PAPERLESS_API_URL}
|
||||||
|
- PAPERLESS_API_TOKEN=${PAPERLESS_API_TOKEN}
|
||||||
|
- PAPERLESS_DESTINATAERE_TAG_ID=${PAPERLESS_DESTINATAERE_TAG_ID}
|
||||||
depends_on:
|
depends_on:
|
||||||
- redis
|
- redis
|
||||||
- db
|
- db
|
||||||
@@ -107,12 +126,12 @@ services:
|
|||||||
- PAPERLESS_REDIS=redis://redis:6379
|
- PAPERLESS_REDIS=redis://redis:6379
|
||||||
- PAPERLESS_DBHOST=db
|
- PAPERLESS_DBHOST=db
|
||||||
- PAPERLESS_DBPORT=5432
|
- PAPERLESS_DBPORT=5432
|
||||||
- PAPERLESS_DBNAME=${POSTGRES_DB}
|
- PAPERLESS_DBNAME=${PAPERLESS_DBNAME:-paperless}
|
||||||
- PAPERLESS_DBUSER=${POSTGRES_USER}
|
- PAPERLESS_DBUSER=${POSTGRES_USER}
|
||||||
- PAPERLESS_DBPASS=${POSTGRES_PASSWORD}
|
- PAPERLESS_DBPASS=${POSTGRES_PASSWORD}
|
||||||
- PAPERLESS_SECRET_KEY=${PAPERLESS_SECRET_KEY}
|
- PAPERLESS_SECRET_KEY=${PAPERLESS_SECRET_KEY}
|
||||||
- PAPERLESS_URL=https://vhtv-stiftung.de
|
- PAPERLESS_URL=https://vhtv-stiftung.de
|
||||||
- PAPERLESS_ALLOWED_HOSTS=vhtv-stiftung.de,localhost
|
- PAPERLESS_ALLOWED_HOSTS=vhtv-stiftung.de,localhost,paperless
|
||||||
- PAPERLESS_CORS_ALLOWED_HOSTS=https://vhtv-stiftung.de
|
- PAPERLESS_CORS_ALLOWED_HOSTS=https://vhtv-stiftung.de
|
||||||
- PAPERLESS_FORCE_SCRIPT_NAME=/paperless
|
- PAPERLESS_FORCE_SCRIPT_NAME=/paperless
|
||||||
- PAPERLESS_STATIC_URL=/paperless/static/
|
- PAPERLESS_STATIC_URL=/paperless/static/
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ CSRF_COOKIE_NAME=stiftung_csrftoken
|
|||||||
REDIS_URL=redis://redis:6379/0
|
REDIS_URL=redis://redis:6379/0
|
||||||
|
|
||||||
# PAPERLESS CONFIGURATION
|
# PAPERLESS CONFIGURATION
|
||||||
PAPERLESS_API_URL=http://paperless:8000/api
|
PAPERLESS_API_URL=http://paperless:8000
|
||||||
PAPERLESS_API_TOKEN=your_paperless_api_token_here
|
PAPERLESS_API_TOKEN=your_paperless_api_token_here
|
||||||
PAPERLESS_SECRET_KEY=your_paperless_secret_key_here
|
PAPERLESS_SECRET_KEY=your_paperless_secret_key_here
|
||||||
PAPERLESS_ADMIN_USER=admin
|
PAPERLESS_ADMIN_USER=admin
|
||||||
|
|||||||
30
knowledge/README.md
Normal file
30
knowledge/README.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# Wissensbasis – van Hees-Theyssen-Vogel'sche Stiftung
|
||||||
|
|
||||||
|
Dieses Verzeichnis enthält langfristig stabiles Stiftungswissen als Referenz für alle Agents und Mitarbeiter.
|
||||||
|
|
||||||
|
## Dateien
|
||||||
|
|
||||||
|
| Datei | Inhalt |
|
||||||
|
|---|---|
|
||||||
|
| [satzung.md](satzung.md) | Stiftungsname, Zweck, Förderberechtigte, Organe |
|
||||||
|
| [richtlinien.md](richtlinien.md) | Förderrichtlinien, Einkommensgrenzen, Nachweisfristen |
|
||||||
|
| [verfahren.md](verfahren.md) | Verwaltungsabläufe: Anträge, E-Mail, Pacht, Backup |
|
||||||
|
| [kontakte.md](kontakte.md) | Institutionelle Kontakte (Behörden, Berater) |
|
||||||
|
| [historie.md](historie.md) | Stiftungsgeschichte und Meilensteine |
|
||||||
|
|
||||||
|
## Wichtige Hinweise
|
||||||
|
|
||||||
|
- **Keine personenbezogenen Daten** von Destinatären in dieser Wissensbasis
|
||||||
|
- Angaben mit `[TODO]` müssen manuell aus Originalunterlagen ergänzt werden
|
||||||
|
- Operative Daten (Destinatäre, Zahlungen, Ländereien) befinden sich in der Datenbank
|
||||||
|
- Diese Dateien sind Strukturwissen, nicht Echtzeitdaten
|
||||||
|
|
||||||
|
## Pflege
|
||||||
|
|
||||||
|
Diese Dateien sollten aktualisiert werden bei:
|
||||||
|
- Satzungsänderungen
|
||||||
|
- Änderungen der Förderkriterien
|
||||||
|
- Wechsel externer Dienstleister/Behörden
|
||||||
|
- Wesentlichen Änderungen der Verwaltungsverfahren
|
||||||
|
|
||||||
|
*Erstellt: 2026-03 durch RentmeisterAI*
|
||||||
163
knowledge/bedienungsanleitung_email_eingang.md
Normal file
163
knowledge/bedienungsanleitung_email_eingang.md
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
# Bedienungsanleitung: E-Mail-Eingangsverarbeitung für Destinatäre
|
||||||
|
|
||||||
|
> VHTV-Stiftung | Stand: März 2026
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Überblick
|
||||||
|
|
||||||
|
Das E-Mail-Eingangssystem verarbeitet automatisch eingehende E-Mails von Destinatären. Eingehende Nachrichten an **paperless@vhtv-stiftung.de** werden alle 15 Minuten abgerufen, dem richtigen Destinatär zugeordnet und Anhänge in Paperless-NGX archiviert.
|
||||||
|
|
||||||
|
**Typische Inhalte:** Studien- und Ausbildungsnachweise, Quartalsbelege, Anträge, allgemeine Korrespondenz.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. So funktioniert der Workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
E-Mail eingehend
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Automatischer Abruf (alle 15 Min.)
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
Absender-Zuordnung ──► Bekannter Destinatär? ──► Ja: Status "zugewiesen"
|
||||||
|
│ Anhänge → Paperless
|
||||||
|
│
|
||||||
|
▼ Nein
|
||||||
|
Status "unbekannt" → Manuelle Zuordnung nötig
|
||||||
|
```
|
||||||
|
|
||||||
|
1. **E-Mail-Eingang:** Jan Siebels leitet Destinatär-Emails von jan.siebels@gmail.com an paperless@vhtv-stiftung.de weiter.
|
||||||
|
2. **Automatische Zuordnung:** Das System gleicht die Absender-E-Mail mit den hinterlegten E-Mail-Adressen der aktiven Destinatäre ab.
|
||||||
|
3. **Anhänge:** Werden automatisch in Paperless-NGX hochgeladen und mit dem Tag `Stiftung_Destinatäre` versehen.
|
||||||
|
4. **Unbekannte Absender:** Erhalten den Status `unbekannt` und müssen manuell zugeordnet werden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. E-Mail-Eingang aufrufen
|
||||||
|
|
||||||
|
Öffnen Sie im Hauptmenü der Stiftungsverwaltung:
|
||||||
|
|
||||||
|
**Pfad:** `/email-eingang/`
|
||||||
|
|
||||||
|
### 3.1 Listenansicht
|
||||||
|
|
||||||
|
Die Übersicht zeigt:
|
||||||
|
|
||||||
|
- **Statuskarten** oben: Gesamtzahl, Neue, Unbekannte, Fehler
|
||||||
|
- **Tabelle** mit allen eingegangenen E-Mails:
|
||||||
|
- Eingangsdatum
|
||||||
|
- Absender (Name und E-Mail)
|
||||||
|
- Zugeordneter Destinatär
|
||||||
|
- Betreff
|
||||||
|
- Anzahl Anhänge
|
||||||
|
- Status
|
||||||
|
|
||||||
|
**Filtern und Suchen:**
|
||||||
|
- **Suchfeld:** Durchsucht Absender, Betreff und Destinatär-Namen
|
||||||
|
- **Status-Filter:** Dropdown zur Einschränkung nach Status (Neu, Zugewiesen, Verarbeitet, Unbekannt, Fehler)
|
||||||
|
|
||||||
|
### 3.2 Detailansicht
|
||||||
|
|
||||||
|
Klicken Sie auf eine E-Mail, um die Details zu sehen (`/email-eingang/<id>/`):
|
||||||
|
|
||||||
|
- Vollständiger E-Mail-Text
|
||||||
|
- Anhänge mit Direktlinks zu Paperless-NGX
|
||||||
|
- Zuordnungsinformationen
|
||||||
|
- Fehlermeldungen (falls vorhanden)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Häufige Aufgaben
|
||||||
|
|
||||||
|
### 4.1 Unbekannten Absender zuordnen
|
||||||
|
|
||||||
|
1. Öffnen Sie die E-Mail mit Status `unbekannt`
|
||||||
|
2. Im Seitenbereich **"Destinatär zuordnen"**: Wählen Sie den richtigen Destinatär aus dem Dropdown
|
||||||
|
3. Klicken Sie **"Zuordnen"**
|
||||||
|
4. Der Status wechselt automatisch zu `zugewiesen`
|
||||||
|
|
||||||
|
### 4.2 E-Mail als verarbeitet markieren
|
||||||
|
|
||||||
|
1. Öffnen Sie die zugewiesene E-Mail
|
||||||
|
2. Im Seitenbereich **"Als verarbeitet markieren"**:
|
||||||
|
- Optional: Notizen zur Bearbeitung eintragen
|
||||||
|
3. Klicken Sie **"Verarbeitet"**
|
||||||
|
4. Der Status wechselt zu `verarbeitet`
|
||||||
|
|
||||||
|
### 4.3 Interne Notizen hinzufügen
|
||||||
|
|
||||||
|
1. Öffnen Sie eine E-Mail
|
||||||
|
2. Im Bereich **"Interne Notizen"**: Text eingeben
|
||||||
|
3. Klicken Sie **"Speichern"**
|
||||||
|
|
||||||
|
Notizen sind nur intern sichtbar und dienen der Dokumentation der Bearbeitung.
|
||||||
|
|
||||||
|
### 4.4 E-Mails manuell abrufen
|
||||||
|
|
||||||
|
Falls Sie nicht auf den nächsten automatischen Abruf warten möchten:
|
||||||
|
|
||||||
|
1. In der Listenansicht: Klicken Sie **"Jetzt abrufen"**
|
||||||
|
2. Der Abruf startet im Hintergrund
|
||||||
|
3. Neue E-Mails erscheinen nach dem Neuladen der Seite
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Status-Übersicht
|
||||||
|
|
||||||
|
| Status | Bedeutung | Aktion erforderlich? |
|
||||||
|
|---|---|---|
|
||||||
|
| **Neu** | Gerade eingegangen, Destinatär zugeordnet | Inhalt prüfen, ggf. verarbeiten |
|
||||||
|
| **Zugewiesen** | Destinatär wurde zugeordnet | Inhalt prüfen, dann verarbeiten |
|
||||||
|
| **Verarbeitet** | Bearbeitung abgeschlossen | Keine |
|
||||||
|
| **Unbekannt** | Absender konnte nicht zugeordnet werden | Manuell zuordnen |
|
||||||
|
| **Fehler** | Technischer Fehler bei Verarbeitung | SysAdmin informieren |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Anhänge und Paperless-NGX
|
||||||
|
|
||||||
|
Alle E-Mail-Anhänge werden automatisch in Paperless-NGX gespeichert:
|
||||||
|
|
||||||
|
- **Tag:** `Stiftung_Destinatäre`
|
||||||
|
- **Korrespondent:** Name des zugeordneten Destinatärs
|
||||||
|
- **Zugriff:** Direktlinks in der Detailansicht der E-Mail
|
||||||
|
|
||||||
|
Um Anhänge in Paperless einzusehen, klicken Sie auf den jeweiligen Dokumentlink in der Detailansicht.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Empfohlener Bearbeitungsablauf
|
||||||
|
|
||||||
|
1. **Täglich** die Listenansicht öffnen (`/email-eingang/`)
|
||||||
|
2. **Statuskarten** prüfen: Gibt es neue oder unbekannte E-Mails?
|
||||||
|
3. **Unbekannte Absender** zuerst zuordnen
|
||||||
|
4. **Neue E-Mails** inhaltlich prüfen:
|
||||||
|
- Handelt es sich um einen Studiennachweis? → Zum Quartalsnachweis verknüpfen
|
||||||
|
- Allgemeine Korrespondenz? → Notizen ergänzen
|
||||||
|
5. **Als verarbeitet markieren** wenn erledigt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Fehlerbehebung
|
||||||
|
|
||||||
|
| Problem | Lösung |
|
||||||
|
|---|---|
|
||||||
|
| Keine neuen E-Mails trotz Weiterleitung | "Jetzt abrufen" klicken; ggf. SysAdmin kontaktieren |
|
||||||
|
| Anhänge fehlen in Paperless | Paperless-API-Verbindung prüfen (SysAdmin) |
|
||||||
|
| Status "Fehler" | Fehlerdetails in der Detailansicht lesen; SysAdmin informieren |
|
||||||
|
| Falscher Destinatär zugeordnet | In Detailansicht korrekten Destinatär neu zuordnen |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Technische Hinweise (für Administratoren)
|
||||||
|
|
||||||
|
- **E-Mail-Postfach:** paperless@vhtv-stiftung.de (IMAP, SSL, Port 993)
|
||||||
|
- **Polling-Intervall:** Alle 15 Minuten via Celery Beat
|
||||||
|
- **Duplikaterkennung:** Basierend auf Absender, Datum und Betreff
|
||||||
|
- **Konfiguration:** Umgebungsvariablen in der Docker-Compose-Datei
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*VHTV-Stiftung | Bedienungsanleitung E-Mail-Eingang | Stand: März 2026*
|
||||||
124
knowledge/bedienungsanleitung_email_eingang.pdf
Normal file
124
knowledge/bedienungsanleitung_email_eingang.pdf
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
%PDF-1.4
|
||||||
|
%<25><><EFBFBD><EFBFBD> ReportLab Generated PDF document (opensource)
|
||||||
|
1 0 obj
|
||||||
|
<<
|
||||||
|
/F1 2 0 R /F2 3 0 R /F3 4 0 R /F4 6 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
2 0 obj
|
||||||
|
<<
|
||||||
|
/BaseFont /Helvetica /Encoding /WinAnsiEncoding /Name /F1 /Subtype /Type1 /Type /Font
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
3 0 obj
|
||||||
|
<<
|
||||||
|
/BaseFont /Helvetica-Bold /Encoding /WinAnsiEncoding /Name /F2 /Subtype /Type1 /Type /Font
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
4 0 obj
|
||||||
|
<<
|
||||||
|
/BaseFont /Helvetica-BoldOblique /Encoding /WinAnsiEncoding /Name /F3 /Subtype /Type1 /Type /Font
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
5 0 obj
|
||||||
|
<<
|
||||||
|
/Contents 12 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 11 0 R /Resources <<
|
||||||
|
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||||
|
>> /Rotate 0 /Trans <<
|
||||||
|
|
||||||
|
>>
|
||||||
|
/Type /Page
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
6 0 obj
|
||||||
|
<<
|
||||||
|
/BaseFont /Symbol /Name /F4 /Subtype /Type1 /Type /Font
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
7 0 obj
|
||||||
|
<<
|
||||||
|
/Contents 13 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 11 0 R /Resources <<
|
||||||
|
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||||
|
>> /Rotate 0 /Trans <<
|
||||||
|
|
||||||
|
>>
|
||||||
|
/Type /Page
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
8 0 obj
|
||||||
|
<<
|
||||||
|
/Contents 14 0 R /MediaBox [ 0 0 595.2756 841.8898 ] /Parent 11 0 R /Resources <<
|
||||||
|
/Font 1 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ]
|
||||||
|
>> /Rotate 0 /Trans <<
|
||||||
|
|
||||||
|
>>
|
||||||
|
/Type /Page
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
9 0 obj
|
||||||
|
<<
|
||||||
|
/PageMode /UseNone /Pages 11 0 R /Type /Catalog
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
10 0 obj
|
||||||
|
<<
|
||||||
|
/Author (\(anonymous\)) /CreationDate (D:20260309224153+00'00') /Creator (\(unspecified\)) /Keywords () /ModDate (D:20260309224153+00'00') /Producer (ReportLab PDF Library - \(opensource\))
|
||||||
|
/Subject (\(unspecified\)) /Title (\(anonymous\)) /Trapped /False
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
11 0 obj
|
||||||
|
<<
|
||||||
|
/Count 3 /Kids [ 5 0 R 7 0 R 8 0 R ] /Type /Pages
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
12 0 obj
|
||||||
|
<<
|
||||||
|
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1781
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
Gau0DD/Yn7&H88.1$U.\A.pV(Ce(C%;^HifF?TNTdj`^ZD$I=(/Em5X27alZZcc"W0/62T6jZV=jdG;"H"Ifr6i(EWpG)qt3JZWr%%%Lq!17Q40`M.9o:rKZXLuuf/T5B")2q'qMbUl6h++Auk-S;4d0cs]e:W$?$eiBY]qB6.jk.YZ]]!g%kN>&$?D9)Lk&Q`-)55.8drUn+Jb^rQBO=@k5190X/G?#n`NhZ')T1]W'A]TVkEp39)I;fZE5=t"3#?VS$.KD6Xqh0n#d0]&X5FE89Lta\3NfEe6Mgbq@?W0VjQJ8L4Dr#W$[<(,#+a_UpZ]s#dpSJu^u5#!Af6s7$!\;qG@1quY7$q1!tU#gqK[9q9hMuTNdn]*P%KCb>]4@O-IW;"&]&;@nt%f9@;7J(XrPJfTNX`m\NV"&B/a=94i+Dr,FHSePN>11TIP#VGK*S6:d=oEAf*n+"IK59ZR"\$8=(JB?%sLp+3PU%\4PaNIRjjllg(PNHC'!s.^9C<Pe\T4s-r3]W89DH7MT@#^:4KURmC4-DNua-_sbDn.UOhA77afi(II7rdDZGm*h_kP:Ec*<M%sp$X7>!,.nl0\.Df?dE%45'Sm[24n9]k$E;B>`i+a-e)hBKF%dPj<Gl.-['&W"`Ds9"VG-+n:GUl-&AU(KqqCKgtGmk&$H+FQLYFS&o\[Ntbm)-l6X,KR5![Csthkr5.3\pZ%-sL+R,2UN65#D[9l\)'sG?r'):Oct*NSO%7.j`r%=TUlAf9o1*QB)YG_<44gK.+:t&1_h58\&,1<5tCFhQF5&D;S2jbQO)H.7?Vh[5)rF*nLSiKhEpJY'mS's0e4b9P%5I]"ac6Ht[BI6#*kToll+.DhiggD^,O;kIU<V>r64nRM@H^+r[D9JPmOa(TFhV@_pCsZ-[5X#_gKEUara$3r5$);kHsWDDSS>b(?*W2aDDC/kidMa"lW?@%t)umH?d$hU_2O/NX<'A[oU5lO1*2'NE")kOftFHY,J7WlFk\NXjomVJSNI2Vj&gHS5Qj_[O'0%/_Oj\E8s=X**6qi`m?7PX]8"qcTLW)-1)"?NjnPd'JUl46A<=."[2m(HnNdS8@aPrQT&!LSsqeV,l>L-uB%7!=stMG^I'P!BI>'gC"(>`iMGDn:p,X=c?8-D^qk]3^$*lP(^Ej[o2-$[;?AW5^qJhZ"kF$^R]s(#8euRq3!*0*s=T]j>,8?dc"WC]:0l=0L'A9hMSa4h_fQ=;Oh?n-OZ.dn/>4HB6QIsqJ_9OFmJoSl,,"[,?,\6[jLRA".sZ"1"#\'VEIE[@'UUU[>(W$U7;QdaMtPKmapRVDu4Do?=Kcc#l9OsX'\p'i@+*XSsf>Xo&RtJ^rYE\NH1KddTh!iqbToO`c]ueqNAm)Wm;aT)(_AD?@u=2r,RRVSuLf):MqHrU=,esFVQ\Fb-g))l.N='oBu7u<G3%K).c>$.^U5i8<\A'pJ.PROX]Q@X`c8GSlQ@:/Ne"&lXEc*?]0R+MrS`piHURZX61iN,%U$?ST\LM>c_3$BbKB'&k/eA4mCF%7o^",NrO:\QVH9`4h\+5dXH-^'X"c5aE:+CefJ!UJ/KGT^%*kEHDF5g$kfD7VaQga;W43%Ra"`s!bLDaqSIbC5OO4+NE:&*$@+lC5^U@U7@)aU7!;-!I?dM#20tIfNKHUfdoM6Z:X/Md)1@!GS+e/P#t3Bb@o)U`T\/4'dA)iOT''r!1VLDpV^fB&WF3*%7%:&g;Eo`YqJ7!K@F9)@.h$I*e-u8?YAZf48,i\.<`30~>endstream
|
||||||
|
endobj
|
||||||
|
13 0 obj
|
||||||
|
<<
|
||||||
|
/Filter [ /ASCII85Decode /FlateDecode ] /Length 1861
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
Gau0D>BALf'Roe[30T+-ai#sH&jgU[4Cf):cG`KbBfd2E3l7u:#&1?=<Sd63HWF>+&AecoN'X]%bBNC3&V2+>8G7<J!PG#?fd-Yc@HIZs$k$Yhi9fmlre0L'2Do<;\cg%Ha<7YQi]=`3$V@lFY)72+>+=n;<4`UG<uf#k1uNf'Dq@[uPV[RX*+n)9n29%?9>O$qlP[nq*Q+&Z0U85''6mooX+Z0>[V<!WRg/51Z_Uij?"P`(im7DRLY<q0e-2]rHrIjG]/5)#>Ra;eJRqZ&Hhu.M+a@Dss01.6(Q;!6+nFVMB<d22pj?g?HG5Z:?RS[A:CNV[eFn<aosVe_V#dI0ome_QO#.h6i&ZIh&m?h_?@KL99JJQ+&oF&Q3W+%Jq*oTPgh\7nU*P[*)1+Xkh:^)Y_"W32*u3YhraV#EU]&5dCEZi>VCI7+ENr7:\N.+p.A5Y4hkP_!kFg*.GCf,1ZV.@`Ci_YCopm*^K\&r9mtaq2mrr\3A,f.)/78iRq;'4ORd$B[L4K+>P`1Y]W25cHTY$,HKpW_nEU*9QjiDS*2!)-dZJl+\?kdo26$_R;_nEI8W*WX1h`E0oZ@Pj0WD4<Jn-8WYW&]r1034dj,P[NS.gL&DQ:;tLBMCAd&,:Tt@Q6:FNFDSXqpgPEG)3r>?,LYe#ChC\mG-P>f![N9hJVMfCVs"e6WAr2g!IiW3&5T$3LuY:KfL_`kMmaiVH([p>(hiOfY&I5,oWIj<-nnKG`<U90,e`*0Y&&HWf(^'[uH+[/*)=&Adqm[Q;^M)c'BoGW<i4DUp[1c[7\ER@NPt2Mp[)V(X5t&0b-HinpPCXg'LDJ\\_%PA9iD)*e&p3k\,h6_NT&iZJqsIAbBDR"4s#JMj9&Si%A&UdoT`WJT'&<O4_4$%<fJ%f-Y@eLKi)@@XL^D:VDkD>tC<R&:KXDq$+GGC*-EU0RQ%t9oh"MYiKsBDN[a4BsmNF.L06EBKkSef)%=6mtRM`:)8)Mb"k>-;D2VGPok,Uh:?l0*^ohj9=>11gkdAM1dfD6GbPNH**PVN("<2GaYMiW\A5&!G"LM<QH9D[_e]PQ5Gg)DKIO&0oI()iohD9e.^KSX3DuK5hefPB_fBj$NB5%O;iTJLQsG\S`L'YbPlT!+SG[&uIPTH+Zo7ck1DNfc9SFK#aNlCBVW0jC3Nrs[Lm<j@bY`o@%R,/^Ql9+jSDINLolsarm>eJI+/`d_4DpGPR2;C^enD-egfJLpG9]8U*p?)c8^Y6/`%apQ2AkpM[W1/K&U#TSWFZ*@-k4Np&("oYT6V\qbl0?krANFi4D&@HEj8@nr%jN/L.S1bRUftSI@Ddo()9'Ml)AG_RF@3+GT5K7)pF?E-*.iE`*7n$l9?mQ*SX)i=mTm-5-I'n:irLE,1Z6BLbH3T[VfpDK/4::D,(hh5`ThBJ)1;&kZHcM%6UP%CW2`2$[PAGcJaX6Ygl<1Y`usnGu\%4f:jii/p^naO2Z"FZ%&_a'ICC<_DXi3/GBnC/qi:fKN9&NNRF-D:(%udR$R<29$ui7;jjB'#iXUB*Z.[*?kS1pMmE>kT"MKJI@Q#$a,2ZfMf\ZE8R8b0'ED)E>4S=VCI/s4nB!Hs)3(7>o/MK%AIGqUMe:Gdp$aHk'$dFWj5p2(hk_e"Vg,NR=k(8,LZQ/Z.M3u/^?^mkWm8DdF<J^R$8UJZ/c;T<"&7)PZuj3+T3q$8NI9:?oCF,-kDojSjfMG&En;6R4_t4EB^WSETZ2n;AMZc;.sBDO9eW\peE#*O.J>V,/s`M=BnWP,eHj/XjBk+`c=P0^.0&-5UY[jZZ`9T3q.>0g<DWAsI]Nk.8'c7nn*K*3cCm_dGZmmuR_fTY,B<u!-iX;82Ve3~>endstream
|
||||||
|
endobj
|
||||||
|
14 0 obj
|
||||||
|
<<
|
||||||
|
/Filter [ /ASCII85Decode /FlateDecode ] /Length 802
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
Gau0A968fP%)2<`F<mF8G\uJ2:5(g!Zse\)2^^3s/Ah[=:"O/9c1K%X?Hf/km;PU5N#98+8Y9$;Yj#c^H^4aBA%9$[^`8//@%QIGquAZqk=::bT"\_1OV9GTqN8`@5GC+mgW$>WSL-^7&i`TF.<k:n5b(49Q=o;qJI8Qbe3\1=FZiO:CGj.!,nfkg`6T!EBSAPA2Sm)&:sDGKALV4!%Eh'e>\]M*PS,K=CQchmUif8$l$;]!+`'@"5'<qj]QBrp=\b#f6O"3%GlC04bFN[Wg2!3'0?;rEeZr&$2><S7Fa$Q.gVNIl]$:Rt?%j$iF9U$W<6u[tA;+G+_B-Ntl&#"[HHU$N[H*'S[[Wh:9ut;U"3`X0LGeJTaFY7A$Roh^iD.H1'ICW'RnhdiGSqd;+JVtB(CK9rm!tV@_"4<gG^kYUGVob=#1ea;>sm;*-$6%9#f+*h!&`?q9lEE%e/K4#:AJ^Gg"<d]?!-V3WXR)Aj*IEN4W:A#=)r1)FWL4O_>"usPD=2r(J7%/\L/+("RfO4\B$EKkIl5A;6>,0*LAM%1'H0`OcWRk5h:P)'kXCn_@X7A!OH^Z/*?/4Q&-e7^e9oT3dQa1a@h(@(CBR\Ku$\Z5>duh2:kh<($ULP'rL4%kWSZGd=3TU.cqHTXsi?co'cAoT;\W-c'@3_))YYi8EsJ^ba,2R=%0hQ9'fD(-r,<]f`,7b5'-be;WpX;E%1G8qLZ.l0*Q:#'GuN#^Okn6?2W?E1oCV.&VZ(WRfAPo%#*(eL[p13RN&/]jtaK#O]gkN7%3/FRKf>?fdimH~>endstream
|
||||||
|
endobj
|
||||||
|
xref
|
||||||
|
0 15
|
||||||
|
0000000000 65535 f
|
||||||
|
0000000061 00000 n
|
||||||
|
0000000122 00000 n
|
||||||
|
0000000229 00000 n
|
||||||
|
0000000341 00000 n
|
||||||
|
0000000460 00000 n
|
||||||
|
0000000665 00000 n
|
||||||
|
0000000742 00000 n
|
||||||
|
0000000947 00000 n
|
||||||
|
0000001152 00000 n
|
||||||
|
0000001221 00000 n
|
||||||
|
0000001502 00000 n
|
||||||
|
0000001574 00000 n
|
||||||
|
0000003447 00000 n
|
||||||
|
0000005400 00000 n
|
||||||
|
trailer
|
||||||
|
<<
|
||||||
|
/ID
|
||||||
|
[<bb45e303f875ee9fae4b75f06d3984e4><bb45e303f875ee9fae4b75f06d3984e4>]
|
||||||
|
% ReportLab generated PDF document -- digest (opensource)
|
||||||
|
|
||||||
|
/Info 10 0 R
|
||||||
|
/Root 9 0 R
|
||||||
|
/Size 15
|
||||||
|
>>
|
||||||
|
startxref
|
||||||
|
6293
|
||||||
|
%%EOF
|
||||||
90
knowledge/historie.md
Normal file
90
knowledge/historie.md
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
# Stiftungsgeschichte
|
||||||
|
|
||||||
|
> **Status:** Grundstruktur angelegt. Inhalte aus der Datenbank-Tabelle `GeschichteSeite` sind zu entnehmen.
|
||||||
|
> Die Verwaltungssoftware enthält ein Wiki-ähnliches Modul (`Geschichte`) unter `/geschichte/`,
|
||||||
|
> in dem die Stiftungsgeschichte gepflegt wird.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hinweis zur Datenpflege
|
||||||
|
|
||||||
|
Die Stiftungsgeschichte wird primär in der **Django-App** gepflegt:
|
||||||
|
- **URL:** `/geschichte/`
|
||||||
|
- **Modell:** `GeschichteSeite` (Markdown-Seiten) + `GeschichteBild` (Bilder)
|
||||||
|
- Seiten sind sortierbar und können mit Bildern versehen werden
|
||||||
|
- Bearbeitungsrecht erfordert Berechtigung `change_geschichteseite`
|
||||||
|
|
||||||
|
Diese Datei dient als **statischer Referenzpunkt** für Agents. Für aktuelle und detaillierte Inhalte ist die Datenbank maßgeblich.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stiftungsname und Familienhintergrund
|
||||||
|
|
||||||
|
Die **van Hees-Theyssen-Vogel'sche Stiftung** (VHTV) ist eine Familienstiftung, deren Name auf die Gründerfamilien(zweige) zurückgeht:
|
||||||
|
- van Hees
|
||||||
|
- Theyssen
|
||||||
|
- Vogel
|
||||||
|
|
||||||
|
[TODO: Herkunft und Bedeutung dieser Familiennamen aus Stiftungsunterlagen ergänzen]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Gründungsgeschichte
|
||||||
|
|
||||||
|
[TODO: Folgende Angaben aus der Datenbank (GeschichteSeite) oder Stiftungsunterlagen entnehmen]
|
||||||
|
|
||||||
|
- **Gründungsjahr:** [TODO]
|
||||||
|
- **Gründer(in):** [TODO]
|
||||||
|
- **Gründungsanlass:** [TODO]
|
||||||
|
- **Ursprüngliches Stiftungsvermögen:** [TODO]
|
||||||
|
- **Ursprünglicher Stiftungszweck:** [TODO]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Historische Entwicklung
|
||||||
|
|
||||||
|
[TODO: Wichtige Meilensteine in chronologischer Reihenfolge aus Stiftungsunterlagen eintragen]
|
||||||
|
|
||||||
|
| Jahr | Ereignis |
|
||||||
|
|---|---|
|
||||||
|
| [TODO] | Gründung der Stiftung |
|
||||||
|
| [TODO] | [Weitere Meilensteine] |
|
||||||
|
| [TODO] | Erste digitale Verwaltung |
|
||||||
|
| 2024/2025 | Einführung der modernen Django-Verwaltungssoftware |
|
||||||
|
| 2025 | Implementierung der automatischen E-Mail-Verarbeitung für Destinatäre |
|
||||||
|
| 2026 | Einführung KI-gestützter Stiftungsverwaltung (RentmeisterAI) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ländereien und Vermögensgeschichte
|
||||||
|
|
||||||
|
[TODO: Geschichte des Landbesitzes beschreiben]
|
||||||
|
|
||||||
|
- Die Stiftung verwaltet landwirtschaftliche Ländereien in der Region Hamminkeln/Kreis Wesel (NRW)
|
||||||
|
- Nutzungsarten: Grünland, Acker, Wald
|
||||||
|
- Die Ländereien werden verpachtet; der Pachtzins bildet einen wesentlichen Teil der Erträge
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Digitalisierung der Verwaltung
|
||||||
|
|
||||||
|
Die Stiftungsverwaltung wurde sukzessive modernisiert:
|
||||||
|
|
||||||
|
- **Frühere Verwaltung:** [TODO: Wie wurde früher verwaltet?]
|
||||||
|
- **2024:** Entwicklung der Django-basierten Verwaltungssoftware
|
||||||
|
- **2025:** Integration von Paperless-NGX zur Dokumentenverwaltung
|
||||||
|
- **2025:** Automatische E-Mail-Verarbeitung für Destinatärs-Eingänge
|
||||||
|
- **2026:** KI-Unterstützung durch RentmeisterAI (Paperclip-Plattform)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Zugriff auf ausführliche Geschichte
|
||||||
|
|
||||||
|
Die vollständige Stiftungsgeschichte mit Bildern und Artikeln ist in der Anwendung abrufbar:
|
||||||
|
|
||||||
|
1. In der Verwaltungsoberfläche → Menüpunkt **Geschichte**
|
||||||
|
2. URL: `https://vhtv-stiftung.de/geschichte/`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Zuletzt aktualisiert: 2026-03 | Pflege der ausführlichen Geschichte: Verwaltungs-App unter /geschichte/*
|
||||||
105
knowledge/kontakte.md
Normal file
105
knowledge/kontakte.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# Wichtige Kontakte der Stiftung
|
||||||
|
|
||||||
|
> **Datenschutzhinweis:** Diese Datei enthält ausschließlich institutionelle Kontakte (Behörden, externe Stellen).
|
||||||
|
> **Keine personenbezogenen Daten von Destinatären** werden hier gespeichert.
|
||||||
|
> Personenbezogene Daten von Rentmeistern und externen Dienstleistern nur in anonymisierter Form.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Stiftung (Eigene Kontaktdaten)
|
||||||
|
|
||||||
|
| Feld | Wert |
|
||||||
|
|---|---|
|
||||||
|
| Name | van Hees-Theyssen-Vogel'sche Stiftung |
|
||||||
|
| Adresse | Raesfelder Str. 3, 46499 Hamminkeln |
|
||||||
|
| E-Mail (Paperless/Eingang) | paperless@vhtv-stiftung.de |
|
||||||
|
| Website | https://vhtv-stiftung.de |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Rentmeister / Geschäftsführung
|
||||||
|
|
||||||
|
Die aktuell aktiven Rentmeister sind im System unter `/rentmeister/` (Geschäftsführung) einsehbar.
|
||||||
|
|
||||||
|
> **Hinweis:** Aus Datenschutzgründen werden hier keine Namen oder persönlichen Kontaktdaten gespeichert. Die aktuellen Rentmeister sind in der Datenbank (`Rentmeister`-Tabelle) hinterlegt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Steuerberater
|
||||||
|
|
||||||
|
[TODO: Kanzleiname, Adresse, Telefon, E-Mail eintragen]
|
||||||
|
|
||||||
|
- **Kanzlei:** [TODO]
|
||||||
|
- **Ansprechpartner:** [TODO]
|
||||||
|
- **Adresse:** [TODO]
|
||||||
|
- **Telefon:** [TODO]
|
||||||
|
- **E-Mail:** [TODO]
|
||||||
|
- **Zuständig für:** Jahresabschluss, Gemeinnützigkeitsrecht, Steuererklärungen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Notar
|
||||||
|
|
||||||
|
[TODO: Notariat und Kontakt eintragen]
|
||||||
|
|
||||||
|
- **Notariat:** [TODO]
|
||||||
|
- **Ansprechpartner:** [TODO]
|
||||||
|
- **Adresse:** [TODO]
|
||||||
|
- **Telefon:** [TODO]
|
||||||
|
- **Zuständig für:** Satzungsänderungen, Grundstücksangelegenheiten
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Bankverbindungen
|
||||||
|
|
||||||
|
Die Bankkonten der Stiftung sind im System unter `/konten/` (Geschäftsführung → Konten) einsehbar.
|
||||||
|
|
||||||
|
> **Sicherheitshinweis:** Kontodaten (IBAN, BIC) werden ausschließlich in der Datenbank gespeichert, nicht in dieser Datei.
|
||||||
|
|
||||||
|
[TODO: Hauptbank und Kontoart eintragen (ohne IBAN)]
|
||||||
|
|
||||||
|
- **Hauptbank:** [TODO – z. B. Sparkasse, Volksbank, etc.]
|
||||||
|
- **Konto-Typ:** [TODO]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Stiftungsaufsicht
|
||||||
|
|
||||||
|
- **Behörde:** [TODO – Bezirksregierung Düsseldorf? Hamminkeln liegt im Kreis Wesel, Regierungsbezirk Düsseldorf]
|
||||||
|
- **Referat:** [TODO]
|
||||||
|
- **Adresse:** [TODO]
|
||||||
|
- **Telefon:** [TODO]
|
||||||
|
- **Aktenzeichen:** [TODO]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Finanzamt
|
||||||
|
|
||||||
|
- **Finanzamt:** [TODO – zuständiges Finanzamt für Hamminkeln/Kreis Wesel]
|
||||||
|
- **Steuernummer:** [TODO]
|
||||||
|
- **Freistellungsbescheid:** [TODO – Datum des letzten Freistellungsbescheids]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Amtsgericht / Grundbuch
|
||||||
|
|
||||||
|
Für Grundstücksangelegenheiten zuständig:
|
||||||
|
- **Amtsgericht:** [TODO – Amtsgericht Wesel? Hamminkeln liegt im Kreis Wesel]
|
||||||
|
- **Grundbuchamt:** [TODO]
|
||||||
|
- **Kontakt:** [TODO]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. Sonstige externe Stellen
|
||||||
|
|
||||||
|
### Landwirtschaft / Ländereien
|
||||||
|
- **Landwirtschaftskammer NRW:** [TODO]
|
||||||
|
- **Katasteramt Kreis Wesel:** [TODO]
|
||||||
|
|
||||||
|
### IT / Hosting
|
||||||
|
- **Server-Hosting:** [TODO – Hoster für vhtv-stiftung.de]
|
||||||
|
- **Paperless-NGX Installation:** https://vhtv-stiftung.de/paperless
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Zuletzt aktualisiert: 2026-03 | Hinweis: Alle mit [TODO] markierten Felder sind manuell zu ergänzen*
|
||||||
142
knowledge/richtlinien.md
Normal file
142
knowledge/richtlinien.md
Normal file
@@ -0,0 +1,142 @@
|
|||||||
|
# Förderrichtlinien und Vergabekriterien
|
||||||
|
|
||||||
|
> **Status:** Abgeleitet aus der Systemlogik der Verwaltungssoftware (models.py).
|
||||||
|
> Angaben mit [TODO] sind aus der formalen Richtliniendokumentation zu ergänzen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Förderberechtigung (Grundvoraussetzungen)
|
||||||
|
|
||||||
|
Für eine laufende Unterstützung durch die Stiftung müssen **alle drei** Voraussetzungen erfüllt sein:
|
||||||
|
|
||||||
|
### 1.1 Abstammungsnachweis
|
||||||
|
- Der Antragsteller muss **Abkömmling gemäß Satzung** sein (`ist_abkoemmling`)
|
||||||
|
- [TODO: Welche Abstammungsnachweise sind zu erbringen?]
|
||||||
|
|
||||||
|
### 1.2 Einkommensgrenzen
|
||||||
|
Die monatlichen Bezüge dürfen eine Einkommensgrenze nicht überschreiten:
|
||||||
|
|
||||||
|
| Haushaltsgröße | Max. monatliche Bezüge |
|
||||||
|
|---|---|
|
||||||
|
| 1 Person | 2.815,00 € |
|
||||||
|
| 2 Personen | 3.265,40 € |
|
||||||
|
| 3 Personen | 3.715,80 € |
|
||||||
|
| n Personen | 2.815,00 € + (n-1) × 450,40 € |
|
||||||
|
|
||||||
|
**Berechnungsgrundlage:** Regelsatz 563 € × 5 (Basis) + 0,8 × Regelsatz je weitere Haushaltsperson
|
||||||
|
|
||||||
|
### 1.3 Vermögensgrenze
|
||||||
|
- Eigenvermögen ≤ **15.500 €**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Förderkategorien
|
||||||
|
|
||||||
|
| Kategorie | Beschreibung |
|
||||||
|
|---|---|
|
||||||
|
| `bildung` | Bildung und Studium |
|
||||||
|
| `forschung` | Wissenschaftliche Forschung |
|
||||||
|
| `kultur` | Kulturelle Projekte |
|
||||||
|
| `soziales` | Soziale Unterstützung |
|
||||||
|
| `umwelt` | Umweltschutz |
|
||||||
|
| `anderes` | Sonstiges |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Laufende Unterstützungen (Destinatär-Unterstützungen)
|
||||||
|
|
||||||
|
### 3.1 Zahlungsmodalitäten
|
||||||
|
- Zahlungen erfolgen **quartalsweise im Voraus**
|
||||||
|
- Betrag: individuell festgelegter **vierteljährlicher Betrag** je Destinatär
|
||||||
|
- Zahlung auf das hinterlegte Bankkonto des Destinatärs (IBAN)
|
||||||
|
|
||||||
|
### 3.2 Zahlungsfristen (quartalsweise, im Voraus)
|
||||||
|
|
||||||
|
| Quartal | Zahlungsfälligkeit |
|
||||||
|
|---|---|
|
||||||
|
| Q1 (Jan–Mär) | 15. Dezember (Vorjahr) |
|
||||||
|
| Q2 (Apr–Jun) | 15. März |
|
||||||
|
| Q3 (Jul–Sep) | 15. Juni |
|
||||||
|
| Q4 (Okt–Dez) | 15. September |
|
||||||
|
|
||||||
|
### 3.3 Wiederkehrende Zahlungen
|
||||||
|
Folgende Zahlungsintervalle können eingerichtet werden:
|
||||||
|
- Monatlich
|
||||||
|
- Vierteljährlich (standard)
|
||||||
|
- Halbjährlich
|
||||||
|
- Jährlich
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Nachweispflichten (Vierteljahresnachweise)
|
||||||
|
|
||||||
|
Destinatäre müssen quartalsweise folgende Nachweise einreichen:
|
||||||
|
|
||||||
|
### 4.1 Pflichtbestandteile
|
||||||
|
1. **Studiennachweis** (sofern erforderlich)
|
||||||
|
2. **Einkommenssituation** – Bestätigung oder Beschreibung von Änderungen
|
||||||
|
3. **Vermögenssituation** – Bestätigung oder Beschreibung von Änderungen
|
||||||
|
4. Ggf. weitere Dokumente
|
||||||
|
|
||||||
|
### 4.2 Fristen für Studiennachweise (semesterbasiert)
|
||||||
|
| Quartal | Studiennachweis-Frist |
|
||||||
|
|---|---|
|
||||||
|
| Q1 (Jan–Mär) | 15. März |
|
||||||
|
| Q2 (Apr–Jun) | 15. März |
|
||||||
|
| Q3 (Jul–Sep) | 15. September |
|
||||||
|
| Q4 (Okt–Dez) | 15. September |
|
||||||
|
|
||||||
|
**Hintergrund:** Die Studiennachweise sind semesterbasiert (Wintersemester / Sommersemester), nicht quartalsbasiert.
|
||||||
|
|
||||||
|
### 4.3 Nachweisstatus
|
||||||
|
| Status | Bedeutung |
|
||||||
|
|---|---|
|
||||||
|
| `offen` | Nachweis ausstehend |
|
||||||
|
| `teilweise` | Teilweise eingereicht |
|
||||||
|
| `eingereicht` | Vollständig eingereicht |
|
||||||
|
| `geprueft` | Geprüft & Freigegeben |
|
||||||
|
| `auto_geprueft` | Automatisch freigegeben (Semesterbasis) |
|
||||||
|
| `nachbesserung` | Nachbesserung erforderlich |
|
||||||
|
| `abgelehnt` | Abgelehnt |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Einzel-Förderungen (Projektförderungen)
|
||||||
|
|
||||||
|
### 5.1 Antragsprozess
|
||||||
|
[TODO: Formalen Antragsprozess beschreiben]
|
||||||
|
|
||||||
|
Bekannte Felder aus der Förderungsdatenbank:
|
||||||
|
- Antragsteller (Destinatär)
|
||||||
|
- Jahr
|
||||||
|
- Betrag
|
||||||
|
- Kategorie
|
||||||
|
- Antragsdatum / Entscheidungsdatum
|
||||||
|
- Verwendungsnachweis
|
||||||
|
|
||||||
|
### 5.2 Förderungsstatus
|
||||||
|
| Status | Bedeutung |
|
||||||
|
|---|---|
|
||||||
|
| `beantragt` | Antrag gestellt |
|
||||||
|
| `genehmigt` | Genehmigt, noch nicht ausgezahlt |
|
||||||
|
| `ausgezahlt` | Ausgezahlt |
|
||||||
|
| `abgelehnt` | Abgelehnt |
|
||||||
|
| `storniert` | Storniert |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Prüfkriterien bei Förderanfragen
|
||||||
|
|
||||||
|
Checkliste für die Prüfung von Förderanträgen:
|
||||||
|
|
||||||
|
- [ ] Antragsteller ist Abkömmling gem. Satzung
|
||||||
|
- [ ] Einkommensgrenzen eingehalten
|
||||||
|
- [ ] Vermögensgrenze eingehalten
|
||||||
|
- [ ] Kategorie entspricht Stiftungszweck
|
||||||
|
- [ ] Betrag ist angemessen und plausibel
|
||||||
|
- [ ] Verwendungsnachweis planbar
|
||||||
|
- [ ] Kein Interessenkonflikt
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Zuletzt aktualisiert: 2026-03 | Quelle: Systemanalyse models.py, Softwarelogik*
|
||||||
101
knowledge/satzung.md
Normal file
101
knowledge/satzung.md
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
# Satzung und Stiftungszweck
|
||||||
|
|
||||||
|
> **Status:** Abgeleitet aus dem Systemcode und den Datenmodellen der Verwaltungsanwendung.
|
||||||
|
> Angaben ohne Quellennachweis aus der Originalsatzung sind als **[TODO: aus Satzungsurkunde prüfen]** markiert.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Stiftungsname und Sitz
|
||||||
|
|
||||||
|
- **Name:** van Hees-Theyssen-Vogel'sche Stiftung
|
||||||
|
- **Kurzbezeichnung:** VHTV-Stiftung
|
||||||
|
- **Sitz:** Raesfelder Str. 3, 46499 Hamminkeln (Nordrhein-Westfalen)
|
||||||
|
- **Website / E-Mail:** vhtv-stiftung.de / paperless@vhtv-stiftung.de
|
||||||
|
- **Rechtsform:** Gemeinnützige Stiftung des bürgerlichen Rechts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Stiftungszweck
|
||||||
|
|
||||||
|
[TODO: Stiftungszweck(e) aus der Satzungsurkunde entnehmen und eintragen]
|
||||||
|
|
||||||
|
Aus dem Systemcode ableitbar sind folgende Förderfelder, die die Stiftung in ihrer Verwaltungssoftware abbildet:
|
||||||
|
|
||||||
|
- **Bildung** – Förderung von Ausbildung und Studium
|
||||||
|
- **Forschung** – Unterstützung wissenschaftlicher Arbeiten
|
||||||
|
- **Kultur** – Kulturelle Projekte und Aktivitäten
|
||||||
|
- **Soziales** – Soziale Unterstützungsleistungen
|
||||||
|
- **Umwelt** – Umweltschutz und Nachhaltigkeit
|
||||||
|
- **Anderes** – Sonstige zweckentsprechende Maßnahmen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Förderberechtigter Personenkreis (Destinatäre)
|
||||||
|
|
||||||
|
Die Stiftung fördert **Destinatäre** – natürliche Personen, die der Stifterfamilie angehören oder ihr nahestehen.
|
||||||
|
|
||||||
|
### Familienzweige
|
||||||
|
Die Software unterscheidet folgende Familienzweige:
|
||||||
|
- Hauptzweig
|
||||||
|
- Nebenzweig
|
||||||
|
- Verwandt
|
||||||
|
- Anderer
|
||||||
|
|
||||||
|
### Fördervoraussetzungen (gemäß Systemlogik)
|
||||||
|
Für eine Förderung/Unterstützung müssen kumulativ erfüllt sein:
|
||||||
|
|
||||||
|
1. **Abkömmling gemäß Satzung** (`ist_abkoemmling = True`)
|
||||||
|
2. **Einkommensgrenzen eingehalten:**
|
||||||
|
- Basierend auf Regelsatz (563 €/Monat)
|
||||||
|
- Einkommensgrenze = 5 × Regelsatz für erste Person + 0,8 × Regelsatz je weiterer Haushaltsperson
|
||||||
|
- Beispiel Einzelperson: max. 2.815 €/Monat
|
||||||
|
- Beispiel 2 Personen: max. 3.265,40 €/Monat
|
||||||
|
3. **Vermögen ≤ 15.500 €**
|
||||||
|
|
||||||
|
Diese Werte sind aus der Softwarelogik abgeleitet und sollten mit der Satzung abgeglichen werden.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Organe der Stiftung
|
||||||
|
|
||||||
|
[TODO: Organe und deren Zusammensetzung aus der Satzungsurkunde entnehmen]
|
||||||
|
|
||||||
|
Aus dem System ableitbar:
|
||||||
|
- **Rentmeister / Geschäftsführung:** Hauptverwaltungsorgan, verwaltet Ländereien, Finanzen und Destinatäre
|
||||||
|
- [TODO: Vorstand? Kuratorium? Beirat? – aus Satzung prüfen]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Stiftungsvermögen
|
||||||
|
|
||||||
|
Das Stiftungsvermögen setzt sich zusammen aus:
|
||||||
|
|
||||||
|
- **Immobilien / Ländereien:** Landwirtschaftliche Nutzflächen (Grünland, Acker, Wald) in verschiedenen Gemeinden des Kreises Wesel/NRW, die verpachtet werden
|
||||||
|
- **Bankkonten:** Stiftungskonten bei [TODO: Bank(en) eintragen]
|
||||||
|
- **Sonstige Vermögenswerte:** [TODO: ergänzen]
|
||||||
|
|
||||||
|
Der Vermögenserhalt ist Grundprinzip der Stiftungsführung; Erträge werden für den Stiftungszweck eingesetzt.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Wichtige Regelungen
|
||||||
|
|
||||||
|
[TODO: Folgende Punkte aus der Satzungsurkunde entnehmen]
|
||||||
|
|
||||||
|
- Regelungen zur Mittelverwendung
|
||||||
|
- Rechnungslegung und Berichtspflichten
|
||||||
|
- Satzungsänderungen und Auflösung
|
||||||
|
- Stiftungsaufsicht (zuständige Behörde: [TODO])
|
||||||
|
- Steuerliche Anerkennung / Freistellungsbescheid [TODO: Finanzamt, AZ]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Stiftungsaufsicht
|
||||||
|
|
||||||
|
- **Zuständige Behörde:** [TODO: Bezirksregierung Düsseldorf? – prüfen, Hamminkeln liegt im Regierungsbezirk Düsseldorf]
|
||||||
|
- **Aktenzeichen Stiftungsregister:** [TODO]
|
||||||
|
- **Steuer-Nr. / Finanzamt:** [TODO]
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Zuletzt aktualisiert: 2026-03 | Quelle: Systemanalyse der Verwaltungssoftware*
|
||||||
176
knowledge/verfahren.md
Normal file
176
knowledge/verfahren.md
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
# Verwaltungsverfahren und Abläufe
|
||||||
|
|
||||||
|
> **Status:** Aus Systemcode, Celery-Tasks und App-Struktur abgeleitet.
|
||||||
|
> Punkte mit [TODO] sind manuell zu ergänzen.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Antragsprozess für Förderungen
|
||||||
|
|
||||||
|
### 1.1 Erstaufnahme eines Destinatärs
|
||||||
|
1. Destinatär-Datensatz anlegen (Django Admin oder Weboberfläche `/destinataere/`)
|
||||||
|
2. Pflichtfelder ausfüllen:
|
||||||
|
- Name, Geburtsdatum, E-Mail, Telefon
|
||||||
|
- Familienzweig
|
||||||
|
- `ist_abkoemmling` setzen
|
||||||
|
- Haushaltsgröße, monatliche Bezüge, Vermögen
|
||||||
|
3. Prüfung Fördervoraussetzungen (automatisch via `erfuellt_voraussetzungen()`)
|
||||||
|
4. `unterstuetzung_bestaetigt` setzen wenn Voraussetzungen erfüllt
|
||||||
|
5. Vierteljährlichen Betrag festlegen
|
||||||
|
6. Standard-Auszahlungskonto zuordnen
|
||||||
|
|
||||||
|
### 1.2 Einzel-Förderungsantrag
|
||||||
|
1. Förderung anlegen unter `/foerderungen/`
|
||||||
|
2. Status beginnt mit `beantragt`
|
||||||
|
3. Prüfung durch Rentmeister
|
||||||
|
4. Entscheidung: `genehmigt` oder `abgelehnt`
|
||||||
|
5. Nach Auszahlung: Status auf `ausgezahlt` setzen
|
||||||
|
6. Verwendungsnachweis als Dokument in Paperless hochladen und verknüpfen
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Vierteljährliches Nachweisverfahren
|
||||||
|
|
||||||
|
### 2.1 Ablauf
|
||||||
|
1. Zu Beginn jedes Quartals: VierteljahresNachweis-Datensätze für alle aktiven Destinatäre erstellen
|
||||||
|
2. Destinatäre werden benachrichtigt (per E-Mail, [TODO: Benachrichtigungsvorlage?])
|
||||||
|
3. Destinatäre reichen Unterlagen ein:
|
||||||
|
- Per E-Mail an paperless@vhtv-stiftung.de (automatisch erfasst)
|
||||||
|
- Oder direkt über das Self-Service-Portal (sofern eingerichtet)
|
||||||
|
4. Rentmeister prüft eingegangene Unterlagen
|
||||||
|
5. Status aktualisieren: `eingereicht` → `geprueft`
|
||||||
|
6. Zahlung freigeben wenn Nachweis genehmigt
|
||||||
|
|
||||||
|
### 2.2 Fristen (Überblick)
|
||||||
|
- **Studiennachweis:** 15. März (Q1/Q2) bzw. 15. September (Q3/Q4)
|
||||||
|
- **Zahlung:** 15. Dez. Vorjahr (Q1), 15. März (Q2), 15. Juni (Q3), 15. September (Q4)
|
||||||
|
|
||||||
|
### 2.3 Automatische Freigabe
|
||||||
|
Bei Destinatären mit Semesterbasis-Studiennachweis kann eine automatische Freigabe erfolgen (`auto_geprueft`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. E-Mail-Eingangsverarbeitung (Automatisiert)
|
||||||
|
|
||||||
|
### 3.1 Übersicht
|
||||||
|
Das System verarbeitet automatisch eingehende E-Mails an `paperless@vhtv-stiftung.de`.
|
||||||
|
|
||||||
|
**Technologie:** Celery Beat Task, läuft alle 15 Minuten
|
||||||
|
|
||||||
|
### 3.2 Konfiguration
|
||||||
|
| Env-Variable | Standard | Bedeutung |
|
||||||
|
|---|---|---|
|
||||||
|
| `IMAP_HOST` | – | IMAP-Server (Pflicht) |
|
||||||
|
| `IMAP_PORT` | 993 | IMAP-Port (SSL) |
|
||||||
|
| `IMAP_USER` | paperless@vhtv-stiftung.de | Benutzername |
|
||||||
|
| `IMAP_PASSWORD` | – | Passwort (Pflicht) |
|
||||||
|
| `IMAP_FOLDER` | INBOX | E-Mail-Ordner |
|
||||||
|
| `IMAP_USE_SSL` | true | SSL verwenden |
|
||||||
|
|
||||||
|
### 3.3 Workflow
|
||||||
|
1. System liest ungelesene E-Mails aus dem IMAP-Postfach
|
||||||
|
2. Absender-E-Mail wird mit Destinatär-Datenbank abgeglichen
|
||||||
|
3. `DestinataerEmailEingang`-Datensatz wird angelegt
|
||||||
|
4. Anhänge werden in **Paperless-NGX** hochgeladen mit Tag `Stiftung_Destinatäre`
|
||||||
|
5. Für jeden Anhang wird ein `DokumentLink` erstellt
|
||||||
|
6. Unbekannte Absender werden als `unbekannt` markiert (manuelle Nachbearbeitung nötig)
|
||||||
|
|
||||||
|
### 3.4 Paperless-Tags
|
||||||
|
| Tag | Verwendung |
|
||||||
|
|---|---|
|
||||||
|
| `Stiftung_Destinatäre` | Dokumente von/für Destinatäre |
|
||||||
|
| `Stiftung_Land_und_Pächter` | Dokumente zu Ländereien/Pächtern |
|
||||||
|
| `Stiftung_Administration` | Verwaltungsdokumente |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Pachtvertragsverwaltung
|
||||||
|
|
||||||
|
### 4.1 Datenstruktur
|
||||||
|
- **Land** (Flurstück): Grundeinheit, identifiziert durch `lfd_nr`
|
||||||
|
- **LandVerpachtung**: Pachtvertrag (neue Struktur)
|
||||||
|
- **LandAbrechnung**: Jährliche Abrechnung je Flurstück
|
||||||
|
|
||||||
|
### 4.2 Anlage eines Pachtvertrags
|
||||||
|
1. Land-Datensatz prüfen/anlegen (`/land/`)
|
||||||
|
2. Pächter anlegen (`/paechter/`) falls nicht vorhanden
|
||||||
|
3. LandVerpachtung anlegen (`/land/<id>/verpachtung/`)
|
||||||
|
- Vertragsnummer vergeben
|
||||||
|
- Pachtbeginn, Pachtende, Verlängerungsklausel
|
||||||
|
- Pachtzins (pro ha oder pauschal)
|
||||||
|
- Zahlungsweise
|
||||||
|
- USt-Option und Umlagen konfigurieren
|
||||||
|
4. System aktualisiert LandAbrechnung automatisch
|
||||||
|
|
||||||
|
### 4.3 Pachtzins-Zahlungsweisen
|
||||||
|
| Option | Beschreibung |
|
||||||
|
|---|---|
|
||||||
|
| `jaehrlich` | Einmal jährlich |
|
||||||
|
| `halbjaehrlich` | Zweimal jährlich |
|
||||||
|
| `vierteljaehrlich` | Quartalsweise |
|
||||||
|
| `monatlich` | Monatlich |
|
||||||
|
|
||||||
|
### 4.4 Umlagen (Durchreichungen an Pächter)
|
||||||
|
Folgende Kosten können als Umlage auf Pächter umgelegt werden:
|
||||||
|
- Grundsteuer
|
||||||
|
- Versicherungen
|
||||||
|
- Verbandsbeiträge
|
||||||
|
- Jagdpachtanteile (optional)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Abrechnungsverfahren für Ländereien (LandAbrechnung)
|
||||||
|
|
||||||
|
### 5.1 Jährliche Abrechnung
|
||||||
|
- Pro Flurstück wird automatisch eine `LandAbrechnung` für jedes Abrechnungsjahr erstellt
|
||||||
|
- Felder: Pacht vereinnahmt, Umlagen, sonstige Einnahmen
|
||||||
|
- Ausgaben: nach Kategorien (Grundsteuer, Versicherung, Verwaltung etc.)
|
||||||
|
- USt-Berechnung wenn USt-Option aktiv
|
||||||
|
|
||||||
|
### 5.2 Dokumentenablage
|
||||||
|
Relevante Dokumente (Pachtverträge, Grundsteuerbescheide, Versicherungsnachweise) werden in **Paperless-NGX** abgelegt und per `DokumentLink` verknüpft.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Backup-Verfahren
|
||||||
|
|
||||||
|
### 6.1 Backup-Typen
|
||||||
|
| Typ | Inhalt |
|
||||||
|
|---|---|
|
||||||
|
| `full` | Datenbank + Dateien |
|
||||||
|
| `database` | Nur PostgreSQL-Datenbank |
|
||||||
|
| `files` | Nur Mediendateien |
|
||||||
|
|
||||||
|
### 6.2 Ablauf
|
||||||
|
1. Backup-Job über Weboberfläche anlegen (`/backup/`)
|
||||||
|
2. System erstellt Backup asynchron im Hintergrund
|
||||||
|
3. Backup wird als `.tar.gz` unter `/app/backups/` gespeichert
|
||||||
|
4. Status: `pending` → `running` → `completed` / `failed`
|
||||||
|
|
||||||
|
### 6.3 Speicherort
|
||||||
|
- **Container:** `/app/backups/`
|
||||||
|
- **Dateiname:** `stiftung_backup_YYYYMMDD_HHMMSS.tar.gz`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Verwaltungskosten-Erfassung
|
||||||
|
|
||||||
|
Verwaltungskosten werden kategorisiert erfasst:
|
||||||
|
- Bezeichnung, Kategorie, Betrag, Datum
|
||||||
|
- Lieferant/Firma, Rechnungsnummer
|
||||||
|
- Zuordnung zu Rentmeister (für Fahrtkosten etc.)
|
||||||
|
- Kilometerpauschale: Standard 0,30 €/km
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Audit Trail
|
||||||
|
|
||||||
|
Alle Änderungen in der Anwendung werden im `AuditLog` erfasst:
|
||||||
|
- Benutzer, Zeitstempel, Aktion
|
||||||
|
- Entitätstyp, ID, Name
|
||||||
|
- Änderungsdetails (JSON)
|
||||||
|
- IP-Adresse, Browser
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Zuletzt aktualisiert: 2026-03 | Quelle: Systemcode tasks.py, models.py, backup_utils.py*
|
||||||
11
scripts/init-paperless-db.sh
Executable file
11
scripts/init-paperless-db.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
# Create separate database for Paperless-NGX if it doesn't exist
|
||||||
|
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||||
|
SELECT 'CREATE DATABASE paperless_dev'
|
||||||
|
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'paperless_dev')\gexec
|
||||||
|
|
||||||
|
SELECT 'CREATE DATABASE paperless'
|
||||||
|
WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = 'paperless')\gexec
|
||||||
|
EOSQL
|
||||||
Reference in New Issue
Block a user