PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.4.8
Booking for Appointments and Events Calendar – Amelia v2.4.8
2.4.9 2.4.8 2.4.7 2.4.6 2.4.5 2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / src / Application / Commands / Booking / Appointment / GetTimeSlotsCommandHandler.php
ameliabooking / src / Application / Commands / Booking / Appointment Last commit date
AddAppointmentCommand.php 1 year ago AddAppointmentCommandHandler.php 3 weeks ago AddBookingCommand.php 1 year ago AddBookingCommandHandler.php 1 week ago ApproveBookingRemotelyCommand.php 1 year ago ApproveBookingRemotelyCommandHandler.php 4 months ago CancelBookingCommand.php 1 year ago CancelBookingCommandHandler.php 3 weeks ago CancelBookingRemotelyCommand.php 1 year ago CancelBookingRemotelyCommandHandler.php 2 months ago DeleteAppointmentCommand.php 1 year ago DeleteAppointmentCommandHandler.php 1 year ago DeleteBookingCommand.php 1 year ago DeleteBookingCommandHandler.php 11 months ago DeleteBookingRemotelyCommand.php 11 months ago DeleteBookingRemotelyCommandHandler.php 1 week ago GetAppointmentBookingsCommand.php 8 months ago GetAppointmentBookingsCommandHandler.php 3 weeks ago GetAppointmentCommand.php 7 years ago GetAppointmentCommandHandler.php 3 weeks ago GetAppointmentsCommand.php 1 year ago GetAppointmentsCommandHandler.php 3 weeks ago GetIcsCommand.php 8 months ago GetIcsCommandHandler.php 4 years ago GetTimeSlotsCommand.php 1 year ago GetTimeSlotsCommandHandler.php 1 week ago ReassignBookingCommand.php 1 year ago ReassignBookingCommandHandler.php 3 weeks ago RejectBookingRemotelyCommand.php 1 year ago RejectBookingRemotelyCommandHandler.php 6 months ago SuccessfulBookingCommand.php 1 year ago SuccessfulBookingCommandHandler.php 2 weeks ago UpdateAppointmentCommand.php 1 year ago UpdateAppointmentCommandHandler.php 1 week ago UpdateAppointmentNoteCommand.php 4 months ago UpdateAppointmentNoteCommandHandler.php 3 weeks ago UpdateAppointmentStatusCommand.php 1 year ago UpdateAppointmentStatusCommandHandler.php 3 weeks ago UpdateAppointmentTimeCommand.php 1 year ago UpdateAppointmentTimeCommandHandler.php 3 weeks ago UpdateBookingStatusCommand.php 1 year ago UpdateBookingStatusCommandHandler.php 7 months ago
GetTimeSlotsCommandHandler.php
444 lines
1 <?php
2
3 namespace AmeliaBooking\Application\Commands\Booking\Appointment;
4
5 use AmeliaBooking\Application\Commands\CommandHandler;
6 use AmeliaBooking\Application\Commands\CommandResult;
7 use AmeliaBooking\Application\Services\TimeSlot\TimeSlotService as ApplicationTimeSlotService;
8 use AmeliaBooking\Domain\Entity\User\Provider;
9 use AmeliaBooking\Domain\Services\Entity\EntityService;
10 use AmeliaBooking\Domain\Common\Exceptions\InvalidArgumentException;
11 use AmeliaBooking\Domain\Entity\Bookable\Service\Service;
12 use AmeliaBooking\Domain\Entity\Booking\SlotsEntities;
13 use AmeliaBooking\Domain\Services\DateTime\DateTimeService;
14 use AmeliaBooking\Domain\Services\Schedule\ScheduleService;
15 use AmeliaBooking\Domain\Services\Settings\SettingsService;
16 use AmeliaBooking\Domain\ValueObjects\PositiveDuration;
17 use AmeliaBooking\Infrastructure\Common\Exceptions\QueryExecutionException;
18 use AmeliaBooking\Infrastructure\Repository\Booking\Appointment\AppointmentRepository;
19 use AmeliaBooking\Infrastructure\Services\Apple\AbstractAppleCalendarService;
20 use AmeliaBooking\Infrastructure\Services\Google\AbstractGoogleCalendarService;
21 use AmeliaBooking\Infrastructure\Services\Outlook\AbstractOutlookCalendarService;
22 use DateTimeZone;
23 use Exception;
24 use Interop\Container\Exception\ContainerException;
25 use Slim\Exception\ContainerValueNotFoundException;
26
27 /**
28 * Class GetTimeSlotsCommandHandler
29 *
30 * @package AmeliaBooking\Application\Commands\Booking\Appointment
31 */
32 class GetTimeSlotsCommandHandler extends CommandHandler
33 {
34 /**
35 * @var array
36 */
37 public $mandatoryFields = [
38 'serviceId'
39 ];
40
41 /**
42 * @param GetTimeSlotsCommand $command
43 *
44 * @return CommandResult
45 * @throws ContainerValueNotFoundException
46 * @throws ContainerException
47 * @throws Exception
48 * @throws InvalidArgumentException
49 * @throws QueryExecutionException
50 */
51 public function handle(GetTimeSlotsCommand $command)
52 {
53 $result = new CommandResult();
54
55 $this->checkMandatoryFields($command);
56
57 /** @var EntityService $entityService */
58 $entityService = $this->container->get('domain.entity.service');
59
60 /** @var ApplicationTimeSlotService $applicationTimeSlotService */
61 $applicationTimeSlotService = $this->container->get('application.timeSlot.service');
62
63 /** @var SettingsService $settingsDS */
64 $settingsDS = $this->container->get('domain.settings.service');
65
66 $props = [
67 'serviceId' => $command->getField('serviceId'),
68 'providerIds' => $command->getField('providerIds'),
69 'locationId' => $command->getField('locationId'),
70 'locationIds' => $command->getField('locationIds'),
71 'extras' => $command->getField('extras'),
72 'excludeAppointmentId' => $command->getField('excludeAppointmentId'),
73 'personsCount' => $command->getField('group') ? $command->getField('persons') : null,
74 'isFrontEndBooking' => $command->getField('page') === 'booking' || $command->getField('page') === 'cabinet',
75 'totalPersons' => $command->getField('persons'),
76 'serviceDuration' => $command->getField('serviceDuration'),
77 'monthsLoad' => $command->getField('monthsLoad'),
78 'startDateTime' => $command->getField('startDateTime'),
79 'endDateTime' => $command->getField('endDateTime'),
80 'queryTimeZone' => $command->getField('queryTimeZone'),
81 'timeZone' => $command->getField('timeZone'),
82 'structured' => $command->getField('structured'),
83
84 ];
85
86 $props = apply_filters('amelia_before_get_timeslots_filter', $props);
87
88 do_action('amelia_before_get_timeslots', $props);
89
90 $isFrontEndBooking = $props['isFrontEndBooking'];
91
92 /** @var SlotsEntities $slotsEntities */
93 $slotsEntities = $applicationTimeSlotService->getSlotsEntities(
94 [
95 'isFrontEndBooking' => $isFrontEndBooking,
96 'providerIds' => $props['providerIds'],
97 ]
98 );
99
100 $settings = $applicationTimeSlotService->getSlotsSettings($isFrontEndBooking, $slotsEntities, $props);
101
102 if (!empty($settings['allowAdminBookAtAnyTime']) && !empty($props['structured'])) {
103 /** @var ScheduleService $scheduleService */
104 $scheduleService = $this->container->get('domain.schedule.service');
105 $normalProvidersIntervals = [];
106 foreach ($slotsEntities->getProviders()->getItems() as $provider) {
107 $normalProvidersIntervals[$provider->getId()->getValue()] = [
108 'weekDays' => $scheduleService->getProviderWeekDaysIntervals(
109 $provider,
110 $slotsEntities->getLocations(),
111 $props['locationId'],
112 $props['serviceId']
113 ),
114 'specialDays' => $scheduleService->getProviderSpecialDayIntervals(
115 $provider,
116 $slotsEntities->getLocations(),
117 $props['locationId'],
118 $props['serviceId']
119 ),
120 ];
121 }
122 $settings['normalProvidersIntervals'] = $normalProvidersIntervals;
123 }
124
125 $lastBookedProviderId = null;
126
127 /** @var SlotsEntities $filteredSlotEntities */
128 $filteredSlotEntities = $entityService->getFilteredSlotsEntities(
129 $settings,
130 $props,
131 $slotsEntities
132 );
133
134 /** @var Service $service */
135 $service = $filteredSlotEntities->getServices()->getItem($props['serviceId']);
136
137 if ($props['serviceDuration']) {
138 $service->setDuration(new PositiveDuration($props['serviceDuration']));
139 }
140
141 $minimumBookingTimeInSeconds = $settingsDS
142 ->getEntitySettings($service->getSettings())
143 ->getGeneralSettings()
144 ->getMinimumTimeRequirementPriorToBooking();
145
146 $maximumBookingTimeInDays = $settingsDS
147 ->getEntitySettings($service->getSettings())
148 ->getGeneralSettings()
149 ->getNumberOfDaysAvailableForBooking();
150
151 $monthsLoad = $props['monthsLoad'];
152
153 $loadGeneratedPeriod = $monthsLoad &&
154 !$props['endDateTime'];
155
156 $timeZone = $props['queryTimeZone'] ?: DateTimeService::getTimeZone()->getName();
157
158 $queryStartDateTime = $props['startDateTime'] ?
159 DateTimeService::getDateTimeObjectInTimeZone(
160 $props['startDateTime'],
161 $timeZone
162 )->setTimezone(DateTimeService::getTimeZone()) : null;
163
164 $queryEndDateTime = $props['endDateTime'] ?
165 DateTimeService::getDateTimeObjectInTimeZone(
166 $props['endDateTime'],
167 $timeZone
168 )->setTimezone(DateTimeService::getTimeZone()) : null;
169
170 $minimumDateTime = $applicationTimeSlotService->getMinimumDateTimeForBooking(
171 null,
172 $isFrontEndBooking,
173 $minimumBookingTimeInSeconds
174 );
175
176 $startDateTime = $queryStartDateTime ?:
177 $applicationTimeSlotService->getMinimumDateTimeForBooking(
178 null,
179 $isFrontEndBooking,
180 $minimumBookingTimeInSeconds
181 );
182
183 $endDateTime = $queryEndDateTime ?:
184 $applicationTimeSlotService->getMaximumDateTimeForBooking(
185 null,
186 $isFrontEndBooking,
187 $maximumBookingTimeInDays
188 );
189
190 $maximumDateTime = $applicationTimeSlotService->getMaximumDateTimeForBooking(
191 null,
192 $isFrontEndBooking,
193 $maximumBookingTimeInDays
194 );
195
196 if ($isFrontEndBooking) {
197 $startDateTime = $startDateTime < $minimumDateTime ? $minimumDateTime : $startDateTime;
198
199 $endDateTime = $endDateTime > $maximumDateTime ? $maximumDateTime : $endDateTime;
200 }
201
202 // set initial search period if query dates are not set
203 if ($loadGeneratedPeriod) {
204 $endDateTime = DateTimeService::getCustomDateTimeObject(
205 $startDateTime->format('Y-m-d H:i:s')
206 )->setTimezone(
207 new DateTimeZone($timeZone)
208 );
209
210 $endDateTime->modify('first day of this month');
211
212 $endDateTime->modify('+' . ($monthsLoad - 1) . 'months');
213
214 $endDateTime->modify('last day of this month');
215
216 $endDateTime->modify('+12days');
217
218 $endDateTime->setTime(23, 59, 59);
219
220 if ($isFrontEndBooking) {
221 $endDateTime = $endDateTime > $maximumDateTime ?
222 DateTimeService::getDateTimeObjectInTimeZone(
223 $maximumDateTime->format('Y-m-d H:i'),
224 $timeZone
225 ) : $endDateTime;
226 }
227
228 $endDateTime->setTimezone(DateTimeService::getTimeZone());
229 }
230
231 /** @var Service $filteredSlotEntitiesService */
232 foreach ($filteredSlotEntities->getServices()->getItems() as $filteredSlotEntitiesService) {
233 if ($filteredSlotEntitiesService->getId()->getValue() === $service->getId()->getValue()) {
234 $filteredSlotEntitiesService->setDuration($service->getDuration());
235
236 break;
237 }
238 }
239
240 /** @var Provider $filteredSlotEntitiesProvider */
241 foreach ($filteredSlotEntities->getProviders()->getItems() as $filteredSlotEntitiesProvider) {
242 /** @var Service $providerService */
243 foreach ($filteredSlotEntitiesProvider->getServiceList()->getItems() as $providerService) {
244 if ($providerService->getId()->getValue() === $service->getId()->getValue()) {
245 $providerService->setDuration($service->getDuration());
246
247 break;
248 }
249 }
250 }
251
252 $freeSlots = $applicationTimeSlotService->getSlotsByProps(
253 $settings,
254 array_merge(
255 $props,
256 [
257 'startDateTime' => $startDateTime,
258 'endDateTime' => $endDateTime,
259 ]
260 ),
261 $filteredSlotEntities
262 );
263
264 if ($loadGeneratedPeriod) {
265 // search with new period until slots are not found
266 while (!$freeSlots['available'] && $endDateTime && $endDateTime <= $maximumDateTime) {
267 $startDateTime = DateTimeService::getCustomDateTimeObject(
268 $endDateTime->format('Y-m-d H:i:s')
269 )->setTimezone(
270 new DateTimeZone($timeZone)
271 );
272
273 $startDateTime->setTime(0, 0, 0);
274
275 $endDateTime->modify('first day of this month');
276
277 $endDateTime->modify('+' . ($monthsLoad - 1) . 'months');
278
279 $endDateTime->modify('last day of this month');
280
281 $endDateTime->modify('+12days');
282
283 $endDateTime->setTime(23, 59, 59);
284
285 if ($isFrontEndBooking) {
286 $endDateTime = $endDateTime > $maximumDateTime ?
287 DateTimeService::getDateTimeObjectInTimeZone(
288 $maximumDateTime->format('Y-m-d H:i'),
289 $timeZone
290 ) : $endDateTime;
291 }
292
293 $endDateTime->setTimezone(DateTimeService::getTimeZone());
294
295 AbstractGoogleCalendarService::$providersGoogleEvents = [];
296
297 AbstractOutlookCalendarService::$providersOutlookEvents = [];
298
299 AbstractAppleCalendarService::$providersAppleEvents = [];
300
301 $freeSlots = $applicationTimeSlotService->getSlotsByProps(
302 $settings,
303 array_merge(
304 $props,
305 [
306 'startDateTime' => $startDateTime,
307 'endDateTime' => $endDateTime,
308 ]
309 ),
310 $filteredSlotEntities
311 );
312
313 $endDateTimeCopy = clone $endDateTime;
314
315 $maximumDateTimeCopy = clone $maximumDateTime;
316
317 if (
318 $endDateTimeCopy->format('Y-m-d H:i') === $maximumDateTimeCopy->format('Y-m-d H:i') ||
319 ($endDateTimeCopy->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i') ===
320 $maximumDateTimeCopy->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i')) ||
321 ($endDateTimeCopy >= $maximumDateTimeCopy)
322 ) {
323 break;
324 }
325 }
326
327 // search once more if first available date is in 11 days added to endDateTime (days outside calendar on frontend form)
328 foreach (array_slice($freeSlots['available'], 0, 1, true) as $slotDate => $slotTimes) {
329 if (substr($slotDate, 0, 7) === $endDateTime->format('Y-m')) {
330 $endDateTime->modify('last day of this month');
331
332 $endDateTime->modify('+12days');
333
334 $endDateTime->setTime(23, 59, 59);
335
336 if ($isFrontEndBooking) {
337 $endDateTime = $endDateTime > $maximumDateTime ?
338 DateTimeService::getDateTimeObjectInTimeZone(
339 $maximumDateTime->format('Y-m-d H:i'),
340 $timeZone
341 ) : $endDateTime;
342 }
343
344 AbstractGoogleCalendarService::$providersGoogleEvents = [];
345
346 AbstractOutlookCalendarService::$providersOutlookEvents = [];
347
348 AbstractAppleCalendarService::$providersAppleEvents = [];
349
350 $freeSlots = $applicationTimeSlotService->getSlotsByProps(
351 $settings,
352 array_merge(
353 $props,
354 [
355 'startDateTime' => $startDateTime,
356 'endDateTime' => $endDateTime,
357 ]
358 ),
359 $filteredSlotEntities
360 );
361 }
362 }
363 }
364
365 $busyness = $applicationTimeSlotService->getBusynessFromSlots(
366 $freeSlots,
367 (int) $props['serviceId'],
368 !empty($props['structured'])
369 );
370
371 $converted = ['available' => [], 'occupied' => []];
372
373 $isUtcResponse = ($settingsDS->getSetting('general', 'showClientTimeZone') && $isFrontEndBooking) ||
374 $props['timeZone'];
375
376 if ($isUtcResponse) {
377 foreach (['available', 'occupied'] as $type) {
378 foreach ($freeSlots[$type] as $slotDate => $slotTimes) {
379 foreach ($freeSlots[$type][$slotDate] as $slotTime => $slotTimesProviders) {
380 $convertedSlotParts = explode(
381 ' ',
382 $props['timeZone'] ?
383 DateTimeService::getCustomDateTimeObjectInTimeZone(
384 $slotDate . ' ' . $slotTime,
385 $props['timeZone']
386 )->format('Y-m-d H:i') :
387 DateTimeService::getCustomDateTimeObjectInUtc(
388 $slotDate . ' ' . $slotTime
389 )->format('Y-m-d H:i')
390 );
391
392 $converted[$type][$convertedSlotParts[0]][$convertedSlotParts[1]] = $slotTimesProviders;
393 }
394 }
395 }
396 }
397
398 if (count($props['providerIds']) !== 1 && $settingsDS->getSetting('appointments', 'employeeSelection') === 'roundRobin') {
399 /** @var AppointmentRepository $appointmentRepository */
400 $appointmentRepository = $this->container->get('domain.booking.appointment.repository');
401
402 $lastBookedProviderId = $appointmentRepository->getLastBookedEmployee($props['providerIds']);
403 }
404
405 $resultData = [
406 'slots' => $converted['available'] ?: $freeSlots['available'],
407 'occupied' => $converted['occupied'] ?: $freeSlots['occupied'],
408 'minimum' => $isUtcResponse ?
409 $minimumDateTime->setTimezone(
410 new DateTimeZone('UTC')
411 )->format('Y-m-d H:i') : $minimumDateTime->format('Y-m-d H:i'),
412 'maximum' => $isUtcResponse ?
413 $maximumDateTime->setTimezone(
414 new DateTimeZone('UTC')
415 )->format('Y-m-d H:i') : $maximumDateTime->format('Y-m-d H:i'),
416 'busyness' => $busyness,
417 'lastBookedProviderId' => $lastBookedProviderId,
418 'appCount' => $freeSlots['appCount'],
419 'duration' => $freeSlots['duration'],
420 ];
421
422 $resultData = apply_filters('amelia_get_timeslots_filter', $resultData, $props);
423
424 do_action('amelia_get_timeslots', $resultData, $props);
425
426 $result->setResult(CommandResult::RESULT_SUCCESS);
427 $result->setMessage('Successfully retrieved free slots');
428 $result->setData(
429 [
430 'minimum' => $resultData['minimum'],
431 'maximum' => $resultData['maximum'],
432 'slots' => $resultData['slots'],
433 'occupied' => $resultData['occupied'],
434 'busyness' => $resultData['busyness'],
435 'lastProvider' => $resultData['lastBookedProviderId'],
436 'appCount' => $resultData['appCount'],
437 'duration' => $resultData['duration'],
438 ]
439 );
440
441 return $result;
442 }
443 }
444