""" app/tenant/checkin/routes.py Customer self check-in kiosk — /checkin/ No authentication required. CSRF-exempt. Rate-limited. Receptionist alert delivered via 5-second polling: GET /api/v1/checkin/queue?status=waiting """ import logging from datetime import datetime, timezone from flask import Blueprint, render_template, request, redirect, url_for, g from app.extensions import db, limiter, csrf from app.models.platform import Tenant from app.models.salon import CheckinQueue, Customer, Service, Location logger = logging.getLogger(__name__) checkin_bp = Blueprint("checkin", __name__) @checkin_bp.route("/checkin/", methods=["GET", "POST"]) @limiter.limit("20 per minute") @csrf.exempt def kiosk(tenant_slug): # Validate slug against known tenants — prevents enumeration tenant = Tenant.query.filter_by(slug=tenant_slug).filter( Tenant.status.in_(["active", "trial"]) ).first() if not tenant: return render_template("tenant/checkin/not_found.html"), 404 # Resolve location: session-stored or primary location = Location.query.filter_by( tenant_id=tenant.id, is_primary=True, is_active=True ).first() if not location: location = Location.query.filter_by( tenant_id=tenant.id, is_active=True ).first() if not location: return render_template("tenant/checkin/not_found.html"), 404 services = Service.query.filter_by( tenant_id=tenant.id, is_active=True).filter( Service.deleted_at.is_(None)).order_by(Service.name).all() confirmed = False error = None if request.method == "POST": # Accept ONLY these three fields — all others ignored customer_name = request.form.get("customer_name", "").strip()[:255] customer_phone = request.form.get("customer_phone", "").strip()[:30] service_requested = request.form.get("service_requested", "").strip()[:255] or None if not customer_name or not customer_phone: error = "Please enter your name and phone number." else: # Look up or create customer profile customer = Customer.query.filter_by( tenant_id=tenant.id, phone=customer_phone ).filter(Customer.deleted_at.is_(None)).first() customer_id = None if customer: customer_id = customer.id else: # Auto-create profile new_cust = Customer( tenant_id=tenant.id, name=customer_name, phone=customer_phone, is_active=True, loyalty_points=0, no_show_count=0, ) db.session.add(new_cust) db.session.flush() customer_id = new_cust.id logger.info("Kiosk: new customer profile created tenant=%s phone=%s", tenant.id, customer_phone) entry = CheckinQueue( tenant_id=tenant.id, location_id=location.id, customer_id=customer_id, customer_name=customer_name, customer_phone=customer_phone, service_requested=service_requested, checked_in_at=datetime.now(timezone.utc), status="waiting", ) db.session.add(entry) db.session.commit() logger.info("Kiosk check-in: tenant=%s location=%s customer=%s service=%s", tenant.id, location.id, customer_id, service_requested) confirmed = True return render_template( "tenant/checkin/kiosk.html", tenant=tenant, services=services, confirmed=confirmed, error=error, )