Add scheduler improvements, API endpoints, and month calendar view
Backend: - Add /api/customers/ endpoint (CustomerViewSet, CustomerSerializer) - Add /api/services/ endpoint with Service model and migrations - Add Resource.type field (STAFF, ROOM, EQUIPMENT) with migration - Fix EventSerializer to return resource_id, customer_id, service_id - Add date range filtering to EventViewSet (start_date, end_date params) - Add create_demo_appointments management command - Set default brand colors in business API Frontend: - Add calendar grid view for month mode in OwnerScheduler - Fix sidebar navigation active link contrast (bg-white/10) - Add default primaryColor/secondaryColor fallbacks in useBusiness - Disable WebSocket (backend not implemented) to stop reconnect loop - Fix Resource.type.toLowerCase() error by adding type to backend 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -7,8 +7,10 @@ from rest_framework import viewsets, status
|
||||
from rest_framework.permissions import IsAuthenticated, AllowAny
|
||||
from rest_framework.response import Response
|
||||
from .models import Resource, Event, Participant
|
||||
from .serializers import ResourceSerializer, EventSerializer, ParticipantSerializer
|
||||
from .serializers import ResourceSerializer, EventSerializer, ParticipantSerializer, CustomerSerializer, ServiceSerializer
|
||||
from .models import Service
|
||||
from core.permissions import HasQuota
|
||||
from smoothschedule.users.models import User
|
||||
|
||||
|
||||
class ResourceViewSet(viewsets.ModelViewSet):
|
||||
@@ -52,17 +54,46 @@ class EventViewSet(viewsets.ModelViewSet):
|
||||
- EventSerializer.validate() automatically checks resource availability
|
||||
- If resource capacity exceeded, returns 400 Bad Request
|
||||
- See schedule/services.py AvailabilityService for logic
|
||||
|
||||
Query Parameters:
|
||||
- start_date: Filter events starting on or after this date (ISO format)
|
||||
- end_date: Filter events starting before this date (ISO format)
|
||||
- status: Filter by event status
|
||||
"""
|
||||
queryset = Event.objects.all()
|
||||
serializer_class = EventSerializer
|
||||
# TODO: Re-enable authentication for production
|
||||
permission_classes = [AllowAny] # Temporarily allow unauthenticated access for development
|
||||
|
||||
filterset_fields = ['status', 'start_time', 'end_time']
|
||||
|
||||
filterset_fields = ['status']
|
||||
search_fields = ['title', 'notes']
|
||||
ordering_fields = ['start_time', 'end_time', 'created_at']
|
||||
ordering = ['start_time']
|
||||
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Filter events by date range if start_date and end_date are provided.
|
||||
"""
|
||||
queryset = Event.objects.all()
|
||||
|
||||
# Filter by date range
|
||||
start_date = self.request.query_params.get('start_date')
|
||||
end_date = self.request.query_params.get('end_date')
|
||||
|
||||
if start_date:
|
||||
from django.utils.dateparse import parse_datetime
|
||||
start_dt = parse_datetime(start_date)
|
||||
if start_dt:
|
||||
queryset = queryset.filter(start_time__gte=start_dt)
|
||||
|
||||
if end_date:
|
||||
from django.utils.dateparse import parse_datetime
|
||||
end_dt = parse_datetime(end_date)
|
||||
if end_dt:
|
||||
queryset = queryset.filter(start_time__lt=end_dt)
|
||||
|
||||
return queryset
|
||||
|
||||
def perform_create(self, serializer):
|
||||
"""
|
||||
Create event with automatic availability validation.
|
||||
@@ -90,14 +121,93 @@ class EventViewSet(viewsets.ModelViewSet):
|
||||
class ParticipantViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoint for managing Event Participants.
|
||||
|
||||
|
||||
Allows adding/removing participants (Resources, Staff, Customers)
|
||||
to/from events via the GenericForeignKey pattern.
|
||||
"""
|
||||
queryset = Participant.objects.all()
|
||||
serializer_class = ParticipantSerializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
|
||||
filterset_fields = ['event', 'role', 'content_type']
|
||||
ordering_fields = ['created_at']
|
||||
ordering = ['-created_at']
|
||||
|
||||
|
||||
class CustomerViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoint for managing Customers.
|
||||
|
||||
Customers are Users with role=CUSTOMER belonging to the current tenant.
|
||||
"""
|
||||
serializer_class = CustomerSerializer
|
||||
# TODO: Re-enable authentication for production
|
||||
permission_classes = [AllowAny] # Temporarily allow unauthenticated access for development
|
||||
|
||||
filterset_fields = ['is_active']
|
||||
search_fields = ['email', 'first_name', 'last_name']
|
||||
ordering_fields = ['email', 'created_at']
|
||||
ordering = ['email']
|
||||
|
||||
def get_queryset(self):
|
||||
"""
|
||||
Return customers for the current tenant.
|
||||
|
||||
Customers are Users with role=CUSTOMER.
|
||||
For now, return all customers. When authentication is enabled,
|
||||
filter by the user's tenant.
|
||||
"""
|
||||
queryset = User.objects.filter(role=User.Role.CUSTOMER)
|
||||
|
||||
# Filter by tenant if user is authenticated and has a tenant
|
||||
# TODO: Re-enable this when authentication is enabled
|
||||
# if self.request.user.is_authenticated and self.request.user.tenant:
|
||||
# queryset = queryset.filter(tenant=self.request.user.tenant)
|
||||
|
||||
# Apply status filter if provided
|
||||
status_filter = self.request.query_params.get('status')
|
||||
if status_filter:
|
||||
if status_filter == 'Active':
|
||||
queryset = queryset.filter(is_active=True)
|
||||
elif status_filter == 'Inactive':
|
||||
queryset = queryset.filter(is_active=False)
|
||||
|
||||
# Apply search filter if provided
|
||||
search = self.request.query_params.get('search')
|
||||
if search:
|
||||
from django.db.models import Q
|
||||
queryset = queryset.filter(
|
||||
Q(email__icontains=search) |
|
||||
Q(first_name__icontains=search) |
|
||||
Q(last_name__icontains=search)
|
||||
)
|
||||
|
||||
return queryset
|
||||
|
||||
|
||||
class ServiceViewSet(viewsets.ModelViewSet):
|
||||
"""
|
||||
API endpoint for managing Services.
|
||||
|
||||
Services are the offerings a business provides (e.g., Haircut, Massage).
|
||||
"""
|
||||
queryset = Service.objects.filter(is_active=True)
|
||||
serializer_class = ServiceSerializer
|
||||
# TODO: Re-enable authentication for production
|
||||
permission_classes = [AllowAny] # Temporarily allow unauthenticated access for development
|
||||
|
||||
filterset_fields = ['is_active']
|
||||
search_fields = ['name', 'description']
|
||||
ordering_fields = ['name', 'price', 'duration', 'created_at']
|
||||
ordering = ['name']
|
||||
|
||||
def get_queryset(self):
|
||||
"""Return services, optionally including inactive ones."""
|
||||
queryset = Service.objects.all()
|
||||
|
||||
# By default only show active services
|
||||
show_inactive = self.request.query_params.get('show_inactive', 'false')
|
||||
if show_inactive.lower() != 'true':
|
||||
queryset = queryset.filter(is_active=True)
|
||||
|
||||
return queryset
|
||||
|
||||
Reference in New Issue
Block a user