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

120 lines
4.1 KiB
Python

"""
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("/<int:card_id>/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