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