diff --git a/admin-dev/themes/default/sass/partials/_tooltip.sass b/admin-dev/themes/default/sass/partials/_tooltip.sass index f3ef5f4b2..0779ca8b1 100644 --- a/admin-dev/themes/default/sass/partials/_tooltip.sass +++ b/admin-dev/themes/default/sass/partials/_tooltip.sass @@ -21,7 +21,8 @@ margin-bottom: 5px .ui-tooltip-body - + max-height: 250px; + overflow-y: auto; > div margin-bottom: 6px margin-right: 10px @@ -42,3 +43,6 @@ .ui-tooltip-label font-weight: 600 margin: 0 + +.label.label-success.service-applied-days + font-size: 11px diff --git a/admin-dev/themes/default/template/controllers/orders/helpers/view/view.tpl b/admin-dev/themes/default/template/controllers/orders/helpers/view/view.tpl index b6c97c962..b364c3140 100644 --- a/admin-dev/themes/default/template/controllers/orders/helpers/view/view.tpl +++ b/admin-dev/themes/default/template/controllers/orders/helpers/view/view.tpl @@ -928,7 +928,7 @@ {/if} - {if isset($total_room_services_tax_excl) && $total_room_services_tax_excl > 0} + {if isset($total_room_services_tax_excl) && ($total_room_services_tax_excl - $total_convenience_fee_tax_excl) > 0} {l s='Total Extra services (Tax excl.)'} @@ -1036,7 +1036,7 @@ {/if} - {if isset($total_convenience_fee_tax_excl) && $total_convenience_fee_tax_excl > 0} + {if isset($total_convenience_fee_tax_only) && $total_convenience_fee_tax_only > 0} {l s='Convenience Fee Tax'} diff --git a/admin-dev/themes/default/template/controllers/orders/modals/_add_order_extra_services_tab_content.tpl b/admin-dev/themes/default/template/controllers/orders/modals/_add_order_extra_services_tab_content.tpl index 138d27e3b..c17bb8606 100644 --- a/admin-dev/themes/default/template/controllers/orders/modals/_add_order_extra_services_tab_content.tpl +++ b/admin-dev/themes/default/template/controllers/orders/modals/_add_order_extra_services_tab_content.tpl @@ -47,6 +47,7 @@ {foreach $serviceProducts as $product} + {if !empty($selectedRoomServiceProduct['date_from']) && !(isset($selectedRoomServiceProduct['selected_service'][$product['id_product']])) && $product['price_calculation_method'] != Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING && !(Product::getServicePriceApplicableDates($product['price_calculation_method'], $selectedRoomServiceProduct['date_from'], $selectedRoomServiceProduct['date_to'])|@array_filter)}{continue}{/if} {if isset($selectedRoomServiceProduct['selected_service']) && $selectedRoomServiceProduct['selected_service'] && ($product['id_product']|array_key_exists:$selectedRoomServiceProduct['selected_service'])} {assign var='serviceSelected' value=true} {$product.price_tax_incl = $selectedRoomServiceProduct['selected_service'][$product['id_product']]['unit_price_tax_incl']} @@ -63,21 +64,7 @@
{$product['name']|escape:'html':'UTF-8'} - {capture name='room_type_service_tooltip_content'} - {assign var="priceCalcMethod" value=$product['price_calculation_method']|default:0} -
-
{l s='Applied on:'}
-
-
- -
- {/capture} - - {include file='helpers/ui-tooltip.tpl' tooltip_content=$smarty.capture.room_type_service_tooltip_content allow_html=true} + {include file='controllers/orders/modals/_partials/service-applied-days.tpl' price_calculation_method=$product['price_calculation_method'] date_from=$selectedRoomServiceProduct['date_from']|default:'' date_to=$selectedRoomServiceProduct['date_to']|default:''}
@@ -108,6 +95,7 @@ {$cartCurrency->sign} + {if ($product['price_calculation_method']) != Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING}{l s='/ Night'}{/if} {/if} @@ -143,40 +131,6 @@
-
- - - -
@@ -193,8 +147,6 @@
-
-
+
+
+
+ + +
+
+
diff --git a/admin-dev/themes/default/template/controllers/orders/modals/_extra_services_service_products_tab_content.tpl b/admin-dev/themes/default/template/controllers/orders/modals/_extra_services_service_products_tab_content.tpl index 2196ec084..faa574de8 100644 --- a/admin-dev/themes/default/template/controllers/orders/modals/_extra_services_service_products_tab_content.tpl +++ b/admin-dev/themes/default/template/controllers/orders/modals/_extra_services_service_products_tab_content.tpl @@ -56,21 +56,7 @@
{$service['name']|escape:'html':'UTF-8'} - {capture name='room_type_service_tooltip_content'} - {assign var="priceCalcMethod" value=$service['price_calculation_method']|default:0} -
-
{l s='Applied on:'}
-
-
-
    - {foreach from=Product::getPriceCalculationMethodDaysLabel($priceCalcMethod) item='pcmDayLabel'} -
  • {$pcmDayLabel}
  • - {/foreach} -
-
- {/capture} - - {include file='helpers/ui-tooltip.tpl' tooltip_content=$smarty.capture.room_type_service_tooltip_content allow_html=true} + {include file='controllers/orders/modals/_partials/service-applied-days.tpl' price_calculation_method=$service['price_calculation_method']|default:0 date_from=$additionalServices['date_from'] date_to=$additionalServices['date_to']}
@@ -101,6 +87,7 @@
{$currencySign} + {if ($service['price_calculation_method']|default:0) != Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING}{l s='/ Night'}{/if}
{displayPrice price=$service['total_price_tax_excl']|escape:'html':'UTF-8' currency=$orderCurrency} @@ -142,26 +129,15 @@ {foreach $serviceProducts as $product} + {* services applied on selected days only are not listed when none of those days exist in the stay *} + {if !empty($date_from) && $product['price_calculation_method'] != Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING && !(Product::getServicePriceApplicableDates($product['price_calculation_method'], $date_from, $date_to)|@array_filter)}{continue}{/if} {$product['name']|escape:'html':'UTF-8'} - {capture name='adm_pcm_tooltip_content'} - {assign var="priceCalcMethod" value=$product['price_calculation_method']|default:0} -
-
{l s='Applied on:'}
-
-
- -
- {/capture} - {include file='helpers/ui-tooltip.tpl' tooltip_content=$smarty.capture.adm_pcm_tooltip_content allow_html=true} + {include file='controllers/orders/modals/_partials/service-applied-days.tpl' price_calculation_method=$product['price_calculation_method'] date_from=$date_from|default:'' date_to=$date_to|default:''} {if $product['auto_add_to_cart'] && $product['price_addition_type'] == Product::PRICE_ADDITION_TYPE_WITH_ROOM} @@ -184,6 +160,7 @@
{$currencySign} + {if ($product['price_calculation_method']) != Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING}{l s='/ Night'}{/if}
{hook h="displayAdminOrderAvailableServicesData" id_service_product=$product['id_product'] id_htl_booking_detail=$id_booking_detail} @@ -218,40 +195,6 @@
-
- - - -
@@ -268,9 +211,7 @@
-
-
-
+
+
+
+
+ + +
+
+ {if $roomTypeTaxRuleGroupExist}
@@ -328,20 +299,8 @@ {$service['name']|escape:'html':'UTF-8'} - {capture name='room_type_service_pcm_tooltip_content'} - {assign var="priceCalcMethod" value=$service['price_calculation_method']|default:0} -
-
{l s='Applied on:'}
-
-
-
    - {foreach from=Product::getPriceCalculationMethodDaysLabel($priceCalcMethod) item='pcmDayLabel'} -
  • {$pcmDayLabel}
  • - {/foreach} -
