Skip to content

Suppliers Service

Manages land service providers (DMCs), their hotel inventory, and tour packages with independent per-hotel pricing.

  • Managing external companies that provide ground services (hotels, tours, transfers)
  • Creating tour packages linked to ProductTemplates
  • Setting up per-hotel pricing with rate periods and room types
  • Setting up pricing periods with weekday restrictions and blackout dates
  • Configuring multi-tenant access for supplier managers
Supplier (land service provider)
├── SupplierHotel[] (hotel inventory)
│ └── SupplierService (1:1 - per-hotel pricing)
│ └── SupplierServiceRate[] (rate periods)
│ └── SupplierServiceRatePrice[] (room type prices)
├── SupplierActivity[] (excursions, day tours)
│ └── SupplierService (1:1 - per-activity pricing)
│ └── SupplierServiceRate[] → SupplierServiceRatePrice[]
├── SupplierTransfer[] (airport/city transfers)
│ └── SupplierService (1:1 - per-transfer pricing)
│ └── SupplierServiceRate[] → SupplierServiceRatePrice[]
└── SupplierTour[] (tour packages)
├── ProductTemplate (1:1 - defines itinerary structure)
├── SupplierTourItinerary[] (assignments per day)
│ ├── guaranteed_hotel_id, upsell_hotel_id
│ ├── supplier_tour_itinerary_activities (pivot)
│ │ ├── type='included' → activities included in base price
│ │ └── type='upsell' → optional upgrade activities
│ └── supplier_tour_itinerary_transfers (pivot)
│ ├── type='included' → transfers included in base price
│ └── type='upsell' → optional upgrade transfers
└── SupplierTourRate[] (pricing periods - legacy)

SupplierService provides independent, per-hotel pricing separate from the legacy tour-based rates. Each hotel can have its own service with flexible pricing models.

Source: backend/app/Models/SupplierService.php

TypeDescriptionUse CaseFK Link
HotelAccommodation pricingPer-hotel ratessupplier_hotel_id
ActivityDay tours, excursionsPer-person pricingsupplier_activity_id
TransferAirport/city transfersPer-trip pricingsupplier_transfer_id

Source: backend/app/Enums/SupplierServiceType.php

Activities represent excursions, tours, and experiences that can be assigned to tour itineraries.

Source: backend/app/Models/SupplierActivity.php

FieldDescription
nameActivity name (e.g., “Great Wall Day Tour”)
cityLocation in CitySelect format
duration_hoursActivity duration
descriptionDetailed description
imagesPhoto gallery

Each activity has ONE linked SupplierService for pricing:

// Activity has one service
$activity->service; // SupplierService with pricing
// Service belongs to one activity
$service->activity; // SupplierActivity

When creating a service for an activity, the pricing model auto-switches to PerPerson.

Transfers represent transportation services (airport pickups, city transfers) that can be assigned to tour itineraries.

Source: backend/app/Models/SupplierTransfer.php

FieldDescription
nameTransfer name (e.g., “Airport to Hotel Transfer”)
cityLocation in CitySelect format
vehicle_typeVehicle description (e.g., “Private Car”, “Minivan”)
duration_minutesEstimated duration
descriptionDetailed description
imagesPhoto gallery

Each transfer has ONE linked SupplierService for pricing:

// Transfer has one service
$transfer->service; // SupplierService with pricing
// Service belongs to one transfer
$service->transfer; // SupplierTransfer

Tours can assign activities and transfers as included (in base price) or upsell (optional upgrade):

TypeDescriptionIn Offer Price?
includedBase package itemsYes
upsellOptional premium itemsNo
// Assign activity to tour itinerary
$itinerary->includedActivities()->attach($activity->id, [
'sort_order' => 0,
'type' => 'included'
]);
// Assign transfer to tour itinerary
$itinerary->includedTransfers()->attach($transfer->id, [
'sort_order' => 0,
'type' => 'included'
]);

The same activity/transfer CAN be assigned as both types to the same day (unique constraint is on itinerary_id, item_id, type).

Source: backend/app/Models/SupplierTourItinerary.php

ModelCalculationExample
PerNightprice × nightsHotel accommodation
PackageFixed priceAll-inclusive tour
PerPersonprice × travelersGroup activities
PerTripFixed priceAirport transfer

Source: backend/app/Enums/ServicePricingModel.php

Each service has rate periods defining when prices apply:

  • Date range: start_date to end_date
  • Weekdays: Array of allowed days (e.g., ["mon", "thu", "sat"])
  • Exclusions: Blackout date ranges
  • Allotment: Available inventory

Methods:

  • isDateAvailable($date) - Checks period, weekday, and exclusions
  • isDateExcluded($date) - Checks blackout ranges only
  • getPriceForRoomType($roomType) - Returns price record

Source: backend/app/Models/SupplierServiceRate.php

Room Type Prices (SupplierServiceRatePrice)

Section titled “Room Type Prices (SupplierServiceRatePrice)”

