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,170 @@
{% extends 'base.html' %}
{% load static %}
{% block title %}Nachweis-Board 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-th text-primary me-2"></i>
Nachweis-Board {{ jahr_filter }}
</h1>
<div class="d-flex gap-2">
{% if overdue_count > 0 %}
<form method="post" action="{% url 'stiftung:batch_erinnerung_senden' %}">
{% csrf_token %}
<input type="hidden" name="jahr" value="{{ jahr_filter }}">
<button type="submit" class="btn btn-danger" onclick="return confirm('{{ overdue_count }} säumige Destinatäre markieren?')">
<i class="fas fa-bell me-2"></i>{{ overdue_count }} Erinnerung(en)
</button>
</form>
{% endif %}
</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">Jahr:</label>
<select name="jahr" class="form-select form-select-sm" style="width: auto;" onchange="this.form.submit()">
{% for j in verfuegbare_jahre %}
<option value="{{ j }}" {% if j == jahr_filter %}selected{% endif %}>{{ j }}</option>
{% endfor %}
</select>
</div>
<div class="d-flex align-items-center gap-2">
<label class="text-muted small fw-bold mb-0">Status:</label>
<select name="status" class="form-select form-select-sm" style="width: auto;" onchange="this.form.submit()">
<option value="">Alle</option>
{% for code, label in status_choices %}
<option value="{{ code }}" {% if code == status_filter %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<span class="ms-auto text-muted small">{{ board|length }} Destinatäre</span>
</form>
</div>
</div>
<!-- Semester-Hinweis -->
<div class="alert alert-info py-2 mb-4 small">
<i class="fas fa-info-circle me-2"></i>
<strong>Semester-Logik:</strong>
Studiennachweis-Frist Q1/Q2 → 15. März · Q3/Q4 → 15. September.
Zahlungsfrist Q1 → 15. Dez (Vorjahr) · Q2 → 15. Mär · Q3 → 15. Jun · Q4 → 15. Sep.
</div>
<!-- Board Table -->
{% if board %}
<div class="card shadow">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover table-sm mb-0">
<thead class="table-dark">
<tr>
<th style="min-width:180px">Destinatär</th>
<th class="text-center">Q1 (JanMär)</th>
<th class="text-center">Q2 (AprJun)</th>
<th class="text-center">Q3 (JulSep)</th>
<th class="text-center">Q4 (OktDez)</th>
<th class="text-center">Gesamt</th>
</tr>
</thead>
<tbody>
{% for row in board %}
<tr>
<td class="align-middle">
<a href="{% url 'stiftung:destinataer_detail' pk=row.destinataer.pk %}" class="text-decoration-none fw-semibold">
{{ row.destinataer.get_full_name }}
</a>
<div class="small text-muted">
<a href="{% url 'stiftung:destinataer_timeline' pk=row.destinataer.pk %}" class="text-muted">
<i class="fas fa-stream"></i> Timeline
</a>
</div>
</td>
{% for q in "1234" %}
{% with nachweis=row.quartale|get_item:q|default:None %}
<td class="text-center align-middle">
{% if nachweis %}
{% if nachweis.status == 'geprueft' or nachweis.status == 'auto_geprueft' %}
<span class="badge bg-success" title="{{ nachweis.get_status_display }}">
<i class="fas fa-check"></i>
</span>
{% elif nachweis.status == 'eingereicht' %}
<span class="badge bg-info" title="{{ nachweis.get_status_display }}">
<i class="fas fa-paper-plane"></i>
</span>
{% elif nachweis.status == 'teilweise' %}
<span class="badge bg-warning text-dark" title="{{ nachweis.get_status_display }}">
<i class="fas fa-circle-half-stroke"></i>
</span>
{% elif nachweis.status == 'nachbesserung' %}
<span class="badge bg-orange" title="{{ nachweis.get_status_display }}" style="background:#fd7e14">
<i class="fas fa-redo"></i>
</span>
{% elif nachweis.status == 'abgelehnt' %}
<span class="badge bg-danger" title="{{ nachweis.get_status_display }}">
<i class="fas fa-times"></i>
</span>
{% elif nachweis.is_overdue %}
<span class="badge bg-danger" title="Überfällig">
<i class="fas fa-exclamation-triangle"></i>
</span>
{% else %}
<span class="badge bg-secondary" title="{{ nachweis.get_status_display }}">
<i class="fas fa-clock"></i>
</span>
{% endif %}
<div class="small mt-1">
<a href="{% url 'stiftung:quarterly_confirmation_edit' pk=nachweis.pk %}" class="text-muted">
{{ nachweis.get_completion_percentage }}%
</a>
</div>
{% else %}
<span class="text-muted small"></span>
{% endif %}
</td>
{% endwith %}
{% endfor %}
<td class="text-center align-middle">
{% with total=0 done=0 %}
{% for q, nachweis in row.quartale.items %}
{% if nachweis %}
{% if nachweis.status == 'geprueft' or nachweis.status == 'auto_geprueft' %}
<i class="fas fa-check-circle text-success"></i>
{% endif %}
{% endif %}
{% endfor %}
{% endwith %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<!-- Legend -->
<div class="mt-3 d-flex flex-wrap gap-3 small text-muted">
<span><span class="badge bg-success"><i class="fas fa-check"></i></span> Geprüft</span>
<span><span class="badge bg-info"><i class="fas fa-paper-plane"></i></span> Eingereicht</span>
<span><span class="badge bg-warning text-dark"><i class="fas fa-circle-half-stroke"></i></span> Teilweise</span>
<span><span class="badge bg-danger"><i class="fas fa-exclamation-triangle"></i></span> Überfällig</span>
<span><span class="badge bg-secondary"><i class="fas fa-clock"></i></span> Offen</span>
</div>
{% else %}
<div class="alert alert-info">
<i class="fas fa-info-circle me-2"></i>
Keine aktiven Destinatäre gefunden.
</div>
{% endif %}
</div>
</div>
{% endblock %}