Skip to main content

Event & Session Management

Complete guide to event management, session scheduling, and reservation systems.

Event System Overview

Lancaster Archery Academy manages diverse event types including classes, tournaments, subscriptions, and private parties through a flexible event system.

Event Types

Regular Classes

  • Single or multi-session programs
  • Fixed schedule with defined dates
  • Seat-based capacity management
  • One-time registration and payment

Subscription Events

  • Recurring enrollment with monthly billing
  • Timeslot-based scheduling (preferred day/time)
  • Automatic session and reservation generation
  • Enrollment fees (optional)
  • Cancellation anytime with period-end or immediate effect

Tournaments

  • Division-based registration (age, gender, equipment)
  • Team support for mixed events (parent+child)
  • IANSEO export for tournament management software
  • Waitlist management
  • Public registration lists

Private Parties

  • Custom scheduling
  • Invite-only or open registration
  • Merchandise and add-ons
  • Flexible pricing

Event Configuration

Nova Resource Fields

Basic Information:

  • Name, Short Description
  • Event Type, Category, Level
  • Environment (Indoor/Outdoor)
  • Location Assignment

Capacity & Scheduling:

  • Max Seats per session
  • Day Span (number of sessions)
  • Session Length (duration)
  • Price per session

Registration:

  • Registration Deadline
  • Early Bird Deadline & Pricing
  • Registration Policy (open, invite-only, approval required)
  • Age Range (min/max)

Advanced Features:

  • Enrollment Fee (for subscriptions)
  • Volume Discount tool
  • Milestones (requirements/achievements)
  • Event Options (equipment rental, extras)
  • Flexible Content blocks
  • Gallery & Downloads (media library)

Session Management

Manual Session Creation

Via Nova:

  1. Navigate to Event detail page
  2. Click "Sessions" tab
  3. Click "Create Session"
  4. Set start/end dates and times
  5. Assign location and instructor
  6. Set seat quantity
  7. Mark as "User Defined" if custom

Automated Session Generation

Generate Sessions Nova Action:

// Parameters:
- Start Date
- End Date
- Time (start time)
- Duration (session length)
- Recurrence Pattern (daily, weekly, custom days)
- Seat Quantity
- Location
- Exclude Dates (holidays, blackouts)

Example: Generate weekly Monday sessions from Jan 1 - Mar 31, 6:00 PM - 7:30 PM

  • Creates ~12 session records automatically
  • All sessions linked to parent event
  • Configurable seat capacity
  • Skip weekends/holidays option

Recurring Sessions

Parent-Child Relationships:

sessions.parent_id → sessions.id

// Parent session defines pattern
// Child sessions inherit configuration
// Modify all children via parent

Timeslots (for subscriptions):

timeslots (
event_id, location_id,
day_of_week, start_time, end_time,
seat_quantity
)

// Subscription selects timeslot preference
// Sessions matching timeslot auto-generate
// Reservations created for matching sessions

Reservation System

Reservation Lifecycle

1. Cart Addition:

// User selects sessions
// Creates pending Order (status=0, expiration=15min)
// Creates pending Reservations linked to Order

2. Cart Expiration:

// 15-minute countdown timer
// If expired: release seats, delete reservations
// Command: app/Console/Commands/ExpireOrders.php

3. Checkout:

// Validate:
// - Session availability (no overbooking)
// - Waiver requirements
// - Payment method exists
// - Age requirements met (if applicable)

4. Payment:

// Process via Authorize.Net CIM
// Create Transaction record
// Update Order status=1
// Update Reservation status=1

5. Confirmation:

// Send confirmation emails
// Fire OrderPaid event
// Generate attendance codes
// Link waivers if signed

Reservation Types

Standard Reservation:

  • Account linked to Session
  • Seats quantity (usually 1)
  • Base cost (captured at time of registration)
  • Status (pending/confirmed/canceled)

Tournament Reservation:

  • Division selection (age/gender/equipment)
  • Team assignment (if team event)
  • Stays on line preference
  • Practice pass option

Subscription-Generated Reservation:

  • Created automatically for subscription
  • Linked to subscription_id
  • No immediate payment (billed monthly)
  • Cannot be individually canceled (cancel subscription instead)

Waitlist System

Waitlist Entry

When Session is Full:

// User can join waitlist
Waitlist::create([
'session_id' => $session->id,
'first_name' => $account->first_name,
'last_name' => $account->last_name,
'email' => $account->email,
'phone' => $account->phone,
'seats_quantity' => 1,
'status' => 0, // pending
]);

Waitlist Notification Flow

1. Space Becomes Available (cancellation or capacity increase):

// Find oldest waitlist entry
$waitlist = Waitlist::where('session_id', $session->id)
->where('status', 0)
->orderBy('created_at')
->first();

2. Send Notification:

Mail::to($waitlist->email)->send(new WaitListNotification($waitlist));

$waitlist->update([
'contacted_at' => now(),
'status' => 1, // contacted
]);

3. User Response:

// User clicks link in email
// Creates reservation
// Marks waitlist entry: reservation_created = true

