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