Products by Market
ProductByMarket represents how a ProductTemplate is sold in a specific market. It connects the base product to a market, adds localized content, and configures flight search parameters.
Purpose
Section titled “Purpose”- Assign products to markets (ES, DE, FR)
- Store market-specific locale (es_ES, ca_ES for Spain)
- Configure departure airports and flight routes
- Define search date ranges for flight caching
- Hold translated content via ProductByMarketTranslation
Data Model
Section titled “Data Model”Table: products_by_market
| Field | Type | Purpose |
|---|---|---|
| product_template_id | FK | Reference to ProductTemplate |
| market_id | FK | Reference to Market |
| locale | varchar | Product locale (e.g., es_ES) |
| sku | varchar | Market SKU: ES-138-10-ES1 |
| status | varchar | draft, active, inactive |
| trip_duration_days | int | Override template duration |
| search_start_date | date | Flight search period start |
| search_end_date | date | Flight search period end |
| excluded_dates | jsonb | Dates to skip in searches |
Unique Constraint: product_template_id + market_id + locale
Source: backend/app/Models/ProductByMarket.php
SKU Format
Section titled “SKU Format”<MARKET>-<TEMPLATE_ID>-<DAYS>-<LANG><VERSION>Examples:
ES-138-10-ES1- Spain, template 138, 10 days, Spanish, version 1ES-138-10-CA1- Spain, template 138, 10 days, Catalan, version 1DE-138-10-DE1- Germany, template 138, 10 days, German, version 1
Source: backend/app/Models/ProductByMarket.php:217-242
Translations
Section titled “Translations”Translated content is stored in ProductByMarketTranslation:
| Field | Purpose |
|---|---|
| title, subtitle | Localized product name |
| short_description, long_description | Marketing copy |
| itinerary | Translated day titles and details |
| url_slug | SEO-friendly URL path |
| meta_title, meta_description | SEO metadata |
AI Translation: Use “Translate from Product” button in admin to auto-translate all fields.
Source: backend/app/Services/ProductByMarketTranslationService.php
Flight Configuration
Section titled “Flight Configuration”Each ProductByMarket can have multiple departure airports, each with its own route configuration.
Flight Config Hierarchy
Section titled “Flight Config Hierarchy”ProductByMarket ├── search_start_date, search_end_date (shared) ├── excluded_dates (shared) │ └── ProductByMarketFlightConfig (per departure airport) ├── airport_id (MAD, BCN, etc.) ├── type (multi_city or separate) ├── is_active │ └── ProductByMarketFlightLeg[] (route segments) ├── origin_airport_id ├── destination_airport_id ├── day_offset └── flight_type (international, domestic, arnk)Flight Types
Section titled “Flight Types”| Type | Description | API Calls |
|---|---|---|
| multi_city | Single booking for all legs | 1 per date |
| separate | Individual bookings per leg | N per date |
Leg Types
Section titled “Leg Types”| Type | Description |
|---|---|
| international | Cross-border flight (requires API search) |
| domestic | Same-country flight (requires API search) |
| arnk | Ground transport (no flight search needed) |
Source: backend/app/Models/ProductByMarketFlightConfig.php, backend/app/Models/ProductByMarketFlightLeg.php
Flight Config to Offer Mapping
Section titled “Flight Config to Offer Mapping”When creating an offer, the wizard uses flight config legs to determine required selections:
- International legs are combined into a single multi-city search
- Domestic legs are searched separately (each creates an OfferFlight record)
- ARNK legs are skipped (no flight selection needed)
The leg_index from ProductByMarketFlightLeg carries through to OfferFlight, maintaining the trip sequence.
See Offers for the selection workflow and OfferFlight model.
Search Date Configuration
Section titled “Search Date Configuration”Flight searches are configured with:
- search_start_date - First departure date to search
- search_end_date - Last departure date to search
- excluded_dates - Specific dates to skip (holidays, blackouts)
$product->shouldSearchDate($date); // Combined check$product->isDateInSearchRange($date); // Range check only$product->isDateExcluded($date); // Exclusion check only$product->getSearchableDaysCount(); // Total searchable daysSource: backend/app/Models/ProductByMarket.php:159-207
API Calls Estimation
Section titled “API Calls Estimation”// Per flight config$config->getApiCallsPerDate(); // 1 for multi_city, N for separate$config->getTotalEstimatedApiCalls(); // Days * calls per date
// Per product by market (all configs)$product->getTotalEstimatedApiCalls(); // Sum across all configsPreview URLs
Section titled “Preview URLs”ProductByMarket supports generating preview URLs for viewing draft/inactive products on the frontend.
// Generate signed API URL (expires in 60 minutes by default)$apiUrl = $product->generatePreviewUrl(60);
// Generate frontend URL with embedded preview token$frontendUrl = $product->getFrontendPreviewUrl(60);// Returns: https://frontend.com/es/circuito/slug?preview=<base64_signed_url>The preview URL uses Laravel’s signed URL feature (HMAC-SHA256) to authenticate access without requiring user login.
Source: backend/app/Models/ProductByMarket.php:437-481
Business Rules
Section titled “Business Rules”- One ProductByMarket per template + market + locale combination
- Templates cannot be edited once they have market products (locked)
- Flight configs are generated based on itinerary when product is created
- Search dates are shared across all departure airports
- ARNK legs do not trigger flight searches
Related
Section titled “Related”- Product Templates - Base definitions
- Admin Panel - Filament management
- Flight Search - Search integration
- Database Relationships - Entity diagram and FK references