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