From 48e08bed870dc4e6ad1486fb26d0bcc06f285a29 Mon Sep 17 00:00:00 2001 From: azhar bhat Date: Tue, 17 Feb 2026 15:33:31 +0530 Subject: [PATCH 01/14] Improve room management: introduce connected rooms in Back Office --- .../orders/_current_cart_details_data.tpl | 6 +- .../controllers/orders/_product_line.tpl | 3 + .../controllers/products/configuration.tpl | 153 ++++++++++++++++++ .../controllers/products/connected-rooms.tpl | 87 ++++++++++ .../products/modal-connected-rooms.tpl | 148 +++++++++++++++++ controllers/admin/AdminOrdersController.php | 1 + .../classes/HotelCartBookingData.php | 1 + .../classes/HotelReservationSystemDb.php | 11 +- .../classes/HotelRoomConnected.php | 86 ++++++++++ .../AdminHotelRoomsBookingController.php | 1 + modules/hotelreservationsystem/define.php | 1 + .../helpers/view/_partials/booking-rooms.tpl | 6 +- 12 files changed, 501 insertions(+), 3 deletions(-) create mode 100644 admin/themes/default/template/controllers/products/connected-rooms.tpl create mode 100644 admin/themes/default/template/controllers/products/modal-connected-rooms.tpl create mode 100644 modules/hotelreservationsystem/classes/HotelRoomConnected.php diff --git a/admin/themes/default/template/controllers/orders/_current_cart_details_data.tpl b/admin/themes/default/template/controllers/orders/_current_cart_details_data.tpl index e3405fa99f..161c83965f 100644 --- a/admin/themes/default/template/controllers/orders/_current_cart_details_data.tpl +++ b/admin/themes/default/template/controllers/orders/_current_cart_details_data.tpl @@ -51,7 +51,11 @@ {assign var=curr_id value=$cart->id_currency|intval} {foreach from=$cart_detail_data item=data} - {$data.room_num|escape:'html':'UTF-8'} {hook h='displayRoomNumAfter' data=$data type='adminOrder'} + {$data.room_num|escape:'html':'UTF-8'} + {if isset($data['connected_rooms'][$data['id_room']]) && $data['connected_rooms'][$data['id_room']]|@count > 0} + {include file="controllers/products/connected-rooms.tpl" htl_connected_rooms= $data['connected_rooms'][$data['id_room']]} + {/if} + {hook h='displayRoomNumAfter' data=$data type='adminOrder'}

{$data.room_type|escape:'html':'UTF-8'}

