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 (the primary entry point for product creation)
  • Publishing tours to markets with AI translation
  • 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, has source_locale for content language)
├── 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[]
├── SupplierContract[] (formal agreements)
│ └── SupplierContractService[] (line items with price snapshots)
│ ├── → SupplierService + SupplierServiceRate (service-based)
│ └── → SupplierTourRate (tour-rate-based)
└── SupplierTour[] (tour packages)
├── ProductTemplate (1:1 - defines itinerary structure)
├── SupplierTourItinerary[] (assignments per day)
│ ├── selection_hotel_id, luxury_hotel_id, grand_luxury_hotel_id
│ ├── supplier_tour_itinerary_activities (pivot)
│ │ ├── type='included' → base tier activities (in package price)
│ │ ├── type='extra' → optional add-on activities
│ │ └── type='substitution' → upgrade replacement activities
│ └── supplier_tour_itinerary_transfers (pivot)
│ ├── type='selection' → base tier transfers
│ ├── type='luxury' → luxury upgrade transfers
│ └── type='grand_luxury' → premium upgrade transfers
└── SupplierTourRate[] (pricing periods - legacy)

Hotels and transfers use a 3-tier luxury categorization system (ServiceTier):

TierEnum ValueLabelDescription
Selectionselection5 Star SelectionBase tier, included in package price
Luxuryluxury5 Star LuxuryUpgrade tier, shown as optional upgrades
Grand Luxurygrand_luxury5 Star Grand LuxuryPremium upgrade tier

Source: backend/app/Enums/ServiceTier.php

Activities use a dedicated ActivityTier enum, separate from the hotel/transfer tiers:

TierEnum ValueLabelDescription
IncludedincludedIncludedBase activities included in package price
ExtraextraExtraOptional add-on activities (shown as “Añadir experiencia”)
SubstitutionsubstitutionSubstitutionUpgrade replacements for included activities (shown as “Mejorar experiencia” with gem badge)

Source: backend/app/Enums/ActivityTier.php

Each tour itinerary day can have hotels and transfers at each service tier, and activities at each activity tier:

// Hotel relationships (ServiceTier)
$itinerary->selectionHotel; // Base tier hotel
$itinerary->luxuryHotel; // Upgrade tier hotel
$itinerary->grandLuxuryHotel; // Premium tier hotel
// Activity relationships (ActivityTier, many-to-many)
$itinerary->includedActivities; // Base activities in package price
$itinerary->extraActivities; // Optional add-on activities
$itinerary->substitutionActivities; // Upgrade replacement activities
// Transfer relationships (ServiceTier, many-to-many)
$itinerary->selectionTransfers; // Base tier transfers
$itinerary->luxuryTransfers; // Upgrade tier transfers
$itinerary->grandLuxuryTransfers; // Premium tier transfers

In checkout, Selection tier hotels/transfers and Included tier activities are included in the base price. Luxury and Grand Luxury hotels and Extra/Substitution activities appear as optional upgrades with prices calculated server-side. The frontend hotel selector uses a tabbed 3-tier UI where users can browse Selection (included), Luxury, and Grand Luxury hotels per time period, with mutual exclusivity per period (only one upgrade at a time).

Source: backend/app/Services/Checkout/CheckoutHotelService.php, backend/app/Services/Checkout/CheckoutActivityService.php, backend/app/Services/Checkout/CheckoutTransferService.php

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

Hotel services have an is_supplement_pricing flag (default false). When enabled, stored prices are treated as per-person/night supplements instead of full room rates. The calculator multiplies the total by pax count derived from the room type.

Modeis_supplement_pricingCalculationExample (2A, 150€/night, 3 nights)
Roomfalserate × nights150 × 3 = 450€
Supplementtruerate × nights × pax150 × 3 × 2 = 900€

When to use: Package tours where the base hotel is bundled in a closed price and extra hotel nights are priced as per-person supplements.

Pax count is parsed from room type via SupplierServiceRatePrice::getPaxCountFromRoomType() (e.g., 2A = 2, 2A+1CH = 3, 2A+1B = 3).

Admin toggle: Visible only for Hotel-type services in the service form.

