PluginProbe
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution / 2.5.0
Fluent Booking – The Ultimate Appointments Scheduling, Events Booking, Events Calendar Solution v2.5.0
2.5.0 2.4.0 2.3.0 2.2.5 2.2.0 2.1.2 2.1.1 trunk 1.10.0 1.10.01 1.10.02 1.5.0 1.5.01 1.5.02 1.5.1 1.5.10 1.5.20 1.5.21 1.5.22 1.5.23 1.5.24 1.5.25 1.6.0 1.7.0 1.7.1 All 34 releases
← All changes | app/Services/TimeSlotService.php +217 -131 1.5.02 → 2.5.0 View file →
@@ -4,9 +4,8 @@
4 4
5 5 use FluentBooking\App\Models\Booking;
6 6 use FluentBooking\App\Models\Calendar;
7 7 use FluentBooking\App\Models\CalendarSlot;
8 -use FluentBooking\App\Models\Availability;
9 8 use FluentBooking\Framework\Support\Arr;
10 9 use FluentBooking\Framework\Support\DateTime;
11 10
12 11 class TimeSlotService
@@ -16,14 +15,11 @@
16 15 protected $calendar;
17 16
18 17 protected $hostId = null;
19 18
20 - protected $groupedSlots = [];
21 -
22 19 public function __construct(Calendar $calendar, CalendarSlot $calendarSlot)
23 20 {
24 21 $this->hostId = null;
25 - $this->groupedSlots = [];
26 22 $this->calendar = $calendar;
27 23 $this->calendarSlot = $calendarSlot;
28 24 }
29 25
@@ -37,24 +33,22 @@
37 33 $ranges = $this->getCurrentDateRange($fromDate, $toDate);
38 34
39 35 $bookedSlots = $this->getBookedSlots([$fromDate, $toDate], 'UTC', $isDoingBooking);
40 36
41 - $ranges = $this->maybeBookingFrequencyLimitRanges($ranges, $bookedSlots);
42 - $ranges = $this->maybeBookingDurationLimitRanges($ranges, $bookedSlots, $duration);
37 + $ranges = $this->maybeBookingFrequencyLimitRanges($ranges);
38 + $ranges = $this->maybeBookingDurationLimitRanges($ranges, $duration);
43 39
44 40 $cutOutTime = DateTimeHelper::getTimestamp() + $this->calendarSlot->getCutoutSeconds();
45 41
46 - $maxBookingTime = strtotime($this->calendarSlot->getMaxBookableDateTime($fromDate, $timeZone, 'Y-m-d H:i:s'));
42 + $maxBookingTime = $this->getMaxBookingTimestamp($fromDate, $toDate, $timeZone);
47 43
48 44 $timezoneInfo = $this->getTimezoneInfo();
49 45
50 46 $rangedSlots = $this->getRangedValidSlots($ranges, $duration, $bookedSlots, $cutOutTime, $maxBookingTime, $timezoneInfo);
51 47
52 - if (!$this->groupedSlots) {
53 - return $rangedSlots;
54 - }
48 + $rangedSlots = $this->maybeBookingPerDayLimitSlots($rangedSlots, $bookedSlots, $duration);
55 49
56 - return $this->adjustGroupedSlots($ranges, $rangedSlots, $cutOutTime, $maxBookingTime, $timezoneInfo);
50 + return $rangedSlots;
57 51 }
58 52
59 53 protected function getRangedValidSlots($ranges, $duration, $bookedSlots, $cutOutTime, $maxBookingTime, $timezoneInfo, $rangedSlots = [], $hostId = null)
60 54 {
@@ -84,10 +78,8 @@
84 78 if (!$availableSlots) {
85 79 continue;
86 80 }
87 81
88 - $currentBookedSlots = $bookedSlots[$date] ?? [];
89 -
90 82 $isToday = $date === $todayDate;
91 83
92 84 $isLastDay = $date === $lastDate;
93 85
@@ -92,8 +84,10 @@
92 84 $isLastDay = $date === $lastDate;
93 85
94 86 $validSlots = [];
95 87
88 + $validDstDateSlots = [];
89 +
96 90 foreach ($availableSlots as $start) {
97 91 $end = gmdate('H:i', strtotime($start) + $period); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
98 92 $endDate = $start < $end ? $date : gmdate('Y-m-d', strtotime($date) + 86400); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
99 93
@@ -103,20 +97,28 @@
103 97 ];
104 98
105 99 $slot = $this->maybeDayLightSavingSlot($slot, $dstTime, $scheduleTimezone);
106 100
101 + $slotDate = gmdate('Y-m-d', strtotime($slot['start'])); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
102 +
103 + $currentBookedSlots = $bookedSlots[$slotDate] ?? [];
104 +
107 105 if ($isToday && strtotime($slot['start']) < $cutOutTime) {
108 106 continue;
109 107 }
110 108
111 109 if ($isLastDay && strtotime($slot['end']) > $maxBookingTime) {
112 - continue;
110 + break;
113 111 }
114 112
115 113 $isSlotAvailable = $this->isSlotAvailable($slot, $currentBookedSlots, $bufferTime, $hostId);
116 114
117 115 if ($isSlotAvailable) {
118 - $validSlots[] = $slot;
116 + if ($slotDate != $date) {
117 + $validDstDateSlots[] = $slot;
118 + } else {
119 + $validSlots[] = $slot;
120 + }
119 121 }
120 122 }
121 123
122 124 if ($validSlots) {
@@ -122,8 +124,14 @@
122 124 if ($validSlots) {
123 125 $currentSlots = $rangedSlots[$date] ?? [];
124 126 $rangedSlots[$date] = $this->mergeAndSortSlots($currentSlots, $validSlots);
125 127 }
128 +
129 + if ($validDstDateSlots) {
130 + $slotDate = gmdate('Y-m-d', strtotime($validDstDateSlots[0]['start'])); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
131 + $currentSlots = $rangedSlots[$slotDate] ?? [];
132 + $rangedSlots[$slotDate] = $this->mergeAndSortSlots($currentSlots, $validDstDateSlots);
133 + }
126 134 }
127 135
128 136 return $rangedSlots;
129 137 }
@@ -161,52 +169,8 @@
161 169
162 170 return true;
163 171 }
164 172
165 - protected function adjustGroupedSlots($ranges, $rangedSlots, $cutOutTime, $maxBookingTime, $timezoneInfo)
166 - {
167 - list($scheduleTimezone, $dstTime) = $timezoneInfo;
168 -
169 - $todayDate = gmdate('Y-m-d'); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
170 -
171 - $lastDate = end($ranges);
172 -
173 - $addedDates = [];
174 -
175 - foreach ($this->groupedSlots as $slot) {
176 - $date = gmdate('Y-m-d', strtotime($slot['start']));
177 - if ($todayDate == $date && strtotime($slot['start']) < $cutOutTime) {
178 - continue;
179 - }
180 -
181 - if ($lastDate == $date && strtotime($slot['end']) > $maxBookingTime) {
182 - continue;
183 - }
184 -
185 - if (!isset($rangedSlots[$date])) {
186 - $rangedSlots[$date] = [];
187 - $rangedSlots[$date][] = $slot;
188 - continue;
189 - }
190 -
191 - $addedDates[$date] = $date;
192 -
193 - $rangedSlots[$date][] = $slot;
194 - }
195 -
196 - foreach ($addedDates as $date) {
197 - $dateSlots = $rangedSlots[$date];
198 -
199 - usort($dateSlots, function ($a, $b) {
200 - return strtotime($a['start']) - strtotime($b['start']);
201 - });
202 -
203 - $rangedSlots[$date] = $dateSlots;
204 - }
205 -
206 - return $rangedSlots;
207 - }
208 -
209 173 public function isSpotAvailable($fromTime, $toTime, $duration = null, $hostId = null)
210 174 {
211 175 $this->hostId = $hostId;
212 176
@@ -212,36 +176,54 @@
212 176
213 177 $fromTimeStamp = strtotime($fromTime);
214 178 $toTimeStamp = strtotime($toTime);
215 179
180 + $duration = $this->calendarSlot->getDuration($duration);
181 +
182 + list($scheduleTimezone, $dstTime) = $this->getTimezoneInfo();
183 +
184 + $toEndTime = $this->maybeDayLightSavingTime($toTime, $dstTime, $scheduleTimezone);
185 +
186 + // Start from the requested time, not the DST-shifted one: the shift can cross into the next UTC day and drop that day's bookings.
216 187 $fromTime = gmdate('Y-m-d 00:00:00', $fromTimeStamp); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
217 - $toTime = gmdate('Y-m-d 23:59:59', $toTimeStamp); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
188 + $toTime = gmdate('Y-m-d 23:59:59', strtotime($toEndTime)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
218 189
219 - $duration = $this->calendarSlot->getDuration($duration);
220 -
221 190 $slots = $this->getDates($fromTime, $toTime, $duration, true);
222 191
223 192 $fromDate = gmdate('Y-m-d', $fromTimeStamp); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
224 193 $toDate = gmdate('Y-m-d', $toTimeStamp); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
225 -
194 +
226 195 $availableSlots = $slots[$fromDate] ?? [];
227 -
196 +
228 197 if ($fromDate != $toDate) {
229 198 $availableSlots = array_merge($availableSlots, $slots[$toDate] ?? []);
230 199 }
231 200
201 + return $this->isSlotExists($availableSlots, $fromTimeStamp, $toTimeStamp);
202 + }
203 +
204 + protected function isSlotExists($availableSlots, $fromTimeStamp, $toTimeStamp)
205 + {
206 + // Slot generation only checks notice on today and the horizon on the last day.
207 + if ($fromTimeStamp < strtotime($this->calendarSlot->getMinBookableDateTime()) ||
208 + $toTimeStamp > $this->getMaxBookingTimestamp(null, gmdate('Y-m-d H:i:s', $toTimeStamp), 'UTC') // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
209 + ) {
210 + return false;
211 + }
212 +
232 213 $left = 0;
233 214 $right = count($availableSlots) - 1;
234 215
235 216 while ($left <= $right) {
236 217 $mid = $left + (($right - $left) >> 1);
218 + $slot = $availableSlots[$mid];
237 219
238 - $midStartTime = strtotime($availableSlots[$mid]['start']);
239 - $midEndTime = strtotime($availableSlots[$mid]['end']);
220 + $slotStartTime = strtotime($slot['start']);
221 + $slotEndTime = strtotime($slot['end']);
240 222
241 - if ($fromTimeStamp == $midStartTime && $toTimeStamp == $midEndTime) {
242 - return true;
243 - } elseif ($fromTimeStamp > $midStartTime) {
223 + if ($fromTimeStamp == $slotStartTime && $toTimeStamp == $slotEndTime) {
224 + return $slot;
225 + } elseif ($fromTimeStamp > $slotStartTime) {
244 226 $left = $mid + 1;
245 227 } else {
246 228 $right = $mid - 1;
247 229 }
@@ -260,9 +242,9 @@
260 242 $endDate = gmdate('Y-m-t 23:59:59', strtotime($startDate)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
261 243 }
262 244
263 245 $currentDate = strtotime($startDate);
264 - $endDate = strtotime($endDate);
246 + $endDate = strtotime($endDate) + 1; // add 1s in case end is 23:59:59
265 247 $oneDay = 24 * 60 * 60;
266 248
267 249 $dateArray = [];
268 250
@@ -294,13 +276,20 @@
294 276
295 277 $hostIds = $this->calendarSlot->getHostIds($this->hostId);
296 278 $status = ['pending', 'reserved', 'approved', 'scheduled', 'completed'];
297 279
280 + // Single indexed start_time range: widen the lower bound by max booking duration to catch overlaps.
281 + $maxDurationMinutes = (int) apply_filters('fluent_booking/max_booking_duration_minutes', DAY_IN_SECONDS / MINUTE_IN_SECONDS, $this->calendarSlot);
282 +
283 + $rangeLowerBound = gmdate('Y-m-d H:i:s', strtotime($dateRange[0]) - $maxDurationMinutes * MINUTE_IN_SECONDS); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
284 +
298 285 $bookings = Booking::with(['calendar_event'])
299 286 ->whereHas('hosts', function ($query) use ($hostIds) {
300 287 $query->whereIn('user_id', $hostIds);
301 288 })
302 - ->whereBetween('start_time', $dateRange)
289 + ->where('start_time', '>=', $rangeLowerBound)
290 + ->where('start_time', '<=', $dateRange[1])
291 + ->where('end_time', '>=', $dateRange[0])
303 292 ->orderBy('start_time', 'ASC')
304 293 ->whereIn('status', $status)
305 294 ->get()
306 295 ->groupBy('group_id');
@@ -370,9 +359,8 @@
370 359
371 360 $eventIdAdded = false;
372 361 foreach ($rangedItems as $date => $slot) {
373 362 if ($isGroupBooking && $remaining && $this->calendarSlot->id == $booking->event_id) {
374 - $this->groupedSlots[] = $slot;
375 363 if ($eventIdAdded) {
376 364 $slot['event_id'] = null;
377 365 }
378 366 $eventIdAdded = true;
@@ -436,9 +424,9 @@
436 424 $start += $interval;
437 425 }
438 426
439 427 if ($slot['end'] == '24:00' && $start < $end) {
440 - $daySlots = $this->handleNextDaySlot($daySlots, $items, $end, $start, $interval, $period, $day, $days);
428 + $daySlots = $this->handleNextDaySlot($daySlots, $items, $start, $end, $interval, $period, $day, $days);
441 429 }
442 430 }
443 431 if ($daySlots) {
444 432 $formattedSlots[$day] = $daySlots;
@@ -462,22 +450,22 @@
462 450 }
463 451
464 452 protected function handleNextDaySlot($daySlots, &$items, $start, $end, $interval, $period, $day, $days)
465 453 {
466 - $nextDayIndex = array_search($day, $days) + 1;
454 + $nextDayIndex = (array_search($day, $days) + 1) % count($items);
467 455
468 456 if (isset($days[$nextDayIndex])) {
469 457 $nextDay = $items[$days[$nextDayIndex]];
470 458
471 459 if ($nextDay && $nextDay[0]['start'] == '00:00') {
472 - $nextDayEnd = strtotime($nextDay[0]['end']);
460 + $nextDayEndTime = strtotime($nextDay[0]['end']) - strtotime($nextDay[0]['start']);
473 461 $reserveTime = $end - $start;
474 462
475 - while ($period - $reserveTime <= $nextDayEnd) {
463 + while ($period - $reserveTime <= $nextDayEndTime) {
476 464 $startTime = gmdate('H:i', $start); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
477 465 $nextDayStart = gmdate('H:i', $interval - $reserveTime); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
478 466 $daySlots[] = $startTime;
479 -
467 +
480 468 if ($nextDayStart < $startTime) {
481 469 $items[$days[$nextDayIndex]][0]['start'] = $nextDayStart;
482 470 break;
483 471 }
@@ -547,12 +535,12 @@
547 535 if (isset($overrideSlots[$nextDayIndex])) {
548 536 $nextDay = $overrideSlots[$nextDayIndex];
549 537
550 538 if ($nextDay && $nextDay[0]['start'] == '00:00') {
551 - $nextDayEnd = strtotime($nextDay[0]['end']);
539 + $nextDayEndTime = strtotime($nextDay[0]['end']) - strtotime($nextDay[0]['start']);
552 540 $reserveTime = $end - $start;
553 541
554 - while ($period - $reserveTime <= $nextDayEnd) {
542 + while ($period - $reserveTime <= $nextDayEndTime) {
555 543 $startTime = gmdate('H:i', $start); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
556 544 $nextDayStart = gmdate('H:i', $interval - $reserveTime); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
557 545 $formattedSlots[] = $startTime;
558 546
@@ -590,20 +578,20 @@
590 578 {
591 579 $this->hostId = $hostId;
592 580
593 581 $event = $this->calendarSlot;
594 - $calendar = $this->calendar;
595 582 $duration = $event->getDuration($duration);
596 583
597 - $startDate = $this->adjustStartDate($startDate, $timeZone);
584 + $adjustedDate = $this->adjustStartDate($startDate, $timeZone);
598 585
599 586 $isDisplaySpots = $event->is_display_spots;
600 587 $isMultiGuest = $event->isMultiGuestEvent();
601 - $endDate = $event->getMaxBookableDateTime($startDate, $timeZone);
588 + $isMultiBooking = $event->isAdditionalGuestEnabled();
589 + $endDate = $event->getMaxBookableDateTime($adjustedDate, $timeZone);
602 590 $startDate = $event->getMinBookableDateTime($startDate, $timeZone);
603 591
604 592 $maxBooking = false;
605 - if ($isDisplaySpots && $isMultiGuest) {
593 + if ($isMultiGuest && ($isDisplaySpots || $isMultiBooking)) {
606 594 $maxBooking = $event->getMaxBookingPerSlot();
607 595 }
608 596
609 597 if (strtotime($startDate) > strtotime($endDate)) {
@@ -670,9 +658,9 @@
670 658 }
671 659 }
672 660
673 661 $convertedSpots = array_map(function ($spots) {
674 - return array_values($spots);
662 + return array_values(ksort($spots) ? $spots : $spots);
675 663 }, $convertedSpots);
676 664
677 665 return $convertedSpots;
678 666 }
@@ -730,9 +718,9 @@
730 718
731 719 return $rangeArray;
732 720 }
733 721
734 - private function maybeBookingFrequencyLimitRanges($ranges, $bookedSlots)
722 + private function maybeBookingFrequencyLimitRanges($ranges)
735 723 {
736 724 if (!$ranges) {
737 725 return $ranges;
738 726 }
@@ -787,39 +775,12 @@
787 775 }
788 776 }
789 777 }
790 778 }
791 -
792 - // Per Day Booking Frequency Limit Hanlder
793 - if (!empty($keyedFrequenceyLimits['per_day'])) {
794 - $perDayLimit = $keyedFrequenceyLimits['per_day'];
795 - foreach ($ranges as $rangeIndex => $rangeDate) {
796 - if (!isset($bookedSlots[$rangeDate])) {
797 - continue;
798 - }
799 -
800 - $dayBooked = array_filter($bookedSlots[$rangeDate], function ($slot) {
801 - return Arr::get($slot, 'event_id') == $this->calendarSlot->id;
802 - });
803 -
804 - if (!$dayBooked) {
805 - continue;
806 - }
807 -
808 - if (count($dayBooked) >= $perDayLimit) {
809 - unset($ranges[$rangeIndex]);
810 - }
811 -
812 - if (!$ranges) {
813 - return [];
814 - }
815 - }
816 - }
817 -
818 779 return $ranges;
819 780 }
820 781
821 - private function maybeBookingDurationLimitRanges($ranges, $bookedSlots, $duration)
782 + private function maybeBookingDurationLimitRanges($ranges, $duration)
822 783 {
823 784 if (!$ranges) {
824 785 return $ranges;
825 786 }
@@ -866,35 +827,153 @@
866 827 }
867 828 }
868 829 }
869 830 }
831 + return $ranges;
832 + }
870 833
871 - // Per Day Booking Frequency Limit Hanlder
872 - if (!empty($keyedLimits['per_day'])) {
873 - $perDayLimit = $keyedLimits['per_day'];
874 - foreach ($ranges as $rangeIndex => $rangeDate) {
875 - if (!isset($bookedSlots[$rangeDate])) {
876 - continue;
834 + protected function maybeBookingPerDayLimitSlots($rangesSlots, $bookedSlots, $duration)
835 + {
836 + $isDurationEnabled = !!Arr::get($this->calendarSlot->settings, 'booking_duration.enabled');
837 +
838 + $isFrequencyEnabled = !!Arr::get($this->calendarSlot->settings, 'booking_frequency.enabled');
839 +
840 + if (!$isDurationEnabled && !$isFrequencyEnabled) {
841 + return $rangesSlots;
842 + }
843 +
844 + $hostTimeZone = $this->calendarSlot->getScheduleTimezone($this->hostId);
845 +
846 + $convertedRangesSlots = $this->convertSlotsByTimezone($rangesSlots, 'UTC', $hostTimeZone);
847 +
848 + $convertedBookedSlots = $this->convertSlotsByTimezone($bookedSlots, 'UTC', $hostTimeZone);
849 +
850 + $convertedRangesSlots = $this->maybeBookingDurationDayLimit($convertedRangesSlots, $convertedBookedSlots, $duration, $isDurationEnabled);
851 +
852 + $convertedRangesSlots = $this->maybeBookingFrequencyDayLimit($convertedRangesSlots, $convertedBookedSlots, $isFrequencyEnabled);
853 +
854 + $rangesSlots = $this->convertSlotsByTimezone($convertedRangesSlots, $hostTimeZone, 'UTC');
855 +
856 + return $rangesSlots;
857 + }
858 +
859 + protected function maybeBookingDurationDayLimit($rangesSlots, $bookedSlots, $duration, $isEnabled)
860 + {
861 + if (!$isEnabled) {
862 + return $rangesSlots;
863 + }
864 +
865 + $limits = Arr::get($this->calendarSlot->settings, 'booking_duration.limits', []);
866 +
867 + $perDayLimit = null;
868 + foreach ($limits as $limit) {
869 + if (Arr::get($limit, 'unit') == 'per_day' && Arr::get($limit, 'value')) {
870 + $perDayLimit = (int)Arr::get($limit, 'value');
871 + break;
872 + }
873 + }
874 +
875 + if (!$perDayLimit) {
876 + return $rangesSlots;
877 + }
878 +
879 + $isMultiSlot = $this->calendarSlot->isMultiGuestEvent();
880 +
881 + foreach ($rangesSlots as $rangeDate => &$slots) {
882 + if (!isset($bookedSlots[$rangeDate])) {
883 + continue;
884 + }
885 +
886 + $dayDuration = array_reduce($bookedSlots[$rangeDate], function ($carry, $slot) {
887 + if (Arr::get($slot, 'event_id') == $this->calendarSlot->id) {
888 + $carry += (int)((strtotime($slot['end']) - strtotime($slot['start'])) / 60);
877 889 }
890 + return $carry;
891 + }, 0);
878 892
879 - $dayDurarion = array_reduce($bookedSlots[$rangeDate], function ($carry, $slot) {
880 - if (Arr::get($slot, 'event_id') == $this->calendarSlot->id) {
881 - $carry += (int)((strtotime($slot['end']) - strtotime($slot['start'])) / 60);
882 - }
883 - return $carry;
884 - }, 0);
893 + if ($dayDuration + $duration > $perDayLimit) {
894 + if ($isMultiSlot) {
895 + $slots = array_values(array_filter($slots, function ($slot) {
896 + return !empty(Arr::get($slot, 'remaining'));
897 + }));
898 + }
899 + if (!$isMultiSlot || !count($slots)) {
900 + unset($rangesSlots[$rangeDate]);
901 + }
902 + }
903 + }
885 904
886 - if (!$dayDurarion) {
887 - continue;
905 + return $rangesSlots;
906 + }
907 +
908 + protected function maybeBookingFrequencyDayLimit($rangesSlots, $bookedSlots, $isEnabled)
909 + {
910 + if (!$isEnabled) {
911 + return $rangesSlots;
912 + }
913 +
914 + $limits = Arr::get($this->calendarSlot->settings, 'booking_frequency.limits', []);
915 +
916 + $perDayLimit = null;
917 + foreach ($limits as $limit) {
918 + if (Arr::get($limit, 'unit') == 'per_day' && Arr::get($limit, 'value')) {
919 + $perDayLimit = (int)Arr::get($limit, 'value');
920 + break;
921 + }
922 + }
923 +
924 + if (!$perDayLimit) {
925 + return $rangesSlots;
926 + }
927 +
928 + $isMultiSlot = $this->calendarSlot->isMultiGuestEvent();
929 +
930 + foreach ($rangesSlots as $rangeDate => &$slots) {
931 + if (!isset($bookedSlots[$rangeDate])) {
932 + continue;
933 + }
934 +
935 + $dayBooked = array_filter($bookedSlots[$rangeDate], function ($slot) {
936 + return Arr::get($slot, 'event_id') == $this->calendarSlot->id;
937 + });
938 +
939 + if (count($dayBooked) >= $perDayLimit) {
940 + if ($isMultiSlot) {
941 + $slots = array_values(array_filter($slots, function ($slot) {
942 + return !empty(Arr::get($slot, 'remaining'));
943 + }));
888 944 }
945 + if (!$isMultiSlot || !count($slots)) {
946 + unset($rangesSlots[$rangeDate]);
947 + }
948 + }
949 + }
889 950
890 - if ($dayDurarion + $duration > $perDayLimit) {
891 - unset($ranges[$rangeIndex]);
892 - }
951 + return $rangesSlots;
952 + }
953 +
954 + protected function convertSlotsByTimezone($slots, $fromTimeZone, $toTimeZone)
955 + {
956 + if ($fromTimeZone == $toTimeZone) {
957 + return $slots;
958 + }
959 +
960 + $convertedSlots = [];
961 +
962 + foreach ($slots as $spots) {
963 + foreach ($spots as $spot) {
964 + $spot['start'] = DateTimeHelper::convertToTimeZone($spot['start'], $fromTimeZone, $toTimeZone);
965 + $spot['end'] = DateTimeHelper::convertToTimeZone($spot['end'], $fromTimeZone, $toTimeZone);
966 +
967 + $spotDate = gmdate('Y-m-d', strtotime($spot['start']));
968 +
969 + $convertedSlots[$spotDate] = $convertedSlots[$spotDate] ?? [];
970 +
971 + $convertedSlots[$spotDate][] = $spot;
893 972 }
894 973 }
895 974
896 - return $ranges;
975 + return $convertedSlots;
897 976 }
898 977
899 978 public function getFilledWeeks($from, $to, $weekStart = '')
900 979 {
@@ -957,8 +1036,15 @@
957 1036 ->where('event_id', $this->calendarSlot->id)
958 1037 ->whereBetween('start_time', [$start, $end])
959 1038 ->whereIn('status', ['scheduled', 'completed'])
960 1039 ->sum('slot_minutes');
1040 + }
1041 +
1042 + protected function getMaxBookingTimestamp($fromDate, $toDate, $timeZone)
1043 + {
1044 + $maxBookingTime = $this->calendarSlot->getMaxBookableDateTime($toDate, $timeZone, 'Y-m-d H:i:s');
1045 +
1046 + return strtotime($maxBookingTime);
961 1047 }
962 1048
963 1049 protected function getTimezoneInfo($hostId = null)
964 1050 {