changes to the booking process

This commit is contained in:
2026-08-25 21:12:46 +02:00
parent 2cea23e391
commit 4a92b2058d
+82 -65
View File
@@ -2166,84 +2166,89 @@ def book_option_package():
return redirect(url_for("booking_payment", packages=packages_raw, flow=booking_flow)) return redirect(url_for("booking_payment", packages=packages_raw, flow=booking_flow))
def background_booking_provisioning(app_instance, prov_id, subdomain, modules_to_provision, user_id, school_name): def background_booking_provisioning(app_instance, prov_id, subdomain, modules_to_provision, user_id, school_name, user_email):
"""Hintergrund-Worker für die klassische Buchung""" """Hintergrund-Worker that actually deploys the instance using the correct API"""
with app_instance.app_context(): with app_instance.app_context():
req_client, req_col = _get_collection("instance_requests") req_client, req_col = _get_collection("instance_requests")
try: try:
# Status auf In Progress setzen # 1. Set Status to In Progress
req_col.update_one({"_id": ObjectId(prov_id)}, {"$set": {"provision_status": "in_progress"}}) req_col.update_one({"_id": ObjectId(prov_id)}, {"$set": {"provision_status": "in_progress"}})
provision_error = "" # 2. Dynamically determine next Port and Password (Proven working logic)
provision_config_ok = True existing_tenants = Instance.list()
provision_port = None used_ports = [t['port'] for t in existing_tenants if 'port' in t]
next_port = max(used_ports) + 1 if used_ports else 10002
admin_password = generate_secure_password() admin_password = generate_secure_password()
# 1. Shell Provisioning # 3. Create Tenant Shell
try: success = Instance.new(subdomain, next_port, admin_password)
port = steering.instance.new(subdomain) if not success:
if port and port is not False: raise Exception("Tenant shell creation failed (Instance.new returned False)")
try:
provision_port = int(port) if isinstance(port, (int, str)) and str(port).isdigit() else port # 4. Enable selected Modules
except Exception: # Always enable "starter" as a baseline if needed, then the booked ones
provision_port = port if "starter" not in modules_to_provision:
Instance.edit(subdomain, "starter")
# 2. Gewählte Module aktivieren
for mod in modules_to_provision: for mod in modules_to_provision:
try: Instance.edit(subdomain, mod)
edit_ok = steering.instance.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 += f" | {mod}: {str(e)}"
else:
provision_error = "Provisioning script returned failure"
provision_config_ok = False
except Exception as e:
provision_error = str(e)
provision_config_ok = False
# 3. Wenn erfolgreich, Instanz in die 'instances' Collection eintragen! (WICHTIG für Health-Check) # 5. Register Instance in Database
if provision_config_ok and not provision_error:
domain = f"{subdomain}.invario-software.de" domain = f"{subdomain}.invario-software.de"
instance_doc = { instance_doc = {
"owner_id": user_id, "owner_id": user_id,
"school_name": school_name, "school_name": school_name,
"subdomain": subdomain, "subdomain": subdomain,
"domain": domain, "domain": domain,
"https_port": provision_port, "https_port": next_port,
"status": "ready", "status": "ready",
"nginx_status": "active", "nginx_status": "active",
"admin_username": "admin", "admin_username": "admin",
"admin_password": admin_password "admin_password": admin_password
} }
inst_client, inst_col = _get_collection("instances") inst_client, inst_col = _get_collection("instances")
try: try:
inst_col.insert_one(instance_doc) created_inst_id = str(inst_col.insert_one(instance_doc).inserted_id)
finally: finally:
inst_client.close() inst_client.close()
# 4. Finalen Status setzen ("ready" löst den Redirect im Frontend aus) # 6. Send Email Notification
final_status = "ready" if provision_config_ok and not provision_error else "failed" try:
if user_email:
send_accreditation_email(
recipient=user_email,
domain=domain,
username="admin",
password=admin_password,
invoice_pdf_path=f"/var/invoices/{subdomain}_invoice.pdf",
contract_pdf_path=f"/var/contracts/{subdomain}_contract.pdf"
)
except Exception as mail_err:
print(f"[WARNING] Email sending failed: {mail_err}")
# 7. Mark Request as Ready for the Frontend
req_col.update_one( req_col.update_one(
{"_id": ObjectId(prov_id)}, {"_id": ObjectId(prov_id)},
{"$set": { {"$set": {
"provision_status": final_status, "provision_status": "ready",
"provision_port": provision_port or "", "provision_port": next_port,
"provision_config_ok": provision_config_ok, "instance_id": created_inst_id,
"provision_error": provision_error.strip(" | "), "url": f"https://{domain}",
"username": "admin",
"password": admin_password,
"updated_at": _utc_now_iso() "updated_at": _utc_now_iso()
}} }}
) )
print(f"[SUCCESS] Provisioning completed for {subdomain}")
except Exception as e: except Exception as e:
print(f"[ERROR] Async Provisioning failed: {e}") print(f"[ERROR] Async Provisioning failed for {subdomain}:")
req_col.update_one({"_id": ObjectId(prov_id)}, {"$set": {"provision_status": "failed", "provision_error": str(e)}}) traceback.print_exc()
req_col.update_one({"_id": ObjectId(prov_id)}, {"$set": {"provision_status": "failed", "last_error": str(e)}})
finally: finally:
req_client.close() req_client.close()
@app.route('/booking/payment', methods=['GET', 'POST']) @app.route('/booking/payment', methods=['GET', 'POST'])
@login_required @login_required
def booking_payment(): def booking_payment():
@@ -2344,10 +2349,11 @@ def booking_payment():
f"Notizen: {form_data['notes'] or '-'}" f"Notizen: {form_data['notes'] or '-'}"
) )
client = None
try: try:
# FLOW 1: Beratung / Gesprächs-Buchung
if booking_flow == "consultation": if booking_flow == "consultation":
client, col = _get_collection("chat_messages") client, col = _get_collection("chat_messages")
try:
col.insert_one( col.insert_one(
{ {
"username": session.get("username"), "username": session.get("username"),
@@ -2360,25 +2366,38 @@ def booking_payment():
"booking_data": form_data, "booking_data": form_data,
} }
) )
finally:
client.close()
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
if booking_flow == "payment": if booking_flow == "payment":
booking_number = f"BOOK-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}" booking_number = f"BOOK-{datetime.utcnow().strftime('%Y%m%d%H%M%S')}"
raw_base = form_data.get("school_name") or session.get("username") or booking_number raw_base = form_data.get("school_name") or session.get("username") or booking_number
base = _slugify_subdomain(raw_base).lower()[:51] base = _slugify_subdomain(raw_base).lower()[:51]
# Subdomain-Prüfung über das korrekte Instance Model
try: try:
existing = steering.instance.list() or [] existing_tenants = Instance.list() or []
except Exception: existing_subdomains = [
existing = [] t.get("subdomain") or t.get("tenant_slug")
for t in existing_tenants if isinstance(t, dict)
]
except Exception as e:
print(f"[WARNING] Konnte bestehende Instanzen nicht abfragen: {e}")
existing_subdomains = []
if base in existing: if base in existing_subdomains:
flash("Die gewünschte Subdomain (aus dem Schulnamen) existiert bereits. Bitte wähle einen anderen Schulnamen.", "error") flash("Die gewünschte Subdomain (aus dem Schulnamen) existiert bereits. Bitte wähle einen anderen Schulnamen.", "error")
return render_template("schnell_buchung.html", **review_data) return render_template("schnell_buchung.html", **review_data)
subdomain = base[:63] subdomain = base[:63]
provision_payload = { provision_payload = {
"user_id": session.get("user_id"),
"username": session.get("username"), "username": session.get("username"),
"booking_number": booking_number, "booking_number": booking_number,
"package": package_raw, "package": package_raw,
@@ -2405,30 +2424,28 @@ def booking_payment():
valid_modules = set(BOOKING_PACKAGE_MAP.keys()) valid_modules = set(BOOKING_PACKAGE_MAP.keys())
modules_to_provision = {pkg for pkg in package_list if pkg in valid_modules} modules_to_provision = {pkg for pkg in package_list if pkg in valid_modules}
# Flask App Context für den Thread sicher abrufen
app_obj = app
user_id = session.get("user_id")
user_email = session.get("email") or form_data.get("contact_email")
try:
thread = threading.Thread( thread = threading.Thread(
target=background_booking_provisioning, target=background_booking_provisioning,
args=(app, prov_id, subdomain, modules_to_provision, session.get("user_id"), form_data["school_name"]) args=(app_obj, prov_id, subdomain, modules_to_provision, user_id, form_data["school_name"], user_email)
) )
thread.start() thread.start()
print(f"[INFO] Background provisioning gestartet für Subdomain: {subdomain}")
except Exception as thread_err:
print(f"[ERROR] Fehler beim Starten des Background Threads: {thread_err}")
except PyMongoError: flash(f"Buchung für {selected_package} wurde erfasst. Die Instanz wird jetzt eingerichtet.", "success")
return redirect(url_for("my_instance_management"))
except PyMongoError as pe:
print(f"[ERROR] Database error in booking_payment: {pe}")
flash("Buchungsanfrage konnte nicht gesendet werden. Bitte versuchen Sie es erneut.", "error") flash("Buchungsanfrage konnte nicht gesendet werden. Bitte versuchen Sie es erneut.", "error")
return redirect(url_for("booking_payment", packages=package_raw, flow=booking_flow)) return render_template("schnell_buchung.html", **review_data)
finally:
if client:
client.close()
success_text = (
f"Gesprächstermin für {selected_package} wurde an das Team gesendet."
if booking_flow == "consultation"
else f"Buchung für {selected_package} wird eingerichtet. Sie werden gleich weitergeleitet."
)
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']) @app.route('/instance_request/delete', methods=['POST'])