PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.3
Yatra – Travel Booking & Tour Operator Software v3.0.3
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 2.0.10 All 83 releases
yatra / app / Controllers / TripAvailabilityController.php

TripAvailabilityController.php in Yatra – Travel Booking & Tour Operator Software 3.0.3, at app/Controllers/TripAvailabilityController.php

887 lines 34.5 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\Controllers;
6
7 use WP_REST_Request;
8 use WP_REST_Response;
9 use WP_Error;
10 use Yatra\Services\DepartureService;
11 use Yatra\Services\RecurringRuleService;
12 use Yatra\Repositories\DepartureRepository;
13 use Yatra\Repositories\RecurringRuleRepository;
14
15 /**
16 * Trip Availability Controller
17 * REST API endpoints for managing trip departures and recurring rules
18 */
19 class TripAvailabilityController extends BaseController
20 {
21 private DepartureService $departureService;
22 private RecurringRuleService $ruleService;
23
24 public function __construct()
25 {
26 $departureRepo = new DepartureRepository();
27 $ruleRepo = new RecurringRuleRepository();
28
29 $this->departureService = new DepartureService($departureRepo);
30 $this->ruleService = new RecurringRuleService($ruleRepo, $departureRepo);
31 }
32
33 /**
34 * Register routes
35 */
36 public function register_routes(): void
37 {
38 $namespace = 'yatra/v1';
39
40 // All departures endpoint (without trip ID)
41 register_rest_route($namespace, '/departures', [
42 [
43 'methods' => \WP_REST_Server::READABLE,
44 'callback' => [$this, 'get_all_departures'],
45 'permission_callback' => [$this, 'check_permission'],
46 ],
47 ]);
48
49 $base = 'trips/(?P<trip_id>[\d]+)/departures';
50
51 // Departures endpoints
52 register_rest_route($namespace, '/' . $base, [
53 [
54 'methods' => \WP_REST_Server::READABLE,
55 'callback' => [$this, 'get_departures'],
56 'permission_callback' => [$this, 'check_permission'],
57 ],
58 [
59 'methods' => \WP_REST_Server::CREATABLE,
60 'callback' => [$this, 'create_departure'],
61 'permission_callback' => [$this, 'check_permission'],
62 ],
63 ]);
64
65 register_rest_route($namespace, '/' . $base . '/(?P<id>[\d]+)', [
66 [
67 'methods' => \WP_REST_Server::READABLE,
68 'callback' => [$this, 'get_departure'],
69 'permission_callback' => [$this, 'check_permission'],
70 ],
71 [
72 'methods' => \WP_REST_Server::EDITABLE,
73 'callback' => [$this, 'update_departure'],
74 'permission_callback' => [$this, 'check_permission'],
75 ],
76 [
77 'methods' => \WP_REST_Server::DELETABLE,
78 'callback' => [$this, 'delete_departure'],
79 'permission_callback' => [$this, 'check_permission'],
80 ],
81 ]);
82
83 // Past departures endpoint
84 register_rest_route($namespace, '/' . $base . '/past', [
85 [
86 'methods' => \WP_REST_Server::READABLE,
87 'callback' => [$this, 'get_past_departures'],
88 'permission_callback' => [$this, 'check_permission'],
89 ],
90 ]);
91
92 // Available dates endpoint (for frontend)
93 register_rest_route($namespace, '/trips/(?P<trip_id>[\d]+)/available-dates', [
94 [
95 'methods' => \WP_REST_Server::READABLE,
96 'callback' => [$this, 'get_available_dates'],
97 'permission_callback' => '__return_true', // Public endpoint
98 ],
99 ]);
100
101 // Recurring rules endpoints
102 $rulesBase = 'trips/(?P<trip_id>[\d]+)/recurring-rules';
103 register_rest_route($namespace, '/' . $rulesBase, [
104 [
105 'methods' => \WP_REST_Server::READABLE,
106 'callback' => [$this, 'get_recurring_rules'],
107 'permission_callback' => [$this, 'check_permission'],
108 ],
109 [
110 'methods' => \WP_REST_Server::CREATABLE,
111 'callback' => [$this, 'create_recurring_rule'],
112 'permission_callback' => [$this, 'check_permission'],
113 ],
114 ]);
115
116 register_rest_route($namespace, '/' . $rulesBase . '/(?P<id>[\d]+)', [
117 [
118 'methods' => \WP_REST_Server::READABLE,
119 'callback' => [$this, 'get_recurring_rule'],
120 'permission_callback' => [$this, 'check_permission'],
121 ],
122 [
123 'methods' => \WP_REST_Server::EDITABLE,
124 'callback' => [$this, 'update_recurring_rule'],
125 'permission_callback' => [$this, 'check_permission'],
126 ],
127 [
128 'methods' => \WP_REST_Server::DELETABLE,
129 'callback' => [$this, 'delete_recurring_rule'],
130 'permission_callback' => [$this, 'check_permission'],
131 ],
132 ]);
133
134 // Preview recurring rule dates
135 register_rest_route($namespace, '/' . $rulesBase . '/(?P<id>[\d]+)/preview', [
136 [
137 'methods' => \WP_REST_Server::READABLE,
138 'callback' => [$this, 'preview_recurring_rule'],
139 'permission_callback' => [$this, 'check_permission'],
140 ],
141 ]);
142 }
143
144 /**
145 * Check permission
146 */
147 public function check_permission(?WP_REST_Request $request = null): bool
148 {
149 return current_user_can('manage_options');
150 }
151
152 // =========================================================================
153 // DEPARTURES ENDPOINTS
154 // =========================================================================
155
156 /**
157 * GET /trips/{trip_id}/departures
158 */
159 public function get_departures(WP_REST_Request $request): WP_REST_Response
160 {
161 $tripId = (int) $request->get_param('trip_id');
162 $status = $request->get_param('status');
163 $source = $request->get_param('source');
164 $dateFrom = $request->get_param('date_from');
165 $dateTo = $request->get_param('date_to');
166 $includePast = $request->get_param('include_past') !== 'false';
167
168 $filters = [];
169 if ($status) $filters['status'] = $status;
170 if ($source) $filters['source'] = $source;
171 if ($dateFrom && trim($dateFrom) !== '') $filters['date_from'] = $dateFrom;
172 if ($dateTo && trim($dateTo) !== '') $filters['date_to'] = $dateTo;
173 $filters['include_past'] = $includePast;
174
175 try {
176 $departures = $this->departureService->getByTripId($tripId, $filters);
177
178 // Get trip information
179 $tripRepository = new \Yatra\Repositories\TripRepository();
180 $trip = $tripRepository->find($tripId);
181
182 // Get booking departure repository for booking links
183 $bookingDepartureRepo = new \Yatra\Repositories\BookingDepartureRepository();
184 $travellerRepo = new \Yatra\Repositories\TravellerRepository();
185 $bookingRepo = new \Yatra\Repositories\BookingRepository();
186
187 // Get capacity service to sync capacity from availability
188 $capacityService = new \Yatra\Services\CapacityService();
189 $departureRepo = new \Yatra\Repositories\DepartureRepository();
190
191 return new WP_REST_Response([
192 'success' => true,
193 'data' => array_map(function ($d) use ($trip, $bookingDepartureRepo, $travellerRepo, $bookingRepo, $capacityService, $departureRepo) {
194 // Sync capacity from availability before returning
195 $date = $d->start_date ?: $d->date;
196 $correctCapacity = $capacityService->getCapacityForDate($d->trip_id, $date);
197 if ($correctCapacity > 0 && $d->max_capacity !== $correctCapacity) {
198 $departureRepo->update($d->id, ['max_capacity' => $correctCapacity]);
199 $d->max_capacity = $correctCapacity;
200 }
201
202 $departureArray = $d->toArray();
203
204 // Add trip information
205 if ($trip) {
206 $departureArray['trip'] = [
207 'id' => (int) $trip->id,
208 'title' => $trip->title ?? '',
209 'slug' => $trip->slug ?? '',
210 ];
211 }
212
213 // Add booking links and get travelers
214 $bookingIds = $bookingDepartureRepo->getBookingIdsForDeparture($d->id);
215 $departureArray['booking_ids'] = $bookingIds;
216 $departureArray['bookings_count'] = count($bookingIds);
217
218 // Recalculate revenue for departures with bookings
219 if (!empty($bookingIds)) {
220 try {
221 // Call the service method to recalculate revenue
222 $totalRevenue = 0.00;
223 foreach ($bookingIds as $bookingId) {
224 $booking = $bookingRepo->find($bookingId);
225 if ($booking && !empty($booking->total_amount)) {
226 $totalRevenue += (float) $booking->total_amount;
227 }
228 }
229 // Set revenue in the array directly
230 $departureArray['total_revenue'] = $totalRevenue;
231 } catch (\Exception $e) {
232 // If any error occurs, leave the original value
233 }
234 }
235
236 // Get all travelers for this departure
237 $allTravelers = [];
238 foreach ($bookingIds as $bookingId) {
239 $travelers = $travellerRepo->getByBookingId($bookingId);
240 $booking = $bookingRepo->find($bookingId);
241 foreach ($travelers as $traveler) {
242 $fields = $traveler['fields'] ?? [];
243
244 $firstName = $fields['first_name']
245 ?? $traveler['first_name']
246 ?? ($booking->contact_first_name ?? '');
247 $lastName = $fields['last_name']
248 ?? $traveler['last_name']
249 ?? ($booking->contact_last_name ?? '');
250
251 $email = $fields['email']
252 ?? $fields['contact_email']
253 ?? $fields['primary_email']
254 ?? ($booking->contact_email ?? '');
255
256 $phone = $fields['phone']
257 ?? $fields['contact_phone']
258 ?? $fields['mobile_phone']
259 ?? $fields['whatsapp']
260 ?? ($booking->contact_phone ?? '');
261
262 $allTravelers[] = [
263 'id' => (int) $traveler['id'],
264 'booking_id' => $bookingId,
265 'booking_reference' => $booking ? ($booking->reference ?? '') : '',
266 'is_lead' => (bool) ($traveler['is_lead'] ?? false),
267 'first_name' => $firstName,
268 'last_name' => $lastName,
269 'email' => $email,
270 'phone' => $phone,
271 ];
272 }
273 }
274 $departureArray['travelers'] = $allTravelers;
275 $departureArray['travelers_count'] = count($allTravelers);
276
277 // Format time for display (remove seconds if present)
278 if (!empty($departureArray['time'])) {
279 $time = $departureArray['time'];
280 // Convert HH:MM:SS to HH:MM if needed
281 if (strlen($time) > 5 && substr_count($time, ':') === 2) {
282 $departureArray['time'] = substr($time, 0, 5);
283 }
284 }
285
286 // Debug: Log time and revenue values
287 return $departureArray;
288 }, $departures),
289 'meta' => [
290 'total' => count($departures),
291 ],
292 ]);
293 } catch (\Exception $e) {
294 return new WP_REST_Response([
295 'success' => false,
296 'message' => $e->getMessage(),
297 ], 400);
298 }
299 }
300
301 /**
302 * GET /trips/{trip_id}/departures/{id}
303 */
304 public function get_departure(WP_REST_Request $request): WP_REST_Response
305 {
306 $id = (int) $request->get_param('id');
307 $tripId = (int) $request->get_param('trip_id');
308
309 $repo = new DepartureRepository();
310 $departure = $repo->findModel($id);
311
312 if (!$departure) {
313 return new WP_REST_Response([
314 'success' => false,
315 'message' => 'Departure not found',
316 ], 404);
317 }
318
319 // Sync capacity from availability before returning
320 $capacityService = new \Yatra\Services\CapacityService();
321 $date = $departure->start_date ?: $departure->date;
322 $correctCapacity = $capacityService->getCapacityForDate($departure->trip_id, $date);
323 if ($correctCapacity > 0 && $departure->max_capacity !== $correctCapacity) {
324 $repo->update($departure->id, ['max_capacity' => $correctCapacity]);
325 $departure->max_capacity = $correctCapacity;
326 }
327
328 // Base departure array
329 $departureArray = $departure->toArray();
330
331 // Trip information
332 $tripRepository = new \Yatra\Repositories\TripRepository();
333 $trip = $tripRepository->find($tripId ?: $departure->trip_id);
334 if ($trip) {
335 // Log trip data for debugging
336 \Yatra\Utils\Logger::info("Trip data for departure {$departure->id}: " . json_encode([
337 'duration' => $trip->duration ?? 'NULL',
338 'group_type' => $trip->group_type ?? 'NULL',
339 'difficulty_level' => $trip->difficulty_level ?? 'NULL',
340 'min_travelers' => $trip->min_travelers ?? 'NULL',
341 'max_travelers' => $trip->max_travelers ?? 'NULL',
342 ]));
343
344 // Fetch difficulty level name from difficulty_levels table
345 $difficultyLevelName = '';
346 if (!empty($trip->difficulty_level) && is_numeric($trip->difficulty_level)) {
347 $difficultyRepo = new \Yatra\Repositories\DifficultyLevelRepository();
348 $difficultyLevel = $difficultyRepo->find((int) $trip->difficulty_level);
349 if ($difficultyLevel) {
350 $difficultyLevelName = $difficultyLevel->name ?? '';
351 }
352 }
353
354 // Fetch group type name from traveler_categories table
355 $groupTypeName = '';
356 if (!empty($trip->group_type) && is_numeric($trip->group_type)) {
357 $travelerCategoryRepo = new \Yatra\Repositories\TravelerCategoryRepository();
358 $travelerCategory = $travelerCategoryRepo->find((int) $trip->group_type);
359 if ($travelerCategory) {
360 $groupTypeName = $travelerCategory->name ?? '';
361 }
362 }
363
364 $departureArray['trip'] = [
365 'id' => (int) $trip->id,
366 'title' => $trip->title ?? '',
367 'slug' => $trip->slug ?? '',
368 'summary' => $trip->short_description
369 ?? $trip->excerpt
370 ?? $trip->summary
371 ?? '',
372 'starting_location' => $trip->starting_location ?? '',
373 'ending_location' => $trip->ending_location ?? '',
374 'difficulty_level' => $difficultyLevelName,
375 'group_type' => $groupTypeName,
376 'min_travelers' => $trip->min_travelers ?? null,
377 'max_travelers' => $trip->max_travelers ?? null,
378 'duration' => $trip->duration ?? null,
379 'price' => $trip->price ?? null,
380 'created_at' => $trip->created_at ?? '',
381 ];
382 }
383
384 // Related bookings and travelers (mirror get_departures logic)
385 $bookingDepartureRepo = new \Yatra\Repositories\BookingDepartureRepository();
386 $travellerRepo = new \Yatra\Repositories\TravellerRepository();
387 $bookingRepo = new \Yatra\Repositories\BookingRepository();
388
389 $bookingIds = $bookingDepartureRepo->getBookingIdsForDeparture($departure->id);
390 $departureArray['booking_ids'] = $bookingIds;
391 $departureArray['bookings_count'] = count($bookingIds);
392
393 // Calculate total revenue from bookings (simple sum of total_amount like list endpoint)
394 if (!empty($bookingIds)) {
395 try {
396 $totalRevenue = 0.00;
397 foreach ($bookingIds as $bookingId) {
398 $booking = $bookingRepo->find($bookingId);
399 if ($booking && !empty($booking->total_amount)) {
400 $totalRevenue += (float) $booking->total_amount;
401 }
402 }
403 $departureArray['total_revenue'] = $totalRevenue;
404 } catch (\Exception $e) {
405 // Leave original value on error
406 if (defined('WP_DEBUG') && WP_DEBUG) {
407 }
408 }
409 }
410
411 // Travelers linked to this departure
412 $allTravelers = [];
413 foreach ($bookingIds as $bookingId) {
414 $travelers = $travellerRepo->getByBookingId($bookingId);
415 $booking = $bookingRepo->find($bookingId);
416 foreach ($travelers as $traveler) {
417 $fields = $traveler['fields'] ?? [];
418
419 $firstName = $fields['first_name']
420 ?? $traveler['first_name']
421 ?? ($booking->contact_first_name ?? '');
422 $lastName = $fields['last_name']
423 ?? $traveler['last_name']
424 ?? ($booking->contact_last_name ?? '');
425
426 $email = $fields['email']
427 ?? $fields['contact_email']
428 ?? $fields['primary_email']
429 ?? ($booking->contact_email ?? '');
430
431 $phone = $fields['phone']
432 ?? $fields['contact_phone']
433 ?? $fields['mobile_phone']
434 ?? $fields['whatsapp']
435 ?? ($booking->contact_phone ?? '');
436
437 $allTravelers[] = [
438 'id' => (int) $traveler['id'],
439 'booking_id' => $bookingId,
440 'booking_reference' => $booking ? ($booking->reference ?? '') : '',
441 'is_lead' => (bool) ($traveler['is_lead'] ?? false),
442 'first_name' => $firstName,
443 'last_name' => $lastName,
444 'email' => $email,
445 'phone' => $phone,
446 ];
447 }
448 }
449 $departureArray['travelers'] = $allTravelers;
450 $departureArray['travelers_count'] = count($allTravelers);
451
452 // Format time (HH:MM)
453 if (!empty($departureArray['time'])) {
454 $time = $departureArray['time'];
455 if (strlen($time) > 5 && substr_count($time, ':') === 2) {
456 $departureArray['time'] = substr($time, 0, 5);
457 }
458 }
459
460 return new WP_REST_Response([
461 'success' => true,
462 'data' => $departureArray,
463 ]);
464 }
465
466 /**
467 * POST /trips/{trip_id}/departures
468 */
469 public function create_departure(WP_REST_Request $request): WP_REST_Response
470 {
471 $tripId = (int) $request->get_param('trip_id');
472 $data = $request->get_json_params();
473 $data['trip_id'] = $tripId;
474
475 try {
476 $id = $this->departureService->create($data);
477
478 $repo = new DepartureRepository();
479 $departure = $repo->findModel($id);
480
481 return new WP_REST_Response([
482 'success' => true,
483 'data' => $departure->toArray(),
484 'message' => 'Departure created successfully',
485 ], 201);
486 } catch (\Exception $e) {
487 return new WP_REST_Response([
488 'success' => false,
489 'message' => $e->getMessage(),
490 ], 400);
491 }
492 }
493
494 /**
495 * PUT /trips/{trip_id}/departures/{id}
496 */
497 public function update_departure(WP_REST_Request $request): WP_REST_Response
498 {
499 $id = (int) $request->get_param('id');
500 $data = $request->get_json_params();
501
502 try {
503 $this->departureService->update($id, $data);
504
505 $repo = new DepartureRepository();
506 $departure = $repo->findModel($id);
507
508 return new WP_REST_Response([
509 'success' => true,
510 'data' => $departure->toArray(),
511 'message' => 'Departure updated successfully',
512 ]);
513 } catch (\Exception $e) {
514 return new WP_REST_Response([
515 'success' => false,
516 'message' => $e->getMessage(),
517 ], 400);
518 }
519 }
520
521 /**
522 * DELETE /trips/{trip_id}/departures/{id}
523 */
524 public function delete_departure(WP_REST_Request $request): WP_REST_Response
525 {
526 $id = (int) $request->get_param('id');
527
528 try {
529 $this->departureService->delete($id);
530
531 return new WP_REST_Response([
532 'success' => true,
533 'message' => 'Departure deleted successfully',
534 ]);
535 } catch (\Exception $e) {
536 return new WP_REST_Response([
537 'success' => false,
538 'message' => $e->getMessage(),
539 ], 400);
540 }
541 }
542
543 /**
544 * GET /departures
545 * Get departures from all trips
546 */
547 public function get_all_departures(WP_REST_Request $request): WP_REST_Response
548 {
549 $status = $request->get_param('status');
550 $source = $request->get_param('source');
551 $dateFrom = $request->get_param('date_from');
552 $dateTo = $request->get_param('date_to');
553 $includePast = $request->get_param('include_past') !== 'false';
554
555 $filters = [];
556 if ($status) $filters['status'] = $status;
557 if ($source) $filters['source'] = $source;
558 if ($dateFrom && trim($dateFrom) !== '') $filters['date_from'] = $dateFrom;
559 if ($dateTo && trim($dateTo) !== '') $filters['date_to'] = $dateTo;
560 $filters['include_past'] = $includePast;
561
562 try {
563 // Get all departures (no trip filter)
564 $departures = $this->departureService->getAllDepartures($filters);
565
566 // Get repository for additional data
567 $bookingDepartureRepo = new \Yatra\Repositories\BookingDepartureRepository();
568 $travellerRepo = new \Yatra\Repositories\TravellerRepository();
569 $bookingRepo = new \Yatra\Repositories\BookingRepository();
570 $tripRepository = new \Yatra\Repositories\TripRepository();
571
572 // Get capacity service to sync capacity from availability
573 $capacityService = new \Yatra\Services\CapacityService();
574 $departureRepo = new \Yatra\Repositories\DepartureRepository();
575
576 // Process each departure to add related data
577 $processed = array_map(function ($d) use ($tripRepository, $bookingDepartureRepo, $travellerRepo, $bookingRepo, $capacityService, $departureRepo) {
578 // Sync capacity from availability before returning
579 $date = $d->start_date ?: $d->date;
580 $correctCapacity = $capacityService->getCapacityForDate($d->trip_id, $date);
581 if ($correctCapacity > 0 && $d->max_capacity !== $correctCapacity) {
582 $departureRepo->update($d->id, ['max_capacity' => $correctCapacity]);
583 $d->max_capacity = $correctCapacity;
584 }
585
586 $departureArray = $d->toArray();
587
588 // Add trip information
589 $trip = $tripRepository->find($d->trip_id);
590 if ($trip) {
591 $departureArray['trip'] = [
592 'id' => (int) $trip->id,
593 'title' => $trip->title ?? '',
594 'slug' => $trip->slug ?? '',
595 ];
596 }
597
598 // Add booking links and get travelers
599 $bookingIds = $bookingDepartureRepo->getBookingIdsForDeparture($d->id);
600 $departureArray['booking_ids'] = $bookingIds;
601 $departureArray['bookings_count'] = count($bookingIds);
602
603 // Recalculate revenue for departures with bookings
604 if (!empty($bookingIds)) {
605 try {
606 $totalRevenue = 0.00;
607 foreach ($bookingIds as $bookingId) {
608 $booking = $bookingRepo->find($bookingId);
609 if ($booking && !empty($booking->total_amount)) {
610 $totalRevenue += (float) $booking->total_amount;
611 }
612 }
613 $departureArray['total_revenue'] = $totalRevenue;
614 } catch (\Exception $e) {
615 }
616 }
617
618 // Get all travelers for this departure
619 $allTravelers = [];
620 foreach ($bookingIds as $bookingId) {
621 $travelers = $travellerRepo->getByBookingId($bookingId);
622 $booking = $bookingRepo->find($bookingId);
623 foreach ($travelers as $traveler) {
624 $fields = $traveler['fields'] ?? [];
625
626 $firstName = $fields['first_name']
627 ?? $traveler['first_name']
628 ?? ($booking->contact_first_name ?? '');
629 $lastName = $fields['last_name']
630 ?? $traveler['last_name']
631 ?? ($booking->contact_last_name ?? '');
632
633 $email = $fields['email']
634 ?? $fields['contact_email']
635 ?? $fields['primary_email']
636 ?? ($booking->contact_email ?? '');
637
638 $phone = $fields['phone']
639 ?? $fields['contact_phone']
640 ?? $fields['mobile_phone']
641 ?? $fields['whatsapp']
642 ?? ($booking->contact_phone ?? '');
643
644 $allTravelers[] = [
645 'id' => (int) $traveler['id'],
646 'booking_id' => $bookingId,
647 'booking_reference' => $booking ? ($booking->reference ?? '') : '',
648 'is_lead' => (bool) ($traveler['is_lead'] ?? false),
649 'first_name' => $firstName,
650 'last_name' => $lastName,
651 'email' => $email,
652 'phone' => $phone,
653 ];
654 }
655 }
656 $departureArray['travelers'] = $allTravelers;
657 $departureArray['travelers_count'] = count($allTravelers);
658
659 // Format time for display (remove seconds if present)
660 if (!empty($departureArray['time'])) {
661 $time = $departureArray['time'];
662 if (strlen($time) > 5 && substr_count($time, ':') === 2) {
663 $departureArray['time'] = substr($time, 0, 5);
664 }
665 }
666
667 return $departureArray;
668 }, $departures);
669
670 return new WP_REST_Response([
671 'success' => true,
672 'data' => $processed,
673 'meta' => [
674 'total' => count($processed),
675 ],
676 ]);
677 } catch (\Exception $e) {
678 return new WP_REST_Response([
679 'success' => false,
680 'message' => $e->getMessage(),
681 ], 400);
682 }
683 }
684
685 /**
686 * GET /trips/{trip_id}/departures/past
687 */
688 public function get_past_departures(WP_REST_Request $request): WP_REST_Response
689 {
690 $tripId = (int) $request->get_param('trip_id');
691
692 try {
693 $departures = $this->departureService->getPastByTripId($tripId);
694
695 return new WP_REST_Response([
696 'success' => true,
697 'data' => array_map(function ($d) {
698 return $d->toArray();
699 }, $departures),
700 ]);
701 } catch (\Exception $e) {
702 return new WP_REST_Response([
703 'success' => false,
704 'message' => $e->getMessage(),
705 ], 400);
706 }
707 }
708
709 /**
710 * GET /trips/{trip_id}/available-dates
711 * Public endpoint for frontend to get available dates
712 */
713 public function get_available_dates(WP_REST_Request $request): WP_REST_Response
714 {
715 $tripId = (int) $request->get_param('trip_id');
716 $fromDate = $request->get_param('from_date') ?: date('Y-m-d');
717 $toDate = $request->get_param('to_date') ?: date('Y-m-d', strtotime('+12 months'));
718
719 try {
720 $dates = $this->departureService->getAvailableDates($tripId, $fromDate, $toDate);
721
722 return new WP_REST_Response([
723 'success' => true,
724 'data' => $dates,
725 ]);
726 } catch (\Exception $e) {
727 return new WP_REST_Response([
728 'success' => false,
729 'message' => $e->getMessage(),
730 ], 400);
731 }
732 }
733
734 // =========================================================================
735 // RECURRING RULES ENDPOINTS
736 // =========================================================================
737
738 /**
739 * GET /trips/{trip_id}/recurring-rules
740 */
741 public function get_recurring_rules(WP_REST_Request $request): WP_REST_Response
742 {
743 $tripId = (int) $request->get_param('trip_id');
744 $activeOnly = $request->get_param('active_only') === 'true';
745
746 try {
747 $rules = $this->ruleService->getByTripId($tripId, $activeOnly);
748
749 return new WP_REST_Response([
750 'success' => true,
751 'data' => array_map(function ($r) {
752 return $r->toArray();
753 }, $rules),
754 ]);
755 } catch (\Exception $e) {
756 return new WP_REST_Response([
757 'success' => false,
758 'message' => $e->getMessage(),
759 ], 400);
760 }
761 }
762
763 /**
764 * GET /trips/{trip_id}/recurring-rules/{id}
765 */
766 public function get_recurring_rule(WP_REST_Request $request): WP_REST_Response
767 {
768 $id = (int) $request->get_param('id');
769
770 $repo = new RecurringRuleRepository();
771 $rule = $repo->findModel($id);
772
773 if (!$rule) {
774 return new WP_REST_Response([
775 'success' => false,
776 'message' => 'Recurring rule not found',
777 ], 404);
778 }
779
780 return new WP_REST_Response([
781 'success' => true,
782 'data' => $rule->toArray(),
783 ]);
784 }
785
786 /**
787 * POST /trips/{trip_id}/recurring-rules
788 */
789 public function create_recurring_rule(WP_REST_Request $request): WP_REST_Response
790 {
791 $tripId = (int) $request->get_param('trip_id');
792 $data = $request->get_json_params();
793 $data['trip_id'] = $tripId;
794
795 try {
796 $id = $this->ruleService->create($data);
797
798 $repo = new RecurringRuleRepository();
799 $rule = $repo->findModel($id);
800
801 return new WP_REST_Response([
802 'success' => true,
803 'data' => $rule->toArray(),
804 'message' => 'Recurring rule created successfully',
805 ], 201);
806 } catch (\Exception $e) {
807 return new WP_REST_Response([
808 'success' => false,
809 'message' => $e->getMessage(),
810 ], 400);
811 }
812 }
813
814 /**
815 * PUT /trips/{trip_id}/recurring-rules/{id}
816 */
817 public function update_recurring_rule(WP_REST_Request $request): WP_REST_Response
818 {
819 $id = (int) $request->get_param('id');
820 $data = $request->get_json_params();
821
822 try {
823 $this->ruleService->update($id, $data);
824
825 $repo = new RecurringRuleRepository();
826 $rule = $repo->findModel($id);
827
828 return new WP_REST_Response([
829 'success' => true,
830 'data' => $rule->toArray(),
831 'message' => 'Recurring rule updated successfully',
832 ]);
833 } catch (\Exception $e) {
834 return new WP_REST_Response([
835 'success' => false,
836 'message' => $e->getMessage(),
837 ], 400);
838 }
839 }
840
841 /**
842 * DELETE /trips/{trip_id}/recurring-rules/{id}
843 */
844 public function delete_recurring_rule(WP_REST_Request $request): WP_REST_Response
845 {
846 $id = (int) $request->get_param('id');
847
848 try {
849 $this->ruleService->delete($id);
850
851 return new WP_REST_Response([
852 'success' => true,
853 'message' => 'Recurring rule deleted successfully',
854 ]);
855 } catch (\Exception $e) {
856 return new WP_REST_Response([
857 'success' => false,
858 'message' => $e->getMessage(),
859 ], 400);
860 }
861 }
862
863 /**
864 * GET /trips/{trip_id}/recurring-rules/{id}/preview
865 */
866 public function preview_recurring_rule(WP_REST_Request $request): WP_REST_Response
867 {
868 $id = (int) $request->get_param('id');
869 $count = (int) ($request->get_param('count') ?: 10);
870
871 try {
872 $dates = $this->ruleService->getPreviewDates($id, $count);
873
874 return new WP_REST_Response([
875 'success' => true,
876 'data' => $dates,
877 ]);
878 } catch (\Exception $e) {
879 return new WP_REST_Response([
880 'success' => false,
881 'message' => $e->getMessage(),
882 ], 400);
883 }
884 }
885 }
886
887