132 lines
4.9 KiB
Python
132 lines
4.9 KiB
Python
"""
|
|
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("/<int:entry_id>/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("/<int:entry_id>/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
|