From dd35b37fb5df1e99a35b368614e3e28cde5d460e Mon Sep 17 00:00:00 2001 From: Aiirondev Date: Sat, 29 Aug 2026 08:55:03 +0200 Subject: [PATCH] changes to the Rechnungs Processing --- Website/main.py | 32 ++++++----- Website/modules/emailservice/email.py | 79 +++++++++++++++------------ 2 files changed, 63 insertions(+), 48 deletions(-) diff --git a/Website/main.py b/Website/main.py index 06e5e31..402c1cd 100644 --- a/Website/main.py +++ b/Website/main.py @@ -2818,18 +2818,18 @@ def view_hauptvertrag(invoice_id): flash("Kein Hauptvertrag für diese Rechnung vorhanden.", "error") return redirect(url_for("my_invoices")) - # Sicherheitsprüfung + # Sicherheitsprüfung: Gehört die Rechnung dem User oder ist es ein Admin? current_user = session.get("username") - is_admin = session.get("is_admin") + is_admin = session.get("is_admin", False) if invoice.get("username") != current_user and not is_admin: flash("Keine Berechtigung zum Anschauen dieses Hauptvertrags.", "error") return redirect(url_for("my_invoices")) file_data = invoice["hauptvertrag_data"] mimetype = invoice.get("hauptvertrag_mimetype", "application/pdf") - filename = invoice.get("hauptvertrag_filename", "hauptvertrag") + filename = invoice.get("hauptvertrag_filename", "Hauptvertrag.pdf") - # Fallback für unterschiedliche Flask-Versionen (< 2.0 vs >= 2.0) + # Rückgabe als Stream an den Browser (inline anzeigen, nicht erzwingen als Download) try: return send_file( io.BytesIO(file_data), @@ -2838,6 +2838,7 @@ def view_hauptvertrag(invoice_id): download_name=filename ) except TypeError: + # Fallback für ältere Flask-Versionen (< 2.0) return send_file( io.BytesIO(file_data), mimetype=mimetype, @@ -2846,18 +2847,14 @@ def view_hauptvertrag(invoice_id): ) except Exception as e: - # WICHTIG: Das schreibt den tatsächlichen Fehler in deine Docker-Logs! - import traceback print(f"KRITISCHER FEHLER in view_hauptvertrag: {e}", flush=True) traceback.print_exc() - flash("Fehler beim Abrufen der Datei aus der Datenbank.", "error") return redirect(url_for("my_invoices")) finally: if client: client.close() - @app.route('/my/invoices', methods=['GET', 'POST']) @login_required def my_invoices(): @@ -2902,7 +2899,13 @@ def my_invoices(): invoices = [] try: client, col = _get_collection("invoices") - raw_invoices = list(col.find({"username": session.get("username")}).sort("created_at", -1)) + + # WICHTIG: "hauptvertrag_data" und "rechnung_data" (die schweren Binärdaten) bei der Abfrage ausschließen (0), + # um den Arbeitsspeicher (RAM) nicht zu überlasten! + raw_invoices = list(col.find( + {"username": session.get("username")}, + {"hauptvertrag_data": 0, "rechnung_data": 0} + ).sort("created_at", -1)) for inv in raw_invoices: inv["id"] = str(inv["_id"]) @@ -2913,11 +2916,13 @@ def my_invoices(): except ValueError: amt = 0.0 inv["amount_eur"] = amt - # Flag setzen, ob Vertrag in MongoDB liegt (ohne die schweren Binärdaten ins Template zu laden) - inv["has_hauptvertrag"] = "hauptvertrag_data" in inv + + # Flags setzen, ob Verträge/Rechnungen in MongoDB existieren (anhand des Dateinamens) + inv["has_hauptvertrag"] = bool(inv.get("hauptvertrag_filename")) + inv["has_rechnung"] = bool(inv.get("rechnung_filename")) invoices.append(inv) - except PyMongoError: + except Exception as e: flash("Rechnungen konnten nicht geladen werden.", "error") finally: if client: @@ -2936,7 +2941,7 @@ def my_invoices(): except ValueError: amt = 0.0 item["amount_eur"] = amt - except PyMongoError: + except Exception: flash("Instanz-Anfragen konnten nicht geladen werden.", "error") finally: if 'req_client' in locals() and req_client: @@ -2944,7 +2949,6 @@ def my_invoices(): return render_template("my_invoices.html", invoices=invoices, instance_requests=instance_requests) - @app.route('/my/instance', methods=['GET', 'POST']) @login_required def my_instance_management(): diff --git a/Website/modules/emailservice/email.py b/Website/modules/emailservice/email.py index 8fe599f..0dc43ca 100644 --- a/Website/modules/emailservice/email.py +++ b/Website/modules/emailservice/email.py @@ -9,6 +9,7 @@ from fpdf import FPDF from flask import current_app from pymongo import MongoClient import threading +from bson import Binary, ObjectId from datetime import datetime, timezone, timedelta @@ -516,11 +517,6 @@ def send_accreditation_email( invoice_to_attach = invoice_path db_pdf_path = relative_invoice_path - if current_app: - static_dir = current_app.static_folder - else: - static_dir = os.path.join(os.getcwd(), "static") - def get_valid_pdf_path(filename): possible_paths = [ os.path.join(static_dir, "downloads", filename), @@ -536,7 +532,7 @@ def send_accreditation_email( agb_pdf_path = get_valid_pdf_path("AGB Invario.pdf") avv_pdf_path = get_valid_pdf_path("AVV Invario.pdf") - # 3. E-Mail Inhalte + # 4. E-Mail Inhalte subject = "Ihre Zugangsdaten und Unterlagen für Invario" text_note = ( @@ -571,18 +567,14 @@ def send_accreditation_email( """ - # 4. Vorhandene Anhänge sammeln + # 5. Vorhandene Anhänge sammeln attachments = [] - if os.path.exists(contract_pdf_path): attachments.append(contract_pdf_path) - if invoice_to_attach and os.path.exists(invoice_to_attach): attachments.append(invoice_to_attach) - if agb_pdf_path: attachments.append(agb_pdf_path) - if avv_pdf_path: attachments.append(avv_pdf_path) @@ -615,7 +607,7 @@ def send_accreditation_email( def close(self): return None - def _get_mongo_client() -> MongoClient: + def _get_mongo_client(): global _MONGO_CLIENT if _MONGO_CLIENT is not None: return _MONGO_CLIENT @@ -645,33 +637,50 @@ def send_accreditation_email( def _utc_now_iso(): return datetime.now(timezone.utc).isoformat() - # Berechne das Fälligkeitsdatum (30 Tage ab heute) + # Berechne das Fälligkeitsdatum (14 Tage ab heute) computed_due_date = (datetime.now(timezone.utc) + timedelta(days=14)).strftime("%d %B %Y") # Collections abrufen req_client, req_col = _get_collection("instance_requests") - inv_client, col = _get_collection("invoices") # Collection für Rechnungen initialisieren + inv_client, col = _get_collection("invoices") - # 1. Die neue Rechnung in die Datenbank (invoices-Collection) schreiben - # Wir nutzen hier die übergebenen Funktionsparameter anstatt des undefinierten "prov" - col.insert_one( - { - "username": user_id, - "school_name": school_name, - "address": address, - "invoice_number": current_invoice_number, - "period": date, - "amount_eur": price, - "status": "Zu prüfen", - "due_date": computed_due_date, - "pdf_path": db_pdf_path, - "created_at": _utc_now_iso(), - "source": "booking_payment", - "software_name": software_name, - "domain": domain - } - ) + # 6. Die generierte PDF-Datei als Binär-Daten für MongoDB einlesen + rechnung_bytes = None + if invoice_to_attach and os.path.exists(invoice_to_attach): + try: + with open(invoice_to_attach, "rb") as f: + rechnung_bytes = f.read() + except Exception as e: + # Im Falle eines Dateilesefehlers überspringen wir das Speichern des Binaries + pass + # 7. Die neue Rechnung in die Datenbank (invoices-Collection) schreiben + invoice_document = { + "username": user_id, + "school_name": school_name, + "address": address, + "invoice_number": current_invoice_number, + "period": date, + "amount_eur": price, + "status": "Zu prüfen", + "due_date": computed_due_date, + "pdf_path": db_pdf_path, + "created_at": _utc_now_iso(), + "source": "booking_payment", + "software_name": software_name, + "domain": domain + } + + # Binärdaten der Rechnung hinzufügen, falls erfolgreich eingelesen + if rechnung_bytes: + invoice_document["rechnung_data"] = Binary(rechnung_bytes) + invoice_document["rechnung_filename"] = invoice_filename + invoice_document["rechnung_mimetype"] = "application/pdf" + + # In MongoDB einfügen + col.insert_one(invoice_document) + + # Anfrage-Status aktualisieren req_col.update_one( {"username": user_id}, {"$set": { @@ -680,11 +689,13 @@ def send_accreditation_email( }} ) - # 5. E-Mail versenden & NUR temporäre Dateien löschen + # 8. E-Mail versenden & NUR temporäre Dateien löschen try: success = send(recipient, subject, text_body=text_note, html_body=html_note, attachments=attachments) return success finally: + # Nur der generierte Hauptvertrag liegt in 'tempfile.gettempdir()' und wird gelöscht. + # Die Rechnung im '/static/invoices/' Ordner bleibt unangetastet (sowie als Backup auf der Festplatte). if os.path.exists(contract_pdf_path): try: os.remove(contract_pdf_path)