"""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("/") @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("//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/") @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)