| 1 |
<?php |
| 2 |
|
| 3 |
declare(strict_types=1); |
| 4 |
|
| 5 |
namespace Yatra\Services; |
| 6 |
|
| 7 |
use Yatra\Repositories\AvailabilityRepository; |
| 8 |
use Yatra\Repositories\BookingRepository; |
| 9 |
|
| 10 |
/** |
| 11 |
* Global + per-trip waitlist rules for checkout (free + Pro via filters). |
| 12 |
*/ |
| 13 |
final class WaitlistService |
| 14 |
{ |
| 15 |
/** |
| 16 |
* Site-wide waitlist toggle (Settings → Booking). |
| 17 |
*/ |
| 18 |
public static function isGloballyEnabled(): bool |
| 19 |
{ |
| 20 |
return SettingsService::isEnabled('allow_waitlist'); |
| 21 |
} |
| 22 |
|
| 23 |
/** |
| 24 |
* Global toggle plus filters (Pro or custom code may require per-trip flags). |
| 25 |
* |
| 26 |
* @param object $trip Trip model from repository |
| 27 |
*/ |
| 28 |
public static function isAllowedForTrip(object $trip): bool |
| 29 |
{ |
| 30 |
if (!self::isGloballyEnabled()) { |
| 31 |
return false; |
| 32 |
} |
| 33 |
|
| 34 |
return (bool) apply_filters('yatra_trip_allows_waitlist', true, $trip); |
| 35 |
} |
| 36 |
|
| 37 |
/** |
| 38 |
* True when this availability row cannot fit the party but waitlist may be offered. |
| 39 |
* |
| 40 |
* @param object|null $availability Resolved availability model or null (no strict cap) |
| 41 |
*/ |
| 42 |
public static function isInsufficientSeats(?object $availability, int $travelersCount): bool |
| 43 |
{ |
| 44 |
if ($availability === null || $travelersCount < 1) { |
| 45 |
return false; |
| 46 |
} |
| 47 |
|
| 48 |
$available = (int) ($availability->seats_available ?? 0); |
| 49 |
|
| 50 |
return $available < $travelersCount; |
| 51 |
} |
| 52 |
|
| 53 |
/** |
| 54 |
* Optional per-trip max waitlist travelers (same departure) — 0 or null = unlimited. |
| 55 |
*/ |
| 56 |
public static function hasWaitlistCapacity(object $trip, int $availabilityId, int $additionalTravelers): bool |
| 57 |
{ |
| 58 |
$cap = isset($trip->waitlist_capacity) ? (int) $trip->waitlist_capacity : 0; |
| 59 |
if ($cap <= 0) { |
| 60 |
return true; |
| 61 |
} |
| 62 |
|
| 63 |
$repo = new BookingRepository(); |
| 64 |
$current = $repo->getTotalWaitlistTravelersForAvailability($availabilityId); |
| 65 |
|
| 66 |
return ($current + $additionalTravelers) <= $cap; |
| 67 |
} |
| 68 |
|
| 69 |
/** |
| 70 |
* Persist waitlist counter on the availability row (display / admin). |
| 71 |
*/ |
| 72 |
public static function incrementAvailabilityWaitlistCount(int $availabilityId, int $travelers): void |
| 73 |
{ |
| 74 |
if ($availabilityId <= 0 || $travelers < 1) { |
| 75 |
return; |
| 76 |
} |
| 77 |
|
| 78 |
$repo = new AvailabilityRepository(); |
| 79 |
$repo->incrementSeatsWaitlist($availabilityId, $travelers); |
| 80 |
} |
| 81 |
|
| 82 |
/** |
| 83 |
* Decrement seats_waitlist when a waitlisted booking is removed (cancel, refund, delete). |
| 84 |
* |
| 85 |
* @param object $booking Row as returned by BookingRepository::find (must reflect waitlist state). |
| 86 |
*/ |
| 87 |
public static function releaseWaitlistHolding(object $booking): void |
| 88 |
{ |
| 89 |
if (($booking->status ?? '') !== 'waitlist') { |
| 90 |
return; |
| 91 |
} |
| 92 |
|
| 93 |
$availabilityId = (int) ($booking->availability_id ?? 0); |
| 94 |
$travelers = max(1, (int) ($booking->travelers_count ?? 1)); |
| 95 |
|
| 96 |
if ($availabilityId <= 0) { |
| 97 |
return; |
| 98 |
} |
| 99 |
|
| 100 |
$repo = new AvailabilityRepository(); |
| 101 |
$repo->incrementSeatsWaitlist($availabilityId, -$travelers); |
| 102 |
} |
| 103 |
|
| 104 |
/** |
| 105 |
* @param object|null $availability |
| 106 |
*/ |
| 107 |
public static function canJoinWaitlist(object $trip, $availability, int $travelersCount): bool |
| 108 |
{ |
| 109 |
if (!self::isAllowedForTrip($trip)) { |
| 110 |
return false; |
| 111 |
} |
| 112 |
|
| 113 |
if ($availability === null) { |
| 114 |
return false; |
| 115 |
} |
| 116 |
|
| 117 |
if (!self::isInsufficientSeats($availability, $travelersCount)) { |
| 118 |
return false; |
| 119 |
} |
| 120 |
|
| 121 |
if (!self::hasWaitlistCapacity($trip, (int) $availability->id, $travelersCount)) { |
| 122 |
return false; |
| 123 |
} |
| 124 |
|
| 125 |
return (bool) apply_filters('yatra_can_join_waitlist', true, $trip, $availability, $travelersCount); |
| 126 |
} |
| 127 |
} |
| 128 |
|