| @@ -57,8 +57,42 @@ | ||
| 57 | 57 | $availabilityId = isset($booking->availability_id) ? (int) $booking->availability_id : 0; |
| 58 | 58 | $travelDate = (string) ($booking->travel_date ?? ''); |
| 59 | 59 | |
| 60 | 60 | if ($tripId > 0 && $availabilityId <= 0 && $travelDate !== '') { |
| 61 | + // Try to infer availability_id from resolved snapshot stored in booking meta. | |
| 62 | + // This is more reliable when a trip has multiple departure times on the same date. | |
| 63 | + $metaRaw = $booking->meta ?? null; | |
| 64 | + $meta = []; | |
| 65 | + if (is_string($metaRaw) && $metaRaw !== '') { | |
| 66 | + $decoded = json_decode($metaRaw, true); | |
| 67 | + $meta = is_array($decoded) ? $decoded : []; | |
| 68 | + } elseif (is_array($metaRaw)) { | |
| 69 | + $meta = $metaRaw; | |
| 70 | + } | |
| 71 | + | |
| 72 | + if (!empty($meta) && isset($meta['resolved_availability']) && is_array($meta['resolved_availability'])) { | |
| 73 | + $ra = $meta['resolved_availability']; | |
| 74 | + | |
| 75 | + $metaAvailabilityId = $ra['availability_id'] ?? null; | |
| 76 | + if (is_numeric($metaAvailabilityId) && (int) $metaAvailabilityId > 0) { | |
| 77 | + $availabilityId = (int) $metaAvailabilityId; | |
| 78 | + } else { | |
| 79 | + $metaDepartureTime = isset($ra['departure_time']) ? (string) $ra['departure_time'] : ''; | |
| 80 | + $metaTravelDate = isset($ra['travel_date']) ? (string) $ra['travel_date'] : $travelDate; | |
| 81 | + if ($metaDepartureTime !== '') { | |
| 82 | + $row = (new AvailabilityRepository())->findByTripIdAndDateTime( | |
| 83 | + $tripId, | |
| 84 | + $metaTravelDate, | |
| 85 | + self::normalizeTimeForAvailabilityMatch($metaDepartureTime), | |
| 86 | + true | |
| 87 | + ); | |
| 88 | + if ($row && !empty($row->id)) { | |
| 89 | + $availabilityId = (int) $row->id; | |
| 90 | + } | |
| 91 | + } | |
| 92 | + } | |
| 93 | + } | |
| 94 | + | |
| 61 | 95 | $availabilityId = self::resolveAvailabilityIdForBooking($bookingId, $tripId, $travelDate); |
| 62 | 96 | if ($availabilityId > 0) { |
| 63 | 97 | (new BookingRepository())->update($bookingId, ['availability_id' => $availabilityId]); |
| 64 | 98 | } |
| @@ -155,11 +189,20 @@ | ||
| 155 | 189 | if (!$departure) { |
| 156 | 190 | return; |
| 157 | 191 | } |
| 158 | 192 | |
| 193 | + // booked_count is sourced from the actual bookings table — it | |
| 194 | + // is the truth. Reflect it to the departure row WITHOUT | |
| 195 | + // clamping to max_capacity. Clamping previously hid oversells | |
| 196 | + // from operators: if direct + OTA bookings legitimately | |
| 197 | + // exceeded capacity (always an OTA-side acceptance — Yatra's | |
| 198 | + // direct checkout enforces capacity), the departure showed | |
| 199 | + // "full" instead of "oversold by N", and operators had no way | |
| 200 | + // to spot the situation needing reconciliation. Allowing the | |
| 201 | + // overshow makes the problem visible so it can be addressed. | |
| 159 | 202 | $updated = [ |
| 160 | 203 | 'max_capacity' => $seatsTotal, |
| 161 | - 'booked_count' => min($seatsTotal, max(0, $bookedCount)), | |
| 204 | + 'booked_count' => max(0, $bookedCount), | |
| 162 | 205 | ]; |
| 163 | 206 | |
| 164 | 207 | $departureRepo->update((int) $departure->id, $updated); |
| 165 | 208 | } |
| @@ -196,9 +239,9 @@ | ||
| 196 | 239 | $date = $departure->start_date ?: $departure->date; |
| 197 | 240 | |
| 198 | 241 | // Get capacity from availability |
| 199 | 242 | $capacityService = new CapacityService(); |
| 200 | - $correctCapacity = $capacityService->getCapacityForDate($departure->trip_id, $date); | |
| 243 | + $correctCapacity = $capacityService->getCapacityForDate($departure->trip_id, $date, $departure->time ?? null); | |
| 201 | 244 | |
| 202 | 245 | if ($correctCapacity > 0 && $departure->max_capacity !== $correctCapacity) { |
| 203 | 246 | // Remove hook temporarily to prevent infinite loop |
| 204 | 247 | remove_action('yatra_departure_saved', [self::class, 'onDepartureSave'], 10); |
| @@ -215,9 +258,9 @@ | ||
| 215 | 258 | unset($processing[$departureId]); |
| 216 | 259 | } |
| 217 | 260 | |
| 218 | 261 | /** |
| 219 | - * Infer yatra_new_trip_availability_dates.id when the booking row missed availability_id | |
| 262 | + * Infer yatra_trip_availability_dates.id when the booking row missed availability_id | |
| 220 | 263 | * (common when checkout omits it). Uses booking→departure time to disambiguate multiple slots per day. |
| 221 | 264 | */ |
| 222 | 265 | private static function resolveAvailabilityIdForBooking(int $bookingId, int $tripId, string $travelDate): int |
| 223 | 266 | { |