""" app/tenant/waitlist/routes.py Waitlist management: list, add, notify, mark booked, expire. """ import logging from datetime import datetime, timezone from flask import Blueprint, render_template, redirect, url_for, flash, request, g from flask_login import login_required from app.extensions import db, mail from app.models.salon import Waitlist, Staff, Service from app.decorators import require_role, demo_readonly, tenant_feature_required from app.tenant.utils import log_tenant_action logger = logging.getLogger(__name__) waitlist_bp = Blueprint("waitlist", __name__, url_prefix="/waitlist") @waitlist_bp.route("/") @login_required @require_role("tenant_admin", "tenant_manager") @tenant_feature_required("waitlist") def index(): entries = Waitlist.query.filter_by( tenant_id=g.tenant.id, location_id=g.location.id ).filter( Waitlist.status.in_(["waiting", "notified"]) ).order_by(Waitlist.created_at).all() return render_template("tenant/waitlist/index.html", entries=entries) @waitlist_bp.route("/add", methods=["GET", "POST"]) @login_required @require_role("tenant_admin", "tenant_manager") @demo_readonly @tenant_feature_required("waitlist") def add(): staff_list = Staff.query.filter_by( tenant_id=g.tenant.id, is_active=True).filter( Staff.deleted_at.is_(None)).order_by(Staff.name).all() services = Service.query.filter_by( tenant_id=g.tenant.id, is_active=True).filter( Service.deleted_at.is_(None)).order_by(Service.name).all() if request.method == "POST": name = request.form.get("customer_name", "").strip() phone = request.form.get("customer_phone", "").strip() or None email_addr = request.form.get("customer_email", "").strip() or None if not name: return render_template("tenant/waitlist/form.html", staff_list=staff_list, services=services, error="Customer name is required.") entry = Waitlist( tenant_id=g.tenant.id, location_id=g.location.id, customer_name=name, customer_phone=phone, customer_email=email_addr, staff_id=request.form.get("staff_id", type=int) or None, service_id=request.form.get("service_id", type=int) or None, requested_date=_parse_date(request.form.get("requested_date", "")), status="waiting", ) db.session.add(entry) db.session.flush() log_tenant_action("waitlist.add", "waitlist", entry.id, {"name": name}) db.session.commit() flash(f"'{name}' added to waitlist.", "success") return redirect(url_for("waitlist.index")) return render_template("tenant/waitlist/form.html", staff_list=staff_list, services=services) @waitlist_bp.route("//notify", methods=["POST"]) @login_required @require_role("tenant_admin", "tenant_manager") @demo_readonly @tenant_feature_required("waitlist") def notify(entry_id): entry = Waitlist.query.filter_by( id=entry_id, tenant_id=g.tenant.id).first_or_404() entry.status = "notified" entry.notified_at = datetime.now(timezone.utc) # Send email notification if available if entry.customer_email: try: from flask_mail import Message msg = Message( subject=f"A slot is available — {g.tenant.name}", recipients=[entry.customer_email], body=( f"Hi {entry.customer_name},\n\n" "A slot has opened up for you. Please call or book online to confirm.\n\n" f"{g.tenant.name}" ), ) mail.send(msg) except Exception as exc: logger.error("Waitlist notify email failed: %s", exc) log_tenant_action("waitlist.notify", "waitlist", entry.id, {"name": entry.customer_name}) db.session.commit() flash(f"'{entry.customer_name}' notified.", "success") return redirect(url_for("waitlist.index")) @waitlist_bp.route("//set-status", methods=["POST"]) @login_required @require_role("tenant_admin", "tenant_manager") @demo_readonly @tenant_feature_required("waitlist") def set_status(entry_id): entry = Waitlist.query.filter_by( id=entry_id, tenant_id=g.tenant.id).first_or_404() new_status = request.form.get("status", "") if new_status in ("booked", "expired"): entry.status = new_status log_tenant_action("waitlist.set_status", "waitlist", entry.id, {"status": new_status}) db.session.commit() flash(f"Entry updated to '{new_status}'.", "success") return redirect(url_for("waitlist.index")) def _parse_date(value): if not value: return None try: from datetime import date return date.fromisoformat(value) except ValueError: return None