Source: backend/app/Services/Checkout/HotelPriceCalculatorService.php, backend/app/Filament/Resources/Suppliers/SupplierServices/Schemas/SupplierServiceForm.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
time_slotWhen activity occurs (morning, afternoon, full_day, lunch, dinner)
descriptionDetailed description
imagesPhoto gallery

Activities can specify when they occur during the day:

SlotDescription
morningMorning activity
afternoonAfternoon activity
full_dayAll-day activity
lunchLunch experience
dinnerDinner experience

Source: backend/app/Enums/ActivityTimeSlot.php

Activities used in supplier tours cannot be edited. The edit form is disabled and the save button hidden. Check $activity->isUsedInTours() to determine if locked.

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

Transfers use ServiceTier (see Service Tiers). Activities use ActivityTier (see Activity Tiers):

Transfer tiers:

TierPivot TypeDescriptionIn Offer Price?
SelectionselectionBase package transfersYes
LuxuryluxuryUpgrade transfersNo
Grand Luxurygrand_luxuryPremium transfersNo

Activity tiers:

TierPivot TypeDescriptionIn Offer Price?
IncludedincludedBase package activitiesYes
ExtraextraOptional add-on activitiesNo
SubstitutionsubstitutionUpgrade replacement activitiesNo
// Assign activity to tour itinerary (Included tier)
$itinerary->includedActivities()->attach($activity->id, [
'sort_order' => 0,
'type' => 'included'
]);
// Assign transfer to tour itinerary (Luxury tier)
$itinerary->luxuryTransfers()->attach($transfer->id, [
'sort_order' => 0,
'type' => 'luxury'
]);

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

Source: backend/app/Models/SupplierTourItinerary.php

When assigning activities to tour itineraries, time slot conflicts are validated per day and tier:

Conflict Rules:

  • full_day conflicts with ALL other slots (can’t have full_day + morning)
  • Same slots conflict (can’t have two morning activities)
  • Different slots are allowed (morning + afternoon + dinner is OK)
  • Activities without time_slot are ignored in validation

Validation Scope:

  • Each tier is validated independently (Included, Extra, Substitution)
  • A morning Included activity does NOT conflict with a morning Extra activity
  • Conflicts within the same tier are blocked

Error Format:

Day 3 (included): "Great Wall Tour" (Full Day) conflicts with "Temple Visit" (Morning)

Source: backend/app/Services/Validation/ActivityTimeSlotOverlapValidator.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"])
  • Blackout Dates: Date ranges when service is unavailable
  • Allotment: Available inventory slots. AllotmentConsumption records track consumed slots per rate per booking. AllotmentService enforces availability at two points: (1) pre-configurator batch check to hide sold-out offers from the PDP, and (2) at payment time with pessimistic locking to prevent concurrent overbooking.

Methods:

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

Source: backend/app/Models/SupplierServiceRate.php, backend/app/Services/Allotment/AllotmentService.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 Tour wizard creates both the SupplierTour and its ProductTemplate in a single flow. The ProductTemplate defines the itinerary structure (cities, nights, titles), while SupplierTour adds supplier-specific data (hotels, rates, guide info).

ProductTemplate is no longer managed as a standalone resource — it is created and edited through Tours. See Admin Panel for the full workflow.

Source: backend/app/Models/SupplierTour.php

Tours have an auto-derived status (TourStatus enum) based on 6 completion steps:

StatusValueDescription
DraftdraftOne or more required steps incomplete
CompletecompleteAll 6 steps pass — ready to publish to market

Status is computed by SupplierTourService::determineStatusFromFormData() on every save. There is no manual status toggle.

Source: backend/app/Enums/TourStatus.php

Centralized business logic for tour operations:

  • Status derivationdetermineStatusFromFormData() / getCompletionProgressForTour() compute the 6-step completion progress
  • Hotel assignmentsgenerateHotelAssignments(), syncHotelAssignments(), saveHotelAssignments(), loadGroupedHotelAssignments()
  • Activity assignmentssaveActivityAssignments(), loadActivityAssignments() with time slot overlap validation
  • Transfer assignmentssaveTransferAssignments(), loadTransferAssignments()
  • Rate periodssaveRatePeriods(), syncRatePeriods(), loadRatePeriods()

