Files
MyPOS/app/tenant/customers/routes.py
T
2026-05-07 12:17:19 -04:00

136 lines
5.3 KiB
Python

"""
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("/<int:customer_id>")
@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("/<int:customer_id>/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("/<int:customer_id>/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