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 / Infrastructure / Repository / Booking / Appointment / AppointmentRepository.php
ameliabooking / src / Infrastructure / Repository / Booking / Appointment Last commit date
AppointmentRepository.php 1 year ago CustomerBookingExtraRepository.php 2 years ago CustomerBookingRepository.php 1 year ago
AppointmentRepository.php
1714 lines
1 <?php
2
3 namespace AmeliaBooking\Infrastructure\Repository\Booking\Appointment;
4
5 use AmeliaBooking\Domain\Collection\Collection;
6 use AmeliaBooking\Domain\Entity\Bookable\Service\Service;
7 use AmeliaBooking\Domain\Entity\Booking\Appointment\Appointment;
8 use AmeliaBooking\Domain\Factory\Booking\Appointment\AppointmentFactory;
9 use AmeliaBooking\Domain\Factory\Booking\Appointment\CustomerBookingFactory;
10 use AmeliaBooking\Domain\Repository\Booking\Appointment\AppointmentRepositoryInterface;
11 use AmeliaBooking\Domain\Services\DateTime\DateTimeService;
12 use AmeliaBooking\Infrastructure\Common\Exceptions\QueryExecutionException;
13 use AmeliaBooking\Infrastructure\Connection;
14 use AmeliaBooking\Infrastructure\Repository\AbstractRepository;
15 use AmeliaBooking\Infrastructure\WP\InstallActions\DB\Location\LocationsTable;
16
17 /**
18 * Class AppointmentRepository
19 *
20 * @package AmeliaBooking\Infrastructure\Repository\Booking\Appointment
21 */
22 class AppointmentRepository extends AbstractRepository implements AppointmentRepositoryInterface
23 {
24
25 const FACTORY = AppointmentFactory::class;
26
27 /** @var string */
28 protected $servicesTable;
29
30 /** @var string */
31 protected $bookingsTable;
32
33 /** @var string */
34 protected $customerBookingsExtrasTable;
35
36 /** @var string */
37 protected $extrasTable;
38
39 /** @var string */
40 protected $usersTable;
41
42 /** @var string */
43 protected $paymentsTable;
44
45 /** @var string */
46 protected $couponsTable;
47
48 /** @var string */
49 protected $providersLocationTable;
50
51 /** @var string */
52 protected $providerServicesTable;
53
54 /** @var string */
55 protected $packagesCustomersTable;
56
57 /** @var string */
58 protected $packagesCustomersServicesTable;
59
60 /**
61 * @param Connection $connection
62 * @param string $table
63 * @param string $servicesTable
64 * @param string $bookingsTable
65 * @param string $customerBookingsExtrasTable
66 * @param string $extrasTable
67 * @param string $usersTable
68 * @param string $paymentsTable
69 * @param string $couponsTable
70 * @param string $providersLocationTable
71 * @param string $providerServicesTable
72 * @param string $packagesCustomersTable
73 * @param string $packagesCustomersServicesTable
74 */
75 public function __construct(
76 Connection $connection,
77 $table,
78 $servicesTable,
79 $bookingsTable,
80 $customerBookingsExtrasTable,
81 $extrasTable,
82 $usersTable,
83 $paymentsTable,
84 $couponsTable,
85 $providersLocationTable,
86 $providerServicesTable,
87 $packagesCustomersTable,
88 $packagesCustomersServicesTable
89 ) {
90 parent::__construct($connection, $table);
91
92 $this->servicesTable = $servicesTable;
93 $this->bookingsTable = $bookingsTable;
94 $this->customerBookingsExtrasTable = $customerBookingsExtrasTable;
95 $this->extrasTable = $extrasTable;
96 $this->usersTable = $usersTable;
97 $this->paymentsTable = $paymentsTable;
98 $this->couponsTable = $couponsTable;
99 $this->providersLocationTable = $providersLocationTable;
100 $this->providerServicesTable = $providerServicesTable;
101 $this->packagesCustomersTable = $packagesCustomersTable;
102 $this->packagesCustomersServicesTable = $packagesCustomersServicesTable;
103 }
104
105 /**
106 * @param int $id
107 *
108 * @return Appointment
109 * @throws QueryExecutionException
110 */
111 public function getById($id)
112 {
113 try {
114 $statement = $this->connection->prepare(
115 "SELECT
116 a.id AS appointment_id,
117 a.bookingStart AS appointment_bookingStart,
118 a.bookingEnd AS appointment_bookingEnd,
119 a.notifyParticipants AS appointment_notifyParticipants,
120 a.internalNotes AS appointment_internalNotes,
121 a.status AS appointment_status,
122 a.serviceId AS appointment_serviceId,
123 a.providerId AS appointment_providerId,
124 a.locationId AS appointment_locationId,
125 a.googleCalendarEventId AS appointment_google_calendar_event_id,
126 a.googleMeetUrl AS appointment_google_meet_url,
127 a.outlookCalendarEventId AS appointment_outlook_calendar_event_id,
128 a.microsoftTeamsUrl AS appointment_microsoft_teams_url,
129 a.appleCalendarEventId AS appointment_apple_calendar_event_id,
130 a.zoomMeeting AS appointment_zoom_meeting,
131 a.lessonSpace AS appointment_lesson_space,
132 a.parentId AS appointment_parentId,
133
134 cb.id AS booking_id,
135 cb.customerId AS booking_customerId,
136 cb.status AS booking_status,
137 cb.price AS booking_price,
138 cb.persons AS booking_persons,
139 cb.customFields AS booking_customFields,
140 cb.info AS booking_info,
141 cb.aggregatedPrice AS booking_aggregatedPrice,
142 cb.utcOffset AS booking_utcOffset,
143 cb.packageCustomerServiceId AS booking_packageCustomerServiceId,
144 cb.duration AS booking_duration,
145 cb.created AS booking_created,
146 cb.tax AS booking_tax,
147
148 cbe.id AS bookingExtra_id,
149 cbe.extraId AS bookingExtra_extraId,
150 cbe.customerBookingId AS bookingExtra_customerBookingId,
151 cbe.quantity AS bookingExtra_quantity,
152 cbe.price AS bookingExtra_price,
153 cbe.aggregatedPrice AS bookingExtra_aggregatedPrice,
154 cbe.tax AS bookingExtra_tax,
155
156 p.id AS payment_id,
157 p.packageCustomerId AS payment_packageCustomerId,
158 p.amount AS payment_amount,
159 p.created AS payment_created,
160 p.invoiceNumber AS payment_invoiceNumber,
161 p.dateTime AS payment_dateTime,
162 p.status AS payment_status,
163 p.parentId AS payment_parentId,
164 p.gateway AS payment_gateway,
165 p.gatewayTitle AS payment_gatewayTitle,
166 p.transactionId AS payment_transactionId,
167 p.data AS payment_data,
168 p.wcOrderId AS payment_wcOrderId,
169 p.wcOrderItemId AS payment_wcOrderItemId,
170
171 c.id AS coupon_id,
172 c.code AS coupon_code,
173 c.discount AS coupon_discount,
174 c.deduction AS coupon_deduction,
175 c.expirationDate AS coupon_expirationDate,
176 c.limit AS coupon_limit,
177 c.customerLimit AS coupon_customerLimit,
178 c.status AS coupon_status,
179
180 pc.id AS package_customer_id,
181 pc.packageId AS package_customer_packageId,
182 pc.tax AS package_customer_tax,
183 pc.price AS package_customer_price,
184 pc.couponId AS package_customer_couponId
185 FROM {$this->table} a
186 INNER JOIN {$this->bookingsTable} cb ON cb.appointmentId = a.id
187 LEFT JOIN {$this->packagesCustomersServicesTable} pcs ON pcs.id = cb.packageCustomerServiceId
188 LEFT JOIN {$this->packagesCustomersTable} pc ON pcs.packageCustomerId = pc.id
189 LEFT JOIN {$this->paymentsTable} p ON ((p.customerBookingId = cb.id AND cb.packageCustomerServiceId IS NULL) OR (p.packageCustomerId = pc.id AND cb.packageCustomerServiceId IS NOT NULL AND cb.packageCustomerServiceId = pcs.id))
190 LEFT JOIN {$this->customerBookingsExtrasTable} cbe ON cbe.customerBookingId = cb.id
191 LEFT JOIN {$this->couponsTable} c ON (pc.couponId IS NOT NULL AND c.id = pc.couponId) OR (c.id = cb.couponId)
192 WHERE a.id = :appointmentId
193 ORDER BY cb.id, p.id"
194 );
195
196 $statement->bindParam(':appointmentId', $id);
197
198 $statement->execute();
199
200 $rows = $statement->fetchAll();
201 } catch (\Exception $e) {
202 throw new QueryExecutionException('Unable to find appointment by id in ' . __CLASS__, $e->getCode(), $e);
203 }
204
205 return call_user_func([static::FACTORY, 'createCollection'], $rows)->getItem($id);
206 }
207
208 /**
209 * @param int $id
210 *
211 * @return Appointment
212 * @throws QueryExecutionException
213 * @throws \AmeliaBooking\Domain\Common\Exceptions\InvalidArgumentException
214 */
215 public function getByBookingId($id)
216 {
217 try {
218 $statement = $this->connection->prepare(
219 "SELECT
220 a.id AS appointment_id,
221 a.bookingStart AS appointment_bookingStart,
222 a.bookingEnd AS appointment_bookingEnd,
223 a.notifyParticipants AS appointment_notifyParticipants,
224 a.internalNotes AS appointment_internalNotes,
225 a.status AS appointment_status,
226 a.serviceId AS appointment_serviceId,
227 a.providerId AS appointment_providerId,
228 a.locationId AS appointment_locationId,
229 a.googleCalendarEventId AS appointment_google_calendar_event_id,
230 a.googleMeetUrl AS appointment_google_meet_url,
231 a.outlookCalendarEventId AS appointment_outlook_calendar_event_id,
232 a.microsoftTeamsUrl AS appointment_microsoft_teams_url,
233 a.appleCalendarEventId AS appointment_apple_calendar_event_id,
234 a.zoomMeeting AS appointment_zoom_meeting,
235 a.lessonSpace AS appointment_lesson_space,
236
237 cb.id AS booking_id,
238 cb.customerId AS booking_customerId,
239 cb.status AS booking_status,
240 cb.price AS booking_price,
241 cb.persons AS booking_persons,
242 cb.customFields AS booking_customFields,
243 cb.info AS booking_info,
244 cb.utcOffset AS booking_utcOffset,
245 cb.aggregatedPrice AS booking_aggregatedPrice,
246 cb.couponId AS booking_couponId,
247 cb.duration AS booking_duration,
248 cb.created AS booking_created,
249 cb.tax AS booking_tax,
250
251 cbe.id AS bookingExtra_id,
252 cbe.extraId AS bookingExtra_extraId,
253 cbe.customerBookingId AS bookingExtra_customerBookingId,
254 cbe.quantity AS bookingExtra_quantity,
255 cbe.price AS bookingExtra_price,
256 cbe.aggregatedPrice AS bookingExtra_aggregatedPrice,
257 cbe.tax AS bookingExtra_tax,
258
259 p.id AS payment_id,
260 p.packageCustomerId AS payment_packageCustomerId,
261 p.amount AS payment_amount,
262 p.dateTime AS payment_dateTime,
263 p.status AS payment_status,
264 p.gateway AS payment_gateway,
265 p.parentId AS payment_parentId,
266 p.gatewayTitle AS payment_gatewayTitle,
267 p.transactionId AS payment_transactionId,
268 p.data AS payment_data,
269 p.wcOrderId AS payment_wcOrderId,
270 p.wcOrderItemId AS payment_wcOrderItemId,
271
272 c.id AS coupon_id,
273 c.code AS coupon_code,
274 c.discount AS coupon_discount,
275 c.deduction AS coupon_deduction,
276 c.expirationDate AS coupon_expirationDate,
277 c.limit AS coupon_limit,
278 c.customerLimit AS coupon_customerLimit,
279 c.status AS coupon_status
280 FROM {$this->table} a
281 INNER JOIN {$this->bookingsTable} cb ON cb.appointmentId = a.id
282 LEFT JOIN {$this->packagesCustomersTable} pc ON pc.customerId = cb.customerId
283 LEFT JOIN {$this->packagesCustomersServicesTable} pcs ON pcs.id = cb.packageCustomerServiceId
284 LEFT JOIN {$this->paymentsTable} p ON ((p.customerBookingId = cb.id AND cb.packageCustomerServiceId IS NULL) OR (p.packageCustomerId = pc.id AND cb.packageCustomerServiceId IS NOT NULL AND cb.packageCustomerServiceId = pcs.id))
285 LEFT JOIN {$this->customerBookingsExtrasTable} cbe ON cbe.customerBookingId = cb.id
286 LEFT JOIN {$this->couponsTable} c ON c.id = cb.couponId
287 WHERE a.id = (
288 SELECT cb2.appointmentId FROM {$this->bookingsTable} cb2 WHERE cb2.id = :customerBookingId
289 )
290 ORDER BY a.bookingStart, cb.id"
291 );
292
293 $statement->bindParam(':customerBookingId', $id);
294
295 $statement->execute();
296
297 $rows = $statement->fetchAll();
298 } catch (\Exception $e) {
299 throw new QueryExecutionException('Unable to find appointment by id in ' . __CLASS__, $e->getCode(), $e);
300 }
301
302 /** @var Collection $appointments */
303 $appointments = call_user_func([static::FACTORY, 'createCollection'], $rows);
304
305 return $appointments->length() ? $appointments->getItem($appointments->keys()[0]) : null;
306 }
307
308 /**
309 * @param int $id
310 *
311 * @return Appointment
312 * @throws QueryExecutionException
313 * @throws \AmeliaBooking\Domain\Common\Exceptions\InvalidArgumentException
314 */
315 public function getByPaymentId($id)
316 {
317 try {
318 $statement = $this->connection->prepare(
319 "SELECT
320 a.id AS appointment_id,
321 a.bookingStart AS appointment_bookingStart,
322 a.bookingEnd AS appointment_bookingEnd,
323 a.notifyParticipants AS appointment_notifyParticipants,
324 a.internalNotes AS appointment_internalNotes,
325 a.status AS appointment_status,
326 a.serviceId AS appointment_serviceId,
327 a.providerId AS appointment_providerId,
328 a.locationId AS appointment_locationId,
329 a.googleCalendarEventId AS appointment_google_calendar_event_id,
330 a.googleMeetUrl AS appointment_google_meet_url,
331 a.outlookCalendarEventId AS appointment_outlook_calendar_event_id,
332 a.microsoftTeamsUrl AS appointment_microsoft_teams_url,
333 a.appleCalendarEventId AS appointment_apple_calendar_event_id,
334 a.zoomMeeting AS appointment_zoom_meeting,
335 a.lessonSpace AS appointment_lesson_space,
336
337 cb.id AS booking_id,
338 cb.customerId AS booking_customerId,
339 cb.status AS booking_status,
340 cb.price AS booking_price,
341 cb.persons AS booking_persons,
342 cb.customFields AS booking_customFields,
343 cb.info AS booking_info,
344 cb.utcOffset AS booking_utcOffset,
345 cb.aggregatedPrice AS booking_aggregatedPrice,
346 cb.couponId AS booking_couponId,
347 cb.duration AS booking_duration,
348 cb.created AS booking_created,
349 cb.tax AS booking_tax,
350
351 cbe.id AS bookingExtra_id,
352 cbe.extraId AS bookingExtra_extraId,
353 cbe.customerBookingId AS bookingExtra_customerBookingId,
354 cbe.quantity AS bookingExtra_quantity,
355 cbe.price AS bookingExtra_price,
356 cbe.aggregatedPrice AS bookingExtra_aggregatedPrice,
357 cbe.tax AS bookingExtra_tax,
358
359 p.id AS payment_id,
360 p.packageCustomerId AS payment_packageCustomerId,
361 p.amount AS payment_amount,
362 p.dateTime AS payment_dateTime,
363 p.status AS payment_status,
364 p.parentId AS payment_parentId,
365 p.gateway AS payment_gateway,
366 p.gatewayTitle AS payment_gatewayTitle,
367 p.transactionId AS payment_transactionId,
368 p.data AS payment_data,
369 p.invoiceNumber AS payment_invoiceNumber,
370 p.created AS payment_created,
371
372 c.id AS coupon_id,
373 c.code AS coupon_code,
374 c.discount AS coupon_discount,
375 c.deduction AS coupon_deduction,
376 c.expirationDate AS coupon_expirationDate,
377 c.limit AS coupon_limit,
378 c.customerLimit AS coupon_customerLimit,
379 c.status AS coupon_status
380 FROM {$this->table} a
381 INNER JOIN {$this->bookingsTable} cb ON cb.appointmentId = a.id
382 LEFT JOIN {$this->packagesCustomersTable} pc ON pc.customerId = cb.customerId
383 LEFT JOIN {$this->packagesCustomersServicesTable} pcs ON pcs.id = cb.packageCustomerServiceId
384 LEFT JOIN {$this->paymentsTable} p ON ((p.customerBookingId = cb.id AND cb.packageCustomerServiceId IS NULL) OR (p.packageCustomerId = pc.id AND cb.packageCustomerServiceId IS NOT NULL AND cb.packageCustomerServiceId = pcs.id))
385 LEFT JOIN {$this->customerBookingsExtrasTable} cbe ON cbe.customerBookingId = cb.id
386 LEFT JOIN {$this->couponsTable} c ON c.id = cb.couponId
387 WHERE a.id IN (
388 SELECT cb2.appointmentId
389 FROM {$this->paymentsTable} p2
390 INNER JOIN {$this->bookingsTable} cb2 ON cb2.id = p2.customerBookingId
391 WHERE p2.id = :paymentId
392 )
393 ORDER BY a.bookingStart"
394 );
395
396 $statement->bindParam(':paymentId', $id);
397
398 $statement->execute();
399
400 $rows = $statement->fetchAll();
401 } catch (\Exception $e) {
402 throw new QueryExecutionException('Unable to find appointment by id in ' . __CLASS__, $e->getCode(), $e);
403 }
404
405 /** @var Collection $appointments */
406 $appointments = call_user_func([static::FACTORY, 'createCollection'], $rows);
407
408 return $appointments->length() ? $appointments->getItem($appointments->keys()[0]) : null;
409 }
410
411 /**
412 * @param Appointment $entity
413 *
414 * @return bool
415 * @throws QueryExecutionException
416 */
417 public function add($entity)
418 {
419 $data = $entity->toArray();
420
421 $params = [
422 ':bookingStart' => DateTimeService::getCustomDateTimeInUtc($data['bookingStart']),
423 ':bookingEnd' => DateTimeService::getCustomDateTimeInUtc($data['bookingEnd']),
424 ':notifyParticipants' => $data['notifyParticipants'],
425 ':internalNotes' => $data['internalNotes'] ?: '',
426 ':status' => $data['status'],
427 ':serviceId' => $data['serviceId'],
428 ':providerId' => $data['providerId'],
429 ':locationId' => $data['locationId'],
430 ':parentId' => $data['parentId'],
431 ':lessonSpace' => !empty($data['lessonSpace']) ? $data['lessonSpace'] : null,
432 ':error' => '',
433 ];
434
435 try {
436 $statement = $this->connection->prepare(
437 "INSERT INTO {$this->table}
438 (
439 `bookingStart`,
440 `bookingEnd`,
441 `notifyParticipants`,
442 `internalNotes`,
443 `status`,
444 `locationId`,
445 `serviceId`,
446 `providerId`,
447 `parentId`,
448 `lessonSpace`,
449 `error`
450 )
451 VALUES (
452 :bookingStart,
453 :bookingEnd,
454 :notifyParticipants,
455 :internalNotes,
456 :status,
457 :locationId,
458 :serviceId,
459 :providerId,
460 :parentId,
461 :lessonSpace,
462 :error
463 )"
464 );
465
466 $res = $statement->execute($params);
467
468 if (!$res) {
469 throw new QueryExecutionException('Unable to add data in ' . __CLASS__);
470 }
471
472 return $this->connection->lastInsertId();
473 } catch (\Exception $e) {
474 throw new QueryExecutionException('Unable to add data in ' . __CLASS__, $e->getCode(), $e);
475 }
476 }
477
478 /**
479 * @param int $id
480 * @param Appointment $entity
481 *
482 * @return mixed
483 * @throws QueryExecutionException
484 */
485 public function update($id, $entity)
486 {
487 $data = $entity->toArray();
488
489 $params = [
490 ':id' => $id,
491 ':bookingStart' => DateTimeService::getCustomDateTimeInUtc($data['bookingStart']),
492 ':bookingEnd' => DateTimeService::getCustomDateTimeInUtc($data['bookingEnd']),
493 ':notifyParticipants' => $data['notifyParticipants'],
494 ':internalNotes' => $data['internalNotes'],
495 ':status' => $data['status'],
496 ':locationId' => $data['locationId'],
497 ':serviceId' => $data['serviceId'],
498 ':providerId' => $data['providerId'],
499 ':googleCalendarEventId' => $data['googleCalendarEventId'],
500 ':googleMeetUrl' => $data['googleMeetUrl'],
501 ':outlookCalendarEventId' => $data['outlookCalendarEventId'],
502 ':microsoftTeamsUrl' => $data['microsoftTeamsUrl'],
503 ':appleCalendarEventId' => $data['appleCalendarEventId'],
504 ':lessonSpace' => $data['lessonSpace'],
505 ];
506
507 try {
508 $statement = $this->connection->prepare(
509 "UPDATE {$this->table}
510 SET
511 `bookingStart` = :bookingStart,
512 `bookingEnd` = :bookingEnd,
513 `notifyParticipants` = :notifyParticipants,
514 `internalNotes` = :internalNotes,
515 `status` = :status,
516 `locationId` = :locationId,
517 `serviceId` = :serviceId,
518 `providerId` = :providerId,
519 `googleCalendarEventId` = :googleCalendarEventId,
520 `googleMeetUrl` = :googleMeetUrl,
521 `outlookCalendarEventId` = :outlookCalendarEventId,
522 `microsoftTeamsUrl` = :microsoftTeamsUrl,
523 `appleCalendarEventId` = :appleCalendarEventId,
524 `lessonSpace` = :lessonSpace
525 WHERE id = :id"
526 );
527
528 $res = $statement->execute($params);
529
530 if (!$res) {
531 throw new QueryExecutionException('Unable to save data in ' . __CLASS__);
532 }
533
534 return $res;
535 } catch (\Exception $e) {
536 throw new QueryExecutionException('Unable to save data in ' . __CLASS__, $e->getCode(), $e);
537 }
538 }
539
540 /**
541 * @param int $id
542 * @param int $status
543 *
544 * @return mixed
545 * @throws QueryExecutionException
546 */
547 public function updateStatusById($id, $status)
548 {
549 $params = [
550 ':id' => $id,
551 ':status' => $status
552 ];
553
554 try {
555 $statement = $this->connection->prepare(
556 "UPDATE {$this->table}
557 SET
558 `status` = :status
559 WHERE id = :id"
560 );
561
562 $res = $statement->execute($params);
563
564 if (!$res) {
565 throw new QueryExecutionException('Unable to save data in ' . __CLASS__);
566 }
567
568 return $res;
569 } catch (\Exception $e) {
570 throw new QueryExecutionException('Unable to save data in ' . __CLASS__, $e->getCode(), $e);
571 }
572 }
573
574 /**
575 * Returns array of current appointments where keys are Provider ID's
576 * and array values are Appointments Data (modified by service padding time)
577 *
578 * @return array
579 * @throws QueryExecutionException
580 */
581 public function getCurrentAppointments()
582 {
583 try {
584 $currentDateTime = "STR_TO_DATE('" . DateTimeService::getNowDateTimeInUtc() . "', '%Y-%m-%d %H:%i:%s')";
585
586 $statement = $this->connection->query(
587 "SELECT
588 a.bookingStart AS bookingStart,
589 a.bookingEnd AS bookingEnd,
590 a.providerId AS providerId,
591 a.serviceId AS serviceId,
592 s.timeBefore AS timeBefore,
593 s.timeAfter AS timeAfter
594 FROM {$this->table} a
595 INNER JOIN {$this->servicesTable} s ON s.id = a.serviceId
596 WHERE {$currentDateTime} >= a.bookingStart
597 AND {$currentDateTime} <= a.bookingEnd
598 ORDER BY a.bookingStart"
599 );
600
601 $rows = $statement->fetchAll();
602 } catch (\Exception $e) {
603 throw new QueryExecutionException('Unable to find appointments in ' . __CLASS__, $e->getCode(), $e);
604 }
605
606 $result = [];
607
608 foreach ($rows as $row) {
609 $row['bookingStart'] = DateTimeService::getCustomDateTimeObjectFromUtc($row['bookingStart'])
610 ->modify('-' . ($row['timeBefore'] ?: '0') . ' seconds')
611 ->format('Y-m-d H:i:s');
612
613 $row['bookingEnd'] = DateTimeService::getCustomDateTimeObjectFromUtc($row['bookingEnd'])
614 ->modify('+' . ($row['timeAfter'] ?: '0') . ' seconds')
615 ->format('Y-m-d H:i:s');
616
617 $result[$row['providerId']] = $row;
618 }
619
620 return $result;
621 }
622
623 /**
624 * @param Collection $collection
625 * @param array $providerIds
626 * @param string $startDateTime
627 * @param string $endDateTime
628 * @return void
629 * @throws QueryExecutionException
630 */
631 public function getFutureAppointments($collection, $providerIds, $startDateTime, $endDateTime)
632 {
633 $params = [];
634
635 $where = [
636 "a.status IN ('approved', 'pending')",
637 "cb.status IN ('approved', 'pending')",
638 "a.bookingStart >= STR_TO_DATE('{$startDateTime}', '%Y-%m-%d %H:%i:%s')",
639 ];
640
641 if ($endDateTime) {
642 $where[] = "a.bookingStart <= STR_TO_DATE('{$endDateTime}', '%Y-%m-%d %H:%i:%s')";
643 }
644
645 if (!empty($providerIds)) {
646 $queryProviders = [];
647
648 foreach ($providerIds as $index => $value) {
649 $param = ':provider' . $index;
650
651 $queryProviders[] = $param;
652
653 $params[$param] = $value;
654 }
655
656 $where[] = 'a.providerId IN (' . implode(', ', $queryProviders) . ')';
657 }
658
659 $where = $where ? 'WHERE ' . implode(' AND ', $where) : '';
660
661 try {
662 $statement = $this->connection->prepare(
663 "SELECT
664 a.id AS id,
665 a.bookingStart AS bookingStart,
666 a.bookingEnd AS bookingEnd,
667 a.providerId AS providerId,
668 a.serviceId AS serviceId,
669 a.locationId AS locationId,
670 a.status AS status,
671
672 cb.id AS bookingId,
673 cb.customerId AS customerId,
674 cb.status AS bookingStatus,
675 cb.persons AS persons
676
677 FROM {$this->table} a
678 INNER JOIN {$this->bookingsTable} cb ON cb.appointmentId = a.id
679 {$where}
680 ORDER BY a.bookingStart
681 "
682 );
683
684 $statement->execute($params);
685
686 while ($row = $statement->fetch()) {
687 $id = (int)$row['id'];
688
689 $bookingId = (int)$row['bookingId'];
690
691 if (!$collection->keyExists($id)) {
692 $collection->addItem(
693 AppointmentFactory::create(
694 [
695 'id' => $id,
696 'bookingStart' => DateTimeService::getCustomDateTimeFromUtc(
697 $row['bookingStart']
698 ),
699 'bookingEnd' => DateTimeService::getCustomDateTimeFromUtc(
700 $row['bookingEnd']
701 ),
702 'providerId' => $row['providerId'],
703 'serviceId' => $row['serviceId'],
704 'locationId' => $row['locationId'],
705 'status' => $row['status'],
706 'bookings' => [],
707 'notifyParticipants' => false
708 ]
709 ),
710 $id
711 );
712 }
713
714 if (!$collection->getItem($id)->getBookings()->keyExists($bookingId)) {
715 $collection->getItem($id)->getBookings()->addItem(
716 CustomerBookingFactory::create(
717 [
718 'id' => $bookingId,
719 'customerId' => $row['customerId'],
720 'status' => $row['bookingStatus'],
721 'persons' => $row['persons'],
722 ]
723 ),
724 $bookingId
725 );
726 }
727 }
728 } catch (\Exception $e) {
729 throw new QueryExecutionException('Unable to find appointments in ' . __CLASS__, $e->getCode(), $e);
730 }
731 }
732
733 /**
734 * @param array $providerIds
735 * @param string $startDateTime
736 * @param string $endDateTime
737 * @return array
738 * @throws QueryExecutionException
739 */
740 public function getFutureAppointmentsServicesIds($providerIds, $startDateTime, $endDateTime)
741 {
742 $params = [];
743
744 $where = [];
745
746 if ($startDateTime) {
747 $where = ["bookingStart >= STR_TO_DATE('{$startDateTime}', '%Y-%m-%d %H:%i:%s')"];
748 }
749
750 if ($endDateTime) {
751 $where = ["bookingStart <= STR_TO_DATE('{$endDateTime}', '%Y-%m-%d %H:%i:%s')"];
752 }
753
754 if (!empty($providerIds)) {
755 $queryProviders = [];
756
757 foreach ($providerIds as $index => $value) {
758 $param = ':provider' . $index;
759
760 $queryProviders[] = $param;
761
762 $params[$param] = $value;
763 }
764
765 $where[] = 'providerId IN (' . implode(', ', $queryProviders) . ')';
766 }
767
768 $where = $where ? 'WHERE ' . implode(' AND ', $where) : '';
769
770 try {
771 $statement = $this->connection->prepare("SELECT DISTINCT(serviceId) FROM {$this->table} {$where}");
772
773 $statement->execute($params);
774
775 $rows = $statement->fetchAll();
776 } catch (\Exception $e) {
777 throw new QueryExecutionException('Unable to find appointments in ' . __CLASS__, $e->getCode(), $e);
778 }
779
780 return $rows ? array_column($rows, 'serviceId') : [];
781 }
782
783 /**
784 * @param array $serviceIds
785 * @param string $startDateTime
786 * @param string $endDateTime
787 * @return array
788 * @throws QueryExecutionException
789 */
790 public function getFutureAppointmentsProvidersIds($serviceIds, $startDateTime, $endDateTime)
791 {
792 $params = [];
793
794 $where = [];
795
796 if ($startDateTime) {
797 $where = ["bookingStart >= STR_TO_DATE('{$startDateTime}', '%Y-%m-%d %H:%i:%s')"];
798 }
799
800 if ($endDateTime) {
801 $where = ["bookingStart <= STR_TO_DATE('{$endDateTime}', '%Y-%m-%d %H:%i:%s')"];
802 }
803
804 if (!empty($serviceIds)) {
805 $queryServices = [];
806
807 foreach ($serviceIds as $index => $value) {
808 $param = ':service' . $index;
809
810 $queryServices[] = $param;
811
812 $params[$param] = $value;
813 }
814
815 $where[] = 'serviceId IN (' . implode(', ', $queryServices) . ')';
816 }
817
818 $where = $where ? 'WHERE ' . implode(' AND ', $where) : '';
819
820 try {
821 $statement = $this->connection->prepare("SELECT DISTINCT(providerId) FROM {$this->table} {$where}");
822
823 $statement->execute($params);
824
825 $rows = $statement->fetchAll();
826 } catch (\Exception $e) {
827 throw new QueryExecutionException('Unable to find appointments in ' . __CLASS__, $e->getCode(), $e);
828 }
829
830 return $rows ? array_column($rows, 'providerId') : [];
831 }
832
833 /**
834 * @param array $criteria
835 *
836 * @return Collection
837 * @throws QueryExecutionException
838 */
839 public function getFiltered($criteria)
840 {
841 try {
842 $params = [];
843
844 $where = [];
845
846 if (!empty($criteria['dates'])) {
847 if (isset($criteria['dates'][0], $criteria['dates'][1])) {
848 $whereStart = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') BETWEEN :bookingFrom AND :bookingTo)";
849
850 $params[':bookingFrom'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][0]);
851
852 $params[':bookingTo'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][1]);
853
854 $whereEnd = '';
855 if (!empty($criteria['endsInDateRange'])) {
856 $whereEnd = "OR (DATE_FORMAT(a.bookingEnd, '%Y-%m-%d %H:%i:%s') BETWEEN :bookingFrom2 AND :bookingTo2)";
857 $params[':bookingFrom2'] = $params[':bookingFrom'];
858 $params[':bookingTo2'] = $params[':bookingTo'];
859 }
860
861 $where[] = "({$whereStart} {$whereEnd})";
862 } elseif (isset($criteria['dates'][0])) {
863 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') >= :bookingFrom)";
864
865 $params[':bookingFrom'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][0]);
866 } elseif (isset($criteria['dates'][1])) {
867 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') <= :bookingTo)";
868
869 $params[':bookingTo'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][1]);
870 } else {
871 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') > :bookingFrom)";
872
873 $params[':bookingFrom'] = DateTimeService::getNowDateTimeInUtc();
874 }
875 }
876
877 if (!empty($criteria['ids'])) {
878 $queryAppointments = [];
879
880 foreach ((array)$criteria['ids'] as $index => $value) {
881 $param = ':id' . $index;
882
883 $queryAppointments[] = $param;
884
885 $params[$param] = $value;
886 }
887
888 $where[] = 'a.id IN (' . implode(', ', $queryAppointments) . ')';
889 }
890
891 if (!empty($criteria['services'])) {
892 $queryServices = [];
893
894 foreach ((array)$criteria['services'] as $index => $value) {
895 $param = ':service' . $index;
896
897 $queryServices[] = $param;
898
899 $params[$param] = $value;
900 }
901
902 $where[] = 'a.serviceId IN (' . implode(', ', $queryServices) . ')';
903 }
904
905 if (!empty($criteria['providers'])) {
906 $queryProviders = [];
907
908 foreach ((array)$criteria['providers'] as $index => $value) {
909 $param = ':provider' . $index;
910
911 $queryProviders[] = $param;
912
913 $params[$param] = $value;
914 }
915
916 $where[] = 'a.providerId IN (' . implode(', ', $queryProviders) . ')';
917 }
918
919 if (!empty($criteria['customers'])) {
920 $queryCustomers = [];
921
922 foreach ((array)$criteria['customers'] as $index => $value) {
923 $param = ':customer' . $index;
924
925 $queryCustomers[] = $param;
926
927 $params[$param] = $value;
928 }
929
930 $where[] = 'cb.customerId IN (' . implode(', ', $queryCustomers) . ')';
931 }
932
933 if (isset($criteria['customerId'])) {
934 $where[] = 'cb.customerId = :customerId';
935 $params[':customerId'] = $criteria['customerId'];
936 }
937
938
939 if (isset($criteria['providerId'])) {
940 $where[] = 'a.providerId = :providerId';
941 $params[':providerId'] = $criteria['providerId'];
942 }
943
944 if (array_key_exists('status', $criteria)) {
945 $where[] = 'a.status = :status';
946
947 $params[':status'] = $criteria['status'];
948 }
949
950 if (!empty($criteria['statuses'])) {
951 $queryStatuses = [];
952
953 foreach ($criteria['statuses'] as $index => $value) {
954 $param = ':statuses' . $index;
955
956 $queryStatuses[] = $param;
957
958 $params[$param] = $value;
959 }
960
961 $where[] = 'a.status IN (' . implode(', ', $queryStatuses) . ')';
962 }
963
964 if (array_key_exists('bookingStatus', $criteria)) {
965 $where[] = 'cb.status = :bookingStatus';
966 $params[':bookingStatus'] = $criteria['bookingStatus'];
967 }
968
969 if (array_key_exists('bookingStatuses', $criteria)) {
970 $queryStatuses = [];
971
972 foreach ($criteria['bookingStatuses'] as $index => $value) {
973 $param = ':bookingStatuses' . $index;
974
975 $queryStatuses[] = $param;
976
977 $params[$param] = $value;
978 }
979
980 $where[] = 'cb.status IN (' . implode(', ', $queryStatuses) . ')';
981 }
982
983 if (!empty($criteria['locations'])) {
984 $queryLocations = [];
985
986 foreach ((array)$criteria['locations'] as $index => $value) {
987 $param = ':location' . $index;
988
989 $queryLocations[] = $param;
990
991 $params[$param] = $value;
992 }
993
994 $where[] = 'a.locationId IN (' . implode(', ', $queryLocations) . ')';
995 }
996
997 if (isset($criteria['bookingId'])) {
998 $where[] = 'cb.id = :bookingId';
999 $params[':bookingId'] = $criteria['bookingId'];
1000 }
1001
1002 if (isset($criteria['bookingIds'])) {
1003 $queryBookings = [];
1004
1005 foreach ((array)$criteria['bookingIds'] as $index => $value) {
1006 $param = ':bookingId' . $index;
1007
1008 $queryBookings[] = $param;
1009
1010 $params[$param] = $value;
1011 }
1012
1013 $where[] = 'cb.id IN (' . implode(', ', $queryBookings) . ')';
1014 }
1015
1016 if (isset($criteria['bookingCouponId'])) {
1017 $where[] = 'cb.couponId = :bookingCouponId';
1018 $params[':bookingCouponId'] = $criteria['bookingCouponId'];
1019 }
1020
1021 if (isset($criteria['parentId'])) {
1022 $where[] = 'a.parentId = :parentId';
1023 $params[':parentId'] = $criteria['parentId'];
1024 }
1025
1026 if (!empty($criteria['packageCustomerServices'])) {
1027 $queryLocations = [];
1028
1029 foreach ($criteria['packageCustomerServices'] as $index => $value) {
1030 $param = ':packageCustomerServices' . $index;
1031
1032 $queryLocations[] = $param;
1033
1034 $params[$param] = $value;
1035 }
1036
1037 $where[] = 'cb.packageCustomerServiceId IN (' . implode(', ', $queryLocations) . ')';
1038 }
1039
1040 $packagesJoin = '';
1041 if (isset($criteria['packageId'])) {
1042 $where[] = 'pc.packageId = :packageId';
1043 $params[':packageId'] = $criteria['packageId'];
1044
1045 $packagesJoin = "LEFT JOIN {$this->packagesCustomersServicesTable} pcs ON pcs.id = cb.packageCustomerServiceId
1046 LEFT JOIN {$this->packagesCustomersTable} pc ON pcs.packageCustomerId = pc.id";
1047 } elseif (!empty($criteria['packageCustomerId'])) {
1048 $where[] = 'pc.id = :packageCustomerId';
1049 $params[':packageCustomerId'] = $criteria['packageCustomerId'];
1050
1051 $packagesJoin = "LEFT JOIN {$this->packagesCustomersServicesTable} pcs ON pcs.id = cb.packageCustomerServiceId
1052 LEFT JOIN {$this->packagesCustomersTable} pc ON pcs.packageCustomerId = pc.id";
1053 }
1054
1055
1056 $servicesFields = '
1057 s.id AS service_id,
1058 s.name AS service_name,
1059 s.description AS service_description,
1060 s.color AS service_color,
1061 s.price AS service_price,
1062 s.status AS service_status,
1063 s.categoryId AS service_categoryId,
1064 s.minCapacity AS service_minCapacity,
1065 s.maxCapacity AS service_maxCapacity,
1066 s.timeAfter AS service_timeAfter,
1067 s.timeBefore AS service_timeBefore,
1068 s.duration AS service_duration,
1069 s.settings AS service_settings,
1070 ';
1071
1072 $servicesJoin = "INNER JOIN {$this->servicesTable} s ON s.id = a.serviceId";
1073
1074 if (!empty($criteria['skipServices'])) {
1075 $servicesFields = '';
1076
1077 $servicesJoin = '';
1078 }
1079
1080 $providersFields = '
1081 pu.id AS provider_id,
1082 pu.firstName AS provider_firstName,
1083 pu.lastName AS provider_lastName,
1084 pu.email AS provider_email,
1085 pu.note AS provider_note,
1086 pu.description AS provider_description,
1087 pu.phone AS provider_phone,
1088 pu.gender AS provider_gender,
1089 pu.translations AS provider_translations,
1090 pu.timeZone AS provider_timeZone,
1091 ';
1092
1093 $providersJoin = "INNER JOIN {$this->usersTable} pu ON pu.id = a.providerId";
1094
1095 if (!empty($criteria['skipProviders'])) {
1096 $providersFields = '';
1097
1098 $providersJoin = '';
1099 }
1100
1101 $locationsTable = LocationsTable::getTableName();
1102
1103 $locationsFields = '';
1104
1105 $locationsJoin = '';
1106
1107 if (!empty($criteria['withLocations'])) {
1108 $locationsFields = '
1109 l.id AS location_id,
1110 l.name AS location_name,
1111 l.address AS location_address,
1112 ';
1113
1114 $locationsJoin = "LEFT JOIN {$locationsTable} l ON l.id = a.locationId";
1115 }
1116
1117 $customersFields = '
1118 cu.id AS customer_id,
1119 cu.firstName AS customer_firstName,
1120 cu.lastName AS customer_lastName,
1121 cu.email AS customer_email,
1122 cu.note AS customer_note,
1123 cu.phone AS customer_phone,
1124 cu.gender AS customer_gender,
1125 cu.status AS customer_status,
1126 ';
1127
1128 $customersJoin = "INNER JOIN {$this->usersTable} cu ON cu.id = cb.customerId";
1129
1130 if (!empty($criteria['skipCustomers'])) {
1131 $customersFields = '';
1132
1133 $customersJoin = '';
1134 }
1135
1136 $paymentsFields = '
1137 p.id AS payment_id,
1138 p.packageCustomerId AS payment_packageCustomerId,
1139 p.amount AS payment_amount,
1140 p.dateTime AS payment_dateTime,
1141 p.status AS payment_status,
1142 p.gateway AS payment_gateway,
1143 p.gatewayTitle AS payment_gatewayTitle,
1144 p.transactionId AS payment_transactionId,
1145 p.data AS payment_data,
1146 p.parentId AS payment_parentId,
1147 p.wcOrderId AS payment_wcOrderId,
1148 p.wcOrderItemId AS payment_wcOrderItemId,
1149 p.created AS payment_created,
1150 ';
1151
1152 $paymentsJoin = "LEFT JOIN {$this->paymentsTable} p ON p.customerBookingId = cb.id";
1153
1154 if (!empty($criteria['skipPayments'])) {
1155 $paymentsFields = '';
1156
1157 $paymentsJoin = '';
1158 }
1159
1160 $bookingExtrasFields = '
1161 cbe.id AS bookingExtra_id,
1162 cbe.extraId AS bookingExtra_extraId,
1163 cbe.customerBookingId AS bookingExtra_customerBookingId,
1164 cbe.quantity AS bookingExtra_quantity,
1165 cbe.price AS bookingExtra_price,
1166 cbe.tax AS bookingExtra_tax,
1167 cbe.aggregatedPrice AS bookingExtra_aggregatedPrice,
1168 ';
1169
1170 $bookingExtrasJoin = "LEFT JOIN {$this->customerBookingsExtrasTable} cbe ON cbe.customerBookingId = cb.id";
1171
1172 if (!empty($criteria['skipExtras'])) {
1173 $bookingExtrasFields = '';
1174
1175 $bookingExtrasJoin = '';
1176 }
1177
1178 $couponsFields = '
1179 c.id AS coupon_id,
1180 c.code AS coupon_code,
1181 c.discount AS coupon_discount,
1182 c.deduction AS coupon_deduction,
1183 c.expirationDate AS coupon_expirationDate,
1184 c.limit AS coupon_limit,
1185 c.customerLimit AS coupon_customerLimit,
1186 c.status AS coupon_status,
1187 ';
1188
1189 $couponsJoin = "LEFT JOIN {$this->couponsTable} c ON c.id = cb.couponId";
1190
1191 if (!empty($criteria['skipCoupons'])) {
1192 $couponsFields = '';
1193
1194 $couponsJoin = '';
1195 }
1196
1197 $bookingsFields = '
1198 cb.id AS booking_id,
1199 cb.customerId AS booking_customerId,
1200 cb.status AS booking_status,
1201 cb.price AS booking_price,
1202 cb.tax AS booking_tax,
1203 cb.persons AS booking_persons,
1204 cb.customFields AS booking_customFields,
1205 cb.info AS booking_info,
1206 cb.aggregatedPrice AS booking_aggregatedPrice,
1207 cb.packageCustomerServiceId AS booking_packageCustomerServiceId,
1208 cb.duration AS booking_duration,
1209 cb.created AS booking_created,
1210 cb.tax AS booking_tax,
1211 ';
1212
1213 $bookingsJoin = "INNER JOIN {$this->bookingsTable} cb ON cb.appointmentId = a.id";
1214
1215 if (!empty($criteria['skipBookings'])) {
1216 $bookingsFields = '';
1217
1218 $bookingsJoin = '';
1219 }
1220
1221 $where = $where ? 'WHERE ' . implode(' AND ', $where) : '';
1222
1223 $statement = $this->connection->prepare(
1224 "SELECT
1225 {$customersFields}
1226 {$bookingExtrasFields}
1227 {$providersFields}
1228 {$locationsFields}
1229 {$servicesFields}
1230 {$paymentsFields}
1231 {$couponsFields}
1232 {$bookingsFields}
1233 a.id AS appointment_id,
1234 a.bookingStart AS appointment_bookingStart,
1235 a.bookingEnd AS appointment_bookingEnd,
1236 a.notifyParticipants AS appointment_notifyParticipants,
1237 a.internalNotes AS appointment_internalNotes,
1238 a.status AS appointment_status,
1239 a.serviceId AS appointment_serviceId,
1240 a.providerId AS appointment_providerId,
1241 a.locationId AS appointment_locationId,
1242 a.googleCalendarEventId AS appointment_google_calendar_event_id,
1243 a.googleMeetUrl AS appointment_google_meet_url,
1244 a.outlookCalendarEventId AS appointment_outlook_calendar_event_id,
1245 a.microsoftTeamsUrl AS appointment_microsoft_teams_url,
1246 a.appleCalendarEventId AS appointment_apple_calendar_event_id,
1247 a.zoomMeeting AS appointment_zoom_meeting,
1248 a.lessonSpace AS appointment_lesson_space,
1249 a.parentId AS appointment_parentId
1250 FROM {$this->table} a
1251 {$bookingsJoin}
1252 {$packagesJoin}
1253 {$customersJoin}
1254 {$providersJoin}
1255 {$locationsJoin}
1256 {$servicesJoin}
1257 {$paymentsJoin}
1258 {$bookingExtrasJoin}
1259 {$couponsJoin}
1260 {$where}
1261 ORDER BY a.bookingStart, a.id"
1262 );
1263
1264 $statement->execute($params);
1265
1266 $rows = $statement->fetchAll();
1267 } catch (\Exception $e) {
1268 throw new QueryExecutionException('Unable to find by id in ' . __CLASS__, $e->getCode(), $e);
1269 }
1270
1271 return call_user_func([static::FACTORY, 'createCollection'], $rows);
1272 }
1273
1274 /**
1275 * @return Collection $criteria
1276 * @throws QueryExecutionException
1277 */
1278 public function getAppointmentsWithoutBookings()
1279 {
1280 try {
1281 $statement = $this->connection->query(
1282 "SELECT
1283 a.id AS appointment_id,
1284 a.bookingStart AS appointment_bookingStart,
1285 a.bookingEnd AS appointment_bookingEnd,
1286 a.providerId AS appointment_providerId,
1287 a.serviceId AS appointment_serviceId,
1288 a.status AS appointment_status,
1289 a.googleCalendarEventId as appointment_google_calendar_event_id,
1290 a.googleMeetUrl AS appointment_google_meet_url,
1291 a.outlookCalendarEventId AS appointment_outlook_calendar_event_id,
1292 a.microsoftTeamsUrl AS appointment_microsoft_teams_url,
1293 a.appleCalendarEventId AS appointment_apple_calendar_event_id,
1294 a.notifyParticipants AS appointment_notifyParticipants
1295 FROM {$this->table} a WHERE (
1296 SELECT COUNT(*) FROM {$this->bookingsTable} cb WHERE a.id = cb.appointmentId
1297 ) = 0"
1298 );
1299
1300 $rows = $statement->fetchAll();
1301 } catch (\Exception $e) {
1302 throw new QueryExecutionException('Unable to find data from ' . __CLASS__, $e->getCode(), $e);
1303 }
1304
1305 return call_user_func([static::FACTORY, 'createCollection'], $rows);
1306 }
1307
1308 /**
1309 * @param array $criteria
1310 * @param null $itemsPerPage
1311 * @return Collection
1312 * @throws QueryExecutionException
1313 */
1314 public function getPeriodAppointments($criteria, $itemsPerPage = null)
1315 {
1316 $params = [];
1317
1318 $where = [];
1319
1320 if (!empty($criteria['dates'])) {
1321 if (isset($criteria['dates'][0], $criteria['dates'][1])) {
1322 $whereStart = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') BETWEEN :bookingFrom AND :bookingTo)";
1323
1324 $params[':bookingFrom'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][0]);
1325
1326 $params[':bookingTo'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][1]);
1327
1328 $whereEnd = '';
1329 if (!empty($criteria['endsInDateRange'])) {
1330 $whereEnd = "OR (DATE_FORMAT(a.bookingEnd, '%Y-%m-%d %H:%i:%s') BETWEEN :bookingFrom2 AND :bookingTo2)";
1331 $params[':bookingFrom2'] = $params[':bookingFrom'];
1332 $params[':bookingTo2'] = $params[':bookingTo'];
1333 }
1334
1335 $where[] = "({$whereStart} {$whereEnd})";
1336 } elseif (isset($criteria['dates'][0])) {
1337 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') >= :bookingFrom)";
1338
1339 $params[':bookingFrom'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][0]);
1340 } elseif (isset($criteria['dates'][1])) {
1341 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') <= :bookingTo)";
1342
1343 $params[':bookingTo'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][1]);
1344 } else {
1345 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') > :bookingFrom)";
1346
1347 $params[':bookingFrom'] = DateTimeService::getNowDateTimeInUtc();
1348 }
1349 }
1350
1351 $whereOr = [];
1352 if (!empty($criteria['services'])) {
1353 $queryServices = [];
1354
1355 foreach ((array)$criteria['services'] as $index => $value) {
1356 $param = ':service' . $index;
1357
1358 $queryServices[] = $param;
1359
1360 $params[$param] = $value;
1361 }
1362
1363 $where[] = 'a.serviceId IN (' . implode(', ', $queryServices) . ')';
1364 }
1365
1366 if (!empty($criteria['providers'])) {
1367 $queryProviders = [];
1368
1369 foreach ((array)$criteria['providers'] as $index => $value) {
1370 $param = ':provider' . $index;
1371
1372 $queryProviders[] = $param;
1373
1374 $params[$param] = $value;
1375 }
1376
1377 $where[] = 'a.providerId IN (' . implode(', ', $queryProviders) . ')';
1378 }
1379
1380 $bookingsJoin = "INNER JOIN {$this->bookingsTable} cb ON cb.appointmentId = a.id";
1381
1382 if (!empty($criteria['skipBookings'])) {
1383 $bookingsJoin = '';
1384 }
1385
1386 if (empty($criteria['skipBookings']) && !empty($criteria['customers'])) {
1387 $queryCustomers = [];
1388
1389 foreach ((array)$criteria['customers'] as $index => $value) {
1390 $param = ':customer' . $index;
1391
1392 $queryCustomers[] = $param;
1393
1394 $params[$param] = $value;
1395 }
1396
1397 $whereOr[] = 'cb.customerId IN (' . implode(', ', $queryCustomers) . ')';
1398 }
1399
1400 if (empty($criteria['skipBookings']) && isset($criteria['customerId'])) {
1401 $where[] = 'cb.customerId = :customerId';
1402 $params[':customerId'] = $criteria['customerId'];
1403 }
1404
1405 if (isset($criteria['providerId'])) {
1406 $where[] = 'a.providerId = :providerId';
1407 $params[':providerId'] = $criteria['providerId'];
1408 }
1409
1410 if (array_key_exists('status', $criteria)) {
1411 $where[] = 'a.status = :status';
1412
1413 $params[':status'] = $criteria['status'];
1414 }
1415
1416 if (!empty($criteria['locations'])) {
1417 $queryLocations = [];
1418
1419 foreach ((array)$criteria['locations'] as $index => $value) {
1420 $param = ':location' . $index;
1421
1422 $queryLocations[] = $param;
1423
1424 $params[$param] = $value;
1425 }
1426
1427 $where[] = 'a.locationId IN (' . implode(', ', $queryLocations) . ')';
1428 }
1429
1430 $limit = $this->getLimit(
1431 !empty($criteria['page']) ? (int)$criteria['page'] : 0,
1432 (int)$itemsPerPage
1433 );
1434
1435 if (!empty($whereOr)) {
1436 $where[] = '(' . implode(' OR ', $whereOr) . ')';
1437 }
1438
1439 $where = $where ? 'WHERE ' . implode(' AND ', $where) : '';
1440
1441 try {
1442 $statement = $this->connection->prepare(
1443 "SELECT
1444 a.id AS appointment_id,
1445 a.bookingStart AS appointment_bookingStart,
1446 a.bookingEnd AS appointment_bookingEnd,
1447 a.notifyParticipants AS appointment_notifyParticipants,
1448 a.internalNotes AS appointment_internalNotes,
1449 a.status AS appointment_status,
1450 a.serviceId AS appointment_serviceId,
1451 a.providerId AS appointment_providerId,
1452 a.locationId AS appointment_locationId,
1453 a.googleCalendarEventId AS appointment_google_calendar_event_id,
1454 a.googleMeetUrl AS appointment_google_meet_url,
1455 a.outlookCalendarEventId AS appointment_outlook_calendar_event_id,
1456 a.microsoftTeamsUrl AS appointment_microsoft_teams_url,
1457 a.appleCalendarEventId AS appointment_apple_calendar_event_id,
1458 a.zoomMeeting AS appointment_zoom_meeting,
1459 a.lessonSpace AS appointment_lesson_space,
1460 a.parentId AS appointment_parentId
1461 FROM {$this->table} a
1462 {$bookingsJoin}
1463 {$where}
1464 GROUP BY a.id
1465 ORDER BY a.bookingStart
1466 {$limit}
1467 "
1468 );
1469
1470 $statement->execute($params);
1471
1472 $rows = $statement->fetchAll();
1473 } catch (\Exception $e) {
1474 throw new QueryExecutionException('Unable to find by id in ' . __CLASS__, $e->getCode(), $e);
1475 }
1476
1477 return call_user_func([static::FACTORY, 'createCollection'], $rows);
1478 }
1479
1480 /**
1481 * @param array $criteria
1482 * @return int
1483 * @throws QueryExecutionException
1484 */
1485 public function getPeriodAppointmentsCount($criteria)
1486 {
1487 $params = [];
1488
1489 $where = [];
1490
1491 if (!empty($criteria['dates'])) {
1492 if (isset($criteria['dates'][0], $criteria['dates'][1])) {
1493 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') BETWEEN :bookingFrom AND :bookingTo)";
1494
1495 $params[':bookingFrom'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][0]);
1496
1497 $params[':bookingTo'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][1]);
1498 } elseif (isset($criteria['dates'][0])) {
1499 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') >= :bookingFrom)";
1500
1501 $params[':bookingFrom'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][0]);
1502 } elseif (isset($criteria['dates'][1])) {
1503 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') <= :bookingTo)";
1504
1505 $params[':bookingTo'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][1]);
1506 } else {
1507 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') > :bookingFrom)";
1508
1509 $params[':bookingFrom'] = DateTimeService::getNowDateTimeInUtc();
1510 }
1511 }
1512
1513 $whereOr = [];
1514 if (!empty($criteria['services'])) {
1515 $queryServices = [];
1516
1517 foreach ((array)$criteria['services'] as $index => $value) {
1518 $param = ':service' . $index;
1519
1520 $queryServices[] = $param;
1521
1522 $params[$param] = $value;
1523 }
1524
1525 $where[] = 'a.serviceId IN (' . implode(', ', $queryServices) . ')';
1526 }
1527
1528 if (!empty($criteria['providers'])) {
1529 $queryProviders = [];
1530
1531 foreach ((array)$criteria['providers'] as $index => $value) {
1532 $param = ':provider' . $index;
1533
1534 $queryProviders[] = $param;
1535
1536 $params[$param] = $value;
1537 }
1538
1539 $where[] = 'a.providerId IN (' . implode(', ', $queryProviders) . ')';
1540 }
1541
1542 if (!empty($criteria['customers'])) {
1543 $queryCustomers = [];
1544
1545 foreach ((array)$criteria['customers'] as $index => $value) {
1546 $param = ':customer' . $index;
1547
1548 $queryCustomers[] = $param;
1549
1550 $params[$param] = $value;
1551 }
1552
1553 $whereOr[] = 'cb.customerId IN (' . implode(', ', $queryCustomers) . ')';
1554 }
1555
1556 if (isset($criteria['customerId'])) {
1557 $where[] = 'cb.customerId = :customerId';
1558 $params[':customerId'] = $criteria['customerId'];
1559 }
1560
1561 if (isset($criteria['providerId'])) {
1562 $where[] = 'a.providerId = :providerId';
1563 $params[':providerId'] = $criteria['providerId'];
1564 }
1565
1566 if (array_key_exists('status', $criteria)) {
1567 $where[] = 'a.status = :status';
1568
1569 $params[':status'] = $criteria['status'];
1570 }
1571
1572 $customerBookingJoin = !empty($criteria['customers']) || isset($criteria['customerId']) ?
1573 "INNER JOIN {$this->bookingsTable} cb ON cb.appointmentId = a.id" : '';
1574
1575 if (!empty($whereOr)) {
1576 $where[] = '(' . implode(' OR ', $whereOr) . ')';
1577 }
1578
1579 $where = $where ? 'WHERE ' . implode(' AND ', $where) : '';
1580
1581 try {
1582 $statement = $this->connection->prepare(
1583 "SELECT
1584 COUNT(*) AS count
1585 FROM {$this->table} a
1586 {$customerBookingJoin}
1587 {$where}
1588 ORDER BY a.bookingStart
1589 "
1590 );
1591
1592 $statement->execute($params);
1593
1594 $rows = $statement->fetch()['count'];
1595 } catch (\Exception $e) {
1596 throw new QueryExecutionException('Unable to find by id in ' . __CLASS__, $e->getCode(), $e);
1597 }
1598
1599 return $rows;
1600 }
1601
1602 /**
1603 * @param Service $service
1604 * @param int $customerId
1605 * @param \DateTime $appointmentStart
1606 * @param int $bookingId
1607 * @return Collection
1608 * @throws QueryExecutionException
1609 */
1610 public function getRelevantAppointmentsCount($service, $customerId, $appointmentStart, $limitPerCustomer, $serviceSpecific, $bookingId = null)
1611 {
1612 $params = [
1613 ':customerId' => $customerId
1614 ];
1615
1616 $paymentTableJoin = '';
1617 $compareToDate = 'a.bookingStart';
1618
1619 if ($limitPerCustomer['from'] === 'bookingDate') {
1620 $appointmentStart = DateTimeService::getCustomDateTimeObject(
1621 $appointmentStart->format('Y-m-d H:i')
1622 )->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i');
1623 } else {
1624 $paymentTableJoin = 'INNER JOIN ' . $this->paymentsTable . ' p ON p.customerBookingId = cb.id';
1625 $appointmentStart = DateTimeService::getNowDateTimeObject()->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i');
1626 $compareToDate = 'p.created';
1627 }
1628
1629 $intervalString = "interval " . $limitPerCustomer['period'] . " " . $limitPerCustomer['timeFrame'];
1630
1631 $where = "(STR_TO_DATE('" . $appointmentStart . "', '%Y-%m-%d %H:%i:%s') BETWEEN " .
1632 "(" . $compareToDate . " - " . $intervalString . " + interval 1 second)"
1633 . " AND (".
1634 $compareToDate . " + " . $intervalString . " - interval 1 second))"; //+ interval 2 day
1635
1636 if ($serviceSpecific) {
1637 $where .= " AND a.serviceId = :serviceId";
1638 $params[':serviceId'] = $service->getId()->getValue();
1639 }
1640
1641 if ($bookingId) {
1642 $where .= " AND cb.id <> :bookingId";
1643 $params[':bookingId'] = $bookingId;
1644 }
1645
1646 try {
1647 $statement = $this->connection->prepare(
1648 "SELECT COUNT(DISTINCT a.id) AS count
1649 FROM {$this->table} a
1650 INNER JOIN {$this->bookingsTable} cb
1651 ON cb.appointmentId = a.id
1652 {$paymentTableJoin}
1653 WHERE cb.customerId = :customerId AND {$where} AND (a.status = 'approved' OR a.status = 'pending') AND (cb.status = 'approved' OR cb.status = 'pending')
1654 "
1655 );
1656
1657 $statement->execute($params);
1658
1659 $rows = $statement->fetch()['count'];
1660 } catch (\Exception $e) {
1661 throw new QueryExecutionException('Unable to find by id in ' . __CLASS__, $e->getCode(), $e);
1662 }
1663
1664 return $rows;
1665 }
1666
1667 /**
1668 * @param $providerIds
1669 *
1670 * @return array
1671 * @throws QueryExecutionException
1672 */
1673 public function getLastBookedEmployee($providerIds)
1674 {
1675 try {
1676 $params = [];
1677
1678 $queryProviders = [];
1679
1680 $where = '';
1681
1682 if (!empty($providerIds)) {
1683 foreach ($providerIds as $index => $value) {
1684 $param = ':provider' . $index;
1685
1686 $queryProviders[] = $param;
1687
1688 $params[$param] = $value;
1689 }
1690
1691 $where = ' AND a.providerId IN (' . implode(', ', $queryProviders) . ')';
1692 }
1693
1694 $statement = $this->connection->prepare(
1695 "SELECT a.providerId
1696 FROM {$this->table} a
1697 JOIN {$this->bookingsTable} cb ON cb.appointmentId = a.id
1698 WHERE (a.status = 'approved' OR a.status = 'pending') AND (cb.status = 'approved' OR cb.status = 'pending')
1699 {$where}
1700 ORDER BY cb.created DESC, a.id DESC LIMIT 1;
1701 "
1702 );
1703
1704 $statement->execute($params);
1705
1706 $rows = $statement->fetchAll(\PDO::FETCH_COLUMN);
1707 } catch (\Exception $e) {
1708 throw new QueryExecutionException('Unable to find by id in ' . __CLASS__, $e->getCode(), $e);
1709 }
1710
1711 return !empty($rows) ? $rows[0] : $providerIds[0];
1712 }
1713 }
1714