changes to the booking process
This commit is contained in:
+115
-98
@@ -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
|
||||||
|
success = Instance.new(subdomain, next_port, admin_password)
|
||||||
|
if not success:
|
||||||
|
raise Exception("Tenant shell creation failed (Instance.new returned False)")
|
||||||
|
|
||||||
|
# 4. Enable selected Modules
|
||||||
|
# Always enable "starter" as a baseline if needed, then the booked ones
|
||||||
|
if "starter" not in modules_to_provision:
|
||||||
|
Instance.edit(subdomain, "starter")
|
||||||
|
|
||||||
|
for mod in modules_to_provision:
|
||||||
|
Instance.edit(subdomain, mod)
|
||||||
|
|
||||||
|
# 5. Register Instance in Database
|
||||||
|
domain = f"{subdomain}.invario-software.de"
|
||||||
|
instance_doc = {
|
||||||
|
"owner_id": user_id,
|
||||||
|
"school_name": school_name,
|
||||||
|
"subdomain": subdomain,
|
||||||
|
"domain": domain,
|
||||||
|
"https_port": next_port,
|
||||||
|
"status": "ready",
|
||||||
|
"nginx_status": "active",
|
||||||
|
"admin_username": "admin",
|
||||||
|
"admin_password": admin_password
|
||||||
|
}
|
||||||
|
|
||||||
|
inst_client, inst_col = _get_collection("instances")
|
||||||
try:
|
try:
|
||||||
port = steering.instance.new(subdomain)
|
created_inst_id = str(inst_col.insert_one(instance_doc).inserted_id)
|
||||||
if port and port is not False:
|
finally:
|
||||||
try:
|
inst_client.close()
|
||||||
provision_port = int(port) if isinstance(port, (int, str)) and str(port).isdigit() else port
|
|
||||||
except Exception:
|
# 6. Send Email Notification
|
||||||
provision_port = port
|
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}")
|
||||||
|
|
||||||
# 2. Gewählte Module aktivieren
|
# 7. Mark Request as Ready for the Frontend
|
||||||
for mod in modules_to_provision:
|
|
||||||
try:
|
|
||||||
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)
|
|
||||||
if provision_config_ok and not provision_error:
|
|
||||||
domain = f"{subdomain}.invario-software.de"
|
|
||||||
instance_doc = {
|
|
||||||
"owner_id": user_id,
|
|
||||||
"school_name": school_name,
|
|
||||||
"subdomain": subdomain,
|
|
||||||
"domain": domain,
|
|
||||||
"https_port": provision_port,
|
|
||||||
"status": "ready",
|
|
||||||
"nginx_status": "active",
|
|
||||||
"admin_username": "admin",
|
|
||||||
"admin_password": admin_password
|
|
||||||
}
|
|
||||||
inst_client, inst_col = _get_collection("instances")
|
|
||||||
try:
|
|
||||||
inst_col.insert_one(instance_doc)
|
|
||||||
finally:
|
|
||||||
inst_client.close()
|
|
||||||
|
|
||||||
# 4. Finalen Status setzen ("ready" löst den Redirect im Frontend aus)
|
|
||||||
final_status = "ready" if provision_config_ok and not provision_error else "failed"
|
|
||||||
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,41 +2349,55 @@ 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")
|
||||||
col.insert_one(
|
try:
|
||||||
{
|
col.insert_one(
|
||||||
"username": session.get("username"),
|
{
|
||||||
"sender": session.get("display_name") or session.get("username"),
|
"username": session.get("username"),
|
||||||
"sender_role": "user",
|
"sender": session.get("display_name") or session.get("username"),
|
||||||
"message": message,
|
"sender_role": "user",
|
||||||
"created_at": _utc_now_iso(),
|
"message": message,
|
||||||
"booking_flow": booking_flow,
|
"created_at": _utc_now_iso(),
|
||||||
"booking_package": package_raw,
|
"booking_flow": booking_flow,
|
||||||
"booking_data": form_data,
|
"booking_package": package_raw,
|
||||||
}
|
"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}
|
||||||
|
|
||||||
thread = threading.Thread(
|
# Flask App Context für den Thread sicher abrufen
|
||||||
target=background_booking_provisioning,
|
app_obj = app
|
||||||
args=(app, prov_id, subdomain, modules_to_provision, session.get("user_id"), form_data["school_name"])
|
user_id = session.get("user_id")
|
||||||
)
|
user_email = session.get("email") or form_data.get("contact_email")
|
||||||
thread.start()
|
|
||||||
|
try:
|
||||||
|
thread = threading.Thread(
|
||||||
|
target=background_booking_provisioning,
|
||||||
|
args=(app_obj, prov_id, subdomain, modules_to_provision, user_id, form_data["school_name"], user_email)
|
||||||
|
)
|
||||||
|
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'])
|
||||||
|
|||||||
Reference in New Issue
Block a user