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