Booking Flows
The Booking Flow entity manages the structured configuration of booking experiences within the scheduling system. Each booking flow represents a defined sequence of steps that guides users through the booking process, with validation to ensure the correct ordering of steps and data integrity. Booking flows are organization-specific and can be created, published, and managed through a RESTful API interface.
Schema Definition
type BookingFlow = BaseEntityWithOrganization & {
// Base Properties
_id: string; // MongoDB ObjectId
organization_id: string; // Required organization reference
content: { // Required content configuration
title: string; // Required flow title (1-100 chars)
};
steps: string[]; // Array of BookingFlowStep references
status: BookingFlowPublishedStatus; // Publication status
// Timestamps
createdAt: Date; // Auto-generated
updatedAt: Date; // Auto-updated
};
Field Descriptions
Base Properties
| Field | Type | Required | Unique | Description |
|---|---|---|---|---|
_id | ObjectId | Yes | Yes | Unique identifier for the booking flow |
organization_id | ObjectId | Yes | No | Organization that owns the booking flow |
content | BookingFlowContent | Yes | No | Content configuration for the flow |
content.title | string | Yes | No* | User-friendly title (1-100 characters) |
steps | ObjectId[] | Yes | No | Ordered array of booking flow step references |
status | BookingFlowPublishedStatus | Yes | No | Publication status of the booking flow |
*Title must be unique within an organization context
Status Types
enum BookingFlowPublishedStatus {
DRAFT = 'draft', // Flow is in draft mode, editable but not live
PUBLISHED = 'published', // Flow is published and available for bookings
DELETED = 'deleted' // Flow is marked as deleted and no longer available
}
API DTOs
Create DTO
type CreateBookingFlowDto = {
content: {
title: string; // Required, min length 1, max length 100
};
steps?: string[]; // Optional array of step IDs
};
Update DTO
type UpdateBookingFlowDto = {
content?: {
title: string; // Required if content is provided
};
steps?: string[]; // Optional array of step IDs
status?: BookingFlowPublishedStatus; // Optional status update
};
Filter DTO
type FilterBookingFlowDto = {
title?: string | { $regex: string }; // Optional title filter (exact or regex)
status?: BookingFlowPublishedStatus; // Optional status filter
populate?: boolean; // Whether to populate step references
};
Relationships
Primary Relationships
-
BookingFlow → Organization (Many-to-One, Required)
- Each flow must belong to a specific organization
- Referenced via
organization_idfield - Enforces multi-tenant isolation
- Repository methods validate organization ownership
-
BookingFlow → BookingFlowStep (One-to-Many, Required)
- Each flow contains an ordered sequence of steps
- Referenced via
stepsarray of ObjectIds - Steps must exist and belong to the same organization
- Steps can be optionally populated in API responses
Validation
Step Ordering Validation
The ordering of steps in a booking flow follows strict business rules based on the step types.
Step Type Requirements and Order Logic
| Step Type | Required? | Position Rules |
|---|---|---|
| Zipcode | No | If included, must be the 1st step in the flow |
| Service Selection | Yes* | Must be 1st step if no Zipcode step exists, or 2nd step if Zipcode step exists |
| Additional Details | No | If included, must follow Service Selection step (can be 2nd or 3rd depending on Zipcode presence) |
| Timeslot Selection | Yes* | Must always be the 3rd to last step in the sequence |
| Contact Info | Yes* | Must always be the 2nd to last step in the sequence |
| Summary | Yes* | Must always be the last step in the sequence |
*Required for PUBLISHED flows only. In DRAFT mode, no steps are required, but any included steps must follow the position rules.
Step Sequence Validation
-
DRAFT Status Flow Validation:
- Lenient validation to allow for work-in-progress flows
- Can have an empty steps array
- No steps are actually required in DRAFT mode
- Any steps that are included must follow the correct sequence
- Each step type can only occur once (no duplicates)
-
PUBLISHED Status Flow Validation:
- Strict validation to ensure complete and correct booking experiences
- Cannot have an empty steps array
- Must include ALL required steps (Service Selection, Timeslot Selection, Contact Info, Summary)
- All steps must follow the exact ordering rules
- Each step type can only occur once (no duplicates)
- Must form a coherent, end-to-end booking experience
Validation Rules
-
Duplicate Step Check
- No duplicate step IDs are allowed in the steps array
- Validation function converts step IDs to strings and checks uniqueness
- Returns a specific error message: 'Duplicate step IDs found'
-
Step Existence and Ownership
- All referenced steps must exist in the database
- Steps must belong to the same organization as the booking flow
- MongoDB query verifies both existence and ownership
- Query fetches step_type for each step to enable ordering validation
-
Step Type Ordering
- Steps are converted to
{type, _id}format for validation - Shared utility function
validateBookingFlowStepsenforces ordering rules - Different validation rules apply based on flow status
- Steps are converted to
-
Title Uniqueness
- Flow titles must be unique within an organization
- Repository has
existsByTitleInOrganizationmethod to check uniqueness - Update operations can exclude the current flow ID from uniqueness checks
Middleware Implementation
The booking flow schema implements mongoose middleware to ensure validation is consistent across all operations:
-
Document Middleware
- Pre-save hook validates document creation and updates via
save() - Only triggers when
stepsorstatusfields are modified - Uses
isModified()to efficiently skip validation when unnecessary
- Pre-save hook validates document creation and updates via
-
Query Middleware
- Pre-operation hooks for
findOneAndUpdate,updateOne,updateMany - Detects updates to
stepsorstatusfields in both direct and$setoperations - Fetches existing document to get the organization_id and unmodified fields
- Uses the same validation function as document middleware for consistency
- Pre-operation hooks for
Implementation Examples
Creating a Booking Flow
// Example API request to create a booking flow
const createDto = {
content: {
title: "Standard Service Booking"
},
steps: [
"60f7b0b9e6b3f3b4e8b4b0b9", // Zipcode step
"60f7b0b9e6b3f3b4e8b4b0ba", // Service Selection step
"60f7b0b9e6b3f3b4e8b4b0bb" // Timeslot Selection step
]
};
// Internal flow in service layer
const newFlow = await bookingFlowRepository.create({
organization_id: new Types.ObjectId(organizationId),
content: createDto.content,
steps: createDto.steps?.map(id => new Types.ObjectId(id)) || [],
// Default status is DRAFT
status: BookingFlowPublishedStatus.DRAFT
});
Publishing a Flow
// Example API request to update a flow's status
const updateDto = {
status: BookingFlowPublishedStatus.PUBLISHED
};
// Internal flow in service layer
const updatedFlow = await bookingFlowRepository.findByIdAndUpdate(
flowId,
{ status: BookingFlowPublishedStatus.PUBLISHED },
// Validation middleware will run to ensure the flow can be published
{ new: true }
);
Updating Steps
// Example API request to update steps
const updateDto = {
steps: [
"60f7b0b9e6b3f3b4e8b4b0b9", // Zipcode step
"60f7b0b9e6b3f3b4e8b4b0ba", // Service Selection step
"60f7b0b9e6b3f3b4e8b4b0bd", // Additional Details step
"60f7b0b9e6b3f3b4e8b4b0bb" // Timeslot Selection step
]
};
// Internal flow in repository
const result = await this.bookingFlowModel.findOneAndUpdate(
{
_id: new Types.ObjectId(id),
organization_id: new Types.ObjectId(organizationId)
},
{ $set: { steps: updateDto.steps.map(id => new Types.ObjectId(id)) } },
{ new: true }
);
Security Considerations
-
Multi-Tenant Isolation
- Organization-scoped validation enforced in repository methods
- Pre-hook validation confirms step ownership before save/update
- Controller extracts organization ID from authenticated session
- All API endpoints require appropriate permissions
-
Status Transitions
- Validation rules are stricter for PUBLISHED flows than DRAFT flows
- Service layer enforces business rules for status transitions
- BadRequestException thrown for invalid transitions
-
Title Uniqueness
- Repository checks title uniqueness within organization scope
- Update operations exclude the current document from uniqueness checks
- Clear error messages for title conflict resolution
-
Input Validation
- DTOs use class-validator for request validation
- Mongoose schema enforces data structure integrity
- Custom validators ensure business rules compliance
Performance Considerations
-
Indexes
- Compound index on
{ organization_id: 1, status: 1 } - Optimizes organization-scoped queries filtered by status
- Used by
findByOrganizationIdandfindPublishedByOrganizationIdmethods
- Compound index on
-
Step Population
- Optional step population controlled via query parameter
- Population uses discriminator support for polymorphic step types
- Configurable through
populateConfigin repository
-
Validation Optimization
- Early exit patterns for validation functions
- Skips validation when no relevant fields are modified
- Batches database queries for step validation
-
Query Efficiency
- Lean queries used for validation to reduce memory overhead
- Repository implements normalized and non-normalized response options
- Selective population of referenced entities