Generalize email system with invoice workflow and Stiftungsgeschichte category

- Rename DestinataerEmailEingang → EmailEingang with category support
  (destinataer, rechnung, land_pacht, stiftungsgeschichte, allgemein)
- Add invoice capture workflow: create Verwaltungskosten from email,
  link DMS documents as invoice attachments, track payment status
- Add Stiftungsgeschichte email category with auto-detection patterns
  (Ahnenforschung, Genealogie, Chronik, etc.) and DMS integration
- Update poll_emails task with category detection and DMS context mapping
- Show available history documents in Geschichte editor sidebar
- Consolidate DMS views, remove legacy dokument templates
- Update all detail/form templates for DMS document linking
- Add deploy.sh script and streamline compose.yml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
SysAdmin Agent
2026-03-12 10:17:14 +00:00
parent f4fc512ad3
commit e6f4c5ba1b
44 changed files with 1076 additions and 3428 deletions

View File

@@ -1,7 +1,7 @@
{% extends 'base.html' %}
{% load humanize %}
{% block title %}E-Mail-Eingang Detail - van Hees-Theyssen-Vogel'sche Stiftung{% endblock %}
{% block title %}E-Mail-Eingang Detail - Stiftungsverwaltung{% endblock %}
{% block content %}
<div class="row">
@@ -11,22 +11,31 @@
<i class="fas fa-envelope me-2"></i>E-Mail-Eingang
</h1>
<a href="{% url 'stiftung:email_eingang_list' %}" class="btn btn-outline-secondary btn-sm">
<i class="fas fa-arrow-left me-1"></i>Zurück zur Übersicht
<i class="fas fa-arrow-left me-1"></i>Zurueck zur Uebersicht
</a>
</div>
</div>
</div>
<div class="row">
<!-- Linke Spalte: E-Mail-Details -->
{# Linke Spalte: E-Mail-Details #}
<div class="col-lg-8">
<div class="card mb-4">
<div class="card-header d-flex justify-content-between align-items-center">
<span><i class="fas fa-envelope-open me-2"></i>E-Mail-Details</span>
<span>
{# Kategorie-Badge #}
{% if eingang.kategorie == "rechnung" %}<span class="badge bg-warning text-dark me-1"><i class="fas fa-file-invoice me-1"></i>Rechnung</span>
{% elif eingang.kategorie == "destinataer" %}<span class="badge bg-info me-1"><i class="fas fa-user me-1"></i>Destinataer</span>
{% elif eingang.kategorie == "land_pacht" %}<span class="badge bg-success me-1"><i class="fas fa-map me-1"></i>Land/Pacht</span>
{% elif eingang.kategorie == "stiftungsgeschichte" %}<span class="badge bg-dark me-1"><i class="fas fa-landmark me-1"></i>Geschichte</span>
{% endif %}
{# Status-Badge #}
{% if eingang.status == "neu" %}<span class="badge bg-warning text-dark">Neu</span>
{% elif eingang.status == "zugewiesen" %}<span class="badge bg-primary">Zugewiesen</span>
{% elif eingang.status == "verarbeitet" %}<span class="badge bg-success">Verarbeitet</span>
{% elif eingang.status == "rechnung_erfasst" %}<span class="badge bg-info">Rechnung erfasst</span>
{% elif eingang.status == "zahlung_gebucht" %}<span class="badge bg-success">Zahlung gebucht</span>
{% elif eingang.status == "unbekannt" %}<span class="badge bg-danger">Unbekannter Absender</span>
{% elif eingang.status == "fehler" %}<span class="badge bg-secondary">Fehler</span>
{% endif %}
@@ -47,17 +56,27 @@
<dt class="col-sm-3">Betreff</dt>
<dd class="col-sm-9">{{ eingang.betreff|default:"(kein Betreff)" }}</dd>
<dt class="col-sm-3">Destinatär</dt>
<dt class="col-sm-3">Destinataer</dt>
<dd class="col-sm-9">
{% if eingang.destinataer %}
<a href="{% url 'stiftung:destinataer_detail' eingang.destinataer.pk %}">
{{ eingang.destinataer }}
</a>
{% else %}
<span class="text-danger"><i class="fas fa-exclamation-circle me-1"></i>Nicht zugeordnet</span>
<span class="text-muted">Nicht zugeordnet</span>
{% endif %}
</dd>
{% if eingang.verwaltungskosten %}
<dt class="col-sm-3">Rechnung</dt>
<dd class="col-sm-9">
<a href="{% url 'stiftung:verwaltungskosten_edit' eingang.verwaltungskosten.pk %}">
{{ eingang.verwaltungskosten.bezeichnung }} ({{ eingang.verwaltungskosten.betrag }} EUR)
</a>
<span class="badge bg-{{ eingang.verwaltungskosten.get_status_color }}">{{ eingang.verwaltungskosten.get_status_display }}</span>
</dd>
{% endif %}
{% if eingang.quartalsnachweis %}
<dt class="col-sm-3">Quartalsnachweis</dt>
<dd class="col-sm-9">
@@ -82,32 +101,34 @@
</div>
</div>
<!-- Anhänge / Paperless-Dokumente -->
{% if dokument_links %}
{# Anhaenge / DMS-Dokumente #}
{% if dms_dokumente %}
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-paperclip me-2"></i>Anhänge in Paperless-NGX
<i class="fas fa-paperclip me-2"></i>Anhaenge ({{ dms_dokumente|length }})
</div>
<div class="card-body p-0">
<table class="table mb-0">
<thead class="table-light">
<tr>
<th>Titel</th>
<th>Kontext</th>
<th>Paperless-ID</th>
<th>Dateiname</th>
<th>Typ</th>
<th>Groesse</th>
<th></th>
</tr>
</thead>
<tbody>
{% for link in dokument_links %}
{% for dok in dms_dokumente %}
<tr>
<td>{{ link.titel }}</td>
<td>{{ link.get_kontext_display }}</td>
<td><code>{{ link.paperless_document_id }}</code></td>
<td>{{ dok.dateiname_original|default:dok.titel }}</td>
<td><span class="text-muted small">{{ dok.dateityp|default:"" }}</span></td>
<td><span class="text-muted small">{{ dok.get_human_size }}</span></td>
<td>
<a href="{{ link.get_paperless_url }}" target="_blank" class="btn btn-sm btn-outline-info">
<i class="fas fa-external-link-alt me-1"></i>Öffnen
{% if dok.datei %}
<a href="{% url 'stiftung:dms_download' dok.pk %}" class="btn btn-sm btn-outline-primary">
<i class="fas fa-download me-1"></i>Herunterladen
</a>
{% endif %}
</td>
</tr>
{% endfor %}
@@ -115,43 +136,109 @@
</table>
</div>
</div>
{% elif eingang.paperless_dokument_ids %}
<div class="alert alert-info">
<i class="fas fa-info-circle me-1"></i>
{{ eingang.paperless_dokument_ids|length }} Anhang/-hänge in Paperless hochgeladen
(IDs: {{ eingang.paperless_dokument_ids|join:", " }}), aber noch kein DokumentLink erstellt.
</div>
{% else %}
<div class="card mb-4">
<div class="card-body text-muted text-center py-3">
<i class="fas fa-paperclip me-1"></i>Keine Anhänge in dieser E-Mail.
<i class="fas fa-paperclip me-1"></i>Keine Anhaenge in dieser E-Mail.
</div>
</div>
{% endif %}
</div>
<!-- Rechte Spalte: Aktionen -->
{# Rechte Spalte: Aktionen #}
<div class="col-lg-4">
<!-- Manuelle Destinatär-Zuordnung -->
{% if not eingang.destinataer or eingang.status == "unbekannt" %}
{# Kategorie aendern #}
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-tag me-2"></i>Kategorie
</div>
<div class="card-body">
<form method="post">
{% csrf_token %}
<input type="hidden" name="action" value="set_kategorie">
<div class="mb-2">
<select class="form-select form-select-sm" name="kategorie">
<option value="allgemein" {% if eingang.kategorie == "allgemein" %}selected{% endif %}>Allgemein</option>
<option value="destinataer" {% if eingang.kategorie == "destinataer" %}selected{% endif %}>Destinataer</option>
<option value="rechnung" {% if eingang.kategorie == "rechnung" %}selected{% endif %}>Rechnung</option>
<option value="land_pacht" {% if eingang.kategorie == "land_pacht" %}selected{% endif %}>Grundstueck / Pacht</option>
<option value="stiftungsgeschichte" {% if eingang.kategorie == "stiftungsgeschichte" %}selected{% endif %}>Stiftungsgeschichte</option>
</select>
</div>
<button type="submit" class="btn btn-outline-primary btn-sm w-100">
<i class="fas fa-save me-1"></i>Kategorie setzen
</button>
</form>
</div>
</div>
{# Rechnung erfassen (nur wenn noch keine zugeordnet) #}
{% if not eingang.verwaltungskosten and eingang.status != "zahlung_gebucht" %}
<div class="card mb-4 border-warning">
<div class="card-header bg-warning text-dark">
<i class="fas fa-user-plus me-2"></i>Destinatär manuell zuordnen
<i class="fas fa-file-invoice-dollar me-2"></i>Als Rechnung erfassen
</div>
<div class="card-body">
<p class="small text-muted">
Die E-Mail-Adresse <strong>{{ eingang.absender_email }}</strong>
konnte keinem Destinatär automatisch zugeordnet werden.
Bitte wählen Sie den passenden Destinatär aus.
Erstellt einen Verwaltungskosten-Eintrag und verknuepft die Anhaenge als Rechnungsdokumente.
</p>
<form method="post">
{% csrf_token %}
<input type="hidden" name="action" value="erfasse_rechnung">
<div class="mb-2">
<label class="form-label small">Bezeichnung</label>
<input type="text" class="form-control form-control-sm" name="bezeichnung"
value="{{ eingang.betreff }}" required>
</div>
<div class="mb-2">
<label class="form-label small">Betrag (EUR)</label>
<input type="number" step="0.01" class="form-control form-control-sm" name="betrag"
placeholder="0.00" required>
</div>
<div class="mb-2">
<label class="form-label small">Lieferant / Firma</label>
<input type="text" class="form-control form-control-sm" name="lieferant"
value="{{ eingang.absender_name|default:eingang.absender_email }}">
</div>
<div class="mb-2">
<label class="form-label small">Rechnungsnummer</label>
<input type="text" class="form-control form-control-sm" name="rechnungsnummer"
placeholder="z.B. RE-2026001">
</div>
<div class="mb-2">
<label class="form-label small">Kategorie</label>
<select class="form-select form-select-sm" name="vk_kategorie">
{% for key, label in vk_kategorie_choices %}
<option value="{{ key }}" {% if key == "rechnung_intern" %}selected{% endif %}>{{ label }}</option>
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-warning w-100">
<i class="fas fa-file-invoice me-1"></i>Rechnung erfassen
</button>
</form>
</div>
</div>
{% endif %}
{# Manuelle Destinataer-Zuordnung #}
{% if not eingang.destinataer or eingang.status == "unbekannt" %}
<div class="card mb-4 border-info">
<div class="card-header bg-info text-white">
<i class="fas fa-user-plus me-2"></i>Destinataer zuordnen
</div>
<div class="card-body">
<p class="small text-muted">
Absender <strong>{{ eingang.absender_email }}</strong>
konnte nicht automatisch zugeordnet werden.
</p>
<form method="post">
{% csrf_token %}
<input type="hidden" name="action" value="assign_destinataer">
<div class="mb-3">
<label class="form-label">Destinatär</label>
<select class="form-select" name="destinataer_id" required>
<option value=""> Bitte wählen </option>
<select class="form-select form-select-sm" name="destinataer_id" required>
<option value=""> Bitte waehlen </option>
{% for d in alle_destinataere %}
<option value="{{ d.pk }}">{{ d.nachname }}, {{ d.vorname }}
{% if d.email %} ({{ d.email }}){% endif %}
@@ -159,16 +246,16 @@
{% endfor %}
</select>
</div>
<button type="submit" class="btn btn-warning w-100">
<i class="fas fa-link me-1"></i>Zuordnen & Speichern
<button type="submit" class="btn btn-info w-100">
<i class="fas fa-link me-1"></i>Zuordnen
</button>
</form>
</div>
</div>
{% endif %}
<!-- Als verarbeitet markieren -->
{% if eingang.status != "verarbeitet" %}
{# Als verarbeitet markieren #}
{% if eingang.status != "verarbeitet" and eingang.status != "zahlung_gebucht" %}
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-check-circle me-2"></i>Als verarbeitet markieren
@@ -178,9 +265,8 @@
{% csrf_token %}
<input type="hidden" name="action" value="mark_verarbeitet">
<div class="mb-3">
<label class="form-label">Interne Notiz (optional)</label>
<textarea class="form-control" name="notizen" rows="3"
placeholder="Z. B. 'Studiennachweis für WS 2025/26 eingegangen und geprüft.'">{{ eingang.notizen }}</textarea>
<textarea class="form-control form-control-sm" name="notizen" rows="3"
placeholder="Optionale Notiz...">{{ eingang.notizen }}</textarea>
</div>
<button type="submit" class="btn btn-success w-100">
<i class="fas fa-check me-1"></i>Verarbeitet
@@ -190,7 +276,7 @@
</div>
{% endif %}
<!-- Notizen bearbeiten -->
{# Notizen #}
<div class="card mb-4">
<div class="card-header">
<i class="fas fa-sticky-note me-2"></i>Interne Notizen
@@ -200,41 +286,42 @@
{% csrf_token %}
<input type="hidden" name="action" value="save_notizen">
<div class="mb-3">
<textarea class="form-control" name="notizen" rows="5"
placeholder="Interne Notizen zur E-Mail...">{{ eingang.notizen }}</textarea>
<textarea class="form-control form-control-sm" name="notizen" rows="4"
placeholder="Interne Notizen...">{{ eingang.notizen }}</textarea>
</div>
<button type="submit" class="btn btn-outline-secondary w-100">
<button type="submit" class="btn btn-outline-secondary btn-sm w-100">
<i class="fas fa-save me-1"></i>Notizen speichern
</button>
</form>
</div>
</div>
<!-- Metadaten -->
{# Metadaten #}
<div class="card mb-4">
<div class="card-header"><i class="fas fa-info-circle me-2"></i>Metadaten</div>
<div class="card-body">
<dl class="row mb-0 small">
<dt class="col-6">Erfasst am</dt>
<dd class="col-6">{{ eingang.created_at|date:"d.m.Y H:i" }}</dd>
<dt class="col-6">Kategorie</dt>
<dd class="col-6">{{ eingang.get_kategorie_display }}</dd>
<dt class="col-6">Datensatz-ID</dt>
<dd class="col-6 text-muted"><code>{{ eingang.pk|stringformat:"s"|slice:":8" }}</code></dd>
<dd class="col-6 text-muted"><code>{{ eingang.pk|stringformat:"s"|slice:":8" }}...</code></dd>
</dl>
</div>
</div>
<!-- Löschen -->
{# Loeschen #}
<div class="card border-danger">
<div class="card-header text-danger">
<i class="fas fa-trash-alt me-2"></i>E-Mail löschen
<i class="fas fa-trash-alt me-2"></i>E-Mail loeschen
</div>
<div class="card-body">
<p class="small text-muted mb-2">Diese E-Mail unwiderruflich aus dem System entfernen.</p>
<form method="post" action="{% url 'stiftung:email_eingang_delete' eingang.pk %}"
onsubmit="return confirm('E-Mail wirklich löschen?');">
onsubmit="return confirm('E-Mail wirklich loeschen?');">
{% csrf_token %}
<button type="submit" class="btn btn-outline-danger w-100">
<i class="fas fa-trash-alt me-1"></i>Löschen
<button type="submit" class="btn btn-outline-danger btn-sm w-100">
<i class="fas fa-trash-alt me-1"></i>Loeschen
</button>
</form>
</div>