Files
customer-portal/customer_portal/web/routes/bookings.py

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)