05/07 Phase 3: initial codes
This commit is contained in:
@@ -1,7 +1,131 @@
|
||||
"""
|
||||
app/tenant/waitlist/routes.py
|
||||
Phase 3+ implementation.
|
||||
Waitlist management: list, add, notify, mark booked, expire.
|
||||
"""
|
||||
from flask import Blueprint
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user