feat: Email-Eingangsverarbeitung für Destinatäre implementieren
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:
@@ -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 [])
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user