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