PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 1.2.11
Booking for Appointments and Events Calendar – Amelia v1.2.11
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
1672 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 ];
427
428 try {
429 $statement = $this->connection->prepare(
430 "INSERT INTO {$this->table}
431 (
432 `bookingStart`,
433 `bookingEnd`,
434 `notifyParticipants`,
435 `internalNotes`,
436 `status`,
437 `locationId`,
438 `serviceId`,
439 `providerId`,
440 `parentId`,
441 `lessonSpace`
442 )
443 VALUES (
444 :bookingStart,
445 :bookingEnd,
446 :notifyParticipants,
447 :internalNotes,
448 :status,
449 :locationId,
450 :serviceId,
451 :providerId,
452 :parentId,
453 :lessonSpace
454 )"
455 );
456
457 $res = $statement->execute($params);
458
459 if (!$res) {
460 throw new QueryExecutionException('Unable to add data in ' . __CLASS__);
461 }
462
463 return $this->connection->lastInsertId();
464 } catch (\Exception $e) {
465 throw new QueryExecutionException('Unable to add data in ' . __CLASS__, $e->getCode(), $e);
466 }
467 }
468
469 /**
470 * @param int $id
471 * @param Appointment $entity
472 *
473 * @return mixed
474 * @throws QueryExecutionException
475 */
476 public function update($id, $entity)
477 {
478 $data = $entity->toArray();
479
480 $params = [
481 ':id' => $id,
482 ':bookingStart' => DateTimeService::getCustomDateTimeInUtc($data['bookingStart']),
483 ':bookingEnd' => DateTimeService::getCustomDateTimeInUtc($data['bookingEnd']),
484 ':notifyParticipants' => $data['notifyParticipants'],
485 ':internalNotes' => $data['internalNotes'],
486 ':status' => $data['status'],
487 ':locationId' => $data['locationId'],
488 ':serviceId' => $data['serviceId'],
489 ':providerId' => $data['providerId'],
490 ':googleCalendarEventId' => $data['googleCalendarEventId'],
491 ':googleMeetUrl' => $data['googleMeetUrl'],
492 ':outlookCalendarEventId' => $data['outlookCalendarEventId'],
493 ':lessonSpace' => $data['lessonSpace'],
494 ];
495
496 try {
497 $statement = $this->connection->prepare(
498 "UPDATE {$this->table}
499 SET
500 `bookingStart` = :bookingStart,
501 `bookingEnd` = :bookingEnd,
502 `notifyParticipants` = :notifyParticipants,
503 `internalNotes` = :internalNotes,
504 `status` = :status,
505 `locationId` = :locationId,
506 `serviceId` = :serviceId,
507 `providerId` = :providerId,
508 `googleCalendarEventId` = :googleCalendarEventId,
509 `googleMeetUrl` = :googleMeetUrl,
510 `outlookCalendarEventId` = :outlookCalendarEventId,
511 `lessonSpace` = :lessonSpace
512 WHERE id = :id"
513 );
514
515 $res = $statement->execute($params);
516
517 if (!$res) {
518 throw new QueryExecutionException('Unable to save data in ' . __CLASS__);
519 }
520
521 return $res;
522 } catch (\Exception $e) {
523 throw new QueryExecutionException('Unable to save data in ' . __CLASS__, $e->getCode(), $e);
524 }
525 }
526
527 /**
528 * @param int $id
529 * @param int $status
530 *
531 * @return mixed
532 * @throws QueryExecutionException
533 */
534 public function updateStatusById($id, $status)
535 {
536 $params = [
537 ':id' => $id,
538 ':status' => $status
539 ];
540
541 try {
542 $statement = $this->connection->prepare(
543 "UPDATE {$this->table}
544 SET
545 `status` = :status
546 WHERE id = :id"
547 );
548
549 $res = $statement->execute($params);
550
551 if (!$res) {
552 throw new QueryExecutionException('Unable to save data in ' . __CLASS__);
553 }
554
555 return $res;
556 } catch (\Exception $e) {
557 throw new QueryExecutionException('Unable to save data in ' . __CLASS__, $e->getCode(), $e);
558 }
559 }
560
561 /**
562 * Returns array of current appointments where keys are Provider ID's
563 * and array values are Appointments Data (modified by service padding time)
564 *
565 * @return array
566 * @throws QueryExecutionException
567 */
568 public function getCurrentAppointments()
569 {
570 try {
571 $currentDateTime = "STR_TO_DATE('" . DateTimeService::getNowDateTimeInUtc() . "', '%Y-%m-%d %H:%i:%s')";
572
573 $statement = $this->connection->query(
574 "SELECT
575 a.bookingStart AS bookingStart,
576 a.bookingEnd AS bookingEnd,
577 a.providerId AS providerId,
578 a.serviceId AS serviceId,
579 s.timeBefore AS timeBefore,
580 s.timeAfter AS timeAfter
581 FROM {$this->table} a
582 INNER JOIN {$this->servicesTable} s ON s.id = a.serviceId
583 WHERE {$currentDateTime} >= a.bookingStart
584 AND {$currentDateTime} <= a.bookingEnd
585 ORDER BY a.bookingStart"
586 );
587
588 $rows = $statement->fetchAll();
589 } catch (\Exception $e) {
590 throw new QueryExecutionException('Unable to find appointments in ' . __CLASS__, $e->getCode(), $e);
591 }
592
593 $result = [];
594
595 foreach ($rows as $row) {
596 $row['bookingStart'] = DateTimeService::getCustomDateTimeObjectFromUtc($row['bookingStart'])
597 ->modify('-' . ($row['timeBefore'] ?: '0') . ' seconds')
598 ->format('Y-m-d H:i:s');
599
600 $row['bookingEnd'] = DateTimeService::getCustomDateTimeObjectFromUtc($row['bookingEnd'])
601 ->modify('+' . ($row['timeAfter'] ?: '0') . ' seconds')
602 ->format('Y-m-d H:i:s');
603
604 $result[$row['providerId']] = $row;
605 }
606
607 return $result;
608 }
609
610 /**
611 * @param Collection $collection
612 * @param array $providerIds
613 * @param string $startDateTime
614 * @param string $endDateTime
615 * @return void
616 * @throws QueryExecutionException
617 */
618 public function getFutureAppointments($collection, $providerIds, $startDateTime, $endDateTime)
619 {
620 $params = [];
621
622 $where = [
623 "a.status IN ('approved', 'pending')",
624 "cb.status IN ('approved', 'pending')",
625 "a.bookingStart >= STR_TO_DATE('{$startDateTime}', '%Y-%m-%d %H:%i:%s')",
626 ];
627
628 if ($endDateTime) {
629 $where[] = "a.bookingStart <= STR_TO_DATE('{$endDateTime}', '%Y-%m-%d %H:%i:%s')";
630 }
631
632 if (!empty($providerIds)) {
633 $queryProviders = [];
634
635 foreach ($providerIds as $index => $value) {
636 $param = ':provider' . $index;
637
638 $queryProviders[] = $param;
639
640 $params[$param] = $value;
641 }
642
643 $where[] = 'a.providerId IN (' . implode(', ', $queryProviders) . ')';
644 }
645
646 $where = $where ? 'WHERE ' . implode(' AND ', $where) : '';
647
648 try {
649 $statement = $this->connection->prepare(
650 "SELECT
651 a.id AS id,
652 a.bookingStart AS bookingStart,
653 a.bookingEnd AS bookingEnd,
654 a.providerId AS providerId,
655 a.serviceId AS serviceId,
656 a.locationId AS locationId,
657 a.status AS status,
658
659 cb.id AS bookingId,
660 cb.customerId AS customerId,
661 cb.status AS bookingStatus,
662 cb.persons AS persons
663
664 FROM {$this->table} a
665 INNER JOIN {$this->bookingsTable} cb ON cb.appointmentId = a.id
666 {$where}
667 ORDER BY a.bookingStart
668 "
669 );
670
671 $statement->execute($params);
672
673 while ($row = $statement->fetch()) {
674 $id = (int)$row['id'];
675
676 $bookingId = (int)$row['bookingId'];
677
678 if (!$collection->keyExists($id)) {
679 $collection->addItem(
680 AppointmentFactory::create(
681 [
682 'id' => $id,
683 'bookingStart' => DateTimeService::getCustomDateTimeFromUtc(
684 $row['bookingStart']
685 ),
686 'bookingEnd' => DateTimeService::getCustomDateTimeFromUtc(
687 $row['bookingEnd']
688 ),
689 'providerId' => $row['providerId'],
690 'serviceId' => $row['serviceId'],
691 'locationId' => $row['locationId'],
692 'status' => $row['status'],
693 'bookings' => [],
694 'notifyParticipants' => false
695 ]
696 ),
697 $id
698 );
699 }
700
701 if (!$collection->getItem($id)->getBookings()->keyExists($bookingId)) {
702 $collection->getItem($id)->getBookings()->addItem(
703 CustomerBookingFactory::create(
704 [
705 'id' => $bookingId,
706 'customerId' => $row['customerId'],
707 'status' => $row['bookingStatus'],
708 'persons' => $row['persons'],
709 ]
710 ),
711 $bookingId
712 );
713 }
714 }
715 } catch (\Exception $e) {
716 throw new QueryExecutionException('Unable to find appointments in ' . __CLASS__, $e->getCode(), $e);
717 }
718 }
719
720 /**
721 * @param array $providerIds
722 * @param string $startDateTime
723 * @param string $endDateTime
724 * @return array
725 * @throws QueryExecutionException
726 */
727 public function getFutureAppointmentsServicesIds($providerIds, $startDateTime, $endDateTime)
728 {
729 $params = [];
730
731 $where = [];
732
733 if ($startDateTime) {
734 $where = ["bookingStart >= STR_TO_DATE('{$startDateTime}', '%Y-%m-%d %H:%i:%s')"];
735 }
736
737 if ($endDateTime) {
738 $where = ["bookingStart <= STR_TO_DATE('{$endDateTime}', '%Y-%m-%d %H:%i:%s')"];
739 }
740
741 if (!empty($providerIds)) {
742 $queryProviders = [];
743
744 foreach ($providerIds as $index => $value) {
745 $param = ':provider' . $index;
746
747 $queryProviders[] = $param;
748
749 $params[$param] = $value;
750 }
751
752 $where[] = 'providerId IN (' . implode(', ', $queryProviders) . ')';
753 }
754
755 $where = $where ? 'WHERE ' . implode(' AND ', $where) : '';
756
757 try {
758 $statement = $this->connection->prepare("SELECT DISTINCT(serviceId) FROM {$this->table} {$where}");
759
760 $statement->execute($params);
761
762 $rows = $statement->fetchAll();
763 } catch (\Exception $e) {
764 throw new QueryExecutionException('Unable to find appointments in ' . __CLASS__, $e->getCode(), $e);
765 }
766
767 return $rows ? array_column($rows, 'serviceId') : [];
768 }
769
770 /**
771 * @param array $serviceIds
772 * @param string $startDateTime
773 * @param string $endDateTime
774 * @return array
775 * @throws QueryExecutionException
776 */
777 public function getFutureAppointmentsProvidersIds($serviceIds, $startDateTime, $endDateTime)
778 {
779 $params = [];
780
781 $where = [];
782
783 if ($startDateTime) {
784 $where = ["bookingStart >= STR_TO_DATE('{$startDateTime}', '%Y-%m-%d %H:%i:%s')"];
785 }
786
787 if ($endDateTime) {
788 $where = ["bookingStart <= STR_TO_DATE('{$endDateTime}', '%Y-%m-%d %H:%i:%s')"];
789 }
790
791 if (!empty($serviceIds)) {
792 $queryServices = [];
793
794 foreach ($serviceIds as $index => $value) {
795 $param = ':service' . $index;
796
797 $queryServices[] = $param;
798
799 $params[$param] = $value;
800 }
801
802 $where[] = 'serviceId IN (' . implode(', ', $queryServices) . ')';
803 }
804
805 $where = $where ? 'WHERE ' . implode(' AND ', $where) : '';
806
807 try {
808 $statement = $this->connection->prepare("SELECT DISTINCT(providerId) FROM {$this->table} {$where}");
809
810 $statement->execute($params);
811
812 $rows = $statement->fetchAll();
813 } catch (\Exception $e) {
814 throw new QueryExecutionException('Unable to find appointments in ' . __CLASS__, $e->getCode(), $e);
815 }
816
817 return $rows ? array_column($rows, 'providerId') : [];
818 }
819
820 /**
821 * @param array $criteria
822 *
823 * @return Collection
824 * @throws QueryExecutionException
825 */
826 public function getFiltered($criteria)
827 {
828 try {
829 $params = [];
830
831 $where = [];
832
833 if (!empty($criteria['dates'])) {
834 if (isset($criteria['dates'][0], $criteria['dates'][1])) {
835 $whereStart = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') BETWEEN :bookingFrom AND :bookingTo)";
836
837 $params[':bookingFrom'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][0]);
838
839 $params[':bookingTo'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][1]);
840
841 $whereEnd = '';
842 if (!empty($criteria['endsInDateRange'])) {
843 $whereEnd = "OR (DATE_FORMAT(a.bookingEnd, '%Y-%m-%d %H:%i:%s') BETWEEN :bookingFrom2 AND :bookingTo2)";
844 $params[':bookingFrom2'] = $params[':bookingFrom'];
845 $params[':bookingTo2'] = $params[':bookingTo'];
846 }
847
848 $where[] = "({$whereStart} {$whereEnd})";
849 } elseif (isset($criteria['dates'][0])) {
850 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') >= :bookingFrom)";
851
852 $params[':bookingFrom'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][0]);
853 } elseif (isset($criteria['dates'][1])) {
854 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') <= :bookingTo)";
855
856 $params[':bookingTo'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][1]);
857 } else {
858 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') > :bookingFrom)";
859
860 $params[':bookingFrom'] = DateTimeService::getNowDateTimeInUtc();
861 }
862 }
863
864 if (!empty($criteria['ids'])) {
865 $queryAppointments = [];
866
867 foreach ((array)$criteria['ids'] as $index => $value) {
868 $param = ':id' . $index;
869
870 $queryAppointments[] = $param;
871
872 $params[$param] = $value;
873 }
874
875 $where[] = 'a.id IN (' . implode(', ', $queryAppointments) . ')';
876 }
877
878 if (!empty($criteria['services'])) {
879 $queryServices = [];
880
881 foreach ((array)$criteria['services'] as $index => $value) {
882 $param = ':service' . $index;
883
884 $queryServices[] = $param;
885
886 $params[$param] = $value;
887 }
888
889 $where[] = 'a.serviceId IN (' . implode(', ', $queryServices) . ')';
890 }
891
892 if (!empty($criteria['providers'])) {
893 $queryProviders = [];
894
895 foreach ((array)$criteria['providers'] as $index => $value) {
896 $param = ':provider' . $index;
897
898 $queryProviders[] = $param;
899
900 $params[$param] = $value;
901 }
902
903 $where[] = 'a.providerId IN (' . implode(', ', $queryProviders) . ')';
904 }
905
906 if (!empty($criteria['customers'])) {
907 $queryCustomers = [];
908
909 foreach ((array)$criteria['customers'] as $index => $value) {
910 $param = ':customer' . $index;
911
912 $queryCustomers[] = $param;
913
914 $params[$param] = $value;
915 }
916
917 $where[] = 'cb.customerId IN (' . implode(', ', $queryCustomers) . ')';
918 }
919
920 if (isset($criteria['customerId'])) {
921 $where[] = 'cb.customerId = :customerId';
922 $params[':customerId'] = $criteria['customerId'];
923 }
924
925
926 if (isset($criteria['providerId'])) {
927 $where[] = 'a.providerId = :providerId';
928 $params[':providerId'] = $criteria['providerId'];
929 }
930
931 if (array_key_exists('status', $criteria)) {
932 $where[] = 'a.status = :status';
933
934 $params[':status'] = $criteria['status'];
935 }
936
937 if (!empty($criteria['statuses'])) {
938 $queryStatuses = [];
939
940 foreach ($criteria['statuses'] as $index => $value) {
941 $param = ':statuses' . $index;
942
943 $queryStatuses[] = $param;
944
945 $params[$param] = $value;
946 }
947
948 $where[] = 'a.status IN (' . implode(', ', $queryStatuses) . ')';
949 }
950
951 if (array_key_exists('bookingStatus', $criteria)) {
952 $where[] = 'cb.status = :bookingStatus';
953 $params[':bookingStatus'] = $criteria['bookingStatus'];
954 }
955
956 if (array_key_exists('bookingStatuses', $criteria)) {
957 $queryStatuses = [];
958
959 foreach ($criteria['bookingStatuses'] as $index => $value) {
960 $param = ':bookingStatuses' . $index;
961
962 $queryStatuses[] = $param;
963
964 $params[$param] = $value;
965 }
966
967 $where[] = 'cb.status IN (' . implode(', ', $queryStatuses) . ')';
968 }
969
970 if (!empty($criteria['locations'])) {
971 $queryLocations = [];
972
973 foreach ((array)$criteria['locations'] as $index => $value) {
974 $param = ':location' . $index;
975
976 $queryLocations[] = $param;
977
978 $params[$param] = $value;
979 }
980
981 $where[] = 'a.locationId IN (' . implode(', ', $queryLocations) . ')';
982 }
983
984 if (isset($criteria['bookingId'])) {
985 $where[] = 'cb.id = :bookingId';
986 $params[':bookingId'] = $criteria['bookingId'];
987 }
988
989 if (isset($criteria['bookingIds'])) {
990 $queryBookings = [];
991
992 foreach ((array)$criteria['bookingIds'] as $index => $value) {
993 $param = ':bookingId' . $index;
994
995 $queryBookings[] = $param;
996
997 $params[$param] = $value;
998 }
999
1000 $where[] = 'cb.id IN (' . implode(', ', $queryBookings) . ')';
1001 }
1002
1003 if (isset($criteria['bookingCouponId'])) {
1004 $where[] = 'cb.couponId = :bookingCouponId';
1005 $params[':bookingCouponId'] = $criteria['bookingCouponId'];
1006 }
1007
1008 if (isset($criteria['parentId'])) {
1009 $where[] = 'a.parentId = :parentId';
1010 $params[':parentId'] = $criteria['parentId'];
1011 }
1012
1013 if (!empty($criteria['packageCustomerServices'])) {
1014 $queryLocations = [];
1015
1016 foreach ($criteria['packageCustomerServices'] as $index => $value) {
1017 $param = ':packageCustomerServices' . $index;
1018
1019 $queryLocations[] = $param;
1020
1021 $params[$param] = $value;
1022 }
1023
1024 $where[] = 'cb.packageCustomerServiceId IN (' . implode(', ', $queryLocations) . ')';
1025 }
1026
1027 $packagesJoin = '';
1028 if (isset($criteria['packageId'])) {
1029 $where[] = 'pc.packageId = :packageId';
1030 $params[':packageId'] = $criteria['packageId'];
1031
1032 $packagesJoin = "LEFT JOIN {$this->packagesCustomersServicesTable} pcs ON pcs.id = cb.packageCustomerServiceId
1033 LEFT JOIN {$this->packagesCustomersTable} pc ON pcs.packageCustomerId = pc.id";
1034 } elseif (!empty($criteria['packageCustomerId'])) {
1035 $where[] = 'pc.id = :packageCustomerId';
1036 $params[':packageCustomerId'] = $criteria['packageCustomerId'];
1037
1038 $packagesJoin = "LEFT JOIN {$this->packagesCustomersServicesTable} pcs ON pcs.id = cb.packageCustomerServiceId
1039 LEFT JOIN {$this->packagesCustomersTable} pc ON pcs.packageCustomerId = pc.id";
1040 }
1041
1042
1043 $servicesFields = '
1044 s.id AS service_id,
1045 s.name AS service_name,
1046 s.description AS service_description,
1047 s.color AS service_color,
1048 s.price AS service_price,
1049 s.status AS service_status,
1050 s.categoryId AS service_categoryId,
1051 s.minCapacity AS service_minCapacity,
1052 s.maxCapacity AS service_maxCapacity,
1053 s.timeAfter AS service_timeAfter,
1054 s.timeBefore AS service_timeBefore,
1055 s.duration AS service_duration,
1056 s.settings AS service_settings,
1057 ';
1058
1059 $servicesJoin = "INNER JOIN {$this->servicesTable} s ON s.id = a.serviceId";
1060
1061 if (!empty($criteria['skipServices'])) {
1062 $servicesFields = '';
1063
1064 $servicesJoin = '';
1065 }
1066
1067 $providersFields = '
1068 pu.id AS provider_id,
1069 pu.firstName AS provider_firstName,
1070 pu.lastName AS provider_lastName,
1071 pu.email AS provider_email,
1072 pu.note AS provider_note,
1073 pu.description AS provider_description,
1074 pu.phone AS provider_phone,
1075 pu.gender AS provider_gender,
1076 pu.translations AS provider_translations,
1077 pu.timeZone AS provider_timeZone,
1078 ';
1079
1080 $providersJoin = "INNER JOIN {$this->usersTable} pu ON pu.id = a.providerId";
1081
1082 if (!empty($criteria['skipProviders'])) {
1083 $providersFields = '';
1084
1085 $providersJoin = '';
1086 }
1087
1088 $locationsTable = LocationsTable::getTableName();
1089
1090 $locationsFields = '';
1091
1092 $locationsJoin = '';
1093
1094 if (!empty($criteria['withLocations'])) {
1095 $locationsFields = '
1096 l.id AS location_id,
1097 l.name AS location_name,
1098 l.address AS location_address,
1099 ';
1100
1101 $locationsJoin = "LEFT JOIN {$locationsTable} l ON l.id = a.locationId";
1102 }
1103
1104 $customersFields = '
1105 cu.id AS customer_id,
1106 cu.firstName AS customer_firstName,
1107 cu.lastName AS customer_lastName,
1108 cu.email AS customer_email,
1109 cu.note AS customer_note,
1110 cu.phone AS customer_phone,
1111 cu.gender AS customer_gender,
1112 cu.status AS customer_status,
1113 ';
1114
1115 $customersJoin = "INNER JOIN {$this->usersTable} cu ON cu.id = cb.customerId";
1116
1117 if (!empty($criteria['skipCustomers'])) {
1118 $customersFields = '';
1119
1120 $customersJoin = '';
1121 }
1122
1123 $paymentsFields = '
1124 p.id AS payment_id,
1125 p.packageCustomerId AS payment_packageCustomerId,
1126 p.amount AS payment_amount,
1127 p.dateTime AS payment_dateTime,
1128 p.status AS payment_status,
1129 p.gateway AS payment_gateway,
1130 p.gatewayTitle AS payment_gatewayTitle,
1131 p.transactionId AS payment_transactionId,
1132 p.data AS payment_data,
1133 p.parentId AS payment_parentId,
1134 p.wcOrderId AS payment_wcOrderId,
1135 p.wcOrderItemId AS payment_wcOrderItemId,
1136 p.created AS payment_created,
1137 ';
1138
1139 $paymentsJoin = "LEFT JOIN {$this->paymentsTable} p ON p.customerBookingId = cb.id";
1140
1141 if (!empty($criteria['skipPayments'])) {
1142 $paymentsFields = '';
1143
1144 $paymentsJoin = '';
1145 }
1146
1147 $bookingExtrasFields = '
1148 cbe.id AS bookingExtra_id,
1149 cbe.extraId AS bookingExtra_extraId,
1150 cbe.customerBookingId AS bookingExtra_customerBookingId,
1151 cbe.quantity AS bookingExtra_quantity,
1152 cbe.price AS bookingExtra_price,
1153 cbe.tax AS bookingExtra_tax,
1154 cbe.aggregatedPrice AS bookingExtra_aggregatedPrice,
1155 ';
1156
1157 $bookingExtrasJoin = "LEFT JOIN {$this->customerBookingsExtrasTable} cbe ON cbe.customerBookingId = cb.id";
1158
1159 if (!empty($criteria['skipExtras'])) {
1160 $bookingExtrasFields = '';
1161
1162 $bookingExtrasJoin = '';
1163 }
1164
1165 $couponsFields = '
1166 c.id AS coupon_id,
1167 c.code AS coupon_code,
1168 c.discount AS coupon_discount,
1169 c.deduction AS coupon_deduction,
1170 c.expirationDate AS coupon_expirationDate,
1171 c.limit AS coupon_limit,
1172 c.customerLimit AS coupon_customerLimit,
1173 c.status AS coupon_status,
1174 ';
1175
1176 $couponsJoin = "LEFT JOIN {$this->couponsTable} c ON c.id = cb.couponId";
1177
1178 if (!empty($criteria['skipCoupons'])) {
1179 $couponsFields = '';
1180
1181 $couponsJoin = '';
1182 }
1183
1184 $where = $where ? 'WHERE ' . implode(' AND ', $where) : '';
1185
1186 $statement = $this->connection->prepare(
1187 "SELECT
1188 a.id AS appointment_id,
1189 a.bookingStart AS appointment_bookingStart,
1190 a.bookingEnd AS appointment_bookingEnd,
1191 a.notifyParticipants AS appointment_notifyParticipants,
1192 a.internalNotes AS appointment_internalNotes,
1193 a.status AS appointment_status,
1194 a.serviceId AS appointment_serviceId,
1195 a.providerId AS appointment_providerId,
1196 a.locationId AS appointment_locationId,
1197 a.googleCalendarEventId AS appointment_google_calendar_event_id,
1198 a.googleMeetUrl AS appointment_google_meet_url,
1199 a.outlookCalendarEventId AS appointment_outlook_calendar_event_id,
1200 a.zoomMeeting AS appointment_zoom_meeting,
1201 a.lessonSpace AS appointment_lesson_space,
1202 a.parentId AS appointment_parentId,
1203
1204 {$customersFields}
1205 {$bookingExtrasFields}
1206 {$providersFields}
1207 {$locationsFields}
1208 {$servicesFields}
1209 {$paymentsFields}
1210 {$couponsFields}
1211
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 FROM {$this->table} a
1227 INNER JOIN {$this->bookingsTable} cb ON cb.appointmentId = a.id
1228 {$packagesJoin}
1229 {$customersJoin}
1230 {$providersJoin}
1231 {$locationsJoin}
1232 {$servicesJoin}
1233 {$paymentsJoin}
1234 {$bookingExtrasJoin}
1235 {$couponsJoin}
1236 {$where}
1237 ORDER BY a.bookingStart"
1238 );
1239
1240 $statement->execute($params);
1241
1242 $rows = $statement->fetchAll();
1243 } catch (\Exception $e) {
1244 throw new QueryExecutionException('Unable to find by id in ' . __CLASS__, $e->getCode(), $e);
1245 }
1246
1247 return call_user_func([static::FACTORY, 'createCollection'], $rows);
1248 }
1249
1250 /**
1251 * @return Collection $criteria
1252 * @throws QueryExecutionException
1253 */
1254 public function getAppointmentsWithoutBookings()
1255 {
1256 try {
1257 $statement = $this->connection->query(
1258 "SELECT
1259 a.id AS appointment_id,
1260 a.bookingStart AS appointment_bookingStart,
1261 a.bookingEnd AS appointment_bookingEnd,
1262 a.providerId AS appointment_providerId,
1263 a.serviceId AS appointment_serviceId,
1264 a.status AS appointment_status,
1265 a.googleCalendarEventId as appointment_google_calendar_event_id,
1266 a.googleMeetUrl AS appointment_google_meet_url,
1267 a.outlookCalendarEventId AS appointment_outlook_calendar_event_id,
1268 a.notifyParticipants AS appointment_notifyParticipants
1269 FROM {$this->table} a WHERE (
1270 SELECT COUNT(*) FROM {$this->bookingsTable} cb WHERE a.id = cb.appointmentId
1271 ) = 0"
1272 );
1273
1274 $rows = $statement->fetchAll();
1275 } catch (\Exception $e) {
1276 throw new QueryExecutionException('Unable to find data from ' . __CLASS__, $e->getCode(), $e);
1277 }
1278
1279 return call_user_func([static::FACTORY, 'createCollection'], $rows);
1280 }
1281
1282 /**
1283 * @param array $criteria
1284 * @param null $itemsPerPage
1285 * @return Collection
1286 * @throws QueryExecutionException
1287 */
1288 public function getPeriodAppointments($criteria, $itemsPerPage = null)
1289 {
1290 $params = [];
1291
1292 $where = [];
1293
1294 if (!empty($criteria['dates'])) {
1295 if (isset($criteria['dates'][0], $criteria['dates'][1])) {
1296 $whereStart = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') BETWEEN :bookingFrom AND :bookingTo)";
1297
1298 $params[':bookingFrom'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][0]);
1299
1300 $params[':bookingTo'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][1]);
1301
1302 $whereEnd = '';
1303 if (!empty($criteria['endsInDateRange'])) {
1304 $whereEnd = "OR (DATE_FORMAT(a.bookingEnd, '%Y-%m-%d %H:%i:%s') BETWEEN :bookingFrom2 AND :bookingTo2)";
1305 $params[':bookingFrom2'] = $params[':bookingFrom'];
1306 $params[':bookingTo2'] = $params[':bookingTo'];
1307 }
1308
1309 $where[] = "({$whereStart} {$whereEnd})";
1310 } elseif (isset($criteria['dates'][0])) {
1311 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') >= :bookingFrom)";
1312
1313 $params[':bookingFrom'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][0]);
1314 } elseif (isset($criteria['dates'][1])) {
1315 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') <= :bookingTo)";
1316
1317 $params[':bookingTo'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][1]);
1318 } else {
1319 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') > :bookingFrom)";
1320
1321 $params[':bookingFrom'] = DateTimeService::getNowDateTimeInUtc();
1322 }
1323 }
1324
1325 $whereOr = [];
1326 if (!empty($criteria['services'])) {
1327 $queryServices = [];
1328
1329 foreach ((array)$criteria['services'] as $index => $value) {
1330 $param = ':service' . $index;
1331
1332 $queryServices[] = $param;
1333
1334 $params[$param] = $value;
1335 }
1336
1337 $where[] = 'a.serviceId IN (' . implode(', ', $queryServices) . ')';
1338 }
1339
1340 if (!empty($criteria['providers'])) {
1341 $queryProviders = [];
1342
1343 foreach ((array)$criteria['providers'] as $index => $value) {
1344 $param = ':provider' . $index;
1345
1346 $queryProviders[] = $param;
1347
1348 $params[$param] = $value;
1349 }
1350
1351 $where[] = 'a.providerId IN (' . implode(', ', $queryProviders) . ')';
1352 }
1353
1354 $bookingsJoin = "INNER JOIN {$this->bookingsTable} cb ON cb.appointmentId = a.id";
1355
1356 if (!empty($criteria['skipBookings'])) {
1357 $bookingsJoin = '';
1358 }
1359
1360 if (empty($criteria['skipBookings']) && !empty($criteria['customers'])) {
1361 $queryCustomers = [];
1362
1363 foreach ((array)$criteria['customers'] as $index => $value) {
1364 $param = ':customer' . $index;
1365
1366 $queryCustomers[] = $param;
1367
1368 $params[$param] = $value;
1369 }
1370
1371 $whereOr[] = 'cb.customerId IN (' . implode(', ', $queryCustomers) . ')';
1372 }
1373
1374 if (empty($criteria['skipBookings']) && isset($criteria['customerId'])) {
1375 $where[] = 'cb.customerId = :customerId';
1376 $params[':customerId'] = $criteria['customerId'];
1377 }
1378
1379 if (isset($criteria['providerId'])) {
1380 $where[] = 'a.providerId = :providerId';
1381 $params[':providerId'] = $criteria['providerId'];
1382 }
1383
1384 if (array_key_exists('status', $criteria)) {
1385 $where[] = 'a.status = :status';
1386
1387 $params[':status'] = $criteria['status'];
1388 }
1389
1390 $limit = $this->getLimit(
1391 !empty($criteria['page']) ? (int)$criteria['page'] : 0,
1392 (int)$itemsPerPage
1393 );
1394
1395 if (!empty($whereOr)) {
1396 $where[] = '(' . implode(' OR ', $whereOr) . ')';
1397 }
1398
1399 $where = $where ? 'WHERE ' . implode(' AND ', $where) : '';
1400
1401 try {
1402 $statement = $this->connection->prepare(
1403 "SELECT
1404 a.id AS appointment_id,
1405 a.bookingStart AS appointment_bookingStart,
1406 a.bookingEnd AS appointment_bookingEnd,
1407 a.notifyParticipants AS appointment_notifyParticipants,
1408 a.internalNotes AS appointment_internalNotes,
1409 a.status AS appointment_status,
1410 a.serviceId AS appointment_serviceId,
1411 a.providerId AS appointment_providerId,
1412 a.locationId AS appointment_locationId,
1413 a.googleCalendarEventId AS appointment_google_calendar_event_id,
1414 a.googleMeetUrl AS appointment_google_meet_url,
1415 a.outlookCalendarEventId AS appointment_outlook_calendar_event_id,
1416 a.zoomMeeting AS appointment_zoom_meeting,
1417 a.lessonSpace AS appointment_lesson_space,
1418 a.parentId AS appointment_parentId
1419 FROM {$this->table} a
1420 {$bookingsJoin}
1421 {$where}
1422 GROUP BY a.id
1423 ORDER BY a.bookingStart
1424 {$limit}
1425 "
1426 );
1427
1428 $statement->execute($params);
1429
1430 $rows = $statement->fetchAll();
1431 } catch (\Exception $e) {
1432 throw new QueryExecutionException('Unable to find by id in ' . __CLASS__, $e->getCode(), $e);
1433 }
1434
1435 return call_user_func([static::FACTORY, 'createCollection'], $rows);
1436 }
1437
1438 /**
1439 * @param array $criteria
1440 * @return int
1441 * @throws QueryExecutionException
1442 */
1443 public function getPeriodAppointmentsCount($criteria)
1444 {
1445 $params = [];
1446
1447 $where = [];
1448
1449 if (!empty($criteria['dates'])) {
1450 if (isset($criteria['dates'][0], $criteria['dates'][1])) {
1451 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') BETWEEN :bookingFrom AND :bookingTo)";
1452
1453 $params[':bookingFrom'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][0]);
1454
1455 $params[':bookingTo'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][1]);
1456 } elseif (isset($criteria['dates'][0])) {
1457 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') >= :bookingFrom)";
1458
1459 $params[':bookingFrom'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][0]);
1460 } elseif (isset($criteria['dates'][1])) {
1461 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') <= :bookingTo)";
1462
1463 $params[':bookingTo'] = DateTimeService::getCustomDateTimeInUtc($criteria['dates'][1]);
1464 } else {
1465 $where[] = "(DATE_FORMAT(a.bookingStart, '%Y-%m-%d %H:%i:%s') > :bookingFrom)";
1466
1467 $params[':bookingFrom'] = DateTimeService::getNowDateTimeInUtc();
1468 }
1469 }
1470
1471 $whereOr = [];
1472 if (!empty($criteria['services'])) {
1473 $queryServices = [];
1474
1475 foreach ((array)$criteria['services'] as $index => $value) {
1476 $param = ':service' . $index;
1477
1478 $queryServices[] = $param;
1479
1480 $params[$param] = $value;
1481 }
1482
1483 $where[] = 'a.serviceId IN (' . implode(', ', $queryServices) . ')';
1484 }
1485
1486 if (!empty($criteria['providers'])) {
1487 $queryProviders = [];
1488
1489 foreach ((array)$criteria['providers'] as $index => $value) {
1490 $param = ':provider' . $index;
1491
1492 $queryProviders[] = $param;
1493
1494 $params[$param] = $value;
1495 }
1496
1497 $where[] = 'a.providerId IN (' . implode(', ', $queryProviders) . ')';
1498 }
1499
1500 if (!empty($criteria['customers'])) {
1501 $queryCustomers = [];
1502
1503 foreach ((array)$criteria['customers'] as $index => $value) {
1504 $param = ':customer' . $index;
1505
1506 $queryCustomers[] = $param;
1507
1508 $params[$param] = $value;
1509 }
1510
1511 $whereOr[] = 'cb.customerId IN (' . implode(', ', $queryCustomers) . ')';
1512 }
1513
1514 if (isset($criteria['customerId'])) {
1515 $where[] = 'cb.customerId = :customerId';
1516 $params[':customerId'] = $criteria['customerId'];
1517 }
1518
1519 if (isset($criteria['providerId'])) {
1520 $where[] = 'a.providerId = :providerId';
1521 $params[':providerId'] = $criteria['providerId'];
1522 }
1523
1524 if (array_key_exists('status', $criteria)) {
1525 $where[] = 'a.status = :status';
1526
1527 $params[':status'] = $criteria['status'];
1528 }
1529
1530 $customerBookingJoin = !empty($criteria['customers']) || isset($criteria['customerId']) ?
1531 "INNER JOIN {$this->bookingsTable} cb ON cb.appointmentId = a.id" : '';
1532
1533 if (!empty($whereOr)) {
1534 $where[] = '(' . implode(' OR ', $whereOr) . ')';
1535 }
1536
1537 $where = $where ? 'WHERE ' . implode(' AND ', $where) : '';
1538
1539 try {
1540 $statement = $this->connection->prepare(
1541 "SELECT
1542 COUNT(*) AS count
1543 FROM {$this->table} a
1544 {$customerBookingJoin}
1545 {$where}
1546 ORDER BY a.bookingStart
1547 "
1548 );
1549
1550 $statement->execute($params);
1551
1552 $rows = $statement->fetch()['count'];
1553 } catch (\Exception $e) {
1554 throw new QueryExecutionException('Unable to find by id in ' . __CLASS__, $e->getCode(), $e);
1555 }
1556
1557 return $rows;
1558 }
1559
1560 /**
1561 * @param Service $service
1562 * @param int $customerId
1563 * @param \DateTime $appointmentStart
1564 * @param int $bookingId
1565 * @return Collection
1566 * @throws QueryExecutionException
1567 */
1568 public function getRelevantAppointmentsCount($service, $customerId, $appointmentStart, $limitPerCustomer, $serviceSpecific, $bookingId = null)
1569 {
1570 $params = [
1571 ':customerId' => $customerId
1572 ];
1573
1574 $paymentTableJoin = '';
1575 $compareToDate = 'a.bookingStart';
1576
1577 if ($limitPerCustomer['from'] === 'bookingDate') {
1578 $appointmentStart = DateTimeService::getCustomDateTimeObject(
1579 $appointmentStart->format('Y-m-d H:i')
1580 )->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i');
1581 } else {
1582 $paymentTableJoin = 'INNER JOIN ' . $this->paymentsTable . ' p ON p.customerBookingId = cb.id';
1583 $appointmentStart = DateTimeService::getNowDateTimeObject()->setTimezone(new \DateTimeZone('UTC'))->format('Y-m-d H:i');
1584 $compareToDate = 'p.created';
1585 }
1586
1587 $intervalString = "interval " . $limitPerCustomer['period'] . " " . $limitPerCustomer['timeFrame'];
1588
1589 $where = "(STR_TO_DATE('" . $appointmentStart . "', '%Y-%m-%d %H:%i:%s') BETWEEN " .
1590 "(" . $compareToDate . " - " . $intervalString . " + interval 1 second)"
1591 . " AND (".
1592 $compareToDate . " + " . $intervalString . " - interval 1 second))"; //+ interval 2 day
1593
1594 if ($serviceSpecific) {
1595 $where .= " AND a.serviceId = :serviceId";
1596 $params[':serviceId'] = $service->getId()->getValue();
1597 }
1598
1599 if ($bookingId) {
1600 $where .= " AND cb.id <> :bookingId";
1601 $params[':bookingId'] = $bookingId;
1602 }
1603
1604 try {
1605 $statement = $this->connection->prepare(
1606 "SELECT COUNT(DISTINCT a.id) AS count
1607 FROM {$this->table} a
1608 INNER JOIN {$this->bookingsTable} cb
1609 ON cb.appointmentId = a.id
1610 {$paymentTableJoin}
1611 WHERE cb.customerId = :customerId AND {$where} AND (a.status = 'approved' OR a.status = 'pending') AND (cb.status = 'approved' OR cb.status = 'pending')
1612 "
1613 );
1614
1615 $statement->execute($params);
1616
1617 $rows = $statement->fetch()['count'];
1618 } catch (\Exception $e) {
1619 throw new QueryExecutionException('Unable to find by id in ' . __CLASS__, $e->getCode(), $e);
1620 }
1621
1622 return $rows;
1623 }
1624
1625 /**
1626 * @param $providerIds
1627 *
1628 * @return array
1629 * @throws QueryExecutionException
1630 */
1631 public function getLastBookedEmployee($providerIds)
1632 {
1633 try {
1634 $params = [];
1635
1636 $queryProviders = [];
1637
1638 $where = '';
1639
1640 if (!empty($providerIds)) {
1641 foreach ($providerIds as $index => $value) {
1642 $param = ':provider' . $index;
1643
1644 $queryProviders[] = $param;
1645
1646 $params[$param] = $value;
1647 }
1648
1649 $where = ' AND a.providerId IN (' . implode(', ', $queryProviders) . ')';
1650 }
1651
1652 $statement = $this->connection->prepare(
1653 "SELECT a.providerId
1654 FROM {$this->table} a
1655 JOIN {$this->bookingsTable} cb ON cb.appointmentId = a.id
1656 WHERE (a.status = 'approved' OR a.status = 'pending') AND (cb.status = 'approved' OR cb.status = 'pending')
1657 {$where}
1658 ORDER BY cb.created DESC, a.id DESC LIMIT 1;
1659 "
1660 );
1661
1662 $statement->execute($params);
1663
1664 $rows = $statement->fetchAll(\PDO::FETCH_COLUMN);
1665 } catch (\Exception $e) {
1666 throw new QueryExecutionException('Unable to find by id in ' . __CLASS__, $e->getCode(), $e);
1667 }
1668
1669 return !empty($rows) ? $rows[0] : $providerIds[0];
1670 }
1671 }
1672