Skip to main content

Events & Experiences - Domain Specification

First Introduced: V2.0 Status: Specification Complete Last Updated: 2025-10-25


Overview

Events & Experiences is a new product line in V2.0 that extends the villa rental platform to include event management, ticketing, and experience booking. This domain enables event organizers to create and manage events/experiences, guests to discover and book activities, and property managers to bundle events with villa stays for enhanced packages. The domain handles event definitions, scheduling, capacity management, ticket sales, and attendee tracking.


Responsibilities

This domain IS responsible for:

  • Event and experience definition and lifecycle management
  • Event occurrence scheduling (one-time and recurring events)
  • Ticket type definition and inventory management
  • Ticket sales and attendee registration
  • Event capacity tracking and enforcement
  • Waitlist management for sold-out events
  • Event cancellation and refund coordination
  • Event discovery and search integration
  • Package creation (villa + event bundles)

This domain is NOT responsible for:

  • Payment processing (→ Payments & Financials domain)
  • User identity and authentication (→ Identity & Tenancy domain)
  • Authorization and permissions (→ Authorization & Access domain)
  • Content storage and media assets (→ Content & Media domain)
  • Search indexing implementation (→ Search & Indexing domain)
  • Audit trail logging (→ Analytics & Audit domain)
  • Villa availability management (→ Availability & Calendars domain)

Relationships

Depends On:

Depended On By:

Related Domains:


Core Concepts

Entity: EventDefinition

Purpose: Template/master record for an event or experience, defining what it is and how it operates.

Key Attributes:

  • id (UUID, primary key)
  • org_id (UUID, foreign key → organizations.id)
  • account_id (UUID, foreign key → accounts.id)
  • name (VARCHAR, required) - Event name (e.g., "Beach Yoga Sunrise Session")
  • slug (VARCHAR, unique per org) - URL-friendly identifier
  • category (ENUM) - tour | activity | class | workshop | dining | wellness | cultural | adventure | entertainment | other
  • event_type (ENUM) - one_time | recurring | multi_day
  • duration_minutes (INTEGER) - Standard duration per occurrence
  • venue_type (ENUM) - on_property | off_property | virtual | mobile
  • venue_space_id (UUID, nullable, foreign key → spaces.id) - If event happens at a specific Space
  • venue_name (VARCHAR) - Venue name if not a Space
  • venue_address (JSONB) - Venue location details
  • organizer_name (VARCHAR) - Name of event organizer/instructor
  • organizer_contact (JSONB) - Contact info for organizer
  • max_capacity (INTEGER, nullable) - Maximum attendees per occurrence (null = unlimited)
  • min_capacity (INTEGER, default 1) - Minimum attendees to run event
  • status (ENUM) - draft | active | paused | archived
  • settings (JSONB) - Event-specific configuration
  • created_at, updated_at (timestamps)

Relationships:

  • EventDefinition → Org (*, many-to-one)
  • EventDefinition → Account (*, many-to-one)
  • EventDefinition → Space (*, many-to-one, optional venue)
  • EventDefinition → EventOccurrence (1:*)
  • EventDefinition → TicketType (1:*)
  • EventDefinition → Package (1:*) - Events can be in multiple packages

Lifecycle:

  • Created: Event organizer creates event template
  • Activated: Set to status='active' when ready for bookings
  • Paused: Temporarily disable new bookings
  • Archived: No longer offered, historical record only

Business Rules:

  • Event definitions scoped to Org + Account (organizer)
  • Slug must be unique within org_id
  • If venue_space_id is set, event location is tied to that Space
  • Cannot delete EventDefinition with active EventOccurrences (archive only)
  • Multi-day events have event_type='multi_day' and multiple linked occurrences

Entity: EventOccurrence

Purpose: Specific instance of an event happening at a particular date/time.

