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.
This commit is contained in:
Jeppe Bundgaard
2025-11-11 13:24:32 +01:00
parent 7f838c0961
commit ccb049519c
4 changed files with 175 additions and 36 deletions
+47
View File
@@ -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
);
}
}
+81 -31
View File
@@ -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);
+15 -3
View File
@@ -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();
}
}
@@ -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',
]
);
}
}