| @@ -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 |
| @@ -34,19 +33,21 @@ | ||
| 34 | 33 | $ranges = $this->getCurrentDateRange($fromDate, $toDate); |
| 35 | 34 | |
| 36 | 35 | $bookedSlots = $this->getBookedSlots([$fromDate, $toDate], 'UTC', $isDoingBooking); |
| 37 | 36 | |
| 38 | - $ranges = $this->maybeBookingFrequencyLimitRanges($ranges, $bookedSlots); | |
| 39 | - $ranges = $this->maybeBookingDurationLimitRanges($ranges, $bookedSlots, $duration); | |
| 37 | + $ranges = $this->maybeBookingFrequencyLimitRanges($ranges); | |
| 38 | + $ranges = $this->maybeBookingDurationLimitRanges($ranges, $duration); | |
| 40 | 39 | |
| 41 | 40 | $cutOutTime = DateTimeHelper::getTimestamp() + $this->calendarSlot->getCutoutSeconds(); |
| 42 | 41 | |
| 43 | - $maxBookingTime = strtotime($this->calendarSlot->getMaxBookableDateTime($fromDate, $timeZone, 'Y-m-d H:i:s')); | |
| 42 | + $maxBookingTime = $this->getMaxBookingTimestamp($fromDate, $toDate, $timeZone); | |
| 44 | 43 | |
| 45 | 44 | $timezoneInfo = $this->getTimezoneInfo(); |
| 46 | 45 | |
| 47 | 46 | $rangedSlots = $this->getRangedValidSlots($ranges, $duration, $bookedSlots, $cutOutTime, $maxBookingTime, $timezoneInfo); |
| 48 | 47 | |
| 48 | + $rangedSlots = $this->maybeBookingPerDayLimitSlots($rangedSlots, $bookedSlots, $duration); | |
| 49 | + | |
| 49 | 50 | return $rangedSlots; |
| 50 | 51 | } |
| 51 | 52 | |
| 52 | 53 | protected function getRangedValidSlots($ranges, $duration, $bookedSlots, $cutOutTime, $maxBookingTime, $timezoneInfo, $rangedSlots = [], $hostId = null) |
| @@ -77,10 +78,8 @@ | ||
| 77 | 78 | if (!$availableSlots) { |
| 78 | 79 | continue; |
| 79 | 80 | } |
| 80 | 81 | |
| 81 | - $currentBookedSlots = $bookedSlots[$date] ?? []; | |
| 82 | - | |
| 83 | 82 | $isToday = $date === $todayDate; |
| 84 | 83 | |
| 85 | 84 | $isLastDay = $date === $lastDate; |
| 86 | 85 | |
| @@ -85,8 +84,10 @@ | ||
| 85 | 84 | $isLastDay = $date === $lastDate; |
| 86 | 85 | |
| 87 | 86 | $validSlots = []; |
| 88 | 87 | |
| 88 | + $validDstDateSlots = []; | |
| 89 | + | |
| 89 | 90 | foreach ($availableSlots as $start) { |
| 90 | 91 | $end = gmdate('H:i', strtotime($start) + $period); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date |
| 91 | 92 | $endDate = $start < $end ? $date : gmdate('Y-m-d', strtotime($date) + 86400); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date |
| 92 | 93 | |
| @@ -96,20 +97,28 @@ | ||
| 96 | 97 | ]; |
| 97 | 98 | |
| 98 | 99 | $slot = $this->maybeDayLightSavingSlot($slot, $dstTime, $scheduleTimezone); |
| 99 | 100 | |
| 101 | + $slotDate = gmdate('Y-m-d', strtotime($slot['start'])); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date | |
| 102 | + | |
| 103 | + $currentBookedSlots = $bookedSlots[$slotDate] ?? []; | |
| 104 | + | |
| 100 | 105 | if ($isToday && strtotime($slot['start']) < $cutOutTime) { |
| 101 | 106 | continue; |
| 102 | 107 | } |
| 103 | 108 | |
| 104 | 109 | if ($isLastDay && strtotime($slot['end']) > $maxBookingTime) { |
| 105 | - continue; | |
| 110 | + break; | |
| 106 | 111 | } |
| 107 | 112 | |
| 108 | 113 | $isSlotAvailable = $this->isSlotAvailable($slot, $currentBookedSlots, $bufferTime, $hostId); |
| 109 | 114 | |
| 110 | 115 | if ($isSlotAvailable) { |
| 111 | - $validSlots[] = $slot; | |
| 116 | + if ($slotDate != $date) { | |
| 117 | + $validDstDateSlots[] = $slot; | |
| 118 | + } else { | |
| 119 | + $validSlots[] = $slot; | |
| 120 | + } | |
| 112 | 121 | } |
| 113 | 122 | } |
| 114 | 123 | |
| 115 | 124 | if ($validSlots) { |
| @@ -115,8 +124,14 @@ | ||
| 115 | 124 | if ($validSlots) { |
| 116 | 125 | $currentSlots = $rangedSlots[$date] ?? []; |
| 117 | 126 | $rangedSlots[$date] = $this->mergeAndSortSlots($currentSlots, $validSlots); |
| 118 | 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 | + } | |
| 119 | 134 | } |
| 120 | 135 | |
| 121 | 136 | return $rangedSlots; |
| 122 | 137 | } |
| @@ -161,36 +176,54 @@ | ||
| 161 | 176 | |
| 162 | 177 | $fromTimeStamp = strtotime($fromTime); |
| 163 | 178 | $toTimeStamp = strtotime($toTime); |
| 164 | 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. | |
| 165 | 187 | $fromTime = gmdate('Y-m-d 00:00:00', $fromTimeStamp); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date |
| 166 | - $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 | |
| 167 | 189 | |
| 168 | - $duration = $this->calendarSlot->getDuration($duration); | |
| 169 | - | |
| 170 | 190 | $slots = $this->getDates($fromTime, $toTime, $duration, true); |
| 171 | 191 | |
| 172 | 192 | $fromDate = gmdate('Y-m-d', $fromTimeStamp); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date |
| 173 | 193 | $toDate = gmdate('Y-m-d', $toTimeStamp); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date |
| 174 | - | |
| 194 | + | |
| 175 | 195 | $availableSlots = $slots[$fromDate] ?? []; |
| 176 | - | |
| 196 | + | |
| 177 | 197 | if ($fromDate != $toDate) { |
| 178 | 198 | $availableSlots = array_merge($availableSlots, $slots[$toDate] ?? []); |
| 179 | 199 | } |
| 180 | 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 | + | |
| 181 | 213 | $left = 0; |
| 182 | 214 | $right = count($availableSlots) - 1; |
| 183 | 215 | |
| 184 | 216 | while ($left <= $right) { |
| 185 | 217 | $mid = $left + (($right - $left) >> 1); |
| 218 | + $slot = $availableSlots[$mid]; | |
| 186 | 219 | |
| 187 | - $midStartTime = strtotime($availableSlots[$mid]['start']); | |
| 188 | - $midEndTime = strtotime($availableSlots[$mid]['end']); | |
| 220 | + $slotStartTime = strtotime($slot['start']); | |
| 221 | + $slotEndTime = strtotime($slot['end']); | |
| 189 | 222 | |
| 190 | - if ($fromTimeStamp == $midStartTime && $toTimeStamp == $midEndTime) { | |
| 191 | - return true; | |
| 192 | - } elseif ($fromTimeStamp > $midStartTime) { | |
| 223 | + if ($fromTimeStamp == $slotStartTime && $toTimeStamp == $slotEndTime) { | |
| 224 | + return $slot; | |
| 225 | + } elseif ($fromTimeStamp > $slotStartTime) { | |
| 193 | 226 | $left = $mid + 1; |
| 194 | 227 | } else { |
| 195 | 228 | $right = $mid - 1; |
| 196 | 229 | } |
| @@ -209,9 +242,9 @@ | ||
| 209 | 242 | $endDate = gmdate('Y-m-t 23:59:59', strtotime($startDate)); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date |
| 210 | 243 | } |
| 211 | 244 | |
| 212 | 245 | $currentDate = strtotime($startDate); |
| 213 | - $endDate = strtotime($endDate); | |
| 246 | + $endDate = strtotime($endDate) + 1; // add 1s in case end is 23:59:59 | |
| 214 | 247 | $oneDay = 24 * 60 * 60; |
| 215 | 248 | |
| 216 | 249 | $dateArray = []; |
| 217 | 250 | |
| @@ -243,13 +276,20 @@ | ||
| 243 | 276 | |
| 244 | 277 | $hostIds = $this->calendarSlot->getHostIds($this->hostId); |
| 245 | 278 | $status = ['pending', 'reserved', 'approved', 'scheduled', 'completed']; |
| 246 | 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 | + | |
| 247 | 285 | $bookings = Booking::with(['calendar_event']) |
| 248 | 286 | ->whereHas('hosts', function ($query) use ($hostIds) { |
| 249 | 287 | $query->whereIn('user_id', $hostIds); |
| 250 | 288 | }) |
| 251 | - ->whereBetween('start_time', $dateRange) | |
| 289 | + ->where('start_time', '>=', $rangeLowerBound) | |
| 290 | + ->where('start_time', '<=', $dateRange[1]) | |
| 291 | + ->where('end_time', '>=', $dateRange[0]) | |
| 252 | 292 | ->orderBy('start_time', 'ASC') |
| 253 | 293 | ->whereIn('status', $status) |
| 254 | 294 | ->get() |
| 255 | 295 | ->groupBy('group_id'); |
| @@ -365,9 +405,9 @@ | ||
| 365 | 405 | |
| 366 | 406 | $interval = $this->calendarSlot->getSlotInterval($duration) * 60; |
| 367 | 407 | |
| 368 | 408 | $weeklySlots = $this->calendarSlot->getWeeklySlots($hostId); |
| 369 | - | |
| 409 | + | |
| 370 | 410 | $items = $this->getEnabledSlots($weeklySlots); |
| 371 | 411 | |
| 372 | 412 | // create range of each day slots from $items array above with $period minutes interval |
| 373 | 413 | $formattedSlots = []; |
| @@ -416,12 +456,12 @@ | ||
| 416 | 456 | if (isset($days[$nextDayIndex])) { |
| 417 | 457 | $nextDay = $items[$days[$nextDayIndex]]; |
| 418 | 458 | |
| 419 | 459 | if ($nextDay && $nextDay[0]['start'] == '00:00') { |
| 420 | - $nextDayEnd = strtotime($nextDay[0]['end']); | |
| 460 | + $nextDayEndTime = strtotime($nextDay[0]['end']) - strtotime($nextDay[0]['start']); | |
| 421 | 461 | $reserveTime = $end - $start; |
| 422 | 462 | |
| 423 | - while ($period - $reserveTime <= $nextDayEnd) { | |
| 463 | + while ($period - $reserveTime <= $nextDayEndTime) { | |
| 424 | 464 | $startTime = gmdate('H:i', $start); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date |
| 425 | 465 | $nextDayStart = gmdate('H:i', $interval - $reserveTime); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date |
| 426 | 466 | $daySlots[] = $startTime; |
| 427 | 467 | |
| @@ -495,12 +535,12 @@ | ||
| 495 | 535 | if (isset($overrideSlots[$nextDayIndex])) { |
| 496 | 536 | $nextDay = $overrideSlots[$nextDayIndex]; |
| 497 | 537 | |
| 498 | 538 | if ($nextDay && $nextDay[0]['start'] == '00:00') { |
| 499 | - $nextDayEnd = strtotime($nextDay[0]['end']); | |
| 539 | + $nextDayEndTime = strtotime($nextDay[0]['end']) - strtotime($nextDay[0]['start']); | |
| 500 | 540 | $reserveTime = $end - $start; |
| 501 | 541 | |
| 502 | - while ($period - $reserveTime <= $nextDayEnd) { | |
| 542 | + while ($period - $reserveTime <= $nextDayEndTime) { | |
| 503 | 543 | $startTime = gmdate('H:i', $start); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date |
| 504 | 544 | $nextDayStart = gmdate('H:i', $interval - $reserveTime); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date |
| 505 | 545 | $formattedSlots[] = $startTime; |
| 506 | 546 | |
| @@ -538,20 +578,20 @@ | ||
| 538 | 578 | { |
| 539 | 579 | $this->hostId = $hostId; |
| 540 | 580 | |
| 541 | 581 | $event = $this->calendarSlot; |
| 542 | - $calendar = $this->calendar; | |
| 543 | 582 | $duration = $event->getDuration($duration); |
| 544 | 583 | |
| 545 | - $startDate = $this->adjustStartDate($startDate, $timeZone); | |
| 584 | + $adjustedDate = $this->adjustStartDate($startDate, $timeZone); | |
| 546 | 585 | |
| 547 | 586 | $isDisplaySpots = $event->is_display_spots; |
| 548 | 587 | $isMultiGuest = $event->isMultiGuestEvent(); |
| 549 | - $endDate = $event->getMaxBookableDateTime($startDate, $timeZone); | |
| 588 | + $isMultiBooking = $event->isAdditionalGuestEnabled(); | |
| 589 | + $endDate = $event->getMaxBookableDateTime($adjustedDate, $timeZone); | |
| 550 | 590 | $startDate = $event->getMinBookableDateTime($startDate, $timeZone); |
| 551 | 591 | |
| 552 | 592 | $maxBooking = false; |
| 553 | - if ($isDisplaySpots && $isMultiGuest) { | |
| 593 | + if ($isMultiGuest && ($isDisplaySpots || $isMultiBooking)) { | |
| 554 | 594 | $maxBooking = $event->getMaxBookingPerSlot(); |
| 555 | 595 | } |
| 556 | 596 | |
| 557 | 597 | if (strtotime($startDate) > strtotime($endDate)) { |
| @@ -618,9 +658,9 @@ | ||
| 618 | 658 | } |
| 619 | 659 | } |
| 620 | 660 | |
| 621 | 661 | $convertedSpots = array_map(function ($spots) { |
| 622 | - return array_values($spots); | |
| 662 | + return array_values(ksort($spots) ? $spots : $spots); | |
| 623 | 663 | }, $convertedSpots); |
| 624 | 664 | |
| 625 | 665 | return $convertedSpots; |
| 626 | 666 | } |
| @@ -678,9 +718,9 @@ | ||
| 678 | 718 | |
| 679 | 719 | return $rangeArray; |
| 680 | 720 | } |
| 681 | 721 | |
| 682 | - private function maybeBookingFrequencyLimitRanges($ranges, $bookedSlots) | |
| 722 | + private function maybeBookingFrequencyLimitRanges($ranges) | |
| 683 | 723 | { |
| 684 | 724 | if (!$ranges) { |
| 685 | 725 | return $ranges; |
| 686 | 726 | } |
| @@ -735,39 +775,12 @@ | ||
| 735 | 775 | } |
| 736 | 776 | } |
| 737 | 777 | } |
| 738 | 778 | } |
| 739 | - | |
| 740 | - // Per Day Booking Frequency Limit Hanlder | |
| 741 | - if (!empty($keyedFrequenceyLimits['per_day'])) { | |
| 742 | - $perDayLimit = $keyedFrequenceyLimits['per_day']; | |
| 743 | - foreach ($ranges as $rangeIndex => $rangeDate) { | |
| 744 | - if (!isset($bookedSlots[$rangeDate])) { | |
| 745 | - continue; | |
| 746 | - } | |
| 747 | - | |
| 748 | - $dayBooked = array_filter($bookedSlots[$rangeDate], function ($slot) { | |
| 749 | - return Arr::get($slot, 'event_id') == $this->calendarSlot->id; | |
| 750 | - }); | |
| 751 | - | |
| 752 | - if (!$dayBooked) { | |
| 753 | - continue; | |
| 754 | - } | |
| 755 | - | |
| 756 | - if (count($dayBooked) >= $perDayLimit) { | |
| 757 | - unset($ranges[$rangeIndex]); | |
| 758 | - } | |
| 759 | - | |
| 760 | - if (!$ranges) { | |
| 761 | - return []; | |
| 762 | - } | |
| 763 | - } | |
| 764 | - } | |
| 765 | - | |
| 766 | 779 | return $ranges; |
| 767 | 780 | } |
| 768 | 781 | |
| 769 | - private function maybeBookingDurationLimitRanges($ranges, $bookedSlots, $duration) | |
| 782 | + private function maybeBookingDurationLimitRanges($ranges, $duration) | |
| 770 | 783 | { |
| 771 | 784 | if (!$ranges) { |
| 772 | 785 | return $ranges; |
| 773 | 786 | } |
| @@ -814,35 +827,153 @@ | ||
| 814 | 827 | } |
| 815 | 828 | } |
| 816 | 829 | } |
| 817 | 830 | } |
| 831 | + return $ranges; | |
| 832 | + } | |
| 818 | 833 | |
| 819 | - // Per Day Booking Frequency Limit Hanlder | |
| 820 | - if (!empty($keyedLimits['per_day'])) { | |
| 821 | - $perDayLimit = $keyedLimits['per_day']; | |
| 822 | - foreach ($ranges as $rangeIndex => $rangeDate) { | |
| 823 | - if (!isset($bookedSlots[$rangeDate])) { | |
| 824 | - 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); | |
| 825 | 889 | } |
| 890 | + return $carry; | |
| 891 | + }, 0); | |
| 826 | 892 | |
| 827 | - $dayDurarion = array_reduce($bookedSlots[$rangeDate], function ($carry, $slot) { | |
| 828 | - if (Arr::get($slot, 'event_id') == $this->calendarSlot->id) { | |
| 829 | - $carry += (int)((strtotime($slot['end']) - strtotime($slot['start'])) / 60); | |
| 830 | - } | |
| 831 | - return $carry; | |
| 832 | - }, 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 | + } | |
| 833 | 904 | |
| 834 | - if (!$dayDurarion) { | |
| 835 | - 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 | + })); | |
| 836 | 944 | } |
| 945 | + if (!$isMultiSlot || !count($slots)) { | |
| 946 | + unset($rangesSlots[$rangeDate]); | |
| 947 | + } | |
| 948 | + } | |
| 949 | + } | |
| 837 | 950 | |
| 838 | - if ($dayDurarion + $duration > $perDayLimit) { | |
| 839 | - unset($ranges[$rangeIndex]); | |
| 840 | - } | |
| 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; | |
| 841 | 972 | } |
| 842 | 973 | } |
| 843 | 974 | |
| 844 | - return $ranges; | |
| 975 | + return $convertedSlots; | |
| 845 | 976 | } |
| 846 | 977 | |
| 847 | 978 | public function getFilledWeeks($from, $to, $weekStart = '') |
| 848 | 979 | { |
| @@ -905,8 +1036,15 @@ | ||
| 905 | 1036 | ->where('event_id', $this->calendarSlot->id) |
| 906 | 1037 | ->whereBetween('start_time', [$start, $end]) |
| 907 | 1038 | ->whereIn('status', ['scheduled', 'completed']) |
| 908 | 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); | |
| 909 | 1047 | } |
| 910 | 1048 | |
| 911 | 1049 | protected function getTimezoneInfo($hostId = null) |
| 912 | 1050 | { |