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