Source: backend/app/Services/SupplierTourService.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
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

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

FieldDescription
hotel_nameHotel name
cityLocation in CitySelect format (“City, Country”)
addressStreet address
categoryServiceTier enum (selection, luxury, grand_luxury)
meal_planHotelMealPlan enum
room_typesAvailable room configurations (array)
room_typeLegacy single room type field
imagesPhoto gallery
descriptionOptional text description, displayed in PDP hotel detail modal and checkout hotel cards
reasonsArray of selling points (e.g., “Beachfront location”)
amenitiesArray of amenity objects with icon, label, and description

Source: backend/app/Models/SupplierHotel.php

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)
└─ With supplement pricing: rate_price × nights × pax_from_room_type
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
ResourceNav LabelModelDescription
SuppliersSuppliersSupplierCompany management
HotelsHotelsSupplierHotelHotel inventory
ActivitiesActivitiesSupplierActivityExcursions, day tours
TransfersTransfersSupplierTransferAirport/city transfers
Supplier ServicesSupplier ServicesSupplierServicePer-hotel/activity/transfer pricing
SupplierTourResourceToursSupplierTourTour packages (primary product entry point)
Supplier ContractsSupplier ContractsSupplierContractContract management with status workflow
Release DatesRelease DatesSupplierTourRateTour rate periods (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 Selection/Luxury/Grand Luxury hotel selections
    • Activities Tab: Per-day activity assignments across Included/Extra/Substitution tiers
    • Transfers Tab: Per-day transfer assignments across Selection/Luxury/Grand Luxury tiers

Paste raw trip information to auto-generate all template fields. Appears as a collapsible section at the top of Step 1. Each generation is logged to activity_log with log name ai_tour_parser, recording the raw input, source locale, TCAI profile, generated title, and total stops.

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())

Step 1 includes a “Validate flight routes before continuing” checkbox (checked by default). When checked, clicking Next triggers FlightRouteValidationService to perform a test Aerticket API search against the international flight legs. If validation fails, a notification is shown and the wizard is halted — uncheck the checkbox to skip validation.

This only applies to the Create wizard. Edit and View pages use a header button instead (see below).

Edit and View pages have a “Validate Flight Route” header button that performs a test flight search via the Aerticket API. The confirmation modal shows the route summary, duration, and test parameters (2 adults, sample date ~60 days out, origin MAD, international legs only). Results appear as a persistent notification with route, date, and fare count.

The button is only visible when the tour has an itinerary defined.

Source: backend/app/Filament/Resources/Suppliers/SupplierTours/Actions/ValidateFlightRouteAction.php

Service: backend/app/Services/Flights/FlightRouteValidationService.php

The “Publish to Market” header action on the Tour edit page creates a ProductByMarket from the tour’s template. See Admin Panel - Publish to Market for details.

Source: backend/app/Filament/Resources/Suppliers/SupplierTours/Actions/PublishToMarketAction.php

The view page shows the TourCompletionWidget (6-step progress), a “Published Markets” section listing linked market products, and three footer widgets with read-only service assignment tables (hotels, activities, transfers).

See Admin Panel - Tour View Page for full details.

Source: backend/app/Filament/Resources/Suppliers/SupplierTours/Pages/ViewSupplierTour.php

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

Manages formal agreements with suppliers, tracking negotiation status and locking in service prices and allotments. Contracts snapshot pricing from existing services/tours so agreed rates are preserved independently of future rate changes.

  • Formalizing agreed pricing with a supplier before a season
  • Tracking contract negotiation and signing workflow
  • Checking whether a supplier has an active contract covering a specific date
  • Auto-generating a contract from an existing tour’s service assignments

Contracts follow a strict state machine with guarded transitions:

Draft → Sent → Negotiating → Signed → Active → Expired
↓ ↓ ↓ ↓ ↓
└───────┴─────────┴───────────┴────────┴──→ Terminated
StatusEditableCan Add ServicesFinal
DraftYesYesNo
SentYesYesNo
NegotiatingYesYesNo
SignedNoNoNo
ActiveNoNoNo
ExpiredNoNoYes
TerminatedNoNoYes

