Add Verwaltungskosten detail view with linked documents and emails

- New detail view at /geschaeftsfuehrung/verwaltungskosten/<pk>/
  showing invoice data, status, linked DMS documents, and emails
- Status change form in sidebar for quick workflow updates
- Link Verwaltungskosten list items to detail page
- Update email detail to link to VK detail instead of edit

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
SysAdmin Agent
2026-03-12 11:21:04 +00:00
parent 5c9db56158
commit d84421ea38
6 changed files with 294 additions and 2 deletions

View File

@@ -180,6 +180,11 @@ urlpatterns = [
views.verwaltungskosten_create, views.verwaltungskosten_create,
name="verwaltungskosten_create", name="verwaltungskosten_create",
), ),
path(
"geschaeftsfuehrung/verwaltungskosten/<uuid:pk>/",
views.verwaltungskosten_detail,
name="verwaltungskosten_detail",
),
path( path(
"geschaeftsfuehrung/verwaltungskosten/<uuid:pk>/bearbeiten/", "geschaeftsfuehrung/verwaltungskosten/<uuid:pk>/bearbeiten/",
views.verwaltungskosten_edit, views.verwaltungskosten_edit,

View File

@@ -32,6 +32,7 @@ from .finanzen import ( # noqa: F401
geschaeftsfuehrung, geschaeftsfuehrung,
konto_list, konto_list,
verwaltungskosten_list, verwaltungskosten_list,
verwaltungskosten_detail,
rentmeister_list, rentmeister_list,
rentmeister_detail, rentmeister_detail,
rentmeister_ausgaben, rentmeister_ausgaben,

View File

@@ -312,6 +312,38 @@ def verwaltungskosten_list(request):
return render(request, "stiftung/verwaltungskosten_list.html", context) return render(request, "stiftung/verwaltungskosten_list.html", context)
@login_required
def verwaltungskosten_detail(request, pk):
"""Detailansicht einer Verwaltungskosten-Position mit verknüpften Dokumenten und E-Mails."""
from stiftung.models import DokumentDatei, EmailEingang, Verwaltungskosten
vk = get_object_or_404(Verwaltungskosten, pk=pk)
if request.method == "POST":
action = request.POST.get("action")
if action == "set_status":
new_status = request.POST.get("status", "")
if new_status in dict(Verwaltungskosten.STATUS_CHOICES):
vk.status = new_status
vk.save()
messages.success(request, f"Status auf '{vk.get_status_display()}' gesetzt.")
return redirect("stiftung:verwaltungskosten_detail", pk=pk)
# Verknüpfte DMS-Dokumente
dms_dokumente = DokumentDatei.objects.filter(verwaltungskosten=vk).order_by("erstellt_am")
# Verknüpfte E-Mails
email_eingaenge = EmailEingang.objects.filter(verwaltungskosten=vk).order_by("-eingangsdatum")
context = {
"vk": vk,
"dms_dokumente": dms_dokumente,
"email_eingaenge": email_eingaenge,
"status_choices": Verwaltungskosten.STATUS_CHOICES,
}
return render(request, "stiftung/verwaltungskosten_detail.html", context)
@login_required @login_required
def rentmeister_list(request): def rentmeister_list(request):
"""Liste aller Rentmeister""" """Liste aller Rentmeister"""

View File

@@ -70,7 +70,7 @@
{% if eingang.verwaltungskosten %} {% if eingang.verwaltungskosten %}
<dt class="col-sm-3">Rechnung</dt> <dt class="col-sm-3">Rechnung</dt>
<dd class="col-sm-9"> <dd class="col-sm-9">
<a href="{% url 'stiftung:verwaltungskosten_edit' eingang.verwaltungskosten.pk %}"> <a href="{% url 'stiftung:verwaltungskosten_detail' eingang.verwaltungskosten.pk %}">
{{ eingang.verwaltungskosten.bezeichnung }} ({{ eingang.verwaltungskosten.betrag }} EUR) {{ eingang.verwaltungskosten.bezeichnung }} ({{ eingang.verwaltungskosten.betrag }} EUR)
</a> </a>
<span class="badge bg-{{ eingang.verwaltungskosten.get_status_color }}">{{ eingang.verwaltungskosten.get_status_display }}</span> <span class="badge bg-{{ eingang.verwaltungskosten.get_status_color }}">{{ eingang.verwaltungskosten.get_status_display }}</span>

View File

@@ -0,0 +1,254 @@
{% extends 'base.html' %}
{% load humanize %}
{% block title %}{{ vk.bezeichnung }} Verwaltungskosten{% endblock %}
{% block content %}
<div class="row">
<div class="col-lg-8">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3">
<i class="fas fa-file-invoice-dollar text-primary me-2"></i>
{{ vk.bezeichnung }}
</h1>
<div class="d-flex gap-2">
<a href="{% url 'stiftung:verwaltungskosten_edit' vk.pk %}" class="btn btn-outline-primary">
<i class="fas fa-edit me-1"></i>Bearbeiten
</a>
<a href="{% url 'stiftung:verwaltungskosten_list' %}" class="btn btn-outline-secondary">
<i class="fas fa-arrow-left me-1"></i>Zurück
</a>
</div>
</div>
<!-- Status + Kategorie Header -->
<div class="d-flex gap-2 mb-3">
<span class="badge bg-{{ vk.get_status_color }} fs-6">{{ vk.get_status_display }}</span>
<span class="badge bg-info fs-6">{{ vk.get_kategorie_display }}</span>
</div>
<!-- Grunddaten -->
<div class="card shadow mb-4">
<div class="card-header bg-dark text-white py-2">
<span class="small fw-bold"><i class="fas fa-info-circle me-2"></i>Rechnungsdaten</span>
</div>
<div class="card-body">
<dl class="row mb-0">
<dt class="col-sm-4 text-muted small">Bezeichnung</dt>
<dd class="col-sm-8 fw-bold">{{ vk.bezeichnung }}</dd>
<dt class="col-sm-4 text-muted small">Betrag</dt>
<dd class="col-sm-8"><span class="fs-5 fw-bold text-success">&euro;{{ vk.betrag|floatformat:2 }}</span></dd>
<dt class="col-sm-4 text-muted small">Datum</dt>
<dd class="col-sm-8">{{ vk.datum|date:"d.m.Y" }}</dd>
{% if vk.lieferant_firma %}
<dt class="col-sm-4 text-muted small">Lieferant / Firma</dt>
<dd class="col-sm-8">{{ vk.lieferant_firma }}</dd>
{% endif %}
{% if vk.rechnungsnummer %}
<dt class="col-sm-4 text-muted small">Rechnungsnummer</dt>
<dd class="col-sm-8"><code>{{ vk.rechnungsnummer }}</code></dd>
{% endif %}
{% if vk.rentmeister %}
<dt class="col-sm-4 text-muted small">Rentmeister</dt>
<dd class="col-sm-8">
<a href="{% url 'stiftung:rentmeister_detail' vk.rentmeister.pk %}">
{{ vk.rentmeister.get_full_name }}
</a>
</dd>
{% endif %}
{% if vk.get_effective_zahlungskonto %}
<dt class="col-sm-4 text-muted small">Zahlungskonto</dt>
<dd class="col-sm-8">{{ vk.get_effective_zahlungskonto.bank_name }} {{ vk.get_effective_zahlungskonto.kontoname }}</dd>
{% endif %}
{% if vk.quellkonto %}
<dt class="col-sm-4 text-muted small">Quellkonto</dt>
<dd class="col-sm-8">{{ vk.quellkonto.bank_name }} {{ vk.quellkonto.kontoname }}</dd>
{% endif %}
</dl>
</div>
</div>
{% if vk.is_fahrtkosten %}
<!-- Fahrtkosten Details -->
<div class="card shadow mb-4">
<div class="card-header bg-dark text-white py-2">
<span class="small fw-bold"><i class="fas fa-route me-2"></i>Fahrtkosten</span>
</div>
<div class="card-body">
<dl class="row mb-0">
{% if vk.km_anzahl %}
<dt class="col-sm-4 text-muted small">Kilometer</dt>
<dd class="col-sm-8">{{ vk.km_anzahl }} km</dd>
{% endif %}
{% if vk.km_satz %}
<dt class="col-sm-4 text-muted small">km-Satz</dt>
<dd class="col-sm-8">&euro;{{ vk.km_satz|floatformat:2 }}/km</dd>
{% endif %}
{% if vk.von_ort %}
<dt class="col-sm-4 text-muted small">Von</dt>
<dd class="col-sm-8">{{ vk.von_ort }}</dd>
{% endif %}
{% if vk.nach_ort %}
<dt class="col-sm-4 text-muted small">Nach</dt>
<dd class="col-sm-8">{{ vk.nach_ort }}</dd>
{% endif %}
{% if vk.zweck %}
<dt class="col-sm-4 text-muted small">Zweck</dt>
<dd class="col-sm-8">{{ vk.zweck }}</dd>
{% endif %}
</dl>
</div>
</div>
{% endif %}
{% if vk.beschreibung %}
<!-- Beschreibung -->
<div class="card shadow mb-4">
<div class="card-header bg-dark text-white py-2">
<span class="small fw-bold"><i class="fas fa-align-left me-2"></i>Beschreibung</span>
</div>
<div class="card-body">
<p class="mb-0" style="white-space: pre-line;">{{ vk.beschreibung }}</p>
</div>
</div>
{% endif %}
{% if vk.notizen %}
<div class="card shadow mb-4">
<div class="card-header bg-dark text-white py-2">
<span class="small fw-bold"><i class="fas fa-sticky-note me-2"></i>Notizen</span>
</div>
<div class="card-body">
<p class="mb-0" style="white-space: pre-line;">{{ vk.notizen }}</p>
</div>
</div>
{% endif %}
<!-- DMS-Dokumente -->
<div class="card shadow mb-4">
<div class="card-header bg-dark text-white py-2 d-flex justify-content-between align-items-center">
<span class="small fw-bold"><i class="fas fa-file-alt me-2"></i>Verknüpfte Dokumente ({{ dms_dokumente.count }})</span>
<a href="{% url 'stiftung:dms_upload' %}?kontext=rechnung" class="btn btn-sm btn-outline-light">
<i class="fas fa-upload me-1"></i>Hochladen
</a>
</div>
<div class="card-body {% if dms_dokumente %}p-0{% endif %}">
{% if dms_dokumente %}
<div class="list-group list-group-flush">
{% for dok in dms_dokumente %}
<a href="{% url 'stiftung:dms_detail' pk=dok.pk %}" class="list-group-item list-group-item-action">
<div class="d-flex justify-content-between align-items-center">
<div>
<div class="small fw-bold">
{% if dok.is_pdf %}<i class="fas fa-file-pdf text-danger me-1"></i>{% else %}<i class="fas fa-file text-primary me-1"></i>{% endif %}
{{ dok.titel|truncatechars:50 }}
</div>
<small class="text-muted">{{ dok.dateiname_original }} &middot; {{ dok.get_human_size }} &middot; {{ dok.erstellt_am|date:"d.m.Y" }}</small>
</div>
<span class="btn btn-sm btn-outline-success" title="Herunterladen" onclick="event.preventDefault(); window.location='{% url 'stiftung:dms_download' dok.pk %}';">
<i class="fas fa-download"></i>
</span>
</div>
</a>
{% endfor %}
</div>
{% else %}
<p class="text-muted small mb-0">Keine Dokumente verknüpft.</p>
{% endif %}
</div>
</div>
<!-- Verknüpfte E-Mails -->
{% if email_eingaenge %}
<div class="card shadow mb-4">
<div class="card-header bg-dark text-white py-2">
<span class="small fw-bold"><i class="fas fa-envelope me-2"></i>Verknüpfte E-Mails ({{ email_eingaenge.count }})</span>
</div>
<div class="card-body p-0">
<div class="list-group list-group-flush">
{% for email in email_eingaenge %}
<a href="{% url 'stiftung:email_eingang_detail' pk=email.pk %}" class="list-group-item list-group-item-action">
<div class="d-flex justify-content-between align-items-center">
<div>
<div class="small fw-bold">{{ email.betreff|truncatechars:60 }}</div>
<small class="text-muted">{{ email.absender_email }} &middot; {{ email.eingangsdatum|date:"d.m.Y H:i" }}</small>
</div>
<span class="badge bg-{{ email.get_status_color|default:'secondary' }}">{{ email.get_status_display }}</span>
</div>
</a>
{% endfor %}
</div>
</div>
</div>
{% endif %}
</div>
<!-- Sidebar -->
<div class="col-lg-4">
<!-- Status ändern -->
<div class="card shadow mb-3">
<div class="card-header bg-dark text-white py-2">
<span class="small fw-bold"><i class="fas fa-tasks me-2"></i>Status ändern</span>
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
<input type="hidden" name="action" value="set_status">
<div class="d-flex gap-2">
<select name="status" class="form-select form-select-sm">
{% for code, label in status_choices %}
<option value="{{ code }}" {% if vk.status == code %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-sm btn-primary">
<i class="fas fa-check"></i>
</button>
</div>
</form>
</div>
</div>
<!-- Audit Info -->
<div class="card shadow mb-3">
<div class="card-header bg-dark text-white py-2">
<span class="small fw-bold"><i class="fas fa-clock me-2"></i>Zeitstempel</span>
</div>
<div class="card-body">
<dl class="row mb-0 small">
<dt class="col-sm-5 text-muted">Erstellt</dt>
<dd class="col-sm-7">{{ vk.erstellt_am|date:"d.m.Y H:i" }}</dd>
<dt class="col-sm-5 text-muted">Aktualisiert</dt>
<dd class="col-sm-7">{{ vk.aktualisiert_am|date:"d.m.Y H:i" }}</dd>
</dl>
</div>
</div>
<!-- Aktionen -->
<div class="card shadow mb-3">
<div class="card-header bg-dark text-white py-2">
<span class="small fw-bold"><i class="fas fa-cog me-2"></i>Aktionen</span>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<a href="{% url 'stiftung:verwaltungskosten_edit' vk.pk %}" class="btn btn-outline-primary btn-sm">
<i class="fas fa-edit me-1"></i>Bearbeiten
</a>
<a href="{% url 'stiftung:dms_upload' %}?kontext=rechnung" class="btn btn-outline-success btn-sm">
<i class="fas fa-upload me-1"></i>Dokument hochladen
</a>
<a href="{% url 'stiftung:verwaltungskosten_delete' vk.pk %}" class="btn btn-outline-danger btn-sm">
<i class="fas fa-trash me-1"></i>Löschen
</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@@ -101,7 +101,7 @@
<tr> <tr>
<td>{{ kosten.datum|date:"d.m.Y" }}</td> <td>{{ kosten.datum|date:"d.m.Y" }}</td>
<td> <td>
<strong>{{ kosten.bezeichnung }}</strong> <a href="{% url 'stiftung:verwaltungskosten_detail' kosten.pk %}" class="text-decoration-none"><strong>{{ kosten.bezeichnung }}</strong></a>
{% if kosten.rechnungsnummer %} {% if kosten.rechnungsnummer %}
<br><small class="text-muted">RG: {{ kosten.rechnungsnummer }}</small> <br><small class="text-muted">RG: {{ kosten.rechnungsnummer }}</small>
{% endif %} {% endif %}