PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / trunk
Booking for Appointments and Events Calendar – Amelia vtrunk
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 / WaitingList / WaitingListService.php
ameliabooking / src / Application / Services / WaitingList Last commit date
WaitingListService.php 6 months ago
WaitingListService.php
143 lines
1 <?php
2
3 namespace AmeliaBooking\Application\Services\WaitingList;
4
5 use AmeliaBooking\Application\Services\Notification\ApplicationNotificationService;
6 use AmeliaBooking\Domain\Collection\Collection;
7 use AmeliaBooking\Domain\Entity\Booking\Appointment\Appointment;
8 use AmeliaBooking\Domain\Entity\Bookable\Service\Service;
9 use AmeliaBooking\Domain\Entity\Booking\Appointment\CustomerBooking;
10 use AmeliaBooking\Domain\ValueObjects\String\BookingStatus;
11 use AmeliaBooking\Infrastructure\Common\Container;
12 use AmeliaBooking\Infrastructure\Repository\Booking\Appointment\AppointmentRepository;
13 use AmeliaBooking\Infrastructure\Common\Exceptions\QueryExecutionException;
14 use Interop\Container\Exception\ContainerException;
15 use Slim\Exception\ContainerValueNotFoundException;
16
17 /**
18 * Class WaitingListService
19 *
20 * Encapsulates logic for determining whether a booking should be treated as a
21 * waiting list booking (and therefore skip the regular slot availability check).
22 */
23 class WaitingListService
24 {
25 /** @var Container */
26 private $container;
27
28 /**
29 * @param Container $container
30 */
31 public function __construct(Container $container)
32 {
33 $this->container = $container;
34 }
35
36 /**
37 * Notify waiting-list customers that a spot is available for the given appointment.
38 *
39 * Builds the waiting bookings collection from the appointment entity and
40 * dispatches notifications through all enabled channels.
41 *
42 * @param Appointment $appointment
43 *
44 * @throws ContainerException
45 * @throws ContainerValueNotFoundException
46 * @throws QueryExecutionException
47 */
48 public function sendAvailableSpotNotifications($appointment)
49 {
50 $waitingBookings = new Collection();
51
52 foreach ($appointment->getBookings()->getItems() as $booking) {
53 if ($booking->getStatus()->getValue() === BookingStatus::WAITING) {
54 $waitingBookings->addItem($booking);
55 }
56 }
57
58 if ($waitingBookings->length()) {
59 /** @var ApplicationNotificationService $applicationNotificationService */
60 $applicationNotificationService = $this->container->get('application.notification.service');
61
62 $applicationNotificationService->sendWaitingListAvailableSpotNotifications(
63 $appointment,
64 $waitingBookings
65 );
66 }
67 }
68
69 /**
70 * Determine if current booking qualifies as a waiting list booking.
71 *
72 * @param Service $service
73 * @param array $appointmentData (expects bookingStart, serviceId, providerId, bookings array)
74 * @param CustomerBooking $booking
75 *
76 * @return bool True if booking is legitimate waiting list booking (skip slot check), false otherwise.
77 * @throws ContainerValueNotFoundException
78 * @throws ContainerException
79 * @throws QueryExecutionException
80 */
81 public function isWaitingListBooking($service, $appointmentData, $booking)
82 {
83 if (empty($appointmentData['bookings'][0]['status']) || $appointmentData['bookings'][0]['status'] !== BookingStatus::WAITING) {
84 return false;
85 }
86
87 // Extract waiting list settings from service settings JSON
88 $rawSettings = null;
89 if ($service->getSettings()) {
90 $rawSettings = $service->getSettings()->getValue() ?? $service->getSettings();
91 }
92
93 $decoded = [];
94 if (is_string($rawSettings)) {
95 $decoded = json_decode($rawSettings, true) ?: [];
96 } elseif (is_array($rawSettings)) {
97 $decoded = $rawSettings;
98 }
99
100 $waitingSettings = $decoded['waitingList'] ?? [];
101 $waitingEnabled = !empty($waitingSettings['enabled']);
102 $waitingCapacity = isset($waitingSettings['maxCapacity']) ? (int)$waitingSettings['maxCapacity'] : 0;
103
104 if (!$waitingEnabled || $waitingCapacity <= 0) {
105 return false; // feature not enabled / no capacity defined
106 }
107
108 // Retrieve existing appointment(s) at same slot
109 /** @var AppointmentRepository $appointmentRepo */
110 $appointmentRepo = $this->container->get('domain.booking.appointment.repository');
111
112 $existingAppointments = $appointmentRepo->getFiltered([
113 'dates' => [$appointmentData['bookingStart'], $appointmentData['bookingStart']],
114 'services' => [$appointmentData['serviceId']],
115 'providers' => [$appointmentData['providerId']],
116 'skipServices' => true,
117 'skipProviders' => true,
118 'skipCustomers' => true,
119 ]);
120
121 if (!$existingAppointments->length()) {
122 return false;
123 }
124
125 $currentWaitingPersons = 0;
126 foreach ($existingAppointments->getItems() as $existingAppointment) {
127 foreach ($existingAppointment->getBookings()->getItems() as $existingBooking) {
128 if ($existingBooking->getStatus()->getValue() === BookingStatus::WAITING) {
129 $currentWaitingPersons += $existingBooking->getPersons()->getValue();
130 }
131 }
132 }
133
134 $newPersons = $booking->getPersons() ? $booking->getPersons()->getValue() : 1;
135
136 if ($currentWaitingPersons + $newPersons <= $waitingCapacity) {
137 return true;
138 }
139
140 return false;
141 }
142 }
143