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