-
- {/capture} - {include file='helpers/ui-tooltip.tpl' tooltip_content=$smarty.capture.room_type_service_pcm_tooltip_content allow_html=true} + {include file='controllers/orders/modals/_partials/service-applied-days.tpl' price_calculation_method=$service['price_calculation_method']|default:0 date_from=$additionalServices['date_from'] date_to=$additionalServices['date_to']} + {if $service['product_auto_add'] && $service['product_price_addition_type'] == Product::PRICE_ADDITION_TYPE_INDEPENDENT} {l s='Convenience fee'} diff --git a/admin-dev/themes/default/template/controllers/orders/modals/_partials/service-applied-days.tpl b/admin-dev/themes/default/template/controllers/orders/modals/_partials/service-applied-days.tpl new file mode 100644 index 000000000..aa905b482 --- /dev/null +++ b/admin-dev/themes/default/template/controllers/orders/modals/_partials/service-applied-days.tpl @@ -0,0 +1,56 @@ +{** +* 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($price_calculation_method) && (int) $price_calculation_method != Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING && !empty($date_from) && !empty($date_to)} + {assign var='serviceAppliedDates' value=Product::getServicePriceApplicableDates($price_calculation_method, $date_from, $date_to)} + {assign var='serviceAppliedDays' value=0} + {foreach from=$serviceAppliedDates item='serviceAppliedGroupDates'} + {assign var='serviceAppliedDays' value=$serviceAppliedDays + count($serviceAppliedGroupDates)} + {/foreach} + {if $serviceAppliedDays} + {capture name='adm_applied_days_tooltip'} +
+
{l s='Applied on:'}
+
+
+
    + {foreach from=$serviceAppliedDates key='serviceAppliedGroup' item='serviceAppliedGroupDates'} + {if $serviceAppliedGroupDates} +
  • + {if $serviceAppliedGroup == 'checkin'}{l s='Check-in day'}{elseif $serviceAppliedGroup == 'stay'}{l s='During-stay days'}{else}{l s='Check-out day'}{/if} +
      + {foreach from=$serviceAppliedGroupDates item='serviceAppliedDate'} +
    • {Tools::displayDate($serviceAppliedDate)}
    • + {/foreach} +
    +
  • + {/if} + {/foreach} +
