Parallel Booking API Testing¶
Complete end-to-end testing guide for the Parallel Booking feature with step-by-step API calls and expected responses.
📚 For Feature Overview: See the Parallel Booking documentation for concepts, configuration, and business use cases.
Overview¶
This guide provides practical, hands-on instructions for testing the parallel booking functionality through actual API endpoints. Each test scenario includes complete request/response examples, verification steps, and troubleshooting tips.
Purpose: Enable developers and QA engineers to thoroughly test parallel booking features including:
- Capacity Management - Verify multiple bookings up to configured limits
- Legacy Compatibility - Ensure single-booking services work unchanged
- Availability Display - Confirm capacity information in availability grid
- Rescheduling Logic - Test capacity updates during reschedules
- Cancellation Impact - Verify capacity release on cancellations
Prerequisites:
- API testing tool (Postman, Insomnia, or cURL)
- Valid JWT token with appropriate permissions
- Access to tenant/outlet IDs
- Basic understanding of REST APIs
Environment Setup¶
Step 1: Authentication¶
Obtain a JWT token for API authentication.
Endpoint: POST /api/v1/auth/login
Request:
Response:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "bearer",
"user": {
"id": "507f1f77bcf86cd799439011",
"email": "admin@example.com",
"role": "admin"
}
}
Save the access_token - You'll need it for all subsequent requests.
Step 2: Configure API Client Headers¶
Add these headers to all requests:
Step 3: Identify Tenant and Outlet¶
Get your tenant and outlet information.
Endpoint: GET /api/v1/tenants/me
Response:
{
"id": "507f1f77bcf86cd799439011",
"name": "Beauty Studio",
"outlets": [
{
"id": "507f1f77bcf86cd799439012",
"name": "Downtown Branch"
}
]
}
Save these IDs:
tenant_id: 507f1f77bcf86cd799439011outlet_id: 507f1f77bcf86cd799439012
Understanding Availability Grid API Response¶
The /api/v1/availability/availability-grid endpoint returns a structured response with the following format:
{
"start_date": "2025-11-03",
"end_date": "2025-11-03",
"num_days": 1,
"slot_interval_minutes": 30,
"availability_grid": {
"2025-11-03": [
{
"start_time": "10:00",
"end_time": "11:00",
"staff_id": "...",
"is_available": true,
"allows_parallel_bookings": true,
"available_capacity": 5,
"max_capacity": 10
}
]
},
"metadata": {
"service_id": "...",
"service_name": "Group Yoga Class",
"outlet_id": "...",
"outlet_name": "Downtown Branch",
"staff_id": "...",
"total_available_slots": 1,
"service_duration_minutes": 60
}
}
Key Points:
- Top-level fields:
start_date,end_date,num_days,slot_interval_minutes,availability_grid,metadata - Time format: Uses
"HH:MM"(e.g.,"10:00") not"HH:MM:SS" - No staff names: Only
staff_idis included in slot data - Date-keyed grid: Slots are nested under date keys (e.g.,
"2025-11-03") - Capacity fields:
allows_parallel_bookings,available_capacity,max_capacityfor parallel booking services - Metadata: Includes service and outlet information for context
Test Scenario 1: Single Booking Service (Legacy Behavior)¶
Objective: Verify backward compatibility - services with allow_parallel_bookings=false still work as before.
💡 Concept Reference: See Parallel Booking - Single-Client Services for business context.
Step 1.1: Create Single-Booking Service¶
Endpoint: POST /api/v1/services
Request:
{
"name": "Haircut",
"slug": "haircut-test",
"description": "Professional haircut service",
"category": "Hair",
"duration_minutes": 60,
"pricing": {
"base_price": 75000,
"currency": "IDR"
},
"allow_parallel_bookings": false,
"max_parallel_bookings": 1,
"outlet_id": "507f1f77bcf86cd799439012"
}
Response:
{
"id": "67890abc12345def67890001",
"name": "Haircut",
"slug": "haircut-test",
"allow_parallel_bookings": false,
"max_parallel_bookings": 1,
"duration_minutes": 60,
"created_at": "2025-10-27T10:00:00Z"
}
Save service_id: 67890abc12345def67890001
Step 1.2: Create Staff Member¶
Endpoint: POST /api/v1/staff
Request:
{
"first_name": "John",
"last_name": "Smith",
"email": "john.smith@example.com",
"phone": "+62812345678",
"position": "Senior Stylist",
"hire_date": "2024-01-01",
"outlet_id": "507f1f77bcf86cd799439012"
}
Response:
{
"id": "67890abc12345def67890002",
"first_name": "John",
"last_name": "Smith",
"full_name": "John Smith",
"email": "john.smith@example.com"
}
Save staff_id: 67890abc12345def67890002
Step 1.3: Create Staff Availability¶
Endpoint: POST /api/v1/availability
Request:
{
"staff_id": "67890abc12345def67890002",
"day_of_week": "monday",
"start_time": "09:00",
"end_time": "17:00",
"outlet_id": "507f1f77bcf86cd799439012"
}
Response:
{
"id": "67890abc12345def67890003",
"staff_id": "67890abc12345def67890002",
"day_of_week": "monday",
"start_time": "09:00:00",
"end_time": "17:00:00",
"is_available": true
}
Step 1.4: Create First Customer¶
Endpoint: POST /api/v1/customers
Request:
{
"first_name": "Alice",
"last_name": "Johnson",
"email": "alice.test@example.com",
"phone": "+62812345679"
}
Response:
{
"id": "67890abc12345def67890004",
"first_name": "Alice",
"last_name": "Johnson",
"email": "alice.test@example.com"
}
Save customer_id: 67890abc12345def67890004
Step 1.5: Book First Appointment (Should Succeed)¶
Endpoint: POST /api/v1/appointments
Request:
{
"customer_id": "67890abc12345def67890004",
"outlet_id": "507f1f77bcf86cd799439012",
"appointment_date": "2025-11-03",
"services": [
{
"service_id": "67890abc12345def67890001",
"staff_id": "67890abc12345def67890002",
"start_time": "10:00",
"duration_minutes": 60
}
]
}
Expected Response: ✅ SUCCESS (201 Created)
{
"id": "67890abc12345def67890005",
"status": "confirmed",
"appointment_date": "2025-11-03",
"services": [
{
"service_id": "67890abc12345def67890001",
"service_name": "Haircut",
"staff_id": "67890abc12345def67890002",
"staff_name": "John Smith",
"start_time": "10:00:00",
"end_time": "11:00:00",
"duration_minutes": 60
}
],
"customer": {
"id": "67890abc12345def67890004",
"name": "Alice Johnson"
}
}
Step 1.6: Create Second Customer¶
Endpoint: POST /api/v1/customers
Request:
{
"first_name": "Bob",
"last_name": "Williams",
"email": "bob.test@example.com",
"phone": "+62812345680"
}
Save customer_id: 67890abc12345def67890006
Step 1.7: Attempt Second Booking (Should Fail)¶
Endpoint: POST /api/v1/appointments
Request:
{
"customer_id": "67890abc12345def67890006",
"outlet_id": "507f1f77bcf86cd799439012",
"appointment_date": "2025-11-03",
"services": [
{
"service_id": "67890abc12345def67890001",
"staff_id": "67890abc12345def67890002",
"start_time": "10:00",
"duration_minutes": 60
}
]
}
Expected Response: ❌ CONFLICT (409 Conflict)
✅ Verification Checklist¶
- [ ] First booking succeeded with status 201
- [ ] Second booking was blocked with status 409
- [ ] Error message mentions staff unavailability
- [ ] Legacy single-booking behavior maintained
- [ ] Same staff available at different time (test by booking at 14:00)
Test Scenario 2: Parallel Booking Within Capacity¶
Objective: Verify services with allow_parallel_bookings=true accept multiple bookings up to capacity.
💡 Concept Reference: See Parallel Booking - Group Services and Service Configuration for details.
Step 2.1: Create Parallel Booking Service¶
Endpoint: POST /api/v1/services
Request:
{
"name": "Group Yoga Class",
"slug": "group-yoga-test",
"description": "60-minute group yoga session",
"category": "Fitness",
"duration_minutes": 60,
"pricing": {
"base_price": 150000,
"currency": "IDR"
},
"allow_parallel_bookings": true,
"max_parallel_bookings": 10,
"outlet_id": "507f1f77bcf86cd799439012"
}
Response:
{
"id": "67890abc12345def67890010",
"name": "Group Yoga Class",
"allow_parallel_bookings": true,
"max_parallel_bookings": 10,
"duration_minutes": 60
}
Save service_id: 67890abc12345def67890010
Step 2.2: Create Yoga Instructor¶
Endpoint: POST /api/v1/staff
Request:
{
"first_name": "Emma",
"last_name": "Martinez",
"email": "emma.test@example.com",
"phone": "+62812345681",
"position": "Yoga Instructor",
"hire_date": "2024-01-01",
"outlet_id": "507f1f77bcf86cd799439012"
}
Save staff_id: 67890abc12345def67890011
Step 2.3: Set Staff Availability¶
Endpoint: POST /api/v1/availability
Request:
{
"staff_id": "67890abc12345def67890011",
"day_of_week": "monday",
"start_time": "10:00",
"end_time": "12:00",
"outlet_id": "507f1f77bcf86cd799439012"
}
Step 2.4: Book Multiple Students (Bookings 1-5)¶
Create 5 customers and book them all for the same time slot.
For each student (i = 1 to 5):
-
Create Customer:
-
Book Appointment:
Expected Response for ALL 5 bookings: ✅ SUCCESS (201 Created)
Step 2.5: Check Availability Grid¶
Endpoint: GET /api/v1/availability/availability-grid
Query Parameters:
outlet_id=507f1f77bcf86cd799439012
service_id=67890abc12345def67890010
staff_id=67890abc12345def67890011
start_date=2025-11-03
end_date=2025-11-03
Expected Response:
{
"start_date": "2025-11-03",
"end_date": "2025-11-03",
"num_days": 1,
"slot_interval_minutes": 30,
"availability_grid": {
"2025-11-03": [
{
"start_time": "10:00",
"end_time": "11:00",
"staff_id": "67890abc12345def67890011",
"is_available": true,
"allows_parallel_bookings": true,
"available_capacity": 5,
"max_capacity": 10
}
]
},
"metadata": {
"service_id": "67890abc12345def67890010",
"service_name": "Group Yoga Class",
"outlet_id": "507f1f77bcf86cd799439012",
"outlet_name": "Downtown Branch",
"staff_id": "67890abc12345def67890011",
"total_available_slots": 1,
"service_duration_minutes": 60
}
}
✅ Verification Checklist¶
- [ ] All 5 bookings succeeded (status 201)
- [ ] Response has top-level fields:
start_date,end_date,num_days,slot_interval_minutes - [ ] Availability grid nested under
availability_gridkey with date keys - [ ] Slot shows
available_capacity: 5(10 - 5 = 5 remaining) - [ ] Slot shows
max_capacity: 10 - [ ] Field
allows_parallel_bookings: truepresent - [ ] Field
is_available: true(slot still available) - [ ] Metadata section includes service and outlet information
- [ ] Time format is
"HH:MM"not"HH:MM:SS" - [ ] 6th booking succeeds (test by creating another customer)
Test Scenario 3: Capacity Exceeded¶
Objective: Verify bookings are blocked when capacity limit is reached.
Step 3.1: Create Small Capacity Service¶
Endpoint: POST /api/v1/services
Request:
{
"name": "Boot Camp Session",
"slug": "boot-camp-test",
"description": "High-intensity training",
"category": "Fitness",
"duration_minutes": 45,
"pricing": {
"base_price": 200000,
"currency": "IDR"
},
"allow_parallel_bookings": true,
"max_parallel_bookings": 3,
"outlet_id": "507f1f77bcf86cd799439012"
}
Save service_id: 67890abc12345def67890020
Step 3.2: Create Trainer Staff¶
Endpoint: POST /api/v1/staff
Request:
{
"first_name": "Mike",
"last_name": "Johnson",
"email": "mike.test@example.com",
"phone": "+62812345682",
"position": "Personal Trainer",
"hire_date": "2024-01-01",
"outlet_id": "507f1f77bcf86cd799439012"
}
Save staff_id: 67890abc12345def67890021
Step 3.3: Set Trainer Availability¶
Endpoint: POST /api/v1/availability
Request:
{
"staff_id": "67890abc12345def67890021",
"day_of_week": "monday",
"start_time": "14:00",
"end_time": "16:00",
"outlet_id": "507f1f77bcf86cd799439012"
}
Step 3.4: Fill to Capacity (Bookings 1-3)¶
Create 3 customers and book them all for the same 14:00 time slot.
Expected Response for bookings 1-3: ✅ SUCCESS (201 Created)
Step 3.5: Check Capacity Status¶
Endpoint: GET /api/v1/availability/availability-grid
Query Parameters:
outlet_id=507f1f77bcf86cd799439012
service_id=67890abc12345def67890020
staff_id=67890abc12345def67890021
start_date=2025-11-03
end_date=2025-11-03
Expected Response:
{
"start_date": "2025-11-03",
"end_date": "2025-11-03",
"num_days": 1,
"slot_interval_minutes": 30,
"availability_grid": {
"2025-11-03": [
{
"start_time": "14:00",
"end_time": "14:45",
"staff_id": "67890abc12345def67890021",
"is_available": false,
"allows_parallel_bookings": true,
"available_capacity": 0,
"max_capacity": 3
}
]
},
"metadata": {
"service_id": "67890abc12345def67890020",
"service_name": "Boot Camp Session",
"outlet_id": "507f1f77bcf86cd799439012",
"outlet_name": "Downtown Branch",
"staff_id": "67890abc12345def67890021",
"total_available_slots": 1,
"service_duration_minutes": 45
}
}
Step 3.6: Attempt 4th Booking (Should Fail)¶
Create 4th customer and attempt booking.
Endpoint: POST /api/v1/appointments
Request:
{
"customer_id": "{customer_4_id}",
"outlet_id": "507f1f77bcf86cd799439012",
"appointment_date": "2025-11-03",
"services": [
{
"service_id": "67890abc12345def67890020",
"staff_id": "67890abc12345def67890021",
"start_time": "14:00",
"duration_minutes": 45
}
]
}
Expected Response: ❌ CONFLICT (409 Conflict)
✅ Verification Checklist¶
- [ ] First 3 bookings succeeded
- [ ] Response has correct structure with
availability_gridandmetadatasections - [ ] Slot shows
available_capacity: 0 - [ ] Slot shows
is_available: false - [ ] 4th booking was blocked (status 409)
- [ ] Error message mentions staff unavailability
- [ ] Capacity limit strictly enforced
Test Scenario 4: Availability Grid Capacity Display¶
Objective: Verify availability grid returns accurate capacity information across different booking states.
Step 4.1: Create Test Service¶
Endpoint: POST /api/v1/services
Request:
{
"name": "Cooking Class",
"slug": "cooking-class-test",
"description": "Learn to cook amazing dishes",
"category": "Education",
"duration_minutes": 120,
"pricing": {
"base_price": 300000,
"currency": "IDR"
},
"allow_parallel_bookings": true,
"max_parallel_bookings": 8,
"outlet_id": "507f1f77bcf86cd799439012"
}
Save service_id: 67890abc12345def67890030
Step 4.2: Create Multiple Time Slots with Different Capacity¶
Create staff availability and bookings to achieve different capacity states:
| Time Slot | Bookings to Create | Expected Capacity | Expected Status |
|---|---|---|---|
| 10:00 | 0 | 8/8 | Available |
| 14:00 | 3 | 5/8 | Available |
| 18:00 | 7 | 1/8 | Available (Almost Full) |
| 21:00 | 8 | 0/8 | Full |
Step 4.3: Query Availability Grid¶
Endpoint: GET /api/v1/availability/availability-grid
Query Parameters:
outlet_id=507f1f77bcf86cd799439012
service_id=67890abc12345def67890030
staff_id=staff_id_assigned_to_service
start_date=2025-11-03
end_date=2025-11-03
Expected Response:
{
"start_date": "2025-11-03",
"end_date": "2025-11-03",
"num_days": 1,
"slot_interval_minutes": 30,
"availability_grid": {
"2025-11-03": [
{
"start_time": "10:00",
"end_time": "12:00",
"staff_id": "67890abc12345def67890031",
"is_available": true,
"allows_parallel_bookings": true,
"available_capacity": 8,
"max_capacity": 8
},
{
"start_time": "14:00",
"end_time": "16:00",
"staff_id": "67890abc12345def67890031",
"is_available": true,
"allows_parallel_bookings": true,
"available_capacity": 5,
"max_capacity": 8
},
{
"start_time": "18:00",
"end_time": "20:00",
"staff_id": "67890abc12345def67890031",
"is_available": true,
"allows_parallel_bookings": true,
"available_capacity": 1,
"max_capacity": 8
},
{
"start_time": "21:00",
"end_time": "23:00",
"staff_id": "67890abc12345def67890031",
"is_available": false,
"allows_parallel_bookings": true,
"available_capacity": 0,
"max_capacity": 8
}
]
},
"metadata": {
"service_id": "67890abc12345def67890030",
"service_name": "Cooking Class",
"outlet_id": "507f1f77bcf86cd799439012",
"outlet_name": "Downtown Branch",
"staff_id": "staff_id_assigned_to_service",
"total_available_slots": 4,
"service_duration_minutes": 120
}
}
✅ Verification Checklist¶
- [ ] Response structure includes
availability_gridwith date keys - [ ] All slots have
allows_parallel_bookings: true - [ ]
available_capacitymatches expected values for each slot (8, 5, 1, 0) - [ ]
max_capacityis 8 for all slots - [ ]
is_available: truewhen capacity > 0 - [ ]
is_available: falsewhen capacity = 0 - [ ] Time format is
"HH:MM"(e.g.,"10:00"not"10:00:00") - [ ] Metadata section present with service details
- [ ] Capacity calculation correct: available = max - current_bookings
Test Scenario 5: Mixed Services on Same Staff¶
Objective: Verify staff can handle both parallel and single-booking services independently.
Step 5.1: Create Two Services for Same Staff¶
Service 1: Group Yoga (Parallel)
Endpoint: POST /api/v1/services
Request:
{
"name": "Group Yoga Mixed",
"slug": "group-yoga-mixed-test",
"description": "Group yoga class",
"category": "Fitness",
"duration_minutes": 60,
"pricing": {
"base_price": 150000,
"currency": "IDR"
},
"allow_parallel_bookings": true,
"max_parallel_bookings": 10,
"outlet_id": "507f1f77bcf86cd799439012"
}
Save yoga_service_id: 67890abc12345def67890040
Service 2: Personal Training (Single)
Endpoint: POST /api/v1/services
Request:
{
"name": "Personal Training Mixed",
"slug": "personal-training-mixed-test",
"description": "One-on-one training",
"category": "Fitness",
"duration_minutes": 60,
"pricing": {
"base_price": 500000,
"currency": "IDR"
},
"allow_parallel_bookings": false,
"max_parallel_bookings": 1,
"outlet_id": "507f1f77bcf86cd799439012"
}
Save training_service_id: 67890abc12345def67890041
Step 5.2: Create Multi-Skill Staff¶
Endpoint: POST /api/v1/staff
Request:
{
"first_name": "Sarah",
"last_name": "Wilson",
"email": "sarah.test@example.com",
"phone": "+62812345683",
"position": "Fitness Instructor",
"hire_date": "2024-01-01",
"outlet_id": "507f1f77bcf86cd799439012"
}
Save staff_id: 67890abc12345def67890042
Step 5.3: Book Multiple Students to Yoga Class (10:00)¶
Book 5 students to the same yoga class at 10:00.
Expected Result: ✅ All 5 bookings succeed
Step 5.4: Attempt 6th Yoga Booking (Should Succeed)¶
Book 6th student to same yoga class at 10:00.
Expected Result: ✅ SUCCESS (capacity not exceeded)
Step 5.5: Book Personal Training (14:00)¶
Book 1 customer for personal training at 14:00.
Expected Result: ✅ SUCCESS
Step 5.6: Attempt 2nd Personal Training (Should Fail)¶
Book 2nd customer for personal training at 14:00.
Expected Result: ❌ CONFLICT (single booking limit)
Step 5.7: Verify Service Isolation¶
Query 1: Yoga Class Availability
Endpoint: GET /api/v1/availability/availability-grid
Query Parameters:
service_id={yoga_service_id}
outlet_id=507f1f77bcf86cd799439012
start_date=2025-11-03
staff_id={staff_id_asigned-to_yoga}
end_date=2025-11-03
Expected: Response availability_grid["2025-11-03"] contains 10:00 slot with available_capacity: 4 (10 - 6 = 4)
Query 2: Personal Training Availability
Endpoint: GET /api/v1/availability/availability-grid
Query Parameters:
service_id={training_service_id}
outlet_id=507f1f77bcf86cd799439012
start_date=2025-11-03
staff_id={staff_id_assigned_to_training}
end_date=2025-11-03
Expected: Response availability_grid["2025-11-03"] contains 14:00 slot with is_available: false (single booking filled)
✅ Verification Checklist¶
- [ ] Multiple yoga bookings succeeded (parallel service)
- [ ] Single personal training succeeded
- [ ] Second personal training blocked (single-booking service)
- [ ] Yoga capacity independent of training bookings
- [ ] Same staff handles both service types correctly
- [ ] Service-specific capacity tracking works
Test Scenario 6: Rescheduling with Capacity¶
Objective: Verify rescheduling respects capacity limits and updates availability correctly.
💡 Concept Reference: See Parallel Booking - Rescheduling with Capacity for detailed behavior explanation.
Step 6.1: Create Service and Initial Bookings¶
Service: Wine Tasting (capacity 4)
Initial State: - 15:00 slot: Create 3 bookings (3/4 capacity) - 18:00 slot: Create 2 bookings (2/4 capacity) - 21:00 slot: Create 4 bookings (4/4 FULL)
Step 6.2: Reschedule from 15:00 to 18:00 (Should Succeed)¶
Endpoint: PUT /api/v1/appointments/{appointment_id}/reschedule
Request:
Expected Response: ✅ SUCCESS (200 OK)
{
"id": "{appointment_id}",
"status": "confirmed",
"appointment_date": "2025-11-03",
"services": [
{
"start_time": "18:00:00",
"end_time": "19:00:00"
}
]
}
Step 6.3: Verify Capacity Updates¶
Check 15:00 slot (should show 2/4 after reschedule out):
Endpoint: GET /api/v1/availability/availability-grid
Expected: Response availability_grid["2025-11-03"] contains 15:00 slot with available_capacity: 2 (was 1, now 2)
Check 18:00 slot (should show 3/4 after reschedule in):
Expected: Response availability_grid["2025-11-03"] contains 18:00 slot with available_capacity: 1 (was 2, now 1)
Step 6.4: Attempt Reschedule to Full Slot (Should Fail)¶
Try to reschedule an appointment to 21:00 (already at 4/4 capacity).
Endpoint: PUT /api/v1/appointments/{appointment_id}/reschedule
Request:
Expected Response: ❌ CONFLICT (409 Conflict)
✅ Verification Checklist¶
- [ ] Reschedule to available slot succeeded
- [ ] Source slot capacity increased (3/4 → 2/4)
- [ ] Destination slot capacity decreased (2/4 → 3/4)
- [ ] Reschedule to full slot blocked
- [ ]
exclude_appointment_idparameter working correctly
Test Scenario 7: Cancellation Impact¶
Objective: Verify cancellations free capacity and allow new bookings.
Step 7.1: Create Full Workshop¶
Service: Art Workshop (capacity 6)
Initial State: Book 6 students to 14:00 slot (6/6 FULL)
Step 7.2: Verify Slot is Full¶
Endpoint: GET /api/v1/availability/availability-grid
Expected Response:
{
"start_date": "2025-11-03",
"end_date": "2025-11-03",
"num_days": 1,
"slot_interval_minutes": 30,
"availability_grid": {
"2025-11-03": [
{
"start_time": "14:00",
"end_time": "16:00",
"staff_id": "67890abc12345def67890051",
"is_available": false,
"allows_parallel_bookings": true,
"available_capacity": 0,
"max_capacity": 6
}
]
},
"metadata": {
"service_id": "67890abc12345def67890050",
"service_name": "Art Workshop",
"outlet_id": "507f1f77bcf86cd799439012",
"outlet_name": "Downtown Branch",
"staff_id": "67890abc12345def67890051",
"total_available_slots": 1,
"service_duration_minutes": 120
}
}
Step 7.3: Cancel One Booking¶
Endpoint: PUT /api/v1/appointments/{appointment_id}/cancel
Expected Response: ✅ SUCCESS (200 OK)
Step 7.4: Verify Capacity Freed¶
Endpoint: GET /api/v1/availability/availability-grid
Expected Response:
{
"start_date": "2025-11-03",
"end_date": "2025-11-03",
"num_days": 1,
"slot_interval_minutes": 30,
"availability_grid": {
"2025-11-03": [
{
"start_time": "14:00",
"end_time": "16:00",
"staff_id": "67890abc12345def67890051",
"is_available": true,
"allows_parallel_bookings": true,
"available_capacity": 1,
"max_capacity": 6
}
]
},
"metadata": {
"service_id": "67890abc12345def67890050",
"service_name": "Art Workshop",
"outlet_id": "507f1f77bcf86cd799439012",
"outlet_name": "Downtown Branch",
"staff_id": "67890abc12345def67890051",
"total_available_slots": 1,
"service_duration_minutes": 120
}
}
Step 7.5: Book New Customer (Should Succeed)¶
Endpoint: POST /api/v1/appointments
Request: Book new customer to 14:00 slot
Expected Response: ✅ SUCCESS (201 Created)
Step 7.6: Test Multiple Cancellations¶
Cancel 2 more bookings.
Expected Capacity: 1 (from step 7.5) + 2 (cancelled) = 3 available
Step 7.7: Test No-Show Status¶
Endpoint: PATCH /api/v1/appointments/{appointment_id}/status
Request:
Expected: No-show appointments also free capacity (excluded from count)
✅ Verification Checklist¶
- [ ] Full slot (6/6) marked as unavailable
- [ ] Single cancellation freed 1 spot (6/6 → 5/6)
- [ ] Capacity immediately available for new bookings
- [ ] Multiple cancellations compound correctly
- [ ] No-show status also frees capacity
- [ ] Cancelled bookings excluded from capacity count
Advanced Testing Scenarios¶
Scenario A: Race Condition Testing¶
Objective: Test concurrent bookings to same slot
Setup: - Service with capacity 1 - Use API client to send 2 simultaneous POST requests
Expected: Exactly 1 succeeds, 1 fails with conflict
Test Steps:
- Use Postman Runner or parallel cURL commands
- Send 2 identical booking requests simultaneously
- Verify one returns 201, other returns 409
Scenario B: Capacity Change on Existing Bookings¶
Objective: Test changing max_parallel_bookings when bookings exist
Test Steps:
- Create service with capacity 10
- Book 8 customers
- Update service to capacity 5 via
PUT /api/v1/services/{id} - Verify existing 8 bookings remain valid
- Attempt 9th booking - should be blocked (over capacity)
Expected: Grandfathered approach - existing bookings honored
Scenario C: Different Services Overlapping¶
Objective: Verify staff can't do two services simultaneously
Test Steps:
- Book Yoga class (parallel) at 10:00-11:00
- Attempt Personal Training (single) at 10:30-11:30 (overlaps)
- Verify booking blocked
Expected: Staff conflict detection across services
Scenario D: Multi-Day Capacity Tracking¶
Objective: Verify capacity tracked per day, not globally
Test Steps:
- Fill Monday 10:00 slot to capacity
- Book Tuesday 10:00 slot
- Verify Tuesday booking succeeds
Expected: Capacity is day-specific
Database Verification (Optional)¶
If you have MongoDB access, verify data directly:
Check Service Configuration¶
db.services.findOne(
{ _id: ObjectId("67890abc12345def67890010") },
{ name: 1, allow_parallel_bookings: 1, max_parallel_bookings: 1 }
)
Count Active Bookings for Time Slot¶
db.appointments.countDocuments({
"tenant_id": ObjectId("507f1f77bcf86cd799439011"),
"appointment_date": ISODate("2025-11-03"),
"services.service_id": ObjectId("67890abc12345def67890010"),
"services.staff_id": ObjectId("67890abc12345def67890011"),
"services.start_time": "10:00",
"status": { $nin: ["cancelled", "no_show", "rescheduled"] }
})
Verify Parallel Booking Index Exists¶
db.appointments.getIndexes().filter(idx =>
idx.name === "idx_appointment_parallel_booking_capacity"
)
Troubleshooting¶
Issue 1: "Service not found" Error¶
Symptom:
Solutions:
- Verify service_id is correct (copy from create response)
- Check service belongs to correct tenant
- Ensure service is not deleted
Issue 2: Capacity Not Updating¶
Symptom: Availability grid shows wrong capacity
Solutions:
- Clear any caching if implemented
- Verify appointments have correct status (not cancelled)
- Check database directly for appointment count
- Verify
exclude_appointment_idparameter in reschedule
Issue 3: Parallel Booking Not Working¶
Symptom: Second booking blocked even with capacity
Solutions:
- Verify service has
allow_parallel_bookings: true - Check
max_parallel_bookings> 1 - Ensure both bookings use same
service_id - Verify staff availability covers time slot
Issue 4: Authentication Errors¶
Symptom: 401 Unauthorized
Solutions:
- Refresh JWT token (may be expired)
- Verify
Authorization: Bearer {token}header format - Check user has correct permissions
Issue 5: Capacity Fields Missing¶
Symptom: API response lacks available_capacity
Solutions:
- Verify using latest API version
- Check service has
allow_parallel_bookings: true - Ensure endpoint supports parallel booking feature
API Reference Summary¶
Core Endpoints¶
| Endpoint | Method | Purpose |
|---|---|---|
/api/v1/services |
POST | Create service with parallel booking config |
/api/v1/services/{id} |
GET | Get service details |
/api/v1/staff |
POST | Create staff member |
/api/v1/availability |
POST | Create availability schedule |
/api/v1/availability/availability-grid |
GET | Get availability with capacity |
/api/v1/customers |
POST | Create customer |
/api/v1/appointments |
POST | Create appointment |
/api/v1/appointments/{id}/reschedule |
PUT | Reschedule appointment |
/api/v1/appointments/{id}/cancel |
PUT | Cancel appointment |
Test Completion Checklist¶
Mandatory Tests¶
- [ ] Scenario 1: Single Booking Service ✅
- [ ] Scenario 2: Parallel Booking Within Capacity ✅
- [ ] Scenario 3: Capacity Exceeded ✅
- [ ] Scenario 4: Availability Grid Display ✅
- [ ] Scenario 5: Mixed Services Same Staff ✅
- [ ] Scenario 6: Rescheduling with Capacity ✅
- [ ] Scenario 7: Cancellation Impact ✅
Advanced Tests (Optional)¶
- [ ] Scenario A: Race Condition Testing
- [ ] Scenario B: Capacity Change on Existing Bookings
- [ ] Scenario C: Different Services Overlapping
- [ ] Scenario D: Multi-Day Capacity Tracking
Regression Tests¶
- [ ] Verify existing single-booking services still work
- [ ] Test API backward compatibility
- [ ] Verify performance (< 100ms for capacity checks)
- [ ] Test with realistic data volumes
Success Criteria¶
✅ All tests passing if:
- Single-booking services block second bookings
- Parallel services accept multiple bookings up to capacity
- Bookings blocked when capacity exceeded
- Availability grid displays accurate capacity info
- Rescheduling updates capacity correctly
- Cancellations free capacity immediately
- No breaking changes to existing functionality
Next Steps After Testing¶
- Document Issues - Report bugs or unexpected behavior
- Performance Testing - Test with realistic load
- Frontend Integration - Test with UI components
- User Acceptance Testing - Get feedback from end users
- Production Deployment - Follow deployment checklist
Related Documentation¶
- Parallel Booking - Feature overview, concepts, configuration, and business use cases
- Appointment Management - Core booking functionality
- Availability Management - Staff schedules
- Service Management - Service configuration