Nova Management:

  • View all waitlists per session
  • Manual contact tracking
  • Status updates (pending/contacted/replied/registered)
  • Bulk notification actions

Session Availability Checking

Real-Time Availability

Model Method (app/Models/Session.php):

public function availableSeats(): int
{
$reserved = $this->reservations()
->whereIn('status', [0, 1]) // pending or confirmed
->sum('seats');

return max(0, $this->seat_quantity - $reserved);
}

public function isFull(): bool
{
return $this->availableSeats() === 0;
}

public function hasSpace(int $seats = 1): bool
{
return $this->availableSeats() >= $seats;
}

Conflict Detection

Reservation Conflict Check:

public static function hasConflict($account, $session): bool
{
// Check if account already registered for overlapping session
return Reservation::where('account_id', $account->id)
->whereHas('session', function($query) use ($session) {
$query->where('start_date', '<', $session->end_date)
->where('end_date', '>', $session->start_date);
})
->whereIn('status', [0, 1])
->exists();
}

Override Option:

// Sessions can have ignore_date_conflicts = true
// Allows overlapping registrations (e.g., open range time)

Volume Pricing

Configuration

Suma Nova Component (nova-components/VolumeDiscount):

// In Event Nova resource
VolumeDiscount::make('Volume Pricing')
->rules('nullable', 'json')
->help('Configure tiered pricing based on session quantity')

JSON Structure:

[
{
"quantity": 5,
"discount_type": "percent",
"amount": 10
},
{
"quantity": 10,
"discount_type": "fixed",
"amount": 50
}
]

Application

Checkout Calculation:

public static function calculateOrderTotal($reservations)
{
$sessionCount = $reservations->count();
$basePrice = $reservations->first()->session->event->price;
$subtotal = $sessionCount * $basePrice;

// Apply volume discount
$discount = self::calculateVolumeDiscount($sessionCount, $basePrice, $volumePricing);

$total = $subtotal - $discount;

return compact('subtotal', 'discount', 'total');
}

Coupons

Coupon Structure

coupons (
code, discount_type, amount,
min_purchase, usage_limit, customer_usage_limit,
start_date, end_date,
event_types (JSON), event_type_exclusions (JSON)
)

Validation

Checkout Application:

public function validateCoupon($code, $order): ?Coupon
{
$coupon = Coupon::where('code', $code)->first();

// Check existence
if (!$coupon) return null;

// Check date validity
if ($coupon->start_date > now() || $coupon->end_date < now()) {
return null;
}

// Check usage limits
if ($coupon->usage_limit && $coupon->redemptions >= $coupon->usage_limit) {
return null;
}

// Check customer usage
$customerUses = CouponRedemption::where('coupon_id', $coupon->id)
->where('user_id', auth()->id())
->count();

if ($coupon->customer_usage_limit && $customerUses >= $coupon->customer_usage_limit) {
return null;
}

// Check minimum purchase
if ($coupon->min_purchase && $order->subtotal < $coupon->min_purchase) {
return null;
}

// Check applicable event types
$eventTypes = $order->reservations->pluck('session.event.event_type_id')->unique();

if ($coupon->event_types && !$eventTypes->intersect($coupon->event_types)->count()) {
return null;
}

if ($coupon->event_type_exclusions && $eventTypes->intersect($coupon->event_type_exclusions)->count()) {
return null;
}

return $coupon;
}

Discount Calculation

public function calculateDiscount($subtotal): float
{
if ($this->discount_type === 'percent') {
return $subtotal * ($this->amount / 100);
}

// Fixed amount
return min($this->amount, $subtotal);
}

Attendance Tracking

Attendance Codes

Generation (on reservation confirmation):

$reservation->update([
'attendance_code' => strtoupper(Str::random(6))
]);

Check-In Process:

  1. Staff scans attendance code (QR code or manual entry)
  2. Mark reservation as attended
  3. Update milestone tracking if applicable

Nova Action: AttendanceSelection on Reservation resource

Reservation Reassignment

Use Case

Move reservation from one account to another (e.g., family member substitute).

Nova Action: ReassignReservation

public function handle(ActionFields $fields, Collection $models)
{
foreach ($models as $reservation) {
$newAccount = Account::find($fields->account_id);

// Validate new account meets requirements
if (!$this->validateAccount($newAccount, $reservation->session)) {
return Action::danger('Account does not meet requirements');
}

// Reassign
$reservation->update([
'account_id' => $newAccount->id,
'reassigned' => true,
]);

// Send notification
Mail::to($newAccount->email)->send(
new ReservationReassigned($reservation)
);
}

return Action::message('Reservation(s) reassigned successfully');
}

Event Insights Dashboard

Suma Nova Component (nova-components/EventInsights):

Dashboard Cards:

  • Sessions for today/tomorrow
  • Hourly schedule by location
  • Calendar view of all sessions
  • Revenue projections
  • Registration statistics

Metrics:

  • Total registrations
  • Available seats
  • Revenue (actual vs projected)
  • Popular events
  • Low-enrollment alerts