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 / Commands / Booking / Appointment / GetTimeSlotsCommandHandler.php
ameliabooking / src / Application / Commands / Booking / Appointment Last commit date
AddAppointmentCommand.php 1 year ago AddAppointmentCommandHandler.php 6 months ago AddBookingCommand.php 1 year ago AddBookingCommandHandler.php 1 year ago ApproveBookingRemotelyCommand.php 1 year ago ApproveBookingRemotelyCommandHandler.php 6 months ago CancelBookingCommand.php 1 year ago CancelBookingCommandHandler.php 6 months ago CancelBookingRemotelyCommand.php 1 year ago CancelBookingRemotelyCommandHandler.php 6 months ago DeleteAppointmentCommand.php 1 year ago DeleteAppointmentCommandHandler.php 1 year ago DeleteBookingCommand.php 1 year ago DeleteBookingCommandHandler.php 10 months ago DeleteBookingRemotelyCommand.php 10 months ago DeleteBookingRemotelyCommandHandler.php 6 months ago GetAppointmentBookingsCommand.php 6 months ago GetAppointmentBookingsCommandHandler.php 6 months ago GetAppointmentCommand.php 7 years ago GetAppointmentCommandHandler.php 5 months ago GetAppointmentsCommand.php 1 year ago GetAppointmentsCommandHandler.php 6 months ago GetIcsCommand.php 6 months ago GetIcsCommandHandler.php 4 years ago GetPackageAppointmentsCommand.php 1 year ago GetPackageAppointmentsCommandHandler.php 6 months ago GetTimeSlotsCommand.php 1 year ago GetTimeSlotsCommandHandler.php 10 months ago ReassignBookingCommand.php 1 year ago ReassignBookingCommandHandler.php 10 months ago RejectBookingRemotelyCommand.php 1 year ago RejectBookingRemotelyCommandHandler.php 6 months ago SuccessfulBookingCommand.php 1 year ago SuccessfulBookingCommandHandler.php 1 year ago UpdateAppointmentCommand.php 1 year ago UpdateAppointmentCommandHandler.php 6 months ago UpdateAppointmentStatusCommand.php 1 year ago UpdateAppointmentStatusCommandHandler.php 5 months ago UpdateAppointmentTimeCommand.php 1 year ago UpdateAppointmentTimeCommandHandler.php 10 months ago UpdateBookingStatusCommand.php 1 year ago UpdateBookingStatusCommandHandler.php 5 months ago
GetTimeSlotsCommandHandler.php
427 lines
1 <?php
2
3 namespace AmeliaBooking\Application\Commands\Booking\Appointment;
4
5 use AmeliaBooking\Application\Commands\CommandHandler;
6 use AmeliaBooking\Application\Commands\CommandResult;
7 use AmeliaBooking\Application\Services\TimeSlot\TimeSlotService as ApplicationTimeSlotService;
8 use AmeliaBooking\Domain\Entity\User\Provider;
9 use AmeliaBooking\Domain\Services\Entity\EntityService;
10 use AmeliaBooking\Domain\Common\Exceptions\InvalidArgumentException;
11 use AmeliaBooking\Domain\Entity\Bookable\Service\Service;
12 use AmeliaBooking\Domain\Entity\Booking\SlotsEntities;
13 use AmeliaBooking\Domain\Services\DateTime\DateTimeService;
14 use AmeliaBooking\Domain\Services\Settings\SettingsService;
15 use AmeliaBooking\Domain\ValueObjects\PositiveDuration;
16 use AmeliaBooking\Infrastructure\Common\Exceptions\QueryExecutionException;
17 use AmeliaBooking\Infrastructure\Repository\Booking\Appointment\AppointmentRepository;
18 use AmeliaBooking\Infrastructure\Services\Apple\AbstractAppleCalendarService;
19 use AmeliaBooking\Infrastructure\Services\Google\AbstractGoogleCalendarService;
20 use AmeliaBooking\Infrastructure\Services\Outlook\AbstractOutlookCalendarService;
21 use DateTimeZone;
22 use Exception;
23 use Interop\Container\Exception\ContainerException;
24 use Slim\Exception\ContainerValueNotFoundException;
25
26 /**
27 * Class GetTimeSlotsCommandHandler
28 *
29 * @package AmeliaBooking\Application\Commands\Booking\Appointment
30 */
31 class GetTimeSlotsCommandHandler extends CommandHandler
32 {
33 /**
34 * @var array
35 */
36 public $mandatoryFields = [
37 'serviceId'
38 ];
39
40 /**
41 * @param GetTimeSlotsCommand $command
42 *
43 * @return CommandResult
44 * @throws ContainerValueNotFoundException
45 * @throws ContainerException
46 * @throws Exception
47 * @throws InvalidArgumentException
48 * @throws QueryExecutionException
49 */
50 public function handle(GetTimeSlotsCommand $command)
51 {
52 $result = new CommandResult();
53
54 $this->checkMandatoryFields($command);
55
56 /** @var EntityService $entityService */
57 $entityService = $this->container->get('domain.entity.service');
58
59 /** @var ApplicationTimeSlotService $applicationTimeSlotService */
60 $applicationTimeSlotService = $this->container->get('application.timeSlot.service');
61
62 /** @var SettingsService $settingsDS */
63 $settingsDS = $this->container->get('domain.settings.service');
64
65 $props = [
66 'serviceId' => $command->getField('serviceId'),
67 'providerIds' => $command->getField('providerIds'),
68 'locationId' => $command->getField('locationId'),
69 'locationIds' => $command->getField('locationIds'),
70 'extras' => $command->getField('extras'),
71 'excludeAppointmentId' => $command->getField('excludeAppointmentId'),
72 'personsCount' => $command->getField('group') ? $command->getField('persons') : null,
73 'isFrontEndBooking' => $command->getField('page') === 'booking' || $command->getField('page') === 'cabinet',
74 'totalPersons' => $command->getField('persons'),
75 'serviceDuration' => $command->getField('serviceDuration'),
76 'monthsLoad' => $command->getField('monthsLoad'),
77 'startDateTime' => $command->getField('startDateTime'),
78 'endDateTime' => $command->getField('endDateTime'),
79 'queryTimeZone' => $command->getField('queryTimeZone'),
80 'timeZone' => $command->getField('timeZone'),
81 'structured' => $command->getField('structured'),
82
83 ];
84
85 $props = apply_filters('amelia_before_get_timeslots_filter', $props);
86
87 do_action('amelia_before_get_timeslots', $props);
88
89 $isFrontEndBooking = $props['isFrontEndBooking'];
90
91 /** @var SlotsEntities $slotsEntities */
92 $slotsEntities = $applicationTimeSlotService->getSlotsEntities(
93 [
94 'isFrontEndBooking' => $isFrontEndBooking,
95 'providerIds' => $props['providerIds'],
96 ]
97 );
98
99 $settings = $applicationTimeSlotService->getSlotsSettings($isFrontEndBooking, $slotsEntities, $props);
100
101 $lastBookedProviderId = null;
102
103 /** @var SlotsEntities $filteredSlotEntities */
104 $filteredSlotEntities = $entityService->getFilteredSlotsEntities(
105 $settings,
106 $props,
107 $slotsEntities
108 );
109
110 /** @var Service $service */
111 $service = $filteredSlotEntities->getServices()->getItem($props['serviceId']);
112
113 if ($props['serviceDuration']) {
114 $service->setDuration(new PositiveDuration($props['serviceDuration']));
115 }
116
117 $minimumBookingTimeInSeconds = $settingsDS
118 ->getEntitySettings($service->getSettings())
119 ->getGeneralSettings()
120 ->getMinimumTimeRequirementPriorToBooking();
121
122 $maximumBookingTimeInDays = $settingsDS
123 ->getEntitySettings($service->getSettings())
124 ->getGeneralSettings()
125 ->getNumberOfDaysAvailableForBooking();
126
127 $monthsLoad = $props['monthsLoad'];
128
129 $loadGeneratedPeriod = $monthsLoad &&
130 !$props['endDateTime'];
131
132 $timeZone = $props['queryTimeZone'] ?: DateTimeService::getTimeZone()->getName();
133
134 $queryStartDateTime = $props['startDateTime'] ?
135 DateTimeService::getDateTimeObjectInTimeZone(
136 $props['startDateTime'],
137 $timeZone
138 )->setTimezone(DateTimeService::getTimeZone()) : null;
139
140 $queryEndDateTime = $props['endDateTime'] ?
141 DateTimeService::getDateTimeObjectInTimeZone(
142 $props['endDateTime'],
143 $timeZone
144 )->setTimezone(DateTimeService::getTimeZone()) : null;
145
146 $minimumDateTime = $applicationTimeSlotService->getMinimumDateTimeForBooking(
147 null,
148 $isFrontEndBooking,
149 $minimumBookingTimeInSeconds
150 );
151
152 $startDateTime = $queryStartDateTime ?:
153 $applicationTimeSlotService->getMinimumDateTimeForBooking(
154 null,
155 $isFrontEndBooking,
156 $minimumBookingTimeInSeconds
157 );
158
159 $endDateTime = $queryEndDateTime ?:
160 $applicationTimeSlotService->getMaximumDateTimeForBooking(
161 null,
162 $isFrontEndBooking,
163 $maximumBookingTimeInDays
164 );
165
166 $maximumDateTime = $applicationTimeSlotService->getMaximumDateTimeForBooking(
167 null,
168 $isFrontEndBooking,
169 $maximumBookingTimeInDays
170 );
171
172 if ($isFrontEndBooking) {
173 $startDateTime = $startDateTime < $minimumDateTime ? $minimumDateTime : $startDateTime;
174
175 $endDateTime = $endDateTime > $maximumDateTime ? $maximumDateTime : $endDateTime;
176 }
177
178 // set initial search period if query dates are not set
179 if ($loadGeneratedPeriod) {
180 $endDateTime = DateTimeService::getCustomDateTimeObject(
181 $startDateTime->format('Y-m-d H:i:s')
182 )->setTimezone(
183 new DateTimeZone($timeZone)
184 );
185
186 $endDateTime->modify('first day of this month');
187
188 $endDateTime->modify('+' . ($monthsLoad - 1) . 'months');
189
190 $endDateTime->modify('last day of this month');
191
192 $endDateTime->modify('+12days');
193
194 $endDateTime->setTime(23, 59, 59);
195
196 if ($isFrontEndBooking) {
197 $endDateTime = $endDateTime > $maximumDateTime ?
198 DateTimeService::getDateTimeObjectInTimeZone(
199 $maximumDateTime->format('Y-m-d H:i'),
200 $timeZone
201 ) : $endDateTime;
202 }
203
204 $endDateTime->setTimezone(DateTimeService::getTimeZone());
205 }
206
207 /** @var Service $filteredSlotEntitiesService */
208 foreach ($filteredSlotEntities->getServices()->getItems() as $filteredSlotEntitiesService) {
209 if ($filteredSlotEntitiesService->getId()->getValue() === $service->getId()->getValue()) {
210 $filteredSlotEntitiesService->setDuration($service->getDuration());
211
212 break;
213 }
214 }
215
216 /** @var Provider $filteredSlotEntitiesProvider */
217 foreach ($filteredSlotEntities->getProviders()->getItems() as $filteredSlotEntitiesProvider) {
218 /** @var Service $providerService */
219 foreach ($filteredSlotEntitiesProvider->getServiceList()->getItems() as $providerService) {
220 if ($providerService->getId()->getValue() === $service->getId()->getValue()) {
221 $providerService->setDuration($service->getDuration());
222
223 break;
224 }
225 }
226 }
227
228 $freeSlots = $applicationTimeSlotService->getSlotsByProps(
229 $settings,
230 array_merge(
231 $props,
232 [
233 'startDateTime' => $startDateTime,
234 'endDateTime' => $endDateTime,
235 ]
236 ),
237 $filteredSlotEntities
238 );
239
240 if ($loadGeneratedPeriod) {
241 // search with new period until slots are not found
242 while (!$freeSlots['available'] && $endDateTime && $endDateTime <= $maximumDateTime) {
243 $startDateTime = DateTimeService::getCustomDateTimeObject(
244 $endDateTime->format('Y-m-d H:i:s')
245 )->setTimezone(
246 new DateTimeZone($timeZone)
247 );
248
249 $startDateTime->setTime(0, 0, 0);
250
251 $endDateTime->modify('first day of this month');
252
253 $endDateTime->modify('+' . ($monthsLoad - 1) . 'months');
254
255 $endDateTime->modify('last day of this month');
256
257 $endDateTime->modify('+12days');
258
259 $endDateTime->setTime(23, 59, 59);
260
261 if ($isFrontEndBooking) {
262 $endDateTime = $endDateTime > $maximumDateTime ?
263 DateTimeService::getDateTimeObjectInTimeZone(
264 $maximumDateTime->format('Y-m-d H:i'),
265 $timeZone
266 ) : $endDateTime;
267 }
268
269 $endDateTime->setTimezone(DateTimeService::getTimeZone());
270
271 AbstractGoogleCalendarService::$providersGoogleEvents = [];
272
273 AbstractOutlookCalendarService::$providersOutlookEvents = [];
274
275 AbstractAppleCalendarService::$providersAppleEvents = [];
276
277 $freeSlots = $applicationTimeSlotService->getSlotsByProps(
278 $settings,
279 array_merge(
280 $props,
281 [
282 'startDateTime' => $startDateTime,
283 'endDateTime' => $endDateTime,
284 ]
285 ),
286 $filteredSlotEntities
287 );
288
289 $endDateTimeCopy = clone $endDateTime;
290
291 $maximumDateTimeCopy = clone $maximumDateTime;
292
293 if (
294 $endDateTimeCopy->format('Y-m-d H:i') === $maximumDateTimeCopy->format('Y-m-d H:i') ||
295 ($endDateTimeCopy->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i') ===
296 $maximumDateTimeCopy->setTimezone(new DateTimeZone('UTC'))->format('Y-m-d H:i')) ||
297 ($endDateTimeCopy > $maximumDateTimeCopy)
298 ) {
299 break;
300 }
301 }
302
303 // search once more if first available date is in 11 days added to endDateTime (days outside calendar on frontend form)
304 foreach (array_slice($freeSlots['available'], 0, 1, true) as $slotDate => $slotTimes) {
305 if (substr($slotDate, 0, 7) === $endDateTime->format('Y-m')) {
306 $endDateTime->modify('last day of this month');
307
308 $endDateTime->modify('+12days');
309
310 $endDateTime->setTime(23, 59, 59);
311
312 if ($isFrontEndBooking) {
313 $endDateTime = $endDateTime > $maximumDateTime ?
314 DateTimeService::getDateTimeObjectInTimeZone(
315 $maximumDateTime->format('Y-m-d H:i'),
316 $timeZone
317 ) : $endDateTime;
318 }
319
320 AbstractGoogleCalendarService::$providersGoogleEvents = [];
321
322 AbstractOutlookCalendarService::$providersOutlookEvents = [];
323
324 AbstractAppleCalendarService::$providersAppleEvents = [];
325
326 $freeSlots = $applicationTimeSlotService->getSlotsByProps(
327 $settings,
328 array_merge(
329 $props,
330 [
331 'startDateTime' => $startDateTime,
332 'endDateTime' => $endDateTime,
333 ]
334 ),
335 $filteredSlotEntities
336 );
337 }
338 }
339 }
340
341 $busyness = [];
342
343 foreach ($freeSlots['available'] as $slotDate => $slotTimes) {
344 $busyness[$slotDate] = round(
345 count(!empty($freeSlots['occupied'][$slotDate]) ? $freeSlots['occupied'][$slotDate] : []) /
346 (count(!empty($freeSlots['available'][$slotDate]) ? $freeSlots['available'][$slotDate] : []) +
347 count(!empty($freeSlots['occupied'][$slotDate]) ? $freeSlots['occupied'][$slotDate] : []))
348 * 100
349 );
350 }
351
352 $converted = ['available' => [], 'occupied' => []];
353
354 $isUtcResponse = ($settingsDS->getSetting('general', 'showClientTimeZone') && $isFrontEndBooking) ||
355 $props['timeZone'];
356
357 if ($isUtcResponse) {
358 foreach (['available', 'occupied'] as $type) {
359 foreach ($freeSlots[$type] as $slotDate => $slotTimes) {
360 foreach ($freeSlots[$type][$slotDate] as $slotTime => $slotTimesProviders) {
361 $convertedSlotParts = explode(
362 ' ',
363 $props['timeZone'] ?
364 DateTimeService::getCustomDateTimeObjectInTimeZone(
365 $slotDate . ' ' . $slotTime,
366 $props['timeZone']
367 )->format('Y-m-d H:i') :
368 DateTimeService::getCustomDateTimeObjectInUtc(
369 $slotDate . ' ' . $slotTime
370 )->format('Y-m-d H:i')
371 );
372
373 $converted[$type][$convertedSlotParts[0]][$convertedSlotParts[1]] = $slotTimesProviders;
374 }
375 }
376 }
377 }
378
379 if (count($props['providerIds']) !== 1 && $settingsDS->getSetting('appointments', 'employeeSelection') === 'roundRobin') {
380 /** @var AppointmentRepository $appointmentRepository */
381 $appointmentRepository = $this->container->get('domain.booking.appointment.repository');
382
383 $lastBookedProviderId = $appointmentRepository->getLastBookedEmployee($props['providerIds']);
384 }
385
386 $resultData = [
387 'slots' => $converted['available'] ?: $freeSlots['available'],
388 'occupied' => $converted['occupied'] ?: $freeSlots['occupied'],
389 'minimum' => $isUtcResponse ?
390 $minimumDateTime->setTimezone(
391 new DateTimeZone('UTC')
392 )->format('Y-m-d H:i') : $minimumDateTime->format('Y-m-d H:i'),
393 'maximum' => $isUtcResponse ?
394 $maximumDateTime->setTimezone(
395 new DateTimeZone('UTC')
396 )->format('Y-m-d H:i') : $maximumDateTime->format('Y-m-d H:i'),
397 'busyness' => $busyness,
398 'lastBookedProviderId' => $lastBookedProviderId,
399 'appCount' => $freeSlots['appCount'],
400 'duration' => $freeSlots['duration'],
401 ];
402
403
404 $resultData = apply_filters('amelia_get_timeslots_filter', $resultData, $props);
405
406 do_action('amelia_get_timeslots', $resultData, $props);
407
408
409 $result->setResult(CommandResult::RESULT_SUCCESS);
410 $result->setMessage('Successfully retrieved free slots');
411 $result->setData(
412 [
413 'minimum' => $resultData['minimum'],
414 'maximum' => $resultData['maximum'],
415 'slots' => $resultData['slots'],
416 'occupied' => $resultData['occupied'],
417 'busyness' => $resultData['busyness'],
418 'lastProvider' => $resultData['lastBookedProviderId'],
419 'appCount' => $resultData['appCount'],
420 'duration' => $resultData['duration'],
421 ]
422 );
423
424 return $result;
425 }
426 }
427