diff --git a/admin/themes/default/template/controllers/orders/_product_line.tpl b/admin/themes/default/template/controllers/orders/_product_line.tpl index 1e44a648f2..299c0fa22c 100644 --- a/admin/themes/default/template/controllers/orders/_product_line.tpl +++ b/admin/themes/default/template/controllers/orders/_product_line.tpl @@ -29,6 +29,9 @@ {if $data.is_back_order} {l s='overbooked'} {/if} + {if isset($data['connected_rooms'][$data['id_room']]) && $data['connected_rooms'][$data['id_room']]|@count > 0} + {include file="controllers/products/connected-rooms.tpl" htl_connected_rooms=$data['connected_rooms'][$data['id_room']]} + {/if} diff --git a/admin/themes/default/template/controllers/products/configuration.tpl b/admin/themes/default/template/controllers/products/configuration.tpl index 7f0bc0f601..c960e3f53d 100644 --- a/admin/themes/default/template/controllers/products/configuration.tpl +++ b/admin/themes/default/template/controllers/products/configuration.tpl @@ -67,6 +67,14 @@ + + + {hook h='displayHotelRoomListTableHeaderColumn'} {l s='--'} @@ -108,6 +116,16 @@ {* Since the data can also be used from post incase of errors, which will cause issues with the id index *} + + {if isset($room_info['id'])} + + + + {/if} + {if isset($room_info['id'])} {hook h='displayHotelRoomListTableRowColumn' index=$key id_room=$room_info['id']} {else} @@ -1885,6 +1903,141 @@ return false; } } + //connected modal + $(document).on('click', '.connectedRoomModal', function(e) { + e.preventDefault(); + + let roomId = $(this).data('id-room'); + let hotelId = $(this).data('id-hotel'); + let currentRoomType = $('[name="id_product"]').val(); + $.ajax({ + type: 'POST', + url: prod_link, + dataType: 'json', + data: { + ajax: true, + action: 'getModalConnectedRooms', + room_id: roomId, + hotel_id: hotelId, + current_roomtype: currentRoomType, + }, + success: function(response) { + $('#modalLoader').remove(); + if (response.success) { + if ($('#connectedRoomModal').length) { + $('#connectedRoomModal').modal('hide'); + $('#connectedRoomModal').data('bs.modal', null); + $('#connectedRoomModal').remove(); + } + $('.modal-backdrop').remove(); + $('body').removeClass('modal-open'); + $("#footer").next(".bootstrap").append(response.html); + $('#connectedRoomModal').modal({ + backdrop: 'static', + keyboard: true + }); + } else { + alert(response.message); + } + } + + + }); + }); + function filterRoomsByType() { + var selectedType = $('#connect_room_type').val(); + var firstVisible = null; + $('#connect_room option').each(function() { + if ($(this).data('type') == selectedType) { + $(this).show(); + if (!firstVisible) firstVisible = $(this); + } else { + $(this).hide(); + } + }); + if (firstVisible) { + firstVisible.prop('selected', true); + } + } + $(document).on('change', '#connect_room_type', function() { + filterRoomsByType(); + }); + $(document).on('shown.bs.modal', '#connectedRoomModal', function() { + filterRoomsByType(); + }); + //add connected room + $(document).on('click', '#save_connected_room', function() { + var mainRoomId = $('#connected_room_main_id').val(); + var connectedRoomId = $('#connect_room').val(); + var hotelId = $('#hotel_id').val(); + let currentRoomType = $('[name="id_product"]').val(); + if (!connectedRoomId) { + alert('Please select a room'); + return; + } + $.ajax({ + url: prod_link, + type: 'POST', + dataType: 'json', + data: { + ajax: true, + action: 'ManageConnectedRoom', + mode: 'add', + hotel_id: hotelId, + room_id: mainRoomId, + connected_room_id: connectedRoomId, + current_roomtype: currentRoomType, + }, + beforeSend: function() { + $('#save_connected_room').prop('disabled', true); + }, + success: function(response) { + if (response.success) { + showSuccessMessage(response.message); + $('#connectedRoomModal .modal-content').replaceWith($(response.html).find('.modal-content')); + filterRoomsByType(); + } else { + alert(response.message); + } + }, + complete: function() { + $('#save_connected_room').prop('disabled', false); + } + }); + }); + //remove connected room + $(document).on('click', '.delete-connected-room', function() { + var roomId = $(this).data('room-id'); + var connectedRoomId = $(this).data('connected-room-id'); + var connectedId = $(this).data('connected-id'); + var hotelId = $(this).data('hotel-id'); + let currentRoomType = $('[name="id_product"]').val(); + $.ajax({ + url: prod_link, + type: 'POST', + dataType: 'json', + data: { + ajax: true, + action: 'ManageConnectedRoom', + mode: 'delete', + room_id: roomId, + connected_room_id: connectedRoomId, + connected_id: connectedId, + hotel_id: hotelId, + current_roomtype: currentRoomType, + }, + success: function(response) { + if (response.success) { + showSuccessMessage(response.message); + $('#connectedRoomModal .modal-content').replaceWith($(response.html).find( + '.modal-content')); + filterRoomsByType(); + } else { + alert(response.message); + } + } + }); + }); }); diff --git a/admin/themes/default/template/controllers/products/connected-rooms.tpl b/admin/themes/default/template/controllers/products/connected-rooms.tpl new file mode 100644 index 0000000000..2e33a4dafd --- /dev/null +++ b/admin/themes/default/template/controllers/products/connected-rooms.tpl @@ -0,0 +1,87 @@ +{** +* NOTICE OF LICENSE +* +* This source file is subject to the Open Software License version 3.0 +* that is bundled with this package in the file LICENSE.md +* It is also available through the world-wide-web at this URL: +* https://opensource.org/license/osl-3-0-php +* If you did not receive a copy of the license and are unable to +* obtain it through the world-wide-web, please send an email +* to support@qloapps.com so we can send you a copy immediately. +* +* DISCLAIMER +* +* Do not edit or add to this file if you wish to upgrade this module to a newer +* versions in the future. If you wish to customize this module for your needs +* please refer to https://store.webkul.com/customisation-guidelines for more information. +* +* @author Webkul IN +* @copyright Since 2010 Webkul +* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0 +*} +{if isset($htl_connected_rooms) && $htl_connected_rooms|@count > 0} + + +
+ + +
+
+{/if} + \ No newline at end of file diff --git a/admin/themes/default/template/controllers/products/modal-connected-rooms.tpl b/admin/themes/default/template/controllers/products/modal-connected-rooms.tpl new file mode 100644 index 0000000000..38b34e2d87 --- /dev/null +++ b/admin/themes/default/template/controllers/products/modal-connected-rooms.tpl @@ -0,0 +1,148 @@ +{** +* NOTICE OF LICENSE +* +* This source file is subject to the Open Software License version 3.0 +* that is bundled with this package in the file LICENSE.md +* It is also available through the world-wide-web at this URL: +* https://opensource.org/license/osl-3-0-php +* If you did not receive a copy of the license and are unable to +* obtain it through the world-wide-web, please send an email +* to support@qloapps.com so we can send you a copy immediately. +* +* DISCLAIMER +* +* Do not edit or add to this file if you wish to upgrade this module to a newer +* versions in the future. If you wish to customize this module for your needs +* please refer to https://store.webkul.com/customisation-guidelines for more information. +* +* @author Webkul IN +* @copyright Since 2010 Webkul +* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0 +*} + \ No newline at end of file diff --git a/controllers/admin/AdminOrdersController.php b/controllers/admin/AdminOrdersController.php index 30026bccaf..1bc5484576 100644 --- a/controllers/admin/AdminOrdersController.php +++ b/controllers/admin/AdminOrdersController.php @@ -3485,6 +3485,7 @@ public function renderView() $order_detail_data[$key]['amt_with_qty_tax_incl'] = $value['total_price_tax_incl']; $order_detail_data[$key]['room_type_info'] = $objHotelRoomType->getRoomTypeInfoByIdProduct($value['id_product']); $order_detail_data[$key]['total_room_tax'] = $order_detail_data[$key]['total_room_price_ti'] - $order_detail_data[$key]['total_room_price_te']; + $order_detail_data[$key]['connected_rooms'] = HotelRoomConnected::getConnectedRoomsByHotel($value['id_hotel'], Context::getContext()->language->id, $value['id_room'], true); if (isset($value['refund_info']) && $value['refund_info']['refunded'] diff --git a/modules/hotelreservationsystem/classes/HotelCartBookingData.php b/modules/hotelreservationsystem/classes/HotelCartBookingData.php index be3cabc94f..d57e208d00 100644 --- a/modules/hotelreservationsystem/classes/HotelCartBookingData.php +++ b/modules/hotelreservationsystem/classes/HotelCartBookingData.php @@ -1331,6 +1331,7 @@ public function getCartFormatedBookinInfoByIdCart($id_cart) ); $cart_detail_data[$key]['amt_with_qty'] = $roomTypeDateRangePrice['total_price_tax_excl']; + $cart_detail_data[$key]['connected_rooms'] = HotelRoomConnected::getConnectedRoomsByHotel($obj_htl_room_info->id_hotel, Context::getContext()->language->id, $obj_htl_room_info->id, true); } } if ($cart_detail_data) { diff --git a/modules/hotelreservationsystem/classes/HotelReservationSystemDb.php b/modules/hotelreservationsystem/classes/HotelReservationSystemDb.php index ca44a28267..1260200cd0 100644 --- a/modules/hotelreservationsystem/classes/HotelReservationSystemDb.php +++ b/modules/hotelreservationsystem/classes/HotelReservationSystemDb.php @@ -552,6 +552,14 @@ public function getModuleSql() SELECT 5, `id_lang`, 'Hotel Amenities Block', 'Configure Hotels Amenities settings. You can display hotel amenities images using this block. This block will be displayed on home page.' FROM `"._DB_PREFIX_."lang` ORDER BY `id_lang`;", + "CREATE TABLE IF NOT EXISTS `"._DB_PREFIX_."htl_connected_room` ( + `id_connected_room` int(11) NOT NULL AUTO_INCREMENT, + `id_room_information` int(11) NOT NULL, + `id_room` int(11) NOT NULL, + `date_add` datetime NOT NULL, + `date_upd` datetime NOT NULL, + PRIMARY KEY (`id_connected_room`) + ) ENGINE="._MYSQL_ENGINE_." DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;", ); } @@ -615,7 +623,8 @@ public function dropTables() `'._DB_PREFIX_.'htl_room_type_bed_type`, `'._DB_PREFIX_.'htl_access`, `'._DB_PREFIX_.'htl_settings_link`, - `'._DB_PREFIX_.'htl_settings_link_lang`' + `'._DB_PREFIX_.'htl_settings_link_lang` + `'._DB_PREFIX_.'htl_connected_room`' ); } } diff --git a/modules/hotelreservationsystem/classes/HotelRoomConnected.php b/modules/hotelreservationsystem/classes/HotelRoomConnected.php new file mode 100644 index 0000000000..be9ccaf6bc --- /dev/null +++ b/modules/hotelreservationsystem/classes/HotelRoomConnected.php @@ -0,0 +1,86 @@ + 'htl_connected_room', + 'primary' => 'id_connected_room', + 'fields' => array( + 'id_room_information' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true), + 'id_room' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true), + 'date_add' => array('type' => self::TYPE_DATE), + 'date_upd' => array('type' => self::TYPE_DATE), + ), + ); + public static function getConnectedRoomsByHotel($idHotel, $idLang, $roomId = null, $isConnected = true) + { + if ($isConnected) { + $sql = 'SELECT + hcr.id_connected_room,hcr.id_room_information,hcr.id_room,main_room.id_hotel,main_room.room_num AS main_room_num,connected_room.room_num AS connected_room_num, + main_pl.name AS main_room_type,conn_pl.name AS connected_room_type + FROM `' . _DB_PREFIX_ . 'htl_connected_room` hcr + INNER JOIN `' . _DB_PREFIX_ . 'htl_room_information` main_room ON main_room.id = hcr.id_room_information + INNER JOIN `' . _DB_PREFIX_ . 'htl_room_information` connected_room ON connected_room.id = hcr.id_room + LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` main_pl ON (main_pl.id_product = main_room.id_product AND main_pl.id_lang = ' . $idLang . ') + LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` conn_pl ON (conn_pl.id_product = connected_room.id_product AND conn_pl.id_lang = ' . $idLang . ') + WHERE main_room.id_hotel = ' . $idHotel . ' + AND connected_room.id_hotel = ' . $idHotel; + if (!empty($roomId)) { + $sql .= ' AND main_room.id = ' . (int) $roomId; + $results = Db::getInstance()->executeS($sql); + } else { + $results = Db::getInstance()->executeS($sql); + } + $grouped = []; + foreach ($results as $row) { + $roomId = $row['id_room_information']; + $connType = $row['connected_room_type']; + if (!isset($grouped[$roomId])) { + $grouped[$roomId] = []; + } + if (!isset($grouped[$roomId][$connType])) { + $grouped[$roomId][$connType] = []; + } + $grouped[$roomId][$connType][] = $row; + } + return $grouped; + } else { + $sql = 'SELECT hri.*, pl.name AS room_type + FROM `' . _DB_PREFIX_ . 'htl_room_information` hri + LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl ON (pl.id_product = hri.id_product AND pl.id_lang = ' . $idLang . ') + WHERE hri.id_hotel = ' . (int) $idHotel . ' + AND hri.id != ' . (int) $roomId . ' + AND NOT EXISTS (SELECT 1 FROM `' . _DB_PREFIX_ . 'htl_connected_room` cr WHERE cr.id_room_information = ' . (int) $roomId . ' AND cr.id_room = hri.id) + ORDER BY hri.room_num ASC'; + return Db::getInstance()->executeS($sql); + } + } +} + diff --git a/modules/hotelreservationsystem/controllers/admin/AdminHotelRoomsBookingController.php b/modules/hotelreservationsystem/controllers/admin/AdminHotelRoomsBookingController.php index fac60d468f..3cccd6ca7e 100644 --- a/modules/hotelreservationsystem/controllers/admin/AdminHotelRoomsBookingController.php +++ b/modules/hotelreservationsystem/controllers/admin/AdminHotelRoomsBookingController.php @@ -387,6 +387,7 @@ public function assignRoomBookingForm() 'max_child_in_room' => Configuration::get('WK_GLOBAL_MAX_CHILD_IN_ROOM'), 'link' => $this->context->link, 'ALLOTMENT_MANUAL' => HotelBookingDetail::ALLOTMENT_MANUAL, + 'htl_connected_rooms' => HotelRoomConnected::getConnectedRoomsByHotel($this->id_hotel, $this->context->language->id, 0, true), )); if (Configuration::get('PS_BACKOFFICE_SEARCH_TYPE') == HotelBookingDetail::SEARCH_TYPE_OWS) { diff --git a/modules/hotelreservationsystem/define.php b/modules/hotelreservationsystem/define.php index e8a7cc909c..141064a132 100644 --- a/modules/hotelreservationsystem/define.php +++ b/modules/hotelreservationsystem/define.php @@ -49,6 +49,7 @@ require_once 'classes/HotelBranchRefundRules.php'; require_once 'classes/HotelBedType.php'; require_once 'classes/HotelRoomTypeBedType.php'; +require_once 'classes/HotelRoomConnected.php'; // linked products require_once 'classes/RoomTypeServiceProduct.php'; diff --git a/modules/hotelreservationsystem/views/templates/admin/hotel_rooms_booking/helpers/view/_partials/booking-rooms.tpl b/modules/hotelreservationsystem/views/templates/admin/hotel_rooms_booking/helpers/view/_partials/booking-rooms.tpl index 7c68db3507..e83979106e 100644 --- a/modules/hotelreservationsystem/views/templates/admin/hotel_rooms_booking/helpers/view/_partials/booking-rooms.tpl +++ b/modules/hotelreservationsystem/views/templates/admin/hotel_rooms_booking/helpers/view/_partials/booking-rooms.tpl @@ -40,7 +40,11 @@ {foreach from=$book_v['data']['available'] key=avai_k item=avai_v} - {$avai_v['room_num']|escape:'htmlall':'UTF-8'} {hook h='displayRoomNumAfter' data=$avai_v type='available'} + {$avai_v['room_num']|escape:'htmlall':'UTF-8'} + {if isset($htl_connected_rooms[$avai_v['id_room']]) && $htl_connected_rooms[$avai_v['id_room']]|@count > 0} + {include file="controllers/products/connected-rooms.tpl" htl_connected_rooms=$htl_connected_rooms[$avai_v['id_room']]} + {/if} + {hook h='displayRoomNumAfter' data=$avai_v type='available'} {assign var="is_full_date" value=($show_full_date && ($date_from|date_format:'%D' == $date_to|date_format:'%D'))} {dateFormat date=$date_from full=$is_full_date} - {dateFormat date=$date_to full=$is_full_date} {$avai_v['room_comment']|escape:'htmlall':'UTF-8'} From 0a5a5a0289e4c84bb7d99550f4b5d03736827fe4 Mon Sep 17 00:00:00 2001 From: azhar bhat Date: Tue, 17 Feb 2026 17:52:55 +0530 Subject: [PATCH 02/14] fixed ajax error --- controllers/admin/AdminProductsController.php | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/controllers/admin/AdminProductsController.php b/controllers/admin/AdminProductsController.php index 0a7acb76f2..113547c93a 100644 --- a/controllers/admin/AdminProductsController.php +++ b/controllers/admin/AdminProductsController.php @@ -5646,4 +5646,114 @@ public function displayPreviewLink($token, $id, $name = null) return $tpl->fetch(); } + + public function ajaxProcessGetModalConnectedRooms() + { + $roomId = (int) Tools::getValue('room_id'); + $hotelId = (int) Tools::getValue('hotel_id'); + if (!$roomId || !$hotelId) { + die(json_encode([ + 'success' => false, + 'message' => $this->l('Invalid data.') + ])); + } + $objRoomType = new HotelRoomType(); + $connectedRooms = HotelRoomConnected::getConnectedRoomsByHotel($hotelId, $this->context->language->id, $roomId, true); + $notConnectedRooms = HotelRoomConnected::getConnectedRoomsByHotel($hotelId, $this->context->language->id, $roomId, false); + $this->context->smarty->assign(array( + 'htl_connected_rooms' => $connectedRooms, + 'htl_not_connected_rooms' => $notConnectedRooms, + 'main_room_id' => $roomId, + 'hotel_id' => $hotelId, + 'current_roomtype' => Tools::getValue('current_roomtype') + )); + $modalContent = $this->context->smarty->fetch( + 'controllers/products/modal-connected-rooms.tpl' + ); + die(json_encode([ + 'success' => true, + 'html' => $modalContent + ])); + } + + public function ajaxProcessManageConnectedRoom() + { + $mode = Tools::getValue('mode'); + $roomId = (int) Tools::getValue('room_id'); + $connectedRoomId = (int) Tools::getValue('connected_room_id'); + $connectedId = (int) Tools::getValue('connected_id'); + $hotelId = Tools::getValue('hotel_id'); + if (!$roomId || !$connectedRoomId) { + die(json_encode([ + 'success' => false, + 'message' => $this->l('Invalid room data.') + ])); + } + if ($roomId == $connectedRoomId) { + die(json_encode([ + 'success' => false, + 'message' => $this->l('You cannot connect a room to itself.') + ])); + } + if ($mode === 'add') { + $connection = new HotelRoomConnected(); + $connection->id_room_information = $roomId; + $connection->id_room = $connectedRoomId; + if ($connection->add()) { + $connectedRooms = HotelRoomConnected::getConnectedRoomsByHotel($hotelId, $this->context->language->id, $roomId, true); + $notConnectedRooms = HotelRoomConnected::getConnectedRoomsByHotel($hotelId, $this->context->language->id, $roomId, false); + $this->context->smarty->assign(array( + 'htl_connected_rooms' => $connectedRooms, + 'htl_not_connected_rooms' => $notConnectedRooms, + 'main_room_id' => $roomId, + 'hotel_id' => $hotelId, + 'current_roomtype' => Tools::getValue('current_roomtype') + )); + $modalContent = $this->context->smarty->fetch( + 'controllers/products/modal-connected-rooms.tpl' + ); + die(json_encode([ + 'html' => $modalContent, + 'success' => true, + 'message' => $this->l('Room connected successfully.') + ])); + } + + die(json_encode([ + 'success' => false, + 'message' => $this->l('Failed to connect room.') + ])); + } + //remove room + if ($mode === 'delete') { + $objHotelRoomConnected = new HotelRoomConnected($connectedId); + if ($objHotelRoomConnected->delete()) { + $connectedRooms = HotelRoomConnected::getConnectedRoomsByHotel($hotelId, $this->context->language->id, $roomId, true); + $notConnectedRooms = HotelRoomConnected::getConnectedRoomsByHotel($hotelId, $this->context->language->id, $roomId, false); + $this->context->smarty->assign(array( + 'htl_connected_rooms' => $connectedRooms, + 'htl_not_connected_rooms' => $notConnectedRooms, + 'main_room_id' => $roomId, + 'hotel_id' => $hotelId, + 'current_roomtype' => Tools::getValue('current_roomtype') + )); + $modalContent = $this->context->smarty->fetch( + 'controllers/products/modal-connected-rooms.tpl' + ); + die(json_encode([ + 'success' => true, + 'html' => $modalContent, + 'message' => $this->l('Connected room removed successfully.') + ])); + } + die(json_encode([ + 'success' => false, + 'message' => $this->l('Failed to remove connected room.') + ])); + } + die(json_encode([ + 'success' => false, + 'message' => $this->l('Invalid action.') + ])); + } } From 7756fb70713579f270679d8b42b31f81ac579f92 Mon Sep 17 00:00:00 2001 From: azhar bhat Date: Sat, 4 Apr 2026 17:10:32 +0530 Subject: [PATCH 03/14] BO: harden connected rooms (permissions, escaping, safe DB handling) --- .../controllers/products/configuration.tpl | 9 +- .../products/modal-connected-rooms.tpl | 26 +-- controllers/admin/AdminOrdersController.php | 10 +- controllers/admin/AdminProductsController.php | 149 +++++++++++++----- .../classes/HotelReservationSystemDb.php | 7 +- .../classes/HotelRoomConnected.php | 93 ++++++++--- 6 files changed, 218 insertions(+), 76 deletions(-) diff --git a/admin/themes/default/template/controllers/products/configuration.tpl b/admin/themes/default/template/controllers/products/configuration.tpl index c960e3f53d..77f0e2b37d 100644 --- a/admin/themes/default/template/controllers/products/configuration.tpl +++ b/admin/themes/default/template/controllers/products/configuration.tpl @@ -119,9 +119,9 @@ {if isset($room_info['id'])} + data-target="#connectedRoomModal" data-id-room="{$room_info['id']|intval}" + data-id-hotel="{$room_info['id_hotel']|intval}" data-room-num="{$room_info['room_num']|escape:'html':'UTF-8'}" + data-room-type="{$room_info['id_product']|intval}"> {/if} @@ -603,6 +603,7 @@ var rm_status = {$rm_status|@json_encode}; var confirmText = "{l s='Are you sure?' js=1}"; var removeDisableDateText = "{l s='Are you sure you want to remove the selected date range?' js=1}"; + var selectRoomText = "{l s='Please select a room' js=1}"; var currentRoomRow = 0; $(document).ready(function() { var tooltipCounter = 0; @@ -1972,7 +1973,7 @@ var hotelId = $('#hotel_id').val(); let currentRoomType = $('[name="id_product"]').val(); if (!connectedRoomId) { - alert('Please select a room'); + alert(selectRoomText); return; } $.ajax({ diff --git a/admin/themes/default/template/controllers/products/modal-connected-rooms.tpl b/admin/themes/default/template/controllers/products/modal-connected-rooms.tpl index 38b34e2d87..fe023de400 100644 --- a/admin/themes/default/template/controllers/products/modal-connected-rooms.tpl +++ b/admin/themes/default/template/controllers/products/modal-connected-rooms.tpl @@ -33,7 +33,7 @@
\ No newline at end of file + + diff --git a/controllers/admin/AdminOrdersController.php b/controllers/admin/AdminOrdersController.php index d7e024438f..eb77bb77ee 100644 --- a/controllers/admin/AdminOrdersController.php +++ b/controllers/admin/AdminOrdersController.php @@ -3484,7 +3484,15 @@ public function renderView() $order_detail_data[$key]['amt_with_qty_tax_incl'] = $value['total_price_tax_incl']; $order_detail_data[$key]['room_type_info'] = $objHotelRoomType->getRoomTypeInfoByIdProduct($value['id_product']); $order_detail_data[$key]['total_room_tax'] = $order_detail_data[$key]['total_room_price_ti'] - $order_detail_data[$key]['total_room_price_te']; - $order_detail_data[$key]['connected_rooms'] = HotelRoomConnected::getConnectedRoomsByHotel($value['id_hotel'], Context::getContext()->language->id, $value['id_room'], true); + $order_detail_data[$key]['connected_rooms'] = array(); + if (class_exists('HotelRoomConnected')) { + $order_detail_data[$key]['connected_rooms'] = HotelRoomConnected::getConnectedRoomsByHotel( + (int) $value['id_hotel'], + (int) Context::getContext()->language->id, + (int) $value['id_room'], + true + ); + } if (isset($value['refund_info']) && $value['refund_info']['refunded'] diff --git a/controllers/admin/AdminProductsController.php b/controllers/admin/AdminProductsController.php index 113547c93a..ddd932a4d9 100644 --- a/controllers/admin/AdminProductsController.php +++ b/controllers/admin/AdminProductsController.php @@ -5647,8 +5647,39 @@ public function displayPreviewLink($token, $id, $name = null) return $tpl->fetch(); } + /** + * Render connected rooms modal HTML for a given room/hotel. + * + * @param int $hotelId + * @param int $roomId + * @param int $currentRoomType + * + * @return string + */ + protected function getConnectedRoomsModalHtml($hotelId, $roomId, $currentRoomType) + { + $connectedRooms = HotelRoomConnected::getConnectedRoomsByHotel($hotelId, $this->context->language->id, $roomId, true); + $notConnectedRooms = HotelRoomConnected::getConnectedRoomsByHotel($hotelId, $this->context->language->id, $roomId, false); + $this->context->smarty->assign(array( + 'htl_connected_rooms' => $connectedRooms, + 'htl_not_connected_rooms' => $notConnectedRooms, + 'main_room_id' => (int) $roomId, + 'hotel_id' => (int) $hotelId, + 'current_roomtype' => (int) $currentRoomType, + )); + + return $this->context->smarty->fetch('controllers/products/modal-connected-rooms.tpl'); + } + public function ajaxProcessGetModalConnectedRooms() { + if ($this->tabAccess['edit'] !== 1) { + die(json_encode([ + 'success' => false, + 'message' => $this->l('Permission denied.') + ])); + } + $roomId = (int) Tools::getValue('room_id'); $hotelId = (int) Tools::getValue('hotel_id'); if (!$roomId || !$hotelId) { @@ -5657,19 +5688,25 @@ public function ajaxProcessGetModalConnectedRooms() 'message' => $this->l('Invalid data.') ])); } - $objRoomType = new HotelRoomType(); - $connectedRooms = HotelRoomConnected::getConnectedRoomsByHotel($hotelId, $this->context->language->id, $roomId, true); - $notConnectedRooms = HotelRoomConnected::getConnectedRoomsByHotel($hotelId, $this->context->language->id, $roomId, false); - $this->context->smarty->assign(array( - 'htl_connected_rooms' => $connectedRooms, - 'htl_not_connected_rooms' => $notConnectedRooms, - 'main_room_id' => $roomId, - 'hotel_id' => $hotelId, - 'current_roomtype' => Tools::getValue('current_roomtype') - )); - $modalContent = $this->context->smarty->fetch( - 'controllers/products/modal-connected-rooms.tpl' + + if (!class_exists('HotelRoomConnected') || !HotelRoomConnected::isConnectedRoomTableAvailable()) { + die(json_encode([ + 'success' => false, + 'message' => $this->l('Connected rooms feature is not available. Please upgrade the Hotel Reservation System module.') + ])); + } + + $mainRoomHotelId = (int) Db::getInstance()->getValue( + 'SELECT `id_hotel` FROM `' . _DB_PREFIX_ . 'htl_room_information` WHERE `id` = ' . (int) $roomId ); + if ($mainRoomHotelId !== (int) $hotelId) { + die(json_encode([ + 'success' => false, + 'message' => $this->l('Invalid room data.') + ])); + } + + $modalContent = $this->getConnectedRoomsModalHtml($hotelId, $roomId, (int) Tools::getValue('current_roomtype')); die(json_encode([ 'success' => true, 'html' => $modalContent @@ -5678,40 +5715,72 @@ public function ajaxProcessGetModalConnectedRooms() public function ajaxProcessManageConnectedRoom() { + if ($this->tabAccess['edit'] !== 1) { + die(json_encode([ + 'success' => false, + 'message' => $this->l('Permission denied.') + ])); + } + $mode = Tools::getValue('mode'); $roomId = (int) Tools::getValue('room_id'); $connectedRoomId = (int) Tools::getValue('connected_room_id'); $connectedId = (int) Tools::getValue('connected_id'); - $hotelId = Tools::getValue('hotel_id'); + $hotelId = (int) Tools::getValue('hotel_id'); if (!$roomId || !$connectedRoomId) { die(json_encode([ 'success' => false, 'message' => $this->l('Invalid room data.') ])); } + + if (!class_exists('HotelRoomConnected') || !HotelRoomConnected::isConnectedRoomTableAvailable()) { + die(json_encode([ + 'success' => false, + 'message' => $this->l('Connected rooms feature is not available. Please upgrade the Hotel Reservation System module.') + ])); + } + if ($roomId == $connectedRoomId) { die(json_encode([ 'success' => false, 'message' => $this->l('You cannot connect a room to itself.') ])); } + + $mainRoomHotelId = (int) Db::getInstance()->getValue( + 'SELECT `id_hotel` FROM `' . _DB_PREFIX_ . 'htl_room_information` WHERE `id` = ' . (int) $roomId + ); + $connectedRoomHotelId = (int) Db::getInstance()->getValue( + 'SELECT `id_hotel` FROM `' . _DB_PREFIX_ . 'htl_room_information` WHERE `id` = ' . (int) $connectedRoomId + ); + if (!$hotelId || $mainRoomHotelId !== $hotelId || $connectedRoomHotelId !== $hotelId) { + die(json_encode([ + 'success' => false, + 'message' => $this->l('Invalid room data.') + ])); + } + + $currentRoomType = (int) Tools::getValue('current_roomtype'); + if ($mode === 'add') { + $existingConnectedId = (int) Db::getInstance()->getValue( + 'SELECT `id_connected_room` FROM `' . _DB_PREFIX_ . 'htl_connected_room` WHERE `id_room_information` = ' . (int) $roomId . ' AND `id_room` = ' . (int) $connectedRoomId + ); + if ($existingConnectedId) { + $modalContent = $this->getConnectedRoomsModalHtml($hotelId, $roomId, $currentRoomType); + die(json_encode([ + 'html' => $modalContent, + 'success' => true, + 'message' => $this->l('Room is already connected.') + ])); + } + $connection = new HotelRoomConnected(); $connection->id_room_information = $roomId; $connection->id_room = $connectedRoomId; if ($connection->add()) { - $connectedRooms = HotelRoomConnected::getConnectedRoomsByHotel($hotelId, $this->context->language->id, $roomId, true); - $notConnectedRooms = HotelRoomConnected::getConnectedRoomsByHotel($hotelId, $this->context->language->id, $roomId, false); - $this->context->smarty->assign(array( - 'htl_connected_rooms' => $connectedRooms, - 'htl_not_connected_rooms' => $notConnectedRooms, - 'main_room_id' => $roomId, - 'hotel_id' => $hotelId, - 'current_roomtype' => Tools::getValue('current_roomtype') - )); - $modalContent = $this->context->smarty->fetch( - 'controllers/products/modal-connected-rooms.tpl' - ); + $modalContent = $this->getConnectedRoomsModalHtml($hotelId, $roomId, $currentRoomType); die(json_encode([ 'html' => $modalContent, 'success' => true, @@ -5726,20 +5795,26 @@ public function ajaxProcessManageConnectedRoom() } //remove room if ($mode === 'delete') { + if (!$connectedId) { + die(json_encode([ + 'success' => false, + 'message' => $this->l('Invalid data.') + ])); + } + + $matchesConnection = (int) Db::getInstance()->getValue( + 'SELECT `id_connected_room` FROM `' . _DB_PREFIX_ . 'htl_connected_room` WHERE `id_connected_room` = ' . (int) $connectedId . ' AND `id_room_information` = ' . (int) $roomId . ' AND `id_room` = ' . (int) $connectedRoomId + ); + if (!$matchesConnection) { + die(json_encode([ + 'success' => false, + 'message' => $this->l('Invalid data.') + ])); + } + $objHotelRoomConnected = new HotelRoomConnected($connectedId); if ($objHotelRoomConnected->delete()) { - $connectedRooms = HotelRoomConnected::getConnectedRoomsByHotel($hotelId, $this->context->language->id, $roomId, true); - $notConnectedRooms = HotelRoomConnected::getConnectedRoomsByHotel($hotelId, $this->context->language->id, $roomId, false); - $this->context->smarty->assign(array( - 'htl_connected_rooms' => $connectedRooms, - 'htl_not_connected_rooms' => $notConnectedRooms, - 'main_room_id' => $roomId, - 'hotel_id' => $hotelId, - 'current_roomtype' => Tools::getValue('current_roomtype') - )); - $modalContent = $this->context->smarty->fetch( - 'controllers/products/modal-connected-rooms.tpl' - ); + $modalContent = $this->getConnectedRoomsModalHtml($hotelId, $roomId, $currentRoomType); die(json_encode([ 'success' => true, 'html' => $modalContent, diff --git a/modules/hotelreservationsystem/classes/HotelReservationSystemDb.php b/modules/hotelreservationsystem/classes/HotelReservationSystemDb.php index 1260200cd0..f907c111a9 100644 --- a/modules/hotelreservationsystem/classes/HotelReservationSystemDb.php +++ b/modules/hotelreservationsystem/classes/HotelReservationSystemDb.php @@ -558,7 +558,10 @@ public function getModuleSql() `id_room` int(11) NOT NULL, `date_add` datetime NOT NULL, `date_upd` datetime NOT NULL, - PRIMARY KEY (`id_connected_room`) + PRIMARY KEY (`id_connected_room`), + UNIQUE KEY `uniq_room_connection` (`id_room_information`, `id_room`), + KEY `idx_id_room_information` (`id_room_information`), + KEY `idx_id_room` (`id_room`) ) ENGINE="._MYSQL_ENGINE_." DEFAULT CHARSET=utf8 AUTO_INCREMENT=1;", ); } @@ -623,7 +626,7 @@ public function dropTables() `'._DB_PREFIX_.'htl_room_type_bed_type`, `'._DB_PREFIX_.'htl_access`, `'._DB_PREFIX_.'htl_settings_link`, - `'._DB_PREFIX_.'htl_settings_link_lang` + `'._DB_PREFIX_.'htl_settings_link_lang`, `'._DB_PREFIX_.'htl_connected_room`' ); } diff --git a/modules/hotelreservationsystem/classes/HotelRoomConnected.php b/modules/hotelreservationsystem/classes/HotelRoomConnected.php index be9ccaf6bc..0bc358b55b 100644 --- a/modules/hotelreservationsystem/classes/HotelRoomConnected.php +++ b/modules/hotelreservationsystem/classes/HotelRoomConnected.php @@ -23,6 +23,9 @@ class HotelRoomConnected extends ObjectModel { + /** @var bool|null */ + protected static $connectedRoomTableAvailable = null; + public $id_connected_room; public $id_room_information; public $id_room; @@ -39,48 +42,100 @@ class HotelRoomConnected extends ObjectModel 'date_upd' => array('type' => self::TYPE_DATE), ), ); + + /** + * Check whether the connected room table exists and is queryable. + * + * @return bool + */ + public static function isConnectedRoomTableAvailable() + { + if (self::$connectedRoomTableAvailable !== null) { + return (bool) self::$connectedRoomTableAvailable; + } + + $result = Db::getInstance()->executeS('SELECT 1 FROM `' . _DB_PREFIX_ . 'htl_connected_room` LIMIT 1'); + self::$connectedRoomTableAvailable = ($result !== false); + + return (bool) self::$connectedRoomTableAvailable; + } + + /** + * Fetch connected/not-connected rooms for a hotel. + * + * @param int $idHotel + * @param int $idLang + * @param int|null $roomId + * @param bool $isConnected + * + * @return array + */ public static function getConnectedRoomsByHotel($idHotel, $idLang, $roomId = null, $isConnected = true) { + $idHotel = (int) $idHotel; + $idLang = (int) $idLang; + $roomId = (int) $roomId; + if ($isConnected) { + if (!self::isConnectedRoomTableAvailable()) { + return array(); + } + $sql = 'SELECT hcr.id_connected_room,hcr.id_room_information,hcr.id_room,main_room.id_hotel,main_room.room_num AS main_room_num,connected_room.room_num AS connected_room_num, main_pl.name AS main_room_type,conn_pl.name AS connected_room_type FROM `' . _DB_PREFIX_ . 'htl_connected_room` hcr INNER JOIN `' . _DB_PREFIX_ . 'htl_room_information` main_room ON main_room.id = hcr.id_room_information INNER JOIN `' . _DB_PREFIX_ . 'htl_room_information` connected_room ON connected_room.id = hcr.id_room - LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` main_pl ON (main_pl.id_product = main_room.id_product AND main_pl.id_lang = ' . $idLang . ') - LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` conn_pl ON (conn_pl.id_product = connected_room.id_product AND conn_pl.id_lang = ' . $idLang . ') - WHERE main_room.id_hotel = ' . $idHotel . ' - AND connected_room.id_hotel = ' . $idHotel; - if (!empty($roomId)) { + LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` main_pl ON (main_pl.id_product = main_room.id_product AND main_pl.id_lang = ' . (int) $idLang . ') + LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` conn_pl ON (conn_pl.id_product = connected_room.id_product AND conn_pl.id_lang = ' . (int) $idLang . ') + WHERE main_room.id_hotel = ' . (int) $idHotel . ' + AND connected_room.id_hotel = ' . (int) $idHotel; + if ($roomId > 0) { $sql .= ' AND main_room.id = ' . (int) $roomId; - $results = Db::getInstance()->executeS($sql); - } else { - $results = Db::getInstance()->executeS($sql); } - $grouped = []; + + $results = Db::getInstance()->executeS($sql); + if ($results === false) { + return array(); + } + + $grouped = array(); foreach ($results as $row) { - $roomId = $row['id_room_information']; - $connType = $row['connected_room_type']; - if (!isset($grouped[$roomId])) { - $grouped[$roomId] = []; + $mainRoomId = (int) $row['id_room_information']; + $connType = (string) $row['connected_room_type']; + if (!isset($grouped[$mainRoomId])) { + $grouped[$mainRoomId] = array(); } - if (!isset($grouped[$roomId][$connType])) { - $grouped[$roomId][$connType] = []; + if (!isset($grouped[$mainRoomId][$connType])) { + $grouped[$mainRoomId][$connType] = array(); } - $grouped[$roomId][$connType][] = $row; + $grouped[$mainRoomId][$connType][] = $row; } return $grouped; } else { + // If the connected room table is not yet available (e.g., older installs), + // fall back to returning all rooms except the main room. + if (!self::isConnectedRoomTableAvailable()) { + $sql = 'SELECT hri.*, pl.name AS room_type + FROM `' . _DB_PREFIX_ . 'htl_room_information` hri + LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl ON (pl.id_product = hri.id_product AND pl.id_lang = ' . (int) $idLang . ') + WHERE hri.id_hotel = ' . (int) $idHotel . ' + AND hri.id != ' . (int) $roomId . ' + ORDER BY hri.room_num ASC'; + $results = Db::getInstance()->executeS($sql); + return ($results === false) ? array() : $results; + } + $sql = 'SELECT hri.*, pl.name AS room_type FROM `' . _DB_PREFIX_ . 'htl_room_information` hri - LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl ON (pl.id_product = hri.id_product AND pl.id_lang = ' . $idLang . ') + LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl ON (pl.id_product = hri.id_product AND pl.id_lang = ' . (int) $idLang . ') WHERE hri.id_hotel = ' . (int) $idHotel . ' AND hri.id != ' . (int) $roomId . ' AND NOT EXISTS (SELECT 1 FROM `' . _DB_PREFIX_ . 'htl_connected_room` cr WHERE cr.id_room_information = ' . (int) $roomId . ' AND cr.id_room = hri.id) ORDER BY hri.room_num ASC'; - return Db::getInstance()->executeS($sql); + $results = Db::getInstance()->executeS($sql); + return ($results === false) ? array() : $results; } } } - From 8d2c1294750ee0c558197cf19130500f3291c2ff Mon Sep 17 00:00:00 2001 From: azhar bhat Date: Wed, 8 Apr 2026 19:51:23 +0530 Subject: [PATCH 04/14] ui updated --- admin/themes/default/sass/admin-theme.sass | 1 + .../default/sass/partials/_qlotooltip.sass | 100 +++++++++ .../controllers/products/configuration.tpl | 125 ++++++++++-- .../controllers/products/connected-rooms.tpl | 103 +++++----- .../products/modal-connected-rooms.tpl | 193 ++++++++++-------- controllers/admin/AdminProductsController.php | 30 +-- .../classes/HotelRoomConnected.php | 47 +++++ 7 files changed, 418 insertions(+), 181 deletions(-) create mode 100644 admin/themes/default/sass/partials/_qlotooltip.sass diff --git a/admin/themes/default/sass/admin-theme.sass b/admin/themes/default/sass/admin-theme.sass index 33b4084b08..92f9e42822 100644 --- a/admin/themes/default/sass/admin-theme.sass +++ b/admin/themes/default/sass/admin-theme.sass @@ -85,6 +85,7 @@ @import "partials/multistore" @import "partials/product" @import "partials/skeleton-loading" + @import "partials/qlotooltip" //Controllers @import "controllers/carrier-wizard" diff --git a/admin/themes/default/sass/partials/_qlotooltip.sass b/admin/themes/default/sass/partials/_qlotooltip.sass new file mode 100644 index 0000000000..369a3c7905 --- /dev/null +++ b/admin/themes/default/sass/partials/_qlotooltip.sass @@ -0,0 +1,100 @@ +.qlo-tooltip + display: none + position: absolute + top: 130% + left: 50% + transform: translateX(-50%) + z-index: 99999 + font-size: 11px !important + line-height: 1.35 !important + font-family: "Open Sans", Arial, sans-serif !important + text-align: left + color: #666 !important + background: #ffffff !important + border: 1px solid #e5e5e5 !important + box-shadow: 0 6px 12px rgba(0, 0, 0, 0.12) !important + padding: 10px 12px !important + border-radius: 4px !important + width: fit-content + min-width: 0 + max-width: none + +.qlo-tooltip-wrapper + position: relative + display: inline-block + cursor: pointer + color: #333 + +.qlo-tooltip-wrapper:hover .qlo-tooltip + display: block + +.qlo-tooltip.align-right + left: auto + right: 0 + transform: none + +.qlo-tooltip.align-left + left: 0 + transform: none + +.qlo-tooltip .qlo_tooltip_content, +.qlo-tooltip .qlo_tooltip_cont + width: 100% + +.qlo-tooltip .qlo_header + margin-bottom: 6px + +.qlo-tooltip .qlo_date + font-weight: 700 + font-size: 12px + color: #333 + white-space: nowrap + +.qlo-tooltip .qlo_body.grid + display: grid + grid-template-columns: repeat(2, minmax(0, max-content)) + gap: 10px 18px + justify-content: start + justify-items: start + width: max-content + +.qlo-tooltip .qlo_body.grid.single-col + grid-template-columns: 1fr + +.qlo-tooltip .qlo_body.grid.single-col .qlo_element + display: block + width: auto + +.qlo-tooltip.single-col + min-width: 0 + max-width: 240px + width: fit-content + +.qlo-tooltip .qlo_element_value + color: #666 + font-weight: 600 + +.qlo_tooltip_list + list-style: none !important + padding: 0 !important + margin: 0 !important + +.qlo_tooltip_list li + margin: 0 !important + padding: 0 !important + font-weight: 600 !important + color: #666 !important + +@media (max-width: 768px) + .qlo-tooltip + left: 0 + right: auto + transform: none + min-width: 0 + max-width: calc(100vw - 24px) + +.qlo-tooltip .qlo_element_heading + font-weight: 700 + color: #333 + margin-bottom: 2px + font-size: 11px diff --git a/admin/themes/default/template/controllers/products/configuration.tpl b/admin/themes/default/template/controllers/products/configuration.tpl index 77f0e2b37d..884fa5a807 100644 --- a/admin/themes/default/template/controllers/products/configuration.tpl +++ b/admin/themes/default/template/controllers/products/configuration.tpl @@ -122,7 +122,12 @@ data-target="#connectedRoomModal" data-id-room="{$room_info['id']|intval}" data-id-hotel="{$room_info['id_hotel']|intval}" data-room-num="{$room_info['room_num']|escape:'html':'UTF-8'}" data-room-type="{$room_info['id_product']|intval}"> - + + + + {if isset($room_info['connected_rooms_count'])}{$room_info['connected_rooms_count']|intval}{else}0{/if} + + {/if} @@ -596,6 +601,26 @@ border: 1px solid #f2f2f2; margin-top: 10px; } + .connected-room-icon-wrapper { + display: inline-flex; + align-items: center; + gap: 4px; + justify-content: center; + } + .connected-room-count { + min-width: 14px; + height: 14px; + padding: 0 3px; + border-radius: 9px; + background: #f5f5f5; + color: #666; + border: 1px solid #d5d5d5; + font-size: 9px; + line-height: 12px; + font-weight: 600; + text-align: center; + box-shadow: none; + } diff --git a/admin/themes/default/template/controllers/products/modal-connected-rooms.tpl b/admin/themes/default/template/controllers/products/modal-connected-rooms.tpl index fe023de400..66548ac807 100644 --- a/admin/themes/default/template/controllers/products/modal-connected-rooms.tpl +++ b/admin/themes/default/template/controllers/products/modal-connected-rooms.tpl @@ -20,95 +20,75 @@ * @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0 *} \ No newline at end of file diff --git a/controllers/admin/AdminProductsController.php b/controllers/admin/AdminProductsController.php index cbcd46fd51..7025a9e795 100644 --- a/controllers/admin/AdminProductsController.php +++ b/controllers/admin/AdminProductsController.php @@ -3726,6 +3726,7 @@ public function ajaxProcessDeleteHotelRoom() $this->errors[] = $this->l('This room cannot be deleted as this room contains future booking.'); } if (empty($this->errors)) { + $response['affected_rooms'] = HotelConnectedRoom::getRoomConnectedWith($idRoom); if ($objRoomInfo->delete()) { $response['success'] = true; } else { @@ -5707,8 +5708,8 @@ public function ajaxProcessManageConnectedRoom() ])); } - $objHotelRoomConnected = new HotelConnectedRoom($connectedId); - if ($objHotelRoomConnected->delete()) { + $objHotelConnectedRoom = new HotelConnectedRoom($connectedId); + if ($objHotelConnectedRoom->delete()) { $modalContent = $this->getConnectedRoomsModalHtml($roomId, $currentRoomType); die(json_encode([ 'success' => true, diff --git a/modules/hotelreservationsystem/classes/HotelConnectedRoom.php b/modules/hotelreservationsystem/classes/HotelConnectedRoom.php index 06150c2515..ccdf75f619 100644 --- a/modules/hotelreservationsystem/classes/HotelConnectedRoom.php +++ b/modules/hotelreservationsystem/classes/HotelConnectedRoom.php @@ -97,4 +97,25 @@ public static function getConnectedRooms($idLang, $roomId = null, $isConnected = return ($results === false) ? array() : $results; } } + + public static function getRoomConnectedWith($roomId) + { + $objHotelRoomInformation = new HotelRoomInformation($roomId); + $idProduct = $objHotelRoomInformation->id_product; + if (!$idProduct) { + return array(); + } + $sql = 'SELECT DISTINCT case + WHEN hcr.id_room = ' . (int) $roomId . ' THEN hcr.id_room_connected + ELSE hcr.id_room + END AS affected_room_id + FROM `' . _DB_PREFIX_ . 'htl_connected_room` hcr + INNER JOIN `' . _DB_PREFIX_ . 'htl_room_information` hri + ON (hri.id = hcr.id_room OR hri.id = hcr.id_room_connected) + WHERE (hcr.id_room = ' . (int) $roomId . ' OR hcr.id_room_connected = ' . (int) $roomId . ') + AND hri.id != ' . (int) $roomId . ' + AND hri.id_product = ' . (int) $idProduct; + $results = Db::getInstance()->executeS($sql); + return $results ? array_column($results, 'affected_room_id') : array(); + } } From d13a7405559a0bc90da92ae17056c45a429083da Mon Sep 17 00:00:00 2001 From: azhar bhat Date: Thu, 16 Apr 2026 16:37:00 +0530 Subject: [PATCH 10/14] fixed connectedroom icon not visiable in hotelroombooking page --- .../classes/HotelConnectedRoom.php | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/modules/hotelreservationsystem/classes/HotelConnectedRoom.php b/modules/hotelreservationsystem/classes/HotelConnectedRoom.php index ccdf75f619..2451550525 100644 --- a/modules/hotelreservationsystem/classes/HotelConnectedRoom.php +++ b/modules/hotelreservationsystem/classes/HotelConnectedRoom.php @@ -46,9 +46,13 @@ class HotelConnectedRoom extends ObjectModel public static function getConnectedRooms($idLang, $roomId = null, $isConnected = true ,$count = false) { $idLang = (int) $idLang; - $roomId = (int) $roomId; - $objHotelRoomInformation = new HotelRoomInformation($roomId); - $hotelId = $objHotelRoomInformation->id_hotel; + $roomId = (!is_bool($roomId) && Validate::isUnsignedId($roomId)) ? (int) $roomId : 0; + $hotelId = 0; + + if ($roomId > 0) { + $objHotelRoomInformation = new HotelRoomInformation($roomId); + $hotelId = (int) $objHotelRoomInformation->id_hotel; + } if ($isConnected) { $sql = 'SELECT @@ -59,8 +63,11 @@ public static function getConnectedRooms($idLang, $roomId = null, $isConnected = INNER JOIN `' . _DB_PREFIX_ . 'htl_room_information` connected_room ON connected_room.id = hcr.id_room_connected LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` main_pl ON (main_pl.id_product = main_room.id_product AND main_pl.id_lang = ' . (int) $idLang . ') LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` conn_pl ON (conn_pl.id_product = connected_room.id_product AND conn_pl.id_lang = ' . (int) $idLang . ') - WHERE main_room.id_hotel = ' . (int) $hotelId . ' - AND connected_room.id_hotel = ' . (int) $hotelId; + WHERE 1'; + if ($hotelId > 0) { + $sql .= ' AND main_room.id_hotel = ' . (int) $hotelId . ' + AND connected_room.id_hotel = ' . (int) $hotelId; + } if ($roomId > 0) { $sql .= ' AND main_room.id = ' . (int) $roomId; } @@ -89,8 +96,11 @@ public static function getConnectedRooms($idLang, $roomId = null, $isConnected = $sql = 'SELECT hri.*, pl.name AS room_type FROM `' . _DB_PREFIX_ . 'htl_room_information` hri LEFT JOIN `' . _DB_PREFIX_ . 'product_lang` pl ON (pl.id_product = hri.id_product AND pl.id_lang = ' . (int) $idLang . ') - WHERE hri.id_hotel = ' . (int) $hotelId . ' - AND hri.id != ' . (int) $roomId . ' + WHERE 1'; + if ($hotelId > 0) { + $sql .= ' AND hri.id_hotel = ' . (int) $hotelId; + } + $sql .= ' AND hri.id != ' . (int) $roomId . ' AND NOT EXISTS (SELECT 1 FROM `' . _DB_PREFIX_ . 'htl_connected_room` cr WHERE cr.id_room = ' . (int) $roomId . ' AND cr.id_room_connected = hri.id) ORDER BY hri.room_num ASC'; $results = Db::getInstance()->executeS($sql); From 32f01a7b6c1e59adc80bf6bf296ff5f7e65ff173 Mon Sep 17 00:00:00 2001 From: azhar bhat Date: Thu, 30 Apr 2026 11:06:03 +0530 Subject: [PATCH 11/14] chore: add co-author for previous changes Co-authored-by: Name From 7ade5a92d6ec3e69d2dfffc01e0552b18ed262c5 Mon Sep 17 00:00:00 2001 From: azhar bhat Date: Fri, 1 May 2026 12:17:50 +0530 Subject: [PATCH 12/14] code optimized Co-authored-by: Name --- .../orders/_current_cart_details_data.tpl | 5 +- .../controllers/orders/_product_line.tpl | 9 +- .../controllers/products/configuration.tpl | 379 ++++++++---------- .../controllers/products/connected-rooms.tpl | 71 +--- .../products/modal-connected-rooms.tpl | 266 +++++------- controllers/admin/AdminOrdersController.php | 5 +- controllers/admin/AdminProductsController.php | 113 +++--- .../classes/HotelCartBookingData.php | 2 +- .../classes/HotelConnectedRoom.php | 175 ++++---- .../AdminHotelRoomsBookingController.php | 3 +- .../views/css/HotelReservationAdmin.css | 15 +- .../helpers/view/_partials/booking-rooms.tpl | 1 + 12 files changed, 494 insertions(+), 550 deletions(-) diff --git a/admin/themes/default/template/controllers/orders/_current_cart_details_data.tpl b/admin/themes/default/template/controllers/orders/_current_cart_details_data.tpl index 161c83965f..46a2aa9608 100644 --- a/admin/themes/default/template/controllers/orders/_current_cart_details_data.tpl +++ b/admin/themes/default/template/controllers/orders/_current_cart_details_data.tpl @@ -52,9 +52,10 @@ {foreach from=$cart_detail_data item=data} {$data.room_num|escape:'html':'UTF-8'} - {if isset($data['connected_rooms'][$data['id_room']]) && $data['connected_rooms'][$data['id_room']]|@count > 0} - {include file="controllers/products/connected-rooms.tpl" htl_connected_rooms= $data['connected_rooms'][$data['id_room']]} + {if isset($data['connected_rooms']) && $data['connected_rooms']|@count > 0} + {include file="controllers/products/connected-rooms.tpl" htl_connected_rooms=$data['connected_rooms']} {/if} + {hook h='displayRoomNumAfter' data=$data type='adminOrder'} diff --git a/admin/themes/default/template/controllers/orders/_product_line.tpl b/admin/themes/default/template/controllers/orders/_product_line.tpl index 299c0fa22c..9061e52db6 100644 --- a/admin/themes/default/template/controllers/orders/_product_line.tpl +++ b/admin/themes/default/template/controllers/orders/_product_line.tpl @@ -25,13 +25,14 @@ -

{$data.room_num}

+

{$data.room_num} + {if isset($data['connected_rooms']) && $data['connected_rooms']|@count > 0} + {include file="controllers/products/connected-rooms.tpl" htl_connected_rooms=$data['connected_rooms']} + {/if} +

{if $data.is_back_order} {l s='overbooked'} {/if} - {if isset($data['connected_rooms'][$data['id_room']]) && $data['connected_rooms'][$data['id_room']]|@count > 0} - {include file="controllers/products/connected-rooms.tpl" htl_connected_rooms=$data['connected_rooms'][$data['id_room']]} - {/if} diff --git a/admin/themes/default/template/controllers/products/configuration.tpl b/admin/themes/default/template/controllers/products/configuration.tpl index e7192e47bb..f092ba8bf5 100644 --- a/admin/themes/default/template/controllers/products/configuration.tpl +++ b/admin/themes/default/template/controllers/products/configuration.tpl @@ -1954,235 +1954,184 @@ return false; } } - //connected modal - $(document).on('click', '.connectedRoomModal', function(e) { - e.preventDefault(); + const initConnectedRoomModal = { + show: function(e) { + e.preventDefault(); + var roomId = $(this).data('id-room'); + var roomNum = $(this).data('room-num'); - let roomId = $(this).data('id-room'); - let roomNum = $(this).data('room-num'); - let currentRoomType = $('[name="id_product"]').val(); - $.ajax({ - type: 'POST', - url: prod_link, - dataType: 'json', - data: { - ajax: true, - action: 'getModalConnectedRooms', - room_id: roomId, - current_roomtype: currentRoomType, - }, - success: function(response) { - $('#modalLoader').remove(); - if (response.success) { - if ($('#connectedRoomModal').length) { - $('#connectedRoomModal').modal('hide'); - $('#connectedRoomModal').data('bs.modal', null); - $('#connectedRoomModal').remove(); - } - $('.modal-backdrop').remove(); - $('body').removeClass('modal-open'); - $("#footer").next(".bootstrap").append(response.html); - $('#connectedRoomModal').modal({ - backdrop: 'static', - keyboard: true - }); - if (roomNum) { - roomNum = '( ' + roomNoText + ' ' + roomNum + ')'; - $('#connectedRoomModal').attr('data-room-num-title', roomNum); - $('#connected_room_title').html(roomNum); + $.ajax({ + type: 'POST', + url: prod_link, + dataType: 'json', + data: { + ajax: true, + action: 'initConnectedRoomModal', + room_id: roomId, + }, + success: function(response) { + if (!response.hasError) { + if ($('#connectedRoomModal').length) { + $('#connectedRoomModal').modal('hide'); + $('#connectedRoomModal').data('bs.modal', null); + $('#connectedRoomModal').remove(); + } + $('.modal-backdrop').remove(); + $('body').removeClass('modal-open'); + $("#footer").next(".bootstrap").append(response.modalHtml); + $('#connectedRoomModal').modal({ + backdrop: 'static', + keyboard: true + }); + if (roomNum) { + roomNum = '( ' + roomNoText + ' ' + roomNum + ')'; + $('#connectedRoomModal').attr('data-room-num-title', roomNum); + $('#connected_room_title').html(roomNum); + } else { + $('#connectedRoomModal').attr('data-room-num-title', ''); + $('#connected_room_title').html(''); + } } else { - $('#connectedRoomModal').attr('data-room-num-title', ''); - $('#connected_room_title').html(''); + alert(response.message); } - } else { - alert(response.message); } - } - - - }); - }); + }); + }, + add: function() { + var $row = $(this).closest('tr'); + var mainRoomId = $('#connected_room_main_id').val(); + var connectedRoomId = $row.find('.connect-room').val(); - function filterRoomsByType($row) { - var selectedType = $row.find('.connect-room-type').val(); - var $roomSelect = $row.find('.connect-room'); - var firstVisible = null; - $roomSelect.find('option').each(function() { - if ($(this).data('type') == selectedType) { - $(this).show(); - if (!firstVisible) { - firstVisible = $(this); - } - } else { - $(this).hide(); + if (!connectedRoomId) { + alert(selectRoomText); + return; } - }); - if (firstVisible) { - firstVisible.prop('selected', true); - } else { - $roomSelect.val(''); - } - } - - function addConnectedRoomRow() { - var $template = $('#connected_room_add_template'); - if (!$template.length) { - return; - } - var $tbody = $('#connected_rooms_tbody'); - if ($tbody.find('.connected-room-add-row').not('#connected_room_add_template').length) { - return; - } - var $newRow = $template.clone().removeAttr('id').removeClass('hide'); - var $tableWrapper = $('#connected_rooms_table_wrapper'); - var $emptyState = $('#connected_rooms_empty_state'); - var $addButton = $('#add_connected_room_row'); - if ($tableWrapper.length) { - $tableWrapper.removeClass('hide'); - } - if ($emptyState.length) { - $emptyState.hide(); - } - $tbody.append($newRow); - filterRoomsByType($newRow); - if ($addButton.length) { - $addButton.prop('disabled', true); - } - } - $(document).on('click', '#add_connected_room_row', function() { - addConnectedRoomRow(); - }); $(document).on('click', '.remove-connected-room-row', function() { - var $row = $(this).closest('tr'); - $('#add_connected_room_row').prop('disabled', false); - $row.remove(); - if (!$('#connected_rooms_tbody').find('.connected-room-row').length && !$('#connected_rooms_tbody') - .find('.connected-room-add-row').not('#connected_room_add_template').length) { - $('#connected_rooms_table_wrapper').addClass('hide'); - $('#connected_rooms_empty_state').removeClass('hide').show(); - } - }); $(document).on('change', '.connect-room-type', function() { - filterRoomsByType($(this).closest('tr')); - }); $(document).on('shown.bs.modal', '#connectedRoomModal', function() { - var $row = $(this).find('.connected-room-add-row').not('#connected_room_add_template').first(); - if ($row.length) { - filterRoomsByType($row); - } - }); - //add connected room - $(document).on('click', '.save-connected-room', function() { - var $row = $(this).closest('tr'); - var mainRoomId = $('#connected_room_main_id').val(); - var connectedRoomId = $row.find('.connect-room').val(); - let currentRoomType = $('[name="id_product"]').val(); - if (!connectedRoomId) { - alert(selectRoomText); - return; - } - $.ajax({ - url: prod_link, - type: 'POST', - dataType: 'json', - data: { - ajax: true, - action: 'ManageConnectedRoom', - mode: 'add', - room_id: mainRoomId, - connected_room_id: connectedRoomId, - current_roomtype: currentRoomType, - }, - beforeSend: function() { - $row.find('.save-connected-room').prop('disabled', true); - }, - success: function(response) { - if (response.success) { - showSuccessMessage(response.message); - $('#connectedRoomModal .modal-content').replaceWith($(response.html).find( - '.modal-content')); - $('#connected_room_title').html($('#connectedRoomModal').attr( - 'data-room-num-title')); - // update connected rooms count badge in list - var $icon = $('.connectedRoomModal[data-id-room="' + mainRoomId + '"]') - .find('.connected-room-count'); - if ($icon.length) { - var currentCount = parseInt($.trim($icon.text()), 10); - if (isNaN(currentCount)) { - currentCount = 0; + $.ajax({ + url: prod_link, + type: 'POST', + dataType: 'json', + data: { + ajax: true, + action: 'ManageConnectedRoom', + mode: 'add', + room_id: mainRoomId, + connected_room_id: connectedRoomId, + }, + beforeSend: function() { + $row.find('.save-connected-room').prop('disabled', true); + }, + success: function(response) { + if (response.success) { + showSuccessMessage(response.message); + $('#connectedRoomModal .modal-body').replaceWith($(response.html).filter('.modal-body')); + $('#connected_room_title').html($('#connectedRoomModal').attr('data-room-num-title')); + var $icon = $('.connectedRoomModal[data-id-room="' + mainRoomId + '"]').find('.connected-room-count'); + if ($icon.length && typeof response.connected_count !== 'undefined') { + $icon.text(response.connected_count); } - $icon.text(currentCount + 1); - } - if (response.details) { - $('.connectedRoomModal[data-id-room="' + mainRoomId + '"]').data( - 'connected-details', response.details); + } else { + alert(response.message); } - } else { - alert(response.message); + }, + complete: function() { + $row.find('.save-connected-room').prop('disabled', false); } - }, - complete: function() { - $row.find('.save-connected-room').prop('disabled', false); + }); + }, + showNotConnectedRooms: function() { + var $typeSelect = $(this); + var $row = $typeSelect.closest('tr'); + var roomTypeId = $typeSelect.val(); + var roomId = $('#connected_room_main_id').val(); + var $roomSelect = $row.find('.connect-room'); + $roomSelect.empty(); + if (!roomTypeId) { + return; } - }); - }); - //remove connected room - $(document).on('click', '.delete-connected-room', function() { - var $row = $(this).closest('tr'); - var mainRoomId = $('#connected_room_main_id').val(); - var connectedRoomId = $(this).data('connected-room-id'); - var connectedId = $(this).data('connected-id'); - let currentRoomType = $('[name="id_product"]').val(); - $.ajax({ - url: prod_link, - type: 'POST', - dataType: 'json', - data: { - ajax: true, - action: 'ManageConnectedRoom', - mode: 'delete', - room_id: mainRoomId, - connected_room_id: connectedRoomId, - connected_id: connectedId, - current_roomtype: currentRoomType, - }, - success: function(response) { - if (response.success) { - showSuccessMessage(response.message); - if (response.html) { - $('#connectedRoomModal .modal-content').replaceWith($(response.html) - .find('.modal-content')); - $('#connected_room_title').html($('#connectedRoomModal').attr( - 'data-room-num-title')); - } else { - $row.remove(); - var $modal = $('#connectedRoomModal'); - var $tbody = $modal.find('#connected_rooms_tbody'); - var hasConnectedRows = $tbody.find('.connected-room-row').length > 0; - if (!hasConnectedRows) { - $modal.find('#connected_rooms_table_wrapper').addClass('hide'); - $modal.find('#connected_rooms_empty_state').removeClass('hide') - .show(); - } + $.ajax({ + url: prod_link, + type: 'POST', + dataType: 'json', + data: { + ajax: true, + action: 'getNotConnectedRooms', + room_id: roomId, + room_type_id: roomTypeId, + }, + success: function(response) { + if (response.success && response.rooms && response.rooms.length) { + $.each(response.rooms, function(i, room) { + $roomSelect.append( + $('