PluginProbe
Booktics – Appointment Booking Calendar for Service Businesses / 1.0.1
Booktics – Appointment Booking Calendar for Service Businesses v1.0.1
1.0.29 1.0.28 1.0.27 1.0.26 1.0.25 1.0.24 1.0.23 1.0.22 1.0.21 1.0.20 1.0.19 trunk 1.0.0 1.0.1 1.0.10 1.0.11 1.0.12 1.0.13 1.0.14 1.0.15 1.0.16 1.0.17 1.0.18 1.0.2 1.0.3 All 31 releases
booktics / core / appointment / schedule / time-slot-manager.php

time-slot-manager.php in Booktics – Appointment Booking Calendar for Service Businesses 1.0.1, at core/appointment/schedule/time-slot-manager.php

426 lines 14.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Time_Slot_Manager class
4 *
5 * @package Booktics\Appointment\Schedule
6 */
7
8 namespace Booktics\Appointment\Schedule;
9
10 use Booktics\Models\Appointment_Model;
11 use DateTime;
12 use Exception;
13
14 /**
15 * Class Time_Slot_Manager
16 */
17 class Time_Slot_Manager {
18 /**
19 * Store Service Schedule Object
20 *
21 * @var Service_Schedule
22 */
23 private $service_schedule;
24
25 /**
26 * Store Team member Schedule Object
27 *
28 * @var Team_Member_Schedule
29 */
30 private $team_member_schedule;
31
32 /**
33 * Store Default Schedule Object
34 *
35 * @var Default_Schedule
36 */
37 private $default_schedule;
38
39 /**
40 * Store service duration in minutes
41 *
42 * @var integer
43 */
44 private $service_duration;
45
46 /**
47 * Store slot length in minutes
48 *
49 * @var integer
50 */
51 private $slot_length;
52
53 /**
54 * Store time format (12 or 24 hour)
55 *
56 * @var string
57 */
58 private $slot_format = '12'; // Default to 12 hour format.
59
60 /**
61 * Constructor
62 *
63 * @param Service_Schedule $service Service schedule object.
64 * @param Team_Member_Schedule $team_member Team member schedule object.
65 * @param Default_Schedule $default_schedule Default schedule object.
66 * @param integer $service_duration Service duration in minutes.
67 * @param integer $slot_length Slot length in minutes.
68 */
69 public function __construct( Service_Schedule $service, Team_Member_Schedule $team_member, Default_Schedule $default_schedule = null, int $service_duration = 30, int $slot_length = 30 ) {
70 $this->service_schedule = $service;
71 $this->team_member_schedule = $team_member;
72 $this->default_schedule = $default_schedule;
73 $this->service_duration = $service_duration;
74 $this->slot_length = $slot_length;
75 }
76
77 /**
78 * Generate time slots for a specific date
79 *
80 * @param DateTime $date
81 *
82 * @return array
83 */
84 public function generate_slots( DateTime $date ): array {
85 $service_entry = $this->service_schedule->get_first_date( $date );
86 $team_member_entry = $this->team_member_schedule->get_first_date( $date );
87 $default_entry = $this->default_schedule ? $this->default_schedule->get_first_date( $date ) : null;
88
89 // Return empty if all entries are holidays
90 if ( ( $service_entry && $service_entry->is_holiday ) &&
91 ( $team_member_entry && $team_member_entry->is_holiday ) &&
92 ( $default_entry && $default_entry->is_holiday ) ) {
93 return [];
94 }
95
96 // Only team member schedule available
97 if ( $team_member_entry && ! $team_member_entry->is_holiday && ( ! $service_entry || $service_entry->is_holiday ) ) {
98 return $this->generate_slots_from_team_member( $team_member_entry );
99 }
100
101 // Only service schedule available
102 if ( $service_entry && ! $service_entry->is_holiday && ( ! $team_member_entry || $team_member_entry->is_holiday ) ) {
103 return $this->generate_slots_from_service( $service_entry );
104 }
105
106 // Only default schedule available
107 if ( $default_entry && ! $default_entry->is_holiday &&
108 ( ! $service_entry || $service_entry->is_holiday ) &&
109 ( ! $team_member_entry || $team_member_entry->is_holiday ) ) {
110 return $this->generate_slots_from_default( $date, $default_entry );
111 }
112
113 // Team member and default schedule available
114 if ( $team_member_entry && ! $team_member_entry->is_holiday && $default_entry && ! $default_entry->is_holiday ) {
115 return $this->generate_merged_slots( $default_entry, $team_member_entry );
116 }
117
118 // Service and default schedule available
119 if ( $service_entry && ! $service_entry->is_holiday && $default_entry && ! $default_entry->is_holiday ) {
120 return $this->merge_service_and_default( $service_entry, $default_entry );
121 }
122
123 // Both service and team member available
124 if ( $service_entry && ! $service_entry->is_holiday && $team_member_entry && ! $team_member_entry->is_holiday ) {
125 return $this->generate_standard_slots( $service_entry, $team_member_entry );
126 }
127
128 return [];
129 }
130
131 /**
132 * Generate merged time slots from default and team member schedules
133 *
134 * @param Schedule_Entry $default_entry
135 * @param Schedule_Entry $team_entry
136 *
137 * @return array
138 */
139 private function generate_merged_slots( Schedule_Entry $default_entry, Schedule_Entry $team_entry ): array {
140 $default_start = strtotime( $default_entry->start_time );
141 $default_end = strtotime( $default_entry->end_time );
142 $team_start = strtotime( $team_entry->start_time );
143 $team_end = strtotime( $team_entry->end_time );
144
145 // Find overlapping time window
146 $start_time = max( $default_start, $team_start );
147 $end_time = min( $default_end, $team_end );
148
149 if ( $start_time >= $end_time ) {
150 return [];
151 }
152
153 $slots = [];
154 $current = $start_time;
155 $duration = $this->slot_length * 60;
156
157 while ( $current + $duration <= $end_time ) {
158 $slot_start = gmdate( 'H:i', $current );
159 $slot_end = gmdate( 'H:i', $current + $duration );
160
161 $slots[] = new Time_Slot( $slot_start, $slot_end, true );
162 $current += $duration;
163 }
164
165 return $slots;
166 }
167
168 /**
169 * Generate standard time slots from service and team member schedules
170 *
171 * @param Schedule_Entry $service_entry
172 * @param Schedule_Entry $team_entry
173 *
174 * @return array Collection of time slots
175 */
176 private function generate_standard_slots( Schedule_Entry $service_entry, Schedule_Entry $team_entry ): array {
177 if ( $service_entry->is_holiday || $team_entry->is_holiday ) {
178 return [];
179 }
180
181 $service_start = strtotime( $service_entry->start_time );
182 $service_end = strtotime( $service_entry->end_time );
183 $team_start = strtotime( $team_entry->start_time );
184 $team_end = strtotime( $team_entry->end_time );
185
186 $start_time = max( $service_start, $team_start );
187 $end_time = min( $service_end, $team_end );
188
189 if ( $start_time >= $end_time ) {
190 return [];
191 }
192
193 $slots = [];
194 $current = $start_time;
195 $duration = $this->slot_length * 60;
196
197 while ( $current + $duration <= $end_time ) {
198 $slot_start = gmdate( 'H:i', $current );
199 $slot_end = gmdate( 'H:i', $current + $duration );
200
201 $slots[] = new Time_Slot( $slot_start, $slot_end, true );
202 $current += $duration;
203 }
204
205 return $slots;
206 }
207
208 /**
209 * Generate time slots for a date range
210 *
211 * @param string $start_date Start date for the date range
212 * @param string $end_date End date for the date range
213 *
214 * @return array Collection of
215 * @throws Exception
216 */
217 public function generate_slots_for_range( string $start_date, string $end_date ): array {
218 $slots_by_day = [];
219 $start = new DateTime( $start_date );
220 $end = new DateTime( $end_date );
221
222 while ( $start <= $end ) {
223 $daily_slots = $this->generate_slots( clone $start );
224 $slots_by_day[ $start->format( 'Y-m-d' ) ] = $this->mark_booked_slots( clone $start, $daily_slots );
225 $start->modify( '+1 day' );
226 }
227
228 return $slots_by_day;
229 }
230
231 /**
232 * Get all available slots
233 *
234 * @param DateTime $date
235 *
236 * @return array
237 * @throws Exception
238 */
239 public function get_available_slots( DateTime $date ): array {
240 $slots = $this->generate_slots( $date );
241 $slots = $this->mark_booked_slots( $date, $slots );
242
243 return array_filter( $slots, fn( $slot ) => $slot->is_available );
244 }
245
246 /**
247 * Mark booked slots
248 *
249 * @param DateTime $date
250 * @param array $slots
251 *
252 * @return array
253 * @throws Exception
254 */
255 public function mark_booked_slots( DateTime $date, array $slots ): array {
256 $booked_slots = $this->get_booked_slots( $date );
257 foreach ( $slots as $slot ) {
258 $slot_start = strtotime( $slot->start );
259 $slot_end = strtotime( $slot->end );
260
261 foreach ( $booked_slots as $booked ) {
262 $booked_start = strtotime( $booked['start'] );
263 $booked_end = strtotime( $booked['end'] );
264 if ( $slot_start < $booked_end && $slot_end > $booked_start ) {
265 if ( $booked['team_member_id'] == $this->team_member_schedule->get_id() ) {
266 $slot->is_available = false;
267 break;
268 }
269 }
270 }
271 }
272
273 return $slots;
274 }
275
276 /**
277 * Get booked slots
278 *
279 * @param DateTime $date
280 *
281 * @return array
282 * @throws Exception
283 */
284 private function get_booked_slots( DateTime $date ): array {
285 $appointments = ( new Appointment_Model() )
286 ->get_appointments_for_slot(
287 $date->format( 'Y-m-d' ),
288 $this->team_member_schedule->get_id()
289 );
290
291 return array_map(
292 function ( $appointment ) use ( $date ) {
293 $start_time = strtotime( $appointment->start_time );
294 $appointment_duration = 30;
295 if ( $appointment->duration ) {
296 $appointment_duration = $appointment->duration;
297 }
298 $actual_end_time = $start_time + ( $appointment_duration * 60 );
299
300 return array(
301 'start' => date_i18n( 'H:i', $start_time ),
302 'end' => date_i18n( 'H:i', $actual_end_time ),
303 'service_id' => $appointment->service_id,
304 'team_member_id' => $appointment->team_member_id,
305 );
306 },
307 $appointments
308 );
309 }
310
311 /**
312 * Generate time slots from default schedule entry
313 *
314 * @param DateTime $date
315 * @param Schedule_Entry $default_entry
316 *
317 * @return array Collection of time slots
318 */
319 private function generate_slots_from_default( DateTime $date, Schedule_Entry $default_entry ): array {
320 $start_time = $default_entry->start_time;
321 $end_time = $default_entry->end_time;
322
323 if ( ! $start_time || ! $end_time ) {
324 return [];
325 }
326
327 $start = strtotime( $start_time );
328 $end = strtotime( $end_time );
329 $duration = $this->slot_length * 60;
330 $slots = [];
331
332 while ( $start + $duration <= $end ) {
333 $slot_start = gmdate( 'H:i', $start );
334 $slot_end = gmdate( 'H:i', $start + $duration );
335
336 $slots[] = new Time_Slot( $slot_start, $slot_end, true );
337 $start += $duration;
338 }
339
340 return $slots;
341 }
342
343 /**
344 * Generate time slots from service schedule entry
345 *
346 * @param Schedule_Entry $service_entry
347 *
348 * @return array Collection of time slots
349 */
350 private function generate_slots_from_service( Schedule_Entry $service_entry ): array {
351 $start_time = strtotime( $service_entry->start_time );
352 $end_time = strtotime( $service_entry->end_time );
353 $duration = $this->service_duration * 60; // Convert to seconds
354 $slots = [];
355
356 while ( $start_time + $duration <= $end_time ) {
357 $slot_start = gmdate( 'H:i', $start_time );
358 $slot_end = gmdate( 'H:i', $start_time + $duration );
359
360 $slots[] = new Time_Slot( $slot_start, $slot_end, true );
361 $start_time += $this->slot_length * 60; // Use slot_length for increment
362 }
363
364 return $slots;
365 }
366
367 /**
368 * Generate time slots from team member schedule entry
369 *
370 * @param Schedule_Entry $team_entry
371 *
372 * @return array Collection of time slots
373 */
374 private function generate_slots_from_team_member( Schedule_Entry $team_entry ): array {
375 $start_time = strtotime( $team_entry->start_time );
376 $end_time = strtotime( $team_entry->end_time );
377 $duration = $this->slot_length * 60;
378 $slots = [];
379
380 while ( $start_time + $duration <= $end_time ) {
381 $slot_start = gmdate( 'H:i', $start_time );
382 $slot_end = gmdate( 'H:i', $start_time + $duration );
383
384 $slots[] = new Time_Slot( $slot_start, $slot_end, true );
385 $start_time += $duration;
386 }
387
388 return $slots;
389 }
390
391 /**
392 * Merge service and default schedules
393 *
394 * @param Schedule_Entry $service_entry
395 * @param Schedule_Entry $default_entry
396 *
397 * @return array Collection of time slots
398 */
399 private function merge_service_and_default( Schedule_Entry $service_entry, Schedule_Entry $default_entry ): array {
400 $service_start = strtotime( $service_entry->start_time );
401 $service_end = strtotime( $service_entry->end_time );
402 $default_start = strtotime( $default_entry->start_time );
403 $default_end = strtotime( $default_entry->end_time );
404
405 $start_time = max( $service_start, $default_start );
406 $end_time = min( $service_end, $default_end );
407
408 if ( $start_time >= $end_time ) {
409 return [];
410 }
411
412 $slots = [];
413 $current = $start_time;
414 $duration = $this->slot_length * 60;
415
416 while ( $current + $duration <= $end_time ) {
417 $slot_start = gmdate( 'H:i', $current );
418 $slot_end = gmdate( 'H:i', $current + $duration );
419
420 $slots[] = new Time_Slot( $slot_start, $slot_end, true );
421 $current += $duration;
422 }
423
424 return $slots;
425 }
426 }