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:
@@ -23,25 +23,6 @@ from .destinataere import ( # noqa: F401
|
||||
destinataer_export,
|
||||
)
|
||||
|
||||
from .dokumente import ( # noqa: F401
|
||||
dokument_management,
|
||||
paperless_document_redirect,
|
||||
dokument_list,
|
||||
dokument_detail,
|
||||
dokument_create,
|
||||
dokument_update,
|
||||
dokument_delete,
|
||||
paperless_ping,
|
||||
paperless_documents,
|
||||
paperless_debug,
|
||||
paperless_tags_only,
|
||||
link_document_search,
|
||||
create_paechter_link_for_verpachtung,
|
||||
link_document_create,
|
||||
link_document_list,
|
||||
link_document_update,
|
||||
link_document_delete,
|
||||
)
|
||||
|
||||
from .finanzen import ( # noqa: F401
|
||||
bericht_list,
|
||||
|
||||
@@ -35,7 +35,7 @@ from stiftung.models import (AppConfiguration, AuditLog, BackupJob, BankTransact
|
||||
BriefVorlage, CSVImport, Destinataer,
|
||||
DestinataerEmailEingang, DestinataerNotiz,
|
||||
DestinataerUnterstuetzung,
|
||||
DokumentLink, Foerderung, GeschichteBild, GeschichteSeite,
|
||||
DokumentDatei, DokumentLink, Foerderung, GeschichteBild, GeschichteSeite,
|
||||
Land, LandAbrechnung, LandVerpachtung, Paechter, Person,
|
||||
Rentmeister, StiftungsKalenderEintrag, StiftungsKonto,
|
||||
UnterstuetzungWiederkehrend, Veranstaltung,
|
||||
@@ -268,8 +268,8 @@ def destinataer_detail(request, pk):
|
||||
destinataer = get_object_or_404(Destinataer, pk=pk)
|
||||
|
||||
# Alle mit diesem Destinatär verknüpften Dokumente laden
|
||||
verknuepfte_dokumente = DokumentLink.objects.filter(
|
||||
destinataer_id=destinataer.pk
|
||||
verknuepfte_dokumente = DokumentDatei.objects.filter(
|
||||
destinataer=destinataer
|
||||
).order_by("kontext", "titel")
|
||||
|
||||
# Förderungen für diesen Destinatär laden
|
||||
|
||||
@@ -115,6 +115,7 @@ def dms_upload(request):
|
||||
"land_id": request.GET.get("land", ""),
|
||||
"paechter_id": request.GET.get("paechter", ""),
|
||||
"verpachtung_id": request.GET.get("verpachtung", ""),
|
||||
"foerderung_id": request.GET.get("foerderung", ""),
|
||||
"kontext": request.GET.get("kontext", "anderes"),
|
||||
}
|
||||
|
||||
@@ -144,6 +145,7 @@ def dms_upload(request):
|
||||
land_id = request.POST.get("land_id", "").strip()
|
||||
paechter_id = request.POST.get("paechter_id", "").strip()
|
||||
verp_id = request.POST.get("verpachtung_id", "").strip()
|
||||
foerd_id = request.POST.get("foerderung_id", "").strip()
|
||||
|
||||
if dest_id:
|
||||
try:
|
||||
@@ -165,6 +167,11 @@ def dms_upload(request):
|
||||
dok.verpachtung_id = verp_id
|
||||
except Exception:
|
||||
pass
|
||||
if foerd_id:
|
||||
try:
|
||||
dok.foerderung_id = foerd_id
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
_save_upload(request, dok)
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -35,7 +35,7 @@ from stiftung.models import (AppConfiguration, AuditLog, BackupJob, BankTransact
|
||||
BriefVorlage, CSVImport, Destinataer,
|
||||
DestinataerEmailEingang, DestinataerNotiz,
|
||||
DestinataerUnterstuetzung,
|
||||
DokumentLink, Foerderung, GeschichteBild, GeschichteSeite,
|
||||
DokumentDatei, DokumentLink, Foerderung, GeschichteBild, GeschichteSeite,
|
||||
Land, LandAbrechnung, LandVerpachtung, Paechter, Person,
|
||||
Rentmeister, StiftungsKalenderEintrag, StiftungsKonto,
|
||||
UnterstuetzungWiederkehrend, Veranstaltung,
|
||||
@@ -138,8 +138,8 @@ def foerderung_detail(request, pk):
|
||||
)
|
||||
|
||||
# Alle mit dieser Förderung verknüpften Dokumente laden
|
||||
verknuepfte_dokumente = DokumentLink.objects.filter(
|
||||
foerderung_id=foerderung.pk
|
||||
verknuepfte_dokumente = DokumentDatei.objects.filter(
|
||||
foerderung=foerderung
|
||||
).order_by("kontext", "titel")
|
||||
|
||||
context = {
|
||||
|
||||
@@ -33,7 +33,7 @@ from rest_framework.response import Response
|
||||
|
||||
from stiftung.models import (AppConfiguration, AuditLog, BackupJob, BankTransaction,
|
||||
BriefVorlage, CSVImport, Destinataer,
|
||||
DestinataerEmailEingang, DestinataerNotiz,
|
||||
DestinataerEmailEingang, EmailEingang, DestinataerNotiz,
|
||||
DestinataerUnterstuetzung,
|
||||
DokumentLink, Foerderung, GeschichteBild, GeschichteSeite,
|
||||
Land, LandAbrechnung, LandVerpachtung, Paechter, Person,
|
||||
@@ -105,11 +105,18 @@ def geschichte_create(request):
|
||||
else:
|
||||
form = GeschichteSeiteForm()
|
||||
|
||||
# Verfuegbare Stiftungsgeschichte-Dokumente aus DMS
|
||||
from stiftung.models import DokumentDatei
|
||||
geschichte_dokumente = DokumentDatei.objects.filter(
|
||||
kontext="stiftungsgeschichte"
|
||||
).order_by("-erstellt_am")[:20]
|
||||
|
||||
context = {
|
||||
'form': form,
|
||||
'title': 'Neue Geschichtsseite'
|
||||
'title': 'Neue Geschichtsseite',
|
||||
'geschichte_dokumente': geschichte_dokumente,
|
||||
}
|
||||
|
||||
|
||||
return render(request, 'stiftung/geschichte/form.html', context)
|
||||
|
||||
|
||||
@@ -134,12 +141,19 @@ def geschichte_edit(request, slug):
|
||||
else:
|
||||
form = GeschichteSeiteForm(instance=seite)
|
||||
|
||||
# Verfuegbare Stiftungsgeschichte-Dokumente aus DMS
|
||||
from stiftung.models import DokumentDatei
|
||||
geschichte_dokumente = DokumentDatei.objects.filter(
|
||||
kontext="stiftungsgeschichte"
|
||||
).order_by("-erstellt_am")[:20]
|
||||
|
||||
context = {
|
||||
'form': form,
|
||||
'seite': seite,
|
||||
'title': f'Bearbeiten: {seite.titel}'
|
||||
'title': f'Bearbeiten: {seite.titel}',
|
||||
'geschichte_dokumente': geschichte_dokumente,
|
||||
}
|
||||
|
||||
|
||||
return render(request, 'stiftung/geschichte/form.html', context)
|
||||
|
||||
|
||||
@@ -583,16 +597,19 @@ def kalender_view(request):
|
||||
@login_required
|
||||
def email_eingang_list(request):
|
||||
"""
|
||||
Übersicht aller eingegangenen E-Mails von Destinatären.
|
||||
Zeigt ungeklärte Absender zuerst, dann chronologisch absteigend.
|
||||
Uebersicht aller eingegangenen E-Mails.
|
||||
Filtert nach Status und Kategorie, zeigt ungeklaerte Absender zuerst.
|
||||
"""
|
||||
status_filter = request.GET.get("status", "")
|
||||
kategorie_filter = request.GET.get("kategorie", "")
|
||||
search = request.GET.get("q", "").strip()
|
||||
|
||||
qs = DestinataerEmailEingang.objects.select_related("destinataer", "quartalsnachweis")
|
||||
qs = EmailEingang.objects.select_related("destinataer", "quartalsnachweis", "verwaltungskosten")
|
||||
|
||||
if status_filter:
|
||||
qs = qs.filter(status=status_filter)
|
||||
if kategorie_filter:
|
||||
qs = qs.filter(kategorie=kategorie_filter)
|
||||
if search:
|
||||
qs = qs.filter(
|
||||
Q(absender_email__icontains=search)
|
||||
@@ -604,7 +621,7 @@ def email_eingang_list(request):
|
||||
|
||||
# Unbekannte Absender zuerst, dann nach Datum absteigend
|
||||
qs = qs.order_by(
|
||||
"status", # "unbekannt" kommt alphabetisch vor "verarbeitet" / "zugewiesen"
|
||||
"status",
|
||||
"-eingangsdatum",
|
||||
)
|
||||
|
||||
@@ -612,16 +629,19 @@ def email_eingang_list(request):
|
||||
page_obj = paginator.get_page(request.GET.get("page"))
|
||||
|
||||
context = {
|
||||
"title": "E-Mail-Eingang (Destinatäre)",
|
||||
"title": "E-Mail-Eingang",
|
||||
"page_obj": page_obj,
|
||||
"status_filter": status_filter,
|
||||
"kategorie_filter": kategorie_filter,
|
||||
"search": search,
|
||||
"status_choices": DestinataerEmailEingang.STATUS_CHOICES,
|
||||
"status_choices": EmailEingang.STATUS_CHOICES,
|
||||
"kategorie_choices": EmailEingang.KATEGORIE_CHOICES,
|
||||
"counts": {
|
||||
"gesamt": DestinataerEmailEingang.objects.count(),
|
||||
"neu": DestinataerEmailEingang.objects.filter(status="neu").count(),
|
||||
"unbekannt": DestinataerEmailEingang.objects.filter(status="unbekannt").count(),
|
||||
"fehler": DestinataerEmailEingang.objects.filter(status="fehler").count(),
|
||||
"gesamt": EmailEingang.objects.count(),
|
||||
"neu": EmailEingang.objects.filter(status="neu").count(),
|
||||
"unbekannt": EmailEingang.objects.filter(status="unbekannt").count(),
|
||||
"rechnung": EmailEingang.objects.filter(kategorie="rechnung").count(),
|
||||
"fehler": EmailEingang.objects.filter(status="fehler").count(),
|
||||
},
|
||||
}
|
||||
return render(request, "stiftung/email_eingang/list.html", context)
|
||||
@@ -629,8 +649,8 @@ def email_eingang_list(request):
|
||||
|
||||
@login_required
|
||||
def email_eingang_detail(request, pk):
|
||||
"""Detailansicht einer eingegangenen E-Mail mit Möglichkeit zur manuellen Zuordnung."""
|
||||
eingang = get_object_or_404(DestinataerEmailEingang, pk=pk)
|
||||
"""Detailansicht einer eingegangenen E-Mail mit Zuordnung und Rechnungserfassung."""
|
||||
eingang = get_object_or_404(EmailEingang, pk=pk)
|
||||
|
||||
if request.method == "POST":
|
||||
action = request.POST.get("action")
|
||||
@@ -641,19 +661,62 @@ def email_eingang_detail(request, pk):
|
||||
try:
|
||||
destinataer = Destinataer.objects.get(pk=dest_id)
|
||||
eingang.destinataer = destinataer
|
||||
eingang.kategorie = "destinataer"
|
||||
eingang.status = "zugewiesen"
|
||||
eingang.save()
|
||||
# Verknüpfte DokumentLinks ebenfalls dem Destinatär zuordnen
|
||||
if eingang.paperless_dokument_ids:
|
||||
DokumentLink.objects.filter(
|
||||
paperless_document_id__in=eingang.paperless_dokument_ids
|
||||
).update(destinataer_id=destinataer.pk)
|
||||
messages.success(
|
||||
request,
|
||||
f"E-Mail wurde {destinataer} zugeordnet.",
|
||||
eingang.dokument_dateien.filter(destinataer__isnull=True).update(
|
||||
destinataer=destinataer
|
||||
)
|
||||
messages.success(request, f"E-Mail wurde {destinataer} zugeordnet.")
|
||||
except Destinataer.DoesNotExist:
|
||||
messages.error(request, "Destinatär nicht gefunden.")
|
||||
messages.error(request, "Destinataer nicht gefunden.")
|
||||
return redirect("stiftung:email_eingang_detail", pk=pk)
|
||||
|
||||
elif action == "erfasse_rechnung":
|
||||
# Erstelle Verwaltungskosten-Eintrag aus Email
|
||||
bezeichnung = request.POST.get("bezeichnung", eingang.betreff[:200]).strip()
|
||||
betrag = request.POST.get("betrag", "0").strip().replace(",", ".")
|
||||
kategorie = request.POST.get("vk_kategorie", "rechnung_intern")
|
||||
lieferant = request.POST.get("lieferant", eingang.absender_name or eingang.absender_email).strip()
|
||||
rechnungsnummer = request.POST.get("rechnungsnummer", "").strip()
|
||||
|
||||
try:
|
||||
from decimal import Decimal
|
||||
vk = Verwaltungskosten(
|
||||
bezeichnung=bezeichnung[:200],
|
||||
kategorie=kategorie,
|
||||
betrag=Decimal(betrag) if betrag else Decimal("0"),
|
||||
datum=eingang.eingangsdatum.date(),
|
||||
lieferant_firma=lieferant[:200],
|
||||
rechnungsnummer=rechnungsnummer[:100],
|
||||
status="erhalten",
|
||||
beschreibung=f"Automatisch erfasst aus E-Mail-Eingang.\nBetreff: {eingang.betreff}\nAbsender: {eingang.absender_email}",
|
||||
)
|
||||
vk.save()
|
||||
|
||||
# Verknuepfe Email mit Verwaltungskosten
|
||||
eingang.verwaltungskosten = vk
|
||||
eingang.kategorie = "rechnung"
|
||||
eingang.status = "rechnung_erfasst"
|
||||
eingang.save()
|
||||
|
||||
# Verknuepfe angehaengte Dokumente mit Verwaltungskosten
|
||||
for dok in eingang.dokument_dateien.all():
|
||||
dok.verwaltungskosten = vk
|
||||
dok.kontext = "rechnung"
|
||||
dok.save()
|
||||
|
||||
messages.success(request, f'Rechnung "{bezeichnung}" erfasst (€{betrag}).')
|
||||
except Exception as exc:
|
||||
messages.error(request, f"Fehler beim Erfassen der Rechnung: {exc}")
|
||||
return redirect("stiftung:email_eingang_detail", pk=pk)
|
||||
|
||||
elif action == "set_kategorie":
|
||||
new_kategorie = request.POST.get("kategorie", "")
|
||||
if new_kategorie in dict(EmailEingang.KATEGORIE_CHOICES):
|
||||
eingang.kategorie = new_kategorie
|
||||
eingang.save()
|
||||
messages.success(request, f"Kategorie auf '{dict(EmailEingang.KATEGORIE_CHOICES)[new_kategorie]}' gesetzt.")
|
||||
return redirect("stiftung:email_eingang_detail", pk=pk)
|
||||
|
||||
elif action == "mark_verarbeitet":
|
||||
@@ -669,38 +732,29 @@ def email_eingang_detail(request, pk):
|
||||
messages.success(request, "Notizen gespeichert.")
|
||||
return redirect("stiftung:email_eingang_detail", pk=pk)
|
||||
|
||||
# Paperless-Links zusammenstellen
|
||||
paperless_links = eingang.get_paperless_links()
|
||||
# DMS-Dokumente
|
||||
dms_dokumente = eingang.dokument_dateien.all().order_by("erstellt_am")
|
||||
|
||||
# DokumentLinks für diese E-Mail (über paperless_dokument_ids)
|
||||
dokument_links = []
|
||||
if eingang.paperless_dokument_ids:
|
||||
dokument_links = DokumentLink.objects.filter(
|
||||
paperless_document_id__in=eingang.paperless_dokument_ids
|
||||
)
|
||||
|
||||
# Alle aktiven Destinatäre für manuelle Zuordnung
|
||||
# Alle aktiven Destinataere fuer manuelle Zuordnung
|
||||
alle_destinataere = Destinataer.objects.filter(aktiv=True).order_by("nachname", "vorname")
|
||||
|
||||
context = {
|
||||
"title": f"E-Mail-Eingang: {eingang}",
|
||||
"eingang": eingang,
|
||||
"paperless_links": paperless_links,
|
||||
"dokument_links": dokument_links,
|
||||
"dms_dokumente": dms_dokumente,
|
||||
"alle_destinataere": alle_destinataere,
|
||||
"vk_kategorie_choices": Verwaltungskosten.KATEGORIE_CHOICES,
|
||||
}
|
||||
return render(request, "stiftung/email_eingang/detail.html", context)
|
||||
|
||||
|
||||
@login_required
|
||||
def email_eingang_poll_trigger(request):
|
||||
"""Löst den IMAP-Poll manuell aus – sucht alle E-Mails der letzten 30 Tage."""
|
||||
"""Loest den IMAP-Poll manuell aus – sucht alle E-Mails der letzten 30 Tage."""
|
||||
if request.method == "POST":
|
||||
from stiftung.tasks import poll_destinataer_emails
|
||||
from stiftung.tasks import poll_emails
|
||||
try:
|
||||
# Synchron ausführen für sofortiges Feedback; sucht auch bereits
|
||||
# gelesene E-Mails der letzten 30 Tage (Duplikate werden übersprungen).
|
||||
result = poll_destinataer_emails.apply(kwargs={"search_all_recent_days": 30}).get(timeout=300)
|
||||
result = poll_emails.apply(kwargs={"search_all_recent_days": 30}).get(timeout=300)
|
||||
processed = result.get("processed", 0) if isinstance(result, dict) else 0
|
||||
if result and result.get("status") == "skipped":
|
||||
messages.warning(request, "IMAP ist nicht konfiguriert. Bitte Einstellungen unter Administration → E-Mail / IMAP prüfen.")
|
||||
@@ -723,8 +777,8 @@ def email_eingang_poll_trigger(request):
|
||||
|
||||
@login_required
|
||||
def email_eingang_delete(request, pk):
|
||||
"""Löscht eine eingegangene E-Mail."""
|
||||
eingang = get_object_or_404(DestinataerEmailEingang, pk=pk)
|
||||
"""Loescht eine eingegangene E-Mail."""
|
||||
eingang = get_object_or_404(EmailEingang, pk=pk)
|
||||
if request.method == "POST":
|
||||
betreff = eingang.betreff or "(kein Betreff)"
|
||||
eingang.delete()
|
||||
|
||||
@@ -11,8 +11,6 @@ from decimal import Decimal
|
||||
|
||||
import qrcode
|
||||
import qrcode.image.svg
|
||||
import requests
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.paginator import Paginator
|
||||
@@ -35,7 +33,7 @@ from stiftung.models import (AppConfiguration, AuditLog, BackupJob, BankTransact
|
||||
BriefVorlage, CSVImport, Destinataer,
|
||||
DestinataerEmailEingang, DestinataerNotiz,
|
||||
DestinataerUnterstuetzung,
|
||||
DokumentLink, Foerderung, GeschichteBild, GeschichteSeite,
|
||||
DokumentDatei, DokumentLink, Foerderung, GeschichteBild, GeschichteSeite,
|
||||
Land, LandAbrechnung, LandVerpachtung, Paechter, Person,
|
||||
Rentmeister, StiftungsKalenderEintrag, StiftungsKonto,
|
||||
UnterstuetzungWiederkehrend, Veranstaltung,
|
||||
@@ -143,8 +141,8 @@ def paechter_detail(request, pk):
|
||||
paechter = get_object_or_404(Paechter, pk=pk)
|
||||
|
||||
# Alle mit diesem Pächter verknüpften Dokumente laden
|
||||
verknuepfte_dokumente = DokumentLink.objects.filter(
|
||||
paechter_id=paechter.pk
|
||||
verknuepfte_dokumente = DokumentDatei.objects.filter(
|
||||
paechter=paechter
|
||||
).order_by("kontext", "titel")
|
||||
|
||||
# Neue LandVerpachtungen für diesen Pächter laden
|
||||
@@ -392,7 +390,7 @@ def land_detail(request, pk):
|
||||
land = get_object_or_404(Land, pk=pk)
|
||||
|
||||
# Alle mit dieser Länderei verknüpften Dokumente laden
|
||||
verknuepfte_dokumente = DokumentLink.objects.filter(land_id=land.pk).order_by(
|
||||
verknuepfte_dokumente = DokumentDatei.objects.filter(land=land).order_by(
|
||||
"kontext", "titel"
|
||||
)
|
||||
|
||||
@@ -584,8 +582,8 @@ def land_verpachtung_detail(request, pk):
|
||||
verpachtung = get_object_or_404(LandVerpachtung, pk=pk)
|
||||
|
||||
# Alle mit dieser Verpachtung verknüpften Dokumente laden
|
||||
verknuepfte_dokumente = DokumentLink.objects.filter(
|
||||
land_verpachtung_id=verpachtung.pk
|
||||
verknuepfte_dokumente = DokumentDatei.objects.filter(
|
||||
verpachtung=verpachtung
|
||||
).order_by("kontext", "titel")
|
||||
|
||||
context = {
|
||||
@@ -747,55 +745,26 @@ def paechter_export(request, pk):
|
||||
json.dumps(entity_data, indent=2, ensure_ascii=False),
|
||||
)
|
||||
|
||||
# 2. Linked documents from Paperless
|
||||
dokumente = DokumentLink.objects.filter(paechter_id=paechter.pk)
|
||||
# 2. DMS-Dokumente (Django-natives DMS)
|
||||
dokumente = DokumentDatei.objects.filter(paechter=paechter)
|
||||
docs_data = []
|
||||
for doc in dokumente:
|
||||
doc_data = {
|
||||
"paperless_id": doc.paperless_document_id,
|
||||
"dms_id": str(doc.id),
|
||||
"titel": doc.titel,
|
||||
"kontext": doc.get_kontext_display(),
|
||||
"beschreibung": doc.beschreibung,
|
||||
"dateiname": doc.dateiname_original,
|
||||
}
|
||||
docs_data.append(doc_data)
|
||||
|
||||
# Try to download document from Paperless
|
||||
try:
|
||||
if (
|
||||
hasattr(settings, "PAPERLESS_API_URL")
|
||||
and settings.PAPERLESS_API_URL
|
||||
):
|
||||
doc_url = f"{settings.PAPERLESS_API_URL}/api/documents/{doc.paperless_document_id}/download/"
|
||||
headers = {}
|
||||
if (
|
||||
hasattr(settings, "PAPERLESS_API_TOKEN")
|
||||
and settings.PAPERLESS_API_TOKEN
|
||||
):
|
||||
headers["Authorization"] = (
|
||||
f"Token {settings.PAPERLESS_API_TOKEN}"
|
||||
)
|
||||
|
||||
response = requests.get(doc_url, headers=headers, timeout=30)
|
||||
if response.status_code == 200:
|
||||
content_type = response.headers.get("content-type", "")
|
||||
if "pdf" in content_type:
|
||||
ext = ".pdf"
|
||||
elif "jpeg" in content_type or "jpg" in content_type:
|
||||
ext = ".jpg"
|
||||
elif "png" in content_type:
|
||||
ext = ".png"
|
||||
else:
|
||||
ext = ".pdf"
|
||||
|
||||
safe_filename = f"dokument_{doc.paperless_document_id}_{doc.titel.replace('/', '_')[:50]}{ext}"
|
||||
zipf.writestr(
|
||||
f"dokumente/{safe_filename}", response.content
|
||||
)
|
||||
doc_data["downloaded"] = True
|
||||
else:
|
||||
doc_data["download_error"] = f"HTTP {response.status_code}"
|
||||
if doc.datei:
|
||||
safe_filename = doc.dateiname_original or str(doc.id)
|
||||
zipf.writestr(f"dokumente/{safe_filename}", doc.datei.read())
|
||||
doc_data["included"] = True
|
||||
except Exception as e:
|
||||
doc_data["download_error"] = str(e)
|
||||
doc_data["error"] = str(e)
|
||||
|
||||
if docs_data:
|
||||
zipf.writestr(
|
||||
@@ -871,55 +840,26 @@ def land_export(request, pk):
|
||||
"land_data.json", json.dumps(entity_data, indent=2, ensure_ascii=False)
|
||||
)
|
||||
|
||||
# 2. Linked documents from Paperless
|
||||
dokumente = DokumentLink.objects.filter(land_id=land.pk)
|
||||
# 2. DMS-Dokumente (Django-natives DMS)
|
||||
dokumente = DokumentDatei.objects.filter(land=land)
|
||||
docs_data = []
|
||||
for doc in dokumente:
|
||||
doc_data = {
|
||||
"paperless_id": doc.paperless_document_id,
|
||||
"dms_id": str(doc.id),
|
||||
"titel": doc.titel,
|
||||
"kontext": doc.get_kontext_display(),
|
||||
"beschreibung": doc.beschreibung,
|
||||
"dateiname": doc.dateiname_original,
|
||||
}
|
||||
docs_data.append(doc_data)
|
||||
|
||||
# Try to download document from Paperless
|
||||
try:
|
||||
if (
|
||||
hasattr(settings, "PAPERLESS_API_URL")
|
||||
and settings.PAPERLESS_API_URL
|
||||
):
|
||||
doc_url = f"{settings.PAPERLESS_API_URL}/api/documents/{doc.paperless_document_id}/download/"
|
||||
headers = {}
|
||||
if (
|
||||
hasattr(settings, "PAPERLESS_API_TOKEN")
|
||||
and settings.PAPERLESS_API_TOKEN
|
||||
):
|
||||
headers["Authorization"] = (
|
||||
f"Token {settings.PAPERLESS_API_TOKEN}"
|
||||
)
|
||||
|
||||
response = requests.get(doc_url, headers=headers, timeout=30)
|
||||
if response.status_code == 200:
|
||||
content_type = response.headers.get("content-type", "")
|
||||
if "pdf" in content_type:
|
||||
ext = ".pdf"
|
||||
elif "jpeg" in content_type or "jpg" in content_type:
|
||||
ext = ".jpg"
|
||||
elif "png" in content_type:
|
||||
ext = ".png"
|
||||
else:
|
||||
ext = ".pdf"
|
||||
|
||||
safe_filename = f"dokument_{doc.paperless_document_id}_{doc.titel.replace('/', '_')[:50]}{ext}"
|
||||
zipf.writestr(
|
||||
f"dokumente/{safe_filename}", response.content
|
||||
)
|
||||
doc_data["downloaded"] = True
|
||||
else:
|
||||
doc_data["download_error"] = f"HTTP {response.status_code}"
|
||||
if doc.datei:
|
||||
safe_filename = doc.dateiname_original or str(doc.id)
|
||||
zipf.writestr(f"dokumente/{safe_filename}", doc.datei.read())
|
||||
doc_data["included"] = True
|
||||
except Exception as e:
|
||||
doc_data["download_error"] = str(e)
|
||||
doc_data["error"] = str(e)
|
||||
|
||||
if docs_data:
|
||||
zipf.writestr(
|
||||
@@ -996,55 +936,26 @@ def verpachtung_export(request, pk):
|
||||
json.dumps(entity_data, indent=2, ensure_ascii=False),
|
||||
)
|
||||
|
||||
# 2. Linked documents from Paperless
|
||||
dokumente = DokumentLink.objects.filter(verpachtung_id=verpachtung.pk)
|
||||
# 2. DMS-Dokumente (Django-natives DMS)
|
||||
dokumente = DokumentDatei.objects.filter(verpachtung=verpachtung)
|
||||
docs_data = []
|
||||
for doc in dokumente:
|
||||
doc_data = {
|
||||
"paperless_id": doc.paperless_document_id,
|
||||
"dms_id": str(doc.id),
|
||||
"titel": doc.titel,
|
||||
"kontext": doc.get_kontext_display(),
|
||||
"beschreibung": doc.beschreibung,
|
||||
"dateiname": doc.dateiname_original,
|
||||
}
|
||||
docs_data.append(doc_data)
|
||||
|
||||
# Try to download document from Paperless
|
||||
try:
|
||||
if (
|
||||
hasattr(settings, "PAPERLESS_API_URL")
|
||||
and settings.PAPERLESS_API_URL
|
||||
):
|
||||
doc_url = f"{settings.PAPERLESS_API_URL}/api/documents/{doc.paperless_document_id}/download/"
|
||||
headers = {}
|
||||
if (
|
||||
hasattr(settings, "PAPERLESS_API_TOKEN")
|
||||
and settings.PAPERLESS_API_TOKEN
|
||||
):
|
||||
headers["Authorization"] = (
|
||||
f"Token {settings.PAPERLESS_API_TOKEN}"
|
||||
)
|
||||
|
||||
response = requests.get(doc_url, headers=headers, timeout=30)
|
||||
if response.status_code == 200:
|
||||
content_type = response.headers.get("content-type", "")
|
||||
if "pdf" in content_type:
|
||||
ext = ".pdf"
|
||||
elif "jpeg" in content_type or "jpg" in content_type:
|
||||
ext = ".jpg"
|
||||
elif "png" in content_type:
|
||||
ext = ".png"
|
||||
else:
|
||||
ext = ".pdf"
|
||||
|
||||
safe_filename = f"dokument_{doc.paperless_document_id}_{doc.titel.replace('/', '_')[:50]}{ext}"
|
||||
zipf.writestr(
|
||||
f"dokumente/{safe_filename}", response.content
|
||||
)
|
||||
doc_data["downloaded"] = True
|
||||
else:
|
||||
doc_data["download_error"] = f"HTTP {response.status_code}"
|
||||
if doc.datei:
|
||||
safe_filename = doc.dateiname_original or str(doc.id)
|
||||
zipf.writestr(f"dokumente/{safe_filename}", doc.datei.read())
|
||||
doc_data["included"] = True
|
||||
except Exception as e:
|
||||
doc_data["download_error"] = str(e)
|
||||
doc_data["error"] = str(e)
|
||||
|
||||
if docs_data:
|
||||
zipf.writestr(
|
||||
@@ -1438,8 +1349,8 @@ def verpachtung_detail(request, pk):
|
||||
verpachtung = get_object_or_404(LandVerpachtung, pk=pk)
|
||||
|
||||
# Alle mit dieser Verpachtung verknüpften Dokumente laden
|
||||
verknuepfte_dokumente = DokumentLink.objects.filter(
|
||||
land_verpachtung_id=verpachtung.pk
|
||||
verknuepfte_dokumente = DokumentDatei.objects.filter(
|
||||
verpachtung=verpachtung
|
||||
).order_by("kontext", "titel")
|
||||
|
||||
context = {
|
||||
|
||||
Reference in New Issue
Block a user