Skip to content

Bookings

Bookings represent finalized customer purchases, linking an Offer with a Client and their passengers, plus optional extras (upsells).

Booking
├── Offer (the package being booked)
├── Client (who is booking)
├── Currency (payment currency)
├── Passengers (many-to-many via booking_passenger)
│ └── is_lead_passenger (pivot field)
└── Upsells (optional extras)
└── Hotels, Activities, Transfers, Flight Upgrades

Table: bookings

ColumnTypeDescription
idbigintPrimary key
offer_idbigintFK to offers (cascade delete)
client_idbigintFK to clients (cascade delete)
booking_referencevarchar(16)Unique reference (BK-XXXXXXXX)
statusvarcharpending / confirmed / cancelled / completed
number_of_travelerssmallintPassenger count (default: 2)
total_amountdecimal(10,2)Booking total (base + extras)
base_pricedecimal(10,2)Offer price at booking time (nullable)
extras_pricedecimal(10,2)Sum of upsell prices (default: 0)
currency_idbigintFK to currencies (restrict delete)
notestextOptional notes
booked_attimestampWhen booking was made

Indexes:

  • (status, booked_at) - For status-filtered date queries
  • (client_id, status) - For client booking history

Table: booking_passenger

ColumnTypeDescription
booking_idbigintFK to bookings (cascade delete)
passenger_idbigintFK to passengers (cascade delete)
is_lead_passengerbooleanLead passenger flag (default: false)

Constraint: Unique (booking_id, passenger_id)

Table: booking_upsells

Stores selected extras for a booking using a selective FK pattern - only one supplier FK is populated per row (except for flight upgrades which use JSON storage).

ColumnTypeDescription
idbigintPrimary key
booking_idbigintFK to bookings (cascade delete)
typevarchar(20)‘hotel’, ‘activity’, ‘transfer’, ‘flight_upgrade’
supplier_hotel_idbigintFK to supplier_hotels (null on delete)
supplier_activity_idbigintFK to supplier_activities (null on delete)
supplier_transfer_idbigintFK to supplier_transfers (null on delete)
unit_pricedecimal(10,2)Price per unit in EUR at booking time
quantitysmallintNumber of units (default: 1)
total_pricedecimal(10,2)unit_price × quantity
daysmallintItinerary day number (nullable)
flight_search_paramsjsonFlight details for upgrades (nullable)

Selective FK Pattern: The type column determines which FK is populated:

  • type='hotel'supplier_hotel_id is set, others null
  • type='activity'supplier_activity_id is set, others null
  • type='transfer'supplier_transfer_id is set, others null
  • type='flight_upgrade' → No FK, uses flight_search_params JSON instead

Index: (booking_id, type) - For filtering upsells by type

Pending ──┬──► Confirmed ──┬──► Completed
│ │
└──► Cancelled ◄─┘

Rules:

  • Pending can transition to Confirmed or Cancelled
  • Confirmed can transition to Completed or Cancelled
  • Cancelled and Completed are final states

Source: backend/app/Enums/BookingStatus.php

Auto-generated on creation with format BK-XXXXXXXX (8 uppercase alphanumeric characters).

// Generation logic in Booking model
$reference = 'BK-' . strtoupper(substr(bin2hex(random_bytes(4)), 0, 8));

Source: backend/app/Models/Booking.php

$booking->offer; // BelongsTo Offer
$booking->client; // BelongsTo Client
$booking->currency; // BelongsTo Currency
$booking->passengers; // BelongsToMany Passenger (with pivot)
$booking->leadPassenger(); // Single Passenger or null
$booking->upsells; // HasMany BookingUpsell
$booking->canBeCancelled(); // true if status allows
$booking->canBeConfirmed(); // true if Pending
$booking->canBeCompleted(); // true if Confirmed

Resource: backend/app/Filament/Resources/Bookings/BookingResource.php

PageDescription
ListView all bookings with filters
CreateNew booking with offer/client/passenger selection
ViewBooking details infolist
EditStatus changes only

Permissions: ViewBooking, CreateBooking, UpdateBooking, DeleteBooking

  1. Select an active Offer
  2. Select a Client
  3. Attach passengers from client’s saved passengers
  4. Designate one lead passenger
  5. Select optional extras (hotels, activities, transfers)
  6. Review pricing (base + extras = total)

Only the status field can be modified after creation, following the allowed transitions.

Bookings can include optional extras (upsells) from the tour itinerary. These are fetched and priced by BookingUpsellPriceService.

TypePricingDescription
HotelFlat per roomPrice difference between guaranteed and upgrade hotel
ActivityPer personUnit price × number of travelers
TransferFlat per tripSingle price for the transfer
Flight UpgradePer personBusiness class upgrade cost × number of travelers

Flight upgrades store selection data in flight_search_params JSON for later verification:

{
"outbound": {
"departure_date": "2026-02-15",
"departure_time": "09:10",
"arrival_time": "15:15",
"departure_airport": "MAD",
"arrival_airport": "NBO",
"flight_numbers": ["EK123", "EK456"],
"airlines": ["EK"],
"stops": 1,
"stopover_airports": ["DXB"]
},
"inbound": {
"departure_date": "2026-02-22",
"departure_time": "08:00",
"arrival_time": "14:30",
"departure_airport": "NBO",
"arrival_airport": "MAD",
"flight_numbers": ["EK789"],
"airlines": ["EK"],
"stops": 0,
"stopover_airports": []
},
"apihubflowid": "4844a05b-3ce2-45b1-b907-1669e574e94c"
}

This data enables:

  • Re-searching to verify flight availability
  • Historical reference for flight details
  • Tracking via apihubflowid (Aerticket API Hub flow ID for booking lookup)

Upsells are sourced from the offer’s tour itinerary:

  1. Service fetches itinerary days with their optional hotels, activities, transfers
  2. Prices are calculated using rate periods covering the departure date
  3. Only upsells with valid rates for the date are shown

Source: backend/app/Services/Checkout/BookingUpsellPriceService.php

Source: backend/app/Models/BookingUpsell.php

$upsell->booking; // BelongsTo Booking
$upsell->hotel; // BelongsTo SupplierHotel (if type=hotel)
$upsell->activity; // BelongsTo SupplierActivity (if type=activity)
$upsell->transfer; // BelongsTo SupplierTransfer (if type=transfer)
$upsell->getItem(); // Returns supplier entity (null for flight_upgrade)
$upsell->getName(); // Returns display name or flight summary
$upsell->getFlightSummary(); // Returns "MAD → NBO / NBO → MAD" for flights

Flight upgrade display: getName() returns route summary like MAD → NBO / JNB → MAD (supports open-jaw flights where return airport differs).

When a customer completes checkout payment, BookingFinalizationService populates the booking:

  1. Passengers - Created from checkout session’s traveler_data, attached via booking_passenger
  2. Upsells - Created from session’s hotel/activity/transfer/flight selections
  3. Flight Upgrades - If business class selected (business_extra_price_per_person > 0), stores flight data in JSON
  4. Prices - Updated with base_price, extras_price, total_amount from session

Source: backend/app/Services/Booking/BookingFinalizationService.php

This happens automatically after successful payment in PaymentController::confirm().

Bookings automatically trigger client.updateBookingAnalytics() on save to keep client statistics current.