changes to he pdf sending
This commit is contained in:
+47
-40
@@ -2206,7 +2206,19 @@ def build_module_config_string(package_list: list) -> str:
|
||||
]
|
||||
return " ".join(config_parts)
|
||||
|
||||
def background_booking_provisioning(app_instance, prov_id, subdomain, package_list, user_id, school_name, user_email, username, address, price, software_name):
|
||||
def background_booking_provisioning(
|
||||
app_instance,
|
||||
prov_id,
|
||||
subdomain,
|
||||
package_list,
|
||||
user_id,
|
||||
school_name,
|
||||
user_email,
|
||||
username,
|
||||
address,
|
||||
price,
|
||||
software_name="Invario Inventarsystem"
|
||||
):
|
||||
"""Hintergrund-Worker, der die Tenant-Bereitstellung über das Host-Watcher-Triggersystem auslöst."""
|
||||
with app_instance.app_context():
|
||||
req_client, req_col = _get_collection("instance_requests")
|
||||
@@ -2214,7 +2226,7 @@ def background_booking_provisioning(app_instance, prov_id, subdomain, package_li
|
||||
# 1. Status auf "in_progress" setzen
|
||||
req_col.update_one({"_id": ObjectId(prov_id)}, {"$set": {"provision_status": "in_progress"}})
|
||||
|
||||
# 2. Naechsten Port und Admin-Passwort ermitteln
|
||||
# 2. Nächsten Port und Admin-Passwort ermitteln
|
||||
inst_client_check, inst_col_check = _get_collection("instances")
|
||||
try:
|
||||
existing_instances = list(inst_col_check.find({}, {"https_port": 1, "port": 1}))
|
||||
@@ -2232,7 +2244,7 @@ def background_booking_provisioning(app_instance, prov_id, subdomain, package_li
|
||||
# Generiere den korrekten Modul-String
|
||||
module_config_str = build_module_config_string(package_list)
|
||||
|
||||
# 3. Trigger-Datei fuer den Host-Watcher schreiben
|
||||
# 3. Trigger-Datei für den Host-Watcher schreiben
|
||||
os.makedirs(TRIGGER_DIR, exist_ok=True)
|
||||
trigger_file = os.path.join(TRIGGER_DIR, f"{subdomain}.json")
|
||||
|
||||
@@ -2248,13 +2260,13 @@ def background_booking_provisioning(app_instance, prov_id, subdomain, package_li
|
||||
with open(trigger_file, "w") as f:
|
||||
json.dump(trigger_payload, f)
|
||||
|
||||
print(f"[INFO] Trigger-Datei fuer Host-Watcher erstellt: {trigger_file} mit Payload: {trigger_payload}")
|
||||
print(f"[INFO] Trigger-Datei für Host-Watcher erstellt: {trigger_file} mit Payload: {trigger_payload}")
|
||||
|
||||
# 4. Instanz-Eintrag in der Datenbank registrieren
|
||||
domain = f"{subdomain}.invario-software.de"
|
||||
instance_doc = {
|
||||
"owner_id": user_id,
|
||||
"owner_username": username, # <--- HIER HINZUGEFÜGT
|
||||
"owner_username": username,
|
||||
"school_name": school_name,
|
||||
"subdomain": subdomain,
|
||||
"domain": domain,
|
||||
@@ -2272,9 +2284,11 @@ def background_booking_provisioning(app_instance, prov_id, subdomain, package_li
|
||||
finally:
|
||||
inst_client.close()
|
||||
|
||||
# 5. Bestaetigungs-E-Mail versenden
|
||||
# 5. Bestätigungs-E-Mail inkl. Vertrag & Anhängen versenden
|
||||
try:
|
||||
if user_email:
|
||||
formatted_date = datetime.today().strftime('%d.%m.%Y')
|
||||
|
||||
send_accreditation_email(
|
||||
recipient=user_email,
|
||||
domain=domain,
|
||||
@@ -2283,9 +2297,10 @@ def background_booking_provisioning(app_instance, prov_id, subdomain, package_li
|
||||
school_name=school_name,
|
||||
address=address,
|
||||
price=price,
|
||||
date=_utc_now_iso(),
|
||||
software_name=software_name,
|
||||
date=formatted_date,
|
||||
software_name=software_name
|
||||
)
|
||||
|
||||
except Exception as mail_err:
|
||||
print(f"[WARNING] E-Mail-Versand fehlgeschlagen: {mail_err}")
|
||||
|
||||
@@ -2302,10 +2317,10 @@ def background_booking_provisioning(app_instance, prov_id, subdomain, package_li
|
||||
"updated_at": _utc_now_iso()
|
||||
}}
|
||||
)
|
||||
print(f"[SUCCESS] Provisioning-Trigger erfolgreich gesendet fuer {subdomain}")
|
||||
print(f"[SUCCESS] Provisioning-Trigger erfolgreich gesendet für {subdomain}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"[ERROR] Async Provisioning fehlgeschlagen fuer {subdomain}:")
|
||||
print(f"[ERROR] Async Provisioning fehlgeschlagen für {subdomain}:")
|
||||
traceback.print_exc()
|
||||
req_col.update_one({"_id": ObjectId(prov_id)}, {"$set": {"provision_status": "failed", "last_error": str(e)}})
|
||||
finally:
|
||||
@@ -2350,7 +2365,6 @@ def booking_payment():
|
||||
|
||||
flow_config = BOOKING_FLOW_MAP[booking_flow]
|
||||
|
||||
# Auslesen aller Formularfelder (inkl. tenant_slug und Rechnungsdaten)
|
||||
form_data = {
|
||||
"contact_person": _sanitize_text(request.values.get("contact_person") or "", 120),
|
||||
"school_name": _sanitize_text(request.values.get("school_name") or "", 160),
|
||||
@@ -2426,21 +2440,8 @@ def booking_payment():
|
||||
assigned_admin_display_name = assigned_admin.get("display_name") if assigned_admin else ""
|
||||
assigned_admin_email = (assigned_admin.get("email") if assigned_admin else "") or BOOKING_ADMIN_EMAIL_FALLBACK
|
||||
|
||||
message = (
|
||||
f"{flow_config['message_prefix']} {selected_package} {flow_config['message_suffix']}\n"
|
||||
f"Ansprechpartner: {form_data['contact_person']}\n"
|
||||
f"Schule: {form_data['school_name']} (Dienstst.-Nr: {form_data['school_number']})\n"
|
||||
f"E-Mail: {form_data['contact_email']}\n"
|
||||
f"Telefon: {form_data['contact_phone'] or '-'}\n"
|
||||
f"Schulträger/Amt: {form_data['billing_organization']}\n"
|
||||
f"Rechnungsadresse: {form_data['billing_street']}, {form_data['billing_zip']} {form_data['billing_city']}\n"
|
||||
f"Kassenzeichen: {form_data['billing_reference'] or '-'}\n"
|
||||
f"Gewünschte Subdomain: {form_data['tenant_slug']}\n"
|
||||
f"Notizen: {form_data['notes'] or '-'}"
|
||||
)
|
||||
|
||||
try:
|
||||
# FLOW 1: Beratung / Gesprächs-Buchung
|
||||
# FLOW 1: Consultation / Chat
|
||||
if booking_flow == "consultation":
|
||||
client, col = _get_collection("chat_messages")
|
||||
try:
|
||||
@@ -2449,7 +2450,7 @@ def booking_payment():
|
||||
"username": session.get("username"),
|
||||
"sender": session.get("display_name") or session.get("username"),
|
||||
"sender_role": "user",
|
||||
"message": message,
|
||||
"message": f"Gesprächswunsch bezüglich: {selected_package}",
|
||||
"created_at": _utc_now_iso(),
|
||||
"booking_flow": booking_flow,
|
||||
"booking_package": package_raw,
|
||||
@@ -2465,18 +2466,16 @@ def booking_payment():
|
||||
flash(f"Gesprächstermin für {selected_package} wurde an das Team gesendet.", "success")
|
||||
return redirect(url_for("user_chat"))
|
||||
|
||||
# FLOW 2: Kostenpflichtige Direkt-Buchung mit Provisioning via Watcher
|
||||
# FLOW 2: Direktbuchung mit Provisioning via Host-Watcher
|
||||
if booking_flow == "payment":
|
||||
booking_number = f"BOOK-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}"
|
||||
|
||||
# Schul-URL aus dem Formular bevorzugen
|
||||
raw_base = form_data.get("tenant_slug") or form_data.get("school_name") or session.get("username") or booking_number
|
||||
base = _slugify_subdomain(raw_base).lower()[:51]
|
||||
|
||||
# Prüfung auf doppelte Subdomains via MongoDB 'instances' Collection
|
||||
inst_check_client, inst_check_col = _get_collection("instances")
|
||||
inst_check_client, inst_col_check = _get_collection("instances")
|
||||
try:
|
||||
existing_docs = list(inst_check_col.find({}, {"subdomain": 1}))
|
||||
existing_docs = list(inst_col_check.find({}, {"subdomain": 1}))
|
||||
existing_subdomains = [d.get("subdomain") for d in existing_docs if d.get("subdomain")]
|
||||
except Exception as e:
|
||||
print(f"[WARNING] Konnte bestehende Subdomains nicht prüfen: {e}")
|
||||
@@ -2523,29 +2522,37 @@ def booking_payment():
|
||||
username = session.get("username")
|
||||
user_email = session.get("email") or form_data.get("contact_email")
|
||||
address_process = f"{form_data['billing_street']}, {form_data['billing_zip']} {form_data['billing_city']}"
|
||||
price_str = f"{total_price:.2f}".replace('.', ',')
|
||||
|
||||
# Startet den asynchronen Provisioning-Thread
|
||||
# Startet den asynchronen Provisioning-Thread mit exakten Parametern
|
||||
thread = threading.Thread(
|
||||
target=background_booking_provisioning,
|
||||
args=(app_obj, prov_id, subdomain, package_list, user_id, form_data["school_name"], user_email, username, address_process, total_price, selected_package)
|
||||
args=(
|
||||
app_obj,
|
||||
prov_id,
|
||||
subdomain,
|
||||
package_list,
|
||||
user_id,
|
||||
form_data["school_name"],
|
||||
user_email,
|
||||
username,
|
||||
address_process,
|
||||
price_str,
|
||||
selected_package
|
||||
)
|
||||
)
|
||||
thread.start()
|
||||
print(f"[INFO] Background Provisioning Trigger gestartet für Subdomain: {subdomain}")
|
||||
|
||||
# ANPASSUNG FÜR BESSERE WARTEZEIT:
|
||||
# Wir geben Metadaten mit, wie lange das Setup ca. dauern wird.
|
||||
if is_json_request:
|
||||
return jsonify({
|
||||
"success": True,
|
||||
"slug": subdomain,
|
||||
"message": "Instanz wird eingerichtet...",
|
||||
"estimated_seconds": 45, # Erwartete Dauer in Sekunden (anpassbar)
|
||||
"status_poll_interval_ms": 3000 # Wie oft soll das Frontend fragen? (3 Sek)
|
||||
"estimated_seconds": 45,
|
||||
"status_poll_interval_ms": 3000
|
||||
}), 200
|
||||
|
||||
# Falls es KEIN AJAX-Request war:
|
||||
# Wir hängen den Parameter "wait_for_slug" an, damit die "my_instance_management"-Seite weiß,
|
||||
# dass sie sofort ein Lade-Overlay für diesen spezifischen Tenant öffnen muss.
|
||||
flash(f"Buchung für {selected_package} wurde erfasst. Deine Instanz wird jetzt automatisch eingerichtet.", "success")
|
||||
return redirect(url_for("my_instance_management", wait_for_slug=subdomain))
|
||||
|
||||
|
||||
@@ -202,12 +202,11 @@ def generate_main_contract_pdf(
|
||||
software_name: str = "Invario Inventarsystem"
|
||||
):
|
||||
"""Generiert den Hauptvertrag mit zentrierter und aufeinander abgestimmter Kopf- und Fußzeilen-Formatierung."""
|
||||
from datetime import datetime
|
||||
pdf = FPDF()
|
||||
pdf.add_page()
|
||||
pdf.set_auto_page_break(auto=True, margin=15)
|
||||
|
||||
# --- KOPFBEREICH (ZENTRIERT & BÜNDIG UNTEREINANDER) ---
|
||||
# --- KOPFBEREICH ---
|
||||
pdf.set_font("Helvetica", style="B", size=13)
|
||||
pdf.cell(0, 6, "Vertrag über die Nutzung der Software-Lizenz", ln=True, align="C")
|
||||
pdf.cell(0, 6, f'"{software_name}"', ln=True, align="C")
|
||||
@@ -231,7 +230,7 @@ def generate_main_contract_pdf(
|
||||
pdf.cell(0, 5, "- Auftraggeber / Schule -", ln=True, align="C")
|
||||
pdf.ln(8)
|
||||
|
||||
# --- PARAGRAPHEN (1 BIS 8) ---
|
||||
# --- PARAGRAPHEN ---
|
||||
pdf.set_font("Helvetica", style="B", size=10)
|
||||
pdf.cell(0, 6, "1. Vertragsgegenstand", ln=True)
|
||||
pdf.set_font("Helvetica", size=10)
|
||||
@@ -309,28 +308,25 @@ def generate_main_contract_pdf(
|
||||
)
|
||||
pdf.ln(6)
|
||||
|
||||
# --- FUSSBEREICH / UNTERSCHRIFTEN (ZENTRIERT & SYMMETRISCH) ---
|
||||
# --- UNTERSCHRIFTEN ---
|
||||
pdf.set_font("Helvetica", style="B", size=10)
|
||||
pdf.cell(0, 6, "Unterschriften und Einverständnis", ln=True, align="C")
|
||||
pdf.set_font("Helvetica", size=9)
|
||||
pdf.multi_cell(0, 4, "Mit der Unterzeichnung bestätigen die Parteien den Abschluss des Hauptvertrages inklusive der beigefügten Anlagen (AGB, EULA und AVV).", align="C")
|
||||
pdf.ln(8)
|
||||
|
||||
# Exakte Zentrierung der zwei Unterschriften-Blöcke
|
||||
col_width = 85
|
||||
start_x = (210 - (col_width * 2)) / 2 # Berechnet den exakten linken Rand für Symmetrie (20mm)
|
||||
start_x = (210 - (col_width * 2)) / 2
|
||||
|
||||
pdf.set_font("Helvetica", size=9)
|
||||
|
||||
pdf.set_x(start_x)
|
||||
pdf.cell(col_width, 5, "Für den Auftraggeber", align="C", ln=False)
|
||||
pdf.cell(col_width, 5, "Für den Anbieter", align="C", ln=True)
|
||||
|
||||
pdf.ln(8)
|
||||
|
||||
pdf.set_x(start_x)
|
||||
pdf.cell(col_width, 5, f"{datetime.today().strftime('%d.%m.%Y')} ________________________", align="C", ln=False)
|
||||
pdf.cell(col_width, 5, f"{datetime.today().strftime('%d.%m.%Y')} ___________ _____________", align="C", ln=True)
|
||||
pdf.cell(col_width, 5, f"{date} ________________________", align="C", ln=False)
|
||||
pdf.cell(col_width, 5, f"{date} ___________ _____________", align="C", ln=True)
|
||||
|
||||
pdf.set_x(start_x)
|
||||
pdf.cell(col_width, 5, "(Verantwortlicher)", align="C", ln=False)
|
||||
@@ -339,27 +335,31 @@ def generate_main_contract_pdf(
|
||||
pdf.output(output_path)
|
||||
return output_path
|
||||
|
||||
|
||||
def send_accreditation_email(
|
||||
recipient: str,
|
||||
domain: str,
|
||||
username: str,
|
||||
password: str,
|
||||
invoice_pdf_path: str,
|
||||
contract_pdf_path: str,
|
||||
school_name: str = "[Name der Schule / des Auftraggebers]",
|
||||
address: str = "[Straße, Hausnummer] [PLZ, Ort]",
|
||||
price: str = "250,00",
|
||||
date: str = "[Ort, Datum]"
|
||||
date: str = "[Ort, Datum]",
|
||||
software_name: str = "Invario Inventarsystem"
|
||||
) -> bool:
|
||||
"""Generiert den Hauptvertrag und sendet diesen zusammen mit Rechnung, AGB und AVV per E-Mail."""
|
||||
"""Generiert den Hauptvertrag temporär und versendet ihn zusammen mit AGB & AVV per E-Mail."""
|
||||
|
||||
# 1. Hauptvertrag generieren
|
||||
# Clean Sanitization für Dateinamen
|
||||
safe_school_name = "".join([c for c in school_name if c.isalnum() or c in (' ', '_', '-')]).rstrip()
|
||||
contract_filename = f"Hauptvertrag_Invario_{safe_school_name}.pdf"
|
||||
contract_pdf_path = os.path.join(tempfile.gettempdir(), contract_filename)
|
||||
|
||||
# 1. Hauptvertrag dynamisch erzeugen
|
||||
generate_main_contract_pdf(
|
||||
school_name=school_name,
|
||||
address=address,
|
||||
price=price,
|
||||
date=date,
|
||||
software_name=software_name,
|
||||
output_path=contract_pdf_path
|
||||
)
|
||||
|
||||
@@ -367,13 +367,13 @@ def send_accreditation_email(
|
||||
agb_pdf_path = os.path.join("static", "download", "AGB Invario.pdf")
|
||||
avv_pdf_path = os.path.join("static", "download", "AVV Invario.pdf")
|
||||
|
||||
# 3. E-Mail Texte
|
||||
# 3. E-Mail Inhalte
|
||||
subject = "Ihre Zugangsdaten und Unterlagen für Invario"
|
||||
|
||||
text_note = (
|
||||
f"Sehr geehrte/r {username},\n\n"
|
||||
"Vielen Dank für Ihre Akkreditierung bei Invario. "
|
||||
"Im Anhang finden Sie Ihre Rechnung, den Hauptvertrag sowie die AGB und den AVV.\n\n"
|
||||
"Im Anhang finden Sie Ihren Hauptvertrag sowie die AGB und den AVV.\n\n"
|
||||
f"Ihre Zugangsdaten:\n"
|
||||
f"Domain: https://{domain}\n"
|
||||
f"Benutzername: {username}\n"
|
||||
@@ -385,7 +385,7 @@ def send_accreditation_email(
|
||||
<div style="font-family: Arial, Helvetica, sans-serif; color: #333333;">
|
||||
<h2 style="color: #2c3e50; margin-top: 0;">Willkommen bei Invario!</h2>
|
||||
<p style="font-size: 15px; line-height: 1.6;">
|
||||
Vielen Dank für Ihre Akkreditierung. Im Anhang finden Sie Ihre Rechnung, den Hauptvertrag sowie die AGB und den AVV.
|
||||
Vielen Dank für Ihre Akkreditierung. Im Anhang finden Sie Ihren Hauptvertrag sowie die AGB und den AVV.
|
||||
</p>
|
||||
|
||||
<h3 style="color: #2c3e50;">Ihre Zugangsdaten:</h3>
|
||||
@@ -401,25 +401,29 @@ def send_accreditation_email(
|
||||
</div>
|
||||
"""
|
||||
|
||||
# 4. Alle existierenden Anhänge sammeln
|
||||
# 4. Vorhandene Anhänge einsammeln
|
||||
attachments = []
|
||||
|
||||
# Generierter Hauptvertrag & Rechnung
|
||||
if contract_pdf_path and os.path.exists(contract_pdf_path):
|
||||
if os.path.exists(contract_pdf_path):
|
||||
attachments.append(contract_pdf_path)
|
||||
if invoice_pdf_path and os.path.exists(invoice_pdf_path):
|
||||
attachments.append(invoice_pdf_path)
|
||||
|
||||
# Statische Dokumente aus static/download/
|
||||
|
||||
if os.path.exists(agb_pdf_path):
|
||||
attachments.append(agb_pdf_path)
|
||||
else:
|
||||
print(f"Warnung: AGB unter {agb_pdf_path} nicht gefunden.")
|
||||
print(f"[WARNING] AGB unter {agb_pdf_path} nicht gefunden.")
|
||||
|
||||
if os.path.exists(avv_pdf_path):
|
||||
attachments.append(avv_pdf_path)
|
||||
else:
|
||||
print(f"Warnung: AVV unter {avv_pdf_path} nicht gefunden.")
|
||||
print(f"[WARNING] AVV unter {avv_pdf_path} nicht gefunden.")
|
||||
|
||||
# 5. E-Mail mit allen 4 Anhängen versenden
|
||||
return send(recipient, subject, text_body=text_note, html_body=html_note, attachments=attachments)
|
||||
# 5. E-Mail versenden & temporäre Datei aufräumen
|
||||
try:
|
||||
success = send(recipient, subject, text_body=text_note, html_body=html_note, attachments=attachments)
|
||||
return success
|
||||
finally:
|
||||
if os.path.exists(contract_pdf_path):
|
||||
try:
|
||||
os.remove(contract_pdf_path)
|
||||
except OSError as e:
|
||||
print(f"[WARNING] Konnte temporären Vertrag nicht löschen: {e}")
|
||||
Reference in New Issue
Block a user