| @@ -75,15 +75,32 @@ | ||
| 75 | 75 | $bookingDate = $createdAt !== '' ? date_i18n(get_option('date_format'), strtotime($createdAt)) : ''; |
| 76 | 76 | $travelRaw = (string) ($source['travel_date'] ?? ''); |
| 77 | 77 | $travelDate = $travelRaw !== '' ? date_i18n(get_option('date_format'), strtotime($travelRaw)) : ''; |
| 78 | 78 | |
| 79 | + // Departure / return date. The booking already stores the real | |
| 80 | + // trip end (`end_date`, written at booking time from the selected | |
| 81 | + // departure or start + duration), so prefer that authoritative | |
| 82 | + // value rather than re-deriving one — this is what keeps the | |
| 83 | + // travel timeline showing the ACTUAL booked end date instead of | |
| 84 | + // repeating the arrival date. Only when no stored end_date exists | |
| 85 | + // do we derive it, and then the SAME way | |
| 86 | + // BookingRepository::calculateEndDate does: start + (duration_days | |
| 87 | + // - 1). A 5-day trip therefore ends on day 5, not day 6 — the old | |
| 88 | + // "+ duration_days" was off by one and implied an extra night. | |
| 79 | 89 | $returnDate = ''; |
| 80 | - if ($travelRaw !== '' && $trip && !empty($trip->duration_days)) { | |
| 81 | - $returnDate = date_i18n( | |
| 82 | - get_option('date_format'), | |
| 83 | - strtotime($travelRaw . ' +' . (int) $trip->duration_days . ' days') | |
| 90 | + $endRaw = (string) ($source['end_date'] ?? ''); | |
| 91 | + if ($endRaw === '' && $travelRaw !== '' && $trip && (int) ($trip->duration_days ?? 0) > 1) { | |
| 92 | + $endRaw = date( | |
| 93 | + 'Y-m-d', | |
| 94 | + strtotime($travelRaw . ' +' . ((int) $trip->duration_days - 1) . ' days') | |
| 84 | 95 | ); |
| 85 | 96 | } |
| 97 | + // Expose a return date only when it genuinely differs from the | |
| 98 | + // arrival date. Single-day bookings store end_date == start_date, | |
| 99 | + // so the timeline renders one item instead of two identical dates. | |
| 100 | + if ($endRaw !== '' && $endRaw !== $travelRaw) { | |
| 101 | + $returnDate = date_i18n(get_option('date_format'), strtotime($endRaw)); | |
| 102 | + } | |
| 86 | 103 | |
| 87 | 104 | $statusRaw = strtolower((string) ($source['booking_status'] ?? $source['status'] ?? '')); |
| 88 | 105 | $statusClass = in_array($statusRaw, ['confirmed', 'completed', 'success'], true) |
| 89 | 106 | ? 'confirmed' |
| @@ -104,8 +121,9 @@ | ||
| 104 | 121 | |
| 105 | 122 | $templateData = [ |
| 106 | 123 | 'company_name' => SettingsService::get('company_name', get_bloginfo('name')), |
| 107 | 124 | 'company_address' => SettingsService::get('company_address', ''), |
| 125 | + 'company_address_lines' => \Yatra\Helpers\FormatHelper::companyAddressLines(), | |
| 108 | 126 | 'company_email' => SettingsService::get('company_email', get_option('admin_email')), |
| 109 | 127 | 'company_phone' => SettingsService::get('company_phone', ''), |
| 110 | 128 | 'customer_name' => $customerName, |
| 111 | 129 | 'customer_email' => (string) ($source['contact_email'] ?? $source['customer_email'] ?? ''), |
| @@ -116,9 +134,15 @@ | ||
| 116 | 134 | 'trip_title' => $trip ? ((string) ($trip->title ?? $tripFallbackTitle)) : $tripFallbackTitle, |
| 117 | 135 | 'trip_description'=> $trip ? (string) ($trip->description ?? $trip->content ?? '') : '', |
| 118 | 136 | // Duration is duration_days/duration_nights (no `duration` column). |
| 119 | 137 | 'trip_duration' => $trip |
| 120 | - ? yatra_format_duration((int) ($trip->duration_days ?? 0), isset($trip->duration_nights) ? (int) $trip->duration_nights : null) | |
| 138 | + ? yatra_format_duration( | |
| 139 | + (int) ($trip->duration_days ?? 0), | |
| 140 | + isset($trip->duration_nights) ? (int) $trip->duration_nights : null, | |
| 141 | + // Hour-based day tours: "8 hours" instead of "1 day". Absent | |
| 142 | + // or NULL on every day-based trip, which keeps its wording. | |
| 143 | + (int) ($trip->duration_hours ?? 0) | |
| 144 | + ) | |
| 121 | 145 | : '', |
| 122 | 146 | 'trip_difficulty' => $trip ? (string) ($trip->difficulty_name ?? '') : '', |
| 123 | 147 | 'trip_highlights' => $trip ? ($trip->highlights ?? $trip->trip_highlights ?? '') : '', |
| 124 | 148 | 'trip_includes' => $trip ? ($trip->includes ?? $trip->trip_includes ?? '') : '', |
| @@ -127,11 +151,11 @@ | ||
| 127 | 151 | 'destination' => $trip ? (string) ($trip->destination ?? '') : (string) ($source['destination'] ?? ''), |
| 128 | 152 | 'travel_date' => $travelDate, |
| 129 | 153 | 'return_date' => $returnDate, |
| 130 | 154 | 'currency_symbol' => FormatHelper::getCurrencySymbol($currency), |
| 131 | - 'total_amount' => number_format((float) ($source['total_amount'] ?? 0), 2), | |
| 132 | - 'amount_paid' => number_format((float) ($source['amount_paid'] ?? 0), 2), | |
| 133 | - 'amount_due' => number_format((float) ($source['amount_due'] ?? 0), 2), | |
| 155 | + 'total_amount' => yatra_format_price((float) ($source['total_amount'] ?? 0), $currency, false), | |
| 156 | + 'amount_paid' => yatra_format_price((float) ($source['amount_paid'] ?? 0), $currency, false), | |
| 157 | + 'amount_due' => yatra_format_price((float) ($source['amount_due'] ?? 0), $currency, false), | |
| 134 | 158 | 'traveler_count' => (int) ($source['travelers_count'] ?? $source['traveler_count'] ?? $source['travelers'] ?? 1), |
| 135 | 159 | 'itinerary_days' => $itineraryDays, |
| 136 | 160 | // Real trip-specific Important Information fields, same |
| 137 | 161 | // ones the single-trip page surfaces in its "Important |
| @@ -177,8 +201,13 @@ | ||
| 177 | 201 | 'trip_id' => $payment->trip_id ?? 0, |
| 178 | 202 | 'booking_id' => $payment->booking_id ?? 0, |
| 179 | 203 | 'created_at' => $payment->created_at ?? null, |
| 180 | 204 | 'travel_date' => $payment->travel_date ?? null, |
| 205 | + // Actual booked trip range — used for the arrival/departure | |
| 206 | + // timeline. Null-safe: build() falls back to a correct | |
| 207 | + // derivation when the joined record doesn't carry these. | |
| 208 | + 'start_date' => $payment->start_date ?? null, | |
| 209 | + 'end_date' => $payment->end_date ?? null, | |
| 181 | 210 | // Use the BOOKING status, not the payment status. Payment |
| 182 | 211 | // status can be "completed" while the booking itself is |
| 183 | 212 | // still "pending" admin confirmation — the itinerary |
| 184 | 213 | // header should reflect the booking, not the transaction. |