177 lines
5.3 KiB
Python
Executable File
177 lines
5.3 KiB
Python
Executable File
"""Bookings routes.
|
|
|
|
Sprint 14: Added local database storage for bookings.
|
|
Bookings can be synced from WordPress and stored locally.
|
|
"""
|
|
|
|
import logging
|
|
|
|
from flask import (
|
|
Blueprint,
|
|
current_app,
|
|
flash,
|
|
g,
|
|
redirect,
|
|
render_template,
|
|
request,
|
|
url_for,
|
|
)
|
|
|
|
from customer_portal.models import get_db
|
|
from customer_portal.models.booking import Booking
|
|
from customer_portal.services.token import TokenService
|
|
from customer_portal.services.wordpress_api import WordPressAPI
|
|
from customer_portal.web.routes.auth import login_required
|
|
|
|
bp = Blueprint("bookings", __name__)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
@bp.route("/")
|
|
@login_required
|
|
def list_bookings():
|
|
"""List customer bookings from local database.
|
|
|
|
Sprint 14: Now uses local database instead of WordPress API.
|
|
Bookings are synced via admin panel.
|
|
"""
|
|
db = get_db()
|
|
|
|
# Get filter
|
|
status_filter = request.args.get("status", "")
|
|
|
|
# Get bookings from local database
|
|
query = db.query(Booking).filter(Booking.customer_id == g.customer.id)
|
|
|
|
if status_filter:
|
|
query = query.filter(Booking.status == status_filter)
|
|
|
|
bookings = query.order_by(Booking.kurs_date.desc()).all()
|
|
|
|
return render_template(
|
|
"bookings/list.html",
|
|
bookings=bookings,
|
|
status_filter=status_filter,
|
|
use_local_db=True,
|
|
)
|
|
|
|
|
|
@bp.route("/<int:booking_id>")
|
|
@login_required
|
|
def detail(booking_id: int):
|
|
"""Booking detail page from local database.
|
|
|
|
Sprint 14: Now uses local database.
|
|
"""
|
|
db = get_db()
|
|
|
|
booking = (
|
|
db.query(Booking)
|
|
.filter(
|
|
Booking.id == booking_id,
|
|
Booking.customer_id == g.customer.id,
|
|
)
|
|
.first()
|
|
)
|
|
|
|
if not booking:
|
|
flash("Buchung nicht gefunden oder kein Zugriff.", "error")
|
|
return redirect(url_for("bookings.list_bookings"))
|
|
|
|
return render_template("bookings/detail.html", booking=booking, use_local_db=True)
|
|
|
|
|
|
@bp.route("/<int:booking_id>/cancel", methods=["GET", "POST"])
|
|
@login_required
|
|
def cancel(booking_id: int):
|
|
"""Request booking cancellation.
|
|
|
|
Sprint 14: Uses local database for booking lookup,
|
|
but still calls WordPress API for the actual cancellation.
|
|
"""
|
|
db = get_db()
|
|
|
|
# Get booking from local database
|
|
booking = (
|
|
db.query(Booking)
|
|
.filter(
|
|
Booking.id == booking_id,
|
|
Booking.customer_id == g.customer.id,
|
|
)
|
|
.first()
|
|
)
|
|
|
|
if not booking:
|
|
flash("Buchung nicht gefunden oder kein Zugriff.", "error")
|
|
return redirect(url_for("bookings.list_bookings"))
|
|
|
|
# Check if already cancelled or pending
|
|
if booking.status in ("cancelled", "cancel_requested"):
|
|
flash("Diese Buchung wurde bereits storniert oder ist in Bearbeitung.", "info")
|
|
return redirect(url_for("bookings.detail", booking_id=booking_id))
|
|
|
|
if request.method == "POST":
|
|
reason = request.form.get("reason", "").strip()
|
|
|
|
try:
|
|
# Call WordPress API to cancel (uses wp_booking_id)
|
|
result = WordPressAPI.cancel_booking(
|
|
booking.wp_booking_id, g.customer.email, reason
|
|
)
|
|
|
|
if result.get("success"):
|
|
# Update local status
|
|
booking.status = "cancel_requested"
|
|
db.commit()
|
|
|
|
flash(
|
|
"Stornierungsanfrage wurde gesendet. Sie erhalten eine Bestätigung per E-Mail.",
|
|
"success",
|
|
)
|
|
return redirect(url_for("bookings.detail", booking_id=booking_id))
|
|
else:
|
|
error = result.get("error", "Unbekannter Fehler")
|
|
flash(f"Stornierung fehlgeschlagen: {error}", "error")
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error cancelling booking {booking_id}: {e}")
|
|
flash("Stornierung konnte nicht durchgeführt werden.", "error")
|
|
|
|
return render_template("bookings/cancel.html", booking=booking, use_local_db=True)
|
|
|
|
|
|
@bp.route("/book/<int:kurs_id>")
|
|
@login_required
|
|
def book_kurs(kurs_id: int):
|
|
"""Redirect to WordPress booking with pre-filled customer data.
|
|
|
|
Generates a signed token containing customer data and redirects
|
|
to the WordPress kurs page with the token as URL parameter.
|
|
WordPress validates the token and pre-fills the booking form.
|
|
|
|
Args:
|
|
kurs_id: WordPress post ID of the kurs to book
|
|
"""
|
|
try:
|
|
token = TokenService.generate_prefill_token(g.customer)
|
|
except ValueError as e:
|
|
logger.error(f"Token generation failed: {e}")
|
|
flash("Verbindung zu WordPress nicht konfiguriert.", "error")
|
|
return redirect(url_for("bookings.list_bookings"))
|
|
|
|
# Get WordPress base URL (remove API path).
|
|
wp_api_url = current_app.config.get("WP_API_URL", "")
|
|
wp_base_url = wp_api_url.replace("/wp-json/kurs-booking/v1", "")
|
|
|
|
if not wp_base_url:
|
|
logger.error("WP_API_URL not configured")
|
|
flash("WordPress-URL nicht konfiguriert.", "error")
|
|
return redirect(url_for("bookings.list_bookings"))
|
|
|
|
# Build URL to kurs page with prefill token.
|
|
# Using ?p=ID format for compatibility with any permalink structure.
|
|
booking_url = f"{wp_base_url}/?p={kurs_id}&kb_prefill={token}"
|
|
|
|
logger.info(f"Redirecting customer {g.customer.id} to book kurs {kurs_id}")
|
|
return redirect(booking_url)
|