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