Product Templates
ProductTemplate defines what a trip IS - its route, structure, and identity. Content is written in a source locale and can be translated when creating market-specific versions.
Purpose
Section titled “Purpose”- Define trip itinerary with POI-based locations and multi-segment routes
- Store base marketing content
- Calculate trip duration automatically
- Provide foundation for market-specific products
Data Model
Section titled “Data Model”Table: product_templates
| Field | Type | Purpose |
|---|---|---|
| title | varchar | Product name |
| subtitle | varchar | Marketing tagline |
| sku | varchar | Auto-generated: <ID>-<DAYS> |
| source_locale | varchar | Content language (e.g., en_US) |
| itinerary | jsonb | Array of arrival + day items with POIs and routes |
| duration | int | Trip length in days (auto-calculated) |
| highlights | jsonb | Key selling points |
| categories | jsonb | Product categories (beach, luxury, etc.) |
Source: backend/app/Models/ProductTemplate.php
Itinerary Schema
Section titled “Itinerary Schema”The itinerary uses a structured format with an arrival item as the entry point followed by day items with locations and routes.
Schema Format
Section titled “Schema Format”[ { "type": "arrival", "arrival_poi_id": 123 }, { "locations": [456, 789], "nights": 2, "routes": [ {"from": "Nairobi", "to": "Amboseli", "from_id": 456, "to_id": 789, "mode": "arnk"} ], "title": "Safari Adventure", "details": "Game drives and wildlife viewing..." }, { "locations": [789, 456, 321], "nights": 1, "routes": [ {"from": "Amboseli", "to": "Nairobi", "from_id": 789, "to_id": 456, "mode": "arnk"}, {"from": "Nairobi", "to": "Lake Naivasha", "from_id": 456, "to_id": 321, "mode": "flight"} ], "title": "Transfer & Flight", "details": "Morning drive to Nairobi, afternoon flight..." }]Item Types
Section titled “Item Types”| Type | Fields | Purpose |
|---|---|---|
| Arrival | type, arrival_poi_id | Entry point to the trip (first item) |
| Day | locations, nights, routes, title, details | Daily journey with overnight location |
Route Modes
Section titled “Route Modes”Routes use the RouteMode enum to distinguish transport types:
| Mode | Value | Description |
|---|---|---|
| Flight | flight | Air travel between locations |
| Surface | arnk | Ground transport (road, rail, etc.) |
The arnk value follows airline terminology for “Arrival Not Known” - used for surface segments in multi-city itineraries.
Source: backend/app/Enums/RouteMode.php
Schema Helper Class
Section titled “Schema Helper Class”ItinerarySchema is the single source of truth for itinerary structure. Use it when working with itinerary data programmatically.
use App\Support\ItinerarySchema;
// Create itemsItinerarySchema::createArrivalItem($poiId);ItinerarySchema::createDayItem($locationIds, $nights, $routes, $title, $details);ItinerarySchema::createRoute($from, $to, RouteMode::Flight, $fromId, $toId);
// Check item typesItinerarySchema::isArrival($item);ItinerarySchema::isFlightRoute($route);Source: backend/app/Support/ItinerarySchema.php
Duration Calculation
Section titled “Duration Calculation”Duration is automatically calculated from itinerary day items:
duration = total_nights + 1Arrival items are skipped (no nights). Example: Day 1 (2 nights) + Day 2 (1 night) = 3 nights + 1 = 4 days.
Source: backend/app/Filament/Resources/ProductTemplates/Support/ItineraryCalculator.php
Route Continuity
Section titled “Route Continuity”The itinerary enforces route continuity: each day must start where the previous day ended. When using AI generation, the service automatically inserts connecting segments to maintain a continuous route graph.
Example: If Day 1 ends in Amboseli and Day 2 starts with Nairobi, a connecting segment is added automatically.
Itinerary Calculator
Section titled “Itinerary Calculator”Helper class for itinerary computations:
use App\Filament\Resources\ProductTemplates\Support\ItineraryCalculator;
ItineraryCalculator::calculateDuration($itinerary); // Total daysItineraryCalculator::getArrivalLocation($itinerary); // Entry city nameItineraryCalculator::getAllLocations($itinerary); // All unique location namesItineraryCalculator::hasFlightSegments($itinerary); // Has any flights?ItineraryCalculator::generateHotelAssignments($itinerary); // Hotel assignment arraySource: backend/app/Filament/Resources/ProductTemplates/Support/ItineraryCalculator.php
Admin Form Component
Section titled “Admin Form Component”The ItineraryRepeater provides the Filament form interface:
- First item is always arrival type (cannot be deleted)
- POI multi-select for day locations with Google Places search
- Auto-generated routes from location sequence
- Route mode selection (flight/surface)
- Duration auto-updates on change
Source: backend/app/Filament/Resources/ProductTemplates/Schemas/Components/ItineraryRepeater.php
AI Content Generation
Section titled “AI Content Generation”Templates support AI-powered content generation with automatic POI resolution:
- Trip Parser - Paste raw trip info, AI generates itinerary with POI lookups
- Title-based Generation - Enter title, AI generates descriptions
The AI service:
- Generates itinerary in the new schema format
- Resolves location names to POI IDs via database and Google Places
- Ensures route continuity between days
- Handles multi-segment days (e.g., drive + flight)
Configuration: OPENROUTER_API_KEY environment variable
Source: backend/app/Services/ProductTemplateAIService.php
Flight Route Extraction
Section titled “Flight Route Extraction”The FlightRouteConfigGenerator analyzes itineraries to extract flight segments for booking configuration:
- Identifies arrival location (first international flight destination)
- Extracts routes with
mode: flightfor domestic segments - Deduplicates consecutive stops sharing the same airport
- Generates multi-city and separate flight options
Source: backend/app/Services/Flights/FlightRouteConfigGenerator.php
Business Rules
Section titled “Business Rules”- SKU is auto-generated on save (cannot be manually edited)
- Duration auto-calculates from itinerary nights
- First itinerary item must be arrival type
- Route continuity is enforced (no gaps in route graph)
- Templates with associated ProductByMarket records are “locked”
- Itinerary changes propagate to market products
Related
Section titled “Related”- POI System - Points of Interest database
- Products by Market - Market-specific configuration
- Admin Panel - Filament management
- Database Relationships - Entity diagram and FK references