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

@@ -0,0 +1,162 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Zahlungs-Pipeline Stiftungsverwaltung{% endblock %}
{% block content %}
<div class="row">
<div class="col-12">
<!-- Header -->
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3">
<i class="fas fa-tasks text-primary me-2"></i>
Zahlungs-Pipeline
</h1>
<div class="d-flex gap-2">
<a href="{% url 'stiftung:sepa_xml_export' %}" class="btn btn-outline-success">
<i class="fas fa-file-code me-2"></i>SEPA XML exportieren
</a>
<a href="{% url 'stiftung:unterstuetzung_create' %}" class="btn btn-primary">
<i class="fas fa-plus me-2"></i>Neue Zahlung
</a>
</div>
</div>
<!-- Filter Bar -->
<div class="card shadow mb-4">
<div class="card-body py-2">
<form method="get" class="d-flex gap-3 flex-wrap align-items-center">
<div class="d-flex align-items-center gap-2">
<label class="text-muted small fw-bold mb-0">Destinatär:</label>
<select name="destinataer" class="form-select form-select-sm" style="width:200px" onchange="this.form.submit()">
<option value="">Alle</option>
{% for d in destinataere %}
<option value="{{ d.pk }}" {% if d.pk|stringformat:'s' == destinataer_filter %}selected{% endif %}>{{ d.get_full_name }}</option>
{% endfor %}
</select>
</div>
<div class="d-flex align-items-center gap-2">
<label class="text-muted small fw-bold mb-0">Konto:</label>
<select name="konto" class="form-select form-select-sm" style="width:180px" onchange="this.form.submit()">
<option value="">Alle</option>
{% for k in konten %}
<option value="{{ k.pk }}" {% if k.pk|stringformat:'s' == konto_filter %}selected{% endif %}>{{ k.kontoname }}</option>
{% endfor %}
</select>
</div>
{% if destinataer_filter or konto_filter %}
<a href="{% url 'stiftung:zahlungs_pipeline' %}" class="btn btn-sm btn-outline-secondary">
<i class="fas fa-times"></i> Filter zurücksetzen
</a>
{% endif %}
</form>
</div>
</div>
<!-- 4-Augen Hinweis -->
<div class="alert alert-warning py-2 mb-4 small">
<i class="fas fa-shield-alt me-2"></i>
<strong>Vier-Augen-Prinzip:</strong>
Zahlungen können nur von einem <em>anderen</em> Nutzer als dem Ersteller freigegeben werden.
SEPA-Export ist nur für Zahlungen im Status „Freigegeben" verfügbar.
</div>
<!-- Pipeline Kanban -->
<div class="row g-3">
{% for stage in pipeline_stages %}
<div class="col-xl col-lg-4 col-md-6">
<div class="card shadow h-100">
<div class="card-header bg-{{ stage.farbe }} {% if stage.farbe == 'secondary' or stage.farbe == 'dark' %}text-white{% elif stage.farbe == 'warning' %}text-dark{% else %}text-white{% endif %} py-2">
<div class="d-flex justify-content-between align-items-center">
<span class="fw-semibold small">
<i class="fas {{ stage.icon }} me-1"></i>{{ stage.label }}
</span>
<span class="badge bg-white text-dark">{{ stage.zahlungen|length }}</span>
</div>
{% if stage.gesamt > 0 %}
<div class="small opacity-75 mt-1">€{{ stage.gesamt|floatformat:2 }}</div>
{% endif %}
</div>
<div class="card-body p-2" style="max-height: 600px; overflow-y: auto;">
{% if stage.zahlungen %}
{% for z in stage.zahlungen %}
<div class="card mb-2 border shadow-sm" style="font-size:0.8rem;">
<div class="card-body py-2 px-2">
<div class="d-flex justify-content-between align-items-start">
<div class="fw-semibold">
<a href="{% url 'stiftung:destinataer_detail' pk=z.destinataer.pk %}" class="text-decoration-none text-dark">
{{ z.destinataer.get_full_name }}
</a>
</div>
<span class="badge bg-{{ stage.farbe }} {% if stage.farbe == 'warning' %}text-dark{% else %}text-white{% endif %}">
€{{ z.betrag|floatformat:2 }}
</span>
</div>
<div class="text-muted mt-1">
Fällig: {{ z.faellig_am|date:"d.m.Y" }}
{% if z.is_overdue %}
<span class="badge bg-danger ms-1">überfällig</span>
{% endif %}
</div>
{% if z.beschreibung %}
<div class="text-muted small mt-1">{{ z.beschreibung }}</div>
{% endif %}
{% if z.freigegeben_von %}
<div class="small text-success mt-1">
<i class="fas fa-shield-alt"></i>
{{ z.freigegeben_von.get_full_name|default:z.freigegeben_von.username }}
({{ z.freigegeben_am|date:"d.m.Y" }})
</div>
{% endif %}
<!-- Actions -->
<div class="mt-2 d-flex gap-1 flex-wrap">
{% if stage.key == 'offen' %}
<form method="post" action="{% url 'stiftung:unterstuetzung_nachweis_eingereicht' pk=z.pk %}">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'stiftung:zahlungs_pipeline' %}">
<button type="submit" class="btn btn-xs btn-outline-info" style="font-size:0.7rem;padding:2px 6px;" title="Nachweis eingereicht">
<i class="fas fa-file-alt"></i>
</button>
</form>
{% elif stage.key == 'nachweis_eingereicht' %}
<form method="post" action="{% url 'stiftung:unterstuetzung_freigeben' pk=z.pk %}">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'stiftung:zahlungs_pipeline' %}">
<button type="submit" class="btn btn-xs btn-outline-warning" style="font-size:0.7rem;padding:2px 6px;" title="4-Augen-Freigabe">
<i class="fas fa-shield-alt"></i>
</button>
</form>
{% elif stage.key == 'freigegeben' %}
<a href="{% url 'stiftung:unterstuetzung_mark_paid' pk=z.pk %}" class="btn btn-xs btn-outline-success" style="font-size:0.7rem;padding:2px 6px;" title="Als überwiesen markieren">
<i class="fas fa-university"></i>
</a>
{% elif stage.key == 'ueberwiesen' %}
<form method="post" action="{% url 'stiftung:unterstuetzung_abschliessen' pk=z.pk %}">
{% csrf_token %}
<input type="hidden" name="next" value="{% url 'stiftung:zahlungs_pipeline' %}">
<button type="submit" class="btn btn-xs btn-outline-dark" style="font-size:0.7rem;padding:2px 6px;" title="Abschließen">
<i class="fas fa-check-double"></i>
</button>
</form>
{% endif %}
<a href="{% url 'stiftung:unterstuetzung_detail' pk=z.pk %}" class="btn btn-xs btn-outline-secondary" style="font-size:0.7rem;padding:2px 6px;" title="Details">
<i class="fas fa-eye"></i>
</a>
</div>
</div>
</div>
{% endfor %}
{% else %}
<div class="text-muted text-center small py-4">
<i class="fas fa-inbox fa-2x mb-2 d-block opacity-25"></i>
Keine Zahlungen
</div>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
{% endblock %}