changes to the Rechnungs Processing
This commit is contained in:
+18
-14
@@ -2818,18 +2818,18 @@ def view_hauptvertrag(invoice_id):
|
|||||||
flash("Kein Hauptvertrag für diese Rechnung vorhanden.", "error")
|
flash("Kein Hauptvertrag für diese Rechnung vorhanden.", "error")
|
||||||
return redirect(url_for("my_invoices"))
|
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")
|
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:
|
if invoice.get("username") != current_user and not is_admin:
|
||||||
flash("Keine Berechtigung zum Anschauen dieses Hauptvertrags.", "error")
|
flash("Keine Berechtigung zum Anschauen dieses Hauptvertrags.", "error")
|
||||||
return redirect(url_for("my_invoices"))
|
return redirect(url_for("my_invoices"))
|
||||||
|
|
||||||
file_data = invoice["hauptvertrag_data"]
|
file_data = invoice["hauptvertrag_data"]
|
||||||
mimetype = invoice.get("hauptvertrag_mimetype", "application/pdf")
|
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:
|
try:
|
||||||
return send_file(
|
return send_file(
|
||||||
io.BytesIO(file_data),
|
io.BytesIO(file_data),
|
||||||
@@ -2838,6 +2838,7 @@ def view_hauptvertrag(invoice_id):
|
|||||||
download_name=filename
|
download_name=filename
|
||||||
)
|
)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
# Fallback für ältere Flask-Versionen (< 2.0)
|
||||||
return send_file(
|
return send_file(
|
||||||
io.BytesIO(file_data),
|
io.BytesIO(file_data),
|
||||||
mimetype=mimetype,
|
mimetype=mimetype,
|
||||||
@@ -2846,18 +2847,14 @@ def view_hauptvertrag(invoice_id):
|
|||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
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)
|
print(f"KRITISCHER FEHLER in view_hauptvertrag: {e}", flush=True)
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
|
|
||||||
flash("Fehler beim Abrufen der Datei aus der Datenbank.", "error")
|
flash("Fehler beim Abrufen der Datei aus der Datenbank.", "error")
|
||||||
return redirect(url_for("my_invoices"))
|
return redirect(url_for("my_invoices"))
|
||||||
finally:
|
finally:
|
||||||
if client:
|
if client:
|
||||||
client.close()
|
client.close()
|
||||||
|
|
||||||
|
|
||||||
@app.route('/my/invoices', methods=['GET', 'POST'])
|
@app.route('/my/invoices', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def my_invoices():
|
def my_invoices():
|
||||||
@@ -2902,7 +2899,13 @@ def my_invoices():
|
|||||||
invoices = []
|
invoices = []
|
||||||
try:
|
try:
|
||||||
client, col = _get_collection("invoices")
|
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:
|
for inv in raw_invoices:
|
||||||
inv["id"] = str(inv["_id"])
|
inv["id"] = str(inv["_id"])
|
||||||
@@ -2913,11 +2916,13 @@ def my_invoices():
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
amt = 0.0
|
amt = 0.0
|
||||||
inv["amount_eur"] = amt
|
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)
|
invoices.append(inv)
|
||||||
|
|
||||||
except PyMongoError:
|
except Exception as e:
|
||||||
flash("Rechnungen konnten nicht geladen werden.", "error")
|
flash("Rechnungen konnten nicht geladen werden.", "error")
|
||||||
finally:
|
finally:
|
||||||
if client:
|
if client:
|
||||||
@@ -2936,7 +2941,7 @@ def my_invoices():
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
amt = 0.0
|
amt = 0.0
|
||||||
item["amount_eur"] = amt
|
item["amount_eur"] = amt
|
||||||
except PyMongoError:
|
except Exception:
|
||||||
flash("Instanz-Anfragen konnten nicht geladen werden.", "error")
|
flash("Instanz-Anfragen konnten nicht geladen werden.", "error")
|
||||||
finally:
|
finally:
|
||||||
if 'req_client' in locals() and req_client:
|
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)
|
return render_template("my_invoices.html", invoices=invoices, instance_requests=instance_requests)
|
||||||
|
|
||||||
|
|
||||||
@app.route('/my/instance', methods=['GET', 'POST'])
|
@app.route('/my/instance', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def my_instance_management():
|
def my_instance_management():
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from fpdf import FPDF
|
|||||||
from flask import current_app
|
from flask import current_app
|
||||||
from pymongo import MongoClient
|
from pymongo import MongoClient
|
||||||
import threading
|
import threading
|
||||||
|
from bson import Binary, ObjectId
|
||||||
from datetime import datetime, timezone, timedelta
|
from datetime import datetime, timezone, timedelta
|
||||||
|
|
||||||
|
|
||||||
@@ -516,11 +517,6 @@ def send_accreditation_email(
|
|||||||
invoice_to_attach = invoice_path
|
invoice_to_attach = invoice_path
|
||||||
db_pdf_path = relative_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):
|
def get_valid_pdf_path(filename):
|
||||||
possible_paths = [
|
possible_paths = [
|
||||||
os.path.join(static_dir, "downloads", filename),
|
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")
|
agb_pdf_path = get_valid_pdf_path("AGB Invario.pdf")
|
||||||
avv_pdf_path = get_valid_pdf_path("AVV 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"
|
subject = "Ihre Zugangsdaten und Unterlagen für Invario"
|
||||||
|
|
||||||
text_note = (
|
text_note = (
|
||||||
@@ -571,18 +567,14 @@ def send_accreditation_email(
|
|||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# 4. Vorhandene Anhänge sammeln
|
# 5. Vorhandene Anhänge sammeln
|
||||||
attachments = []
|
attachments = []
|
||||||
|
|
||||||
if os.path.exists(contract_pdf_path):
|
if os.path.exists(contract_pdf_path):
|
||||||
attachments.append(contract_pdf_path)
|
attachments.append(contract_pdf_path)
|
||||||
|
|
||||||
if invoice_to_attach and os.path.exists(invoice_to_attach):
|
if invoice_to_attach and os.path.exists(invoice_to_attach):
|
||||||
attachments.append(invoice_to_attach)
|
attachments.append(invoice_to_attach)
|
||||||
|
|
||||||
if agb_pdf_path:
|
if agb_pdf_path:
|
||||||
attachments.append(agb_pdf_path)
|
attachments.append(agb_pdf_path)
|
||||||
|
|
||||||
if avv_pdf_path:
|
if avv_pdf_path:
|
||||||
attachments.append(avv_pdf_path)
|
attachments.append(avv_pdf_path)
|
||||||
|
|
||||||
@@ -615,7 +607,7 @@ def send_accreditation_email(
|
|||||||
def close(self):
|
def close(self):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _get_mongo_client() -> MongoClient:
|
def _get_mongo_client():
|
||||||
global _MONGO_CLIENT
|
global _MONGO_CLIENT
|
||||||
if _MONGO_CLIENT is not None:
|
if _MONGO_CLIENT is not None:
|
||||||
return _MONGO_CLIENT
|
return _MONGO_CLIENT
|
||||||
@@ -645,33 +637,50 @@ def send_accreditation_email(
|
|||||||
def _utc_now_iso():
|
def _utc_now_iso():
|
||||||
return datetime.now(timezone.utc).isoformat()
|
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")
|
computed_due_date = (datetime.now(timezone.utc) + timedelta(days=14)).strftime("%d %B %Y")
|
||||||
|
|
||||||
# Collections abrufen
|
# Collections abrufen
|
||||||
req_client, req_col = _get_collection("instance_requests")
|
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
|
# 6. Die generierte PDF-Datei als Binär-Daten für MongoDB einlesen
|
||||||
# Wir nutzen hier die übergebenen Funktionsparameter anstatt des undefinierten "prov"
|
rechnung_bytes = None
|
||||||
col.insert_one(
|
if invoice_to_attach and os.path.exists(invoice_to_attach):
|
||||||
{
|
try:
|
||||||
"username": user_id,
|
with open(invoice_to_attach, "rb") as f:
|
||||||
"school_name": school_name,
|
rechnung_bytes = f.read()
|
||||||
"address": address,
|
except Exception as e:
|
||||||
"invoice_number": current_invoice_number,
|
# Im Falle eines Dateilesefehlers überspringen wir das Speichern des Binaries
|
||||||
"period": date,
|
pass
|
||||||
"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
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
# 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(
|
req_col.update_one(
|
||||||
{"username": user_id},
|
{"username": user_id},
|
||||||
{"$set": {
|
{"$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:
|
try:
|
||||||
success = send(recipient, subject, text_body=text_note, html_body=html_note, attachments=attachments)
|
success = send(recipient, subject, text_body=text_note, html_body=html_note, attachments=attachments)
|
||||||
return success
|
return success
|
||||||
finally:
|
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):
|
if os.path.exists(contract_pdf_path):
|
||||||
try:
|
try:
|
||||||
os.remove(contract_pdf_path)
|
os.remove(contract_pdf_path)
|
||||||
|
|||||||
Reference in New Issue
Block a user