Phase 2: Destinatär-Timeline, Nachweis-Board, Zahlungs-Pipeline & Pächter-Workflow

2a. Destinatär-Timeline (/destinataere/<pk>/timeline/)
    - Chronologische Ansicht aller Events (Zahlungen, Nachweise, E-Mails, Notizen)
    - Filter nach Typ via GET-Parameter

2b. Nachweis-Board (/nachweis-board/)
    - Quartals-Übersicht aller aktiver Destinatäre (Q1–Q4) in einer Tabellenansicht
    - Batch-Erinnerung: erzeugt Audit-Log-Einträge für säumige Destinatäre
    - Semester-Logik erhalten (15.03 / 15.09 Fristen)

2c. Zahlungs-Pipeline (/zahlungs-pipeline/)
    - 5-Stufen-Kanban: Offen → Nachweis eingereicht → Freigegeben → Überwiesen → Abgeschlossen
    - Vier-Augen-Prinzip: can_be_freigegeben() prüft anderen Nutzer als Ersteller
    - SEPA pain.001 XML-Export (/sepa-export/) für freigegebene Zahlungen
    - Neue Status-Werte: nachweis_eingereicht, freigegeben, abgeschlossen
    - Neue Felder: freigegeben_von, freigegeben_am, erstellt_von

2d. Pächter-Workflow (/paechter/workflow/)
    - Pipeline nach Restlaufzeit: abgelaufen / <6M / 6–24M / >24M / unbefristet
    - Ausstehende Jahresabrechnungen (Vorjahr ohne Abrechnung)
    - Pachtanpassungen fällig (Verträge > 5 Jahre laufend)
    - Top-Pächter nach Gesamtfläche

Sidebar-Navigation um Pipeline, Nachweis-Board und Pacht-Workflow erweitert.
Migration 0047 erzeugt und angewendet.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
SysAdmin Agent
2026-03-11 10:40:43 +00:00
parent bf47ba11c9
commit ee2c827d85
11 changed files with 1327 additions and 3 deletions

View File

@@ -346,13 +346,25 @@ class DestinataerUnterstuetzung(models.Model):
"""Geplante/ausgeführte Unterstützungszahlungen an Destinatäre"""
STATUS_CHOICES = [
("geplant", "Geplant"),
("geplant", "Offen"),
("faellig", "Fällig"),
("nachweis_eingereicht", "Nachweis eingereicht"),
("freigegeben", "Freigegeben (4-Augen)"),
("in_bearbeitung", "In Bearbeitung"),
("ausgezahlt", "Ausgezahlt"),
("ausgezahlt", "Überwiesen"),
("abgeschlossen", "Abgeschlossen"),
("storniert", "Storniert"),
]
# Pipeline-stage für Zahlungsstatus-Anzeige
PIPELINE_STAGES = [
("geplant", "Offen"),
("nachweis_eingereicht", "Nachweis eingereicht"),
("freigegeben", "Freigegeben"),
("ausgezahlt", "Überwiesen"),
("abgeschlossen", "Abgeschlossen"),
]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
destinataer = models.ForeignKey(
"Destinataer",
@@ -392,9 +404,32 @@ class DestinataerUnterstuetzung(models.Model):
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="ausgezahlte_unterstuetzungen",
verbose_name="Ausgezahlt von",
)
# 4-Augen-Prinzip: Freigabe durch zweiten Nutzer
freigegeben_am = models.DateField(
null=True, blank=True, verbose_name="Freigegeben am"
)
freigegeben_von = models.ForeignKey(
"auth.User",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="freigegebene_unterstuetzungen",
verbose_name="Freigegeben von (4-Augen)",
help_text="Muss ein anderer Nutzer als der Ersteller sein (Vier-Augen-Prinzip)",
)
erstellt_von = models.ForeignKey(
"auth.User",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="erstellte_unterstuetzungen",
verbose_name="Erstellt von",
)
# Link to recurrent payment template if this was auto-generated
wiederkehrend_von = models.ForeignKey(
"UnterstuetzungWiederkehrend",
@@ -431,7 +466,29 @@ class DestinataerUnterstuetzung(models.Model):
def can_be_marked_paid(self):
"""Check if payment can be marked as paid"""
return self.status in ["geplant", "faellig", "in_bearbeitung"]
return self.status in ["geplant", "faellig", "nachweis_eingereicht", "freigegeben", "in_bearbeitung"]
def can_be_freigegeben(self, requesting_user):
"""4-Augen: Freigabe nur durch anderen Nutzer als Ersteller"""
if self.status not in ["nachweis_eingereicht", "faellig", "in_bearbeitung"]:
return False
if self.erstellt_von and self.erstellt_von == requesting_user:
return False # Selber Nutzer darf nicht freigeben
return True
def get_pipeline_stage(self):
"""Gibt die Pipeline-Stufe als Integer zurück (1-5)"""
stage_map = {
"geplant": 1,
"faellig": 2,
"nachweis_eingereicht": 2,
"in_bearbeitung": 3,
"freigegeben": 3,
"ausgezahlt": 4,
"abgeschlossen": 5,
"storniert": 0,
}
return stage_map.get(self.status, 1)
class UnterstuetzungWiederkehrend(models.Model):