+
+ {/capture} + {capture name='adm_applied_days_badge'}{if $serviceAppliedDays == 1}{l s='Applied for 1 Day'}{else}{l s='Applied for %s Days' sprintf=$serviceAppliedDays}{/if}{/capture} +
+ {include file='helpers/ui-tooltip.tpl' tooltip_title=$smarty.capture.adm_applied_days_badge tooltip_content=$smarty.capture.adm_applied_days_tooltip allow_html=true} +
+ {/if} +{/if} diff --git a/classes/Product.php b/classes/Product.php index 27f9704bd..2a5b3e29e 100644 --- a/classes/Product.php +++ b/classes/Product.php @@ -6973,6 +6973,36 @@ class ProductCore extends ObjectModel } } + public static function getServicePriceApplicableDates($priceCalculationMethod, $dateFrom, $dateTo) + { + $priceCalculationMethod = (int) $priceCalculationMethod; + $dates = array(); + if (empty($dateFrom) || empty($dateTo)) { + return $dates; + } + + $checkinDate = new DateTime(date('Y-m-d', strtotime($dateFrom))); + $checkoutDate = new DateTime(date('Y-m-d', strtotime($dateTo))); + + if ($priceCalculationMethod & self::PRICE_CALCULATION_METHOD_ON_CHECKIN_DAY) { + $dates['checkin'] = array($checkinDate->format('Y-m-d')); + } + if ($priceCalculationMethod & self::PRICE_CALCULATION_METHOD_ON_DURING_STAY) { + $dates['stay'] = array(); + $stayDate = clone $checkinDate; + $stayDate->modify('+1 day'); + while ($stayDate < $checkoutDate) { + $dates['stay'][] = $stayDate->format('Y-m-d'); + $stayDate->modify('+1 day'); + } + } + if ($priceCalculationMethod & self::PRICE_CALCULATION_METHOD_ON_CHECKOUT_DAY) { + $dates['checkout'] = array($checkoutDate->format('Y-m-d')); + } + + return $dates; + } + public static function getPriceCalculationMethodDaysLabel($priceCalculationMethod) { $priceCalculationMethod = (int) $priceCalculationMethod; diff --git a/controllers/admin/AdminOrdersController.php b/controllers/admin/AdminOrdersController.php index ffe856f1d..b667515ae 100644 --- a/controllers/admin/AdminOrdersController.php +++ b/controllers/admin/AdminOrdersController.php @@ -1059,6 +1059,8 @@ class AdminOrdersControllerCore extends AdminController $smartyVars['link'] = $this->context->link; $smartyVars['id_booking_detail'] = $productLineData['id']; + $smartyVars['date_from'] = $dateFrom; + $smartyVars['date_to'] = $dateTo; // set context currency So that we can get prices in the order currency $this->context->currency = $objCurrency; @@ -7881,6 +7883,8 @@ class AdminOrdersControllerCore extends AdminController $smartyVars['link'] = $this->context->link; $smartyVars['id_booking_detail'] = $idHtlBookingDetail; + $smartyVars['date_from'] = $dateFrom; + $smartyVars['date_to'] = $dateTo; $objServiceProductOrderDetail = new ServiceProductOrderDetail(); if ($additionalServices = $objServiceProductOrderDetail->getRoomTypeServiceProducts( 0, diff --git a/controllers/front/ProductController.php b/controllers/front/ProductController.php index e4d3a0389..233b64e65 100644 --- a/controllers/front/ProductController.php +++ b/controllers/front/ProductController.php @@ -491,7 +491,7 @@ class ProductControllerCore extends FrontController } } $this->assignBookingFormVars($this->product->id, $date_from, $date_to, $occupancy_value); - $this->assignRoomServiceProductVars(); + $this->assignRoomServiceProductVars($date_from, $date_to); // product price after imposing feature prices... if ($useTax) { @@ -614,7 +614,7 @@ class ProductControllerCore extends FrontController $this->setTemplate(_PS_THEME_DIR_.'product.tpl'); } - public function assignRoomServiceProductVars() + public function assignRoomServiceProductVars($dateFrom = null, $dateTo = null) { // get service products for room type $p = 1; @@ -626,7 +626,12 @@ class ProductControllerCore extends FrontController $this->product->id, $p, $n, - true + true, + 2, + 0, + false, + $dateFrom, + $dateTo ); if ($serviceProductsByCategory) { $smartyVars['service_products_exists'] = 1; @@ -637,7 +642,13 @@ class ProductControllerCore extends FrontController $this->product->id, $p, $n, - true + true, + 2, + 0, + false, + false, + $dateFrom, + $dateTo ); $numTotalServiceProducts = $this->product->getProductServiceProducts( $this->context->language->id, @@ -839,10 +850,16 @@ class ProductControllerCore extends FrontController $serviceNumAdults = isset($serviceOccupancy['adults']) ? (int) $serviceOccupancy['adults'] : 1; $serviceChildAges = !empty($serviceOccupancy['child_ages']) ? (array) $serviceOccupancy['child_ages'] : array(); - foreach ($roomServiceProducts as &$product) { + foreach ($roomServiceProducts as $keyServiceProduct => &$product) { + $priceCalcMethod = Product::getProductPriceCalculation((int) $product['id_product']); if (!$objRoomTypeServiceProduct->isRoomTypeLinkedWithProduct($idProduct, $product['id_product'])) { unset($product); continue; + } elseif ($priceCalcMethod != Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING && !array_filter(Product::getServicePriceApplicableDates($priceCalcMethod, $dateFrom, $dateTo))) { + // service is applied on selected days only and none of those days exist in the stay + unset($product); + unset($roomServiceProducts[$keyServiceProduct]); + continue; } else { $objServiceProduct = new ProductCore($product['id_product'], false, $this->context->language->id); if (!$objServiceProduct->allow_multiple_quantity && $product['quantity'] > 1) { @@ -854,6 +871,7 @@ class ProductControllerCore extends FrontController } $product['name'] = $objServiceProduct->name; $product['allow_multiple_quantity'] = $objServiceProduct->allow_multiple_quantity; + $product['price_calculation_method'] = $objServiceProduct->price_calculation_method; $productPrice = Product::getServiceProductPrice( $product['id_product'], @@ -1506,7 +1524,7 @@ class ProductControllerCore extends FrontController $roomServiceProducts = Tools::getValue('room_service_products'); $dateFrom = date('Y-m-d H:i:s', strtotime($dateFrom)); $dateTo = date('Y-m-d H:i:s', strtotime($dateTo)); - $this->assignRoomServiceProductVars(); + $this->assignRoomServiceProductVars($dateFrom, $dateTo); if ($this->assignBookingFormVars( $idProduct, $dateFrom, @@ -1514,6 +1532,9 @@ class ProductControllerCore extends FrontController $occupancy, $roomServiceProducts )) { + if (Tools::getValue('refresh_services')) { + $response['html_service_products'] = $this->getRefreshedServiceProductsHtml(); + } } } else { $idHotel = Tools::getValue('service_id_hotel'); @@ -1536,6 +1557,21 @@ class ProductControllerCore extends FrontController $this->ajaxDie(json_encode($response)); } + /** + * Render the service products block using the service products already assigned to smarty. + * + * @return string + */ + protected function getRefreshedServiceProductsHtml() + { + $this->context->smarty->assign(array( + 'product' => $this->product, + 'PS_SERVICE_PRODUCT_CATEGORY_FILTER' => Configuration::get('PS_SERVICE_PRODUCT_CATEGORY_FILTER'), + )); + + return $this->context->smarty->fetch(_PS_THEME_DIR_.'_partials/service-products.tpl'); + } + public function displayAjaxGetHotelImages() { $response = array('status' => false); @@ -1582,6 +1618,8 @@ class ProductControllerCore extends FrontController $p = Tools::getValue('p'); $id_product = Tools::getValue('id_product'); $id_category = Tools::getValue('id_category'); + $date_from = Tools::getValue('date_from'); + $date_to = Tools::getValue('date_to'); if ($this->isTokenValid()) { if ($p && $id_product) { @@ -1600,7 +1638,10 @@ class ProductControllerCore extends FrontController true, 2, 0, - $id_category + $id_category, + false, + $date_from, + $date_to )) { $this->context->smarty->assign('service_products', $roomTypeServiceProducts); if (Configuration::get('PS_SERVICE_PRODUCT_CATEGORY_FILTER')) { diff --git a/modules/hotelreservationsystem/classes/HotelHelper.php b/modules/hotelreservationsystem/classes/HotelHelper.php index 3ebcaffb3..baca47ba8 100644 --- a/modules/hotelreservationsystem/classes/HotelHelper.php +++ b/modules/hotelreservationsystem/classes/HotelHelper.php @@ -1312,30 +1312,79 @@ class HotelHelper public function createHotelDefaultPropertyTypes() { $propertyTypes = array( - 'Hotel', - 'Motel', - 'Resort', - 'Hostel', - 'Guest House', - 'Bed & Breakfast', - 'Lodge', - 'Apartment', - 'Vacation Rental', - 'Homestay', - 'Co-Living', - 'Camping', - 'Glamping', - 'Farm Stay', - 'Houseboat', - 'Boat Stay', + array( + 'name' => 'Hotel', + 'plural_name' => 'Hotels', + ), + array( + 'name' => 'Motel', + 'plural_name' => 'Motels', + ), + array( + 'name' => 'Resort', + 'plural_name' => 'Resorts', + ), + array( + 'name' => 'Hostel', + 'plural_name' => 'Hostels', + ), + array( + 'name' => 'Guest House', + 'plural_name' => 'Guest Houses', + ), + array( + 'name' => 'Bed & Breakfast', + 'plural_name' => 'Bed & Breakfasts', + ), + array( + 'name' => 'Lodge', + 'plural_name' => 'Lodges', + ), + array( + 'name' => 'Apartment', + 'plural_name' => 'Apartments', + ), + array( + 'name' => 'Vacation Rental', + 'plural_name' => 'Vacation Rentals', + ), + array( + 'name' => 'Homestay', + 'plural_name' => 'Homestays', + ), + array( + 'name' => 'Co-Living', + 'plural_name' => 'Co-Livings', + ), + array( + 'name' => 'Camping', + 'plural_name' => 'Campings', + ), + array( + 'name' => 'Glamping', + 'plural_name' => 'Glampings', + ), + array( + 'name' => 'Farm Stay', + 'plural_name' => 'Farm Stays', + ), + array( + 'name' => 'Houseboat', + 'plural_name' => 'Houseboats', + ), + array( + 'name' => 'Boat Stay', + 'plural_name' => 'Boat Stays', + ), ); $languages = Language::getLanguages(true); - foreach ($propertyTypes as $typeName) { + foreach ($propertyTypes as $propertyType) { $objType = new HotelPropertyType(); $objType->active = 1; foreach ($languages as $lang) { - $objType->name[$lang['id_lang']] = $typeName; + $objType->name[$lang['id_lang']] = $propertyType['name']; + $objType->plural_name[$lang['id_lang']] = $propertyType['plural_name']; } $objType->save(); } diff --git a/modules/hotelreservationsystem/classes/HotelPropertyType.php b/modules/hotelreservationsystem/classes/HotelPropertyType.php index 14dde5724..c1486e8af 100644 --- a/modules/hotelreservationsystem/classes/HotelPropertyType.php +++ b/modules/hotelreservationsystem/classes/HotelPropertyType.php @@ -27,6 +27,7 @@ class HotelPropertyType extends ObjectModel public $date_add; public $date_upd; public $name; + public $plural_name; public static $definition = array( 'table' => 'htl_property_type', @@ -37,6 +38,7 @@ class HotelPropertyType extends ObjectModel 'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDate', 'copy_post' => false), 'date_upd' => array('type' => self::TYPE_DATE, 'validate' => 'isDate', 'copy_post' => false), 'name' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'required' => true, 'size' => 255), + 'plural_name' => array('type' => self::TYPE_STRING, 'lang' => true, 'validate' => 'isGenericName', 'required' => true, 'size' => 255), ), ); @@ -46,6 +48,7 @@ class HotelPropertyType extends ObjectModel 'objectMethods' => array('update' => 'updateWs', 'delete' => 'deleteWs'), 'fields' => array( 'name' => array('required' => true), + 'plural_name' => array('required' => true), 'active' => array('required' => false), ), ); @@ -121,7 +124,7 @@ class HotelPropertyType extends ObjectModel $cacheKey = 'HotelPropertyType::getPropertyTypes'.$idLang.'_'.(int) $active; if (!Cache::isStored($cacheKey)) { - $sql = 'SELECT hpt.`id_htl_property_type` AS `id_property_type`, hptl.`name` + $sql = 'SELECT hpt.`id_htl_property_type` AS `id_property_type`, hptl.`name`, hptl.`plural_name` FROM `'._DB_PREFIX_.'htl_property_type` hpt LEFT JOIN `'._DB_PREFIX_.'htl_property_type_lang` hptl ON (hpt.`id_htl_property_type` = hptl.`id_htl_property_type` diff --git a/modules/hotelreservationsystem/classes/HotelReservationSystemDb.php b/modules/hotelreservationsystem/classes/HotelReservationSystemDb.php index 32542bc30..d2fca3b04 100644 --- a/modules/hotelreservationsystem/classes/HotelReservationSystemDb.php +++ b/modules/hotelreservationsystem/classes/HotelReservationSystemDb.php @@ -142,6 +142,7 @@ class HotelReservationSystemDb `id_htl_property_type` int(10) unsigned NOT NULL, `id_lang` int(10) unsigned NOT NULL, `name` varchar(255) NOT NULL, + `plural_name` varchar(255) NOT NULL, PRIMARY KEY (`id_htl_property_type`, `id_lang`) ) ENGINE="._MYSQL_ENGINE_." DEFAULT CHARSET=utf8;", "CREATE TABLE IF NOT EXISTS `"._DB_PREFIX_."htl_branch_amenity` ( diff --git a/modules/hotelreservationsystem/classes/RoomTypeServiceProduct.php b/modules/hotelreservationsystem/classes/RoomTypeServiceProduct.php index 409a7696e..84dc017ec 100644 --- a/modules/hotelreservationsystem/classes/RoomTypeServiceProduct.php +++ b/modules/hotelreservationsystem/classes/RoomTypeServiceProduct.php @@ -184,7 +184,7 @@ class RoomTypeServiceProduct extends ObjectModel return false; } - public function getServiceProductsData($idProductRoomType, $p = 1, $n = 0, $front = false, $available_for_order = 2, $auto_add_to_cart = 0, $subCategory = false, $idLang = false) + public function getServiceProductsData($idProductRoomType, $p = 1, $n = 0, $front = false, $available_for_order = 2, $auto_add_to_cart = 0, $subCategory = false, $idLang = false, $dateFrom = null, $dateTo = null) { $context = Context::getContext(); if (!$idLang) { @@ -209,6 +209,18 @@ class RoomTypeServiceProduct extends ObjectModel continue; } + if ($dateFrom && $dateTo) { + $priceCalcMethod = (int) $serviceProduct['price_calculation_method']; + if ($priceCalcMethod != Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING && !array_filter(Product::getServicePriceApplicableDates($priceCalcMethod, $dateFrom, $dateTo))) { + unset($serviceProducts[$key]); + continue; + } + + $serviceProduct['price_calculation_method'] = $priceCalcMethod; + $serviceProduct['stay_date_from'] = $dateFrom; + $serviceProduct['stay_date_to'] = $dateTo; + } + $serviceProduct['price_tax_exc'] = Product::getServiceProductPrice( (int)$serviceProduct['id_product'], 0, @@ -352,7 +364,7 @@ class RoomTypeServiceProduct extends ObjectModel return $pricingByBookingAndProduct; } - public function getServiceProductsGroupByCategory($idProduct, $p = 1, $n = 0, $front = false, $available_for_order = 2, $auto_add_to_cart = 0, $idLang = false) + public function getServiceProductsGroupByCategory($idProduct, $p = 1, $n = 0, $front = false, $available_for_order = 2, $auto_add_to_cart = 0, $idLang = false, $dateFrom = null, $dateTo = null) { if (!$idLang) { $idLang = Context::getContext()->language->id; @@ -361,7 +373,7 @@ class RoomTypeServiceProduct extends ObjectModel $objProduct = new Product($idProduct); if ($serviceProductsCategories = $objProduct->getAvailableServiceProductsCategories($idLang, 1)) { foreach ($serviceProductsCategories as $key => $category) { - if ($products = $this->getServiceProductsData($idProduct, $p, $n, $front, $available_for_order, $auto_add_to_cart, $category['id_category'], $idLang)) { + if ($products = $this->getServiceProductsData($idProduct, $p, $n, $front, $available_for_order, $auto_add_to_cart, $category['id_category'], $idLang, $dateFrom, $dateTo)) { $serviceProductsCategories[$key]['products'] = $products; } else { unset($serviceProductsCategories[$key]); diff --git a/modules/hotelreservationsystem/controllers/admin/AdminHotelPropertyTypesController.php b/modules/hotelreservationsystem/controllers/admin/AdminHotelPropertyTypesController.php index d3485a680..e59a8e6be 100644 --- a/modules/hotelreservationsystem/controllers/admin/AdminHotelPropertyTypesController.php +++ b/modules/hotelreservationsystem/controllers/admin/AdminHotelPropertyTypesController.php @@ -45,6 +45,10 @@ class AdminHotelPropertyTypesController extends ModuleAdminController 'title' => $this->l('Name'), 'align' => 'left', ), + 'plural_name' => array( + 'title' => $this->l('Plural Name'), + 'align' => 'left', + ), 'active' => array( 'title' => $this->l('Status'), 'align' => 'center', @@ -109,6 +113,14 @@ class AdminHotelPropertyTypesController extends ModuleAdminController 'required' => true, 'hint' => $this->l('Displayed name for this property type.'), ), + array( + 'type' => 'text', + 'label' => $this->l('Plural Name'), + 'name' => 'plural_name', + 'lang' => true, + 'required' => true, + 'hint' => $this->l('Displayed name when referring to more than one property of this type.'), + ), array( 'type' => 'switch', 'label' => $this->l('Enabled'), @@ -166,6 +178,18 @@ class AdminHotelPropertyTypesController extends ModuleAdminController } } + if (!trim(Tools::getValue('plural_name_'.$defaultLangId))) { + $this->errors[] = $this->l('Plural Name is required at least in ').$objDefaultLanguage['name']; + } else { + foreach ($languages as $lang) { + if (trim(Tools::getValue('plural_name_'.$lang['id_lang']))) { + if (!Validate::isGenericName(Tools::getValue('plural_name_'.$lang['id_lang']))) { + $this->errors[] = $this->l('Invalid Plural Name in ').$lang['name']; + } + } + } + } + $this->display = $idPropertyType ? 'edit' : 'add'; if (!$this->errors && !Tools::getValue('active') && $idPropertyType) { diff --git a/modules/wkabouthotelblock/classes/WkHotelInteriorImage.php b/modules/wkabouthotelblock/classes/WkHotelInteriorImage.php index 6b7aa7fbe..6bdc9b12f 100644 --- a/modules/wkabouthotelblock/classes/WkHotelInteriorImage.php +++ b/modules/wkabouthotelblock/classes/WkHotelInteriorImage.php @@ -23,6 +23,10 @@ class WkHotelInteriorImage extends ObjectModel { + const NAV_TYPE_DOTS = 1; + const NAV_TYPE_ARROWS = 2; + const NAV_TYPE_BOTH = 3; + public $name; public $display_name; public $active; @@ -172,6 +176,9 @@ class WkHotelInteriorImage extends ObjectModel // update global configuration values in multilang Configuration::updateValue('HOTEL_INTERIOR_HEADING', $HOTEL_INTERIOR_HEADING); Configuration::updateValue('HOTEL_INTERIOR_DESCRIPTION', $HOTEL_INTERIOR_DESCRIPTION); + Configuration::updateValue('HOTEL_INTERIOR_NAV_TYPE', self::NAV_TYPE_DOTS); + Configuration::updateValue('HOTEL_INTERIOR_AUTOPLAY', 1); + Configuration::updateValue('HOTEL_INTERIOR_AUTOPLAY_INTERVAL', 5000); // Database Entry for ($i = 1; $i <= 12; $i++) { diff --git a/modules/wkabouthotelblock/controllers/admin/AdminAboutHotelBlockSettingController.php b/modules/wkabouthotelblock/controllers/admin/AdminAboutHotelBlockSettingController.php index 351d9fc48..8087744df 100644 --- a/modules/wkabouthotelblock/controllers/admin/AdminAboutHotelBlockSettingController.php +++ b/modules/wkabouthotelblock/controllers/admin/AdminAboutHotelBlockSettingController.php @@ -33,6 +33,7 @@ class AdminAboutHotelBlockSettingController extends ModuleAdminController $this->context = Context::getContext(); $this->identifier_name = 'display_name'; $this->identifier = 'id_interior_image'; + parent::__construct(); $this->fields_options = array( 'global' => array( @@ -57,6 +58,33 @@ class AdminAboutHotelBlockSettingController extends ModuleAdminController 'validation' => 'isGenericName', 'hint' => $this->l('Enter a description for the interior block.') ), + 'HOTEL_INTERIOR_NAV_TYPE' => array( + 'title' => $this->l('Navigation Type'), + 'type' => 'select', + 'identifier' => 'id', + 'list' => array( + array('id' => WkHotelInteriorImage::NAV_TYPE_DOTS, 'name' => $this->l('Dots')), + array('id' => WkHotelInteriorImage::NAV_TYPE_ARROWS, 'name' => $this->l('Arrows')), + array('id' => WkHotelInteriorImage::NAV_TYPE_BOTH, 'name' => $this->l('Arrows and dots')), + ), + 'hint' => $this->l('How the interior photos slider of the home page is navigated.'), + ), + 'HOTEL_INTERIOR_AUTOPLAY' => array( + 'title' => $this->l('Auto slide'), + 'cast' => 'intval', + 'type' => 'bool', + 'default' => '1', + 'hint' => $this->l('Slide the interior photos automatically on the home page.'), + ), + 'HOTEL_INTERIOR_AUTOPLAY_INTERVAL' => array( + 'title' => $this->l('Slide Interval'), + 'cast' => 'intval', + 'type' => 'text', + 'class' => 'fixed-width-sm', + 'suffix' => $this->l('ms'), + 'default' => '5000', + 'desc' => $this->l('Milliseconds between slides. Min: 500. Example: 5000 = 5 s.'), + ), ), 'submit' => array( 'title' => $this->l('Save'), @@ -124,7 +152,7 @@ class AdminAboutHotelBlockSettingController extends ModuleAdminController 'icon' => 'icon-power-off text-danger', ), ); - parent::__construct(); + } public function initContent() @@ -335,6 +363,21 @@ class AdminAboutHotelBlockSettingController extends ModuleAdminController } } } + if (!in_array((int) Tools::getValue('HOTEL_INTERIOR_NAV_TYPE'), array( + WkHotelInteriorImage::NAV_TYPE_DOTS, + WkHotelInteriorImage::NAV_TYPE_ARROWS, + WkHotelInteriorImage::NAV_TYPE_BOTH, + ))) { + $this->errors[] = $this->l('Invalid navigation type.'); + } + if (Tools::getValue('HOTEL_INTERIOR_AUTOPLAY')) { + $slideInterval = Tools::getValue('HOTEL_INTERIOR_AUTOPLAY_INTERVAL'); + if (!Validate::isUnsignedInt($slideInterval) || $slideInterval < 500) { + $this->errors[] = $this->l('Slide interval must be a whole number of at least 500 milliseconds.'); + } + } else { + $_POST['HOTEL_INTERIOR_AUTOPLAY_INTERVAL'] = Configuration::get('HOTEL_INTERIOR_AUTOPLAY_INTERVAL'); + } if (!count($this->errors)) { foreach ($languages as $lang) { // if lang fileds are at least in default language and not available in other languages then diff --git a/modules/wkabouthotelblock/views/css/WkAboutHotelBlockFront.css b/modules/wkabouthotelblock/views/css/WkAboutHotelBlockFront.css index 332b27df1..c751e7d64 100644 --- a/modules/wkabouthotelblock/views/css/WkAboutHotelBlockFront.css +++ b/modules/wkabouthotelblock/views/css/WkAboutHotelBlockFront.css @@ -174,3 +174,35 @@ html body .htl-light-fancybox .fancybox-nav:focus:not(:focus-visible), html body .htl-gallery-fancybox .fancybox-nav:focus:not(:focus-visible) { outline: 0; } + +/* arrows of the interior slider */ +.htlInterior-owlCarousel .owl-nav .owl-prev, +.htlInterior-owlCarousel .owl-nav .owl-next { + position: absolute; + top: 42%; + display: flex; + align-items: center; + justify-content: center; + width: 44px; + height: 44px; + border-radius: 50%; + background: #fff; + box-shadow: 0 2px 8px rgba(0, 0, 0, .2); + cursor: pointer; +} +.htlInterior-owlCarousel .owl-nav .owl-prev { + left: -17px; +} +.htlInterior-owlCarousel .owl-nav .owl-next { + right: -17px; +} +.htlInterior-owlCarousel .owl-nav .nav-direction-secondary, +.htlInterior-owlCarousel .owl-nav .owl-prev:hover .nav-direction-secondary, +.htlInterior-owlCarousel .owl-nav .owl-next:hover .nav-direction-secondary { + padding: 0; + background: none; + color: #333; +} +.htlInterior-owlCarousel .owl-nav .disabled { + display: none; +} diff --git a/modules/wkabouthotelblock/views/js/WkAboutHotelBlockAdmin.js b/modules/wkabouthotelblock/views/js/WkAboutHotelBlockAdmin.js index 2a32530f4..3b9d8ad9e 100644 --- a/modules/wkabouthotelblock/views/js/WkAboutHotelBlockAdmin.js +++ b/modules/wkabouthotelblock/views/js/WkAboutHotelBlockAdmin.js @@ -21,6 +21,14 @@ */ $(document).ready(function(){ + $(document).on('change', 'input[name="HOTEL_INTERIOR_AUTOPLAY"]', function () { + if ($(this).val() === '1') { + $('input[name="HOTEL_INTERIOR_AUTOPLAY_INTERVAL"]').closest('.form-group').show(200); + } else { + $('input[name="HOTEL_INTERIOR_AUTOPLAY_INTERVAL"]').closest('.form-group').hide(200); + } + }); + $("#interior_img").on("change", function(event) { if (typeof this.files[0] != 'undefined') { if (this.files[0].size > maxSizeAllowed) { diff --git a/modules/wkabouthotelblock/views/js/WkAboutHotelBlockFront.js b/modules/wkabouthotelblock/views/js/WkAboutHotelBlockFront.js index 40c2a1af8..b9c657ac6 100644 --- a/modules/wkabouthotelblock/views/js/WkAboutHotelBlockFront.js +++ b/modules/wkabouthotelblock/views/js/WkAboutHotelBlockFront.js @@ -23,21 +23,32 @@ $(document).ready(function(){ $('.htlInterior-owlCarousel .owl-carousel').owlCarousel({ loop:true, - nav:false, - dots:true, + nav: htlInteriorSliderArrows, + navText: [ + '', + '' + ], + dots: htlInteriorSliderDots, items: 1, margin: 8, - slideBy: 'page', responsive: {576: {items: 2}}, autoHeight:true, - autoplay:true, - autoplaySpeed:1000, - autoplayTimeout:5000, + autoplay: htlInteriorSliderAutoplay, + autoplaySpeed: 500, + smartSpeed: 500, + autoplayTimeout: htlInteriorSliderInterval, autoplayHoverPause:true, responsiveClass:true, rtl: language_is_rtl }); + $('.htlInterior-owlCarousel .owl-carousel').on('translated.owl.carousel mouseleave', function() { + var owl = $(this).data('owl.carousel'); + if (owl && owl.settings.autoplay && owl._plugins.autoplay && !owl._plugins.autoplay._paused) { + owl._plugins.autoplay._setAutoPlayInterval(); + } + }); + $('.interiorbox').on('mouseenter', function(){ $(this).find('div.interiorHoverBlockWrapper').show(); }); diff --git a/modules/wkabouthotelblock/wkabouthotelblock.php b/modules/wkabouthotelblock/wkabouthotelblock.php index 05fd71933..062ff54f5 100644 --- a/modules/wkabouthotelblock/wkabouthotelblock.php +++ b/modules/wkabouthotelblock/wkabouthotelblock.php @@ -67,6 +67,22 @@ class WkAboutHotelBlock extends Module } } + $navType = (int) Configuration::get('HOTEL_INTERIOR_NAV_TYPE'); + if (!in_array($navType, array( + WkHotelInteriorImage::NAV_TYPE_DOTS, + WkHotelInteriorImage::NAV_TYPE_ARROWS, + WkHotelInteriorImage::NAV_TYPE_BOTH, + ))) { + $navType = WkHotelInteriorImage::NAV_TYPE_DOTS; + } + $autoplay = Configuration::get('HOTEL_INTERIOR_AUTOPLAY'); + Media::addJsDef(array( + 'htlInteriorSliderArrows' => $navType != WkHotelInteriorImage::NAV_TYPE_DOTS, + 'htlInteriorSliderDots' => $navType != WkHotelInteriorImage::NAV_TYPE_ARROWS, + 'htlInteriorSliderAutoplay' => $autoplay === false ? true : (bool) $autoplay, + 'htlInteriorSliderInterval' => max(500, (int) Configuration::get('HOTEL_INTERIOR_AUTOPLAY_INTERVAL') ?: 5000), + )); + $this->context->smarty->assign( array( 'HOTEL_INTERIOR_HEADING' => $HOTEL_INTERIOR_HEADING, @@ -204,7 +220,13 @@ class WkAboutHotelBlock extends Module public function deleteConfigKeys() { - $var = array('HOTEL_INTERIOR_HEADING', 'HOTEL_INTERIOR_DESCRIPTION'); + $var = array( + 'HOTEL_INTERIOR_HEADING', + 'HOTEL_INTERIOR_DESCRIPTION', + 'HOTEL_INTERIOR_NAV_TYPE', + 'HOTEL_INTERIOR_AUTOPLAY', + 'HOTEL_INTERIOR_AUTOPLAY_INTERVAL', + ); foreach ($var as $key) { if (!Configuration::deleteByName($key)) { return false; diff --git a/modules/wkhotelroom/classes/WkHotelRoomDisplay.php b/modules/wkhotelroom/classes/WkHotelRoomDisplay.php index 9ee4d3a0b..6c86737be 100644 --- a/modules/wkhotelroom/classes/WkHotelRoomDisplay.php +++ b/modules/wkhotelroom/classes/WkHotelRoomDisplay.php @@ -23,6 +23,10 @@ class WkHotelRoomDisplay extends ObjectModel { + const NAV_TYPE_DOTS = 1; + const NAV_TYPE_ARROWS = 2; + const NAV_TYPE_BOTH = 3; + public $id_product; public $active; public $position; @@ -188,6 +192,10 @@ class WkHotelRoomDisplay extends ObjectModel // update global configuration values in multilang Configuration::updateValue('HOTEL_ROOM_DISPLAY_HEADING', $HOTEL_ROOM_DISPLAY_HEADING); Configuration::updateValue('HOTEL_ROOM_DISPLAY_DESCRIPTION', $HOTEL_ROOM_DISPLAY_DESCRIPTION); + Configuration::updateValue('HOTEL_ROOM_DISPLAY_PROPERTY', 0); + Configuration::updateValue('HOTEL_ROOM_DISPLAY_NAV_TYPE', self::NAV_TYPE_ARROWS); + Configuration::updateValue('HOTEL_ROOM_DISPLAY_AUTOPLAY', 1); + Configuration::updateValue('HOTEL_ROOM_DISPLAY_AUTOPLAY_INTERVAL', 5000); if ($roomTypes = HotelHelper::getPsProducts(Configuration::get('PS_LANG_DEFAULT'), 0, 5, 1)) { foreach ($roomTypes as $product) { if (Validate::isLoadedObject($objProduct = new Product($product['id_product']))) { diff --git a/modules/wkhotelroom/controllers/admin/AdminHotelRoomModuleSettingController.php b/modules/wkhotelroom/controllers/admin/AdminHotelRoomModuleSettingController.php index d295347fe..9ca9877ac 100644 --- a/modules/wkhotelroom/controllers/admin/AdminHotelRoomModuleSettingController.php +++ b/modules/wkhotelroom/controllers/admin/AdminHotelRoomModuleSettingController.php @@ -31,6 +31,7 @@ class AdminHotelRoomModuleSettingController extends ModuleAdminController $this->_defaultOrderBy = 'position'; $this->bootstrap = true; $this->context = Context::getContext(); + parent::__construct(); $this->_select = ' pl.`name`, hbil.`hotel_name` '; @@ -64,6 +65,40 @@ class AdminHotelRoomModuleSettingController extends ModuleAdminController 'cols' => '2', 'hint' => $this->l('Enter a description for the property rooms block.'), ), + 'HOTEL_ROOM_DISPLAY_PROPERTY' => array( + 'title' => $this->l('Property location'), + 'cast' => 'intval', + 'type' => 'bool', + 'default' => '0', + 'hint' => $this->l('Display the property location below the room type name in the room cards, or hide it if it is not required.'), + ), + 'HOTEL_ROOM_DISPLAY_NAV_TYPE' => array( + 'title' => $this->l('Navigation Type'), + 'type' => 'select', + 'identifier' => 'id', + 'list' => array( + array('id' => WkHotelRoomDisplay::NAV_TYPE_ARROWS, 'name' => $this->l('Arrows')), + array('id' => WkHotelRoomDisplay::NAV_TYPE_DOTS, 'name' => $this->l('Dots')), + array('id' => WkHotelRoomDisplay::NAV_TYPE_BOTH, 'name' => $this->l('Arrows and dots')), + ), + 'hint' => $this->l('How the room cards slider of the home page is navigated.'), + ), + 'HOTEL_ROOM_DISPLAY_AUTOPLAY' => array( + 'title' => $this->l('Auto slide'), + 'cast' => 'intval', + 'type' => 'bool', + 'default' => '1', + 'hint' => $this->l('Slide the room cards automatically on the home page.'), + ), + 'HOTEL_ROOM_DISPLAY_AUTOPLAY_INTERVAL' => array( + 'title' => $this->l('Slide Interval'), + 'cast' => 'intval', + 'type' => 'text', + 'class' => 'fixed-width-sm', + 'suffix' => $this->l('ms'), + 'default' => '5000', + 'desc' => $this->l('Milliseconds between slides. Min: 500. Example: 5000 = 5 s.'), + ), ), 'submit' => array('title' => $this->l('Save')) ), @@ -131,7 +166,6 @@ class AdminHotelRoomModuleSettingController extends ModuleAdminController ); $this->identifier = 'id_room_block'; - parent::__construct(); if ($this->hotelList = HotelBranchInformation::getProfileAccessedHotels($this->context->employee->id_profile, 1)) { $objHotelRoomType = new HotelRoomType(); if ($this->loadObject(true) @@ -358,6 +392,21 @@ class AdminHotelRoomModuleSettingController extends ModuleAdminController } } } + if (!in_array((int) Tools::getValue('HOTEL_ROOM_DISPLAY_NAV_TYPE'), array( + WkHotelRoomDisplay::NAV_TYPE_DOTS, + WkHotelRoomDisplay::NAV_TYPE_ARROWS, + WkHotelRoomDisplay::NAV_TYPE_BOTH, + ))) { + $this->errors[] = $this->l('Invalid navigation type.'); + } + if (Tools::getValue('HOTEL_ROOM_DISPLAY_AUTOPLAY')) { + $slideInterval = Tools::getValue('HOTEL_ROOM_DISPLAY_AUTOPLAY_INTERVAL'); + if (!Validate::isUnsignedInt($slideInterval) || $slideInterval < 500) { + $this->errors[] = $this->l('Slide interval must be a whole number of at least 500 milliseconds.'); + } + } else { + $_POST['HOTEL_ROOM_DISPLAY_AUTOPLAY_INTERVAL'] = Configuration::get('HOTEL_ROOM_DISPLAY_AUTOPLAY_INTERVAL'); + } if (!count($this->errors)) { foreach ($languages as $lang) { // if lang fileds are at least in default language and not available in other languages then diff --git a/modules/wkhotelroom/views/css/WkHotelRoomBlockFront.css b/modules/wkhotelroom/views/css/WkHotelRoomBlockFront.css index 3a8228f5d..8ffc0d805 100644 --- a/modules/wkhotelroom/views/css/WkHotelRoomBlockFront.css +++ b/modules/wkhotelroom/views/css/WkHotelRoomBlockFront.css @@ -134,6 +134,13 @@ a.htlRoomTypeBookNow { margin: 0 0 12px; font-size: 22px; } +.htlRoomTypeNameText a:focus, +.htlRoomTypeNameText a:active, +.htlRoomCardImg:focus, +.htlRoomTypeBookNow:focus { + outline: none; + text-decoration: none; +} .htlRoomCardFeatured { padding: 12px 0; overflow: hidden; @@ -326,3 +333,51 @@ a.htlRoomTypeBookNow:hover { .htlRoomCardFeatured .htlRoomFeatureMore { margin-left: 5px; } + +/* property name and city under the room type name */ +.htlRoomCardBody .htlRoomCardProperty { + margin: 0px 0 12px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: #777; + font-size: 13px; +} +.htlRoomCardProperty > span + span:before { + margin: 0 10px; + color: #CCC; +} +.htlRoomCardProperty i { + margin-right: 4px; + color: #999; +} + +/* with the property line, the features get a line above them */ +.htlRoomCardProperty + .htlRoomCardFeatured { + border-top: 1px solid #E5E5E5; +} + +/* the amenities keep their line under the property line */ +.htlRoomCardProperty + .htlRoomCardMeta { + border-top: 1px solid #E5E5E5; + padding-top: 12px; +} + +/* dots under the room cards */ +.htlRoom-owlCarousel .owl-dots { + display: flex; + justify-content: center; + gap: 6px; + margin-top: 20px; +} +.htlRoom-owlCarousel .owl-dots .owl-dot span { + display: block; + width: 7px; + height: 7px; + margin: 0; + border-radius: 50%; + background: #C4C4C4; +} +.htlRoom-owlCarousel .owl-dots .owl-dot.active span { + background: #333; +} diff --git a/modules/wkhotelroom/views/js/WkHotelRoomBlockAdmin.js b/modules/wkhotelroom/views/js/WkHotelRoomBlockAdmin.js index 481e7bbcf..d5f941221 100644 --- a/modules/wkhotelroom/views/js/WkHotelRoomBlockAdmin.js +++ b/modules/wkhotelroom/views/js/WkHotelRoomBlockAdmin.js @@ -21,6 +21,13 @@ */ $(document).ready(function() { + $(document).on('change', 'input[name="HOTEL_ROOM_DISPLAY_AUTOPLAY"]', function () { + if ($(this).val() === '1') { + $('input[name="HOTEL_ROOM_DISPLAY_AUTOPLAY_INTERVAL"]').closest('.form-group').show(200); + } else { + $('input[name="HOTEL_ROOM_DISPLAY_AUTOPLAY_INTERVAL"]').closest('.form-group').hide(200); + } + }); var trigger_ajax = ''; $('#productName').on('keyup', function(event) { var suggestion_ul = $(this).siblings("ul.prod_suggest_ul"); diff --git a/modules/wkhotelroom/views/js/WkHotelRoomBlockFront.js b/modules/wkhotelroom/views/js/WkHotelRoomBlockFront.js index d921dcbac..6de1d6476 100644 --- a/modules/wkhotelroom/views/js/WkHotelRoomBlockFront.js +++ b/modules/wkhotelroom/views/js/WkHotelRoomBlockFront.js @@ -86,11 +86,14 @@ $(document).ready(function() { fitRoomCards($(this)); }).owlCarousel({ loop: true, - nav: true, + nav: htlRoomSliderArrows, navText: ['', ''], - dots: false, - autoplay: true, - autoplaySpeed: 1000, + dots: htlRoomSliderDots, + autoplay: htlRoomSliderAutoplay, + autoplayTimeout: htlRoomSliderInterval, + autoplaySpeed: 500, + smartSpeed: 500, + autoplayHoverPause: true, margin: 24, items: 1, rtl: typeof language_is_rtl != 'undefined' && language_is_rtl, @@ -99,6 +102,13 @@ $(document).ready(function() { 992: { items: 3 } } }); + + $('.htlRoom-owlCarousel .owl-carousel').on('translated.owl.carousel mouseleave', function() { + var owl = $(this).data('owl.carousel'); + if (owl && owl.settings.autoplay && owl._plugins.autoplay && !owl._plugins.autoplay._paused) { + owl._plugins.autoplay._setAutoPlayInterval(); + } + }); // fonts and icons can change the widths after the page is shown $(window).on('load', function() { fitRoomCards($('.htlRoom-owlCarousel .owl-carousel')); diff --git a/modules/wkhotelroom/views/templates/hook/hotelRoomDisplayBlock.tpl b/modules/wkhotelroom/views/templates/hook/hotelRoomDisplayBlock.tpl index b63d3bcd1..3e2989d8d 100644 --- a/modules/wkhotelroom/views/templates/hook/hotelRoomDisplayBlock.tpl +++ b/modules/wkhotelroom/views/templates/hook/hotelRoomDisplayBlock.tpl @@ -53,7 +53,12 @@ {/block}
{block name='hotel_room_block_room_type_description'} -

{$roomDisplay.name|escape:'htmlall':'UTF-8'}

+

{$roomDisplay.name|escape:'htmlall':'UTF-8'}

+ {if isset($roomDisplay.property_name) && $roomDisplay.property_name} +

+ {$roomDisplay.property_name|escape:'htmlall':'UTF-8'}{if isset($roomDisplay.property_location) && $roomDisplay.property_location}, {$roomDisplay.property_location|escape:'htmlall':'UTF-8'}{/if} +

+ {/if} {if $roomDisplay.feature}
{* one line: the row is cut with an ellipsis by the css when it is wider than the card *} diff --git a/modules/wkhotelroom/wkhotelroom.php b/modules/wkhotelroom/wkhotelroom.php index 67425eb78..24706de8f 100644 --- a/modules/wkhotelroom/wkhotelroom.php +++ b/modules/wkhotelroom/wkhotelroom.php @@ -51,6 +51,7 @@ class WkHotelRoom extends Module $dateFrom = date('Y-m-d'); $dateTo = date('Y-m-d', strtotime($dateFrom) + 86400); $useTax = HotelBookingDetail::useTax(); + $displayProperty = (bool) Configuration::get('HOTEL_ROOM_DISPLAY_PROPERTY'); $maxFeatures = (int) Configuration::get('WK_MAX_FEATURES_ROOM_SEARCH'); $maxFeatureValues = (int) Configuration::get('WK_MAX_ROOM_TYPE_FEATURE_VALUES'); foreach ($hotelRoomDisplay as &$htlRoom) { @@ -113,6 +114,13 @@ class WkHotelRoom extends Module ); $objRoomType = new HotelRoomType(); $roomTypeInfo = $objRoomType->getRoomTypeInfoByIdProduct($idProduct); + if ($displayProperty && $roomTypeInfo) { + $objHotelBranch = new HotelBranchInformation((int) $roomTypeInfo['id_hotel'], $idLang); + $htlRoom['property_name'] = $objHotelBranch->hotel_name; + $addressInfo = HotelBranchInformation::getAddress((int) $roomTypeInfo['id_hotel'], $idLang); + $htlRoom['property_location'] = $addressInfo['city']. + ($addressInfo['state'] ? ', '.$addressInfo['state'] : '').', '.$addressInfo['country']; + } $htlRoom['max_adults'] = $roomTypeInfo ? (int) $roomTypeInfo['max_adults'] : 0; $htlRoom['max_children'] = $roomTypeInfo ? (int) $roomTypeInfo['max_children'] : 0; $features = array(); @@ -147,6 +155,22 @@ class WkHotelRoom extends Module ) ); + $autoplay = Configuration::get('HOTEL_ROOM_DISPLAY_AUTOPLAY'); + $navType = (int) Configuration::get('HOTEL_ROOM_DISPLAY_NAV_TYPE'); + if (!in_array($navType, array( + WkHotelRoomDisplay::NAV_TYPE_DOTS, + WkHotelRoomDisplay::NAV_TYPE_ARROWS, + WkHotelRoomDisplay::NAV_TYPE_BOTH, + ))) { + $navType = WkHotelRoomDisplay::NAV_TYPE_ARROWS; + } + Media::addJsDef(array( + 'htlRoomSliderArrows' => $navType != WkHotelRoomDisplay::NAV_TYPE_DOTS, + 'htlRoomSliderDots' => $navType != WkHotelRoomDisplay::NAV_TYPE_ARROWS, + 'htlRoomSliderAutoplay' => $autoplay === false ? true : (bool) $autoplay, + 'htlRoomSliderInterval' => max(500, (int) Configuration::get('HOTEL_ROOM_DISPLAY_AUTOPLAY_INTERVAL') ?: 5000), + )); + $this->context->controller->addCSS(_PS_JS_DIR_.'owl-carousel/assets/owl.carousel.min.css'); $this->context->controller->addCSS(_PS_JS_DIR_.'owl-carousel/assets/owl.theme.default.min.css'); $this->context->controller->addJS(_PS_JS_DIR_.'owl-carousel/owl.carousel.min.js'); @@ -287,7 +311,11 @@ class WkHotelRoom extends Module { $configVars = array( 'HOTEL_ROOM_DISPLAY_HEADING', - 'HOTEL_ROOM_DISPLAY_DESCRIPTION' + 'HOTEL_ROOM_DISPLAY_DESCRIPTION', + 'HOTEL_ROOM_DISPLAY_PROPERTY', + 'HOTEL_ROOM_DISPLAY_NAV_TYPE', + 'HOTEL_ROOM_DISPLAY_AUTOPLAY', + 'HOTEL_ROOM_DISPLAY_AUTOPLAY_INTERVAL', ); foreach ($configVars as $key) { if (!Configuration::deleteByName($key)) { diff --git a/themes/hotel-reservation-theme/_partials/booking-form.tpl b/themes/hotel-reservation-theme/_partials/booking-form.tpl index c0109f278..179ad07a7 100644 --- a/themes/hotel-reservation-theme/_partials/booking-form.tpl +++ b/themes/hotel-reservation-theme/_partials/booking-form.tpl @@ -99,6 +99,15 @@
{$product['name']} + {assign var='appliedDays' value=0} + {if $product['price_calculation_method'] != Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING} + {foreach Product::getServicePriceApplicableDates($product['price_calculation_method'], $date_from, $date_to) as $appliedGroupDates} + {assign var='appliedDays' value=$appliedDays + count($appliedGroupDates)} + {/foreach} + {/if} + {if $appliedDays} +

{if $appliedDays == 1}{l s='Applied for 1 Day'}{else}{l s='Applied for %s Days' sprintf=$appliedDays}{/if}

+ {/if} {if $product['allow_multiple_quantity']}

{l s='qty'}: {$product['quantity']}

{/if} diff --git a/themes/hotel-reservation-theme/_partials/cart_booking_services.tpl b/themes/hotel-reservation-theme/_partials/cart_booking_services.tpl index f9f437596..22cf53395 100644 --- a/themes/hotel-reservation-theme/_partials/cart_booking_services.tpl +++ b/themes/hotel-reservation-theme/_partials/cart_booking_services.tpl @@ -56,6 +56,8 @@ {else} {assign var='serviceSelected' value=false} {/if} + {* services applied on selected days only are not shown when none of those days exist in the stay *} + {if $serviceSelected || $product['price_calculation_method'] == Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING || (Product::getServicePriceApplicableDates($product['price_calculation_method'], $cartRoom['date_from'], $cartRoom['date_to'])|@array_filter)}
@@ -68,20 +70,8 @@
{$product['name']|escape:'html':'UTF-8'} - {assign var='priceCalcMethod' value=$product.price_calculation_method|default:0} - {capture name='htl_pcm_tooltip'} -
-
{l s='Applied on:'}
-
-
-
    - {foreach from=Product::getPriceCalculationMethodDaysLabel($priceCalcMethod) item='pcmDayLabel'} -
  • {$pcmDayLabel}
  • - {/foreach} -
-
- {/capture} - {include file='_partials/ui-tooltip.tpl' tooltip_content=$smarty.capture.htl_pcm_tooltip allow_html=true} {if $product.allow_multiple_quantity} + {include file='_partials/service-applied-days.tpl' price_calculation_method=$product.price_calculation_method|default:0 date_from=$cartRoom['date_from'] date_to=$cartRoom['date_to']} + {if $product.allow_multiple_quantity}
@@ -96,10 +86,25 @@
{if ($product.show_price && !isset($restricted_country_mode)) || isset($groups)} - {if !$priceDisplay}{convertPrice price=$product.price_tax_incl}{else}{convertPrice price=$product.price_tax_exc}{/if} + {assign var='serviceDayBased' value=(($product.price_calculation_method|default:0) != Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING)} + {assign var='serviceTotal' value=false} + {if $serviceSelected} + {assign var='serviceTotal' value=$cartRoom['selected_service'][$product['id_product']]} + {elseif isset($cartRoom['available_service_pricing'][$product['id_product']])} + {assign var='serviceTotal' value=$cartRoom['available_service_pricing'][$product['id_product']]} + {/if} +
+ {if $serviceDayBased && $serviceTotal} + {if !$priceDisplay}{convertPrice price=$serviceTotal.total_price_tax_incl}{else}{convertPrice price=$serviceTotal.total_price_tax_excl}{/if} +
{if !$priceDisplay}{convertPrice price=$product.price_tax_incl}{else}{convertPrice price=$product.price_tax_exc}{/if} {l s='/Night'} + {else} + {if !$priceDisplay}{convertPrice price=$product.price_tax_incl}{else}{convertPrice price=$product.price_tax_exc}{/if} + {/if} +
{/if}
+ {/if} {/foreach} {/if}
diff --git a/themes/hotel-reservation-theme/_partials/order-extra-services.tpl b/themes/hotel-reservation-theme/_partials/order-extra-services.tpl index 8d4bfe473..cc818bb05 100644 --- a/themes/hotel-reservation-theme/_partials/order-extra-services.tpl +++ b/themes/hotel-reservation-theme/_partials/order-extra-services.tpl @@ -53,20 +53,7 @@ {if $additionalService['allow_multiple_quantity']} {l s='(Quantity: %s)' sprintf=[$additionalService['quantity']|string_format:'%02d']} {/if} - {assign var='priceCalcMethod' value=$additionalService.price_calculation_method|default:0} - {capture name='htl_pcm_tooltip'} -
-
{l s='Applied on:'}
-
-
-
    - {foreach from=Product::getPriceCalculationMethodDaysLabel($priceCalcMethod) item='pcmDayLabel'} -
  • {$pcmDayLabel}
  • - {/foreach} -
-
- {/capture} - {include file='_partials/ui-tooltip.tpl' tooltip_content=$smarty.capture.htl_pcm_tooltip allow_html=true} + {include file='_partials/service-applied-days.tpl' price_calculation_method=$additionalService.price_calculation_method|default:0 date_from=$roomAdditionalService['date_from'] date_to=$roomAdditionalService['date_to']}
{hook h='displayOrderDetailServiceProductNameBlockAfter' product_order_detail=$additionalService}
@@ -78,6 +65,9 @@ {displayPrice price=$additionalService['total_price_tax_excl']|escape:'html':'UTF-8' currency=$objOrder->id_currency} {/if} + {if ($additionalService.price_calculation_method|default:0) != Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING} +
{if $useTax}{displayPrice price=$additionalService['unit_price_tax_incl']|escape:'html':'UTF-8' currency=$objOrder->id_currency}{else}{displayPrice price=$additionalService['unit_price_tax_excl']|escape:'html':'UTF-8' currency=$objOrder->id_currency}{/if} {l s='/Night'} + {/if}
{/foreach} diff --git a/themes/hotel-reservation-theme/_partials/service-applied-days.tpl b/themes/hotel-reservation-theme/_partials/service-applied-days.tpl new file mode 100644 index 000000000..3c426c4b6 --- /dev/null +++ b/themes/hotel-reservation-theme/_partials/service-applied-days.tpl @@ -0,0 +1,56 @@ +{** +* 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($price_calculation_method) && (int) $price_calculation_method != Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING && !empty($date_from) && !empty($date_to)} + {assign var='serviceAppliedDates' value=Product::getServicePriceApplicableDates($price_calculation_method, $date_from, $date_to)} + {assign var='serviceAppliedDays' value=0} + {foreach from=$serviceAppliedDates item='serviceAppliedGroupDates'} + {assign var='serviceAppliedDays' value=$serviceAppliedDays + count($serviceAppliedGroupDates)} + {/foreach} + {if $serviceAppliedDays} + {capture name='htl_applied_days_tooltip'} +
+
{l s='Applied on:'}
+
+
+
    + {foreach from=$serviceAppliedDates key='serviceAppliedGroup' item='serviceAppliedGroupDates'} + {if $serviceAppliedGroupDates} +
  • + {if $serviceAppliedGroup == 'checkin'}{l s='Check-in day'}{elseif $serviceAppliedGroup == 'stay'}{l s='During-stay days'}{else}{l s='Check-out day'}{/if} +
      + {foreach from=$serviceAppliedGroupDates item='serviceAppliedDate'} +
    • {Tools::displayDate($serviceAppliedDate)}
    • + {/foreach} +
    +
  • + {/if} + {/foreach} +
+
+ {/capture} + {capture name='htl_applied_days_badge'}{if $serviceAppliedDays == 1}{l s='Applied for 1 Day'}{else}{l s='Applied for %s Days' sprintf=$serviceAppliedDays}{/if}{/capture} +
+ {include file='_partials/ui-tooltip.tpl' tooltip_title=$smarty.capture.htl_applied_days_badge tooltip_content=$smarty.capture.htl_applied_days_tooltip allow_html=true} +
+ {/if} +{/if} diff --git a/themes/hotel-reservation-theme/_partials/service-products-list-row.tpl b/themes/hotel-reservation-theme/_partials/service-products-list-row.tpl index f82b7c8b4..1432c8829 100644 --- a/themes/hotel-reservation-theme/_partials/service-products-list-row.tpl +++ b/themes/hotel-reservation-theme/_partials/service-products-list-row.tpl @@ -39,19 +39,6 @@
{$service_product['name']} {assign var='priceCalcMethod' value=$service_product.price_calculation_method|default:0} - {capture name='htl_pcm_tooltip'} -
-
{l s='Applied on:'}
-
-
-
    - {foreach from=Product::getPriceCalculationMethodDaysLabel($priceCalcMethod) item='pcmDayLabel'} -
  • {$pcmDayLabel}
  • - {/foreach} -
-
- {/capture} - {include file='_partials/ui-tooltip.tpl' tooltip_content=$smarty.capture.htl_pcm_tooltip allow_html=true}
{/block} {block name='service_product_description'} @@ -68,12 +55,17 @@
{block name='service_product_price'} {if !$priceDisplay}{convertPrice price=$service_product.price_tax_incl}{else}{convertPrice price=$service_product.price_tax_exc}{/if} + {if $priceCalcMethod != Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING}{l s='/Night'}{/if} {/block} + {include file='_partials/service-applied-days.tpl' price_calculation_method=$priceCalcMethod date_from=$service_product.stay_date_from|default:'' date_to=$service_product.stay_date_to|default:''} {if $service_product.allow_multiple_quantity && $service_product.available_for_order && $service_product.max_quantity > 0}
{l s='Maximum'} {$service_product.max_quantity} {l s='quantity can be added'}.
{/if} + {block name='displayServiceProductActionsAfter'} + {hook h='displayServiceProductActionsAfter' id_service_product=$service_product['id_product']} + {/block}
{/if} {/block} diff --git a/themes/hotel-reservation-theme/css/global.css b/themes/hotel-reservation-theme/css/global.css index d70e202d5..ed0870c60 100644 --- a/themes/hotel-reservation-theme/css/global.css +++ b/themes/hotel-reservation-theme/css/global.css @@ -9495,6 +9495,8 @@ div a.our_properties_link{ display: grid; grid-template-columns: auto 1fr; gap: 5px 15px; + max-height: 250px; + overflow-y: auto; } .ui-tooltip-elem-txt { @@ -9503,3 +9505,15 @@ div a.our_properties_link{ text-align: right; white-space: nowrap; } + +.service-applied-days { + display: inline-block; + font-size: 11px; + font-weight: 600; + line-height: 1.3; + background-color: #72c279; + color: #fff; + border-radius: 4px; + padding: 3px 9px; + vertical-align: text-top; +} \ No newline at end of file diff --git a/themes/hotel-reservation-theme/js/product.js b/themes/hotel-reservation-theme/js/product.js index 6b4505b41..d7261f66a 100644 --- a/themes/hotel-reservation-theme/js/product.js +++ b/themes/hotel-reservation-theme/js/product.js @@ -1114,7 +1114,7 @@ $(document).ready(function() { var ajax_check_var = ''; - $('#service_products_cont .get-service-products').on('click', function(e) { + $('#service_products_cont').on('click', '.get-service-products', function(e) { var triggerElement = $(this); var p = $(this).data('page'); var id_category = $(this).data('id_category'); @@ -1131,6 +1131,8 @@ $(document).ready(function() { id_category: id_category, p: p, id_product: $('#product_page_product_id').val(), + date_from: $('#room_check_in').val(), + date_to: $('#room_check_out').val(), action: 'getServiceProducts', ajax: true, token: static_token @@ -1393,7 +1395,7 @@ var BookingForm = { }).on('datepicker-change', function(event,obj){ $('#room_check_in').val($.datepicker.formatDate('yy-mm-dd', obj.date1)); $('#room_check_out').val($.datepicker.formatDate('yy-mm-dd', obj.date2)); - BookingForm.refresh(); + BookingForm.refresh(false, true); }); $('body button').on('click', '', function() { @@ -1413,9 +1415,12 @@ var BookingForm = { formData.append('room_service_products', JSON.stringify(getRoomsServiceProducts())); return formData; }, - refresh: function(resetOccupancy = false) { + refresh: function(resetOccupancy = false, refreshServices = false) { return new Promise((resolve, reject) => { let formData = BookingForm.getFormData(); + if (refreshServices) { + formData.append('refresh_services', 1); + } formData.append('ajax', true); formData.append('action', 'refreshBookingForm'); BookingForm.currentRequest = $.ajax({ @@ -1438,6 +1443,19 @@ var BookingForm = { $('.booking-form').replaceWith(response.html_booking_form); BookingForm.init(); + if (typeof response.html_service_products !== 'undefined') { + $('#service_products_cont').html(response.html_service_products); + initUITooltip(); + $('#additional_products input.service_product').each(function () { + let id = $(this).data('id_product'); + $('#room_service_product_qty_' + id).val($(this).val()).closest('.qty_container').find('.qty_count span').text($(this).val()); + $('.select_room_service_' + id).text(unselect_txt).removeClass('btn-success add_roomtype_product').addClass('btn-danger remove_roomtype_product'); + }); + if (!$('.max_avail_type_qty').length || $('.max_avail_type_qty').val() < 1) { + disableRoomTypeServices(1); + } + } + if (resetOccupancy) { BookingForm.resetOccupancy(); }