| @@ -66,20 +66,20 @@ | ||
| 66 | 66 | // ===================== |
| 67 | 67 | // BOOKINGS ROUTES |
| 68 | 68 | // ===================== |
| 69 | 69 | |
| 70 | - // List bookings | |
| 70 | + // List bookings — view cap. | |
| 71 | 71 | register_rest_route($this->namespace, '/bookings', [ |
| 72 | 72 | 'methods' => 'GET', |
| 73 | 73 | 'callback' => [$this, 'getBookings'], |
| 74 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 74 | + 'permission_callback' => [$this, 'checkCanView'], | |
| 75 | 75 | ]); |
| 76 | 76 | |
| 77 | - // Get single booking | |
| 77 | + // Get single booking — view cap. | |
| 78 | 78 | register_rest_route($this->namespace, '/bookings/(?P<id>\d+)', [ |
| 79 | 79 | 'methods' => 'GET', |
| 80 | 80 | 'callback' => [$this, 'getBooking'], |
| 81 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 81 | + 'permission_callback' => [$this, 'checkCanView'], | |
| 82 | 82 | 'args' => [ |
| 83 | 83 | 'id' => [ |
| 84 | 84 | 'required' => true, |
| 85 | 85 | 'type' => 'integer', |
| @@ -87,66 +87,75 @@ | ||
| 87 | 87 | ], |
| 88 | 88 | ], |
| 89 | 89 | ]); |
| 90 | 90 | |
| 91 | - // Create booking | |
| 91 | + // Create booking — create cap. | |
| 92 | 92 | register_rest_route($this->namespace, '/bookings', [ |
| 93 | 93 | 'methods' => 'POST', |
| 94 | 94 | 'callback' => [$this, 'createBooking'], |
| 95 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 95 | + 'permission_callback' => [$this, 'checkCanCreate'], | |
| 96 | 96 | ]); |
| 97 | 97 | |
| 98 | - // Update booking | |
| 98 | + // Update booking — edit cap. | |
| 99 | 99 | register_rest_route($this->namespace, '/bookings/(?P<id>\d+)', [ |
| 100 | 100 | 'methods' => 'PUT', |
| 101 | 101 | 'callback' => [$this, 'updateBooking'], |
| 102 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 102 | + 'permission_callback' => [$this, 'checkCanEdit'], | |
| 103 | 103 | ]); |
| 104 | 104 | |
| 105 | - // Delete booking | |
| 105 | + // Delete booking — critical-sensitivity delete cap. Only | |
| 106 | + // Owner role gets this by default. | |
| 106 | 107 | register_rest_route($this->namespace, '/bookings/(?P<id>\d+)', [ |
| 107 | 108 | 'methods' => 'DELETE', |
| 108 | 109 | 'callback' => [$this, 'deleteBooking'], |
| 109 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 110 | + 'permission_callback' => [$this, 'checkCanDelete'], | |
| 110 | 111 | ]); |
| 111 | 112 | |
| 112 | - // Update booking status | |
| 113 | + // Update booking status — dedicated change-status cap so | |
| 114 | + // Front Desk (who has this cap but NOT edit) can flip | |
| 115 | + // confirmed → checked-in without being able to mutate other | |
| 116 | + // fields. | |
| 113 | 117 | register_rest_route($this->namespace, '/bookings/(?P<id>\d+)/status', [ |
| 114 | 118 | 'methods' => 'PUT', |
| 115 | 119 | 'callback' => [$this, 'updateBookingStatus'], |
| 116 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 120 | + 'permission_callback' => [$this, 'checkCanChangeStatus'], | |
| 117 | 121 | ]); |
| 118 | 122 | |
| 119 | - // Get booking statistics | |
| 123 | + // Get booking statistics — view cap (aggregates only). | |
| 120 | 124 | register_rest_route($this->namespace, '/bookings/stats', [ |
| 121 | 125 | 'methods' => 'GET', |
| 122 | 126 | 'callback' => [$this, 'getBookingStats'], |
| 123 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 127 | + 'permission_callback' => [$this, 'checkCanView'], | |
| 124 | 128 | ]); |
| 125 | 129 | |
| 126 | - // Send booking email | |
| 130 | + // Send booking email — edit cap. Sending a transactional | |
| 131 | + // re-confirmation is a write-side operation against the | |
| 132 | + // customer's record. | |
| 127 | 133 | register_rest_route($this->namespace, '/bookings/(?P<id>\d+)/send-email', [ |
| 128 | 134 | 'methods' => 'POST', |
| 129 | 135 | 'callback' => [$this, 'sendBookingEmail'], |
| 130 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 136 | + 'permission_callback' => [$this, 'checkCanEdit'], | |
| 131 | 137 | ]); |
| 132 | 138 | |
| 133 | 139 | // ===================== |
| 134 | 140 | // PAYMENTS ROUTES |
| 135 | 141 | // ===================== |
| 136 | - | |
| 137 | - // Get booking payments | |
| 142 | + | |
| 143 | + // Get booking payments — view cap. | |
| 138 | 144 | register_rest_route($this->namespace, '/bookings/(?P<id>\d+)/payments', [ |
| 139 | 145 | 'methods' => 'GET', |
| 140 | 146 | 'callback' => [$this, 'getBookingPayments'], |
| 141 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 147 | + 'permission_callback' => [$this, 'checkCanView'], | |
| 142 | 148 | ]); |
| 143 | 149 | |
| 144 | - // Add payment to booking | |
| 150 | + // Add payment to booking — edit cap (modifies the booking's | |
| 151 | + // payment state). Refunds + payment deletion live on the | |
| 152 | + // dedicated PaymentController with their own high-sensitivity | |
| 153 | + // caps. | |
| 145 | 154 | register_rest_route($this->namespace, '/bookings/(?P<id>\d+)/payments', [ |
| 146 | 155 | 'methods' => 'POST', |
| 147 | 156 | 'callback' => [$this, 'addPayment'], |
| 148 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 157 | + 'permission_callback' => [$this, 'checkCanEdit'], | |
| 149 | 158 | ]); |
| 150 | 159 | |
| 151 | 160 | // NOTE: Payment CRUD operations moved to PaymentController |
| 152 | 161 | // This keeps BookingsController focused on booking operations only |
| @@ -153,56 +162,23 @@ | ||
| 153 | 162 | |
| 154 | 163 | // ===================== |
| 155 | 164 | // TRAVELERS ROUTES |
| 156 | 165 | // ===================== |
| 157 | - | |
| 166 | + | |
| 167 | + // Travelers list — view cap. | |
| 158 | 168 | register_rest_route($this->namespace, '/travelers', [ |
| 159 | 169 | 'methods' => 'GET', |
| 160 | 170 | 'callback' => [$this, 'getTravelers'], |
| 161 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 171 | + 'permission_callback' => [$this, 'checkCanView'], | |
| 162 | 172 | ]); |
| 163 | 173 | |
| 164 | - // Traveler bulk actions | |
| 174 | + // Traveler bulk actions — edit cap. | |
| 165 | 175 | register_rest_route($this->namespace, '/travelers/bulk', [ |
| 166 | 176 | 'methods' => 'PUT', |
| 167 | 177 | 'callback' => [$this, 'bulkTravelers'], |
| 168 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 178 | + 'permission_callback' => [$this, 'checkCanEdit'], | |
| 169 | 179 | ]); |
| 170 | 180 | |
| 171 | - // ===================== | |
| 172 | - // SCHEDULED PAYMENTS | |
| 173 | - // ===================== | |
| 174 | - | |
| 175 | - register_rest_route($this->namespace, '/scheduled-payments', [ | |
| 176 | - 'methods' => 'GET', | |
| 177 | - 'callback' => [$this, 'getScheduledPayments'], | |
| 178 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 179 | - ]); | |
| 180 | - | |
| 181 | - register_rest_route($this->namespace, '/scheduled-payments/(?P<id>\d+)', [ | |
| 182 | - 'methods' => 'GET', | |
| 183 | - 'callback' => [$this, 'getScheduledPayment'], | |
| 184 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 185 | - ]); | |
| 186 | - | |
| 187 | - register_rest_route($this->namespace, '/scheduled-payments/(?P<id>\d+)', [ | |
| 188 | - 'methods' => 'PUT', | |
| 189 | - 'callback' => [$this, 'updateScheduledPayment'], | |
| 190 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 191 | - ]); | |
| 192 | - | |
| 193 | - register_rest_route($this->namespace, '/scheduled-payments/(?P<id>\d+)/cancel', [ | |
| 194 | - 'methods' => 'POST', | |
| 195 | - 'callback' => [$this, 'cancelScheduledPayment'], | |
| 196 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 197 | - ]); | |
| 198 | - | |
| 199 | - register_rest_route($this->namespace, '/bookings/(?P<id>\d+)/scheduled-payments', [ | |
| 200 | - 'methods' => 'GET', | |
| 201 | - 'callback' => [$this, 'getBookingScheduledPayments'], | |
| 202 | - 'permission_callback' => [$this, 'checkAdminPermission'], | |
| 203 | - ]); | |
| 204 | - | |
| 205 | 181 | // Download travel voucher for a booking |
| 206 | 182 | register_rest_route($this->namespace, '/bookings/(?P<id>\d+)/voucher', [ |
| 207 | 183 | 'methods' => 'GET', |
| 208 | 184 | 'callback' => [$this, 'downloadVoucher'], |
| @@ -217,17 +193,53 @@ | ||
| 217 | 193 | ]); |
| 218 | 194 | } |
| 219 | 195 | |
| 220 | 196 | /** |
| 221 | - * Check admin permission | |
| 197 | + * Granular permission checks — one per operation. WP administrators | |
| 198 | + * pass every cap via the Team module's admin-fallback filter | |
| 199 | + * (priority 7 / 8), so an explicit `manage_options` check isn't | |
| 200 | + * needed at this layer — the cap covers it. | |
| 222 | 201 | */ |
| 202 | + public function checkCanView(): bool | |
| 203 | + { | |
| 204 | + return current_user_can('yatra_view_bookings'); | |
| 205 | + } | |
| 206 | + | |
| 207 | + public function checkCanCreate(): bool | |
| 208 | + { | |
| 209 | + return current_user_can('yatra_create_bookings'); | |
| 210 | + } | |
| 211 | + | |
| 212 | + public function checkCanEdit(): bool | |
| 213 | + { | |
| 214 | + return current_user_can('yatra_edit_bookings'); | |
| 215 | + } | |
| 216 | + | |
| 217 | + public function checkCanDelete(): bool | |
| 218 | + { | |
| 219 | + // Critical-sensitivity cap. By default only the Owner role | |
| 220 | + // holds this — Manager, Sales Agent, Front Desk, etc. cannot | |
| 221 | + // delete bookings even when they can edit them. | |
| 222 | + return current_user_can('yatra_delete_bookings'); | |
| 223 | + } | |
| 224 | + | |
| 225 | + public function checkCanChangeStatus(): bool | |
| 226 | + { | |
| 227 | + // Separate from edit — Front Desk has this without the | |
| 228 | + // broader edit cap so they can confirm/check-in bookings | |
| 229 | + // without being able to mutate other fields. | |
| 230 | + return current_user_can('yatra_change_booking_status'); | |
| 231 | + } | |
| 232 | + | |
| 233 | + /** | |
| 234 | + * @deprecated Kept for any external code (snippet, integration) | |
| 235 | + * that referenced the old method name. Routes to the view-only | |
| 236 | + * cap — safer than the old `view OR manage_options` shorthand, | |
| 237 | + * and admin users still pass via the admin-fallback layer. | |
| 238 | + */ | |
| 223 | 239 | public function checkAdminPermission(): bool |
| 224 | 240 | { |
| 225 | - // Allow custom booking capability or fallback to manage_options | |
| 226 | - if (current_user_can('yatra_view_bookings')) { | |
| 227 | - return true; | |
| 228 | - } | |
| 229 | - return current_user_can('manage_options'); | |
| 241 | + return $this->checkCanView(); | |
| 230 | 242 | } |
| 231 | 243 | |
| 232 | 244 | // ========================================================================= |
| 233 | 245 | // BOOKING ENDPOINTS |
| @@ -247,8 +259,11 @@ | ||
| 247 | 259 | 'trip_id' => (int) $request->get_param('trip_id'), |
| 248 | 260 | 'search' => $request->get_param('search') ?: '', |
| 249 | 261 | 'date_from' => $request->get_param('date_from') ?: '', |
| 250 | 262 | 'date_to' => $request->get_param('date_to') ?: '', |
| 263 | + // Column sorting from the table headers — whitelisted in the repository. | |
| 264 | + 'orderby' => $request->get_param('orderby') ?: '', | |
| 265 | + 'order' => $request->get_param('order') ?: '', | |
| 251 | 266 | ]; |
| 252 | 267 | |
| 253 | 268 | // Delegate to service |
| 254 | 269 | $result = $this->bookingService->getBookings($filters); |
| @@ -347,9 +362,9 @@ | ||
| 347 | 362 | return $this->error_response($result['message'] ?? 'Failed to update booking', 400); |
| 348 | 363 | } |
| 349 | 364 | |
| 350 | 365 | Logger::info("Booking updated successfully", ['booking_id' => $id]); |
| 351 | - return $this->success_response($result['data']); | |
| 366 | + return $this->success_response($result['data'] ?? null); | |
| 352 | 367 | |
| 353 | 368 | } catch (\Exception $e) { |
| 354 | 369 | Logger::error("Failed to update booking", ['booking_id' => $id ?? 0, 'data' => $data ?? [], 'error' => $e->getMessage()]); |
| 355 | 370 | return $this->handle_exception($e); |
| @@ -618,108 +633,9 @@ | ||
| 618 | 633 | |
| 619 | 634 | return new WP_REST_Response($result, $result['success'] ? 200 : 400); |
| 620 | 635 | } |
| 621 | 636 | |
| 622 | - // ========================================================================= | |
| 623 | - // SCHEDULED PAYMENTS ENDPOINTS | |
| 624 | - // ========================================================================= | |
| 625 | - | |
| 626 | 637 | /** |
| 627 | - * GET /scheduled-payments - List scheduled payments | |
| 628 | - */ | |
| 629 | - public function getScheduledPayments(WP_REST_Request $request): WP_REST_Response | |
| 630 | - { | |
| 631 | - $filters = [ | |
| 632 | - 'page' => (int) ($request->get_param('page') ?: 1), | |
| 633 | - 'per_page' => (int) ($request->get_param('per_page') ?: 20), | |
| 634 | - 'status' => $request->get_param('status') ?: '', | |
| 635 | - ]; | |
| 636 | - | |
| 637 | - $result = $this->paymentService->getScheduledPayments($filters); | |
| 638 | - | |
| 639 | - return new WP_REST_Response([ | |
| 640 | - 'success' => true, | |
| 641 | - 'data' => $result['data'], | |
| 642 | - 'meta' => [ | |
| 643 | - 'total' => $result['total'], | |
| 644 | - 'page' => $result['page'], | |
| 645 | - 'per_page' => $result['per_page'], | |
| 646 | - 'total_pages' => $result['total_pages'], | |
| 647 | - ], | |
| 648 | - ]); | |
| 649 | - } | |
| 650 | - | |
| 651 | - /** | |
| 652 | - * GET /scheduled-payments/{id} - Get single scheduled payment | |
| 653 | - */ | |
| 654 | - public function getScheduledPayment(WP_REST_Request $request): WP_REST_Response | |
| 655 | - { | |
| 656 | - $id = (int) $request->get_param('id'); | |
| 657 | - | |
| 658 | - $payment = $this->paymentService->getScheduledPayment($id); | |
| 659 | - | |
| 660 | - if (!$payment) { | |
| 661 | - return new WP_REST_Response([ | |
| 662 | - 'success' => false, | |
| 663 | - 'message' => __('Scheduled payment not found.', 'yatra'), | |
| 664 | - ], 404); | |
| 665 | - } | |
| 666 | - | |
| 667 | - return new WP_REST_Response([ | |
| 668 | - 'success' => true, | |
| 669 | - 'data' => $payment, | |
| 670 | - ]); | |
| 671 | - } | |
| 672 | - | |
| 673 | - /** | |
| 674 | - * PUT /scheduled-payments/{id} - Update scheduled payment | |
| 675 | - */ | |
| 676 | - public function updateScheduledPayment(WP_REST_Request $request): WP_REST_Response | |
| 677 | - { | |
| 678 | - $id = (int) $request->get_param('id'); | |
| 679 | - $data = $request->get_json_params(); | |
| 680 | - | |
| 681 | - $result = $this->paymentService->updateScheduledPayment($id, $data); | |
| 682 | - | |
| 683 | - if (!$result['success']) { | |
| 684 | - return new WP_REST_Response($result, 400); | |
| 685 | - } | |
| 686 | - | |
| 687 | - return new WP_REST_Response($result); | |
| 688 | - } | |
| 689 | - | |
| 690 | - /** | |
| 691 | - * POST /scheduled-payments/{id}/cancel - Cancel scheduled payment | |
| 692 | - */ | |
| 693 | - public function cancelScheduledPayment(WP_REST_Request $request): WP_REST_Response | |
| 694 | - { | |
| 695 | - $id = (int) $request->get_param('id'); | |
| 696 | - | |
| 697 | - $result = $this->paymentService->cancelScheduledPayment($id); | |
| 698 | - | |
| 699 | - if (!$result['success']) { | |
| 700 | - return new WP_REST_Response($result, 400); | |
| 701 | - } | |
| 702 | - | |
| 703 | - return new WP_REST_Response($result); | |
| 704 | - } | |
| 705 | - | |
| 706 | - /** | |
| 707 | - * GET /bookings/{id}/scheduled-payments - Get booking's scheduled payments | |
| 708 | - */ | |
| 709 | - public function getBookingScheduledPayments(WP_REST_Request $request): WP_REST_Response | |
| 710 | - { | |
| 711 | - $bookingId = (int) $request->get_param('id'); | |
| 712 | - | |
| 713 | - $payments = $this->paymentService->getBookingScheduledPayments($bookingId); | |
| 714 | - | |
| 715 | - return new WP_REST_Response([ | |
| 716 | - 'success' => true, | |
| 717 | - 'data' => $payments, | |
| 718 | - ]); | |
| 719 | - } | |
| 720 | - | |
| 721 | - /** | |
| 722 | 638 | * GET /bookings/{id}/voucher - Download travel voucher for a booking |
| 723 | 639 | */ |
| 724 | 640 | public function downloadVoucher(WP_REST_Request $request) |
| 725 | 641 | { |
| @@ -865,11 +781,21 @@ | ||
| 865 | 781 | $bookingDate = !empty($createdAt) ? date_i18n(get_option('date_format'), strtotime((string) $createdAt)) : ''; |
| 866 | 782 | $travelDateRaw = $booking['travel_date'] ?? ''; |
| 867 | 783 | $travelDate = !empty($travelDateRaw) ? date_i18n(get_option('date_format'), strtotime((string) $travelDateRaw)) : ''; |
| 868 | 784 | |
| 785 | + // Return date. Prefer the booking's STORED end_date — the actual booked | |
| 786 | + // return (accounts for a flexible window or a trip duration changed after | |
| 787 | + // booking). Fall back to the trip duration only when no end is stored: | |
| 788 | + // duration_days is INCLUSIVE, so the return is travel_date + (days - 1) | |
| 789 | + // (matches BookingRepository::calculateEndDate; a bare "+ duration_days" | |
| 790 | + // was one day too far and implied an extra night — see ItineraryPdfBuilder). | |
| 869 | 791 | $returnDate = ''; |
| 870 | - if (!empty($travelDateRaw) && $trip && !empty($trip->duration)) { | |
| 871 | - $returnTimestamp = strtotime((string) $travelDateRaw . ' +' . (int) $trip->duration . ' days'); | |
| 792 | + $storedEnd = (string) ($booking['end_date'] ?? ''); | |
| 793 | + if ($storedEnd !== '' && ($travelDateRaw === '' || $storedEnd >= $travelDateRaw)) { | |
| 794 | + $returnDate = date_i18n(get_option('date_format'), strtotime($storedEnd)); | |
| 795 | + } elseif (!empty($travelDateRaw) && $trip && !empty($trip->duration_days)) { | |
| 796 | + $returnOffset = max(0, (int) $trip->duration_days - 1); | |
| 797 | + $returnTimestamp = strtotime((string) $travelDateRaw . ' +' . $returnOffset . ' days'); | |
| 872 | 798 | $returnDate = date_i18n(get_option('date_format'), $returnTimestamp); |
| 873 | 799 | } |
| 874 | 800 | |
| 875 | 801 | $statusRaw = (string) ($booking['booking_status'] ?? $booking['status'] ?? ''); |
| @@ -882,12 +808,14 @@ | ||
| 882 | 808 | |
| 883 | 809 | $templateData = [ |
| 884 | 810 | 'company_name' => $companyName, |
| 885 | 811 | 'company_address' => $companyAddress, |
| 812 | + 'company_address_lines' => \Yatra\Helpers\FormatHelper::companyAddressLines(), | |
| 886 | 813 | 'company_email' => $companyEmail, |
| 887 | 814 | 'company_phone' => $companyPhone, |
| 888 | 815 | 'customer_name' => $customerName, |
| 889 | 816 | 'customer_email' => (string) ($booking['contact_email'] ?? $booking['customer_email'] ?? ''), |
| 817 | + 'customer_address_lines' => FormatHelper::customerAddressLines($booking), | |
| 890 | 818 | 'booking_ref' => $bookingRef, |
| 891 | 819 | 'booking_date' => $bookingDate, |
| 892 | 820 | 'booking_status' => ucfirst($statusRaw ?: 'pending'), |
| 893 | 821 | 'status_class' => in_array(strtolower($statusRaw), ['confirmed', 'completed', 'success'], true) ? 'confirmed' : |
| @@ -892,9 +820,19 @@ | ||
| 892 | 820 | 'booking_status' => ucfirst($statusRaw ?: 'pending'), |
| 893 | 821 | 'status_class' => in_array(strtolower($statusRaw), ['confirmed', 'completed', 'success'], true) ? 'confirmed' : |
| 894 | 822 | (in_array(strtolower($statusRaw), ['cancelled'], true) ? 'cancelled' : 'pending'), |
| 895 | 823 | 'trip_title' => $trip ? ($trip->title ?? $booking['trip_title'] ?? __('Trip Booking', 'yatra')) : ($booking['trip_title'] ?? __('Trip Booking', 'yatra')), |
| 896 | - 'trip_duration' => $trip && $trip->duration ? sprintf(__('%d days', 'yatra'), (int) $trip->duration) : '', | |
| 824 | + // Trip duration comes from duration_days/duration_nights (there is no | |
| 825 | + // `duration` column — accessing it caused a blank value + PHP notice). | |
| 826 | + 'trip_duration' => $trip | |
| 827 | + ? yatra_format_duration( | |
| 828 | + (int) ($trip->duration_days ?? 0), | |
| 829 | + isset($trip->duration_nights) ? (int) $trip->duration_nights : null, | |
| 830 | + // Hour-based day tours: "8 hours" instead of "1 day". Absent | |
| 831 | + // or NULL on every day-based trip, which keeps its wording. | |
| 832 | + (int) ($trip->duration_hours ?? 0) | |
| 833 | + ) | |
| 834 | + : '', | |
| 897 | 835 | 'trip_difficulty' => $trip ? ($trip->difficulty_name ?? '') : '', |
| 898 | 836 | 'departure_location' => $trip ? ($trip->departure_location ?? '') : '', |
| 899 | 837 | 'destination' => $trip ? ($trip->destination ?? '') : '', |
| 900 | 838 | 'travel_date' => $travelDate, |
| @@ -899,11 +837,11 @@ | ||
| 899 | 837 | 'destination' => $trip ? ($trip->destination ?? '') : '', |
| 900 | 838 | 'travel_date' => $travelDate, |
| 901 | 839 | 'return_date' => $returnDate, |
| 902 | 840 | 'currency_symbol' => $currencySymbol, |
| 903 | - 'total_amount' => number_format((float) ($booking['total_amount'] ?? 0), 2), | |
| 904 | - 'amount_paid' => number_format((float) ($booking['amount_paid'] ?? 0), 2), | |
| 905 | - 'amount_due' => number_format((float) ($booking['amount_due'] ?? 0), 2), | |
| 841 | + 'total_amount' => yatra_format_price((float) ($booking['total_amount'] ?? 0), $currency, false), | |
| 842 | + 'amount_paid' => yatra_format_price((float) ($booking['amount_paid'] ?? 0), $currency, false), | |
| 843 | + 'amount_due' => yatra_format_price((float) ($booking['amount_due'] ?? 0), $currency, false), | |
| 906 | 844 | 'traveler_count' => (int) ($booking['travelers_count'] ?? $booking['travelers'] ?? 1), |
| 907 | 845 | ]; |
| 908 | 846 | |
| 909 | 847 | $pdfService = new PdfService(); |
| @@ -939,43 +877,10 @@ | ||
| 939 | 877 | * @param array<string,mixed> $booking From BookingService::getBooking() |
| 940 | 878 | */ |
| 941 | 879 | private function renderItineraryFromBookingData(array $booking, bool $isPreview) |
| 942 | 880 | { |
| 943 | - $tripRepository = new TripRepository(); | |
| 944 | - $trip = null; | |
| 945 | - $tripId = (int) ($booking['trip_id'] ?? 0); | |
| 946 | - if ($tripId > 0) { | |
| 947 | - $trip = $tripRepository->find($tripId); | |
| 948 | - } | |
| 949 | - | |
| 950 | - $companyName = SettingsService::get('company_name', get_bloginfo('name')); | |
| 951 | - $companyAddress = SettingsService::get('company_address', ''); | |
| 952 | - $companyEmail = SettingsService::get('company_email', get_option('admin_email')); | |
| 953 | - $companyPhone = SettingsService::get('company_phone', ''); | |
| 954 | - $currency = SettingsService::getCurrency(); | |
| 955 | - $currencySymbol = FormatHelper::getCurrencySymbol($currency); | |
| 956 | - | |
| 957 | - $createdAt = $booking['created_at'] ?? $booking['booking_date'] ?? ''; | |
| 958 | - $bookingDate = !empty($createdAt) ? date_i18n(get_option('date_format'), strtotime((string) $createdAt)) : ''; | |
| 959 | - $travelDateRaw = $booking['travel_date'] ?? ''; | |
| 960 | - $travelDate = !empty($travelDateRaw) ? date_i18n(get_option('date_format'), strtotime((string) $travelDateRaw)) : ''; | |
| 961 | - | |
| 962 | - $returnDate = ''; | |
| 963 | - if (!empty($travelDateRaw) && $trip && !empty($trip->duration)) { | |
| 964 | - $returnTimestamp = strtotime((string) $travelDateRaw . ' +' . (int) $trip->duration . ' days'); | |
| 965 | - $returnDate = date_i18n(get_option('date_format'), $returnTimestamp); | |
| 966 | - } | |
| 967 | - | |
| 968 | - $statusRaw = (string) ($booking['booking_status'] ?? $booking['status'] ?? ''); | |
| 969 | - $bookingId = (int) ($booking['id'] ?? 0); | |
| 970 | - $bookingRef = 'YTR-' . strtoupper(str_pad((string) $bookingId, 8, '0', STR_PAD_LEFT)); | |
| 971 | - | |
| 972 | - $customerName = trim( | |
| 973 | - (string) ($booking['contact_first_name'] ?? '') . ' ' . (string) ($booking['contact_last_name'] ?? '') | |
| 974 | - ) ?: (string) ($booking['customer_name'] ?? __('Customer', 'yatra')); | |
| 975 | - | |
| 976 | - $pdfService = new PdfService(); | |
| 977 | - if (!$pdfService->isAvailable()) { | |
| 881 | + $builder = new \Yatra\Services\ItineraryPdfBuilder(); | |
| 882 | + if (!$builder->pdfService()->isAvailable()) { | |
| 978 | 883 | return new WP_Error( |
| 979 | 884 | 'pdf_engine_missing', |
| 980 | 885 | __('Itinerary PDF generator is not installed. Please run composer install to install dompdf/dompdf.', 'yatra'), |
| 981 | 886 | ['status' => 500] |
| @@ -981,46 +886,20 @@ | ||
| 981 | 886 | ['status' => 500] |
| 982 | 887 | ); |
| 983 | 888 | } |
| 984 | 889 | |
| 890 | + $bookingId = (int) ($booking['id'] ?? 0); | |
| 891 | + $bookingRef = $bookingId > 0 | |
| 892 | + ? 'YTR-' . strtoupper(str_pad((string) $bookingId, 8, '0', STR_PAD_LEFT)) | |
| 893 | + : 'PENDING'; | |
| 985 | 894 | $filename = 'Travel-Itinerary-' . $bookingRef . '.pdf'; |
| 986 | 895 | |
| 987 | - $templateData = [ | |
| 988 | - 'company_name' => $companyName, | |
| 989 | - 'company_address' => $companyAddress, | |
| 990 | - 'company_email' => $companyEmail, | |
| 991 | - 'company_phone' => $companyPhone, | |
| 992 | - 'customer_name' => $customerName, | |
| 993 | - 'customer_email' => (string) ($booking['contact_email'] ?? $booking['customer_email'] ?? ''), | |
| 994 | - 'booking_ref' => $bookingRef, | |
| 995 | - 'booking_date' => $bookingDate, | |
| 996 | - 'booking_status' => ucfirst($statusRaw ?: 'pending'), | |
| 997 | - 'status_class' => in_array(strtolower($statusRaw), ['confirmed', 'completed', 'success'], true) ? 'confirmed' : | |
| 998 | - (in_array(strtolower($statusRaw), ['cancelled'], true) ? 'cancelled' : 'pending'), | |
| 999 | - 'trip_title' => $trip ? ($trip->title ?? $booking['trip_title'] ?? __('Trip Booking', 'yatra')) : ($booking['trip_title'] ?? __('Trip Booking', 'yatra')), | |
| 1000 | - 'trip_description' => $trip ? ($trip->description ?? $trip->content ?? '') : '', | |
| 1001 | - 'trip_duration' => $trip && $trip->duration ? sprintf(__('%d days', 'yatra'), (int) $trip->duration) : '', | |
| 1002 | - 'trip_difficulty' => $trip ? ($trip->difficulty_name ?? '') : '', | |
| 1003 | - 'trip_highlights' => $trip ? ($trip->highlights ?? $trip->trip_highlights ?? '') : '', | |
| 1004 | - 'trip_includes' => $trip ? ($trip->includes ?? $trip->trip_includes ?? '') : '', | |
| 1005 | - 'trip_excludes' => $trip ? ($trip->excludes ?? $trip->trip_excludes ?? '') : '', | |
| 1006 | - 'departure_location' => $trip ? ($trip->departure_location ?? '') : '', | |
| 1007 | - 'destination' => $trip ? ($trip->destination ?? '') : '', | |
| 1008 | - 'travel_date' => $travelDate, | |
| 1009 | - 'return_date' => $returnDate, | |
| 1010 | - 'currency_symbol' => $currencySymbol, | |
| 1011 | - 'total_amount' => number_format((float) ($booking['total_amount'] ?? 0), 2), | |
| 1012 | - 'amount_paid' => number_format((float) ($booking['amount_paid'] ?? 0), 2), | |
| 1013 | - 'amount_due' => number_format((float) ($booking['amount_due'] ?? 0), 2), | |
| 1014 | - 'traveler_count' => (int) ($booking['travelers_count'] ?? $booking['travelers'] ?? 1), | |
| 1015 | - ]; | |
| 896 | + // The builder accepts the booking array shape directly — just | |
| 897 | + // forward `id` as `booking_id` so the reference resolves the | |
| 898 | + // same as the legacy code, and let it normalise everything else. | |
| 899 | + $source = $booking + ['booking_id' => $bookingId]; | |
| 900 | + $pdfBinary = $builder->build($source); | |
| 1016 | 901 | |
| 1017 | - $pdfBinary = $pdfService->renderTemplateToPdfSafely('pdf/itinerary.php', $templateData, [ | |
| 1018 | - 'paper' => 'A4', | |
| 1019 | - 'orientation' => 'portrait', | |
| 1020 | - 'default_font' => 'DejaVu Sans', | |
| 1021 | - ]); | |
| 1022 | - | |
| 1023 | 902 | if ($isPreview) { |
| 1024 | 903 | return new WP_REST_Response([ |
| 1025 | 904 | 'success' => true, |
| 1026 | 905 | 'pdf_data' => base64_encode($pdfBinary), |
| @@ -1027,8 +906,8 @@ | ||
| 1027 | 906 | 'filename' => $filename, |
| 1028 | 907 | ]); |
| 1029 | 908 | } |
| 1030 | 909 | |
| 1031 | - $pdfService->outputPdfDownload($pdfBinary, $filename); | |
| 910 | + $builder->pdfService()->outputPdfDownload($pdfBinary, $filename); | |
| 1032 | 911 | exit; |
| 1033 | 912 | } |
| 1034 | 913 | } |