""" app/tenant/gift_cards/routes.py Gift card issuance, management, and balance enquiry. """ import logging import secrets import string 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 from app.models.salon import GiftCard, Customer from app.decorators import require_role, demo_readonly from app.tenant.utils import log_tenant_action logger = logging.getLogger(__name__) gift_cards_bp = Blueprint("gift_cards", __name__, url_prefix="/gift-cards") def _generate_code(tenant_id: int) -> str: """Generate a unique gift card code for this tenant.""" alphabet = string.ascii_uppercase + string.digits for _ in range(20): code = "".join(secrets.choice(alphabet) for _ in range(12)) code = f"{code[:4]}-{code[4:8]}-{code[8:12]}" if not GiftCard.query.filter_by(tenant_id=tenant_id, code=code).first(): return code raise RuntimeError("Failed to generate unique gift card code") @gift_cards_bp.route("/") @login_required @require_role("tenant_admin", "tenant_manager") def index(): cards = GiftCard.query.filter_by( tenant_id=g.tenant.id ).order_by(GiftCard.created_at.desc()).limit(200).all() return render_template("tenant/gift_cards/index.html", cards=cards) @gift_cards_bp.route("/issue", methods=["GET", "POST"]) @login_required @require_role("tenant_admin", "tenant_manager") @demo_readonly def issue(): customers = Customer.query.filter_by( tenant_id=g.tenant.id, is_active=True).filter( Customer.deleted_at.is_(None)).order_by(Customer.name).limit(500).all() if request.method == "POST": try: value = float(request.form.get("value", 0)) assert value > 0 except (ValueError, AssertionError): return render_template("tenant/gift_cards/form.html", customers=customers, error="Value must be greater than 0.") customer_id = request.form.get("customer_id", type=int) or None expires_raw = request.form.get("expires_at", "").strip() expires_at = None if expires_raw: try: expires_at = datetime.fromisoformat(expires_raw) except ValueError: pass code = _generate_code(g.tenant.id) card = GiftCard( tenant_id=g.tenant.id, code=code, original_value=value, remaining_balance=value, issued_by=_user_db_id(), issued_to_customer_id=customer_id, expires_at=expires_at, is_active=True, ) db.session.add(card) db.session.flush() log_tenant_action("gift_card.issue", "gift_card", card.id, {"code": code, "value": value}) db.session.commit() flash(f"Gift card issued: {code} (${value:.2f})", "success") return redirect(url_for("gift_cards.index")) return render_template("tenant/gift_cards/form.html", customers=customers) @gift_cards_bp.route("/lookup") @login_required @require_role("tenant_admin", "tenant_manager") def lookup(): code = request.args.get("code", "").strip().upper() card = None if code: card = GiftCard.query.filter_by( tenant_id=g.tenant.id, code=code).first() return render_template("tenant/gift_cards/lookup.html", card=card, code=code) @gift_cards_bp.route("//deactivate", methods=["POST"]) @login_required @require_role("tenant_admin") @demo_readonly def deactivate(card_id): card = GiftCard.query.filter_by( id=card_id, tenant_id=g.tenant.id).first_or_404() card.is_active = False log_tenant_action("gift_card.deactivate", "gift_card", card.id, {"code": card.code}) db.session.commit() flash(f"Gift card {card.code} deactivated.", "success") return redirect(url_for("gift_cards.index")) def _user_db_id(): from flask_login import current_user try: uid_str = current_user.get_id() return int(uid_str.split(":")[1]) if uid_str and ":" in uid_str else None except Exception: return None