Key Attributes:

  • id (UUID, primary key)
  • org_id (UUID, foreign key → organizations.id)
  • account_id (UUID, foreign key → accounts.id)
  • event_definition_id (UUID, foreign key → event_definitions.id)
  • occurrence_name (VARCHAR, nullable) - Optional override name
  • start_at (TIMESTAMP WITH TIME ZONE, required)
  • end_at (TIMESTAMP WITH TIME ZONE, required)
  • timezone (VARCHAR) - Event timezone (e.g., "America/New_York")
  • capacity_override (INTEGER, nullable) - Override max_capacity for this occurrence
  • status (ENUM) - scheduled | confirmed | in_progress | completed | canceled
  • cancellation_reason (TEXT, nullable)
  • canceled_at (TIMESTAMP, nullable)
  • seats_sold (INTEGER, default 0) - Current ticket sales count
  • seats_available (INTEGER GENERATED) - Computed: capacity - seats_sold
  • waitlist_count (INTEGER, default 0)
  • settings (JSONB) - Occurrence-specific overrides
  • created_at, updated_at (timestamps)

Relationships:

  • EventOccurrence → EventDefinition (*, many-to-one)
  • EventOccurrence → Org (*, many-to-one)
  • EventOccurrence → Account (*, many-to-one)
  • EventOccurrence → Ticket (1:*)
  • EventOccurrence → Attendee (1:*)
  • EventOccurrence → WaitlistEntry (1:*)

Lifecycle:

  • Created: When event organizer schedules specific date/time
  • Confirmed: Minimum capacity reached, event will proceed
  • In Progress: Event currently happening
  • Completed: Event finished successfully
  • Canceled: Event canceled (triggers refunds)

Business Rules:

  • end_at must be after start_at
  • Cannot schedule overlapping occurrences for same venue
  • Capacity enforced: seats_sold cannot exceed capacity_override or event_definition.max_capacity
  • Status transitions: scheduled → confirmed → in_progress → completed
  • Cancellation allowed up to cutoff time (defined in settings)
  • Canceled events retain all data for refund processing

Entity: TicketType

Purpose: Defines pricing tiers and ticket categories for an event.

Key Attributes:

  • id (UUID, primary key)
  • org_id (UUID, foreign key → organizations.id)
  • account_id (UUID, foreign key → accounts.id)
  • event_definition_id (UUID, foreign key → event_definitions.id)
  • name (VARCHAR, required) - e.g., "General Admission", "VIP", "Early Bird"
  • description (TEXT) - What this ticket includes
  • tier (ENUM) - general | vip | early_bird | group | child | senior | student | member
  • price_cents (INTEGER, required) - Base price in minor currency units
  • currency (CHAR(3), default 'USD')
  • quantity_available (INTEGER, nullable) - Limited quantity (null = unlimited)
  • quantity_sold (INTEGER, default 0)
  • valid_from (TIMESTAMP, nullable) - When this ticket type becomes available
  • valid_until (TIMESTAMP, nullable) - When this ticket type expires
  • min_quantity (INTEGER, default 1) - Minimum purchase (e.g., 2 for couples ticket)
  • max_quantity (INTEGER, default 10) - Maximum per transaction
  • status (ENUM) - active | sold_out | expired | disabled
  • settings (JSONB) - Additional rules (e.g., discount codes, restrictions)
  • created_at, updated_at (timestamps)

Relationships:

  • TicketType → EventDefinition (*, many-to-one)
  • TicketType → Org (*, many-to-one)
  • TicketType → Account (*, many-to-one)
  • TicketType → Ticket (1:*) - Sold tickets of this type

Lifecycle:

  • Created: When event organizer defines pricing tiers
  • Activated: Available for purchase
  • Sold Out: All quantity_available tickets sold
  • Expired: Past valid_until date
  • Disabled: Manually paused by organizer

Business Rules:

  • At least one TicketType required per EventDefinition
  • quantity_sold cannot exceed quantity_available
  • valid_from must be before valid_until
  • Price must be non-negative
  • Status auto-updates: sold_out when quantity_sold >= quantity_available
  • Expired tickets cannot be purchased even if quantity remains

Entity: Ticket

Purpose: Individual ticket instance representing a sold ticket for an event occurrence.

