Suppliers Service
Manages land service providers (DMCs), their hotel inventory, and tour packages with independent per-hotel pricing.
When to Use
Section titled “When to Use”- 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
Data Architecture
Section titled “Data Architecture”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)Key Concepts
Section titled “Key Concepts”Service Tiers (Hotels & Transfers)
Section titled “Service Tiers (Hotels & Transfers)”Hotels and transfers use a 3-tier luxury categorization system (ServiceTier):
| Tier | Enum Value | Label | Description |
|---|---|---|---|
| Selection | selection | 5 Star Selection | Base tier, included in package price |
| Luxury | luxury | 5 Star Luxury | Upgrade tier, shown as optional upgrades |
| Grand Luxury | grand_luxury | 5 Star Grand Luxury | Premium upgrade tier |
Source: backend/app/Enums/ServiceTier.php
Activity Tiers
Section titled “Activity Tiers”Activities use a dedicated ActivityTier enum, separate from the hotel/transfer tiers:
| Tier | Enum Value | Label | Description |
|---|---|---|---|
| Included | included | Included | Base activities included in package price |
| Extra | extra | Extra | Optional add-on activities (shown as “Añadir experiencia”) |
| Substitution | substitution | Substitution | Upgrade replacements for included activities (shown as “Mejorar experiencia” with gem badge) |
Source: backend/app/Enums/ActivityTier.php
Tier Relationships
Section titled “Tier Relationships”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 transfersCheckout Flow
Section titled “Checkout Flow”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 Architecture
Section titled “SupplierService Architecture”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
Supplement Pricing Mode
Section titled “Supplement Pricing Mode”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.
| Mode | is_supplement_pricing | Calculation | Example (2A, 150€/night, 3 nights) |
|---|---|---|---|
| Room | false | rate × nights | 150 × 3 = 450€ |
| Supplement | true | rate × nights × pax | 150 × 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
Service Types
Section titled “Service Types”| Type | Description | Use Case | FK Link |
|---|---|---|---|
| Hotel | Accommodation pricing | Per-hotel rates | supplier_hotel_id |
| Activity | Day tours, excursions | Per-person pricing | supplier_activity_id |
| Transfer | Airport/city transfers | Per-trip pricing | supplier_transfer_id |
Source: backend/app/Enums/SupplierServiceType.php
SupplierActivity
Section titled “SupplierActivity”Activities represent excursions, tours, and experiences that can be assigned to tour itineraries.
Source: backend/app/Models/SupplierActivity.php
Activity Fields
Section titled “Activity Fields”| Field | Description |
|---|---|
name | Activity name (e.g., “Great Wall Day Tour”) |
city | Location in CitySelect format |
time_slot | When activity occurs (morning, afternoon, full_day, lunch, dinner) |
description | Detailed description |
images | Photo gallery |
Time Slots
Section titled “Time Slots”Activities can specify when they occur during the day:
| Slot | Description |
|---|---|
morning | Morning activity |
afternoon | Afternoon activity |
full_day | All-day activity |
lunch | Lunch experience |
dinner | Dinner experience |
Source: backend/app/Enums/ActivityTimeSlot.php
Edit Lock
Section titled “Edit Lock”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.
Activity → Service Link
Section titled “Activity → Service Link”Each activity has ONE linked SupplierService for pricing:
// Activity has one service$activity->service; // SupplierService with pricing
// Service belongs to one activity$service->activity; // SupplierActivityWhen creating a service for an activity, the pricing model auto-switches to PerPerson.
SupplierTransfer
Section titled “SupplierTransfer”Transfers represent transportation services (airport pickups, city transfers) that can be assigned to tour itineraries.
Source: backend/app/Models/SupplierTransfer.php
Transfer Fields
Section titled “Transfer Fields”| Field | Description |
|---|---|
name | Transfer name (e.g., “Airport to Hotel Transfer”) |
city | Location in CitySelect format |
vehicle_type | Vehicle description (e.g., “Private Car”, “Minivan”) |
duration_minutes | Estimated duration |
description | Detailed description |
images | Photo gallery |
Transfer → Service Link
Section titled “Transfer → Service Link”Each transfer has ONE linked SupplierService for pricing:
// Transfer has one service$transfer->service; // SupplierService with pricing
// Service belongs to one transfer$service->transfer; // SupplierTransferActivity and Transfer Assignment Types
Section titled “Activity and Transfer Assignment Types”Transfers use ServiceTier (see Service Tiers). Activities use ActivityTier (see Activity Tiers):
Transfer tiers:
| Tier | Pivot Type | Description | In Offer Price? |
|---|---|---|---|
| Selection | selection | Base package transfers | Yes |
| Luxury | luxury | Upgrade transfers | No |
| Grand Luxury | grand_luxury | Premium transfers | No |
Activity tiers:
| Tier | Pivot Type | Description | In Offer Price? |
|---|---|---|---|
| Included | included | Base package activities | Yes |
| Extra | extra | Optional add-on activities | No |
| Substitution | substitution | Upgrade replacement activities | No |
// 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
Activity Time Slot Validation
Section titled “Activity Time Slot Validation”When assigning activities to tour itineraries, time slot conflicts are validated per day and tier:
Conflict Rules:
full_dayconflicts 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_slotare 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
Pricing Models
Section titled “Pricing Models”| Model | Calculation | Example |
|---|---|---|
| PerNight | price × nights | Hotel accommodation |
| Package | Fixed price | All-inclusive tour |
| PerPerson | price × travelers | Group activities |
| PerTrip | Fixed price | Airport transfer |
Source: backend/app/Enums/ServicePricingModel.php
Rate Periods (SupplierServiceRate)
Section titled “Rate Periods (SupplierServiceRate)”Each service has rate periods defining when prices apply:
- Date range:
start_datetoend_date - Weekdays: Array of allowed days (e.g.,
["mon", "thu", "sat"]) - Blackout Dates: Date ranges when service is unavailable
- Allotment: Available inventory slots.
AllotmentConsumptionrecords track consumed slots per rate per booking.AllotmentServiceenforces 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 datesisDateExcluded($date)- Checks blackout ranges onlygetPriceForRoomType($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:
| Code | Description |
|---|---|
| 1A | 1 Adult |
| 2A | 2 Adults |
| 3A | 3 Adults |
| 4A | 4 Adults |
| 1A+1CH | 1 Adult + 1 Child |
| 1A+2CH | 1 Adult + 2 Children |
| 1A+3CH | 1 Adult + 3 Children |
| 2A+1CH | 2 Adults + 1 Child |
| 2A+2CH | 2 Adults + 2 Children |
| 2A+1B | 2 Adults + 1 Baby |
| 2A+1CH+1B | 2 Adults + 1 Child + 1 Baby |
| 3A+1CH | 3 Adults + 1 Child |
| per_person | Per Person (Activity) |
| per_trip | Per Trip (Transfer) |
Special room types:
per_person- Used for activity pricing, multiplied by number of travelersper_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
TourStatus
Section titled “TourStatus”Tours have an auto-derived status (TourStatus enum) based on 6 completion steps:
| Status | Value | Description |
|---|---|---|
| Draft | draft | One or more required steps incomplete |
| Complete | complete | All 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
SupplierTourService
Section titled “SupplierTourService”Centralized business logic for tour operations:
- Status derivation —
determineStatusFromFormData()/getCompletionProgressForTour()compute the 6-step completion progress - Hotel assignments —
generateHotelAssignments(),syncHotelAssignments(),saveHotelAssignments(),loadGroupedHotelAssignments() - Activity assignments —
saveActivityAssignments(),loadActivityAssignments()with time slot overlap validation - Transfer assignments —
saveTransferAssignments(),loadTransferAssignments() - Rate periods —
saveRatePeriods(),syncRatePeriods(),loadRatePeriods()
Source: backend/app/Services/SupplierTourService.php
Rate Date Validation
Section titled “Rate Date Validation”Tour rates have specific validity rules checked by isDateAvailable():
- Date within
start_date-end_daterange - Weekday in allowed
weekdaysarray (e.g.,["mon", "thu"]) - Date not in
excluded_date_ranges(blackout periods)
Source: backend/app/Models/SupplierTourRate.php
Room Type Codes
Section titled “Room Type Codes”Standardized room type codes used across the system:
| Code | Description |
|---|---|
| 1A | 1 Adult |
| 2A | 2 Adults |
| 3A | 3 Adults |
| 4A | 4 Adults |
| 1A+1CH | 1 Adult + 1 Child |
| 1A+2CH | 1 Adult + 2 Children |
| 1A+3CH | 1 Adult + 3 Children |
| 2A+1CH | 2 Adults + 1 Child |
| 2A+2CH | 2 Adults + 2 Children |
| 2A+1B | 2 Adults + 1 Baby |
| 2A+1CH+1B | 2 Adults + 1 Child + 1 Baby |
| 3A+1CH | 3 Adults + 1 Child |
Source: backend/app/Models/SupplierTourRateRoomPrice.php (roomTypeOptions())
SupplierHotel Fields
Section titled “SupplierHotel Fields”| Field | Description |
|---|---|
hotel_name | Hotel name |
city | Location in CitySelect format (“City, Country”) |
address | Street address |
category | ServiceTier enum (selection, luxury, grand_luxury) |
meal_plan | HotelMealPlan enum |
room_types | Available room configurations (array) |
room_type | Legacy single room type field |
images | Photo gallery |
description | Optional text description, displayed in PDP hotel detail modal and checkout hotel cards |
reasons | Array of selling points (e.g., “Beachfront location”) |
amenities | Array of amenity objects with icon, label, and description |
Source: backend/app/Models/SupplierHotel.php
SupplierHotel Room Types
Section titled “SupplierHotel Room Types”Hotels use a different room type configuration defined as model constants:
| Code | Description |
|---|---|
1 pax | 1 Pax (Single) |
2 pax | 2 Pax (Double) - Required |
3 pax | 3 Pax (Triple) |
2 pax 1 baby | 2 Pax + 1 Baby |
2 pax 1 child | 2 Pax + 1 Child |
2 pax 2 children | 2 Pax + 2 Children |
4 pax | 4 Pax (Quad) |
family | Family Room |
suite | Suite |
All hotels must include the “2 pax” room type.
Source: backend/app/Models/SupplierHotel.php (ROOM_TYPES, REQUIRED_ROOM_TYPE)
Connection to Offers
Section titled “Connection to Offers”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)Offer Creation Flow
Section titled “Offer Creation Flow”- Select ProductByMarket (includes ProductTemplate and linked SupplierTour)
- System extracts all hotels from the tour’s itinerary
- System extracts all included activities (not upsells)
- System finds SupplierService for each hotel and activity
- Room type dropdown shows types available at ALL hotels (intersection)
- Price displayed is the TOTAL across all services for selected room type
- Offer stores the combined
land_base_price
Price Calculation
Section titled “Price Calculation”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_typeActivity 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_personroom 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.
Multi-Tenant Access Control
Section titled “Multi-Tenant Access Control”SupplierManager Role
Section titled “SupplierManager Role”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
Permissions
Section titled “Permissions”| Permission | Description |
|---|---|
| supplier.view_any | List suppliers |
| supplier.view | View supplier details |
| supplier.create | Create suppliers (Admin only) |
| supplier.update | Update supplier |
| supplier.delete | Delete supplier (Admin only) |
| supplier_tour.* | Tour CRUD operations |
| supplier_tour_rate.* | Rate CRUD operations |
| supplier_hotel.* | Hotel CRUD operations |
Filament Admin Resources
Section titled “Filament Admin Resources”Navigation Group: “Suppliers”
Section titled “Navigation Group: “Suppliers””| Resource | Nav Label | Model | Description |
|---|---|---|---|
| Suppliers | Suppliers | Supplier | Company management |
| Hotels | Hotels | SupplierHotel | Hotel inventory |
| Activities | Activities | SupplierActivity | Excursions, day tours |
| Transfers | Transfers | SupplierTransfer | Airport/city transfers |
| Supplier Services | Supplier Services | SupplierService | Per-hotel/activity/transfer pricing |
| SupplierTourResource | Tours | SupplierTour | Tour packages (primary product entry point) |
| Supplier Contracts | Supplier Contracts | SupplierContract | Contract management with status workflow |
| Release Dates | Release Dates | SupplierTourRate | Tour rate periods (legacy) |
Source: backend/app/Filament/Resources/Suppliers/
Tour Creation Wizard
Section titled “Tour Creation Wizard”Creating a SupplierTour uses an embedded wizard that also creates the ProductTemplate:
- Product Template Step: AI parser, title, duration, itinerary (cities/nights)
- Tour Details Step: Guide info, meals, transport, images
- 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
AI Trip Parser
Section titled “AI Trip Parser”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 DelhiGenerates:
- 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())
Flight Route Validation (Create Wizard)
Section titled “Flight Route Validation (Create Wizard)”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).
Flight Route Validation (Edit/View)
Section titled “Flight Route Validation (Edit/View)”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
Publish to Market (Edit Page)
Section titled “Publish to Market (Edit Page)”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
Tour View Page
Section titled “Tour View Page”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 Selection Performance
Section titled “Hotel Selection Performance”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
Supplier Contracts
Section titled “Supplier Contracts”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.
Use Cases
Section titled “Use Cases”- 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
Status Lifecycle
Section titled “Status Lifecycle”Contracts follow a strict state machine with guarded transitions:
Draft → Sent → Negotiating → Signed → Active → Expired ↓ ↓ ↓ ↓ ↓ └───────┴─────────┴───────────┴────────┴──→ Terminated| Status | Editable | Can Add Services | Final |
|---|---|---|---|
| Draft | Yes | Yes | No |
| Sent | Yes | Yes | No |
| Negotiating | Yes | Yes | No |
| Signed | No | No | No |
| Active | No | No | No |
| Expired | No | No | Yes |
| Terminated | No | No | Yes |
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
Reference Numbers
Section titled “Reference Numbers”Auto-generated sequential pattern: SC-{YYYY}-{0001}. The sequence resets each year.
Source: backend/app/Models/SupplierContract.php (generateReferenceNumber())
Contract Services (Line Items)
Section titled “Contract Services (Line Items)”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
SupplierContractService (Business Logic)
Section titled “SupplierContractService (Business Logic)”The service class provides:
transitionStatus()— validates transitions and sets timestampsgetServicesForQuotation()— 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 datebuildContractDataFromTour()— 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
Admin UI (Filament)
Section titled “Admin UI (Filament)”Create Page
Section titled “Create Page”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.
Edit Page Workflow Actions
Section titled “Edit Page Workflow Actions”Header actions enforce the status lifecycle:
| Action | Visible When | Modal |
|---|---|---|
| Send to Supplier | Draft | Confirmation only |
| Mark as Signed | Sent or Negotiating | Prompts for signed_at and signed_by |
| Activate | Signed | Confirmation only |
| Terminate | Any non-final status | Prompts for termination_reason |
Source: backend/app/Filament/Resources/Suppliers/SupplierContracts/
CLI: suppliers:contracts:generate
Section titled “CLI: suppliers:contracts:generate”Auto-generate a contract from a tour’s service assignments via Artisan.
# 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-runCollects 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
Bulk Template Import/Export
Section titled “Bulk Template Import/Export”The Supplier Services list page provides Excel template export/import for bulk-creating hotels or activities with their full entity chain.
Location
Section titled “Location”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
Hotels Template
Section titled “Hotels Template”Columns across entity fields, rate period, pricing type, and prices:
| Column | Field | Required | Notes |
|---|---|---|---|
| A | Hotel Name | Yes | Creates SupplierHotel |
| B | City | Yes | |
| C | Address | No | |
| D-G | Category, Room Config, Room Type, Meal Plan | No | See cell comments for format |
| H | Rate Start Date | Yes | DD/MM/YYYY |
| I | Rate End Date | Yes | DD/MM/YYYY |
| J | Rooms/Day | Yes | Allotment, minimum 1 |
| K | Release Days | No | |
| L | Operating Days | No | Comma-separated 3-letter codes (defaults to all days) |
| M | Blackout Start | No | DD/MM/YYYY |
| N | Blackout End | No | DD/MM/YYYY |
| O | Pricing Type | No | Dropdown: “Room” (default, pre-filled) or “Supplement”. Accepts old values (“Room Price”, “Supplement per Person”) on import. |
| P+ | Price columns | Min 1 | 1A, 2A, 3A, 4A, 6A, 2A+1CH, 2A+2CH, 2A+1B |
One row creates: SupplierHotel → SupplierService (type=Hotel, pricing=PerNight) → SupplierServiceRate → SupplierServiceRatePrice (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.
Activities Template
Section titled “Activities Template”10 columns:
| Column | Field | Required | Notes |
|---|---|---|---|
| A | Name | Yes | Creates SupplierActivity |
| B | Description | No | Max 500 chars |
| C | City | Yes | |
| D | Time Slot | No | Dropdown: Morning, Afternoon, Full Day, Lunch, Dinner |
| E | Rate Start Date | Yes | DD/MM/YYYY |
| F | Rate End Date | Yes | DD/MM/YYYY |
| G | Operating Days | No | Comma-separated 3-letter codes (defaults to all days) |
| H | Blackout Start | No | DD/MM/YYYY |
| I | Blackout End | No | DD/MM/YYYY |
| J | Price per Person | Yes | In supplier currency |
One row creates: SupplierActivity → SupplierService (type=Activity, pricing=PerPerson) → SupplierServiceRate (allotment=999) → SupplierServiceRatePrice (room_type=per_person).
Template Features
Section titled “Template Features”- 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)
Test Data Generation
Section titled “Test Data Generation”Use these commands to populate your local environment with test data. Run them in order: hotels → activities → services.
suppliers:hotels:generate
Section titled “suppliers:hotels:generate”Generate test supplier hotels with realistic luxury hotel data.
# 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-aiOptions:
| Option | Default | Description |
|---|---|---|
--suppliers=* | interactive | Supplier IDs (multiple values allowed) |
--countries=* | interactive | Country names from airports table |
--hotels-per-city | 2 | Hotels per city (minimum 2) |
--use-ai | off | Use 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
suppliers:activities:generate
Section titled “suppliers:activities:generate”Generate test supplier activities (tours, excursions, experiences) by city.
# 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=2Options:
| Option | Default | Description |
|---|---|---|
--suppliers=* | interactive | Supplier IDs (multiple values allowed) |
--countries=* | interactive | Country names from airports table |
--activities-per-city | 3 | Activities 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
suppliers:services:generate
Section titled “suppliers:services:generate”Generate supplier services with rates and pricing. Links to hotels/activities created in previous steps.
# 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=10Options:
| Option | Default | Description |
|---|---|---|
--type | interactive | Service type: hotel, activity, or transfer |
--suppliers=* | interactive | Supplier IDs (multiple values allowed) |
--count | 20 | Number 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
Database Schema
Section titled “Database Schema”Core Tables
Section titled “Core Tables”| Table | Purpose |
|---|---|
suppliers | Land service provider companies |
supplier_hotels | Hotel inventory per supplier |
supplier_activities | Excursion/tour inventory per supplier |
supplier_transfers | Transfer inventory per supplier |
supplier_services | Purchasable services (hotel, activity, transfer) |
supplier_service_rates | Rate periods with date/weekday constraints |
supplier_service_rate_prices | Room type prices per rate period |
Tour Tables
Section titled “Tour Tables”| Table | Purpose |
|---|---|
supplier_tours | Tour packages linked to ProductTemplates |
supplier_tour_itineraries | Hotel assignments per tour day (selection/luxury/grand_luxury) |
supplier_tour_itinerary_activities | Activity pivot (included/extra/substitution per day) |
supplier_tour_itinerary_transfers | Transfer pivot (selection/luxury/grand_luxury per day) |
supplier_tour_rates | Tour rate periods (legacy) |
supplier_tour_rate_room_prices | Tour room prices (legacy) |
Contract Tables
Section titled “Contract Tables”| Table | Purpose |
|---|---|
supplier_contracts | Contract header with status, validity, signing info |
supplier_contract_services | Line items linking contracts to services/rates with price snapshots |
Key Constraints
Section titled “Key Constraints”supplier_services.supplier_hotel_idis unique (one service per hotel)supplier_services.supplier_activity_idis unique (one service per activity)supplier_services.supplier_transfer_idis unique (one service per transfer)supplier_tour_itinerary_activitiesunique on(itinerary_id, activity_id, type)supplier_tour_itinerary_transfersunique 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