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