PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 2.0.2
Booking for Appointments and Events Calendar – Amelia v2.0.2
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 / Application / Services / Notification / AbstractNotificationService.php
ameliabooking / src / Application / Services / Notification Last commit date
AbstractNotificationService.php 6 months ago AbstractWhatsAppNotificationService.php 6 months ago ApplicationNotificationService.php 6 months ago AppointmentNotificationService.php 6 months ago BasicWhatsAppNotificationService.php 6 months ago EmailNotificationService.php 6 months ago NotificationHelperService.php 6 months ago SMSAPIService.php 6 months ago SMSNotificationService.php 6 months ago
AbstractNotificationService.php
1324 lines
1 <?php
2
3 /**
4 * @copyright © Melograno Ventures. All rights reserved.
5 * @licence See LICENCE.md for license details.
6 */
7
8 namespace AmeliaBooking\Application\Services\Notification;
9
10 use AmeliaBooking\Application\Services\Booking\BookingApplicationService;
11 use AmeliaBooking\Application\Services\Booking\EventApplicationService;
12 use AmeliaBooking\Application\Services\Invoice\AbstractInvoiceApplicationService;
13 use AmeliaBooking\Application\Services\Payment\PaymentApplicationService;
14 use AmeliaBooking\Domain\Collection\Collection;
15 use AmeliaBooking\Domain\Common\Exceptions\InvalidArgumentException;
16 use AmeliaBooking\Domain\Entity\Booking\Appointment\CustomerBooking;
17 use AmeliaBooking\Domain\Entity\Entities;
18 use AmeliaBooking\Domain\Entity\Notification\Notification;
19 use AmeliaBooking\Domain\Factory\Booking\Appointment\CustomerBookingFactory;
20 use AmeliaBooking\Domain\Services\DateTime\DateTimeService;
21 use AmeliaBooking\Domain\Services\Settings\SettingsService;
22 use AmeliaBooking\Domain\ValueObjects\String\BookingStatus;
23 use AmeliaBooking\Domain\ValueObjects\String\NotificationSendTo;
24 use AmeliaBooking\Domain\ValueObjects\String\NotificationStatus;
25 use AmeliaBooking\Infrastructure\Common\Container;
26 use AmeliaBooking\Infrastructure\Common\Exceptions\NotFoundException;
27 use AmeliaBooking\Infrastructure\Common\Exceptions\QueryExecutionException;
28 use AmeliaBooking\Infrastructure\Repository\Bookable\Service\ServiceRepository;
29 use AmeliaBooking\Infrastructure\Repository\Notification\NotificationLogRepository;
30 use AmeliaBooking\Infrastructure\Repository\Notification\NotificationRepository;
31 use AmeliaBooking\Infrastructure\Repository\Notification\NotificationsToEntitiesRepository;
32 use AmeliaBooking\Infrastructure\Repository\User\ProviderRepository;
33 use Exception;
34 use Interop\Container\Exception\ContainerException;
35 use Slim\Exception\ContainerValueNotFoundException;
36
37 /**
38 * Class AbstractNotificationService
39 *
40 * @package AmeliaBooking\Application\Services\Notification
41 */
42 abstract class AbstractNotificationService
43 {
44 /** @var Container */
45 protected $container;
46
47 /** @var string */
48 protected $type;
49
50 /** @var boolean */
51 protected $sendNotifications = true;
52
53 /** @var array */
54 protected $preparedNotificationData = [];
55
56 /**
57 * AbstractNotificationService constructor.
58 *
59 * @param Container $container
60 * @param string $type
61 */
62 public function __construct(Container $container, $type)
63 {
64 $this->container = $container;
65
66 $this->type = $type;
67 }
68
69 /**
70 * @return string
71 */
72 public function getType()
73 {
74 return $this->type;
75 }
76
77 /**
78 * @param bool $value
79 */
80 public function setSend($value)
81 {
82 $this->sendNotifications = $value;
83 }
84
85 /**
86 * @return bool
87 */
88 public function getSend()
89 {
90 return $this->sendNotifications;
91 }
92
93 /**
94 * @return array
95 */
96 protected function getPreparedNotificationData()
97 {
98 return $this->preparedNotificationData;
99 }
100
101 /**
102 * @param array $data
103 */
104 protected function addPreparedNotificationData($data)
105 {
106 $this->preparedNotificationData[] = $data;
107 }
108
109 /**
110 * @return void
111 */
112 abstract public function sendPreparedNotifications();
113
114 /**
115 * @param array $appointmentArray
116 * @param Notification $notification
117 * @param bool $logNotification
118 * @param null $bookingKey
119 *
120 * @return mixed
121 */
122 abstract public function sendNotification(
123 $appointmentArray,
124 $notification,
125 $logNotification,
126 $bookingKey = null,
127 $allBookings = null,
128 $invoice = []
129 );
130
131
132 /**
133 * @throws NotFoundException
134 * @throws QueryExecutionException
135 * @throws InvalidArgumentException
136 * @throws ContainerException
137 * @throws Exception
138 */
139 abstract public function sendBirthdayGreetingNotifications();
140
141 /**
142 *
143 * @param string $name
144 * @param string $type
145 *
146 * @return Collection
147 *
148 * @throws QueryExecutionException
149 * @throws InvalidArgumentException
150 */
151 public function getByNameAndType($name, $type)
152 {
153 /** @var NotificationRepository $notificationRepo */
154 $notificationRepo = $this->container->get('domain.notification.repository');
155 /** @var NotificationsToEntitiesRepository $notificationEntitiesRepo */
156 $notificationEntitiesRepo = $this->container->get('domain.notificationEntities.repository');
157 /** @var SettingsService $settingsService */
158 $settingsService = $this->container->get('domain.settings.service');
159
160 // Check if custom notifications feature is enabled
161 $isCustomNotificationsEnabled = $settingsService->isFeatureEnabled('customNotifications');
162
163 /** @var Collection $notifications */
164 $notifications = $notificationRepo->getByNameAndType($name, $type, $isCustomNotificationsEnabled);
165 /** @var Notification $notification */
166 foreach ($notifications->getItems() as $notification) {
167 if ($notification->getCustomName() !== null) {
168 $notification->setEntityIds($notificationEntitiesRepo->getEntities($notification->getId()->getValue()));
169 }
170 }
171
172 return $notifications;
173 }
174
175 /**
176 *
177 * @param int $id
178 *
179 * @return Notification
180 *
181 * @throws QueryExecutionException
182 * @throws NotFoundException
183 */
184 public function getById($id)
185 {
186 /** @var NotificationRepository $notificationRepo */
187 $notificationRepo = $this->container->get('domain.notification.repository');
188
189 return $notificationRepo->getById($id);
190 }
191
192 /**
193 * @param array $appointmentArray
194 * @param bool $forcedStatusChange - True when appointment status is changed to 'pending' because minimum capacity
195 * condition is not satisfied
196 * @param bool $logNotification
197 * @param bool $isBackend
198 *
199 * @throws ContainerValueNotFoundException
200 * @throws QueryExecutionException
201 * @throws InvalidArgumentException
202 */
203 public function sendAppointmentStatusNotifications($appointmentArray, $forcedStatusChange, $logNotification, $isBackend = false, $sendInvoice = false)
204 {
205 /** @var BookingApplicationService $bookingAS */
206 $bookingAS = $this->container->get('application.booking.booking.service');
207
208 // Notify provider
209 /** @var Collection $providerNotifications */
210 $providerNotifications = $this->getByNameAndType(
211 "provider_{$appointmentArray['type']}_{$appointmentArray['status']}",
212 $this->type
213 );
214
215 $sendDefault = $this->sendDefault($providerNotifications, $appointmentArray);
216
217 $appointmentArray['sendCF'] = true;
218
219 $dontSend = $appointmentArray['type'] === Entities::EVENT && $appointmentArray['status'] === BookingStatus::REJECTED
220 && DateTimeService::getNowDateTimeObject() >
221 DateTimeService::getCustomDateTimeObject($appointmentArray['periods'][count($appointmentArray['periods']) - 1]['periodStart']);
222
223 /** @var Notification $providerNotification */
224 foreach ($providerNotifications->getItems() as $providerNotification) {
225 if ($providerNotification && $providerNotification->getStatus()->getValue() === NotificationStatus::ENABLED && !$dontSend) {
226 if (!$this->checkCustom($providerNotification, $appointmentArray, $sendDefault)) {
227 continue;
228 }
229 $this->sendNotification(
230 $appointmentArray,
231 $providerNotification,
232 $logNotification
233 );
234 }
235 }
236
237 // Notify customers
238 if ($appointmentArray['notifyParticipants']) {
239
240 /** @var Collection $customerNotifications */
241 $customerNotifications = $this->getByNameAndType(
242 "customer_{$appointmentArray['type']}_{$appointmentArray['status']}",
243 $this->type
244 );
245
246 $sendDefault = $this->sendDefault($customerNotifications, $appointmentArray);
247
248 foreach ($customerNotifications->getItems() as $customerNotification) {
249 if ($customerNotification->getStatus()->getValue() === NotificationStatus::ENABLED && !$dontSend) {
250 if (!$this->checkCustom($customerNotification, $appointmentArray, $sendDefault)) {
251 continue;
252 }
253 // If appointment status is changed to 'pending' because minimum capacity condition is not satisfied,
254 // return all 'approved' bookings and send them notification that appointment is now 'pending'.
255 if ($forcedStatusChange === true) {
256 $appointmentArray['bookings'] = $bookingAS->filterApprovedBookings($appointmentArray['bookings']);
257 }
258
259 $appointmentArray['isBackend'] = $isBackend;
260 // Notify each customer from customer bookings
261 foreach (array_keys($appointmentArray['bookings']) as $bookingKey) {
262 if (
263 !$appointmentArray['bookings'][$bookingKey]['isChangedStatus'] ||
264 $appointmentArray['bookings'][$bookingKey]['status'] === BookingStatus::WAITING ||
265 (
266 isset($appointmentArray['bookings'][$bookingKey]['skipNotification']) &&
267 $appointmentArray['bookings'][$bookingKey]['skipNotification']
268 )
269 ) {
270 continue;
271 }
272
273 $invoice = null;
274 if ($sendInvoice) {
275 /** @var AbstractInvoiceApplicationService $invoiceService */
276 $invoiceService = $this->container->get('application.invoice.service');
277
278 $invoice = $invoiceService->generateInvoice($appointmentArray['bookings'][$bookingKey]['payments'][0]['id']);
279 }
280
281 $this->sendNotification(
282 $appointmentArray,
283 $customerNotification,
284 $logNotification,
285 $bookingKey,
286 null,
287 $invoice
288 );
289 }
290 }
291 }
292 }
293 }
294
295 /**
296 * @param $appointmentArray
297 *
298 * @throws QueryExecutionException
299 * @throws InvalidArgumentException
300 */
301 public function sendAppointmentRescheduleNotifications($appointmentArray)
302 {
303 // Notify customers
304 if ($appointmentArray['notifyParticipants']) {
305
306 /** @var Collection $customerNotifications */
307 $customerNotifications = $this->getByNameAndType(
308 "customer_{$appointmentArray['type']}_rescheduled",
309 $this->type
310 );
311
312 $sendDefault = $this->sendDefault($customerNotifications, $appointmentArray);
313 foreach ($customerNotifications->getItems() as $customerNotification) {
314 if ($customerNotification->getStatus()->getValue() === NotificationStatus::ENABLED) {
315 if (!$this->checkCustom($customerNotification, $appointmentArray, $sendDefault)) {
316 continue;
317 }
318 // Notify each customer from customer bookings
319 foreach (array_keys($appointmentArray['bookings']) as $bookingKey) {
320 $this->sendNotification(
321 $appointmentArray,
322 $customerNotification,
323 true,
324 $bookingKey
325 );
326 }
327 }
328 }
329 }
330
331 if (empty($appointmentArray['employee_changed'])) {
332 // Notify provider
333 /** @var Collection $providerNotifications */
334 $providerNotifications = $this->getByNameAndType(
335 "provider_{$appointmentArray['type']}_rescheduled",
336 $this->type
337 );
338
339 $sendDefault = $this->sendDefault($providerNotifications, $appointmentArray);
340 foreach ($providerNotifications->getItems() as $providerNotification) {
341 if ($providerNotification->getStatus()->getValue() === NotificationStatus::ENABLED) {
342 if (!$this->checkCustom($providerNotification, $appointmentArray, $sendDefault)) {
343 continue;
344 }
345 $this->sendNotification(
346 $appointmentArray,
347 $providerNotification,
348 true
349 );
350 }
351 }
352 }
353 }
354
355 /**
356 * @param $appointmentArray
357 * @param $appointmentRescheduled
358 *
359 * @throws QueryExecutionException
360 * @throws InvalidArgumentException
361 */
362 public function sendAppointmentUpdatedNotifications($appointmentArray, $appointmentRescheduled = null)
363 {
364 // Notify customers
365 if ($appointmentArray['notifyParticipants'] && !$appointmentRescheduled) {
366
367 /** @var Collection $customerNotifications */
368 $customerNotifications = $this->getByNameAndType(
369 "customer_{$appointmentArray['type']}_updated",
370 $this->type
371 );
372
373 $sendDefault = $this->sendDefault($customerNotifications, $appointmentArray);
374 foreach ($customerNotifications->getItems() as $customerNotification) {
375 if ($customerNotification->getStatus()->getValue() === NotificationStatus::ENABLED) {
376 if (!$this->checkCustom($customerNotification, $appointmentArray, $sendDefault)) {
377 continue;
378 }
379 // Notify each customer from customer bookings
380 foreach (array_keys($appointmentArray['bookings']) as $bookingKey) {
381 if (
382 $appointmentArray['bookings'][$bookingKey]['status'] === BookingStatus::APPROVED &&
383 $appointmentArray['status'] === BookingStatus::APPROVED &&
384 ($appointmentArray['bookings'][$bookingKey]['isUpdated'] || $appointmentArray['type'] === Entities::EVENT)
385 ) {
386 $this->sendNotification(
387 $appointmentArray,
388 $customerNotification,
389 true,
390 $bookingKey
391 );
392 }
393 }
394 }
395 }
396 }
397
398 if (!empty($appointmentArray['employee_changed'])) {
399 $appointmentArray['providerId'] = $appointmentArray['employee_changed'];
400 }
401
402 if ($appointmentArray['status'] === BookingStatus::APPROVED) {
403 /** @var Collection $providerNotifications */
404 $providerNotifications = $this->getByNameAndType(
405 "provider_{$appointmentArray['type']}_updated",
406 $this->type
407 );
408
409 $sendDefault = $this->sendDefault($providerNotifications, $appointmentArray);
410 foreach ($providerNotifications->getItems() as $providerNotification) {
411 if ($providerNotification->getStatus()->getValue() === NotificationStatus::ENABLED) {
412 if (!$this->checkCustom($providerNotification, $appointmentArray, $sendDefault)) {
413 continue;
414 }
415 $this->sendNotification(
416 $appointmentArray,
417 $providerNotification,
418 true
419 );
420 }
421 }
422 }
423 }
424
425 /**
426 * @param array $appointmentArray
427 * @param array $bookingArray
428 * @param bool $logNotification
429 * @param array $invoice
430 *
431 * @throws QueryExecutionException
432 * @throws InvalidArgumentException
433 */
434 public function sendBookingAddedNotifications($appointmentArray, $bookingArray, $logNotification, $invoice = null)
435 {
436 /** @var SettingsService $settingsService */
437 $settingsService = $this->container->get('domain.settings.service');
438
439 $defaultStatus = BookingStatus::WAITING !== $bookingArray['status'] ? $appointmentArray['status'] : $bookingArray['status'];
440
441 if ($appointmentArray['type'] !== Entities::EVENT && $defaultStatus === BookingStatus::APPROVED && empty($bookingArray['packageBookingFromBackend'])) {
442
443 /** @var ServiceRepository $serviceRepository */
444 $serviceRepository = $this->container->get('domain.bookable.service.repository');
445
446 $service = $serviceRepository->getById($appointmentArray['serviceId']);
447
448 $defaultStatus =
449 ($service->getSettings() && !empty(json_decode($service->getSettings()->getValue(), true)['general']['defaultAppointmentStatus'])) ?
450 json_decode($service->getSettings()->getValue(), true)['general']['defaultAppointmentStatus'] :
451 $settingsService->getSetting('general', 'defaultAppointmentStatus');
452 } elseif (
453 $appointmentArray['type'] === Entities::EVENT &&
454 $settingsService->getSetting('general', 'defaultEventStatus') === BookingStatus::PENDING
455 ) {
456 $defaultStatus = $bookingArray['status'];
457 }
458
459 $customerNotifications = $this->getByNameAndType(
460 "customer_{$appointmentArray['type']}_{$defaultStatus}",
461 $this->type
462 );
463
464 $sendDefault = $this->sendDefault($customerNotifications, $appointmentArray);
465
466 foreach ($customerNotifications->getItems() as $customerNotification) {
467 if ($customerNotification->getStatus()->getValue() === NotificationStatus::ENABLED) {
468 if (!$this->checkCustom($customerNotification, $appointmentArray, $sendDefault)) {
469 continue;
470 }
471
472 // Notify customer that scheduled the appointment
473 $this->sendNotification(
474 $appointmentArray,
475 $customerNotification,
476 $logNotification,
477 $bookingArray ? array_search($bookingArray['id'], array_column($appointmentArray['bookings'], 'id'), true) : null,
478 null,
479 $invoice
480 );
481 }
482 }
483
484 // Notify provider
485 $providerNotifications = $this->getByNameAndType(
486 "provider_{$appointmentArray['type']}_{$defaultStatus}",
487 $this->type
488 );
489
490 $sendDefault = $this->sendDefault($providerNotifications, $appointmentArray);
491 foreach ($providerNotifications->getItems() as $providerNotification) {
492 if ($providerNotification && $providerNotification->getStatus()->getValue() === NotificationStatus::ENABLED) {
493 if (!$this->checkCustom($providerNotification, $appointmentArray, $sendDefault)) {
494 continue;
495 }
496 $allBookings = null;
497 if ($appointmentArray['type'] === Entities::EVENT) {
498 $allBookings = $appointmentArray['bookings'];
499 $appointmentArray['bookings'] = [$bookingArray];
500 }
501 $this->sendNotification(
502 $appointmentArray,
503 $providerNotification,
504 $logNotification,
505 null,
506 $allBookings
507 );
508 }
509 }
510 }
511
512 /**
513 * Notify the customer when he changes his booking status.
514 *
515 * @param $appointmentArray
516 * @param $bookingArray
517 *
518 * @throws QueryExecutionException
519 * @throws InvalidArgumentException
520 */
521 public function sendCustomerBookingNotification($appointmentArray, $bookingArray, $sendInvoice = false)
522 {
523 // Notify customers
524 if ($appointmentArray['notifyParticipants']) {
525 $customerNotifications = $this->getByNameAndType("customer_{$appointmentArray['type']}_{$bookingArray['status']}", $this->type);
526
527 $sendDefault = $this->sendDefault($customerNotifications, $appointmentArray);
528 foreach ($customerNotifications->getItems() as $customerNotification) {
529 if ($customerNotification->getStatus()->getValue() === NotificationStatus::ENABLED) {
530 if (!$this->checkCustom($customerNotification, $appointmentArray, $sendDefault)) {
531 continue;
532 }
533 // Notify customer
534 $bookingKey = array_search(
535 $bookingArray['id'],
536 array_column($appointmentArray['bookings'], 'id'),
537 true
538 );
539
540 $invoice = null;
541
542 if ($sendInvoice) {
543 /** @var AbstractInvoiceApplicationService $invoiceService */
544 $invoiceService = $this->container->get('application.invoice.service');
545
546 $invoice = $invoiceService->generateInvoice($appointmentArray['bookings'][$bookingKey]['payments'][0]['id']);
547 }
548
549 $this->sendNotification(
550 $appointmentArray,
551 $customerNotification,
552 true,
553 $bookingKey,
554 null,
555 $invoice
556 );
557 }
558 }
559 }
560 }
561
562 /**
563 * Notify the provider when he changes his booking status.
564 *
565 * @param $appointmentArray
566 * @param $bookingArray
567 *
568 * @throws QueryExecutionException
569 * @throws InvalidArgumentException
570 */
571 public function sendProviderBookingNotification($appointmentArray, $bookingArray)
572 {
573 // Notify provider
574 if ($appointmentArray['notifyParticipants'] && $bookingArray['status'] !== BookingStatus::REJECTED) {
575 $providerNotifications = $this->getByNameAndType("provider_{$appointmentArray['type']}_{$bookingArray['status']}", $this->type);
576
577 $sendDefault = $this->sendDefault($providerNotifications, $appointmentArray);
578 foreach ($providerNotifications->getItems() as $customerNotification) {
579 if ($customerNotification->getStatus()->getValue() === NotificationStatus::ENABLED) {
580 if (!$this->checkCustom($customerNotification, $appointmentArray, $sendDefault)) {
581 continue;
582 }
583 // Notify provider
584 $bookingKey = array_search(
585 $bookingArray['id'],
586 array_column($appointmentArray['bookings'], 'id'),
587 true
588 );
589
590 $this->sendNotification(
591 $appointmentArray,
592 $customerNotification,
593 true,
594 $bookingKey
595 );
596 }
597 }
598 }
599 }
600
601 /**
602 * Notify the provider when the customer cancels event booking.
603 *
604 * @param $eventArray
605 * @param $bookingArray
606 *
607 * @throws QueryExecutionException
608 * @throws InvalidArgumentException
609 */
610 public function sendProviderEventCancelledNotification($eventArray, $bookingArray)
611 {
612 $providerNotifications = $this->getByNameAndType(
613 "provider_event_canceled",
614 $this->type
615 );
616
617 $eventArray['bookings'] = [$bookingArray];
618
619 $sendDefault = $this->sendDefault($providerNotifications, $eventArray);
620 foreach ($providerNotifications->getItems() as $providerNotification) {
621 if ($providerNotification && $providerNotification->getStatus()->getValue() === NotificationStatus::ENABLED) {
622 if (!$this->checkCustom($providerNotification, $eventArray, $sendDefault)) {
623 continue;
624 }
625 $this->sendNotification(
626 $eventArray,
627 $providerNotification,
628 false,
629 null
630 );
631 }
632 }
633 }
634
635 /**
636 * Returns an array of next day reminder notifications that have to be sent to customers with cron
637 *
638 * @param string $entityType
639 *
640 * @return void
641 * @throws QueryExecutionException
642 * @throws InvalidArgumentException
643 * @throws Exception
644 */
645 public function sendNextDayReminderNotifications($entityType)
646 {
647 /** @var NotificationLogRepository $notificationLogRepo */
648 $notificationLogRepo = $this->container->get('domain.notificationLog.repository');
649
650 /** @var SettingsService $settingsService */
651 $settingsService = $this->container->get('domain.settings.service');
652
653 $customerNotifications = $this->getByNameAndType("customer_{$entityType}_next_day_reminder", $this->type);
654 $customerNotifications2 = $this->getByNameAndType("customer_{$entityType}_scheduled", $this->type);
655
656 foreach ($customerNotifications2->getItems() as $notification) {
657 $customerNotifications->addItem($notification);
658 }
659
660 $reminderStatuses = ['approved'];
661
662 if ($settingsService->getSetting('notifications', 'pendingReminder')) {
663 $reminderStatuses[] = 'pending';
664 }
665
666 $reservations = new Collection();
667
668 /** @var Notification $customerNotification */
669 foreach ($customerNotifications->getItems() as $customerNotification) {
670 // Check if notification is enabled and it is time to send notification
671 if (
672 $customerNotification->getStatus()->getValue() === NotificationStatus::ENABLED &&
673 $customerNotification->getTime() &&
674 DateTimeService::getNowDateTimeObject() >=
675 DateTimeService::getCustomDateTimeObject($customerNotification->getTime()->getValue())
676 ) {
677 switch ($entityType) {
678 case Entities::APPOINTMENT:
679 $reservations = $notificationLogRepo->getCustomersNextDayAppointments(
680 $customerNotification->getId()->getValue(),
681 $customerNotification->getCustomName() === null,
682 $reminderStatuses
683 );
684
685 break;
686 case Entities::EVENT:
687 $reservations =
688 $notificationLogRepo->getCustomersNextDayEvents(
689 $customerNotification->getId()->getValue(),
690 $customerNotification->getCustomName() === null
691 );
692
693 break;
694 }
695
696 $approvedReservations = new Collection();
697 foreach ($reservations->getItems() as $appointment) {
698 if ($appointment->getStatus()->getValue() === BookingStatus::APPROVED) {
699 $approvedReservations->addItem($appointment);
700 }
701 }
702
703 try {
704 $this->sendBookingsNotifications($customerNotification, $approvedReservations, true);
705 } catch (\Exception $e) {
706 }
707 }
708 }
709
710
711 /** @var Collection $providerNotifications */
712 $providerNotifications = $this->getByNameAndType("provider_{$entityType}_next_day_reminder", $this->type);
713 $providerNotifications2 = $this->getByNameAndType("provider_{$entityType}_scheduled", $this->type);
714
715 foreach ($providerNotifications2->getItems() as $notification) {
716 $providerNotifications->addItem($notification);
717 }
718
719 /** @var Notification $providerNotification */
720 foreach ($providerNotifications->getItems() as $providerNotification) {
721 // Check if notification is enabled and it is time to send notification
722 if (
723 $providerNotification->getStatus()->getValue() === NotificationStatus::ENABLED &&
724 $providerNotification->getTime() &&
725 DateTimeService::getNowDateTimeObject() >=
726 DateTimeService::getCustomDateTimeObject($providerNotification->getTime()->getValue())
727 ) {
728 switch ($entityType) {
729 case Entities::APPOINTMENT:
730 $reservations = $notificationLogRepo->getProvidersNextDayAppointments(
731 $providerNotification->getId()->getValue(),
732 $providerNotification->getCustomName() === null,
733 $reminderStatuses
734 );
735
736 break;
737 case Entities::EVENT:
738 $reservations =
739 $notificationLogRepo->getProvidersNextDayEvents(
740 $providerNotification->getId()->getValue(),
741 $providerNotification->getCustomName() === null
742 );
743
744 break;
745 }
746
747 foreach ((array)$reservations->toArray() as $reservationArray) {
748 if (!$this->checkCustom($providerNotification, $reservationArray, true)) {
749 continue;
750 }
751 if ($providerNotification->getCustomName() === null && !$this->checkShouldSend($reservationArray, true, NotificationSendTo::PROVIDER)) {
752 continue;
753 }
754
755 $bookingArray = $reservationArray['bookings'][count($reservationArray['bookings']) - 1];
756 /** @var CustomerBooking $bookingObject */
757 $bookingObject = $bookingArray ? CustomerBookingFactory::create($bookingArray) : null;
758 $reservationStart =
759 $entityType === Entities::APPOINTMENT ? $reservationArray['bookingStart'] : $reservationArray['periods'][0]['periodStart'];
760
761 if ($this->pastMinimumTimeBeforeBooking($providerNotification, $bookingObject, $reservationStart)) {
762 continue;
763 }
764
765 $reservationArray['sendCF'] = true;
766
767 try {
768 $this->sendNotification(
769 $reservationArray,
770 $providerNotification,
771 true
772 );
773 } catch (\Exception $e) {
774 }
775 }
776 }
777 }
778 }
779
780 /**
781 * @param int $entityId
782 * @param string $entityType
783 * @param int $userId
784 * @param string $userType
785 *
786 * @throws QueryExecutionException
787 * @throws InvalidArgumentException
788 */
789 public function invalidateSentScheduledNotifications($entityId, $entityType, $userId, $userType)
790 {
791 /** @var NotificationLogRepository $notificationLogRepo */
792 $notificationLogRepo = $this->container->get('domain.notificationLog.repository');
793
794 $templates = [
795 "{$userType}_{$entityType}_next_day_reminder",
796 "{$userType}_{$entityType}_scheduled",
797 "{$userType}_{$entityType}_scheduled_%",
798 ];
799
800 $notificationsIds = [];
801
802 foreach ($templates as $template) {
803 /** @var Collection $notifications */
804 $notifications = $this->getByNameAndType($template, $this->type);
805
806 $notificationsIds = array_merge($notificationsIds, $notifications->keys());
807 }
808
809 $notificationLogRepo->invalidateSentEmails($entityId, $entityType, $userId, array_unique($notificationsIds));
810 }
811
812 /**
813 * @param string $entityType
814 *
815 * @throws QueryExecutionException
816 * @throws InvalidArgumentException
817 */
818 public function sendScheduledNotifications($entityType)
819 {
820 /** @var SettingsService $settingsService */
821 $settingsService = $this->container->get('domain.settings.service');
822
823 /** @var Collection $notifications */
824 $notifications = $this->getByNameAndType("customer_{$entityType}_follow_up", $this->type);
825 $notifications2 = $this->getByNameAndType("customer_{$entityType}_scheduled_%", $this->type);
826 foreach ($notifications2->getItems() as $notification) {
827 $notifications->addItem($notification);
828 }
829 $notifications2 = $this->getByNameAndType("provider_{$entityType}_scheduled_%", $this->type);
830 foreach ($notifications2->getItems() as $notification) {
831 $notifications->addItem($notification);
832 }
833
834 $reminderStatuses = ['approved'];
835
836 if ($settingsService->getSetting('notifications', 'pendingReminder')) {
837 $reminderStatuses[] = 'pending';
838 }
839
840 /** @var Notification $notification */
841 foreach ($notifications->getItems() as $notification) {
842 if ($notification->getStatus()->getValue() === NotificationStatus::ENABLED) {
843 /** @var NotificationLogRepository $notificationLogRepo */
844 $notificationLogRepo = $this->container->get('domain.notificationLog.repository');
845
846 $reservations = new Collection();
847
848 switch ($entityType) {
849 case Entities::APPOINTMENT:
850 $reservations = $notificationLogRepo->getScheduledAppointments(
851 $notification,
852 $reminderStatuses
853 );
854
855 break;
856 case Entities::EVENT:
857 /** @var Collection $reservations */
858 $reservations = $notificationLogRepo->getScheduledEvents($notification);
859
860 /** @var EventApplicationService $eventAS */
861 $eventAS = $this->container->get('application.booking.event.service');
862
863 /** @var Collection $reservations */
864 $reservations = $reservations->length() ? $eventAS->getEventsByIds(
865 $reservations->keys(),
866 [
867 'fetchEventsPeriods' => true,
868 'fetchEventsProviders' => true,
869 'fetchBookings' => true,
870 'fetchBookingsCoupons' => true,
871 'fetchBookingsPayments' => true,
872 ]
873 ) : new Collection();
874
875 break;
876 }
877
878 $approvedReservations = new Collection();
879 foreach ($reservations->getItems() as $appointment) {
880 if ($appointment->getStatus()->getValue() === BookingStatus::APPROVED) {
881 $approvedReservations->addItem($appointment);
882 }
883 }
884
885 try {
886 $this->sendBookingsNotifications($notification, $approvedReservations, $notification->getTimeBefore() !== null);
887 } catch (\Exception $e) {
888 }
889 }
890 }
891 }
892
893
894 /**
895 *
896 * @param Notification $notification
897 * @param CustomerBooking $booking
898 * @param string $appointmentStart
899 *
900 */
901 private function pastMinimumTimeBeforeBooking($notification, $booking, $appointmentStart)
902 {
903 if (
904 $booking && $booking->getCreated() && $notification->getMinimumTimeBeforeBooking() && $notification->getMinimumTimeBeforeBooking()->getValue() &&
905 json_decode($notification->getMinimumTimeBeforeBooking()->getValue())
906 ) {
907 $minimumTime = json_decode($notification->getMinimumTimeBeforeBooking()->getValue(), true);
908 $seconds = 1;
909 switch ($minimumTime['period']) {
910 case 'minutes':
911 $seconds = 60;
912 break;
913 case 'hours':
914 $seconds = 60 * 60;
915 break;
916 case 'days':
917 $seconds = 24 * 60 * 60;
918 break;
919 case 'weeks':
920 $seconds = 7 * 24 * 60 * 60;
921 break;
922 case 'months':
923 $seconds = 30 * 7 * 24 * 60 * 60;
924 break;
925 }
926 $time = $minimumTime['amount'] * $seconds;
927 if (
928 DateTimeService::getCustomDateTimeObject($appointmentStart)->modify('-' . $time . ' second')
929 <= DateTimeService::getCustomDateTimeObject($booking->getCreated()->getValue()->format('Y-m-d H:i:s'))
930 ) {
931 return true;
932 }
933 }
934 return false;
935 }
936
937 /**
938 * Send passed notification for all passed bookings and save log in the database
939 *
940 * @param Notification $notification
941 * @param Collection $appointments
942 * @param bool $before
943 * @throws QueryExecutionException
944 * @throws InvalidArgumentException
945 */
946 private function sendBookingsNotifications($notification, $appointments, $before)
947 {
948 /** @var PaymentApplicationService $paymentAS */
949 $paymentAS = $this->container->get('application.payment.service');
950
951 /** @var array $appointmentArray */
952 foreach ($appointments->toArray() as $appointmentArray) {
953 if (!$this->checkCustom($notification, $appointmentArray, true)) {
954 continue;
955 }
956 if ($notification->getCustomName() === null && !$this->checkShouldSend($appointmentArray, $before, $notification->getSendTo()->getValue())) {
957 continue;
958 }
959
960 $appointmentArray['sendCF'] = true;
961
962 /** @var BookingApplicationService $bookingApplicationService */
963 $bookingApplicationService = $this->container->get('application.booking.booking.service');
964 $data = $appointmentArray;
965 $reservationObject = $bookingApplicationService->getReservationEntity($appointmentArray);
966
967 $reservationStart =
968 $appointmentArray['type'] === Entities::APPOINTMENT ? $appointmentArray['bookingStart'] : $appointmentArray['periods'][0]['periodStart'];
969
970 if ($notification->getSendTo()->getValue() === NotificationSendTo::PROVIDER) {
971 /** @var CustomerBooking $bookingObject */
972 $bookingObject =
973 $reservationObject->getBookings()->getItem($reservationObject->getBookings()->keys()[$reservationObject->getBookings()->length() - 1]);
974
975 if ($this->pastMinimumTimeBeforeBooking($notification, $bookingObject, $reservationStart)) {
976 continue;
977 }
978
979 $this->sendNotification(
980 $appointmentArray,
981 $notification,
982 true
983 );
984 } else {
985 if ($appointmentArray['type'] === Entities::APPOINTMENT) {
986 $data['bookable'] = $reservationObject->getService()->toArray();
987 } else {
988 $data['bookable'] = $appointmentArray;
989 }
990
991 // Notify each customer from customer bookings
992 foreach (array_keys($appointmentArray['bookings']) as $bookingKey) {
993 /** @var CustomerBooking $bookingObject */
994 $bookingObject = $reservationObject->getBookings()->getItem($reservationObject->getBookings()->keys()[$bookingKey]);
995
996 if ($appointmentArray['type'] === 'event' && $bookingObject->getStatus()->getValue() !== BookingStatus::APPROVED) {
997 continue;
998 }
999
1000 if (
1001 (!array_key_exists('createPaymentLinks', $appointmentArray) || !empty($appointmentArray['createPaymentLinks'])) &&
1002 $notification->getContent() &&
1003 $notification->getContent()->getValue() &&
1004 strpos($notification->getContent()->getValue(), '%payment_link_') !== false
1005 ) {
1006 $data['booking'] = $bookingObject ? $bookingObject->toArray() : $appointmentArray['bookings'][$bookingKey];
1007 $data['customer'] = $data['booking']['customer'];
1008 $data[$appointmentArray['type']] = $appointmentArray;
1009 $data['paymentId'] = $appointmentArray['bookings'][$bookingKey]['payments'][0]['id'];
1010 $appointmentArray['bookings'][$bookingKey]['payments'][0]['paymentLinks'] = $paymentAS->createPaymentLink($data, $bookingKey);
1011 }
1012
1013 if ($this->pastMinimumTimeBeforeBooking($notification, $bookingObject, $reservationStart)) {
1014 continue;
1015 }
1016
1017 $this->sendNotification(
1018 $appointmentArray,
1019 $notification,
1020 true,
1021 $bookingKey
1022 );
1023 }
1024 }
1025 }
1026 }
1027
1028 /**
1029 * Check if schedule default notification should be sent
1030 *
1031 * @param array $appointmentArray
1032 * @param bool $before
1033 * @param string $sendTo
1034 *
1035 * @throws QueryExecutionException
1036 * @throws InvalidArgumentException
1037 *
1038 * return bool
1039 *
1040 */
1041 private function checkShouldSend($appointmentArray, $before, $sendTo)
1042 {
1043 $time = $before ? 'timeBefore' : 'timeAfter';
1044 $entityId = $appointmentArray['type'] === Entities::EVENT ? $appointmentArray['id'] : $appointmentArray['serviceId'];
1045 $notifications = $this->getByNameAndType("{$sendTo}_{$appointmentArray['type']}_scheduled_%", $this->type);
1046 $parentId = $appointmentArray['parentId'];
1047 return empty(
1048 array_filter(
1049 $notifications->toArray(),
1050 function ($a) use (&$entityId, &$time, &$parentId) {
1051 return $a['customName'] && $a[$time] && $a['sendOnlyMe'] &&
1052 ($a['entityIds'] === null || in_array($entityId, $a['entityIds']) || ($parentId && in_array($parentId, $a['entityIds'])));
1053 }
1054 )
1055 );
1056 }
1057
1058 /**
1059 * Check if custom notification should be sent
1060 *
1061 * @param Notification $notification
1062 * @param array $appointmentArray
1063 *
1064 * @return bool
1065 *
1066 */
1067 public function checkCustom($notification, $appointmentArray, $sendDefault)
1068 {
1069 if (!$sendDefault && !$notification->getCustomName()) {
1070 return false;
1071 }
1072 if ($notification->getCustomName() && $notification->getEntityIds()) {
1073 $entityId = $appointmentArray['type'] === Entities::EVENT ? $appointmentArray['id'] : $appointmentArray['serviceId'];
1074 if (!in_array($entityId, $notification->getEntityIds())) {
1075 if (!in_array($appointmentArray['parentId'], $notification->getEntityIds())) {
1076 //Shouldn't be sent
1077 return false;
1078 }
1079 }
1080 }
1081 return true;
1082 }
1083
1084 /**
1085 * Check if default notification should be sent
1086 *
1087 * @param Collection $notifications
1088 * @param array $appointmentArray
1089 *
1090 * @return bool
1091 *
1092 */
1093 public function sendDefault($notifications, $appointmentArray)
1094 {
1095 $entityId = $appointmentArray['type'] === Entities::EVENT ? $appointmentArray['id'] : $appointmentArray['serviceId'];
1096 $parentId = $appointmentArray['parentId'];
1097 return empty(
1098 array_filter(
1099 $notifications->toArray(),
1100 function ($a) use (&$entityId, &$parentId) {
1101 return $a['customName'] && $a['sendOnlyMe'] &&
1102 ($a['entityIds'] === null || in_array($entityId, $a['entityIds']) || ($parentId && in_array($parentId, $a['entityIds'])));
1103 }
1104 )
1105 );
1106 }
1107
1108 /**
1109 * @param array $data
1110 * @param bool $logNotification
1111 *
1112 * @throws QueryExecutionException
1113 * @throws InvalidArgumentException
1114 */
1115 public function sendPackageNotifications($data, $logNotification, $notifyCustomers = true, $invoice = null)
1116 {
1117 /** @var Collection $customerNotifications */
1118 $customerNotifications = $this->getByNameAndType(
1119 "customer_package_" . $data['status'],
1120 $this->type
1121 );
1122
1123 $data['isForCustomer'] = true;
1124
1125 foreach ($customerNotifications->getItems() as $customerNotification) {
1126 if ($customerNotification->getStatus()->getValue() === NotificationStatus::ENABLED && $notifyCustomers) {
1127 $this->sendNotification(
1128 $data,
1129 $customerNotification,
1130 $logNotification,
1131 null,
1132 null,
1133 $invoice
1134 );
1135 }
1136 }
1137
1138 /** @var Collection $providerNotifications */
1139 $providerNotifications = $this->getByNameAndType(
1140 "provider_package_" . $data['status'],
1141 $this->type
1142 );
1143
1144 $data['isForCustomer'] = false;
1145 foreach ($providerNotifications->getItems() as $providerNotification) {
1146 if ($providerNotification->getStatus()->getValue() === NotificationStatus::ENABLED) {
1147 $this->sendNotification(
1148 $data,
1149 $providerNotification,
1150 $logNotification
1151 );
1152 }
1153 }
1154 }
1155
1156 /**
1157 * @param array $data
1158 * @param bool $logNotification
1159 *
1160 * @throws QueryExecutionException
1161 * @throws InvalidArgumentException
1162 */
1163 public function sendCartNotifications($data, $logNotification, $notifyCustomers = true, $invoice = null)
1164 {
1165 /** @var Collection $customerNotifications */
1166 $customerNotifications = $this->getByNameAndType(
1167 'customer_cart',
1168 $this->type
1169 );
1170
1171 $data['isForCustomer'] = true;
1172
1173 foreach ($customerNotifications->getItems() as $customerNotification) {
1174 if ($customerNotification->getStatus()->getValue() === NotificationStatus::ENABLED && $notifyCustomers) {
1175 $this->sendNotification(
1176 $data,
1177 $customerNotification,
1178 $logNotification,
1179 null,
1180 null,
1181 $invoice
1182 );
1183 }
1184 }
1185
1186 /** @var Collection $providerNotifications */
1187 $providerNotifications = $this->getByNameAndType(
1188 'provider_cart',
1189 $this->type
1190 );
1191
1192 $data['isForCustomer'] = false;
1193
1194 foreach ($providerNotifications->getItems() as $providerNotification) {
1195 if ($providerNotification->getStatus()->getValue() === NotificationStatus::ENABLED) {
1196 $this->sendNotification(
1197 $data,
1198 $providerNotification,
1199 $logNotification
1200 );
1201 }
1202 }
1203 }
1204
1205 /**
1206 * Get User info for notification
1207 *
1208 * @param string $userType
1209 * @param array $entityData
1210 * @param int $bookingKey
1211 * @param array $emailData
1212 *
1213 * @return array
1214 * @throws QueryExecutionException
1215 */
1216 protected function getUsersInfo($userType, $entityData, $bookingKey, $emailData)
1217 {
1218 /** @var ProviderRepository $providerRepository */
1219 $providerRepository = $this->container->get('domain.users.providers.repository');
1220
1221 /** @var \AmeliaBooking\Application\Services\Settings\SettingsService $settingsAS*/
1222 $settingsAS = $this->container->get('application.settings.service');
1223
1224
1225 $usersInfo = [];
1226
1227 switch ($userType) {
1228 case (Entities::CUSTOMER):
1229 switch ($entityData['type']) {
1230 case (Entities::APPOINTMENT):
1231 case (Entities::EVENT):
1232 if ($bookingKey !== null) {
1233 $usersInfo[$entityData['bookings'][$bookingKey]['customerId']] = [
1234 'id' => $entityData['bookings'][$bookingKey]['customerId'],
1235 'email' => $emailData['customer_email'],
1236 'phone' => $emailData['customer_phone']
1237 ];
1238 }
1239
1240 break;
1241
1242 case (Entities::PACKAGE):
1243 case (Entities::APPOINTMENTS):
1244 $usersInfo[$entityData['customer']['id']] = [
1245 'id' => $entityData['customer']['id'],
1246 'email' => $entityData['customer']['email'],
1247 'phone' => $entityData['customer']['phone']
1248 ];
1249
1250 break;
1251 }
1252
1253
1254 break;
1255
1256 case (Entities::PROVIDER):
1257 switch ($entityData['type']) {
1258 case (Entities::APPOINTMENT):
1259 $usersInfo[$entityData['providerId']] = [
1260 'id' => $entityData['providerId'],
1261 'email' => $emailData['employee_email'],
1262 'phone' => $emailData['employee_phone']
1263 ];
1264
1265 break;
1266
1267 case (Entities::EVENT):
1268 foreach ((array)$entityData['providers'] as $provider) {
1269 $usersInfo[$provider['id']] = [
1270 'id' => $provider['id'],
1271 'email' => $provider['email'],
1272 'phone' => $provider['phone']
1273 ];
1274 }
1275 if ($entityData['organizerId']) {
1276 $organizer = $providerRepository->getById($entityData['organizerId'])->toArray();
1277 $usersInfo[$organizer['id']] = [
1278 'id' => $organizer['id'],
1279 'email' => $organizer['email'],
1280 'phone' => $organizer['phone']
1281 ];
1282 }
1283
1284 break;
1285
1286 case (Entities::PACKAGE):
1287 case (Entities::APPOINTMENTS):
1288 foreach ($entityData['recurring'] as $reservation) {
1289 $usersInfo[$reservation['appointment']['provider']['id']] = [
1290 'id' => $reservation['appointment']['provider']['id'],
1291 'email' => $reservation['appointment']['provider']['email'],
1292 'phone' => $reservation['appointment']['provider']['phone']
1293 ];
1294 }
1295 if (empty($entityData['recurring'])) {
1296 if (!empty($entityData['onlyOneEmployee'])) {
1297 $usersInfo[$entityData['onlyOneEmployee']['id']] = [
1298 'id' => $entityData['onlyOneEmployee']['id'],
1299 'email' => $entityData['onlyOneEmployee']['email'],
1300 'phone' => $entityData['onlyOneEmployee']['phone']
1301 ];
1302 }
1303 $emptyPackageEmployees = $settingsAS->getEmptyPackageEmployees();
1304 if (!empty($emptyPackageEmployees)) {
1305 foreach ($emptyPackageEmployees as $employee) {
1306 $usersInfo[$employee['id']] = [
1307 'id' => $employee['id'],
1308 'email' => $employee['email'],
1309 'phone' => $employee['phone']
1310 ];
1311 }
1312 }
1313 }
1314
1315 break;
1316 }
1317
1318 break;
1319 }
1320
1321 return $usersInfo;
1322 }
1323 }
1324