PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.0.2
Booking for Appointments and Events Calendar – Amelia v2.0.2
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 / Services / TimeSlot / TimeSlotService.php
ameliabooking / src / Application / Services / TimeSlot Last commit date
TimeSlotService.php 6 months ago
TimeSlotService.php
520 lines
1 <?php
2
3 namespace AmeliaBooking\Application\Services\TimeSlot;
4
5 use AmeliaBooking\Application\Services\Booking\EventApplicationService;
6 use AmeliaBooking\Application\Services\Location\AbstractLocationApplicationService;
7 use AmeliaBooking\Application\Services\Resource\AbstractResourceApplicationService;
8 use AmeliaBooking\Application\Services\User\UserApplicationService;
9 use AmeliaBooking\Domain\Entity\Booking\Appointment\Appointment;
10 use AmeliaBooking\Domain\Entity\Booking\SlotsEntities;
11 use AmeliaBooking\Domain\Entity\User\Provider;
12 use AmeliaBooking\Domain\Factory\Booking\SlotsEntitiesFactory;
13 use AmeliaBooking\Domain\Services\Entity\EntityService;
14 use AmeliaBooking\Domain\Services\Resource\AbstractResourceService;
15 use AmeliaBooking\Domain\Collection\Collection;
16 use AmeliaBooking\Domain\Common\Exceptions\InvalidArgumentException;
17 use AmeliaBooking\Domain\Entity\Bookable\Service\Service;
18 use AmeliaBooking\Domain\Services\TimeSlot\TimeSlotService as DomainTimeSlotService;
19 use AmeliaBooking\Domain\Services\DateTime\DateTimeService;
20 use AmeliaBooking\Domain\Services\Settings\SettingsService;
21 use AmeliaBooking\Domain\ValueObjects\String\Status;
22 use AmeliaBooking\Infrastructure\Common\Container;
23 use AmeliaBooking\Infrastructure\Common\Exceptions\QueryExecutionException;
24 use AmeliaBooking\Infrastructure\Repository\Bookable\Service\ServiceRepository;
25 use AmeliaBooking\Infrastructure\Repository\Booking\Appointment\AppointmentRepository;
26 use AmeliaBooking\Infrastructure\Repository\User\ProviderRepository;
27 use AmeliaBooking\Infrastructure\Services\Apple\AbstractAppleCalendarService;
28 use AmeliaBooking\Infrastructure\Services\Google\AbstractGoogleCalendarService;
29 use AmeliaBooking\Infrastructure\Services\Outlook\AbstractOutlookCalendarService;
30 use DateTime;
31 use Exception;
32 use Interop\Container\Exception\ContainerException;
33 use Slim\Exception\ContainerValueNotFoundException;
34
35 /**
36 * Class TimeSlotService
37 *
38 * @package AmeliaBooking\Application\Services\TimeSlot
39 */
40 class TimeSlotService
41 {
42 /** @var Container $container */
43 private $container;
44
45 /**
46 * TimeSlotService constructor.
47 *
48 * @param Container $container
49 */
50 public function __construct(Container $container)
51 {
52 $this->container = $container;
53 }
54
55 /** @noinspection MoreThanThreeArgumentsInspection */
56 /**
57 * @param Service $service
58 * @param DateTime $requiredDateTime
59 * @param DateTime $minimumAppointmentDateTime
60 * @param DateTime $maximumAppointmentDateTime
61 * @param int $providerId
62 * @param int|null $locationId
63 * @param array $selectedExtras
64 * @param int|null $excludeAppointmentId
65 * @param int $personsCount
66 * @param boolean $isFrontEndBooking
67 *
68 * @return boolean
69 * @throws QueryExecutionException
70 * @throws ContainerValueNotFoundException
71 * @throws InvalidArgumentException
72 * @throws ContainerException
73 * @throws Exception
74 */
75 public function isSlotFree(
76 $service,
77 $requiredDateTime,
78 $minimumAppointmentDateTime,
79 $maximumAppointmentDateTime,
80 $providerId,
81 $locationId,
82 $selectedExtras,
83 $excludeAppointmentId,
84 $personsCount,
85 $isFrontEndBooking
86 ) {
87 $dateKey = $requiredDateTime->format('Y-m-d');
88 $timeKey = $requiredDateTime->format('H:i');
89
90 /** @var SettingsService $settingsDS */
91 $settingsDS = $this->container->get('domain.settings.service');
92
93 /** @var EntityService $entityService */
94 $entityService = $this->container->get('domain.entity.service');
95
96 $minimumBookingDateTime = $this->getMinimumDateTimeForBooking(
97 '',
98 $isFrontEndBooking,
99 $settingsDS
100 ->getEntitySettings($service->getSettings())
101 ->getGeneralSettings()
102 ->getMinimumTimeRequirementPriorToBooking()
103 );
104
105 if ($requiredDateTime < $minimumBookingDateTime) {
106 return false;
107 }
108
109 $searchStartDateTime = clone $requiredDateTime;
110
111 $searchStartDateTime->modify('first day of this month')->modify('-1 days');
112
113 $searchEndDateTime = clone $requiredDateTime;
114
115 $searchEndDateTime->modify('+1 days');
116
117 /** @var SlotsEntities $slotsEntities */
118 $slotsEntities = $this->getSlotsEntities(
119 [
120 'isFrontEndBooking' => $isFrontEndBooking,
121 'providerIds' => [$providerId],
122 ]
123 );
124
125 /** @var Service $slotEntitiesService */
126 foreach ($slotsEntities->getServices()->getItems() as $slotEntitiesService) {
127 if ($slotEntitiesService->getId()->getValue() === $service->getId()->getValue()) {
128 $slotEntitiesService->setDuration($service->getDuration());
129 break;
130 }
131 }
132
133 $settings = $this->getSlotsSettings($isFrontEndBooking, $slotsEntities);
134
135 $props = [
136 'startDateTime' => $searchStartDateTime,
137 'endDateTime' => $searchEndDateTime,
138 'minimumDateTime' => $minimumAppointmentDateTime,
139 'maximumDateTime' => $maximumAppointmentDateTime,
140 'serviceId' => $service->getId()->getValue(),
141 'providerIds' => [$providerId],
142 'locationId' => $locationId,
143 'extras' => $selectedExtras,
144 'excludeAppointmentId' => $excludeAppointmentId,
145 'personsCount' => $personsCount,
146 'isFrontEndBooking' => $isFrontEndBooking,
147 ];
148
149 /** @var SlotsEntities $filteredSlotEntities */
150 $filteredSlotEntities = $entityService->getFilteredSlotsEntities(
151 $settings,
152 $props,
153 $slotsEntities
154 );
155
156 $freeSlots = $this->getSlotsByProps(
157 $settings,
158 $props,
159 $filteredSlotEntities
160 );
161
162 return
163 array_key_exists($dateKey, $freeSlots['available']) &&
164 array_key_exists($timeKey, $freeSlots['available'][$dateKey]);
165 }
166
167 /**
168 * @param string $requiredBookingDateTimeString
169 * @param boolean $isFrontEndBooking
170 * @param string $minimumTime
171 *
172 * @return DateTime
173 * @throws Exception
174 */
175 public function getMinimumDateTimeForBooking($requiredBookingDateTimeString, $isFrontEndBooking, $minimumTime)
176 {
177 $requiredTimeOffset = $isFrontEndBooking ? $minimumTime : 0;
178
179 $minimumBookingDateTime = DateTimeService::getNowDateTimeObject()->modify("+{$requiredTimeOffset} seconds");
180
181 $requiredBookingDateTime = DateTimeService::getCustomDateTimeObject($requiredBookingDateTimeString);
182
183 $minimumDateTime = ($minimumBookingDateTime > $requiredBookingDateTime ||
184 $minimumBookingDateTime->format('Y-m-d') === $requiredBookingDateTime->format('Y-m-d')
185 ) ? $minimumBookingDateTime : $requiredBookingDateTime->setTime(0, 0, 0);
186
187 /** @var SettingsService $settingsDS */
188 $settingsDS = $this->container->get('domain.settings.service');
189
190 $pastAvailableDays = $settingsDS->getSetting('general', 'backendSlotsDaysInPast');
191
192 if (!$isFrontEndBooking && $pastAvailableDays) {
193 $minimumDateTime->modify("-{$pastAvailableDays} days");
194 }
195
196 return $minimumDateTime;
197 }
198
199 /**
200 * @param string $requiredBookingDateTimeString
201 * @param boolean $isFrontEndBooking
202 * @param int $maximumTime
203 *
204 * @return DateTime
205 * @throws Exception
206 */
207 public function getMaximumDateTimeForBooking($requiredBookingDateTimeString, $isFrontEndBooking, $maximumTime)
208 {
209 /** @var SettingsService $settingsDS */
210 $settingsDS = $this->container->get('domain.settings.service');
211
212 $futureAvailableDays = $settingsDS->getSetting('general', 'backendSlotsDaysInFuture');
213
214 $days = $maximumTime > $futureAvailableDays ?
215 $maximumTime :
216 $futureAvailableDays;
217
218 $daysAvailableForBooking = $isFrontEndBooking ? $maximumTime : $days;
219
220 $maximumBookingDateTime = DateTimeService::getNowDateTimeObject()->modify("+{$daysAvailableForBooking} day");
221
222 $requiredBookingDateTime = $requiredBookingDateTimeString ?
223 DateTimeService::getCustomDateTimeObject($requiredBookingDateTimeString) : $maximumBookingDateTime;
224
225 return ($maximumBookingDateTime < $requiredBookingDateTime ||
226 $maximumBookingDateTime->format('Y-m-d') === $requiredBookingDateTime->format('Y-m-d')
227 ) ? $maximumBookingDateTime : $requiredBookingDateTime;
228 }
229
230 /**
231 * get provider id values for appointments fetch needed for slot calculation
232 *
233 * @param Collection $providers
234 * @param Collection $resources
235 *
236 * @return array
237 */
238 private function getAppointmentsProvidersIds($providers, $resources)
239 {
240 /** @var AbstractResourceService $resourceService */
241 $resourceService = $this->container->get('domain.resource.service');
242
243 if ($resources->length()) {
244 $resourcesProvidersIds = $resourceService->getResourcesProvidersIds($resources);
245
246 return $resourcesProvidersIds ? array_unique(array_merge($providers->keys(), $resourcesProvidersIds)) : [];
247 }
248
249 return $providers->keys();
250 }
251
252 /**
253 * add busy appointments to providers from google calendar events, outlook calendar events and amelia events
254 *
255 * @param Collection $providers
256 * @param array $props
257 *
258 * @throws ContainerException
259 * @throws Exception
260 */
261 public function setBlockerAppointments($providers, $props)
262 {
263 /** @var AbstractGoogleCalendarService $googleCalendarService */
264 $googleCalendarService = $this->container->get('infrastructure.google.calendar.service');
265
266 /** @var AbstractOutlookCalendarService $outlookCalendarService */
267 $outlookCalendarService = $this->container->get('infrastructure.outlook.calendar.service');
268
269 /** @var AbstractAppleCalendarService $appleCalendarService */
270 $appleCalendarService = $this->container->get('infrastructure.apple.calendar.service');
271
272 /** @var EventApplicationService $eventApplicationService */
273 $eventApplicationService = $this->container->get('application.booking.event.service');
274
275 try {
276 $googleCalendarService->removeSlotsFromGoogleCalendar(
277 $providers,
278 $props['excludeAppointmentId'],
279 !empty($props['minimumDateTime']) ? $props['minimumDateTime'] : $props['startDateTime'],
280 !empty($props['maximumDateTime']) ? $props['maximumDateTime'] : $props['endDateTime']
281 );
282 } catch (Exception $e) {
283 }
284
285 try {
286 $outlookCalendarService->removeSlotsFromOutlookCalendar(
287 $providers,
288 $props['excludeAppointmentId'],
289 !empty($props['minimumDateTime']) ? $props['minimumDateTime'] : $props['startDateTime'],
290 !empty($props['maximumDateTime']) ? $props['maximumDateTime'] : $props['endDateTime']
291 );
292 } catch (Exception $e) {
293 }
294
295 try {
296 $appleCalendarService->removeSlotsFromAppleCalendar(
297 $providers,
298 $props['excludeAppointmentId'],
299 !empty($props['minimumDateTime']) ? $props['minimumDateTime'] : $props['startDateTime'],
300 !empty($props['maximumDateTime']) ? $props['maximumDateTime'] : $props['endDateTime']
301 );
302 } catch (Exception $e) {
303 }
304
305 $eventApplicationService->removeSlotsFromEvents(
306 $providers,
307 [
308 DateTimeService::getCustomDateTimeObject($props['startDateTime']->format('Y-m-d H:i:s'))
309 ->modify('-10 day')
310 ->format('Y-m-d H:i:s'),
311 DateTimeService::getCustomDateTimeObject($props['startDateTime']->format('Y-m-d H:i:s'))
312 ->modify('+2 years')
313 ->format('Y-m-d H:i:s')
314 ]
315 );
316 }
317
318 /**
319 * add busy appointments to providers from google calendar events, outlook calendar events and amelia events
320 *
321 * @param array $props
322 * @param SlotsEntities $slotsEntities
323 *
324 * @return Collection
325 * @throws Exception
326 */
327 public function getBookedAppointments($slotsEntities, $props)
328 {
329 /** @var AppointmentRepository $appointmentRepository */
330 $appointmentRepository = $this->container->get('domain.booking.appointment.repository');
331
332 /** @var Collection $appointments */
333 $appointments = new Collection();
334
335 $startDateTime = DateTimeService::getCustomDateTimeObjectInUtc($props['startDateTime']->format('Y-m-d H:i:s'))
336 ->format('Y-m-d H:i:s');
337
338 if ($props['startDateTime']->format('Y-m-d') == DateTimeService::getNowDateTimeObjectInUtc()->format('Y-m-d')) {
339 $startDateTime = DateTimeService::getCustomDateTimeObjectInUtc($props['startDateTime']->format('Y-m-d H:i:s'))
340 ->setTime(0, 0, 0)
341 ->format('Y-m-d H:i:s');
342 }
343
344 $appointmentRepository->getFutureAppointments(
345 $appointments,
346 $this->getAppointmentsProvidersIds(
347 $slotsEntities->getProviders(),
348 $slotsEntities->getResources()
349 ),
350 $startDateTime,
351 DateTimeService::getCustomDateTimeObjectInUtc($props['endDateTime']->format('Y-m-d H:i:s'))
352 ->modify('+1 day')
353 ->format('Y-m-d H:i:s')
354 );
355
356 return $appointments;
357 }
358
359 /**
360 * get slot settings
361 *
362 * @param bool $isFrontEndBooking
363 * @param SlotsEntities $slotsEntities
364 * @param array $customSettings
365 *
366 * @return array
367 * @throws ContainerException
368 */
369 public function getSlotsSettings($isFrontEndBooking, $slotsEntities, $customSettings = [])
370 {
371 /** @var SettingsService $settingsDomainService */
372 $settingsDomainService = $this->container->get('domain.settings.service');
373
374 /** @var UserApplicationService $userApplicationService */
375 $userApplicationService = $this->container->get('application.user.service');
376
377 /** @var \AmeliaBooking\Application\Services\Settings\SettingsService $settingsApplicationService */
378 $settingsApplicationService = $this->container->get('application.settings.service');
379
380 return [
381 'allowAdminBookAtAnyTime' =>
382 isset($customSettings['allowAdminBookAtAnyTime'])
383 ? filter_var($customSettings['allowAdminBookAtAnyTime'], FILTER_VALIDATE_BOOLEAN) :
384 (!$isFrontEndBooking && $userApplicationService->isAdminAndAllowedToBookAtAnyTime()),
385 'allowAdminBookOverApp' => isset($customSettings['allowAdminBookOverApp']) ?
386 filter_var($customSettings['allowAdminBookOverApp'], FILTER_VALIDATE_BOOLEAN) : (
387 !$isFrontEndBooking &&
388 $userApplicationService->isAdminAndAllowedToBookOver()
389 ),
390 'isGloballyBusySlot' =>
391 $settingsDomainService->getSetting('appointments', 'isGloballyBusySlot') &&
392 !$slotsEntities->getResources()->length(),
393 'allowBookingIfPending' =>
394 isset($customSettings['allowBookingIfPending']) ? filter_var($customSettings['allowBookingIfPending'], FILTER_VALIDATE_BOOLEAN) :
395 $settingsDomainService->getSetting('appointments', 'allowBookingIfPending'),
396 'allowBookingIfNotMin' =>
397 isset($customSettings['allowBookingIfNotMin']) ? filter_var($customSettings['allowBookingIfNotMin'], FILTER_VALIDATE_BOOLEAN) :
398 $settingsDomainService->getSetting('appointments', 'allowBookingIfNotMin'),
399 'openedBookingAfterMin' =>
400 $settingsDomainService->getSetting('appointments', 'openedBookingAfterMin'),
401 'timeSlotLength' => isset($customSettings['timeSlotLength']) ? (int)$customSettings['timeSlotLength'] :
402 $settingsDomainService->getSetting('general', 'timeSlotLength'),
403 'serviceDurationAsSlot' =>
404 isset($customSettings['serviceDurationAsSlot']) ? filter_var($customSettings['serviceDurationAsSlot'], FILTER_VALIDATE_BOOLEAN) :
405 $settingsDomainService->getSetting('general', 'serviceDurationAsSlot'),
406 'bufferTimeInSlot' =>
407 isset($customSettings['bufferTimeInSlot']) ? filter_var($customSettings['bufferTimeInSlot'], FILTER_VALIDATE_BOOLEAN) :
408 $settingsDomainService->getSetting('general', 'bufferTimeInSlot'),
409 'globalDaysOff' => $settingsApplicationService->getGlobalDaysOff(),
410 'adminServiceDurationAsSlot' => !$isFrontEndBooking &&
411 $userApplicationService->isAdminAndAllowedToBookAtAnyTime() &&
412 $settingsDomainService->getSetting('roles', 'adminServiceDurationAsSlot'),
413 'limitPerEmployee' => $settingsDomainService->getSetting('roles', 'limitPerEmployee'),
414 'timezonesFeatureEnabled' => $settingsDomainService->getSetting('featuresIntegrations', 'timezones')['enabled'],
415 ];
416 }
417
418 /**
419 * @param array $props
420 *
421 * @return SlotsEntities
422 * @throws InvalidArgumentException
423 * @throws QueryExecutionException
424 */
425 public function getSlotsEntities($props)
426 {
427 /** @var SettingsService $settingsDS */
428 $settingsDS = $this->container->get('domain.settings.service');
429
430 /** @var ServiceRepository $serviceRepository */
431 $serviceRepository = $this->container->get('domain.bookable.service.repository');
432
433 /** @var ProviderRepository $providerRepository */
434 $providerRepository = $this->container->get('domain.users.providers.repository');
435
436 /** @var AbstractResourceApplicationService $resourceApplicationService */
437 $resourceApplicationService = $this->container->get('application.resource.service');
438
439 /** @var AbstractLocationApplicationService $locationAS */
440 $locationAS = $this->container->get('application.location.service');
441
442 /** @var Collection $services */
443 $services = $serviceRepository->getWithExtras(
444 !empty($props['serviceCriteria']) ? $props['serviceCriteria'] : []
445 );
446
447 /** @var Collection $providers */
448 $providers = $providerRepository->getWithSchedule(
449 array_merge(
450 [
451 'dates' => [
452 DateTimeService::getNowDateTimeObject()->modify('-1 days')->format('Y-m-d H:i:s')
453 ],
454 'providers' => $props['providerIds'],
455 'fetchCalendars' => true,
456 ],
457 $props['isFrontEndBooking'] ? ['providerStatus' => Status::VISIBLE, 'show' => 1] : []
458 )
459 );
460
461 /** @var Collection $locations */
462 $locations = $locationAS->getAllIndexedById();
463
464 /** @var Collection $resources */
465 $resources = $settingsDS->isFeatureEnabled('resources')
466 ? $resourceApplicationService->getAll(['status' => Status::VISIBLE])
467 : new Collection();
468
469 /** @var SlotsEntities $slotEntities */
470 $slotEntities = SlotsEntitiesFactory::create();
471
472 $slotEntities->setServices($services);
473
474 $slotEntities->setProviders($providers);
475
476 $slotEntities->setLocations($locations);
477
478 $slotEntities->setResources($resources);
479
480 return $slotEntities;
481 }
482
483 /**
484 * @param array $settings
485 * @param array $props
486 * @param SlotsEntities $slotsEntities
487 *
488 * @return array
489 * @throws ContainerException
490 * @throws Exception
491 */
492 public function getSlotsByProps($settings, $props, $slotsEntities)
493 {
494 /** @var DomainTimeSlotService $timeSlotService */
495 $timeSlotService = $this->container->get('domain.timeSlot.service');
496
497 /** @var SettingsService $settingsService */
498 $settingsService = $this->container->get('domain.settings.service');
499
500 $settings['defaultAppointmentStatus'] = $settingsService
501 ->getEntitySettings($slotsEntities->getServices()->getItem($props['serviceId'])->getSettings())
502 ->getGeneralSettings()
503 ->getDefaultAppointmentStatus();
504
505 /** @var Provider $provider */
506 foreach ($slotsEntities->getProviders()->getItems() as $provider) {
507 $provider->setAppointmentList(new Collection());
508 }
509
510 $this->setBlockerAppointments($slotsEntities->getProviders(), $props);
511
512 return $timeSlotService->getSlots(
513 $settings,
514 $props,
515 $slotsEntities,
516 $this->getBookedAppointments($slotsEntities, $props)
517 );
518 }
519 }
520