Key Attributes:

  • id (UUID, primary key)
  • org_id (UUID, foreign key → organizations.id)
  • account_id (UUID, foreign key → accounts.id)
  • event_occurrence_id (UUID, foreign key → event_occurrences.id)
  • ticket_type_id (UUID, foreign key → ticket_types.id)
  • attendee_id (UUID, foreign key → attendees.id)
  • booking_id (UUID, nullable, foreign key → bookings.id) - If part of villa package
  • confirmation_code (VARCHAR, unique) - Human-readable code
  • qr_code_data (TEXT) - QR code payload for check-in
  • price_paid_cents (INTEGER) - Actual price paid (may differ from ticket_type.price)
  • currency (CHAR(3))
  • status (ENUM) - reserved | confirmed | checked_in | canceled | refunded
  • reserved_at (TIMESTAMP) - When ticket was reserved (holds inventory)
  • confirmed_at (TIMESTAMP) - When payment completed
  • checked_in_at (TIMESTAMP, nullable) - When attendee arrived
  • canceled_at (TIMESTAMP, nullable)
  • refund_amount_cents (INTEGER, default 0)
  • settings (JSONB) - Ticket-specific metadata
  • created_at, updated_at (timestamps)

Relationships:

  • Ticket → EventOccurrence (*, many-to-one)
  • Ticket → TicketType (*, many-to-one)
  • Ticket → Attendee (*, many-to-one)
  • Ticket → Booking (*, many-to-one, optional if part of villa package)
  • Ticket → Payment (via booking or direct payment)

Lifecycle:

  • Reserved: Ticket held during checkout (TTL 15 minutes)
  • Confirmed: Payment succeeded, ticket issued
  • Checked In: Attendee scanned at event
  • Canceled: Ticket voided before event
  • Refunded: Payment returned to customer

Business Rules:

  • Confirmation code must be globally unique
  • Reserved tickets expire after 15 minutes if not confirmed
  • Cannot check in before event start_at time
  • Canceled tickets release capacity back to occurrence
  • Refund policies determined by event_definition.settings
  • QR code generated on confirmation, unique per ticket

Entity: Attendee

Purpose: Person attending an event (may or may not be a registered User).

Key Attributes:

  • id (UUID, primary key)
  • org_id (UUID, foreign key → organizations.id)
  • user_id (UUID, nullable, foreign key → users.id) - If attendee is a registered user
  • email (VARCHAR, required) - Contact email
  • given_name (VARCHAR, required)
  • family_name (VARCHAR)
  • phone (VARCHAR, nullable)
  • emergency_contact (JSONB, nullable) - Emergency contact info
  • dietary_restrictions (TEXT[], nullable)
  • accessibility_needs (TEXT[], nullable)
  • notes (TEXT, nullable) - Special requests
  • created_at, updated_at (timestamps)

Relationships:

  • Attendee → Org (*, many-to-one)
  • Attendee → User (*, many-to-one, optional)
  • Attendee → Ticket (1:*)
  • Attendee → WaitlistEntry (1:*)

Lifecycle:

  • Created: When ticket is purchased or waitlist entry added
  • Updated: Attendee updates their information
  • Linked to User: If attendee creates account later

Business Rules:

  • Email required for ticket delivery and communication
  • If user_id is set, attendee is a registered platform user
  • Same person can attend multiple events (multiple Attendee records)
  • Dietary and accessibility info used for event planning
  • GDPR compliant: attendees can request data deletion

Entity: WaitlistEntry

Purpose: Queue for sold-out events, tracks people waiting for available tickets.

Key Attributes:

  • id (UUID, primary key)
  • org_id (UUID, foreign key → organizations.id)
  • event_occurrence_id (UUID, foreign key → event_occurrences.id)
  • attendee_id (UUID, foreign key → attendees.id)
  • position (INTEGER) - Queue position (1 = first in line)
  • quantity_requested (INTEGER, default 1)
  • ticket_type_id (UUID, nullable, foreign key → ticket_types.id) - Preferred ticket type
  • status (ENUM) - waiting | notified | converted | expired | canceled
  • notified_at (TIMESTAMP, nullable) - When availability notification sent
  • expires_at (TIMESTAMP, nullable) - When notification expires
  • converted_ticket_id (UUID, nullable, foreign key → tickets.id)
  • created_at, updated_at (timestamps)

Relationships:

  • WaitlistEntry → EventOccurrence (*, many-to-one)
  • WaitlistEntry → Attendee (*, many-to-one)
  • WaitlistEntry → TicketType (*, many-to-one, optional)
  • WaitlistEntry → Ticket (1:1, optional after conversion)

Lifecycle:

  • Created: When user joins waitlist for sold-out event
  • Notified: Ticket becomes available, notification sent
  • Converted: User purchased ticket from waitlist
  • Expired: Notification timeout passed without purchase
  • Canceled: User left waitlist

