| @@ -156,16 +156,53 @@ | ||
| 156 | 156 | throw new \InvalidArgumentException('A departure already exists for this trip and date'); |
| 157 | 157 | } |
| 158 | 158 | } |
| 159 | 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 | + | |
| 160 | 188 | // Mark as manually edited by admin |
| 161 | 189 | if (!isset($data['source'])) { |
| 162 | 190 | $data['source'] = 'manual'; // Admin edits mark as manual |
| 163 | 191 | } |
| 164 | - | |
| 192 | + | |
| 165 | 193 | // Update departure |
| 166 | 194 | $result = $this->repository->update($id, $data); |
| 167 | - | |
| 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 | + | |
| 168 | 205 | // Trigger hook to sync capacity from availability |
| 169 | 206 | if ($result) { |
| 170 | 207 | do_action('yatra_departure_saved', $id); |
| 171 | 208 | } |
| @@ -305,8 +342,27 @@ | ||
| 305 | 342 | return $this->repository->findByTripId($tripId, $filters); |
| 306 | 343 | } |
| 307 | 344 | |
| 308 | 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 | + /** | |
| 309 | 365 | * Get past departures by trip ID |
| 310 | 366 | */ |
| 311 | 367 | public function getPastByTripId(int $tripId, array $filters = []): array |
| 312 | 368 | { |
| @@ -326,15 +382,18 @@ | ||
| 326 | 382 | * Combines manual departures and dynamically generated recurring rule dates |
| 327 | 383 | * |
| 328 | 384 | * @param int $tripId Trip ID |
| 329 | 385 | * @param string $fromDate Start date (default: today) |
| 330 | - * @param string $toDate End date (default: +12 months) | |
| 386 | + * @param string $toDate End date (default: today + the configurable booking horizon, 12 months unless changed) | |
| 331 | 387 | * @return array Available dates with pricing and capacity info |
| 332 | 388 | */ |
| 333 | 389 | public function getAvailableDates(int $tripId, ?string $fromDate = null, ?string $toDate = null): array |
| 334 | 390 | { |
| 335 | 391 | $fromDate = $fromDate ?? date('Y-m-d'); |
| 336 | - $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(); | |
| 337 | 396 | |
| 338 | 397 | // Get all manual departures |
| 339 | 398 | $manualDepartures = $this->repository->findByTripId($tripId, [ |
| 340 | 399 | 'date_from' => $fromDate, |