feat: Email-Eingangsverarbeitung für Destinatäre implementieren
Some checks failed
CI/CD Pipeline / test (push) Has been cancelled
CI/CD Pipeline / deploy (push) Has been cancelled
Code Quality / quality (push) Has been cancelled

Neues System zur automatischen Verarbeitung eingehender E-Mails von
Destinatären. IMAP-Polling alle 15 Minuten via Celery Beat, automatische
Zuordnung zu Destinatären anhand der E-Mail-Adresse, Upload von Anhängen
zu Paperless-NGX.

Umfasst:
- DestinataerEmailEingang Model mit Status-Tracking
- Celery Task für IMAP-Polling und Paperless-Integration
- Web-UI (Liste + Detail) mit Such- und Filterfunktion
- Admin-Interface mit Bulk-Actions
- Agent-Dokumentation (SysAdmin, RentmeisterAI)
- Dev-Environment Modernisierung (docker compose v2)

Reviewed by: SysAdmin (STI-15), RentmeisterAI (STI-16)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Stiftung CEO Agent
2026-03-09 21:11:22 +00:00
parent 6c8ddbb4f0
commit 4b21f553c3
16 changed files with 1554 additions and 49 deletions

View File

@@ -3183,3 +3183,99 @@ class StiftungsKalenderEintrag(models.Model):
return False
today = timezone.now().date()
return today <= self.datum <= (today + timezone.timedelta(days=days))
class DestinataerEmailEingang(models.Model):
"""
Erfasst eingehende E-Mails von Destinatären.
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.
"""
STATUS_CHOICES = [
("neu", "Neu / Unbearbeitet"),
("zugewiesen", "Destinatär zugewiesen"),
("verarbeitet", "Verarbeitet"),
("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)
destinataer = models.ForeignKey(
Destinataer,
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="email_eingaenge",
verbose_name="Destinatär",
)
# E-Mail-Metadaten
absender_email = models.EmailField(verbose_name="Absender-E-Mail")
absender_name = models.CharField(
max_length=255, blank=True, verbose_name="Absender-Name"
)
betreff = models.CharField(max_length=500, blank=True, verbose_name="Betreff")
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)
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",
)
# Verarbeitungsstatus
status = models.CharField(
max_length=20,
choices=STATUS_CHOICES,
default="neu",
verbose_name="Status",
)
fehler_details = models.TextField(
blank=True,
verbose_name="Fehlerdetails",
help_text="Technische Fehlermeldung bei Verarbeitungsfehlern",
)
notizen = models.TextField(
blank=True,
verbose_name="Interne Notizen",
help_text="Manuelle Notizen der Verwaltung zur E-Mail",
)
# Verweis auf VierteljahresNachweis, falls E-Mail einem Quartal zugeordnet
quartalsnachweis = models.ForeignKey(
"VierteljahresNachweis",
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name="email_eingaenge",
verbose_name="Quartalsnachweis (zugeordnet)",
)
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)"
ordering = ["-eingangsdatum"]
def __str__(self):
dest = str(self.destinataer) if self.destinataer else self.absender_email
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."""
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 [])
]