Business Rules:

  • Position auto-assigned based on creation timestamp
  • Notification sent when capacity opens up
  • 24-hour window to purchase after notification
  • One waitlist entry per attendee per occurrence
  • Converted entries create Ticket and remove from waitlist

Entity: EventPackage

Purpose: Bundles events with villa stays or combines multiple events into packages.

Key Attributes:

  • id (UUID, primary key)
  • org_id (UUID, foreign key → organizations.id)
  • account_id (UUID, foreign key → accounts.id)
  • name (VARCHAR, required) - e.g., "Ultimate Wellness Retreat"
  • slug (VARCHAR, unique per org)
  • package_type (ENUM) - villa_event | multi_event | experience_bundle
  • description (TEXT)
  • base_price_cents (INTEGER, nullable) - Package price override
  • currency (CHAR(3), default 'USD')
  • discount_percent (DECIMAL, default 0) - Discount when purchased as package
  • status (ENUM) - draft | active | paused | archived
  • valid_from (DATE, nullable)
  • valid_until (DATE, nullable)
  • settings (JSONB) - Package rules and inclusions
  • created_at, updated_at (timestamps)

Relationships:

  • EventPackage → Org (*, many-to-one)
  • EventPackage → Account (*, many-to-one)
  • EventPackage → EventPackageItem (1:*)
  • EventPackage → Booking (1:*) - Bookings using this package

Lifecycle:

  • Created: Property manager or event organizer creates bundle
  • Activated: Available for guest purchase
  • Paused: Temporarily unavailable
  • Archived: No longer offered

Business Rules:

  • Package must include at least 1 item (event or villa)
  • Discount applies to total of individual component prices
  • All package components must be available for selected dates
  • Package booking creates individual tickets for each event
  • Cannot modify package while active bookings exist

Entity: EventPackageItem

Purpose: Links specific events or villas to packages.

Key Attributes:

  • id (UUID, primary key)
  • package_id (UUID, foreign key → event_packages.id)
  • item_type (ENUM) - event | villa
  • event_definition_id (UUID, nullable, foreign key → event_definitions.id)
  • space_id (UUID, nullable, foreign key → spaces.id)
  • quantity (INTEGER, default 1) - Number of tickets/nights included
  • is_required (BOOLEAN, default true) - Must be selected
  • is_customizable (BOOLEAN, default false) - Guest can choose quantity
  • position (INTEGER) - Display order
  • created_at, updated_at (timestamps)

Relationships:

  • EventPackageItem → EventPackage (*, many-to-one)
  • EventPackageItem → EventDefinition (*, many-to-one, if event)
  • EventPackageItem → Space (*, many-to-one, if villa)

Business Rules:

  • Either event_definition_id or space_id must be set (not both)
  • Required items must be included in package booking
  • Customizable items allow guest to adjust quantity
  • Position determines display order in UI

Workflows

Workflow: Create Recurring Event

  1. Event organizer creates EventDefinition with event_type='recurring'
  2. Define recurrence rule in settings (e.g., "Every Tuesday 7am-8am")
  3. Generate EventOccurrences for next 90 days (batch job)
  4. Create TicketTypes (e.g., "General Admission $25", "Early Bird $20")
  5. Activate EventDefinition (status='active')
  6. Publish to search index for guest discovery
  7. Return success with event_definition_id

Postconditions:

  • Event appears in search results
  • Guests can browse and book any future occurrence
  • Organizer can manage all occurrences from one definition

Workflow: Book Event Ticket

  1. Guest searches events by date, category, location
  2. Select event occurrence and ticket type
  3. Specify quantity and attendee details
  4. Acquire capacity lock on EventOccurrence
  5. Create Ticket records with status='reserved'
  6. Create Attendee records for each person
  7. Generate Quote with ticket prices and fees
  8. Hold reservation for 15 minutes (TTL)
  9. Guest completes payment via Payments domain
  10. On payment success:
    • Update Tickets to status='confirmed'
    • Generate QR codes and confirmation codes
    • Send confirmation email with ticket PDFs
    • Update EventOccurrence.seats_sold
  11. On timeout: Release tickets, decrement seats_sold

Postconditions:

  • Tickets reserved in guest's account
  • Capacity updated to reflect sales
  • Payment recorded and revenue split applied
  • Confirmation sent with event details

