from datetime import date as date_cls from rest_framework import decorators, mixins, response, status, viewsets from rest_framework.permissions import IsAuthenticated from apps.core.errors import ApiError, error_response from apps.rbac.permissions import ActionPermission from apps.reservations.models import Facility, FacilitySchedule, ProcessingLine, Reservation, Slot from apps.reservations.serializers import ( FacilityScheduleSerializer, FacilitySerializer, ReservationSerializer, SlotSerializer, ) from apps.reservations.services import ReservationService from apps.reservations.slot_service import SlotService class FacilityViewSet(viewsets.ReadOnlyModelViewSet): queryset = Facility.objects.all() serializer_class = FacilitySerializer permission_classes = [IsAuthenticated] class FacilityScheduleViewSet(viewsets.ModelViewSet): queryset = FacilitySchedule.objects.select_related("facility").all() serializer_class = FacilityScheduleSerializer permission_classes = [ActionPermission] filterset_fields = ["facility", "date"] code_map = { "list": "reservation.view", "retrieve": "reservation.view", "create": "slot.manage", "update": "slot.manage", "partial_update": "slot.manage", "destroy": "slot.manage", } class SlotViewSet( mixins.ListModelMixin, mixins.RetrieveModelMixin, mixins.CreateModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, viewsets.GenericViewSet, ): queryset = Slot.objects.select_related("facility", "line").all() serializer_class = SlotSerializer permission_classes = [ActionPermission] filterset_fields = ["facility", "date", "blocked"] code_map = { "list": "reservation.view", "retrieve": "reservation.view", "create": "slot.manage", "update": "slot.manage", "partial_update": "slot.manage", "destroy": "slot.manage", "generate": "slot.manage", } @decorators.action(detail=False, methods=["post"]) def generate(self, request): facility_id = request.data.get("facility") date_str = request.data.get("date") line_id = request.data.get("line") if not facility_id or not date_str: return response.Response( error_response("INVALID_INPUT", "facility and date are required."), status=status.HTTP_400_BAD_REQUEST, ) try: facility = Facility.objects.get(pk=facility_id) except Facility.DoesNotExist: return response.Response( error_response("FACILITY_NOT_FOUND", "Facility not found."), status=status.HTTP_404_NOT_FOUND, ) line = None if line_id: try: line = ProcessingLine.objects.get(pk=line_id) except ProcessingLine.DoesNotExist: return response.Response( error_response("LINE_NOT_FOUND", "Processing line not found."), status=status.HTTP_404_NOT_FOUND, ) try: day = date_cls.fromisoformat(date_str) except ValueError: return response.Response( error_response("INVALID_DATE", "Invalid date."), status=status.HTTP_400_BAD_REQUEST, ) try: created = SlotService().generate_slots(facility, day, line) except ValueError as exc: return response.Response( error_response("INVALID_SETTINGS", str(exc)), status=status.HTTP_400_BAD_REQUEST, ) return response.Response({"created": created}) class ReservationViewSet(viewsets.ModelViewSet): serializer_class = ReservationSerializer permission_classes = [ActionPermission] filterset_fields = ["status"] code_map = { "list": "reservation.view", "retrieve": "reservation.view", "create": "reservation.create", "cancel": "reservation.cancel", "approve": "reservation.approve", "reject": "reservation.approve", } service = ReservationService() def get_queryset(self): user = self.request.user if user.is_superuser or user.user_roles.filter(role__slug__in=["administrator", "facility_manager"]).exists(): return Reservation.objects.select_related("customer__user", "slot").all() return Reservation.objects.select_related("customer__user", "slot").filter(customer__user=user) def perform_create(self, serializer): slot_id = self.request.data.get("slot") if not slot_id: raise ApiError("SLOT_REQUIRED", "A slot id is required.") try: reservation = self.service.book( self._customer(), Slot.objects.get(pk=slot_id), title=self.request.data.get("title", ""), notes=self.request.data.get("notes", ""), ) except Slot.DoesNotExist: raise ApiError("SLOT_NOT_FOUND", "Slot not found.") serializer.instance = reservation def _customer(self): from apps.accounts.models import Customer customer, _ = Customer.objects.get_or_create(user=self.request.user) return customer @decorators.action(detail=True, methods=["post"]) def cancel(self, request, pk=None): reservation = self.get_object() try: self.service.cancel(reservation) except ApiError as exc: return response.Response(error_response(exc.code, exc.message, exc.details), status=exc.status) return response.Response(ReservationSerializer(reservation).data) @decorators.action(detail=True, methods=["post"]) def approve(self, request, pk=None): reservation = self.get_object() try: reservation = self.service.approve(reservation, request.user) except ApiError as exc: return response.Response(error_response(exc.code, exc.message, exc.details), status=exc.status) return response.Response(ReservationSerializer(reservation).data) @decorators.action(detail=True, methods=["post"]) def reject(self, request, pk=None): reservation = self.get_object() try: reservation = self.service.reject(reservation, request.user) except ApiError as exc: return response.Response(error_response(exc.code, exc.message, exc.details), status=exc.status) return response.Response(ReservationSerializer(reservation).data)