| @@ -264,8 +264,14 @@ | ||
| 264 | 264 | t.duration_days, t.duration_nights, t.difficulty_level, |
| 265 | 265 | t.starting_location, t.ending_location" |
| 266 | 266 | ]; |
| 267 | 267 | |
| 268 | + // Hour-based day tours (3.0.14+ column) — guarded so an install whose | |
| 269 | + // upgrade ALTER has not run yet keeps rendering the confirmation page. | |
| 270 | + if ($tripRepository->hasTripColumn('duration_hours')) { | |
| 271 | + $selectParts[] = 't.duration_hours'; | |
| 272 | + } | |
| 273 | + | |
| 268 | 274 | $joins[] = "LEFT JOIN {$tripClassificationTable} tc ON tc.trip_id = t.id"; |
| 269 | 275 | $joins[] = "LEFT JOIN {$classificationTable} cls ON cls.id = tc.classification_id"; |
| 270 | 276 | $selectParts[] = "GROUP_CONCAT(DISTINCT cls.name ORDER BY tc.`sort_order` SEPARATOR ',') as trip_classifications"; |
| 271 | 277 | |
| @@ -747,13 +753,24 @@ | ||
| 747 | 753 | // Use TripRepository for trips table |
| 748 | 754 | $tripRepository = new \Yatra\Repositories\TripRepository(); |
| 749 | 755 | $tripsTable = $tripRepository->getTableName(); |
| 750 | 756 | |
| 757 | + // Confirmed bookings, plus PENDING bookings that have paid something | |
| 758 | + // (a deposit). Under the Auto-Confirm `online` mode a deposit booking | |
| 759 | + // legitimately stays pending until the balance is paid; those customers | |
| 760 | + // are real travellers and must still get the pre-trip reminder — which | |
| 761 | + // already carries the "outstanding balance, please pay before travel" | |
| 762 | + // block for exactly this case. Unpaid pending bookings stay excluded. | |
| 763 | + // | |
| 764 | + // No `t.currency`: the trips table has never had that column, so the | |
| 765 | + // previous SELECT threw "Unknown column" — get_results() then returned | |
| 766 | + // nothing and the reminder cron silently sent zero emails. The booking's | |
| 767 | + // own currency arrives via b.* (and is no longer clobbered by the join). | |
| 751 | 768 | return $this->wpdb->get_results($this->wpdb->prepare( |
| 752 | - "SELECT b.*, t.title as trip_title, t.currency | |
| 769 | + "SELECT b.*, t.title as trip_title | |
| 753 | 770 | FROM {$table} b |
| 754 | 771 | LEFT JOIN {$tripsTable} t ON b.trip_id = t.id |
| 755 | - WHERE b.status = 'confirmed' | |
| 772 | + WHERE (b.status = 'confirmed' OR (b.status = 'pending' AND b.amount_paid > 0)) | |
| 756 | 773 | AND b.travel_date = %s |
| 757 | 774 | AND b.reminder_sent = 0", |
| 758 | 775 | $travelDate |
| 759 | 776 | )); |
| @@ -832,20 +849,44 @@ | ||
| 832 | 849 | * |
| 833 | 850 | * @param string $expiryThreshold Datetime threshold |
| 834 | 851 | * @return array |
| 835 | 852 | */ |
| 836 | - public function getExpiredPendingBookings(string $expiryThreshold): array | |
| 853 | + /** | |
| 854 | + * Unpaid bookings past the expiry threshold. | |
| 855 | + * | |
| 856 | + * @param string $expiryThreshold Bookings created before this are expired. | |
| 857 | + * @param string $createdSince Activation floor: when set, bookings created | |
| 858 | + * before it are never expired. Keeps a site | |
| 859 | + * that switches the feature on from | |
| 860 | + * retroactively cancelling (and emailing about) | |
| 861 | + * its historical pending bookings. | |
| 862 | + * @param int $limit Batch size. The sweep emails each customer, | |
| 863 | + * so an unbounded run could try to send | |
| 864 | + * hundreds of emails in one cron request and | |
| 865 | + * time out half-way. It runs hourly, so a | |
| 866 | + * backlog simply drains over the next runs. | |
| 867 | + * @return array<int, object> | |
| 868 | + */ | |
| 869 | + public function getExpiredPendingBookings(string $expiryThreshold, string $createdSince = '', int $limit = 200): array | |
| 837 | 870 | { |
| 838 | 871 | $table = $this->getTableName(); |
| 839 | 872 | |
| 840 | - return $this->wpdb->get_results($this->wpdb->prepare( | |
| 841 | - "SELECT id, reference, contact_email, contact_first_name, contact_last_name, trip_id | |
| 842 | - FROM {$table} | |
| 843 | - WHERE status = 'pending' | |
| 844 | - AND payment_status = 'pending' | |
| 845 | - AND created_at < %s", | |
| 846 | - $expiryThreshold | |
| 847 | - )); | |
| 873 | + $sql = "SELECT id, reference, contact_email, contact_first_name, contact_last_name, trip_id | |
| 874 | + FROM {$table} | |
| 875 | + WHERE status = 'pending' | |
| 876 | + AND payment_status = 'pending' | |
| 877 | + AND created_at < %s"; | |
| 878 | + $params = [$expiryThreshold]; | |
| 879 | + | |
| 880 | + if ($createdSince !== '') { | |
| 881 | + $sql .= ' AND created_at >= %s'; | |
| 882 | + $params[] = $createdSince; | |
| 883 | + } | |
| 884 | + | |
| 885 | + $sql .= ' ORDER BY created_at ASC LIMIT %d'; | |
| 886 | + $params[] = max(1, $limit); | |
| 887 | + | |
| 888 | + return $this->wpdb->get_results($this->wpdb->prepare($sql, $params)); | |
| 848 | 889 | } |
| 849 | 890 | |
| 850 | 891 | /** |
| 851 | 892 | * Expire a booking |