Workflow: Join Waitlist

  1. Guest attempts to book sold-out event
  2. System offers waitlist option
  3. Guest provides attendee details and quantity
  4. Create WaitlistEntry with next available position
  5. Send confirmation email "You're #5 on the waitlist"
  6. When ticket becomes available (cancellation or capacity increase):
    • Find first WaitlistEntry with matching quantity
    • Send notification email with purchase link
    • Set status='notified', expires_at=now()+24h
  7. If guest purchases: Convert WaitlistEntry → Ticket
  8. If expires: Move to next person in queue

Postconditions:

  • Guest queued for notification
  • Auto-notification when capacity opens
  • Fair queue management (FIFO)

Workflow: Create Villa + Event Package

  1. Property manager creates EventPackage with package_type='villa_event'
  2. Add package items:
    • EventPackageItem: space_id=villa123, quantity=5 (nights)
    • EventPackageItem: event_definition_id=cooking_class, quantity=2 (tickets)
    • EventPackageItem: event_definition_id=wine_tour, quantity=2
  3. Set discount (e.g., 15% off total)
  4. Define date constraints (valid_from/valid_until)
  5. Activate package (status='active')
  6. Publish to search and package catalog
  7. Guest books package:
    • Creates Booking for villa stay
    • Creates Tickets for each event
    • Links all via package_id
    • Applies package discount to total
  8. Return unified confirmation with itinerary

Postconditions:

  • Single booking with multiple components
  • Coordinated availability across villa and events
  • Unified payment and receipt
  • Enhanced guest experience

Business Rules

  1. Capacity Enforcement: seats_sold cannot exceed max_capacity (atomic lock required)
  2. Reservation Timeout: Reserved tickets auto-release after 15 minutes if not confirmed
  3. Event Timing: Occurrences cannot overlap for same venue/space
  4. Cancellation Cutoff: Events can be canceled up to configured hours before start_at
  5. Minimum Capacity: Events with min_capacity auto-cancel if not met by cutoff
  6. Refund Policy: Defined per EventDefinition, processed via Payments domain
  7. QR Code Uniqueness: Each ticket has unique QR code for check-in scanning
  8. Package Availability: All package components must be available for selected dates
  9. Waitlist Notification: 24-hour window to purchase after notification
  10. Multi-Day Events: Linked occurrences with same event_definition_id and sequential dates

Implementation Notes

MVP.0 Scope

OUT OF SCOPE for MVP.0:

  • Entire Events & Experiences domain deferred to V2.0
  • MVP.0 focuses only on villa rentals

V1.0 Scope

OUT OF SCOPE for V1.0:

  • Events & Experiences domain not included
  • V1.0 focuses on villa marketplace maturity

V2.0 Scope (Full Implementation)

Included:

  • Complete Events & Experiences domain
  • Event definitions and occurrence scheduling
  • Ticket types and sales
  • Attendee management and check-in
  • Waitlist functionality
  • Villa + event package creation
  • Event search and discovery
  • Integration with existing villa platform

Implementation Phases:

V2.0 Phase 1 (Months 13-14):

  • EventDefinition, EventOccurrence, TicketType entities
  • Basic ticket sales and attendee records
  • One-time and recurring event support
  • QR code generation for check-in

V2.0 Phase 2 (Months 15-16):

  • Waitlist management
  • EventPackage for villa bundles
  • Multi-event packages
  • Discount and pricing rules

V2.0 Phase 3 (Months 17-18):

  • Event search optimization
  • Analytics and reporting
  • Mobile check-in app
  • Organizer dashboard

Database Indexes

Critical for performance:

-- Event lookups
CREATE INDEX idx_event_definitions_org_account ON event_definitions(org_id, account_id);
CREATE INDEX idx_event_definitions_status ON event_definitions(status) WHERE status = 'active';
CREATE INDEX idx_event_definitions_category ON event_definitions(category);

-- Occurrence queries
CREATE INDEX idx_event_occurrences_definition ON event_occurrences(event_definition_id);
CREATE INDEX idx_event_occurrences_time ON event_occurrences(start_at, end_at);
CREATE INDEX idx_event_occurrences_status ON event_occurrences(status);

