PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / trunk
Booking for Appointments and Events Calendar – Amelia vtrunk
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 2 months ago AddBookingCommand.php 1 year ago AddBookingCommandHandler.php 1 year ago ApproveBookingRemotelyCommand.php 1 year ago ApproveBookingRemotelyCommandHandler.php 2 months ago CancelBookingCommand.php 1 year ago CancelBookingCommandHandler.php 6 months ago CancelBookingRemotelyCommand.php 1 year ago CancelBookingRemotelyCommandHandler.php 1 month ago DeleteAppointmentCommand.php 1 year ago DeleteAppointmentCommandHandler.php 1 year ago DeleteBookingCommand.php 1 year ago DeleteBookingCommandHandler.php 10 months ago DeleteBookingRemotelyCommand.php 10 months ago DeleteBookingRemotelyCommandHandler.php 6 months ago GetAppointmentBookingsCommand.php 6 months ago GetAppointmentBookingsCommandHandler.php 2 months ago GetAppointmentCommand.php 7 years ago GetAppointmentCommandHandler.php 2 weeks ago GetAppointmentsCommand.php 1 year ago GetAppointmentsCommandHandler.php 2 weeks ago GetIcsCommand.php 6 months ago GetIcsCommandHandler.php 4 years ago GetTimeSlotsCommand.php 1 year ago GetTimeSlotsCommandHandler.php 1 month ago ReassignBookingCommand.php 1 year ago ReassignBookingCommandHandler.php 3 months ago RejectBookingRemotelyCommand.php 1 year ago RejectBookingRemotelyCommandHandler.php 4 months ago SuccessfulBookingCommand.php 1 year ago SuccessfulBookingCommandHandler.php 1 year ago UpdateAppointmentCommand.php 1 year ago UpdateAppointmentCommandHandler.php 3 months ago UpdateAppointmentNoteCommand.php 3 months ago UpdateAppointmentNoteCommandHandler.php 3 months ago UpdateAppointmentStatusCommand.php 1 year ago UpdateAppointmentStatusCommandHandler.php 2 months ago UpdateAppointmentTimeCommand.php 1 year ago UpdateAppointmentTimeCommandHandler.php 1 month ago UpdateBookingStatusCommand.php 1 year ago UpdateBookingStatusCommandHandler.php 5 months ago
GetTimeSlotsCommandHandler.php
451 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 = [];
366
367 foreach ($freeSlots['available'] as $slotDate => $slotTimes) {
368 $busyness[$slotDate] = round(
369 count(!empty($freeSlots['occupied'][$slotDate]) ? $freeSlots['occupied'][$slotDate] : []) /
370 (count(!empty($freeSlots['available'][$slotDate]) ? $freeSlots['available'][$slotDate] : []) +
371 count(!empty($freeSlots['occupied'][$slotDate]) ? $freeSlots['occupied'][$slotDate] : []))
372 * 100
373 );
374 }
375
376 $converted = ['available' => [], 'occupied' => []];
377
378 $isUtcResponse = ($settingsDS->getSetting('general', 'showClientTimeZone') && $isFrontEndBooking) ||
379 $props['timeZone'];
380
381 if ($isUtcResponse) {
382 foreach (['available', 'occupied'] as $type) {
383 foreach ($freeSlots[$type] as $slotDate => $slotTimes) {
384 foreach ($freeSlots[$type][$slotDate] as $slotTime => $slotTimesProviders) {
385 $convertedSlotParts = explode(
386 ' ',
387 $props['timeZone'] ?
388 DateTimeService::getCustomDateTimeObjectInTimeZone(
389 $slotDate . ' ' . $slotTime,
390 $props['timeZone']
391 )->format('Y-m-d H:i') :
392 DateTimeService::getCustomDateTimeObjectInUtc(
393 $slotDate . ' ' . $slotTime
394 )->format('Y-m-d H:i')
395 );
396
397 $converted[$type][$convertedSlotParts[0]][$convertedSlotParts[1]] = $slotTimesProviders;
398 }
399 }
400 }
401 }
402
403 if (count($props['providerIds']) !== 1 && $settingsDS->getSetting('appointments', 'employeeSelection') === 'roundRobin') {
404 /** @var AppointmentRepository $appointmentRepository */
405 $appointmentRepository = $this->container->get('domain.booking.appointment.repository');
406
407 $lastBookedProviderId = $appointmentRepository->getLastBookedEmployee($props['providerIds']);
408 }
409
410 $resultData = [
411 'slots' => $converted['available'] ?: $freeSlots['available'],
412 'occupied' => $converted['occupied'] ?: $freeSlots['occupied'],
413 'minimum' => $isUtcResponse ?
414 $minimumDateTime->setTimezone(
415 new DateTimeZone('UTC')
416 )->format('Y-m-d H:i') : $minimumDateTime->format('Y-m-d H:i'),
417 'maximum' => $isUtcResponse ?
418 $maximumDateTime->setTimezone(
419 new DateTimeZone('UTC')
420 )->format('Y-m-d H:i') : $maximumDateTime->format('Y-m-d H:i'),
421 'busyness' => $busyness,
422 'lastBookedProviderId' => $lastBookedProviderId,
423 'appCount' => $freeSlots['appCount'],
424 'duration' => $freeSlots['duration'],
425 ];
426
427
428 $resultData = apply_filters('amelia_get_timeslots_filter', $resultData, $props);
429
430 do_action('amelia_get_timeslots', $resultData, $props);
431
432
433 $result->setResult(CommandResult::RESULT_SUCCESS);
434 $result->setMessage('Successfully retrieved free slots');
435 $result->setData(
436 [
437 'minimum' => $resultData['minimum'],
438 'maximum' => $resultData['maximum'],
439 'slots' => $resultData['slots'],
440 'occupied' => $resultData['occupied'],
441 'busyness' => $resultData['busyness'],
442 'lastProvider' => $resultData['lastBookedProviderId'],
443 'appCount' => $resultData['appCount'],
444 'duration' => $resultData['duration'],
445 ]
446 );
447
448 return $result;
449 }
450 }
451