PluginProbe
Yatra – Travel Booking & Tour Operator Software / 3.0.16
Yatra – Travel Booking & Tour Operator Software v3.0.16
3.0.16 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 All 84 releases
← All changes | app/Controllers/ReportsController.php +62 -9 3.0.10 → 3.0.16 View file →
@@ -78,15 +78,24 @@
78 78 ];
79 79
80 80 $bookingsList = $this->request('GET', '/yatra/v1/bookings', $params);
81 81 $paymentsList = $this->request('GET', '/yatra/v1/payments', $params);
82 - // The reporting window [$dateFrom, $dateTo] is historical (default: last
83 - // 30 days), matching the bookings/payments/revenue stats. Departures must
84 - // therefore INCLUDE past departures — otherwise "in the last 30 days" AND
85 - // "not past" is an empty set, and every occupancy figure renders as 0%.
82 + // Departures are deliberately NOT capped at $dateTo.
83 + //
84 + // The reporting window [$dateFrom, $dateTo] is historical (default: the
85 + // last 30 days) because bookings/payments/revenue are historical. Applying
86 + // that same upper bound to departures is wrong: an operator's seats live in
87 + // UPCOMING departures, so "ends today" excludes exactly the departures the
88 + // occupancy figures are about — a site selling future tours then reports 0%
89 + // occupancy while each departure page correctly shows e.g. 1/9 = 11.1%.
90 + //
91 + // So we take departures from the period start onwards, including past ones
92 + // (a past departure in the window still counts) and upcoming ones. Every
93 + // departure-derived figure — the Occupancy Rate card, per-trip occupancy,
94 + // the occupancy trend, seat utilisation and the departures table — reads
95 + // this one set, so they always agree with each other.
86 96 $departuresList = $this->request('GET', '/yatra/v1/departures', [
87 97 'date_from' => $dateFrom,
88 - 'date_to' => $dateTo,
89 98 'include_past' => 'true',
90 99 ]);
91 100
92 101 $bookings = isset($bookingsList['data']) && is_array($bookingsList['data'])
@@ -375,9 +384,9 @@
375 384 $tripTitle = $d['trip']['title'] ?? ($d['trip_title'] ?? '');
376 385 if ($tripTitle === '') {
377 386 continue;
378 387 }
379 - $cap = (int) ($d['max_capacity'] ?? $d['total_spots'] ?? 0);
388 + $cap = $this->departureAvailabilityCapacity($d);
380 389 $bkd = (int) ($d['booked_count'] ?? $d['travelers_count'] ?? 0);
381 390 if (!isset($occupancyByTripTitle[$tripTitle])) {
382 391 $occupancyByTripTitle[$tripTitle] = ['booked' => 0, 'capacity' => 0];
383 392 }
@@ -455,13 +464,13 @@
455 464 $upcomingTrips[] = [
456 465 'trip' => $d['trip']['title'] ?? ($d['trip_title'] ?? __('Unknown Trip', 'yatra')),
457 466 'date' => $dateStr,
458 467 'booked' => (int) ($d['booked_count'] ?? $d['travelers_count'] ?? 0),
459 - 'capacity' => (int) ($d['max_capacity'] ?? $d['total_spots'] ?? 0),
468 + 'capacity' => $this->departureAvailabilityCapacity($d),
460 469 ];
461 470 }
462 471
463 - $capacity = (int) ($d['max_capacity'] ?? $d['total_spots'] ?? 0);
472 + $capacity = $this->departureAvailabilityCapacity($d);
464 473 $booked = (int) ($d['booked_count'] ?? $d['travelers_count'] ?? 0);
465 474 $totalCapacity += $capacity;
466 475 $bookedCapacity += $booked;
467 476 if ($booked > 0) {
@@ -716,9 +725,9 @@
716 725
717 726 foreach ($departures as $d) {
718 727 $dateStr = $d['start_date'] ?? ($d['date'] ?? null);
719 728 $tripTitle = $d['trip']['title'] ?? ($d['trip_title'] ?? __('Unknown Trip', 'yatra'));
720 - $capacity = (int) ($d['max_capacity'] ?? $d['total_spots'] ?? 0);
729 + $capacity = $this->departureAvailabilityCapacity($d);
721 730 $booked = (int) ($d['booked_count'] ?? $d['travelers_count'] ?? 0);
722 731 $left = $capacity > 0 ? max(0, $capacity - $booked) : 0;
723 732 $status = strtolower((string) ($d['status'] ?? 'upcoming'));
724 733
@@ -956,8 +965,52 @@
956 965 'refunds' => $refundsSummary,
957 966 'top_destinations' => $topDestinations,
958 967 ],
959 968 ]);
969 + }
970 +
971 + /**
972 + * Resolve a departure's capacity from the Availability configuration — the
973 + * same authoritative source the Departures page and the /departures
974 + * endpoint use (Availability date > recurring rule > the trip's
975 + * max_travelers). The dashboard previously summed each departure's stored
976 + * `max_capacity`, which could be a stale trip-settings value or a legacy
977 + * "unlimited" default (e.g. 9999/11111), so its capacity and occupancy
978 + * disagreed with the Departures page. Reading the live availability figure
979 + * keeps them consistent.
980 + *
981 + * @param array<string,mixed> $d Departure row (from /departures)
982 + */
983 + private function departureAvailabilityCapacity(array $d): int
984 + {
985 + static $capacityService = null;
986 + static $memo = [];
987 + if ($capacityService === null && class_exists('\\Yatra\\Services\\CapacityService')) {
988 + $capacityService = new \Yatra\Services\CapacityService();
989 + }
990 +
991 + $tripId = (int) ($d['trip_id'] ?? ($d['trip']['id'] ?? 0));
992 + $date = (string) ($d['start_date'] ?? ($d['date'] ?? ''));
993 + $time = isset($d['time']) ? (string) $d['time'] : '';
994 + $stored = (int) ($d['max_capacity'] ?? $d['total_spots'] ?? 0);
995 +
996 + // The same departure is read across several reporting loops, so memoise
997 + // the resolution per (trip, date, time, stored) to avoid re-querying.
998 + $key = $tripId . '|' . $date . '|' . $time . '|' . $stored;
999 + if (isset($memo[$key])) {
1000 + return $memo[$key];
1001 + }
1002 +
1003 + if ($capacityService !== null && $tripId > 0 && $date !== '') {
1004 + $cap = $capacityService->getCapacityForDate($tripId, $date, $time !== '' ? $time : null);
1005 + if ($cap > 0) {
1006 + return $memo[$key] = $cap;
1007 + }
1008 + }
1009 +
1010 + // No availability/trip capacity resolved — fall back to the stored value,
1011 + // but drop the legacy "unlimited" sentinels that would inflate occupancy.
1012 + return $memo[$key] = ($stored >= 9999 ? 0 : $stored);
960 1013 }
961 1014
962 1015 /**
963 1016 * Helper to call an internal REST endpoint and return decoded data