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