05/07 Phase 3: initial codes
This commit is contained in:
@@ -1,7 +1,119 @@
|
||||
"""
|
||||
app/tenant/gift_cards/routes.py
|
||||
Phase 3+ implementation.
|
||||
Gift card issuance, management, and balance enquiry.
|
||||
"""
|
||||
from flask import Blueprint
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user