Offers
An Offer is a bookable travel package that combines one or more flights with a land component (tour). Each offer has a unique SKU, calculated pricing, and lifecycle status.
Creation Wizard
Section titled “Creation Wizard”Offers are created through a 3-step wizard in the admin panel.
Step 1: Product & Tour
Section titled “Step 1: Product & Tour”Select the product configuration:
- Product by Market: The product and target market (e.g., “India fun 8 days” for Spain)
- Tour Rate: Date period with pricing (e.g., Mar 1 - Jul 31, 2026)
- Room Type: Passenger configuration (e.g., “2 Adults”)
Step 2: Select Flights
Section titled “Step 2: Select Flights”Choose flight source using the toggle:
Cached Flights (default) - Select from pre-cached flight pricing:
- Filtered to match tour rate period dates
- Filtered to allowed travel weekdays (e.g., Fri, Sat, Sun)
- Blackout dates excluded
Manual Entry - Enter externally purchased flights (charter, direct airline bookings):
- Select departure/arrival airports
- Enter flight times for outbound and return
- Optionally specify flight number and notes
- Creates a
FlightBookingwithsource: manual
Source: backend/app/Enums/FlightBookingSource.php defines api | manual values.
Multi-Leg Flight Selection
Section titled “Multi-Leg Flight Selection”For products with domestic legs (e.g., international flight + internal domestic flight), the wizard shows a unified table with both flight types:
- International flights: Cross-border flights (main journey)
- Domestic flights: Same-country internal flights (e.g., Delhi to Goa)
Selection Rules:
- Only ONE flight per leg type can be selected
- All required legs must be selected before proceeding
- Domestic flights are matched to their route based on the flight config
- The wizard auto-detects leg type from the route’s
is_domesticflag
Validation:
- If multiple flights of the same leg type are selected, an error is shown
- Missing legs are listed in the notification when selection is incomplete
Source: backend/app/Filament/Resources/Offers/Pages/CreateOffer.php:848-927
Step 3: Review
Section titled “Step 3: Review”Preview offer before creation:
- All selected flight legs with individual prices
- Tour Services breakdown (hotels + included activities with prices)
- Combined total and margin calculation
View Offer Page
Section titled “View Offer Page”The offer detail page shows all components organized in sections.
Offer Details
Section titled “Offer Details”Basic identification and dates:
- SKU: Unique identifier (e.g.,
ES-173-10-ES1-MAD-260301-01) - Status: Draft or Active
- Number of Pax: Passenger count (e.g., 2)
- Departure/Return Dates: Trip window
Pricing
Section titled “Pricing”Shows the price breakdown and margin calculation:
| Field | Example | Description |
|---|---|---|
| Flight Price | 691.99 | Flight cost for all passengers |
| Land Price | 388.00 | Tour cost for all passengers |
| Total Base Price | 1,079.99 | Combined flight + land |
| Margin | 20% | Markup percentage |
| Price per Person | 649.00 | Per-pax price (rounded to xx49/xx99) |
| Final Price | 1,298.00 | Total price (per-pax × pax count) |
Land Component (Tour)
Section titled “Land Component (Tour)”Tour details when a rate is linked:
- Supplier: Tour provider (e.g., Condor Travel)
- Tour: Product name (e.g., India fun 8 days)
- Room Type: Selected configuration
- Rate Period: Valid date range
- Travel Window: Allowed weekdays
- Allotment: Used vs. total capacity
Tour Services
Section titled “Tour Services”Services breakdown showing individual prices for the selected room type:
| Service Type | Icon | Price Calculation |
|---|---|---|
| Hotel | 🏨 | Per-night × nights at location |
| Activity | 🎫 | Per-person × number of travelers |
Only included activities appear here. Upsell activities are optional upgrades not in the base price.
Source: backend/app/Filament/Resources/Offers/Schemas/OfferPreviewData.php (getTourServicesBreakdown())
Hotels
Section titled “Hotels”Day-by-day hotel schedule showing:
- Day number with actual date
- Destination city
- Guaranteed hotel (default, included in base price)
- Upsell hotel (premium upgrade option)
Market Product
Section titled “Market Product”Link to the associated product configuration with market and status.
Flight(s)
Section titled “Flight(s)”Flight details from cache or manual booking. For multi-leg offers, shows each leg:
| Field | Description |
|---|---|
| Flight Type | International or Domestic |
| Route | Airport codes (e.g., MAD-DEL-BKK) |
| Departure Date | Flight departure |
| Price | Cost for this leg |
| CUG Type | Fare category |
| Source | cache or manual |
Pricing Formula
Section titled “Pricing Formula”Total Flight Price = Sum of all flight leg pricesLand Price = Σ(Hotel Services) + Σ(Included Activity Services)Base Price = Total Flight Price + Land PriceRaw Total = Base Price × (1 + Margin%)Per-Pax Price = roundUp(Raw Total / Pax Count) ← rounded to xx49/xx99Final Price = Per-Pax Price × Pax CountPer-Person Marketing Price
Section titled “Per-Person Marketing Price”Prices are rounded per person first, then multiplied back to get the total. This ensures clean per-person prices on the website (e.g., “€2,399/persona” instead of “€2,399.50/persona”).
Example calculation:
Base Price: €3,957.86 (for 2 pax)With 20% margin: €3,957.86 × 1.20 = €4,749.43 totalPer-pax raw: €4,749.43 / 2 = €2,374.72Per-pax rounded: €2,399 (rounded to xx99)Final price: €2,399 × 2 = €4,798The marketing_price_per_pax field stores the clean per-person price for display on the website.
Pax count is derived from room_type (e.g., “2A” → 2 pax, “2A+1CH” → 3 pax). Defaults to 2 when room_type is null.
Land Price Components
Section titled “Land Price Components”| Component | Calculation | Room Type Lookup |
|---|---|---|
| Hotel Services | rate_price × nights | Selected room type (e.g., 2A) |
| Activity Services | rate_price × travelers | Always per_person |
Note: Only included activities are counted. Upsell activities are optional and not in base price.
Marketing Price Rounding
Section titled “Marketing Price Rounding”Prices round UP to marketing-friendly values ending in 49 or 99:
- €2,374.72 → €2,399 (per person)
- €300.00 → €349 (per person)
- €50.00 → €99 (per person)
SKU Format
Section titled “SKU Format”Pattern: <ProductByMarket SKU>-<Airport>-<Date>-<Sequence>
Example: ES-173-10-ES1-MAD-260301-01
| Part | Value | Meaning |
|---|---|---|
| ES-173-10-ES1 | ProductByMarket SKU | Spain, Product 173, 10 days, template 1 |
| MAD | Airport IATA | Madrid departure |
| 260301 | YYMMDD | March 1, 2026 |
| 01 | Sequence | First offer for this combination |
Status Lifecycle
Section titled “Status Lifecycle”| Status | Editable | Description |
|---|---|---|
| Draft | Yes (margin only) | Work in progress |
| Active | No | Published and locked |
Once active, offers cannot be modified. Create a new offer instead.
Data Model
Section titled “Data Model”OfferFlight (Pivot Model)
Section titled “OfferFlight (Pivot Model)”Tracks multiple flight legs per offer.
Table: offer_flights
| Field | Type | Description |
|---|---|---|
| offer_id | FK | Parent offer |
| leg_index | tinyint | Order within trip (0=intl, 1+=domestic) |
| flight_type | string | international or domestic |
| source_type | string | cache or manual |
| dynamic_flight_cache_id | FK (nullable) | Cached flight reference |
| flight_booking_id | FK (nullable) | Manual booking reference |
| price | decimal | Price for this leg |
Unique constraint: (offer_id, leg_index) - one flight per leg position.
Source: backend/app/Models/OfferFlight.php
Relationship Flow
Section titled “Relationship Flow”Offer └── OfferFlight[] (hasMany, ordered by leg_index) ├── DynamicFlightCache (when source_type='cache') └── FlightBooking (when source_type='manual')Key Methods
Section titled “Key Methods”// Offer model$offer->hasLandComponent(); // Has tour linked?$offer->getRoomTypeLabel(); // "2 Adults" from "2A"$offer->getReturnDate(); // Departure + trip duration$offer->getPaxCount(); // Parse room_type for pax count (default: 2)$offer->isEditable(); // True if draft$offer->hasMultipleFlightLegs(); // Has 2+ legs?$offer->getTotalFlightPrice(); // Sum of all leg prices$offer->calculateFinalPrice(); // Calculates per-pax rounded price$offer->marketing_price_per_pax; // Clean per-person price for website
// OfferFlight model$leg->isInternational(); // flight_type check$leg->isDomestic(); // flight_type check$leg->isCachedFlight(); // source_type check$leg->isManualFlight(); // source_type check$leg->getFlightSource(); // Returns cache or booking$leg->getRouteString(); // Route from sourceRelated
Section titled “Related”- Suppliers - Tour providers and rates
- Products by Market - Product configuration
- Dynamic Flight Cache - Flight data source