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