Outlet Management¶
Complete guide to managing outlets, business hours, settings, and analytics in the Reserva platform.
Overview¶
The outlet management system provides comprehensive tools for:
- Outlet CRUD Operations - Create, read, update, and delete outlet locations
- Business Hours Management - Configure operating hours for each day of the week
- Advanced Search - Filter and search outlets by status, name, or location
- Subscription Limit Enforcement - Automatic validation against plan limits
- Outlet Statistics - Performance metrics and analytics per location
- Multi-Tenant Isolation - Strict tenant-based data separation
- Soft Delete Support - Preserve data for audit trail purposes
Key Concepts:
- Outlet = Physical business location under a tenant (brand)
- Tenant = Brand/business that owns multiple outlets
- Slug = Unique URL-friendly identifier per outlet within tenant
- Business Hours = Weekly operating schedule with open/close times
- Soft Delete = Outlet marked deleted, data preserved for audit
- Subscription Limit = Maximum outlets allowed by subscription plan
Subscription Plan Limits¶
Outlet creation is strictly limited by subscription plan.
Plan Limits¶
| Plan | Max Outlets | Notes |
|---|---|---|
| FREE | 1 | Single location only |
| PRO | 10 | Supports multi-location businesses |
| ENTERPRISE | Unlimited (-1) | No outlet limit |
Enforcement:
- Outlet creation fails with HTTP 402 Payment Required if limit exceeded
- Current outlet count checked before creation
- Deleted outlets (soft delete) still count toward limit
- Upgrade subscription to increase outlet limit
Error Response When Limit Exceeded:
See Subscription Management - Available Plans for complete plan details and Subscription Upgrade to increase limits.
List Tenant Outlets¶
Retrieve paginated list of outlets with advanced filtering and sorting.
Endpoint¶
Authentication: Required (JWT token) Access: All staff members
Query Parameters¶
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
page |
integer | No | Page number (starts from 1) | 1 |
size |
integer | No | Items per page (max 100, default 20) | 20 |
status |
string | No | Filter by outlet status | "active" |
search |
string | No | Search by name or location | "Downtown" |
sort_by |
string | No | Field to sort by | "name" |
sort_order |
string | No | Sort order: asc or desc |
"asc" |
Outlet Statuses¶
active- Outlet is operational and accepting bookingsinactive- Outlet temporarily closed or not accepting bookingsmaintenance- Outlet under maintenance or renovationpermanently_closed- Outlet permanently closed (typically soft-deleted)
Response¶
{
"items": [
{
"id": "507f1f77bcf86cd799439011",
"tenant_id": "507f1f77bcf86cd799439010",
"name": "Downtown Beauty Spa",
"slug": "downtown-spa",
"description": "Our flagship downtown location",
"status": "active",
"address": {
"street": "123 Main Street",
"city": "New York",
"state": "NY",
"postal_code": "10001",
"country": "US",
"district": "Manhattan"
},
"contact": {
"phone": "+1234567890",
"email": "downtown@beautyspa.com",
"website": "https://beautyspa.com"
},
"business_hours": [
{
"day": 0,
"is_open": true,
"open_time": "09:00",
"close_time": "18:00"
},
{
"day": 1,
"is_open": true,
"open_time": "09:00",
"close_time": "18:00"
},
{
"day": 2,
"is_open": true,
"open_time": "09:00",
"close_time": "18:00"
},
{
"day": 3,
"is_open": true,
"open_time": "09:00",
"close_time": "18:00"
},
{
"day": 4,
"is_open": true,
"open_time": "09:00",
"close_time": "18:00"
},
{
"day": 5,
"is_open": true,
"open_time": "10:00",
"close_time": "16:00"
},
{
"day": 6,
"is_open": false,
"open_time": null,
"close_time": null
}
],
"settings": {
"accepts_online_booking": true,
"requires_appointment": true,
"walk_ins_allowed": true,
"advance_booking_days": 30,
"cancellation_hours": 24,
"auto_confirm_bookings": false,
"payment_required_upfront": false,
"timezone": "America/New_York",
"default_service_buffer_minutes": 15,
"accepts_online_payment": true,
"accepts_cash_payment": true,
"payment_on_arrival": false
},
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2025-01-20T14:45:00Z"
}
],
"total": 5,
"page": 1,
"size": 20,
"pages": 1
}
Search Behavior¶
Full-text search across:
- Outlet name
- City name
- District/region name
Case-insensitive matching with regex support.
Business Hours Format¶
Day of Week Numbering:
0= Monday1= Tuesday2= Wednesday3= Thursday4= Friday5= Saturday6= Sunday
Time Format: 24-hour format (HH:MM), e.g., "09:00", "18:00"
Closed Days: Set is_open: false, open_time and close_time will be null
Related Endpoints¶
- Get Outlet Details - Single outlet information
- Get Business Hours - Detailed hours for specific outlet
Create New Outlet¶
Create a new outlet with automatic validation and subscription limit checking.
Endpoint¶
Authentication: Required (JWT token) Access: TENANT_ADMIN, OUTLET_MANAGER, or SUPER_ADMIN
Request Body¶
{
"name": "Downtown Beauty Spa",
"slug": "downtown-spa",
"description": "Our flagship downtown location",
"status": "active",
"address": {
"street": "123 Main Street",
"city": "New York",
"state": "NY",
"postal_code": "10001",
"country": "US",
"district": "Manhattan"
},
"contact": {
"phone": "+1234567890",
"email": "downtown@beautyspa.com",
"website": "https://beautyspa.com"
},
"business_hours": [
{
"day": 0,
"is_open": true,
"open_time": "09:00",
"close_time": "18:00"
},
{
"day": 1,
"is_open": true,
"open_time": "09:00",
"close_time": "18:00"
},
{
"day": 2,
"is_open": true,
"open_time": "09:00",
"close_time": "18:00"
},
{
"day": 3,
"is_open": true,
"open_time": "09:00",
"close_time": "18:00"
},
{
"day": 4,
"is_open": true,
"open_time": "09:00",
"close_time": "18:00"
},
{
"day": 5,
"is_open": true,
"open_time": "10:00",
"close_time": "16:00"
},
{
"day": 6,
"is_open": false
}
],
"settings": {
"accepts_online_booking": true,
"requires_appointment": true,
"walk_ins_allowed": true,
"advance_booking_days": 30,
"cancellation_hours": 24,
"auto_confirm_bookings": false,
"payment_required_upfront": false,
"timezone": "America/New_York",
"default_service_buffer_minutes": 15,
"accepts_online_payment": true,
"accepts_cash_payment": true,
"payment_on_arrival": false
}
}
Request Fields¶
| Field | Type | Required | Description | Validation |
|---|---|---|---|---|
name |
string | Yes | Outlet display name | 1-200 characters |
slug |
string | Yes | URL-friendly identifier | Lowercase, hyphens, unique per tenant |
description |
string | No | Outlet description | Max 1000 characters |
status |
string | No | Outlet status | active, inactive, maintenance (default: active) |
address |
object | Yes | Physical address | See Address schema below |
contact |
object | Yes | Contact information | See Contact schema below |
business_hours |
array | No | Weekly operating hours | 7 entries (Mon-Sun), see BusinessHours schema |
settings |
object | No | Operational settings | See Settings schema below |
Address Schema¶
| Field | Type | Required | Description |
|---|---|---|---|
street |
string | Yes | Street address |
city |
string | Yes | City name |
state |
string | Yes | State/province code |
postal_code |
string | Yes | Postal/ZIP code |
country |
string | Yes | Country code (ISO 3166-1 alpha-2) |
district |
string | No | District/region name |
Contact Schema¶
| Field | Type | Required | Description | Validation |
|---|---|---|---|---|
phone |
string | Yes | Phone number | Valid phone format with country code |
email |
string | Yes | Email address | Valid email format |
website |
string | No | Website URL | Valid URL format |
Settings Schema¶
| Field | Type | Default | Description |
|---|---|---|---|
accepts_online_booking |
boolean | true |
Allow online bookings |
requires_appointment |
boolean | true |
Appointment required for service |
walk_ins_allowed |
boolean | true |
Accept walk-in customers |
advance_booking_days |
integer | 30 |
Max days in advance for booking |
cancellation_hours |
integer | 24 |
Hours before appointment for free cancellation |
auto_confirm_bookings |
boolean | false |
Automatically confirm bookings |
payment_required_upfront |
boolean | false |
Require payment before appointment |
timezone |
string | "UTC" |
IANA timezone identifier |
default_service_buffer_minutes |
integer | 15 |
Buffer time between services |
accepts_online_payment |
boolean | true |
Accept online payment methods |
accepts_cash_payment |
boolean | true |
Accept cash payments |
payment_on_arrival |
boolean | false |
Payment at outlet on arrival |
Response¶
Returns complete outlet profile with generated ID (same format as List Outlets items).
Status Code: 201 Created
Business Rules¶
-
Subscription Limit Enforcement:
-
Current outlet count checked against subscription plan limit
- Returns 402 Payment Required if limit exceeded
-
Deleted outlets count toward limit
-
Slug Uniqueness:
-
Slug must be unique within the tenant
- Returns 409 Conflict if duplicate slug exists
-
Case-insensitive uniqueness check
-
Business Hours Validation:
-
Time format must be HH:MM (24-hour)
- Open time must be before close time
- All 7 days must be defined (Monday=0 to Sunday=6)
-
Closed days: set
is_open: false -
Default Status:
-
If not provided, status defaults to
active - Newly created outlets are immediately operational
Error Responses¶
402 Payment Required - Subscription Limit:
409 Conflict - Duplicate Slug:
400 Bad Request - Invalid Business Hours:
400 Bad Request - Validation Error:
{
"detail": [
{
"loc": ["body", "contact", "email"],
"msg": "value is not a valid email address",
"type": "value_error.email"
}
]
}
Process Flow¶
- Subscription Limit Check - Validate outlet count against plan limit
- Slug Uniqueness Check - Ensure slug is unique within tenant
- Business Hours Validation - Validate time format and logic
- Outlet Creation - Create outlet record with tenant isolation
- Default Settings Initialization - Apply default operational settings
- Return Profile - Return complete outlet profile with ID
Get Outlet Details¶
Retrieve comprehensive outlet information by ID.
Endpoint¶
Authentication: Required (JWT token) Access: All staff members
Path Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
outlet_id |
string | Yes | Outlet ID (MongoDB ObjectId) |
Response¶
Returns complete outlet profile (same format as List Outlets items).
Error Responses¶
400 Bad Request - Invalid ID:
404 Not Found:
Note: Returns 404 if outlet doesn't exist or belongs to different tenant (tenant isolation).
Update Outlet Information¶
Update outlet information with validation and conflict checking.
Endpoint¶
Authentication: Required (JWT token) Access: TENANT_ADMIN, OUTLET_MANAGER, or SUPER_ADMIN
Path Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
outlet_id |
string | Yes | Outlet ID (MongoDB ObjectId) |
Request Body¶
All fields are optional. Only provided fields will be updated.
{
"name": "Updated Spa Name",
"description": "Updated description for the spa",
"status": "inactive",
"business_hours": [
{
"day": 0,
"is_open": true,
"open_time": "10:00",
"close_time": "20:00"
}
],
"settings": {
"advance_booking_days": 45,
"cancellation_hours": 48,
"timezone": "America/Los_Angeles"
}
}
Response¶
Returns updated outlet profile with all current information (same format as Get Outlet Details).
Business Rules¶
- Partial Updates:
- Only fields provided in request body are updated
- Unchanged fields remain as-is
-
Nested objects (settings, contact) can be partially updated
-
Slug Uniqueness:
- If slug is changed, uniqueness is re-validated
-
Returns 409 Conflict if new slug already exists
-
Business Hours Validation:
- If business_hours provided, full validation applies
- Time format and logic validated
-
Can update individual days or entire week
-
Protected Fields:
tenant_idcannot be changedcreated_atcannot be changedidcannot be changed
Error Responses¶
409 Conflict - Duplicate Slug:
400 Bad Request - Invalid Business Hours:
Delete Outlet¶
Soft delete an outlet with dependency validation.
Endpoint¶
Authentication: Required (JWT token) Access: TENANT_ADMIN or SUPER_ADMIN
Path Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
outlet_id |
string | Yes | Outlet ID (MongoDB ObjectId) |
Response¶
Status Code: 200 OK
Delete Behavior¶
Soft Delete (Default):
- Sets
is_deleted: trueflag - Changes status to
permanently_closed - Preserves all outlet data
- Excluded from normal queries
- Maintains referential integrity
- Can be restored by admin
Note: This endpoint performs soft delete only. Hard delete is not supported to maintain data integrity and audit trail.
Business Rules¶
-
Active Appointments Check:
-
Cannot delete outlet with active appointments
- Returns 409 Conflict if appointments exist
-
Must cancel/complete all appointments first
-
Data Preservation:
-
All outlet data preserved in database
- Appointment history remains intact
-
Staff assignments preserved
-
Subscription Impact:
-
Deleted outlets still count toward subscription limit
- To free up outlet slots, contact support for hard delete
Error Responses¶
409 Conflict - Active Appointments:
404 Not Found:
Get Outlet Statistics¶
Retrieve comprehensive performance statistics and analytics for an outlet.
Endpoint¶
Authentication: Required (JWT token) Access: All staff members
Path Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
outlet_id |
string | Yes | Outlet ID (MongoDB ObjectId) |
Query Parameters¶
| Parameter | Type | Required | Description | Example |
|---|---|---|---|---|
period |
string | No | Time period for statistics | "month" |
Period Options:
- week - Last 7 days
- month - Last 30 days (default)
- quarter - Last 90 days
- year - Last 365 days
Response¶
{
"outlet_id": "507f1f77bcf86cd799439011",
"outlet_name": "Downtown Beauty Spa",
"period": "month",
"overview": {
"total_appointments": 245,
"completed_appointments": 220,
"cancelled_appointments": 15,
"no_show_appointments": 10
},
"revenue": {
"total_revenue": 12500000.00,
"average_transaction": 51020.41,
"top_services": [
{
"service_id": "507f1f77bcf86cd799439020",
"service_name": "Hair Coloring",
"bookings": 85,
"revenue": 4250000.00
},
{
"service_id": "507f1f77bcf86cd799439021",
"service_name": "Facial Treatment",
"bookings": 65,
"revenue": 2600000.00
}
]
},
"customers": {
"total_customers": 180,
"new_customers": 45,
"returning_customers": 135,
"average_rating": 4.7
},
"staff": {
"total_staff": 12,
"utilization_rate": 78.5,
"top_performers": [
{
"staff_id": "507f1f77bcf86cd799439030",
"staff_name": "Sarah Johnson",
"completed_appointments": 42,
"revenue_generated": 2100000.00,
"average_rating": 4.9
}
]
},
"operational": {
"average_booking_lead_time_hours": 48,
"peak_hours": ["14:00", "15:00", "16:00"],
"busiest_days": ["Friday", "Saturday"]
},
"updated_at": "2025-10-08T10:00:00Z"
}
Metric Definitions¶
Overview Metrics:
total_appointments- Total appointments in periodcompleted_appointments- Successfully completed appointmentscancelled_appointments- Cancelled appointmentsno_show_appointments- Customers who didn't show up
Revenue Metrics:
total_revenue- Sum of all completed appointment paymentsaverage_transaction- Total revenue ÷ completed appointmentstop_services- Services ranked by revenue
Customer Metrics:
total_customers- Unique customers servednew_customers- First-time customers in periodreturning_customers- Repeat customersaverage_rating- Average customer satisfaction rating
Staff Metrics:
total_staff- Number of active staff at outletutilization_rate- % of available time with bookingstop_performers- Staff ranked by completed appointments
Operational Metrics:
average_booking_lead_time_hours- Hours between booking and appointmentpeak_hours- Hours with most appointmentsbusiest_days- Days of week with most appointments
Performance Notes¶
Data Sources:
- Aggregated from appointments, payments, and reviews collections
- Statistics cached and updated periodically
- Real-time updates for critical metrics
- May take several seconds for large datasets
Access Control¶
- All staff members can view outlet statistics
- Statistics scoped to current tenant only
- Cannot view statistics for outlets in other tenants
Related Endpoints¶
- Customer Analytics - Customer-focused metrics
- Subscription Usage Tracking - Tenant-wide usage
Get Business Hours¶
Retrieve complete business hours configuration for an outlet.
Endpoint¶
Authentication: Required (JWT token) Access: All staff members
Path Parameters¶
| Parameter | Type | Required | Description |
|---|---|---|---|
outlet_id |
string | Yes | Outlet ID (MongoDB ObjectId) |
Response¶
[
{
"day": 0,
"is_open": true,
"open_time": "09:00",
"close_time": "18:00"
},
{
"day": 1,
"is_open": true,
"open_time": "09:00",
"close_time": "18:00"
},
{
"day": 2,
"is_open": true,
"open_time": "09:00",
"close_time": "18:00"
},
{
"day": 3,
"is_open": true,
"open_time": "09:00",
"close_time": "18:00"
},
{
"day": 4,
"is_open": true,
"open_time": "09:00",
"close_time": "18:00"
},
{
"day": 5,
"is_open": true,
"open_time": "10:00",
"close_time": "16:00"
},
{
"day": 6,
"is_open": false,
"open_time": null,
"close_time": null
}
]
Business Hours Schema¶
| Field | Type | Description |
|---|---|---|
day |
integer | Day of week (0=Monday, 6=Sunday) |
is_open |
boolean | Whether outlet is open on this day |
open_time |
string | Opening time in HH:MM format (24-hour) |
close_time |
string | Closing time in HH:MM format (24-hour) |
Note: For closed days (is_open: false), open_time and close_time will be null.
Default Hours¶
If outlet has no custom business hours configured, returns default hours:
- Monday-Saturday: 09:00 - 18:00
- Sunday: Closed
Use Cases¶
Booking Availability:
- Check if outlet is open on specific day
- Validate appointment time within business hours
- Calculate available time slots
Display Hours:
- Show business hours on customer-facing pages
- Generate "Open Now" / "Closed" indicators
- Display next opening time
Best Practices¶
For Outlet Creation¶
✅ DO:
- Check current subscription limit before creating
- Use descriptive, unique slugs (e.g., "downtown-manhattan")
- Define business hours for all 7 days
- Set realistic advance booking days
- Configure timezone correctly for the outlet location
- Enable appropriate payment methods
❌ DON'T:
- Create outlets without checking subscription limits
- Use generic slugs (e.g., "outlet1", "location")
- Skip business hours configuration
- Use UTC timezone for all outlets (use local timezone)
- Enable all payment methods without verification
For Outlet Updates¶
✅ DO:
- Update business hours when schedule changes
- Adjust cancellation hours based on business needs
- Keep contact information current
- Use maintenance status during renovations
- Validate slug uniqueness when changing
❌ DON'T:
- Change slug frequently (breaks URLs)
- Update timezone without considering existing appointments
- Reduce advance_booking_days below existing bookings
- Change tenant_id (violates tenant isolation)
For Outlet Deletion¶
✅ DO:
- Cancel all active appointments first
- Notify customers of closure
- Update staff assignments
- Archive important data before deletion
- Use soft delete for data preservation
❌ DON'T:
- Delete outlets with pending appointments
- Skip customer notifications
- Hard delete without business approval
- Delete primary/flagship outlet without replacement
For Multi-Outlet Management¶
✅ DO:
- Use consistent naming conventions
- Standardize business hours across similar outlets
- Configure settings appropriately per location
- Monitor outlet statistics regularly
- Plan for subscription limit increases
❌ DON'T:
- Create too many outlets on FREE plan (max 1)
- Use identical slugs for different outlets
- Ignore local regulations per outlet location
- Set different cancellation policies without reason
Integration Examples¶
Create Outlet (Downtown Location)¶
curl -X POST https://api.example.com/api/v1/outlets \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Downtown Beauty Spa",
"slug": "downtown-spa",
"description": "Our flagship downtown location",
"status": "active",
"address": {
"street": "123 Main Street",
"city": "New York",
"state": "NY",
"postal_code": "10001",
"country": "US"
},
"contact": {
"phone": "+1234567890",
"email": "downtown@beautyspa.com"
},
"business_hours": [
{"day": 0, "is_open": true, "open_time": "09:00", "close_time": "18:00"},
{"day": 1, "is_open": true, "open_time": "09:00", "close_time": "18:00"},
{"day": 2, "is_open": true, "open_time": "09:00", "close_time": "18:00"},
{"day": 3, "is_open": true, "open_time": "09:00", "close_time": "18:00"},
{"day": 4, "is_open": true, "open_time": "09:00", "close_time": "18:00"},
{"day": 5, "is_open": true, "open_time": "10:00", "close_time": "16:00"},
{"day": 6, "is_open": false}
],
"settings": {
"accepts_online_booking": true,
"advance_booking_days": 30,
"cancellation_hours": 24,
"timezone": "America/New_York"
}
}'
List Active Outlets¶
curl -X GET "https://api.example.com/api/v1/outlets?status=active&page=1&size=20" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Update Business Hours (Extended Weekend Hours)¶
curl -X PUT https://api.example.com/api/v1/outlets/507f1f77bcf86cd799439011 \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"business_hours": [
{"day": 5, "is_open": true, "open_time": "09:00", "close_time": "20:00"},
{"day": 6, "is_open": true, "open_time": "10:00", "close_time": "18:00"}
]
}'
Get Outlet Statistics (Last Quarter)¶
curl -X GET "https://api.example.com/api/v1/outlets/507f1f77bcf86cd799439011/stats?period=quarter" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Search Outlets by Location¶
curl -X GET "https://api.example.com/api/v1/outlets?search=Manhattan&sort_by=name&sort_order=asc" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Error Handling¶
Common Error Codes¶
| Status Code | Error Type | Description | Resolution |
|---|---|---|---|
| 400 | Bad Request | Invalid input data | Check request body format and field values |
| 401 | Not Authenticated | Missing or invalid JWT token | Provide valid authentication token |
| 402 | Payment Required | Subscription limit exceeded | Upgrade subscription plan |
| 403 | Forbidden | Insufficient permissions | Check user role (requires TENANT_ADMIN+) |
| 404 | Not Found | Outlet not found | Verify outlet ID exists |
| 409 | Conflict | Duplicate slug or active appointments | Use different slug or resolve dependencies |
Example Error Responses¶
Subscription Limit Error:
Duplicate Slug Error:
Invalid Business Hours:
Tenant Isolation Error:
Note: Returns 404 even if outlet exists but belongs to different tenant (security).API Reference Summary¶
| Endpoint | Method | Purpose | Access Level |
|---|---|---|---|
/outlets |
GET | List all outlets with filtering | All staff |
/outlets |
POST | Create new outlet | TENANT_ADMIN+ |
/outlets/{outlet_id} |
GET | Get outlet details | All staff |
/outlets/{outlet_id} |
PUT | Update outlet | TENANT_ADMIN+ |
/outlets/{outlet_id} |
DELETE | Delete outlet (soft) | TENANT_ADMIN+ |
/outlets/{outlet_id}/stats |
GET | Get statistics | All staff |
/outlets/{outlet_id}/business-hours |
GET | Get business hours | All staff |
Related Documentation¶
- Subscription Management - Plan limits and upgrades
- Customer Management - Customer profiles and data
- User Management - Staff and role management
- Appointment Management - Booking system
Next Steps:
- Review current subscription plan and outlet limits
- Create outlets for each physical location
- Configure business hours and operational settings
- Set up payment methods and booking preferences
- Monitor outlet statistics and performance
- Upgrade subscription if more outlets needed
For subscription limit increases, refer to the Subscription Upgrade section.