PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 1.2.31
Booking for Appointments and Events Calendar – Amelia v1.2.31
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 / Services / TimeSlot / TimeSlotService.php
ameliabooking / src / Application / Services / TimeSlot Last commit date
TimeSlotService.php 1 year ago
TimeSlotService.php
515 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('-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']) ? filter_var($customSettings['allowAdminBookAtAnyTime'], FILTER_VALIDATE_BOOLEAN) :
383 (!$isFrontEndBooking &&
384 $userApplicationService->isAdminAndAllowedToBookAtAnyTime()),
385 'allowAdminBookOverApp' => isset($customSettings['allowAdminBookOverApp']) ?
386 filter_var($customSettings['allowAdminBookOverApp'], FILTER_VALIDATE_BOOLEAN) :
387 (
388 !$isFrontEndBooking &&
389 $userApplicationService->isAdminAndAllowedToBookOver()
390 ),
391 'isGloballyBusySlot' =>
392 $settingsDomainService->getSetting('appointments', 'isGloballyBusySlot') &&
393 !$slotsEntities->getResources()->length(),
394 'allowBookingIfPending' =>
395 isset($customSettings['allowBookingIfPending']) ? filter_var($customSettings['allowBookingIfPending'], FILTER_VALIDATE_BOOLEAN) :
396 $settingsDomainService->getSetting('appointments', 'allowBookingIfPending'),
397 'allowBookingIfNotMin' =>
398 isset($customSettings['allowBookingIfNotMin']) ? filter_var($customSettings['allowBookingIfNotMin'], FILTER_VALIDATE_BOOLEAN) :
399 $settingsDomainService->getSetting('appointments', 'allowBookingIfNotMin'),
400 'openedBookingAfterMin' =>
401 $settingsDomainService->getSetting('appointments', 'openedBookingAfterMin'),
402 'timeSlotLength' => isset($customSettings['timeSlotLength']) ? (int)$customSettings['timeSlotLength'] :
403 $settingsDomainService->getSetting('general', 'timeSlotLength'),
404 'serviceDurationAsSlot' =>
405 isset($customSettings['serviceDurationAsSlot']) ? filter_var($customSettings['serviceDurationAsSlot'], FILTER_VALIDATE_BOOLEAN) :
406 $settingsDomainService->getSetting('general', 'serviceDurationAsSlot'),
407 'bufferTimeInSlot' =>
408 isset($customSettings['bufferTimeInSlot']) ? filter_var($customSettings['bufferTimeInSlot'], FILTER_VALIDATE_BOOLEAN) :
409 $settingsDomainService->getSetting('general', 'bufferTimeInSlot'),
410 'globalDaysOff' => $settingsApplicationService->getGlobalDaysOff(),
411 'adminServiceDurationAsSlot' => !$isFrontEndBooking &&
412 $userApplicationService->isAdminAndAllowedToBookAtAnyTime() &&
413 $settingsDomainService->getSetting('roles', 'adminServiceDurationAsSlot'),
414 'limitPerEmployee' => $settingsDomainService->getSetting('roles', 'limitPerEmployee'),
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 ServiceRepository $serviceRepository */
428 $serviceRepository = $this->container->get('domain.bookable.service.repository');
429
430 /** @var ProviderRepository $providerRepository */
431 $providerRepository = $this->container->get('domain.users.providers.repository');
432
433 /** @var AbstractResourceApplicationService $resourceApplicationService */
434 $resourceApplicationService = $this->container->get('application.resource.service');
435
436 /** @var AbstractLocationApplicationService $locationAS */
437 $locationAS = $this->container->get('application.location.service');
438
439 /** @var Collection $services */
440 $services = $serviceRepository->getWithExtras(
441 !empty($props['serviceCriteria']) ? $props['serviceCriteria'] : []
442 );
443
444 /** @var Collection $providers */
445 $providers = $providerRepository->getWithSchedule(
446 array_merge(
447 [
448 'dates' => [
449 DateTimeService::getNowDateTimeObject()->modify('-1 days')->format('Y-m-d H:i:s')
450 ],
451 'providers' => $props['providerIds'],
452 'fetchCalendars' => true,
453 ],
454 $props['isFrontEndBooking'] ? ['providerStatus' => Status::VISIBLE] : []
455 )
456 );
457
458 /** @var Collection $locations */
459 $locations = $locationAS->getAllIndexedById();
460
461 /** @var Collection $resources */
462 $resources = $resourceApplicationService->getAll(['status' => Status::VISIBLE]);
463
464 /** @var SlotsEntities $slotEntities */
465 $slotEntities = SlotsEntitiesFactory::create();
466
467 $slotEntities->setServices($services);
468
469 $slotEntities->setProviders($providers);
470
471 $slotEntities->setLocations($locations);
472
473 $slotEntities->setResources($resources);
474
475 return $slotEntities;
476 }
477
478 /**
479 * @param array $settings
480 * @param array $props
481 * @param SlotsEntities $slotsEntities
482 *
483 * @return array
484 * @throws ContainerException
485 * @throws Exception
486 */
487 public function getSlotsByProps($settings, $props, $slotsEntities)
488 {
489 /** @var DomainTimeSlotService $timeSlotService */
490 $timeSlotService = $this->container->get('domain.timeSlot.service');
491
492 /** @var SettingsService $settingsService */
493 $settingsService = $this->container->get('domain.settings.service');
494
495 $settings['defaultAppointmentStatus'] = $settingsService
496 ->getEntitySettings($slotsEntities->getServices()->getItem($props['serviceId'])->getSettings())
497 ->getGeneralSettings()
498 ->getDefaultAppointmentStatus();
499
500 /** @var Provider $provider */
501 foreach ($slotsEntities->getProviders()->getItems() as $provider) {
502 $provider->setAppointmentList(new Collection());
503 }
504
505 $this->setBlockerAppointments($slotsEntities->getProviders(), $props);
506
507 return $timeSlotService->getSlots(
508 $settings,
509 $props,
510 $slotsEntities,
511 $this->getBookedAppointments($slotsEntities, $props)
512 );
513 }
514 }
515