-- Ticket management
CREATE INDEX idx_tickets_occurrence ON tickets(event_occurrence_id);
CREATE INDEX idx_tickets_attendee ON tickets(attendee_id);
CREATE INDEX idx_tickets_confirmation ON tickets(confirmation_code);
CREATE INDEX idx_tickets_status ON tickets(status);

-- Attendee lookups
CREATE INDEX idx_attendees_email ON attendees(email);
CREATE INDEX idx_attendees_user ON attendees(user_id) WHERE user_id IS NOT NULL;

-- Waitlist processing
CREATE INDEX idx_waitlist_occurrence_position ON waitlist_entries(event_occurrence_id, position);
CREATE INDEX idx_waitlist_status ON waitlist_entries(status) WHERE status = 'waiting';

-- Package queries
CREATE INDEX idx_event_packages_org_account ON event_packages(org_id, account_id);
CREATE INDEX idx_package_items_package ON event_package_items(package_id);

Constraints

Enforce data integrity:

-- Event definitions
ALTER TABLE event_definitions ADD CONSTRAINT event_def_org_slug_unique UNIQUE (org_id, slug);
ALTER TABLE event_definitions ADD CONSTRAINT event_def_capacity_check CHECK (max_capacity IS NULL OR max_capacity > 0);

-- Event occurrences
ALTER TABLE event_occurrences ADD CONSTRAINT event_occ_time_check CHECK (end_at > start_at);
ALTER TABLE event_occurrences ADD CONSTRAINT event_occ_seats_check CHECK (seats_sold >= 0);

-- Ticket types
ALTER TABLE ticket_types ADD CONSTRAINT ticket_type_price_check CHECK (price_cents >= 0);
ALTER TABLE ticket_types ADD CONSTRAINT ticket_type_quantity_check CHECK (quantity_sold <= quantity_available);

-- Tickets
ALTER TABLE tickets ADD CONSTRAINT ticket_confirmation_unique UNIQUE (confirmation_code);
ALTER TABLE tickets ADD CONSTRAINT ticket_qr_unique UNIQUE (qr_code_data);

-- Packages
ALTER TABLE event_packages ADD CONSTRAINT package_org_slug_unique UNIQUE (org_id, slug);
ALTER TABLE event_package_items ADD CONSTRAINT package_item_type_check
CHECK ((event_definition_id IS NOT NULL AND space_id IS NULL) OR
(event_definition_id IS NULL AND space_id IS NOT NULL));

Future Enhancements

V2.1: Advanced Event Features

  • Multi-session events (courses, workshops)
  • Event series and memberships
  • Loyalty points for repeat attendees
  • Gift vouchers and promotional codes

V2.2: Enhanced Ticketing

  • Dynamic pricing based on demand
  • Group booking discounts
  • Corporate event management
  • Private event bookings

V2.3: Organizer Tools

  • Revenue analytics dashboard
  • Attendee communication tools
  • Check-in mobile app with offline mode
  • Automated event reminders

V3.0: Marketplace Expansion

  • White-label event platforms
  • Multi-vendor event marketplace
  • Franchise/partner organizer programs
  • Global event search and syndication

Integration Points

With Existing Domains

Identity & Tenancy:

  • Event organizers are Accounts within Orgs
  • Attendees optionally linked to User records
  • Multi-tenant event isolation

Authorization & Access:

  • Permissions: event.create, event.manage, ticket.sell, attendee.view
  • Organizers manage only their events
  • Package creators need both event and villa permissions

Supply (Spaces & Units):

  • Events can specify venue via space_id
  • Packages link villas and events
  • Venue capacity constraints

Availability & Calendars:

  • Event occurrences consume time slots
  • Integrated calendar view (villas + events)
  • Conflict detection for shared venues

Pricing & Revenue:

  • TicketType pricing uses RatePlan concepts
  • Revenue splits between organizers and platform
  • Package discounting rules

Payments & Financials:

  • Ticket sales create Payment records
  • Refunds processed through existing flows
  • Payout to event organizers

Bookings:

  • Event packages extend Booking entity
  • Combined villa + event reservations
  • Unified confirmation and itinerary

Content & Media:

  • Event photos and descriptions
  • Organizer profiles and bios
  • Marketing assets

Search & Indexing:

  • Event discovery by date/location/category
  • Featured events and recommendations
  • Availability-aware search

Analytics & Audit:

  • Event performance metrics
  • Sales and attendance tracking
  • Audit trail for cancellations and refunds