Transitions set timestamps automatically: sent_at when moving to Sent, terminated_at when moving to Terminated. The signed_at and signed_by fields are captured via a modal form when marking as Signed.

Source: backend/app/Enums/SupplierContractStatus.php

Auto-generated sequential pattern: SC-{YYYY}-{0001}. The sequence resets each year.

Source: backend/app/Models/SupplierContract.php (generateReferenceNumber())

Each contract has line items (SupplierContractService) that snapshot a price and allotment from either a SupplierService + SupplierServiceRate pair or a SupplierTourRate. Price priority for snapshots: 2A room type, then per_person, then per_trip, then first available.

Source: backend/app/Models/SupplierContractService.php

The service class provides:

  • transitionStatus() — validates transitions and sets timestamps
  • getServicesForQuotation() — returns contract line items applicable on a specific date (filters by rate date availability)
  • isSupplierCovered() — checks if a supplier has an active contract valid on a date
  • buildContractDataFromTour() — collects all services (hotels, activities, transfers, package service) and tour rates from a tour’s itinerary, computes validity range, and returns repeater-compatible data for form pre-fill

Source: backend/app/Services/SupplierContractService.php

The “Generate from Tour” dropdown (visible after selecting a supplier) auto-fills the contract title, currency, validity dates, and all service line items from the selected tour’s itinerary assignments.

Header actions enforce the status lifecycle:

ActionVisible WhenModal
Send to SupplierDraftConfirmation only
Mark as SignedSent or NegotiatingPrompts for signed_at and signed_by
ActivateSignedConfirmation only
TerminateAny non-final statusPrompts for termination_reason

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

Auto-generate a contract from a tour’s service assignments via Artisan.

Terminal window
# Interactive (prompts for tour selection)
./vendor/bin/sail artisan suppliers:contracts:generate
# Direct with tour ID
./vendor/bin/sail artisan suppliers:contracts:generate 42
# Preview without creating
./vendor/bin/sail artisan suppliers:contracts:generate 42 --dry-run

Collects all hotels, activities, transfers, and package services from the tour itinerary, previews line items in a table, and creates the contract with status Draft inside a DB transaction.

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

The Supplier Services list page provides Excel template export/import for bulk-creating hotels or activities with their full entity chain.

Header actions on Supplier Services list page (/admin/supplier-services):

  • Export Template — generates an XLSX template
  • Import Template — uploads a filled XLSX and creates records

Both actions prompt for a Supplier and a Type (Hotel or Activity).

Source: backend/app/Filament/Resources/Suppliers/SupplierServices/Actions/ExportTemplateAction.php, ImportTemplateAction.php

Columns across entity fields, rate period, pricing type, and prices:

ColumnFieldRequiredNotes
AHotel NameYesCreates SupplierHotel
BCityYes
CAddressNo
D-GCategory, Room Config, Room Type, Meal PlanNoSee cell comments for format
HRate Start DateYesDD/MM/YYYY
IRate End DateYesDD/MM/YYYY
JRooms/DayYesAllotment, minimum 1
KRelease DaysNo
LOperating DaysNoComma-separated 3-letter codes (defaults to all days)
MBlackout StartNoDD/MM/YYYY
NBlackout EndNoDD/MM/YYYY
OPricing TypeNoDropdown: “Room” (default, pre-filled) or “Supplement”. Accepts old values (“Room Price”, “Supplement per Person”) on import.
P+Price columnsMin 11A, 2A, 3A, 4A, 6A, 2A+1CH, 2A+2CH, 2A+1B

One row creates: SupplierHotelSupplierService (type=Hotel, pricing=PerNight) → SupplierServiceRateSupplierServiceRatePrice (one per filled price column).

Pricing Type validation: All rows for the same hotel identity (matching name, city, address, category, meal plan, and room config) must use the same Pricing Type. Conflicting rows produce a validation error.

10 columns:

ColumnFieldRequiredNotes
ANameYesCreates SupplierActivity
BDescriptionNoMax 500 chars
CCityYes
DTime SlotNoDropdown: Morning, Afternoon, Full Day, Lunch, Dinner
ERate Start DateYesDD/MM/YYYY
FRate End DateYesDD/MM/YYYY
GOperating DaysNoComma-separated 3-letter codes (defaults to all days)
HBlackout StartNoDD/MM/YYYY
IBlackout EndNoDD/MM/YYYY
JPrice per PersonYesIn supplier currency

