diff --git a/Website/main.py b/Website/main.py
index dbb66c2..5c73a61 100644
--- a/Website/main.py
+++ b/Website/main.py
@@ -1972,34 +1972,54 @@ def preise():
@app.route('/preise/book-option', methods=['POST'])
@login_required
def book_option_package():
- package_raw = _sanitize_text(request.form.get("package") or "", 40).lower()
+ # Unterstützt "packages" (Warenkorb) und Fallback auf "package" (altes Formular)
+ packages_raw = _sanitize_text(request.form.get("packages") or request.form.get("package") or "", 200).lower()
booking_flow = _sanitize_text(request.form.get("flow") or "payment", 24).lower()
+
if booking_flow not in BOOKING_FLOW_MAP:
booking_flow = "payment"
- selected_package = BOOKING_PACKAGE_MAP.get(package_raw)
- if not selected_package:
- flash("Ungültige Buchungsoption ausgewählt.", "error")
+ if not packages_raw:
+ flash("Keine Buchungsoption ausgewählt.", "error")
return redirect(url_for("preise"))
- return redirect(url_for("booking_payment", package=package_raw, flow=booking_flow))
+ return redirect(url_for("booking_payment", packages=packages_raw, flow=booking_flow))
@app.route('/booking/payment', methods=['GET', 'POST'])
@login_required
def booking_payment():
- package_raw = _sanitize_text((request.values.get("package") or ""), 40).lower()
- booking_flow = _sanitize_text((request.values.get("flow") or "payment"), 24).lower()
+ # Hole kommagetrennte Liste (z. B. "inventarsystem,terminverwaltung")
+ packages_str = _sanitize_text(request.values.get("packages") or request.values.get("package") or "", 200).lower()
+ booking_flow = _sanitize_text(request.values.get("flow") or "payment", 24).lower()
+
if booking_flow not in BOOKING_FLOW_MAP:
booking_flow = "payment"
- selected_package = BOOKING_PACKAGE_MAP.get(package_raw)
- package_price = BOOKING_PRICE_MAP.get(package_raw)
- if not selected_package:
- flash("Ungültige Buchungsoption ausgewählt.", "error")
+ if not packages_str:
+ flash("Keine Buchungsoption ausgewählt.", "error")
return redirect(url_for("preise"))
+ package_list = [p.strip() for p in packages_str.split(',') if p.strip()]
+
+ # Validiere Pakete, sammle Namen und berechne Gesamtpreis
+ selected_package_names = []
+ total_price = 0.0
+ for pkg in package_list:
+ name = BOOKING_PACKAGE_MAP.get(pkg)
+ if name:
+ selected_package_names.append(name)
+ total_price += BOOKING_PRICE_MAP.get(pkg, 0.0)
+ else:
+ flash(f"Ungültige Buchungsoption: {pkg}", "error")
+ return redirect(url_for("preise"))
+
+ # Zusammengefasster String für Frontend und Datenbank (z.B. "Inventarsystem, Terminplaner")
+ selected_package = ", ".join(selected_package_names)
+ package_raw = packages_str # Speichert alle raw-keys für DB
+
flow_config = BOOKING_FLOW_MAP[booking_flow]
+
form_data = {
"contact_person": _sanitize_text(request.values.get("contact_person") or "", 120),
"school_name": _sanitize_text(request.values.get("school_name") or "", 160),
@@ -2018,7 +2038,7 @@ def booking_payment():
review_data = {
"package": selected_package,
"package_key": package_raw,
- "package_price": package_price,
+ "package_price": total_price,
"flow": booking_flow,
"title": flow_config["title"],
"intro": flow_config["intro"],
@@ -2031,6 +2051,7 @@ def booking_payment():
if request.method == "GET":
return render_template("schnell_buchung.html", **review_data)
+ # --- POST Logik & Validierung ---
required_fields = {
"contact_person": "Ansprechpartner",
"school_name": "Schule/Einrichtung",
@@ -2039,6 +2060,7 @@ def booking_payment():
"billing_zip": "PLZ",
"billing_city": "Ort",
}
+
missing_labels = [label for key, label in required_fields.items() if not form_data.get(key)]
if missing_labels:
flash(f"Bitte folgende Pflichtfelder ausfüllen: {', '.join(missing_labels)}.", "error")
@@ -2079,20 +2101,17 @@ def booking_payment():
"message": message,
"created_at": _utc_now_iso(),
"booking_flow": booking_flow,
- "booking_package": package_raw,
+ "booking_package": package_raw, # Hier steht nun z.B. "inventarsystem,terminverwaltung"
"booking_data": form_data,
}
)
+
if booking_flow == "payment":
- # Do NOT create an invoice directly. Instead request provisioning via server_steering
booking_number = f"BOOK-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}"
- # Build a subdomain from the school name (fallback to username)
- # Use lowercase, sanitized school name and check for existing instances.
raw_base = form_data.get("school_name") or session.get("username") or booking_number
base = _slugify_subdomain(raw_base).lower()[:51]
- # Check existing instances and require the user to choose a different name on conflict
try:
existing = steering.instace.list() or []
except Exception:
@@ -2103,11 +2122,10 @@ def booking_payment():
return render_template("schnell_buchung.html", **review_data)
subdomain = base[:63]
-
provision_status = "not_started"
provision_port = None
provision_error = ""
- provision_config_ok = False
+ provision_config_ok = True # Startet mit True, wird False wenn ein Modul fehlschlägt
try:
try:
@@ -2115,6 +2133,7 @@ def booking_payment():
except Exception as e:
port = False
provision_error = str(e)
+ provision_config_ok = False
if port and port is not False:
provision_status = "started"
@@ -2123,19 +2142,24 @@ def booking_payment():
except Exception:
provision_port = port
- # Configure module if applicable
- module = None
- if package_raw in {"inventarsystem", "buecherei", "terminverwaltung", "emailversand", "starter", "advanced"}:
- module = package_raw
- if module:
+ # Multi-Modul-Konfiguration: Startpaket wird in Inventar + Bücherei aufgeschlüsselt
+ modules_to_provision = set()
+ for pkg in package_list:
+ if pkg == "startpaket":
+ modules_to_provision.update(["inventarsystem", "buecherei"])
+ elif pkg in {"inventarsystem", "buecherei", "terminverwaltung", "emailversand", "starter", "advanced"}:
+ modules_to_provision.add(pkg)
+
+ # Einzelne Module in der erstellten Instanz editieren/aktivieren
+ for mod in modules_to_provision:
try:
- edit_ok = steering.instace.edit(subdomain, module)
- provision_config_ok = bool(edit_ok)
- if not provision_config_ok and not provision_error:
- provision_error = f"Module configuration failed for {module}"
+ edit_ok = steering.instace.edit(subdomain, mod)
+ if not edit_ok:
+ provision_config_ok = False
+ provision_error += f" | Edit failed for {mod}"
except Exception as e:
provision_config_ok = False
- provision_error = str(e)
+ provision_error += f" | {mod}: {str(e)}"
else:
provision_status = "failed"
if not provision_error:
@@ -2149,7 +2173,7 @@ def booking_payment():
"booking_number": booking_number,
"package": package_raw,
"package_label": selected_package,
- "amount_eur": BOOKING_PRICE_MAP.get(package_raw, 0.0),
+ "amount_eur": total_price,
"booking_flow": booking_flow,
"booking_data": form_data,
"assigned_admin_username": assigned_admin_username,
@@ -2159,7 +2183,7 @@ def booking_payment():
"provision_status": provision_status,
"provision_port": provision_port or "",
"provision_config_ok": provision_config_ok,
- "provision_error": provision_error,
+ "provision_error": provision_error.strip(" | "),
"created_at": _utc_now_iso(),
"source": "booking_payment",
}
@@ -2173,8 +2197,8 @@ def booking_payment():
req_client.close()
except PyMongoError:
- flash("Buchungsanfrage konnte nicht gesendet werden.", "error")
- return redirect(url_for("booking_payment", package=package_raw, flow=booking_flow))
+ flash("Buchungsanfrage konnte nicht gesendet werden. Bitte versuchen Sie es erneut.", "error")
+ return redirect(url_for("booking_payment", packages=package_raw, flow=booking_flow))
finally:
if client:
client.close()
@@ -2185,13 +2209,13 @@ def booking_payment():
else f"Buchung für {selected_package} wurde erfolgreich erfasst. Die Rechnung wird per E-Mail versendet."
)
flash(success_text, "success")
+
if booking_flow == "payment":
return redirect(url_for("my_invoices"))
else:
return redirect(url_for("user_chat"))
-
@app.route('/instance_request/delete', methods=['POST'])
@login_required
def instance_request_delete():
diff --git a/Website/templates/preise.html b/Website/templates/preise.html
index 05a3990..84db49c 100644
--- a/Website/templates/preise.html
+++ b/Website/templates/preise.html
@@ -5,6 +5,7 @@
{% block head %}
{{ super() }}
{% endblock %}
{% block content %}
-
Preise
-
Preise zu unseren Modulen
-
Unsere Module und Pakete sind klar strukturiert und transparent. Wählen Sie die passende Lösung für Ihre Schule und senden Sie die Anfrage direkt an unser Admin-Team.
-
Nach der Anfrage wird sich ein Administrator zeitnah über das Portal mit Ihnen in Verbindung setzen, um die Buchung persönlich vor Ort zu besprechen oder einen direkten Start zu vereinbaren.
-
Unsere Server stehen in einem Deutschen Data-Center der Firma Strato und garantieren eine hohe Verfügbarkeit und Sicherheit Ihrer Daten.
+
Preise & Module
+
Unsere Module und Pakete sind klar strukturiert und transparent. Stellen Sie sich Ihre Lösung flexibel zusammen.
+
Nach Ihrer Anfrage meldet sich unser Admin-Team, um die Bereitstellung oder einen Termin zur Abstimmung einzuplanen. Die Server stehen in einem deutschen Rechenzentrum (Strato) – DSGVO-konform und hochverfügbar.
+
@@ -368,30 +154,28 @@
Inventarsystem
- 350 €
- / Jahr
+ 350 €/ Jahr
-
Der stabile Einstieg in die Schulverwaltung mit Inventarverwaltung, Benutzerrechten und Berichten für den gesamten Schulbetrieb.
+
Der stabile Einstieg in die Schulverwaltung mit Inventarverwaltung, Benutzerrechten und Berichten.
Ein für flexible Terminplanung entwickeltes Modul, dass dem Nutzer erlaubt, seine Termine für beispielsweise Elternsprechstunden komfortabel und schnell im Zugriff zu haben.
+
Für flexible Terminplanung entwickelt (z.B. Elternsprechstunden), um Termine hürdenlos online anzubieten.