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

@@ -1104,34 +1104,79 @@ class VierteljahresNachweis(models.Model):
return None
class DestinataerEmailEingang(models.Model):
class EmailEingang(models.Model):
"""
Erfasst eingehende E-Mails von Destinatären.
Erfasst eingehende E-Mails (Destinataere, Rechnungen, Grundstuecke, Allgemein).
Wird automatisch durch den Celery-Task `poll_destinataer_emails` befüllt,
der das IMAP-Postfach der Stiftung (paperless@vhtv-stiftung.de) überwacht.
Anhänge werden automatisch in Paperless-NGX hochgeladen und als DokumentLink
mit dem jeweiligen Destinatär verknüpft.
Wird automatisch durch den Celery-Task `poll_emails` befuellt,
der das IMAP-Postfach der Stiftung (paperless@vhtv-stiftung.de) ueberwacht.
Anhaenge werden direkt als DokumentDatei im Django-DMS gespeichert.
"""
KATEGORIE_CHOICES = [
("destinataer", "Destinataer"),
("rechnung", "Rechnung"),
("land_pacht", "Grundstueck / Pacht"),
("stiftungsgeschichte", "Stiftungsgeschichte"),
("allgemein", "Allgemein"),
]
STATUS_CHOICES = [
("neu", "Neu / Unbearbeitet"),
("zugewiesen", "Destinatär zugewiesen"),
("zugewiesen", "Destinataer zugewiesen"),
("verarbeitet", "Verarbeitet"),
("rechnung_erfasst", "Rechnung erfasst"),
("zahlung_gebucht", "Zahlung gebucht"),
("unbekannt", "Unbekannter Absender"),
("fehler", "Fehler bei Verarbeitung"),
]
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
# Verknüpfung zum Destinatär (None = unbekannter Absender)
# Klassifizierung
kategorie = models.CharField(
max_length=20,
choices=KATEGORIE_CHOICES,
default="allgemein",
verbose_name="Kategorie",
)
# Verknuepfung zum Destinataer (None = kein Destinataer-Bezug)
destinataer = models.ForeignKey(
Destinataer,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="email_eingaenge",
verbose_name="Destinatär",
verbose_name="Destinataer",
)
# Verknuepfung zu Verwaltungskosten (Rechnungsworkflow)
verwaltungskosten = models.ForeignKey(
"Verwaltungskosten",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="email_eingaenge",
verbose_name="Verwaltungskosten / Rechnung",
)
# Verknuepfung zu Land / Verpachtung
land = models.ForeignKey(
"Land",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="email_eingaenge",
verbose_name="Laenderei",
)
verpachtung = models.ForeignKey(
"LandVerpachtung",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="email_eingaenge",
verbose_name="Verpachtung",
)
# E-Mail-Metadaten
@@ -1143,12 +1188,21 @@ class DestinataerEmailEingang(models.Model):
eingangsdatum = models.DateTimeField(verbose_name="Eingangsdatum")
email_text = models.TextField(blank=True, verbose_name="E-Mail-Text")
# Anhänge: Liste der Paperless-Dokument-IDs (JSON-Format)
# Anhaenge: DMS-Dokumente (Phase 3 DokumentDatei)
dokument_dateien = models.ManyToManyField(
"DokumentDatei",
blank=True,
related_name="email_eingaenge",
verbose_name="DMS-Dokumente (Anhaenge)",
help_text="Automatisch befuellte Anhaenge als Django-DMS-Dateien.",
)
# Anhaenge: Liste der Paperless-Dokument-IDs (JSON-Format, deprecated)
paperless_dokument_ids = models.JSONField(
default=list,
blank=True,
verbose_name="Paperless Dokument-IDs (Anhänge)",
help_text="Automatisch befüllte Liste der hochgeladenen Anhänge in Paperless-NGX",
verbose_name="Paperless Dokument-IDs (Anhaenge, veraltet)",
help_text="Veraltet wird nach vollstaendiger Migration entfernt. Neue Anhaenge in dokument_dateien.",
)
# Verarbeitungsstatus
@@ -1182,8 +1236,8 @@ class DestinataerEmailEingang(models.Model):
created_at = models.DateTimeField(auto_now_add=True, verbose_name="Erfasst am")
class Meta:
verbose_name = "E-Mail-Eingang (Destinatär)"
verbose_name_plural = "E-Mail-Eingänge (Destinatäre)"
verbose_name = "E-Mail-Eingang"
verbose_name_plural = "E-Mail-Eingaenge"
ordering = ["-eingangsdatum"]
def __str__(self):
@@ -1191,10 +1245,18 @@ class DestinataerEmailEingang(models.Model):
return f"[{self.eingangsdatum.strftime('%d.%m.%Y')}] {dest}: {self.betreff[:60]}"
def get_paperless_links(self):
"""Gibt Liste der Paperless-Dokument-URLs zurück."""
"""Gibt Liste der Paperless-Dokument-URLs zurueck (deprecated)."""
from django.conf import settings
base = settings.PAPERLESS_API_URL or ""
return [
f"{base}/documents/{doc_id}/"
for doc_id in (self.paperless_dokument_ids or [])
]
def get_dms_dokumente(self):
"""Gibt alle verknuepften DokumentDatei-Objekte zurueck."""
return self.dokument_dateien.all()
# Backward-compatible alias
DestinataerEmailEingang = EmailEingang