""" app/tenant/customers/routes.py Customer management: list (search), create, view, edit, soft-delete, restore. """ import logging from flask import Blueprint, render_template, redirect, url_for, flash, request, g from flask_login import login_required from app.extensions import db from app.models.salon import Customer, Appointment, Transaction from app.decorators import require_role, demo_readonly from app.tenant.utils import log_tenant_action logger = logging.getLogger(__name__) customers_bp = Blueprint("customers", __name__, url_prefix="/customers") @customers_bp.route("/") @login_required @require_role("tenant_admin", "tenant_manager") def index(): q = request.args.get("q", "").strip() query = Customer.query.filter_by(tenant_id=g.tenant.id).filter( Customer.deleted_at.is_(None)) if q: query = query.filter( db.or_( Customer.name.ilike(f"%{q}%"), Customer.phone.ilike(f"%{q}%"), Customer.email.ilike(f"%{q}%"), ) ) customers = query.order_by(Customer.name).limit(200).all() return render_template("tenant/customers/index.html", customers=customers, q=q) @customers_bp.route("/new", methods=["GET", "POST"]) @login_required @require_role("tenant_admin", "tenant_manager") @demo_readonly def create(): if request.method == "POST": name = request.form.get("name", "").strip() phone = request.form.get("phone", "").strip() or None if not name: return render_template("tenant/customers/form.html", mode="create", error="Name is required.") customer = Customer( tenant_id=g.tenant.id, name=name, phone=phone, email=request.form.get("email", "").strip() or None, date_of_birth=_parse_date(request.form.get("date_of_birth", "")), notes=request.form.get("notes", "").strip() or None, is_active=True, loyalty_points=0, no_show_count=0, ) db.session.add(customer) db.session.flush() log_tenant_action("customer.create", "customer", customer.id, {"name": name}) db.session.commit() flash(f"Customer \'{name}\' created.", "success") return redirect(url_for("customers.view", customer_id=customer.id)) return render_template("tenant/customers/form.html", mode="create") @customers_bp.route("/") @login_required @require_role("tenant_admin", "tenant_manager") def view(customer_id): customer = Customer.query.filter_by( id=customer_id, tenant_id=g.tenant.id).filter( Customer.deleted_at.is_(None)).first_or_404() appointments = Appointment.query.filter_by( tenant_id=g.tenant.id, customer_id=customer_id ).order_by(Appointment.start_time.desc()).limit(20).all() transactions = Transaction.query.filter_by( tenant_id=g.tenant.id, customer_id=customer_id ).filter(Transaction.voided_at.is_(None)).order_by( Transaction.created_at.desc()).limit(20).all() return render_template("tenant/customers/view.html", customer=customer, appointments=appointments, transactions=transactions) @customers_bp.route("//edit", methods=["GET", "POST"]) @login_required @require_role("tenant_admin", "tenant_manager") @demo_readonly def edit(customer_id): customer = Customer.query.filter_by( id=customer_id, tenant_id=g.tenant.id).filter( Customer.deleted_at.is_(None)).first_or_404() if request.method == "POST": name = request.form.get("name", "").strip() if not name: return render_template("tenant/customers/form.html", mode="edit", customer=customer, error="Name is required.") customer.name = name customer.phone = request.form.get("phone", "").strip() or None customer.email = request.form.get("email", "").strip() or None customer.date_of_birth = _parse_date(request.form.get("date_of_birth", "")) customer.notes = request.form.get("notes", "").strip() or None log_tenant_action("customer.edit", "customer", customer.id, {"name": name}) db.session.commit() flash(f"Customer \'{name}\' updated.", "success") return redirect(url_for("customers.view", customer_id=customer.id)) return render_template("tenant/customers/form.html", mode="edit", customer=customer) @customers_bp.route("//delete", methods=["POST"]) @login_required @require_role("tenant_admin") @demo_readonly def soft_delete(customer_id): customer = Customer.query.filter_by( id=customer_id, tenant_id=g.tenant.id).filter( Customer.deleted_at.is_(None)).first_or_404() from datetime import datetime, timezone customer.deleted_at = datetime.now(timezone.utc) log_tenant_action("customer.delete", "customer", customer.id, {"name": customer.name}) db.session.commit() flash(f"Customer \'{customer.name}\' deleted.", "success") return redirect(url_for("customers.index")) def _parse_date(value): if not value: return None try: from datetime import date return date.fromisoformat(value) except ValueError: return None