One row creates: SupplierActivitySupplierService (type=Activity, pricing=PerPerson) → SupplierServiceRate (allotment=999) → SupplierServiceRatePrice (room_type=per_person).

  • Date validation cells (DD/MM/YYYY format)
  • Number validation for allotment and price columns
  • Cell comments on headers with format hints and examples
  • Instructions sheet in each template
  • Empty rows are skipped automatically
  • All validation errors reported with row numbers

Source: backend/app/Services/SupplierTemplateService.php (export), backend/app/Services/SupplierTemplateImportService.php (import)

Use these commands to populate your local environment with test data. Run them in order: hotels → activities → services.

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 --suppliers=2 \
--countries=Spain --countries=France \
--hotels-per-city=3 \
--use-ai

Options:

OptionDefaultDescription
--suppliers=*interactiveSupplier IDs (multiple values allowed)
--countries=*interactiveCountry names from airports table
--hotels-per-city2Hotels per city (minimum 2)
--use-aioffUse OpenRouter API for realistic data
  • 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

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

Generate test supplier activities (tours, excursions, experiences) by city.

Terminal window
# Interactive mode
./vendor/bin/sail artisan suppliers:activities:generate
# Non-interactive
./vendor/bin/sail artisan suppliers:activities:generate \
--suppliers=1 \
--countries=Spain \
--activities-per-city=2

Options:

OptionDefaultDescription
--suppliers=*interactiveSupplier IDs (multiple values allowed)
--countries=*interactiveCountry names from airports table
--activities-per-city3Activities per city (minimum 1)

Creates activities for each supplier. Use suppliers:services:generate --type=activity afterward to create linked services with per_person pricing.

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

Generate supplier services with rates and pricing. Links to hotels/activities created in previous steps.

Terminal window
# Interactive mode (prompts for type and supplier)
./vendor/bin/sail artisan suppliers:services:generate
# Non-interactive
./vendor/bin/sail artisan suppliers:services:generate \
--type=hotel --suppliers=1 --count=10

Options:

OptionDefaultDescription
--typeinteractiveService type: hotel, activity, or transfer
--suppliers=*interactiveSupplier IDs (multiple values allowed)
--count20Number of services to create per supplier

Pricing models are assigned automatically by type: per_night for hotels, per_person for activities, per_trip for transfers.

Source: backend/app/Console/Commands/GenerateTestSupplierServices.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 (selection/luxury/grand_luxury)
supplier_tour_itinerary_activitiesActivity pivot (included/extra/substitution per day)
supplier_tour_itinerary_transfersTransfer pivot (selection/luxury/grand_luxury per day)
supplier_tour_ratesTour rate periods (legacy)
supplier_tour_rate_room_pricesTour room prices (legacy)
TablePurpose
supplier_contractsContract header with status, validity, signing info
supplier_contract_servicesLine items linking contracts to services/rates with price snapshots
  • 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, backend/app/Enums/ActivityTimeSlot.php, backend/app/Enums/ServiceTier.php, backend/app/Enums/ActivityTier.php, backend/app/Enums/SupplierContractStatus.php, backend/app/Enums/TourStatus.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/
  • Template Export: backend/app/Services/SupplierTemplateService.php
  • Template Import: backend/app/Services/SupplierTemplateImportService.php
  • Tour Service: backend/app/Services/SupplierTourService.php
  • Contract Service: backend/app/Services/SupplierContractService.php
  • Contract Resource: backend/app/Filament/Resources/Suppliers/SupplierContracts/
  • Contract CLI: backend/app/Console/Commands/GenerateSupplierContractCommand.php
  • Time Slot Validator: backend/app/Services/Validation/ActivityTimeSlotOverlapValidator.php
  • Checkout Services: backend/app/Services/Checkout/CheckoutHotelService.php, CheckoutActivityService.php, CheckoutTransferService.php
  • Preview DTO: backend/app/Filament/Resources/Offers/Schemas/OfferPreviewData.php