Prices per room configuration within a rate period:

CodeDescription
1A1 Adult
2A2 Adults
3A3 Adults
4A4 Adults
1A+1CH1 Adult + 1 Child
1A+2CH1 Adult + 2 Children
1A+3CH1 Adult + 3 Children
2A+1CH2 Adults + 1 Child
2A+2CH2 Adults + 2 Children
2A+1B2 Adults + 1 Baby
2A+1CH+1B2 Adults + 1 Child + 1 Baby
3A+1CH3 Adults + 1 Child
per_personPer Person (Activity)
per_tripPer Trip (Transfer)

Special room types:

  • per_person - Used for activity pricing, multiplied by number of travelers
  • per_trip - Used for transfer pricing, fixed price regardless of travelers

Currency auto-assignment: When creating prices without a currency, the system automatically assigns the supplier’s default currency.

Source: backend/app/Models/SupplierServiceRatePrice.php

SupplierTour and ProductTemplate Relationship

Section titled “SupplierTour and ProductTemplate Relationship”

Tours are linked 1:1 with ProductTemplates. The ProductTemplate defines the itinerary structure (cities, nights, titles), while SupplierTour adds supplier-specific data (hotels, rates, guide info).

Source: backend/app/Models/SupplierTour.php

Tour rates have specific validity rules checked by isDateAvailable():

  1. Date within start_date - end_date range
  2. Weekday in allowed weekdays array (e.g., ["mon", "thu"])
  3. Date not in excluded_date_ranges (blackout periods)

Source: backend/app/Models/SupplierTourRate.php

Standardized room type codes used across the system:

CodeDescription
1A1 Adult
2A2 Adults
2A+1CH2 Adults + 1 Child
2A+2CH2 Adults + 2 Children
2A+1B2 Adults + 1 Baby

Source: backend/app/Models/SupplierTourRateRoomPrice.php (roomTypeOptions())

Hotels use a different room type configuration defined as model constants:

CodeDescription
1 pax1 Pax (Single)
2 pax2 Pax (Double) - Required
3 pax3 Pax (Triple)
2 pax 1 baby2 Pax + 1 Baby
2 pax 1 child2 Pax + 1 Child
2 pax 2 children2 Pax + 2 Children
4 pax4 Pax (Quad)
familyFamily Room
suiteSuite

All hotels must include the “2 pax” room type.

Source: backend/app/Models/SupplierHotel.php (ROOM_TYPES, REQUIRED_ROOM_TYPE)

Offers use SupplierService pricing from hotels AND included activities in the tour itinerary:

ProductByMarket → ProductTemplate ← SupplierTour → SupplierTourItinerary[]
┌─────────────┴─────────────┐
↓ ↓
SupplierHotel[] SupplierActivity[] (included)
↓ ↓
SupplierService[] SupplierService[]
↓ ↓
Rate → Prices Rate → Prices
↓ ↓
└───────────┬───────────────┘
Offer (land_base_price)
  1. Select ProductByMarket (includes ProductTemplate and linked SupplierTour)
  2. System extracts all hotels from the tour’s itinerary
  3. System extracts all included activities (not upsells)
  4. System finds SupplierService for each hotel and activity
  5. Room type dropdown shows types available at ALL hotels (intersection)
  6. Price displayed is the TOTAL across all services for selected room type
  7. Offer stores the combined land_base_price

The land price sums hotel and included activity services:

Land Price = Σ(Hotel Services) + Σ(Included Activity Services)
Hotel Service Price = rate_price × nights (per_night model)
Activity Service Price = rate_price × travelers (per_person model)

Room type lookup:

  • Hotels: Use selected room type (e.g., 2A, 2A+1CH)
  • Activities: Always use per_person room type

Source: backend/app/Filament/Resources/Offers/Pages/CreateOffer.php, backend/app/Filament/Resources/Offers/Schemas/OfferPreviewData.php

See Offers documentation for combined pricing details.

Users with SupplierManager role are automatically scoped to their assigned supplier. The Filament resources filter queries based on user.supplier_id.

Source: backend/app/Filament/Resources/Suppliers/SupplierTourResource.php

PermissionDescription
supplier.view_anyList suppliers
supplier.viewView supplier details
supplier.createCreate suppliers (Admin only)
supplier.updateUpdate supplier
supplier.deleteDelete supplier (Admin only)
supplier_tour.*Tour CRUD operations
supplier_tour_rate.*Rate CRUD operations
supplier_hotel.*Hotel CRUD operations
Section titled “Navigation Group: “Suppliers Management””
ResourceModelDescription
SuppliersSupplierCompany management
HotelsSupplierHotelHotel inventory
ActivitiesSupplierActivityExcursions, day tours
TransfersSupplierTransferAirport/city transfers
Supplier ServicesSupplierServicePer-hotel/activity/transfer pricing
ToursSupplierTourTour packages
Tour RatesSupplierTourRateRoomPriceFlat rate view (legacy)

Source: backend/app/Filament/Resources/Suppliers/

