The new updates of partners and user
Made-with: Cursor
This commit is contained in:
101
bookings/migrations/0001_initial.py
Normal file
101
bookings/migrations/0001_initial.py
Normal file
@@ -0,0 +1,101 @@
|
||||
# Generated by Django 4.2.27 on 2026-03-13 16:55
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('events', '0007_event_gst_percentage_1_event_gst_percentage_2_and_more'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Booking',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('booking_id', models.CharField(max_length=250)),
|
||||
('quantity', models.IntegerField()),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||
('created_date', models.DateField(auto_now_add=True)),
|
||||
('updated_date', models.DateField(auto_now=True)),
|
||||
('transaction_id', models.CharField(blank=True, max_length=250, null=True)),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TicketMeta',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('ticket_name', models.CharField(max_length=250)),
|
||||
('maximum_quantity', models.IntegerField()),
|
||||
('available_quantity', models.IntegerField(default=0)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created_date', models.DateField(auto_now_add=True)),
|
||||
('updated_date', models.DateField(auto_now=True)),
|
||||
('event', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='events.event')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='TicketType',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('ticket_type', models.CharField(max_length=250)),
|
||||
('ticket_type_description', models.TextField()),
|
||||
('ticket_type_quantity', models.IntegerField()),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created_date', models.DateField(auto_now_add=True)),
|
||||
('updated_date', models.DateField(auto_now=True)),
|
||||
('is_offer', models.BooleanField(default=False)),
|
||||
('offer_percentage', models.IntegerField(default=0)),
|
||||
('offer_price', models.DecimalField(decimal_places=2, default=0, max_digits=10)),
|
||||
('offer_start_date', models.DateField(blank=True, null=True)),
|
||||
('offer_end_date', models.DateField(blank=True, null=True)),
|
||||
('ticket_meta', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookings.ticketmeta')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Ticket',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('ticket_id', models.CharField(max_length=250)),
|
||||
('is_checked_in', models.BooleanField(default=False)),
|
||||
('checked_in_date_time', models.DateTimeField(blank=True, null=True)),
|
||||
('booking', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookings.booking')),
|
||||
],
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Cart',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('quantity', models.IntegerField()),
|
||||
('price', models.DecimalField(decimal_places=2, max_digits=10)),
|
||||
('is_active', models.BooleanField(default=True)),
|
||||
('created_date', models.DateField(auto_now_add=True)),
|
||||
('updated_date', models.DateField(auto_now=True)),
|
||||
('ticket_meta', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookings.ticketmeta')),
|
||||
('ticket_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookings.tickettype')),
|
||||
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
||||
],
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='booking',
|
||||
name='ticket_meta',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookings.ticketmeta'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='booking',
|
||||
name='ticket_type',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='bookings.tickettype'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='booking',
|
||||
name='user',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
|
||||
),
|
||||
]
|
||||
@@ -4,10 +4,9 @@ from events.models import Event
|
||||
from accounts.models import User
|
||||
|
||||
# Create your models here.
|
||||
class Ticket(models.Model):
|
||||
class TicketMeta(models.Model):
|
||||
event = models.ForeignKey(Event, on_delete=models.CASCADE)
|
||||
ticket_name = models.CharField(max_length=250)
|
||||
price_per_ticket = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
maximum_quantity = models.IntegerField()
|
||||
available_quantity = models.IntegerField(default=0)
|
||||
is_active = models.BooleanField(default=True)
|
||||
@@ -19,11 +18,13 @@ class Ticket(models.Model):
|
||||
|
||||
|
||||
class TicketType(models.Model):
|
||||
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE)
|
||||
ticket_meta = models.ForeignKey(TicketMeta, on_delete=models.CASCADE)
|
||||
ticket_type = models.CharField(max_length=250)
|
||||
ticket_type_description = models.TextField()
|
||||
quantity = models.IntegerField()
|
||||
ticket_type_quantity = models.IntegerField()
|
||||
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
|
||||
is_active = models.BooleanField(default=True)
|
||||
created_date = models.DateField(auto_now_add=True)
|
||||
updated_date = models.DateField(auto_now=True)
|
||||
@@ -41,10 +42,11 @@ class TicketType(models.Model):
|
||||
|
||||
class Cart(models.Model):
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE)
|
||||
ticket_meta = models.ForeignKey(TicketMeta, on_delete=models.CASCADE)
|
||||
ticket_type = models.ForeignKey(TicketType, on_delete=models.CASCADE)
|
||||
quantity = models.IntegerField()
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
is_active = models.BooleanField(default=True)
|
||||
created_date = models.DateField(auto_now_add=True)
|
||||
updated_date = models.DateField(auto_now=True)
|
||||
|
||||
@@ -55,7 +57,7 @@ class Cart(models.Model):
|
||||
class Booking(models.Model):
|
||||
booking_id = models.CharField(max_length=250)
|
||||
user = models.ForeignKey(User, on_delete=models.CASCADE)
|
||||
ticket = models.ForeignKey(Ticket, on_delete=models.CASCADE)
|
||||
ticket_meta = models.ForeignKey(TicketMeta, on_delete=models.CASCADE)
|
||||
ticket_type = models.ForeignKey(TicketType, on_delete=models.CASCADE)
|
||||
quantity = models.IntegerField()
|
||||
price = models.DecimalField(max_digits=10, decimal_places=2)
|
||||
@@ -70,4 +72,26 @@ class Booking(models.Model):
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return self.booking_id
|
||||
return self.booking_id
|
||||
|
||||
class Ticket(models.Model):
|
||||
booking = models.ForeignKey(Booking, on_delete=models.CASCADE)
|
||||
ticket_id = models.CharField(max_length=250)
|
||||
is_checked_in = models.BooleanField(default=False)
|
||||
checked_in_date_time = models.DateTimeField(blank=True, null=True)
|
||||
|
||||
def __save__(self):
|
||||
if not self.ticket_id:
|
||||
self.ticket_id = str(self.booking.ticket_meta.event.name[:3].upper()) + str(uuid.uuid4().hex[:10]).upper()
|
||||
super().save(*args, **kwargs)
|
||||
|
||||
def __str__(self):
|
||||
return self.ticket_id
|
||||
|
||||
def check_in(self, ticket_id):
|
||||
if self.ticket_id == ticket_id:
|
||||
self.is_checked_in = True
|
||||
self.checked_in_date_time = datetime.now()
|
||||
self.save()
|
||||
return True
|
||||
return False
|
||||
61
bookings/services.py
Normal file
61
bookings/services.py
Normal file
@@ -0,0 +1,61 @@
|
||||
from typing import List
|
||||
import uuid
|
||||
|
||||
from django.utils import timezone
|
||||
|
||||
from bookings.models import Booking, Ticket
|
||||
|
||||
|
||||
def _generate_ticket_id(booking: Booking) -> str:
|
||||
"""
|
||||
Generate a ticket_id based on the event name and a random UUID segment.
|
||||
|
||||
Pattern: <EVT><RANDOM_HEX>
|
||||
- EVT: first 3 characters of event name (uppercase), or 'EVT' fallback
|
||||
- RANDOM_HEX: first 10 chars of uuid4 hex (uppercase)
|
||||
"""
|
||||
event = getattr(booking.ticket_meta, "event", None)
|
||||
if event and getattr(event, "name", None):
|
||||
prefix = (event.name or "EVT")[:3].upper()
|
||||
else:
|
||||
prefix = "EVT"
|
||||
|
||||
return prefix + uuid.uuid4().hex[:10].upper()
|
||||
|
||||
|
||||
def generate_tickets_for_booking(booking: Booking) -> List[Ticket]:
|
||||
"""
|
||||
Generate Ticket instances for a given Booking based on its quantity.
|
||||
|
||||
This function does NOT perform any payment or business-rule validation.
|
||||
It simply creates one Ticket per quantity on the booking.
|
||||
|
||||
Args:
|
||||
booking: Booking instance for which tickets should be generated.
|
||||
|
||||
Returns:
|
||||
List[Ticket]: List of created Ticket instances.
|
||||
"""
|
||||
if not isinstance(booking, Booking):
|
||||
raise TypeError("booking must be a Booking instance")
|
||||
|
||||
if booking.quantity <= 0:
|
||||
return []
|
||||
|
||||
tickets: List[Ticket] = []
|
||||
for _ in range(booking.quantity):
|
||||
tickets.append(
|
||||
Ticket(
|
||||
booking=booking,
|
||||
ticket_id=_generate_ticket_id(booking),
|
||||
is_checked_in=False,
|
||||
checked_in_date_time=None,
|
||||
)
|
||||
)
|
||||
|
||||
# Bulk create for efficiency
|
||||
Ticket.objects.bulk_create(tickets)
|
||||
|
||||
# Refresh from DB to ensure we have primary keys and any defaults
|
||||
return list[Ticket](Ticket.objects.filter(booking=booking).order_by("id"))
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
from . import ticket_meta_type
|
||||
from . import booking_api
|
||||
@@ -1,301 +0,0 @@
|
||||
from django.http import JsonResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.forms.models import model_to_dict
|
||||
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from bookings.models import Ticket
|
||||
from events.models import Event
|
||||
from mobile_api.utils import validate_token_and_get_user
|
||||
|
||||
|
||||
def _ticket_to_dict(ticket):
|
||||
"""
|
||||
Helper to serialise a Ticket instance into a simple dict suitable for JSON.
|
||||
"""
|
||||
data = model_to_dict(
|
||||
ticket,
|
||||
fields=[
|
||||
"id",
|
||||
"ticket_name",
|
||||
"price_per_ticket",
|
||||
"maximum_quantity",
|
||||
"available_quantity",
|
||||
"is_active",
|
||||
"created_date",
|
||||
"updated_date",
|
||||
],
|
||||
)
|
||||
data["event_id"] = ticket.event_id
|
||||
return data
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class TicketCreateAPI(APIView):
|
||||
"""
|
||||
Create a new Ticket.
|
||||
|
||||
Expected JSON body (along with token & username used across mobile_api):
|
||||
{
|
||||
"token": "...",
|
||||
"username": "...",
|
||||
"event_id": 1,
|
||||
"ticket_name": "VIP",
|
||||
"price_per_ticket": 1000.0,
|
||||
"maximum_quantity": 50,
|
||||
"available_quantity": 50, # optional, defaults to maximum_quantity
|
||||
"is_active": true # optional, defaults to true
|
||||
}
|
||||
"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
event_id = data.get("event_id")
|
||||
ticket_name = data.get("ticket_name")
|
||||
price_per_ticket = data.get("price_per_ticket")
|
||||
maximum_quantity = data.get("maximum_quantity")
|
||||
available_quantity = data.get("available_quantity")
|
||||
is_active = data.get("is_active", True)
|
||||
|
||||
if not event_id or not ticket_name or price_per_ticket is None or maximum_quantity is None:
|
||||
return JsonResponse(
|
||||
{
|
||||
"status": "error",
|
||||
"message": "event_id, ticket_name, price_per_ticket and maximum_quantity are required.",
|
||||
},
|
||||
status=400,
|
||||
)
|
||||
|
||||
try:
|
||||
event = Event.objects.get(id=event_id)
|
||||
except Event.DoesNotExist:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "Event not found."},
|
||||
status=404,
|
||||
)
|
||||
|
||||
try:
|
||||
price_per_ticket = float(price_per_ticket)
|
||||
maximum_quantity = int(maximum_quantity)
|
||||
if available_quantity is not None:
|
||||
available_quantity = int(available_quantity)
|
||||
else:
|
||||
available_quantity = maximum_quantity
|
||||
except (TypeError, ValueError):
|
||||
return JsonResponse(
|
||||
{
|
||||
"status": "error",
|
||||
"message": "price_per_ticket, maximum_quantity and available_quantity must be numeric.",
|
||||
},
|
||||
status=400,
|
||||
)
|
||||
|
||||
if maximum_quantity <= 0:
|
||||
return JsonResponse(
|
||||
{
|
||||
"status": "error",
|
||||
"message": "maximum_quantity must be greater than zero.",
|
||||
},
|
||||
status=400,
|
||||
)
|
||||
|
||||
ticket = Ticket.objects.create(
|
||||
event=event,
|
||||
ticket_name=ticket_name,
|
||||
price_per_ticket=price_per_ticket,
|
||||
maximum_quantity=maximum_quantity,
|
||||
available_quantity=available_quantity,
|
||||
is_active=bool(is_active),
|
||||
)
|
||||
|
||||
return JsonResponse(
|
||||
{"status": "success", "ticket": _ticket_to_dict(ticket)},
|
||||
status=201,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": str(e)},
|
||||
status=500,
|
||||
)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class TicketListAPI(APIView):
|
||||
"""
|
||||
List tickets, optionally filtered by event_id.
|
||||
|
||||
Expected JSON body:
|
||||
{
|
||||
"token": "...",
|
||||
"username": "...",
|
||||
"event_id": 1 # optional
|
||||
}
|
||||
"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
event_id = data.get("event_id")
|
||||
|
||||
tickets_qs = Ticket.objects.all().order_by("-created_date")
|
||||
if event_id:
|
||||
tickets_qs = tickets_qs.filter(event_id=event_id)
|
||||
|
||||
tickets = [_ticket_to_dict(t) for t in tickets_qs]
|
||||
|
||||
return JsonResponse(
|
||||
{"status": "success", "tickets": tickets},
|
||||
status=200,
|
||||
)
|
||||
except Exception as e:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": str(e)},
|
||||
status=500,
|
||||
)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class TicketUpdateAPI(APIView):
|
||||
"""
|
||||
Update an existing Ticket.
|
||||
|
||||
Expected JSON body:
|
||||
{
|
||||
"token": "...",
|
||||
"username": "...",
|
||||
"ticket_id": 1,
|
||||
"ticket_name": "...", # optional
|
||||
"price_per_ticket": 1000.0, # optional
|
||||
"maximum_quantity": 50, # optional
|
||||
"available_quantity": 50, # optional
|
||||
"is_active": true # optional
|
||||
}
|
||||
"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
ticket_id = data.get("ticket_id")
|
||||
if not ticket_id:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "ticket_id is required."},
|
||||
status=400,
|
||||
)
|
||||
|
||||
try:
|
||||
ticket = Ticket.objects.get(id=ticket_id)
|
||||
except Ticket.DoesNotExist:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "Ticket not found."},
|
||||
status=404,
|
||||
)
|
||||
|
||||
# Optional updates
|
||||
ticket_name = data.get("ticket_name")
|
||||
price_per_ticket = data.get("price_per_ticket")
|
||||
maximum_quantity = data.get("maximum_quantity")
|
||||
available_quantity = data.get("available_quantity")
|
||||
is_active = data.get("is_active")
|
||||
|
||||
if ticket_name is not None:
|
||||
ticket.ticket_name = ticket_name
|
||||
|
||||
try:
|
||||
if price_per_ticket is not None:
|
||||
ticket.price_per_ticket = float(price_per_ticket)
|
||||
if maximum_quantity is not None:
|
||||
maximum_quantity = int(maximum_quantity)
|
||||
if maximum_quantity <= 0:
|
||||
return JsonResponse(
|
||||
{
|
||||
"status": "error",
|
||||
"message": "maximum_quantity must be greater than zero.",
|
||||
},
|
||||
status=400,
|
||||
)
|
||||
ticket.maximum_quantity = maximum_quantity
|
||||
if available_quantity is not None:
|
||||
ticket.available_quantity = int(available_quantity)
|
||||
except (TypeError, ValueError):
|
||||
return JsonResponse(
|
||||
{
|
||||
"status": "error",
|
||||
"message": "price_per_ticket, maximum_quantity and available_quantity must be numeric.",
|
||||
},
|
||||
status=400,
|
||||
)
|
||||
|
||||
if is_active is not None:
|
||||
ticket.is_active = bool(is_active)
|
||||
|
||||
ticket.save()
|
||||
|
||||
return JsonResponse(
|
||||
{"status": "success", "ticket": _ticket_to_dict(ticket)},
|
||||
status=200,
|
||||
)
|
||||
except Exception as e:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": str(e)},
|
||||
status=500,
|
||||
)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class TicketDeleteAPI(APIView):
|
||||
"""
|
||||
Delete an existing Ticket.
|
||||
|
||||
Expected JSON body:
|
||||
{
|
||||
"token": "...",
|
||||
"username": "...",
|
||||
"ticket_id": 1
|
||||
}
|
||||
"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
ticket_id = data.get("ticket_id")
|
||||
if not ticket_id:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "ticket_id is required."},
|
||||
status=400,
|
||||
)
|
||||
|
||||
try:
|
||||
ticket = Ticket.objects.get(id=ticket_id)
|
||||
except Ticket.DoesNotExist:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "Ticket not found."},
|
||||
status=404,
|
||||
)
|
||||
|
||||
ticket.delete()
|
||||
|
||||
return JsonResponse(
|
||||
{"status": "success", "message": "Ticket deleted successfully."},
|
||||
status=200,
|
||||
)
|
||||
except Exception as e:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": str(e)},
|
||||
status=500,
|
||||
)
|
||||
|
||||
369
bookings/tickets_view/booking_api.py
Normal file
369
bookings/tickets_view/booking_api.py
Normal file
@@ -0,0 +1,369 @@
|
||||
import uuid
|
||||
from decimal import Decimal
|
||||
from django.http import JsonResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.forms.models import model_to_dict
|
||||
from django.utils import timezone
|
||||
from datetime import date
|
||||
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from bookings.models import Cart, TicketType, TicketMeta, Booking, Ticket
|
||||
from mobile_api.utils import validate_token_and_get_user
|
||||
from banking_operations.services import transaction_initiate
|
||||
from eventify_logger.services import log
|
||||
|
||||
|
||||
def _cart_to_dict(cart):
|
||||
"""Serialize Cart for JSON."""
|
||||
data = model_to_dict(
|
||||
cart,
|
||||
fields=["id", "quantity", "price", "created_date", "updated_date"],
|
||||
)
|
||||
data["user_id"] = cart.user_id
|
||||
data["ticket_meta_id"] = cart.ticket_meta_id
|
||||
data["ticket_type_id"] = cart.ticket_type_id
|
||||
return data
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class AddToCartAPI(APIView):
|
||||
"""
|
||||
Add TicketType to Cart (when customer clicks plus button).
|
||||
Body: token, username, ticket_type_id, quantity (optional, defaults to 1).
|
||||
|
||||
If cart item already exists for this user + ticket_type, quantity is incremented.
|
||||
Price is taken from TicketType (offer_price if offer is active, else price).
|
||||
"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
ticket_type_id = data.get("ticket_type_id")
|
||||
quantity = data.get("quantity", 1) # Default to 1 for plus button click
|
||||
|
||||
if not ticket_type_id:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "ticket_type_id is required."},
|
||||
status=400,
|
||||
)
|
||||
|
||||
try:
|
||||
ticket_type = TicketType.objects.select_related("ticket_meta").get(id=ticket_type_id)
|
||||
except TicketType.DoesNotExist:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "TicketType not found."},
|
||||
status=404,
|
||||
)
|
||||
|
||||
if not ticket_type.is_active:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "TicketType is not active."},
|
||||
status=400,
|
||||
)
|
||||
|
||||
try:
|
||||
quantity = int(quantity)
|
||||
if quantity <= 0:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "quantity must be greater than zero."},
|
||||
status=400,
|
||||
)
|
||||
except (TypeError, ValueError):
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "quantity must be a positive integer."},
|
||||
status=400,
|
||||
)
|
||||
|
||||
# Determine price: use offer_price if offer is active, else regular price
|
||||
price = ticket_type.price
|
||||
if ticket_type.is_offer:
|
||||
# Check if offer is currently valid (if dates are set)
|
||||
today = date.today()
|
||||
offer_valid = True
|
||||
if ticket_type.offer_start_date and ticket_type.offer_start_date > today:
|
||||
offer_valid = False
|
||||
if ticket_type.offer_end_date and ticket_type.offer_end_date < today:
|
||||
offer_valid = False
|
||||
|
||||
if offer_valid and ticket_type.offer_price > 0:
|
||||
price = ticket_type.offer_price
|
||||
|
||||
# Check if cart item already exists for this user + ticket_type
|
||||
cart_item, created = Cart.objects.get_or_create(
|
||||
user=user,
|
||||
ticket_type=ticket_type,
|
||||
defaults={
|
||||
"ticket_meta": ticket_type.ticket_meta,
|
||||
"quantity": quantity,
|
||||
"price": price,
|
||||
},
|
||||
)
|
||||
|
||||
if not created:
|
||||
# Update existing cart item: increment quantity
|
||||
cart_item.quantity += quantity
|
||||
cart_item.price = price # Update price in case offer changed
|
||||
cart_item.save()
|
||||
|
||||
return JsonResponse(
|
||||
{
|
||||
"status": "success",
|
||||
"message": "TicketType added to cart." if created else "Cart item updated.",
|
||||
"cart_item": _cart_to_dict(cart_item),
|
||||
},
|
||||
status=201 if created else 200,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return JsonResponse({"status": "error", "message": str(e)}, status=500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class DeleteFromCartAPI(APIView):
|
||||
"""
|
||||
Remove or decrement TicketType from Cart (when customer clicks minus button).
|
||||
Body: token, username, ticket_type_id, quantity (optional, defaults to 1).
|
||||
|
||||
Decrements quantity by 1 (or by given quantity). If quantity becomes 0 or less,
|
||||
the cart item is deleted.
|
||||
"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
ticket_type_id = data.get("ticket_type_id")
|
||||
quantity = data.get("quantity", 1) # Default to 1 for minus button click
|
||||
|
||||
if not ticket_type_id:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "ticket_type_id is required."},
|
||||
status=400,
|
||||
)
|
||||
|
||||
try:
|
||||
quantity = int(quantity)
|
||||
if quantity <= 0:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "quantity must be greater than zero."},
|
||||
status=400,
|
||||
)
|
||||
except (TypeError, ValueError):
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "quantity must be a positive integer."},
|
||||
status=400,
|
||||
)
|
||||
|
||||
try:
|
||||
cart_item = Cart.objects.get(user=user, ticket_type_id=ticket_type_id)
|
||||
except Cart.DoesNotExist:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "Cart item not found for this ticket type."},
|
||||
status=404,
|
||||
)
|
||||
|
||||
cart_item.quantity -= quantity
|
||||
if cart_item.quantity <= 0:
|
||||
cart_item.delete()
|
||||
return JsonResponse(
|
||||
{
|
||||
"status": "success",
|
||||
"message": "TicketType removed from cart.",
|
||||
"cart_item": None,
|
||||
"removed": True,
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
|
||||
cart_item.save()
|
||||
return JsonResponse(
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Cart quantity updated.",
|
||||
"cart_item": _cart_to_dict(cart_item),
|
||||
"removed": False,
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
return JsonResponse({"status": "error", "message": str(e)}, status=500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class CheckoutAPI(APIView):
|
||||
"""
|
||||
Checkout the authenticated user's cart: create one Booking per cart line,
|
||||
call transaction_initiate in banking_operations, then clear the checked-out cart items.
|
||||
Body: token, username.
|
||||
"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
cart_items = list(
|
||||
Cart.objects.filter(user=user, is_active=True).select_related(
|
||||
"ticket_meta", "ticket_type", "ticket_meta__event"
|
||||
)
|
||||
)
|
||||
if not cart_items:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "Cart is empty. Add ticket types before checkout."},
|
||||
status=400,
|
||||
)
|
||||
|
||||
total_amount = Decimal("0")
|
||||
created_bookings = []
|
||||
cart_ids_to_clear = []
|
||||
|
||||
for item in cart_items:
|
||||
event_name = (
|
||||
(item.ticket_meta.event.name or "EVT")[:3].upper()
|
||||
if item.ticket_meta.event_id else "EVT"
|
||||
)
|
||||
booking_id = event_name + uuid.uuid4().hex[:10].upper()
|
||||
line_total = Decimal(str(item.price)) * item.quantity
|
||||
total_amount += line_total
|
||||
|
||||
booking = Booking.objects.create(
|
||||
booking_id=booking_id,
|
||||
user=user,
|
||||
ticket_meta=item.ticket_meta,
|
||||
ticket_type=item.ticket_type,
|
||||
quantity=item.quantity,
|
||||
price=item.price,
|
||||
)
|
||||
created_bookings.append(booking)
|
||||
cart_ids_to_clear.append(item.id)
|
||||
|
||||
reference_id = ",".join(b.booking_id for b in created_bookings)
|
||||
result = transaction_initiate(
|
||||
request=request,
|
||||
user=user,
|
||||
amount=float(total_amount),
|
||||
currency="INR",
|
||||
reference_type="checkout",
|
||||
reference_id=reference_id,
|
||||
bookings=created_bookings,
|
||||
extra_data=None,
|
||||
)
|
||||
|
||||
if not result.get("success"):
|
||||
for b in created_bookings:
|
||||
b.delete()
|
||||
return JsonResponse(
|
||||
{
|
||||
"status": "error",
|
||||
"message": result.get("message", "Transaction initiation failed."),
|
||||
},
|
||||
status=502,
|
||||
)
|
||||
|
||||
transaction_id = result.get("transaction_id")
|
||||
if transaction_id:
|
||||
for b in created_bookings:
|
||||
b.transaction_id = transaction_id
|
||||
b.save(update_fields=["transaction_id"])
|
||||
|
||||
Cart.objects.filter(id__in=cart_ids_to_clear).delete()
|
||||
|
||||
log("info", "Checkout complete", request=request, user=user, logger_data={
|
||||
"booking_ids": [b.booking_id for b in created_bookings],
|
||||
"total_amount": str(total_amount),
|
||||
})
|
||||
response_payload = {
|
||||
"status": "success",
|
||||
"message": "Checkout complete. Proceed to payment.",
|
||||
"booking_ids": [b.booking_id for b in created_bookings],
|
||||
"total_amount": str(total_amount),
|
||||
"transaction_id": result.get("transaction_id"),
|
||||
"payment_url": result.get("payment_url"),
|
||||
}
|
||||
return JsonResponse(response_payload, status=200)
|
||||
|
||||
except Exception as e:
|
||||
log("error", "Checkout exception", request=request, logger_data={"error": str(e)})
|
||||
return JsonResponse({"status": "error", "message": str(e)}, status=500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class CheckInAPI(APIView):
|
||||
"""
|
||||
Check-in a ticket by scanning QR code (ticket_id).
|
||||
Body: token, username, ticket_id (required).
|
||||
|
||||
Looks up Ticket by ticket_id. If found and not already checked in,
|
||||
sets is_checked_in=True and checked_in_date_time=now. Returns success or
|
||||
appropriate error (ticket not found / already checked in).
|
||||
"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request, error_status_code=True)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
ticket_id = data.get("ticket_id")
|
||||
if not ticket_id or not str(ticket_id).strip():
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "ticket_id is required."},
|
||||
status=400,
|
||||
)
|
||||
ticket_id = str(ticket_id).strip()
|
||||
|
||||
try:
|
||||
ticket = Ticket.objects.select_related(
|
||||
"booking", "booking__ticket_meta", "booking__ticket_meta__event", "booking__ticket_type"
|
||||
).get(ticket_id=ticket_id)
|
||||
except Ticket.DoesNotExist:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "Ticket not found."},
|
||||
status=404,
|
||||
)
|
||||
|
||||
if ticket.is_checked_in:
|
||||
log("info", "Check-in duplicate - ticket already checked in", request=request, user=user, logger_data={"ticket_id": ticket_id})
|
||||
return JsonResponse(
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Ticket already checked in.",
|
||||
"ticket_id": ticket.ticket_id,
|
||||
"is_checked_in": True,
|
||||
"checked_in_date_time": ticket.checked_in_date_time.isoformat() if ticket.checked_in_date_time else None,
|
||||
"event_name": ticket.booking.ticket_meta.event.name if ticket.booking.ticket_meta_id else None,
|
||||
"booking_id": ticket.booking.booking_id,
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
|
||||
ticket.is_checked_in = True
|
||||
ticket.checked_in_date_time = timezone.now()
|
||||
ticket.save(update_fields=["is_checked_in", "checked_in_date_time"])
|
||||
|
||||
log("info", "Check-in successful", request=request, user=user, logger_data={"ticket_id": ticket_id, "booking_id": ticket.booking.booking_id})
|
||||
return JsonResponse(
|
||||
{
|
||||
"status": "success",
|
||||
"message": "Check-in successful.",
|
||||
"ticket_id": ticket.ticket_id,
|
||||
"is_checked_in": True,
|
||||
"checked_in_date_time": ticket.checked_in_date_time.isoformat(),
|
||||
"event_name": ticket.booking.ticket_meta.event.name if ticket.booking.ticket_meta_id else None,
|
||||
"booking_id": ticket.booking.booking_id,
|
||||
},
|
||||
status=200,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
log("error", "Check-in exception", request=request, logger_data={"error": str(e)})
|
||||
return JsonResponse({"status": "error", "message": str(e)}, status=500)
|
||||
489
bookings/tickets_view/ticket_meta_type.py
Normal file
489
bookings/tickets_view/ticket_meta_type.py
Normal file
@@ -0,0 +1,489 @@
|
||||
from django.http import JsonResponse
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.views.decorators.csrf import csrf_exempt
|
||||
from django.forms.models import model_to_dict
|
||||
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from bookings.models import TicketMeta, TicketType
|
||||
from events.models import Event
|
||||
from mobile_api.utils import validate_token_and_get_user
|
||||
|
||||
|
||||
def _ticket_meta_to_dict(meta):
|
||||
"""Serialize TicketMeta for JSON."""
|
||||
data = model_to_dict(
|
||||
meta,
|
||||
fields=[
|
||||
"id",
|
||||
"ticket_name",
|
||||
"maximum_quantity",
|
||||
"available_quantity",
|
||||
"is_active",
|
||||
"created_date",
|
||||
"updated_date",
|
||||
],
|
||||
)
|
||||
data["event_id"] = meta.event_id
|
||||
return data
|
||||
|
||||
|
||||
def _ticket_type_to_dict(tt):
|
||||
"""Serialize TicketType for JSON."""
|
||||
data = model_to_dict(
|
||||
tt,
|
||||
fields=[
|
||||
"id",
|
||||
"ticket_type",
|
||||
"ticket_type_description",
|
||||
"quantity",
|
||||
"price",
|
||||
"is_active",
|
||||
"created_date",
|
||||
"updated_date",
|
||||
"is_offer",
|
||||
"offer_percentage",
|
||||
"offer_price",
|
||||
"offer_start_date",
|
||||
"offer_end_date",
|
||||
],
|
||||
)
|
||||
data["ticket_meta_id"] = tt.ticket_meta_id
|
||||
return data
|
||||
|
||||
|
||||
# ---------- TicketMeta (event-level ticket config) CRUD ----------
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class TicketMetaCreateAPI(APIView):
|
||||
"""
|
||||
Create a new TicketMeta (event-level ticket config).
|
||||
Body: token, username, event_id, ticket_name, maximum_quantity,
|
||||
available_quantity (optional, defaults to maximum_quantity), is_active (optional, default true).
|
||||
"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
event_id = data.get("event_id")
|
||||
ticket_name = data.get("ticket_name")
|
||||
maximum_quantity = data.get("maximum_quantity")
|
||||
available_quantity = data.get("available_quantity")
|
||||
is_active = data.get("is_active", True)
|
||||
|
||||
if not event_id or not ticket_name or maximum_quantity is None:
|
||||
return JsonResponse(
|
||||
{
|
||||
"status": "error",
|
||||
"message": "event_id, ticket_name and maximum_quantity are required.",
|
||||
},
|
||||
status=400,
|
||||
)
|
||||
|
||||
try:
|
||||
event = Event.objects.get(id=event_id)
|
||||
except Event.DoesNotExist:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "Event not found."},
|
||||
status=404,
|
||||
)
|
||||
|
||||
try:
|
||||
maximum_quantity = int(maximum_quantity)
|
||||
available_quantity = int(available_quantity) if available_quantity is not None else maximum_quantity
|
||||
except (TypeError, ValueError):
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "maximum_quantity and available_quantity must be integers."},
|
||||
status=400,
|
||||
)
|
||||
|
||||
if maximum_quantity <= 0:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "maximum_quantity must be greater than zero."},
|
||||
status=400,
|
||||
)
|
||||
|
||||
meta = TicketMeta.objects.create(
|
||||
event=event,
|
||||
ticket_name=ticket_name,
|
||||
maximum_quantity=maximum_quantity,
|
||||
available_quantity=available_quantity,
|
||||
is_active=bool(is_active),
|
||||
)
|
||||
return JsonResponse(
|
||||
{"status": "success", "ticket_meta": _ticket_meta_to_dict(meta)},
|
||||
status=201,
|
||||
)
|
||||
except Exception as e:
|
||||
return JsonResponse({"status": "error", "message": str(e)}, status=500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class TicketMetaListAPI(APIView):
|
||||
"""List TicketMeta, optionally filtered by event_id. Body: token, username, event_id (optional)."""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
event_id = data.get("event_id")
|
||||
qs = TicketMeta.objects.filter(is_active=True).order_by("-created_date")
|
||||
if event_id:
|
||||
qs = qs.filter(event_id=event_id)
|
||||
items = [_ticket_meta_to_dict(m) for m in qs]
|
||||
return JsonResponse({"status": "success", "ticket_metas": items}, status=200)
|
||||
except Exception as e:
|
||||
return JsonResponse({"status": "error", "message": str(e)}, status=500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class TicketMetaUpdateAPI(APIView):
|
||||
"""
|
||||
Update TicketMeta. Body: token, username, ticket_meta_id (required);
|
||||
ticket_name, maximum_quantity, available_quantity, is_active (optional).
|
||||
"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
pk = data.get("ticket_meta_id")
|
||||
if not pk:
|
||||
return JsonResponse({"status": "error", "message": "ticket_meta_id is required."}, status=400)
|
||||
|
||||
try:
|
||||
meta = TicketMeta.objects.get(id=pk)
|
||||
except TicketMeta.DoesNotExist:
|
||||
return JsonResponse({"status": "error", "message": "TicketMeta not found."}, status=404)
|
||||
|
||||
if data.get("ticket_name") is not None:
|
||||
meta.ticket_name = data["ticket_name"]
|
||||
if data.get("maximum_quantity") is not None:
|
||||
try:
|
||||
val = int(data["maximum_quantity"])
|
||||
if val <= 0:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "maximum_quantity must be greater than zero."},
|
||||
status=400,
|
||||
)
|
||||
meta.maximum_quantity = val
|
||||
except (TypeError, ValueError):
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "maximum_quantity must be an integer."},
|
||||
status=400,
|
||||
)
|
||||
if data.get("available_quantity") is not None:
|
||||
try:
|
||||
meta.available_quantity = int(data["available_quantity"])
|
||||
except (TypeError, ValueError):
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "available_quantity must be an integer."},
|
||||
status=400,
|
||||
)
|
||||
if data.get("is_active") is not None:
|
||||
meta.is_active = bool(data["is_active"])
|
||||
|
||||
meta.save()
|
||||
return JsonResponse({"status": "success", "ticket_meta": _ticket_meta_to_dict(meta)}, status=200)
|
||||
except Exception as e:
|
||||
return JsonResponse({"status": "error", "message": str(e)}, status=500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class TicketMetaDeleteAPI(APIView):
|
||||
"""Delete TicketMeta. Body: token, username, ticket_meta_id."""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
pk = data.get("ticket_meta_id")
|
||||
if not pk:
|
||||
return JsonResponse({"status": "error", "message": "ticket_meta_id is required."}, status=400)
|
||||
|
||||
try:
|
||||
meta = TicketMeta.objects.get(id=pk)
|
||||
except TicketMeta.DoesNotExist:
|
||||
return JsonResponse({"status": "error", "message": "TicketMeta not found."}, status=404)
|
||||
|
||||
meta.delete()
|
||||
return JsonResponse({"status": "success", "message": "TicketMeta deleted successfully."}, status=200)
|
||||
except Exception as e:
|
||||
return JsonResponse({"status": "error", "message": str(e)}, status=500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class TicketMetaDeactivateAPI(APIView):
|
||||
"""Deactivate a TicketMeta (set is_active=False). Body: token, username, ticket_meta_id."""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
pk = data.get("ticket_meta_id")
|
||||
if not pk:
|
||||
return JsonResponse({"status": "error", "message": "ticket_meta_id is required."}, status=400)
|
||||
|
||||
try:
|
||||
meta = TicketMeta.objects.get(id=pk)
|
||||
except TicketMeta.DoesNotExist:
|
||||
return JsonResponse({"status": "error", "message": "TicketMeta not found."}, status=404)
|
||||
|
||||
meta.is_active = False
|
||||
meta.save()
|
||||
return JsonResponse(
|
||||
{"status": "success", "message": "TicketMeta deactivated.", "ticket_meta": _ticket_meta_to_dict(meta)},
|
||||
status=200,
|
||||
)
|
||||
except Exception as e:
|
||||
return JsonResponse({"status": "error", "message": str(e)}, status=500)
|
||||
|
||||
|
||||
# ---------- TicketType CRUD ----------
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class TicketTypeCreateAPI(APIView):
|
||||
"""
|
||||
Create a new TicketType.
|
||||
Body: token, username, ticket_meta_id, ticket_type, ticket_type_description, quantity, price (required);
|
||||
is_active (optional, default true); is_offer, offer_percentage, offer_price, offer_start_date, offer_end_date (optional).
|
||||
"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
ticket_meta_id = data.get("ticket_meta_id")
|
||||
ticket_type_name = data.get("ticket_type")
|
||||
ticket_type_description = data.get("ticket_type_description")
|
||||
quantity = data.get("quantity")
|
||||
price = data.get("price")
|
||||
is_active = data.get("is_active", True)
|
||||
is_offer = data.get("is_offer", False)
|
||||
offer_percentage = data.get("offer_percentage", 0)
|
||||
offer_price = data.get("offer_price", 0)
|
||||
offer_start_date = data.get("offer_start_date")
|
||||
offer_end_date = data.get("offer_end_date")
|
||||
|
||||
if not ticket_meta_id or not ticket_type_name or quantity is None or price is None:
|
||||
return JsonResponse(
|
||||
{
|
||||
"status": "error",
|
||||
"message": "ticket_meta_id, ticket_type, ticket_type_description, quantity and price are required.",
|
||||
},
|
||||
status=400,
|
||||
)
|
||||
|
||||
try:
|
||||
ticket_meta = TicketMeta.objects.get(id=ticket_meta_id)
|
||||
except TicketMeta.DoesNotExist:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "TicketMeta not found."},
|
||||
status=404,
|
||||
)
|
||||
|
||||
try:
|
||||
quantity = int(quantity)
|
||||
price = float(price)
|
||||
offer_percentage = int(offer_percentage) if offer_percentage is not None else 0
|
||||
offer_price = float(offer_price) if offer_price is not None else 0
|
||||
except (TypeError, ValueError):
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "quantity, price, offer_percentage and offer_price must be numeric."},
|
||||
status=400,
|
||||
)
|
||||
|
||||
if quantity <= 0:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "quantity must be greater than zero."},
|
||||
status=400,
|
||||
)
|
||||
|
||||
tt = TicketType.objects.create(
|
||||
ticket_meta=ticket_meta,
|
||||
ticket_type=ticket_type_name,
|
||||
ticket_type_description=ticket_type_description or "",
|
||||
quantity=quantity,
|
||||
price=price,
|
||||
is_active=bool(is_active),
|
||||
is_offer=bool(is_offer),
|
||||
offer_percentage=offer_percentage,
|
||||
offer_price=offer_price,
|
||||
offer_start_date=offer_start_date,
|
||||
offer_end_date=offer_end_date,
|
||||
)
|
||||
return JsonResponse(
|
||||
{"status": "success", "ticket_type": _ticket_type_to_dict(tt)},
|
||||
status=201,
|
||||
)
|
||||
except Exception as e:
|
||||
return JsonResponse({"status": "error", "message": str(e)}, status=500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class TicketTypeListAPI(APIView):
|
||||
"""List TicketType, optionally filtered by ticket_meta_id. Body: token, username, ticket_meta_id (optional)."""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
ticket_meta_id = data.get("ticket_meta_id")
|
||||
qs = TicketType.objects.filter(is_active=True).order_by("-created_date")
|
||||
if ticket_meta_id:
|
||||
qs = qs.filter(ticket_meta_id=ticket_meta_id)
|
||||
items = [_ticket_type_to_dict(tt) for tt in qs]
|
||||
return JsonResponse({"status": "success", "ticket_types": items}, status=200)
|
||||
except Exception as e:
|
||||
return JsonResponse({"status": "error", "message": str(e)}, status=500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class TicketTypeUpdateAPI(APIView):
|
||||
"""
|
||||
Update TicketType. Body: token, username, ticket_type_id (required);
|
||||
ticket_type, ticket_type_description, quantity, price, is_active,
|
||||
is_offer, offer_percentage, offer_price, offer_start_date, offer_end_date (optional).
|
||||
"""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
pk = data.get("ticket_type_id")
|
||||
if not pk:
|
||||
return JsonResponse({"status": "error", "message": "ticket_type_id is required."}, status=400)
|
||||
|
||||
try:
|
||||
tt = TicketType.objects.get(id=pk)
|
||||
except TicketType.DoesNotExist:
|
||||
return JsonResponse({"status": "error", "message": "TicketType not found."}, status=404)
|
||||
|
||||
if data.get("ticket_type") is not None:
|
||||
tt.ticket_type = data["ticket_type"]
|
||||
if data.get("ticket_type_description") is not None:
|
||||
tt.ticket_type_description = data["ticket_type_description"]
|
||||
if data.get("quantity") is not None:
|
||||
try:
|
||||
val = int(data["quantity"])
|
||||
if val <= 0:
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "quantity must be greater than zero."},
|
||||
status=400,
|
||||
)
|
||||
tt.quantity = val
|
||||
except (TypeError, ValueError):
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "quantity must be an integer."},
|
||||
status=400,
|
||||
)
|
||||
if data.get("price") is not None:
|
||||
try:
|
||||
tt.price = float(data["price"])
|
||||
except (TypeError, ValueError):
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "price must be numeric."},
|
||||
status=400,
|
||||
)
|
||||
if data.get("is_active") is not None:
|
||||
tt.is_active = bool(data["is_active"])
|
||||
if data.get("is_offer") is not None:
|
||||
tt.is_offer = bool(data["is_offer"])
|
||||
if data.get("offer_percentage") is not None:
|
||||
try:
|
||||
tt.offer_percentage = int(data["offer_percentage"])
|
||||
except (TypeError, ValueError):
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "offer_percentage must be an integer."},
|
||||
status=400,
|
||||
)
|
||||
if data.get("offer_price") is not None:
|
||||
try:
|
||||
tt.offer_price = float(data["offer_price"])
|
||||
except (TypeError, ValueError):
|
||||
return JsonResponse(
|
||||
{"status": "error", "message": "offer_price must be numeric."},
|
||||
status=400,
|
||||
)
|
||||
if "offer_start_date" in data:
|
||||
tt.offer_start_date = data["offer_start_date"]
|
||||
if "offer_end_date" in data:
|
||||
tt.offer_end_date = data["offer_end_date"]
|
||||
|
||||
tt.save()
|
||||
return JsonResponse({"status": "success", "ticket_type": _ticket_type_to_dict(tt)}, status=200)
|
||||
except Exception as e:
|
||||
return JsonResponse({"status": "error", "message": str(e)}, status=500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class TicketTypeDeactivateAPI(APIView):
|
||||
"""Deactivate a TicketType (set is_active=False). Body: token, username, ticket_type_id."""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
pk = data.get("ticket_type_id")
|
||||
if not pk:
|
||||
return JsonResponse({"status": "error", "message": "ticket_type_id is required."}, status=400)
|
||||
|
||||
try:
|
||||
tt = TicketType.objects.get(id=pk)
|
||||
except TicketType.DoesNotExist:
|
||||
return JsonResponse({"status": "error", "message": "TicketType not found."}, status=404)
|
||||
|
||||
tt.is_active = False
|
||||
tt.save()
|
||||
return JsonResponse(
|
||||
{"status": "success", "message": "TicketType deactivated.", "ticket_type": _ticket_type_to_dict(tt)},
|
||||
status=200,
|
||||
)
|
||||
except Exception as e:
|
||||
return JsonResponse({"status": "error", "message": str(e)}, status=500)
|
||||
|
||||
|
||||
@method_decorator(csrf_exempt, name="dispatch")
|
||||
class TicketTypeDeleteAPI(APIView):
|
||||
"""Delete TicketType. Body: token, username, ticket_type_id."""
|
||||
|
||||
def post(self, request):
|
||||
try:
|
||||
user, token, data, error_response = validate_token_and_get_user(request)
|
||||
if error_response:
|
||||
return error_response
|
||||
|
||||
pk = data.get("ticket_type_id")
|
||||
if not pk:
|
||||
return JsonResponse({"status": "error", "message": "ticket_type_id is required."}, status=400)
|
||||
|
||||
try:
|
||||
tt = TicketType.objects.get(id=pk)
|
||||
except TicketType.DoesNotExist:
|
||||
return JsonResponse({"status": "error", "message": "TicketType not found."}, status=404)
|
||||
|
||||
tt.delete()
|
||||
return JsonResponse({"status": "success", "message": "TicketType deleted successfully."}, status=200)
|
||||
except Exception as e:
|
||||
return JsonResponse({"status": "error", "message": str(e)}, status=500)
|
||||
@@ -1,17 +1,34 @@
|
||||
from django.urls import path
|
||||
|
||||
from bookings.tickets_view.api import (
|
||||
TicketCreateAPI,
|
||||
TicketListAPI,
|
||||
TicketUpdateAPI,
|
||||
TicketDeleteAPI,
|
||||
from bookings.tickets_view.ticket_meta_type import (
|
||||
TicketMetaCreateAPI,
|
||||
TicketMetaListAPI,
|
||||
TicketMetaUpdateAPI,
|
||||
TicketMetaDeleteAPI,
|
||||
TicketMetaDeactivateAPI,
|
||||
TicketTypeCreateAPI,
|
||||
TicketTypeListAPI,
|
||||
TicketTypeUpdateAPI,
|
||||
TicketTypeDeleteAPI,
|
||||
TicketTypeDeactivateAPI,
|
||||
)
|
||||
from bookings.tickets_view.booking_api import AddToCartAPI, DeleteFromCartAPI, CheckoutAPI, CheckInAPI
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path("tickets/create/", TicketCreateAPI.as_view(), name="ticket_create"),
|
||||
path("tickets/list/", TicketListAPI.as_view(), name="ticket_list"),
|
||||
path("tickets/update/", TicketUpdateAPI.as_view(), name="ticket_update"),
|
||||
path("tickets/delete/", TicketDeleteAPI.as_view(), name="ticket_delete"),
|
||||
path("ticket-meta/create/", TicketMetaCreateAPI.as_view(), name="ticket_meta_create"),
|
||||
path("ticket-meta/list/", TicketMetaListAPI.as_view(), name="ticket_meta_list"),
|
||||
path("ticket-meta/update/", TicketMetaUpdateAPI.as_view(), name="ticket_meta_update"),
|
||||
path("ticket-meta/delete/", TicketMetaDeleteAPI.as_view(), name="ticket_meta_delete"),
|
||||
path("ticket-meta/deactivate/", TicketMetaDeactivateAPI.as_view(), name="ticket_meta_deactivate"),
|
||||
path("ticket-type/create/", TicketTypeCreateAPI.as_view(), name="ticket_type_create"),
|
||||
path("ticket-type/list/", TicketTypeListAPI.as_view(), name="ticket_type_list"),
|
||||
path("ticket-type/update/", TicketTypeUpdateAPI.as_view(), name="ticket_type_update"),
|
||||
path("ticket-type/delete/", TicketTypeDeleteAPI.as_view(), name="ticket_type_delete"),
|
||||
path("ticket-type/deactivate/", TicketTypeDeactivateAPI.as_view(), name="ticket_type_deactivate"),
|
||||
path("cart/add/", AddToCartAPI.as_view(), name="add_to_cart"),
|
||||
path("cart/delete/", DeleteFromCartAPI.as_view(), name="delete_from_cart"),
|
||||
path("checkout/", CheckoutAPI.as_view(), name="checkout"),
|
||||
path("check-in/", CheckInAPI.as_view(), name="check_in"),
|
||||
]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user