170 lines
7.3 KiB
Python
170 lines
7.3 KiB
Python
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)
|