From ccb049519c0e753d1f6e8169bcf93d0b1ae78fea Mon Sep 17 00:00:00 2001 From: Jeppe Bundgaard Date: Tue, 11 Nov 2025 13:24:32 +0100 Subject: [PATCH] Add support for customer booking notifications and phone number updates - Introduced phone number update functionality in `userSecurityRoute`. - Enhanced `order_bookings_o` to send booking confirmations to customers via SMS and email. - Added `sendOrderBookingConfirmationEmail` method in `email` for HTML-based email notifications. - Updated `notifyNewBooking` to include customer confirmation logic with SMS and email. - Adjusted `users_o` to make phone and email properties public and added `setPhoneNumber` method. - Optimized notification logic in `order_bookings_o` with department and customer-specific notifications. --- services/nginx/app/classes/email.php | 47 ++++++++ .../nginx/app/objects/order_bookings_o.php | 112 +++++++++++++----- services/nginx/app/objects/users_o.php | 18 ++- .../nginx/app/routes/userSecurityRoute.php | 34 +++++- 4 files changed, 175 insertions(+), 36 deletions(-) diff --git a/services/nginx/app/classes/email.php b/services/nginx/app/classes/email.php index 020529e6..f40cc6cc 100644 --- a/services/nginx/app/classes/email.php +++ b/services/nginx/app/classes/email.php @@ -335,5 +335,52 @@ class email implements email_i ); } + /** + * @throws JsonException + * @throws Exception + */ + public function sendOrderBookingConfirmationEmail(\objects\order_bookings_o $order_booking): void + { + // Validate the booking object + $order_booking->requireSelected(); + // Get customer details + $customer = (new users_o())->getUserByCustomerNumber((int)$order_booking->customer_number->value()); + $customer->requireSelected(); + $recipient_name = $customer->getCustomerName((int)$customer->customer_number->value()); + $recipient_email = $customer->email->value(); + // Get the department details + $department = (new departments_o())->select((int)$order_booking->department->value()); + $department->requireSelected(); + // Generate HTML email + $html = self::generateHtmlHeader(); + // Add email content + $html .= (new email_template_booking_confirmation( + $order_booking->id, + $recipient_name, + $customer->customer_number->value(), + $customer->email->value(), + $order_booking->reference->value(), + $order_booking->reg_1->value(), + $order_booking->reg_2->value(), + ($order_booking->containsWashCertificateItem() ? 'Ja' : 'Nej'), + $customer->email->value(), // Wash certificate email is the customer's email + $order_booking->datetime->value(), + (string)$department->getDepartmentName((int)$department->id), + (string)$department->getBranding()->address->value(), + !!$order_booking->pickup->value() ? 'Ja' : 'Nej', + $order_booking->note->value() + ))->generate_html(); + // Add email footer + $html .= self::generateHtmlFooter(); + // Send email + $this->sendEmailMailerSend( + $recipient_email, + $department->getDepartmentName((int)$department->id), + 'Truck Wash Booking ( ID: ' . $order_booking->id . ', REF: ' . $order_booking->reference->value() . ' )', + '', + $html + ); + } + } \ No newline at end of file diff --git a/services/nginx/app/objects/order_bookings_o.php b/services/nginx/app/objects/order_bookings_o.php index e2d6eabc..1c5f90ed 100644 --- a/services/nginx/app/objects/order_bookings_o.php +++ b/services/nginx/app/objects/order_bookings_o.php @@ -73,8 +73,10 @@ class order_bookings_o extends db public function notifyNewBooking(): void { self::requireSelected(); + $department_notification = false; + $customer_notification = true; /** - * Department booking notification + * Get the department, branding and customer */ // Get the department from the booking $department = (new departments_o())->select((int)$this->department->value()); @@ -90,36 +92,84 @@ class order_bookings_o extends db $customer = (new users_o())->getUserByCustomerNumber((int)$this->customer_number->value()); $department_array = $department->asArray(); $customer_array = $customer->asArray(); - // Define delivery methods - $deliverSlack = (boolean)!empty($department->slack_webhook->value()); - $deliverSMS = true; - // Check if the department has a slack webhook - if ($deliverSlack) { - // Construct email - $message = "*" . $branding->name->value() . "*\n"; - $message .= "_New Booking Created (TEST)_\n\n"; - $message .= "*Customer:* " . $customer->getCustomerName($customer_array['customer_number']) . " (`" . $customer_array['customer_number'] . "`)\n"; - // Send a notification to the department - $slack = new slack(); - $slack->send_department_booking_notification($department->id, $message); - } - - if ($deliverSMS) { - // Send an SMS notification to the department - $gatewayapi = new gatewayapi(); - try { - $gatewayapi->send( - [ - // Add all the phone numbers from the department - ...$department->notificationSmsPhoneNumbers() - ], - 'New booking from ' . $customer->getCustomerName($customer_array['customer_number']) . ' (' . $this->id . ')', - ); - } catch (Exception $e) { - // Log the error - $logs = new logs_o(); - $logs->add('gatewayapi', 0, 3, 0, 'SEND_DEPARTMENT_BOOKING_NOTIFICATION', $e->getMessage()); + /** + * Department booking notification + */ + if ($department_notification) { + // Define delivery methods + $deliverSlack = (boolean)!empty($department->slack_webhook->value()); + $deliverSMS = true; + // Check if the department has a slack webhook + if ($deliverSlack) { + // Construct email + $message = "*" . $branding->name->value() . "*\n"; + $message .= "_New Booking Created (TEST)_\n\n"; + $message .= "*Customer:* " . $customer->getCustomerName($customer_array['customer_number']) . " (`" . $customer_array['customer_number'] . "`)\n"; + // Send a notification to the department + $slack = new slack(); + $slack->send_department_booking_notification($department->id, $message); } + + if ($deliverSMS) { + // Send an SMS notification to the department + $gatewayapi = new gatewayapi(); + try { + $gatewayapi->send( + [ + // Add all the phone numbers from the department + ...$department->notificationSmsPhoneNumbers() + ], + 'New booking from ' . $customer->getCustomerName($customer_array['customer_number']) . ' (' . $this->id . ')', + ); + } catch (Exception $e) { + // Log the error + $logs = new logs_o(); + $logs->add('gatewayapi', 0, 3, 0, 'SEND_DEPARTMENT_BOOKING_NOTIFICATION', $e->getMessage()); + } + } + } + /** + * Customer booking confirmation + */ + if ($customer_notification) { + // Check if the customer has a phone number + $country_code = $customer->phone_country_code->value(); + $customer_phone = $customer->phone->value(); + $customer_email = $customer->email->value(); + if (empty($customer_phone) && empty($customer_email)) { + return; + } + if (!empty($customer_phone)) { + // Send an SMS confirmation to the customer + $gatewayapi = new gatewayapi(); + $message = "Tak for din booking hos " . $branding->name->value() . ". Dit bookingnummer er " . $this->id . ". " + . "\nDato: " . date('d-m-Y', strtotime($this->datetime->value())) . "." + . "\nKøretøj: " . $this->reg_1->value() . (!empty($this->reg_2->value()) ? ", " . $this->reg_2->value() : "") . ( + !empty($this->reg_3->value()) ? ", " . $this->reg_3->value() : "" + ) .". " + . "\nAdresse: " . $branding->address->value() . ". " + . "\nVi glæder os til at se dig!"; + try { + $gatewayapi->send( + [ + (string)$country_code . (string)$customer_phone, + ], + $message, + true + ); + } catch (Exception $e) { + // Log the error + $logs = new logs_o(); + $logs->add('gatewayapi', 0, 3, 0, 'SEND_CUSTOMER_BOOKING_CONFIRMATION', $e->getMessage()); + } + } + // Check if the customer has an email + if (!empty($customer_email)) { + // Send an email confirmation to the customer + $email = new email(); + $email->sendOrderBookingConfirmationEmail($this); + } + // TODO: Send booking confirmation to customer } } @@ -230,7 +280,7 @@ class order_bookings_o extends db /** * @throws Exception */ - private function containsWashCertificateItem(): bool + public function containsWashCertificateItem(): bool { self::requireSelected(); return self::containsProductId(41); diff --git a/services/nginx/app/objects/users_o.php b/services/nginx/app/objects/users_o.php index 0ae5d2cd..901734ad 100644 --- a/services/nginx/app/objects/users_o.php +++ b/services/nginx/app/objects/users_o.php @@ -34,9 +34,9 @@ class users_o extends db public language_pack_en_us $language_pack; public object_property $xlvask_customer_id; protected object_property $password; - protected object_property $phone_country_code; - protected object_property $phone; - protected object_property $email; + public object_property $phone_country_code; + public object_property $phone; + public object_property $email; protected array $wash_subscription_transactions; public function structure(): void @@ -1333,4 +1333,16 @@ class users_o extends db return $customer_numbers; } + /** + * @throws Exception + */ + public function setPhoneNumber(int $phone_number, int $country_code = 45): void + { + self::requireSelected(); + // Set the phone number + $this->phone->set((int)$phone_number); + $this->phone_country_code->set((int)$country_code); + $this->objectChanged(); + } + } \ No newline at end of file diff --git a/services/nginx/app/routes/userSecurityRoute.php b/services/nginx/app/routes/userSecurityRoute.php index 8b71165e..0e7d45fb 100644 --- a/services/nginx/app/routes/userSecurityRoute.php +++ b/services/nginx/app/routes/userSecurityRoute.php @@ -34,7 +34,7 @@ class userSecurityRoute } // Validate the password $password = (string)self::getParameter('password'); - self::requireMinLength('password', 5); + self::requireMinLength('password', 4); self::requireMaxLength('password', 255); self::requireType($password, self::type_string()); if (!$user->passwordMatches($password)) { @@ -65,7 +65,7 @@ class userSecurityRoute self::requireParameters(['password']); // Validate the password $password = (string)self::getParameter('password'); - self::requireMinLength('password', 5); + self::requireMinLength('password', 4); self::requireMaxLength('password', 255); self::requireType($password, self::type_string()); if (!$user->passwordMatches($password)) { @@ -120,5 +120,35 @@ class userSecurityRoute 'user_security_change_password' => 'Change the password of the user', ] ); + + $this->post('/account/security/change-phone-number', function () { + // Require the user to be logged in + global $response; + self::requirePermission('user_security_change_phone_number'); + $user = (new authentication())->get_user(); + if (!$user) { + (new logs_o())->add('user_security', 'global', 0, 0, 'USER_SECURITY_CHANGE_PHONE_NUMBER', 'User not logged in'); + $response->error('Invalid session', 400); + } + // Require the phone_number parameter + self::requireParameters(['phone_number', 'country_code']); + // Validate the phone number + $phone_number = (int)self::getParameter('phone_number'); + self::requireMinLength('phone_number', 4); + self::requireMaxLength('phone_number', 20); + self::requireType($phone_number, self::type_int()); + $country_code = (int)self::getParameter('country_code'); + self::requireMinLength('country_code', 1); + self::requireMaxLength('country_code', 5); + self::requireType($country_code, self::type_int()); + // Change the phone number + $user->setPhoneNumber($phone_number, $country_code); + (new logs_o())->add('user_security', 'global', 0, $user->id, 'USER_SECURITY_CHANGE_PHONE_NUMBER', 'Phone number changed'); + $response->success(['message' => 'Phone number changed']); + }, + [ + 'user_security_change_phone_number' => 'Change the phone number of the user', + ] + ); } } \ No newline at end of file