PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
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 2.0.1 All 84 releases
← All changes | app/Services/DepartureService.php +83 -9 3.0.11 → 3.0.16 View file →
@@ -56,9 +56,10 @@
56 56 // Calculate capacity based on the date if not provided
57 57 if (empty($data['max_capacity'])) {
58 58 $data['max_capacity'] = $this->capacityService->getCapacityForDate(
59 59 (int) $data['trip_id'],
60 - $startDate
60 + $startDate,
61 + $data['time'] ?? null
61 62 );
62 63
63 64 // If still no capacity, throw an error
64 65 if ($data['max_capacity'] <= 0) {
@@ -155,16 +156,53 @@
155 156 throw new \InvalidArgumentException('A departure already exists for this trip and date');
156 157 }
157 158 }
158 159
160 + // Detect a tour (trip) reassignment: the operator is moving this
161 + // departure from one trip to another. The DB layer already persists
162 + // trip_id, but the move must be validated and kept consistent:
163 + // 1. the target trip must exist,
164 + // 2. it must not collide with an existing departure on the target
165 + // trip for the same date/time, and
166 + // 3. the departure's bookings must move to the new trip too, so a
167 + // booking never ends up pointing at a departure that belongs to a
168 + // different trip (the invariant the booking edit path also keeps).
169 + $newTripId = isset($data['trip_id']) ? (int) $data['trip_id'] : 0;
170 + $tripReassigned = $newTripId > 0 && $newTripId !== (int) $departure->trip_id;
171 +
172 + if ($tripReassigned) {
173 + $targetTrip = $this->tripRepository->find($newTripId);
174 + if (!$targetTrip) {
175 + throw new \InvalidArgumentException('Target tour not found');
176 + }
177 +
178 + $checkDate = $data['start_date'] ?? $data['date'] ?? ($departure->start_date ?: $departure->date);
179 + $checkTime = $data['time'] ?? $departure->time;
180 + $existing = $this->repository->findByTripIdAndStartDate($newTripId, (string) $checkDate, $checkTime);
181 + if ($existing && (int) $existing->id !== $id) {
182 + throw new \InvalidArgumentException(
183 + 'A departure already exists for the selected tour on this date'
184 + );
185 + }
186 + }
187 +
159 188 // Mark as manually edited by admin
160 189 if (!isset($data['source'])) {
161 190 $data['source'] = 'manual'; // Admin edits mark as manual
162 191 }
163 -
192 +
164 193 // Update departure
165 194 $result = $this->repository->update($id, $data);
166 -
195 +
196 + // Move the departure's bookings onto the new trip so booking.trip_id and
197 + // departure.trip_id stay in sync after a manual reassignment.
198 + if ($result && $tripReassigned) {
199 + $bookingIds = $this->bookingDepartureRepository->getBookingIdsForDeparture($id);
200 + foreach ($bookingIds as $bookingId) {
201 + $this->bookingRepository->update((int) $bookingId, ['trip_id' => $newTripId]);
202 + }
203 + }
204 +
167 205 // Trigger hook to sync capacity from availability
168 206 if ($result) {
169 207 do_action('yatra_departure_saved', $id);
170 208 }
@@ -304,8 +342,27 @@
304 342 return $this->repository->findByTripId($tripId, $filters);
305 343 }
306 344
307 345 /**
346 + * Count departures across all trips for the same filters as
347 + * getAllDepartures() (pagination keys ignored) — the true total behind a
348 + * paginated list.
349 + */
350 + public function countAllDepartures(array $filters = []): int
351 + {
352 + return $this->repository->countAll($filters);
353 + }
354 +
355 + /**
356 + * Count departures for one trip for the same filters as getByTripId()
357 + * (pagination keys ignored).
358 + */
359 + public function countByTripId(int $tripId, array $filters = []): int
360 + {
361 + return $this->repository->countByTripId($tripId, $filters);
362 + }
363 +
364 + /**
308 365 * Get past departures by trip ID
309 366 */
310 367 public function getPastByTripId(int $tripId, array $filters = []): array
311 368 {
@@ -325,15 +382,18 @@
325 382 * Combines manual departures and dynamically generated recurring rule dates
326 383 *
327 384 * @param int $tripId Trip ID
328 385 * @param string $fromDate Start date (default: today)
329 - * @param string $toDate End date (default: +12 months)
386 + * @param string $toDate End date (default: today + the configurable booking horizon, 12 months unless changed)
330 387 * @return array Available dates with pricing and capacity info
331 388 */
332 389 public function getAvailableDates(int $tripId, ?string $fromDate = null, ?string $toDate = null): array
333 390 {
334 391 $fromDate = $fromDate ?? date('Y-m-d');
335 - $toDate = $toDate ?? date('Y-m-d', strtotime('+12 months'));
392 + // Default counted from today (not $fromDate), exactly as the previous
393 + // hard-coded '+12 months' was, so callers passing only a start date
394 + // keep the same window.
395 + $toDate = $toDate ?? yatra_get_availability_horizon_date();
336 396
337 397 // Get all manual departures
338 398 $manualDepartures = $this->repository->findByTripId($tripId, [
339 399 'date_from' => $fromDate,
@@ -419,9 +479,9 @@
419 479 */
420 480 public function findOrCreateForBooking(int $tripId, string $startDate, string $endDate, int $travelersCount = 0, ?int $defaultMaxCapacity = null, ?string $time = null): Departure
421 481 {
422 482 // Get capacity based on priority
423 - $maxCapacity = $this->capacityService->getCapacityForDate($tripId, $startDate);
483 + $maxCapacity = $this->capacityService->getCapacityForDate($tripId, $startDate, $time);
424 484
425 485 // If no capacity found from availability or rules, use the provided default
426 486 if ($maxCapacity <= 0 && $defaultMaxCapacity !== null) {
427 487 $maxCapacity = $defaultMaxCapacity;
@@ -602,9 +662,14 @@
602 662 * @param string $newStartDate New start date
603 663 * @param string $newEndDate New end date
604 664 * @return array {success: bool, new_departure_id: int, old_departure_id: int|null}
605 665 */
606 - public function handleBookingDateChange(int $bookingId, string $newStartDate, string $newEndDate): array
666 + /**
667 + * @param string|null $newDepartureTime Departure time (HH:MM/HH:MM:SS) when the trip
668 + * runs several departures a day. Optional, so
669 + * existing callers keep their behaviour.
670 + */
671 + public function handleBookingDateChange(int $bookingId, string $newStartDate, string $newEndDate, ?string $newDepartureTime = null): array
607 672 {
608 673 // Get booking to find trip_id
609 674 $booking = $this->bookingRepository->find($bookingId);
610 675 if (!$booking) {
@@ -621,10 +686,19 @@
621 686 if ($trip && !empty($trip->max_travelers)) {
622 687 $maxCapacity = (int) $trip->max_travelers;
623 688 }
624 689
625 - // Find or create new departure
626 - $newDeparture = $this->findOrCreateForBooking($tripId, $newStartDate, $newEndDate, 0, $maxCapacity);
690 + // Find or create new departure. The time matters when a trip runs several
691 + // departures a day: without it the booking lands on whichever departure
692 + // matches the date alone, so it never occupies the slot it was booked for.
693 + $newDeparture = $this->findOrCreateForBooking(
694 + $tripId,
695 + $newStartDate,
696 + $newEndDate,
697 + 0,
698 + $maxCapacity,
699 + ($newDepartureTime !== null && trim($newDepartureTime) !== '') ? trim($newDepartureTime) : null
700 + );
627 701
628 702 // Link booking to new departure
629 703 $this->bookingDepartureRepository->updateDepartureForBooking($bookingId, $newDeparture->id);
630 704