Creating a SupplierTour uses an embedded wizard that also creates the ProductTemplate:

  1. Product Template Step: AI parser, title, duration, itinerary (cities/nights)
  2. Tour Details Step: Guide info, meals, transport, images
  3. Services Assignments Step: Three tabs:
    • Hotels Tab: Grouped by stop with guaranteed/upsell hotel selections
    • Activities Tab: Per-day included/upsell activity selections
    • Transfers Tab: Per-day included/upsell transfer selections

Paste raw trip information to auto-generate all template fields. Appears as a collapsible section at the top of Step 1.

Example input:

Asia India 9 nights - 2 Delhi - 1 Jodhpur - 2 Udaipur - 2 Jaipur - 1 Agra - 1 Delhi

Generates:

  • Tour title and descriptions (short/long)
  • Complete itinerary with POI locations resolved via Google Places
  • Duration auto-calculated from total nights
  • Hotel assignments regenerated from itinerary

Content is generated in the selected source language. Shows confirmation modal before replacing existing values.

Source: backend/app/Services/ProductTemplateAIService.php (generateFromRawInput())

Hotel dropdowns use lazy loading to prevent N+1 issues with large inventories:

  • Hotels fetched on-demand when user searches (not preloaded)
  • Search filtered by itinerary location
  • Results limited to 50 per search

Cascade behavior: Deleting SupplierTour also deletes its ProductTemplate. Deleting Supplier cascades to tours and hotels.

Source: backend/app/Filament/Resources/Suppliers/SupplierTours/Schemas/SupplierTourForm.php

Generate test supplier activities linked to services with per-person pricing.

Terminal window
./vendor/bin/sail artisan suppliers:activities:generate

Creates activities for each supplier with linked SupplierService records and per_person rate prices.

Source: backend/app/Console/Commands/GenerateTestSupplierActivities.php

Generate test supplier hotels with realistic luxury hotel data.

Terminal window
# Interactive mode with prompts
./vendor/bin/sail artisan suppliers:hotels:generate
# Non-interactive with options
./vendor/bin/sail artisan suppliers:hotels:generate \
--suppliers=1,2 \
--countries="Spain,France,Italy" \
--hotels-per-city=3 \
--use-ai

Options:

OptionDescription
--suppliers=*Supplier IDs (comma-separated)
--countries=*Country names from airports table
--hotels-per-city=2Hotels per city (minimum 2)
--use-aiUse OpenRouter API for realistic data

Key Features:

  • Cities sourced from airports table (valid destinations only)
  • AI mode generates realistic luxury hotel names via OpenRouter
  • Fallback mode uses hotel brand names (Four Seasons, Ritz-Carlton, etc.)
  • Room types always include required “2 pax” plus random optional types
  • Sensible defaults: 1 supplier, 5 random countries, 2 hotels per city

Source: backend/app/Console/Commands/GenerateTestSupplierHotels.php, backend/app/Services/SupplierHotelGeneratorService.php

TablePurpose
suppliersLand service provider companies
supplier_hotelsHotel inventory per supplier
supplier_activitiesExcursion/tour inventory per supplier
supplier_transfersTransfer inventory per supplier
supplier_servicesPurchasable services (hotel, activity, transfer)
supplier_service_ratesRate periods with date/weekday constraints
supplier_service_rate_pricesRoom type prices per rate period
TablePurpose
supplier_toursTour packages linked to ProductTemplates
supplier_tour_itinerariesHotel assignments per tour day
supplier_tour_itinerary_activitiesActivity pivot (included/upsell per day)
supplier_tour_itinerary_transfersTransfer pivot (included/upsell per day)
supplier_tour_ratesTour rate periods (legacy)
supplier_tour_rate_room_pricesTour room prices (legacy)
  • supplier_services.supplier_hotel_id is unique (one service per hotel)
  • supplier_services.supplier_activity_id is unique (one service per activity)
  • supplier_services.supplier_transfer_id is unique (one service per transfer)
  • supplier_tour_itinerary_activities unique on (itinerary_id, activity_id, type)
  • supplier_tour_itinerary_transfers unique on (itinerary_id, transfer_id, type)
  • Deleting a supplier cascades to services, hotels, activities, and transfers
  • Deleting a service rate cascades to its prices

Source: backend/database/migrations/ (search for supplier)


Files:

  • Models: backend/app/Models/Supplier*.php
  • Enums: backend/app/Enums/SupplierServiceType.php, backend/app/Enums/ServicePricingModel.php
  • Resources: backend/app/Filament/Resources/Suppliers/
  • Policies: backend/app/Policies/Supplier*.php
  • Transfer Resource: backend/app/Filament/Resources/Suppliers/SupplierTransfers/
  • Activity Resource: backend/app/Filament/Resources/Suppliers/SupplierActivities/
  • Tour Service: backend/app/Services/SupplierTourService.php
  • Preview DTO: backend/app/Filament/Resources/Offers/Schemas/OfferPreviewData.php