inital commit

This commit is contained in:
2026-04-10 20:51:43 -05:00
parent cd1f2eae29
commit 562a8525d0
85 changed files with 4820 additions and 2 deletions

169
booking/views.py Normal file
View File

@@ -0,0 +1,169 @@
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
from rest_framework import generics, permissions, status
from rest_framework.exceptions import PermissionDenied
from rest_framework.response import Response
from rest_framework.views import APIView
from .models import Booking
from .serializers import BookingCreateSerializer, BookingSerializer
from .services import is_bookable, transition_booking_status
def health_check(request):
return JsonResponse({"status": "ok", "service": "WaterTrek"})
class AvailabilityView(APIView):
permission_classes = (permissions.AllowAny,)
def get(self, request):
equipment_item_id = request.query_params.get("equipment_item_id")
adventure_offering_id = request.query_params.get("adventure_offering_id")
starts_at = request.query_params.get("starts_at")
ends_at = request.query_params.get("ends_at")
if not starts_at or not ends_at:
return Response(
{"detail": "starts_at and ends_at are required query params."},
status=status.HTTP_400_BAD_REQUEST,
)
if bool(equipment_item_id) == bool(adventure_offering_id):
return Response(
{"detail": "Provide exactly one target: equipment_item_id or adventure_offering_id."},
status=status.HTTP_400_BAD_REQUEST,
)
payload = {"starts_at": starts_at, "ends_at": ends_at}
if equipment_item_id:
payload["equipment_item_id"] = equipment_item_id
if adventure_offering_id:
payload["adventure_offering_id"] = adventure_offering_id
serializer = BookingCreateSerializer(data=payload)
serializer.is_valid(raise_exception=True)
equipment_item = serializer.validated_data.get("equipment_item_id")
adventure_offering = serializer.validated_data.get("adventure_offering_id")
starts_at_value = serializer.validated_data["starts_at"]
ends_at_value = serializer.validated_data["ends_at"]
conflict_filter = {
"starts_at__lt": ends_at_value,
"ends_at__gt": starts_at_value,
"status__in": [Booking.Status.REQUESTED, Booking.Status.APPROVED, Booking.Status.CONFIRMED],
}
if equipment_item:
conflict_filter["equipment_item"] = equipment_item
else:
conflict_filter["adventure_offering"] = adventure_offering
conflicts = Booking.objects.filter(**conflict_filter).count()
available = is_bookable(
equipment_item=equipment_item,
adventure_offering=adventure_offering,
starts_at=starts_at_value,
ends_at=ends_at_value,
)
return Response(
{
"equipment_item_id": equipment_item.id if equipment_item else None,
"adventure_offering_id": adventure_offering.id if adventure_offering else None,
"starts_at": starts_at_value,
"ends_at": ends_at_value,
"is_available": available,
"conflicts": conflicts,
}
)
class BookingCreateView(generics.CreateAPIView):
serializer_class = BookingCreateSerializer
permission_classes = (permissions.IsAuthenticated,)
def perform_create(self, serializer):
if not self.request.user.is_customer:
raise PermissionDenied("Only customers can request bookings.")
self.instance = serializer.save()
def create(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
self.perform_create(serializer)
return Response(BookingSerializer(self.instance).data, status=status.HTTP_201_CREATED)
class BookingListView(generics.ListAPIView):
serializer_class = BookingSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
user = self.request.user
if user.is_vendor and hasattr(user, "vendor_profile"):
return Booking.objects.filter(vendor=user.vendor_profile).select_related(
"customer", "vendor", "equipment_item", "adventure_offering"
)
return Booking.objects.filter(customer=user).select_related("customer", "vendor", "equipment_item", "adventure_offering")
class BookingDetailView(generics.RetrieveAPIView):
serializer_class = BookingSerializer
permission_classes = (permissions.IsAuthenticated,)
def get_queryset(self):
user = self.request.user
if user.is_vendor and hasattr(user, "vendor_profile"):
return Booking.objects.filter(vendor=user.vendor_profile).select_related(
"customer", "vendor", "equipment_item", "adventure_offering"
)
return Booking.objects.filter(customer=user).select_related("customer", "vendor", "equipment_item", "adventure_offering")
class VendorApproveBookingView(APIView):
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, pk):
if not request.user.is_vendor or not hasattr(request.user, "vendor_profile"):
raise PermissionDenied("Only vendors can approve bookings.")
booking = get_object_or_404(Booking, pk=pk, vendor=request.user.vendor_profile)
if booking.status != Booking.Status.REQUESTED:
return Response({"detail": "Only requested bookings can be approved."}, status=status.HTTP_400_BAD_REQUEST)
transition_booking_status(
booking=booking,
to_status=Booking.Status.APPROVED,
actor=request.user,
note=request.data.get("note", "Booking approved."),
vendor_notes=request.data.get("vendor_notes", booking.vendor_notes),
)
return Response(BookingSerializer(booking).data)
class VendorDeclineBookingView(APIView):
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, pk):
if not request.user.is_vendor or not hasattr(request.user, "vendor_profile"):
raise PermissionDenied("Only vendors can decline bookings.")
booking = get_object_or_404(Booking, pk=pk, vendor=request.user.vendor_profile)
if booking.status != Booking.Status.REQUESTED:
return Response({"detail": "Only requested bookings can be declined."}, status=status.HTTP_400_BAD_REQUEST)
transition_booking_status(
booking=booking,
to_status=Booking.Status.DECLINED,
actor=request.user,
note=request.data.get("note", "Booking declined."),
vendor_notes=request.data.get("vendor_notes", booking.vendor_notes),
)
return Response(BookingSerializer(booking).data)
class CustomerCancelBookingView(APIView):
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, pk):
booking = get_object_or_404(Booking, pk=pk, customer=request.user)
if booking.status in [Booking.Status.CANCELLED, Booking.Status.DECLINED]:
return Response({"detail": "This booking cannot be cancelled."}, status=status.HTTP_400_BAD_REQUEST)
transition_booking_status(
booking=booking,
to_status=Booking.Status.CANCELLED,
actor=request.user,
note=request.data.get("note", "Booking cancelled by customer."),
)
return Response(BookingSerializer(booking).data)