| @@ -18,8 +18,9 @@ | ||
| 18 | 18 | use Yatra\Utils\Cache; |
| 19 | 19 | use Yatra\Utils\QueryCache; |
| 20 | 20 | use Yatra\Database\Tables\TripAvailabilityDatesTable; |
| 21 | 21 | use Yatra\Database\Tables\TripAvailabilityRulesTable; |
| 22 | +use Yatra\Database\Tables\DeparturesTable; | |
| 22 | 23 | use Yatra\Constants\ClassificationTypes; |
| 23 | 24 | |
| 24 | 25 | /** |
| 25 | 26 | * Trip Repository |
| @@ -270,8 +271,9 @@ | ||
| 270 | 271 | * Create trip row; {@see afterWrite} handles cache; fires action `yatra_trip_created` once per insert. |
| 271 | 272 | */ |
| 272 | 273 | public function create(array $data): int |
| 273 | 274 | { |
| 275 | + $data = $this->dropUnsupportedDurationHours($data); | |
| 274 | 276 | $id = parent::create($data); |
| 275 | 277 | do_action('yatra_trip_created', $id); |
| 276 | 278 | |
| 277 | 279 | return $id; |
| @@ -277,12 +279,32 @@ | ||
| 277 | 279 | return $id; |
| 278 | 280 | } |
| 279 | 281 | |
| 280 | 282 | /** |
| 283 | + * Defensive: if the `duration_hours` column is somehow missing (a failed or | |
| 284 | + * pending column-add migration on a restrictive host), drop it from the | |
| 285 | + * write set so a normal trip save still succeeds instead of erroring on an | |
| 286 | + * unknown column. The hour-based feature simply stays off until the column | |
| 287 | + * exists — it never breaks saving. | |
| 288 | + * | |
| 289 | + * @param array<string, mixed> $data | |
| 290 | + * @return array<string, mixed> | |
| 291 | + */ | |
| 292 | + protected function dropUnsupportedDurationHours(array $data): array | |
| 293 | + { | |
| 294 | + if (array_key_exists('duration_hours', $data) && !$this->tripTableHasColumn('duration_hours')) { | |
| 295 | + unset($data['duration_hours']); | |
| 296 | + } | |
| 297 | + | |
| 298 | + return $data; | |
| 299 | + } | |
| 300 | + | |
| 301 | + /** | |
| 281 | 302 | * Override update method to provide proper field formats |
| 282 | 303 | */ |
| 283 | 304 | public function update(int $id, array $data): bool |
| 284 | 305 | { |
| 306 | + $data = $this->dropUnsupportedDurationHours($data); | |
| 285 | 307 | $data = $this->sanitizeData($data); |
| 286 | 308 | $data['updated_at'] = current_time('mysql'); |
| 287 | 309 | |
| 288 | 310 | // Build format array based on field types |
| @@ -400,8 +422,21 @@ | ||
| 400 | 422 | return self::$tripColumnExistsCache[$column]; |
| 401 | 423 | } |
| 402 | 424 | |
| 403 | 425 | /** |
| 426 | + * Public, cached column-existence check for the trips table. | |
| 427 | + * | |
| 428 | + * Lets raw SELECTs outside this repository (booking confirmation join, | |
| 429 | + * similar-trips query) add columns introduced by later upgrades — e.g. | |
| 430 | + * `duration_hours` — without failing on an install whose ALTER has not | |
| 431 | + * run yet (see InstallerService::maybeAddTripDurationHoursColumn()). | |
| 432 | + */ | |
| 433 | + public function hasTripColumn(string $column): bool | |
| 434 | + { | |
| 435 | + return $this->tripTableHasColumn($column); | |
| 436 | + } | |
| 437 | + | |
| 438 | + /** | |
| 404 | 439 | * SQL expression for trip "current" list price (matches TripPricingService::resolveRegularCurrentPrice). |
| 405 | 440 | */ |
| 406 | 441 | protected function sqlTripEffectiveListPrice(): string |
| 407 | 442 | { |
| @@ -810,9 +845,29 @@ | ||
| 810 | 845 | if (!empty($filters['duration_max']) && $filters['duration_max'] > 0) { |
| 811 | 846 | $wheres[] = "CAST(t.duration_days AS UNSIGNED) <= %d"; |
| 812 | 847 | $params[] = $filters['duration_max']; |
| 813 | 848 | } |
| 814 | - | |
| 849 | + | |
| 850 | + // Availability date filter ("available from this date onward"): keep only | |
| 851 | + // trips that have a departure starting ON OR AFTER the selected date, so | |
| 852 | + // a customer who picks a date sees every trip they could still travel on | |
| 853 | + // from then. A departure matches when its effective start date (the | |
| 854 | + // explicit start_date, else the single `date`) is >= the chosen day and | |
| 855 | + // it has not been cancelled — the date bound alone excludes past dates | |
| 856 | + // when the picker's min=today is respected, so no reliance on a possibly | |
| 857 | + // stale 'past' status. Clause + param are appended together so the WHERE | |
| 858 | + // ordering stays in sync with $params (see the count/main queries). | |
| 859 | + if (!empty($filters['available_date'])) { | |
| 860 | + $departures_table = DeparturesTable::getTableName(); | |
| 861 | + $wheres[] = "EXISTS ( | |
| 862 | + SELECT 1 FROM {$departures_table} yd | |
| 863 | + WHERE yd.trip_id = t.id | |
| 864 | + AND COALESCE(NULLIF(yd.start_date, '0000-00-00'), yd.date) >= %s | |
| 865 | + AND yd.status <> 'cancelled' | |
| 866 | + )"; | |
| 867 | + $params[] = $filters['available_date']; | |
| 868 | + } | |
| 869 | + | |
| 815 | 870 | // Rating filter |
| 816 | 871 | if (!empty($filters['rating_min']) && $filters['rating_min'] > 0) { |
| 817 | 872 | $having_clauses[] = "AVG(r.rating) >= %f"; |
| 818 | 873 | $rating_params[] = $filters['rating_min']; |