PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.13
Yatra – Travel Booking & Tour Operator Software v3.0.13
3.0.14 3.0.14.1 3.0.14.2 3.0.12 3.0.13 3.0.11 3.0.10 3.0.9 3.0.8 3.0.7 3.0.6 3.0.5 3.0.5.1 3.0.4 3.0.3 3.0.2.9 3.0.2.7 3.0.2.8 3.0.2.6 trunk 1.0.0 2.0.0 2.0.1 2.0.10 2.0.11 All 82 releases
yatra / app / Services / CapacityService.php

CapacityService.php in Yatra – Travel Booking & Tour Operator Software 3.0.13, at app/Services/CapacityService.php

160 lines 6.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 declare(strict_types=1);
4
5 namespace Yatra\Services;
6
7 use Yatra\Repositories\AvailabilityRepository;
8 use Yatra\Repositories\RecurringAvailabilityRepository;
9 use Yatra\Repositories\TripRepository;
10
11 class CapacityService
12 {
13 private AvailabilityRepository $availabilityRepository;
14 private RecurringAvailabilityRepository $recurringAvailabilityRepository;
15 private TripRepository $tripRepository;
16
17 public function __construct(
18 ?AvailabilityRepository $availabilityRepository = null,
19 ?RecurringAvailabilityRepository $recurringAvailabilityRepository = null,
20 ?TripRepository $tripRepository = null
21 ) {
22 $this->availabilityRepository = $availabilityRepository ?? new AvailabilityRepository();
23 $this->recurringAvailabilityRepository = $recurringAvailabilityRepository ?? new RecurringAvailabilityRepository();
24 $this->tripRepository = $tripRepository ?? new TripRepository();
25 }
26
27 /**
28 * Get capacity for a specific trip and date based on priority
29 *
30 * @param int $tripId Trip ID
31 * @param string $date Date in YYYY-MM-DD format
32 * @param string|null $time Departure time (HH:MM or HH:MM:SS). When the matching
33 * rule defines time slots, this selects the slot whose
34 * seat count applies. Optional for backward compatibility.
35 * @return int Maximum capacity
36 */
37 public function getCapacityForDate(int $tripId, string $date, ?string $time = null): int
38 {
39 // Normalize to Y-m-d. The availability-date and recurring-rule columns are
40 // DATE type, so a datetime input (e.g. "2026-07-18 12:30:00") breaks the
41 // rule's `end_date >= %s` boundary match (the DATE end_date is treated as
42 // midnight) and silently falls through to the trip default. Strip any time
43 // component so matching is date-only.
44 if (preg_match('/^(\d{4}-\d{2}-\d{2})/', $date, $m)) {
45 $date = $m[1];
46 }
47
48 // 1. Check Availability Date first (specific date overrides)
49 $availability = $this->availabilityRepository->findByTripIdAndDate($tripId, $date);
50 if ($availability && isset($availability->seats_total) && $availability->seats_total > 0) {
51 return (int) $availability->seats_total;
52 }
53
54 // 2. Check Recurring Availability Rules
55 $recurringRules = $this->recurringAvailabilityRepository->findActiveRulesForDate($tripId, $date);
56 if (!empty($recurringRules)) {
57 // Sort by priority (if applicable) and get the first matching rule
58 $matchingRule = reset($recurringRules);
59
60 // A rule may define its capacity per time slot rather than on the rule
61 // itself (e.g. a private tour whose vehicle seats 9 but sells one group
62 // booking per departure). The public availability path already honours
63 // the slot seats, so resolve them here too or departures/capacity would
64 // fall through to the trip default and report the wrong number.
65 $slotSeats = $this->resolveSlotSeats($matchingRule, $time);
66 if ($slotSeats > 0) {
67 return $slotSeats;
68 }
69
70 $seats = (int) ($matchingRule->seats_total ?? 0);
71 if ($seats <= 0 && !empty($matchingRule->capacity_value)) {
72 $capType = $matchingRule->capacity_type ?? 'fixed';
73 if ($capType === 'fixed') {
74 $seats = (int) $matchingRule->capacity_value;
75 }
76 }
77 if ($seats > 0) {
78 return $seats;
79 }
80 }
81
82 // 3. Fall back to trip's default capacity (column is max_travelers; max_travellers kept for legacy rows)
83 $trip = $this->tripRepository->find($tripId);
84 if (!$trip) {
85 return 0;
86 }
87
88 $cap = (int) ($trip->max_travelers ?? $trip->max_travellers ?? 0);
89
90 return $cap > 0 ? $cap : 0;
91 }
92
93 /**
94 * Resolve the seat count a rule defines on its time slots.
95 *
96 * Returns 0 when the rule has no slots, or when no slot carries a positive
97 * seat count, so the caller falls back to the existing rule/trip resolution
98 * exactly as before.
99 *
100 * @param object $rule Recurring availability rule
101 * @param string|null $time Departure time to match against (HH:MM or HH:MM:SS)
102 * @return int
103 */
104 private function resolveSlotSeats(object $rule, ?string $time): int
105 {
106 $slots = $rule->time_slots ?? null;
107
108 // Most finders hydrate this to an array, but some return the raw column.
109 if (is_string($slots)) {
110 $slots = json_decode($slots, true);
111 }
112
113 if (empty($slots) || !is_array($slots)) {
114 return 0;
115 }
116
117 // With a known departure time, only the matching slot's seats apply.
118 if ($time !== null && $time !== '') {
119 $wanted = $this->normalizeTime($time);
120 foreach ($slots as $slot) {
121 if (!is_array($slot) || !isset($slot['departure_time'])) {
122 continue;
123 }
124 if ($this->normalizeTime((string) $slot['departure_time']) === $wanted) {
125 return max(0, (int) ($slot['seats'] ?? 0));
126 }
127 }
128
129 return 0;
130 }
131
132 // Without a time, a single slot is unambiguous; multiple slots make up the
133 // day's total capacity.
134 $total = 0;
135 foreach ($slots as $slot) {
136 if (is_array($slot)) {
137 $total += max(0, (int) ($slot['seats'] ?? 0));
138 }
139 }
140
141 return $total;
142 }
143
144 /**
145 * Normalize a time value to HH:MM for comparison, so "8:00", "08:00" and
146 * "08:00:00" all match.
147 *
148 * @param string $time
149 * @return string
150 */
151 private function normalizeTime(string $time): string
152 {
153 $parts = explode(':', trim($time));
154 $hour = isset($parts[0]) ? (int) $parts[0] : 0;
155 $minute = isset($parts[1]) ? (int) $parts[1] : 0;
156
157 return sprintf('%02d:%02d', $hour, $minute);
158 }
159 }
160