PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.17
Yatra – Travel Booking & Tour Operator Software v3.0.17
3.0.17 3.0.16 3.0.15 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 All 85 releases
← All changes | app/Services/AvailabilityService.php +72 -1 3.0.4 → 3.0.17 View file →
@@ -126,9 +126,29 @@
126 126 */
127 127 public function create(array $data): Availability
128 128 {
129 129 $this->validate($data);
130 -
130 +
131 + // The table carries a UNIQUE KEY on (trip_id, departure_date, departure_time).
132 + // Without this check the insert is rejected by the database, the repository
133 + // hands back id 0, findModel(0) returns null and the declared return type
134 + // raises a TypeError — which is an Error, not an Exception, so it escaped
135 + // the controller's catch blocks and surfaced as a fatal 500. Rejecting it
136 + // here gives the operator the same clear message duplicate() already gives.
137 + $departureTime = !empty($data['departure_time'])
138 + ? $this->normalizeTimeFormat((string) $data['departure_time'])
139 + : null;
140 + if ($departureTime === false) {
141 + throw new \InvalidArgumentException('Invalid departure time format. Use HH:MM');
142 + }
143 + if ($this->repository->existsForTripDateTime(
144 + (int) $data['trip_id'],
145 + (string) $data['departure_date'],
146 + $departureTime
147 + )) {
148 + throw new \InvalidArgumentException('Availability date already exists for the selected departure');
149 + }
150 +
131 151 // Set default seats_available if not provided
132 152 if (!isset($data['seats_available'])) {
133 153 $data['seats_available'] = $data['seats_total'] ?? 0;
134 154 }
@@ -163,8 +183,30 @@
163 183
164 184 // Merge with existing data for validation
165 185 $mergedData = array_merge($existing->toArray(), $data);
166 186 $this->validate($mergedData, $id);
187 +
188 + // Moving a date onto a slot another row already holds is rejected by the
189 + // same UNIQUE KEY. wpdb::update() then returns false, which this method
190 + // used to ignore — it re-read the untouched row and returned 200, so the
191 + // operator was told the change saved when nothing had changed.
192 + if (array_key_exists('departure_date', $data) || array_key_exists('departure_time', $data)) {
193 + $targetDate = (string) ($data['departure_date'] ?? $existing->departure_date);
194 + if (array_key_exists('departure_time', $data)) {
195 + $targetTime = !empty($data['departure_time'])
196 + ? $this->normalizeTimeFormat((string) $data['departure_time'])
197 + : null;
198 + if ($targetTime === false) {
199 + throw new \InvalidArgumentException('Invalid departure time format. Use HH:MM');
200 + }
201 + } else {
202 + $targetTime = $existing->departure_time;
203 + }
204 +
205 + if ($this->repository->existsForTripDateTime((int) $existing->trip_id, $targetDate, $targetTime, $id)) {
206 + throw new \InvalidArgumentException('Availability date already exists for the selected departure');
207 + }
208 + }
167 209
168 210 // Auto-update status based on availability
169 211 if (isset($data['seats_available']) || isset($data['seats_total'])) {
170 212 $seatsAvailable = (int) ($data['seats_available'] ?? $existing->seats_available);
@@ -358,8 +400,37 @@
358 400 public function getBookingCountsByAvailabilityIds(array $availabilityIds): array
359 401 {
360 402 $bookingRepository = new \Yatra\Repositories\BookingRepository();
361 403 return $bookingRepository->getBookingCountsByAvailabilityIds($availabilityIds);
404 + }
405 +
406 + /**
407 + * Booked travellers for a date/time, counted from the bookings themselves.
408 + *
409 + * The `availability_id` join used by getBookingCountsByAvailabilityIds only
410 + * counts bookings whose availability_id was set to this exact row — a stored
411 + * link that the booking paths don't reliably set, and that rule-generated
412 + * dates never have. That made the "Booked" column read 0 for real bookings.
413 + *
414 + * This counts by the booking's own identity — (trip, date, time) — via the
415 + * booking_departures link both checkout and manual booking always create, so
416 + * it can't desync. It is the same method the recurring-rule availability path
417 + * already uses. Pass the row's own departure_time so a date with several
418 + * departures reports each slot separately rather than the day's total.
419 + */
420 + public function getBookedCountForSlot(int $tripId, string $date, ?string $departureTime = null): int
421 + {
422 + if ($tripId <= 0 || $date === '') {
423 + return 0;
424 + }
425 +
426 + // Dates are stored DATE-only; a datetime input would never string-match.
427 + if (preg_match('/^(\d{4}-\d{2}-\d{2})/', $date, $m)) {
428 + $date = $m[1];
429 + }
430 +
431 + $bookingRepository = new \Yatra\Repositories\BookingRepository();
432 + return $bookingRepository->countActiveSeatsForSlot($tripId, $date, $departureTime);
362 433 }
363 434
364 435 /**
365 436 * Update availability status based on booking counts