Merge branch 'Qloapps:develop' into gli-3342

This commit is contained in:
Shazar Rahman
2026-11-03 19:25:24 +05:30
committed by GitHub
33 changed files with 757 additions and 223 deletions

View File

@@ -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

View File

@@ -928,7 +928,7 @@
<td class="partial_refund_fields current-edit" style="display:none;"></td>
</tr>
{/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}
<tr id="total_products">
<td class="text-right">{l s='Total Extra services (Tax excl.)'}</td>
<td class="amount text-right nowrap">
@@ -1036,7 +1036,7 @@
<td class="partial_refund_fields current-edit" style="display:none;"></td>
</tr>
{/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}
<tr id="total_products">
<td class="text-left">{l s='Convenience Fee Tax'}</td>
<td class="amount text-right nowrap">

View File

@@ -47,6 +47,7 @@
</thead>
<tbody>
{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 @@
<td>
<div>
{$product['name']|escape:'html':'UTF-8'}
{capture name='room_type_service_tooltip_content'}
{assign var="priceCalcMethod" value=$product['price_calculation_method']|default:0}
<div class="ui-tooltip-header">
<div class="ui-tooltip-label">{l s='Applied on:'}</div>
</div>
<div class="ui-tooltip-body">
<ul>
{foreach from=Product::getPriceCalculationMethodDaysLabel($priceCalcMethod) item='pcmDayLabel'}
<li>{$pcmDayLabel}</li>
{/foreach}
</ul>
</div>
{/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:''}
</div>
</td>
<td>
@@ -108,6 +95,7 @@
{$cartCurrency->sign}
</span>
<input class="service_cart_price_input" id="service_cart_price_{$selectedRoomServiceProduct['id']}_{$product['id_product']}" type="text" value="{$product.price_tax_exc}" name="service_price[{$product['id_product']|escape:'html':'UTF-8'}]"{if isset($can_edit) && !$can_edit} readonly{/if}/>
{if ($product['price_calculation_method']) != Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING}<span class="input-group-addon">{l s='/ Night'}</span>{/if}
</div>
{/if}
</td>
@@ -143,40 +131,6 @@
</div>
</div>
<div class="row form-group">
<div class="col-sm-6">
<label class="control-label required">{l s='Price calculation method'}</label>
<select id="new_service_pcm_type" name="price_calculation_type">
<option value="{Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING|intval}" selected="selected">{l s='Once for the booking range'}</option>
<option value="0">{l s='Select applicable days'}</option>
</select>
<div id="new_service_pcm_days_container" style="margin-top: 10px; display:none;">
<div class="row">
<div class="col-sm-3">
<label class="control-label required">{l s='Applied on:'}</label>
</div>
<div class="col-sm-4">
<div class="checkbox">
<label>
<input type="checkbox" id="pcm_checkin" name="new_service_price_calc_method[]" value="{Product::PRICE_CALCULATION_METHOD_ON_CHECKIN_DAY|intval}">
{l s='Check-in day'}
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" id="pcm_checkout" name="new_service_price_calc_method[]" value="{Product::PRICE_CALCULATION_METHOD_ON_CHECKOUT_DAY|intval}">
{l s='Check-out day'}
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" id="pcm_duringstay" name="new_service_price_calc_method[]" value="{Product::PRICE_CALCULATION_METHOD_ON_DURING_STAY|intval}"/>
{l s='During-stay days'}
</label>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6">
<label class="control-label">{l s='Auto added service'}</label>
<div>
@@ -193,8 +147,6 @@
</span>
</div>
</div>
</div>
<div class="row form-group">
<div id="new_service_price_tax_rule_container" class="col-sm-6">
<label class="control-label">{l s='Tax rule'}</label>
<select name="new_service_price_tax_rule_group">
@@ -211,10 +163,40 @@
<option value="{Product::PRICE_ADDITION_TYPE_INDEPENDENT}">{l s='Add price as convenience Fee'}</option>
</select>
</div>
</div>
<div class="row form-group">
<div id="new_service_qty_container" class="col-sm-6">
<label class="control-label required">{l s='Quantity'}</label>
<input type="number" class="form-control qty" min="1" name="new_service_qty" value="1">
</div>
<div class="col-sm-6">
<label class="control-label required">{l s='Price calculation method'}</label>
<select id="new_service_pcm_type" name="price_calculation_type">
<option value="{Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING|intval}" selected="selected">{l s='Once for the booking range'}</option>
<option value="0">{l s='Select applicable days'}</option>
</select>
</div>
</div>
<div id="new_service_pcm_days_container" style="display:none;">
<div class="row form-group">
<div class="col-sm-12">
<label class="control-label required">{l s='Applied on:'}</label>
</div>
<div class="col-sm-12">
<label class="checkbox-inline">
<input type="checkbox" id="pcm_checkin" name="new_service_price_calc_method[]" value="{Product::PRICE_CALCULATION_METHOD_ON_CHECKIN_DAY|intval}">
{l s='Check-in day'}
</label>
<label class="checkbox-inline">
<input type="checkbox" id="pcm_checkout" name="new_service_price_calc_method[]" value="{Product::PRICE_CALCULATION_METHOD_ON_CHECKOUT_DAY|intval}">
{l s='Check-out day'}
</label>
<label class="checkbox-inline">
<input type="checkbox" id="pcm_duringstay" name="new_service_price_calc_method[]" value="{Product::PRICE_CALCULATION_METHOD_ON_DURING_STAY|intval}"/>
{l s='During-stay days'}
</label>
</div>
</div>
</div>
<input type="hidden" name="id_hotel_cart_booking" value="{$id_hotel_cart_booking}">
<div class="row form-group">

View File

@@ -56,21 +56,7 @@
<td width="22%">
<div>
{$service['name']|escape:'html':'UTF-8'}
{capture name='room_type_service_tooltip_content'}
{assign var="priceCalcMethod" value=$service['price_calculation_method']|default:0}
<div class="ui-tooltip-header">
<div class="ui-tooltip-label">{l s='Applied on:'}</div>
</div>
<div class="ui-tooltip-body">
<ul>
{foreach from=Product::getPriceCalculationMethodDaysLabel($priceCalcMethod) item='pcmDayLabel'}
<li>{$pcmDayLabel}</li>
{/foreach}
</ul>
</div>
{/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']}
</div>
</div>
<input value="{$service['id_service_product_order_detail']|escape:'html':'UTF-8'}" name="id_service_product_order_detail[]" type="hidden"/>
@@ -101,6 +87,7 @@
<div class="input-group">
<span class="input-group-addon">{$currencySign}</span>
<input type="text" class="form-control unit_price" value="{Tools::ps_round($service['unit_price_tax_excl'], 2)}" data-id-product="{$service['id_product']}" name="service_price[{$service['id_service_product_order_detail']}]">
{if ($service['price_calculation_method']|default:0) != Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING}<span class="input-group-addon">{l s='/ Night'}</span>{/if}
</div>
</td>
<td>{displayPrice price=$service['total_price_tax_excl']|escape:'html':'UTF-8' currency=$orderCurrency}</td>
@@ -142,26 +129,15 @@
</thead>
<tbody>
{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}
<tr class="room_service_block">
<td>
<input data-id_booking_detail="{$id_booking_detail}" value="{$product['id_product']|escape:'html':'UTF-8'}" name="selected_service[]" type="checkbox" class="id_room_type_service"/>
</td>
<td>
{$product['name']|escape:'html':'UTF-8'}
{capture name='adm_pcm_tooltip_content'}
{assign var="priceCalcMethod" value=$product['price_calculation_method']|default:0}
<div class="ui-tooltip-header">
<div class="ui-tooltip-label">{l s='Applied on:'}</div>
</div>
<div class="ui-tooltip-body">
<ul>
{foreach from=Product::getPriceCalculationMethodDaysLabel($priceCalcMethod) item='pcmDayLabel'}
<li>{$pcmDayLabel}</li>
{/foreach}
</ul>
</div>
{/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:''}
</td>
<td class="text-center">
{if $product['auto_add_to_cart'] && $product['price_addition_type'] == Product::PRICE_ADDITION_TYPE_WITH_ROOM}
@@ -184,6 +160,7 @@
<div class="input-group">
<span class="input-group-addon">{$currencySign}</span>
<input type="text" class="form-control unit_price" name="service_price[{$product['id_product']|escape:'html':'UTF-8'}]" value="{$product['price_tax_exc']}" data-id-product="{$product.id_product}">
{if ($product['price_calculation_method']) != Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING}<span class="input-group-addon">{l s='/ Night'}</span>{/if}
</div>
</td>
{hook h="displayAdminOrderAvailableServicesData" id_service_product=$product['id_product'] id_htl_booking_detail=$id_booking_detail}
@@ -218,40 +195,6 @@
</div>
</div>
<div class="row form-group">
<div class="col-sm-6">
<label class="control-label required">{l s='Price calculation method'}</label>
<select id="new_service_pcm_type" name="price_calculation_type">
<option value="{Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING|intval}" selected="selected">{l s='Once for the booking range'}</option>
<option value="0">{l s='Select applicable days'}</option>
</select>
<div id="new_service_pcm_days_container" style="margin-top: 10px; display:none;">
<div class="row">
<div class="col-sm-3">
<label class="control-label required">{l s='Applied on:'}</label>
</div>
<div class="col-sm-4">
<div class="checkbox">
<label>
<input type="checkbox" id="pcm_checkin" name="new_service_price_calc_method[]" value="{Product::PRICE_CALCULATION_METHOD_ON_CHECKIN_DAY|intval}">
{l s='Check-in day'}
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" id="pcm_checkout" name="new_service_price_calc_method[]" value="{Product::PRICE_CALCULATION_METHOD_ON_CHECKOUT_DAY|intval}">
{l s='Check-out day'}
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" id="pcm_duringstay" name="new_service_price_calc_method[]" value="{Product::PRICE_CALCULATION_METHOD_ON_DURING_STAY|intval}"/>
{l s='During-stay days'}
</label>
</div>
</div>
</div>
</div>
</div>
<div class="col-sm-6">
<label class="control-label">{l s='Auto added service'}</label>
<div>
@@ -268,9 +211,7 @@
</span>
</div>
</div>
</div>
<div class="row form-group">
<div id="new_service_price_tax_rule_container" class="col-sm-6">
<div id="new_service_price_tax_rule_container" class="col-sm-6">
<label class="control-label">{l s='Tax rule'}</label>
<select name="new_service_price_tax_rule_group">
<option value="0">{l s='No Tax'}</option>
@@ -286,10 +227,40 @@
<option value="{Product::PRICE_ADDITION_TYPE_INDEPENDENT}">{l s='Add price as convenience Fee'}</option>
</select>
</div>
</div>
<div class="row form-group">
<div id="new_service_qty_container" class="col-sm-6">
<label class="control-label required">{l s='Quantity'}</label>
<input type="number" class="form-control qty" min="1" name="new_service_qty" value="1">
</div>
<div class="col-sm-6">
<label class="control-label required">{l s='Price calculation method'}</label>
<select id="new_service_pcm_type" name="price_calculation_type">
<option value="{Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING|intval}" selected="selected">{l s='Once for the booking range'}</option>
<option value="0">{l s='Select applicable days'}</option>
</select>
</div>
</div>
<div id="new_service_pcm_days_container" style="display:none;">
<div class="row form-group">
<div class="col-sm-12">
<label class="control-label required">{l s='Applied on:'}</label>
</div>
<div class="col-sm-12">
<label class="checkbox-inline">
<input type="checkbox" id="pcm_checkin" name="new_service_price_calc_method[]" value="{Product::PRICE_CALCULATION_METHOD_ON_CHECKIN_DAY|intval}">
{l s='Check-in day'}
</label>
<label class="checkbox-inline">
<input type="checkbox" id="pcm_checkout" name="new_service_price_calc_method[]" value="{Product::PRICE_CALCULATION_METHOD_ON_CHECKOUT_DAY|intval}">
{l s='Check-out day'}
</label>
<label class="checkbox-inline">
<input type="checkbox" id="pcm_duringstay" name="new_service_price_calc_method[]" value="{Product::PRICE_CALCULATION_METHOD_ON_DURING_STAY|intval}"/>
{l s='During-stay days'}
</label>
</div>
</div>
</div>
{if $roomTypeTaxRuleGroupExist}
<div class="row form-group">
@@ -328,20 +299,8 @@
</td>
<td>
{$service['name']|escape:'html':'UTF-8'}
{capture name='room_type_service_pcm_tooltip_content'}
{assign var="priceCalcMethod" value=$service['price_calculation_method']|default:0}
<div class="ui-tooltip-header">
<div class="ui-tooltip-label">{l s='Applied on:'}</div>
</div>
<div class="ui-tooltip-body">
<ul>
{foreach from=Product::getPriceCalculationMethodDaysLabel($priceCalcMethod) item='pcmDayLabel'}
<li>{$pcmDayLabel}</li>
{/foreach}
</ul>
</div>
{/capture}
{include file='helpers/ui-tooltip.tpl' tooltip_content=$smarty.capture.room_type_service_pcm_tooltip_content allow_html=true} </td>
{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']}
</td>
<td>
{if $service['product_auto_add'] && $service['product_price_addition_type'] == Product::PRICE_ADDITION_TYPE_INDEPENDENT}
<span class="badge badge-info label">{l s='Convenience fee'}</span>

View File

@@ -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'}
<div class="ui-tooltip-header">
<div class="ui-tooltip-label">{l s='Applied on:'}</div>
</div>
<div class="ui-tooltip-body">
<ul>
{foreach from=$serviceAppliedDates key='serviceAppliedGroup' item='serviceAppliedGroupDates'}
{if $serviceAppliedGroupDates}
<li>
{if $serviceAppliedGroup == 'checkin'}{l s='Check-in day'}{elseif $serviceAppliedGroup == 'stay'}{l s='During-stay days'}{else}{l s='Check-out day'}{/if}
<ul>
{foreach from=$serviceAppliedGroupDates item='serviceAppliedDate'}
<li>{Tools::displayDate($serviceAppliedDate)}</li>
{/foreach}
</ul>
</li>
{/if}
{/foreach}
</ul>
</div>
{/capture}
{capture name='adm_applied_days_badge'}<span class="label label-success label service-applied-days">{if $serviceAppliedDays == 1}{l s='Applied for 1 Day'}{else}{l s='Applied for %s Days' sprintf=$serviceAppliedDays}{/if}</span>{/capture}
<div>
{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}
</div>
{/if}
{/if}

View File

@@ -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;

View File

@@ -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,

View File

@@ -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')) {

View File

@@ -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();
}

View File

@@ -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`

View File

@@ -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` (

View File

@@ -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]);

View File

@@ -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) {

View File

@@ -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++) {

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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) {

View File

@@ -23,21 +23,32 @@
$(document).ready(function(){
$('.htlInterior-owlCarousel .owl-carousel').owlCarousel({
loop:true,
nav:false,
dots:true,
nav: htlInteriorSliderArrows,
navText: [
'<div class="nav-direction-wrapper"><div class="nav-direction-primary"><div class="nav-direction-secondary"><i class="icon-angle-left"></i></div></div></div>',
'<div class="nav-direction-wrapper"><div class="nav-direction-primary"><div class="nav-direction-secondary"><i class="icon-angle-right"></i></div></div></div>'
],
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();
});

View File

@@ -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;

View File

@@ -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']))) {

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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");

View File

@@ -86,11 +86,14 @@ $(document).ready(function() {
fitRoomCards($(this));
}).owlCarousel({
loop: true,
nav: true,
nav: htlRoomSliderArrows,
navText: ['<i class="icon-angle-left"></i>', '<i class="icon-angle-right"></i>'],
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'));

View File

@@ -53,7 +53,12 @@
{/block}
<div class="htlRoomCardBody">
{block name='hotel_room_block_room_type_description'}
<p class="htlRoomTypeNameText">{$roomDisplay.name|escape:'htmlall':'UTF-8'}</p>
<p class="htlRoomTypeNameText"><a href="{$link->getProductLink($roomDisplay.id_product)|escape:'html':'UTF-8'}">{$roomDisplay.name|escape:'htmlall':'UTF-8'}</a></p>
{if isset($roomDisplay.property_name) && $roomDisplay.property_name}
<p class="htlRoomCardProperty" title="{$roomDisplay.property_name|escape:'htmlall':'UTF-8'}{if isset($roomDisplay.property_location) && $roomDisplay.property_location}, {$roomDisplay.property_location|escape:'htmlall':'UTF-8'}{/if}">
<span><i class="icon-map-marker"></i> {$roomDisplay.property_name|escape:'htmlall':'UTF-8'}{if isset($roomDisplay.property_location) && $roomDisplay.property_location}, {$roomDisplay.property_location|escape:'htmlall':'UTF-8'}</span>{/if}
</p>
{/if}
{if $roomDisplay.feature}
<div class="htlRoomCardFeatured">
{* one line: the row is cut with an ellipsis by the css when it is wider than the card *}

View File

@@ -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)) {

View File

@@ -99,6 +99,15 @@
<div class="services-list-row">
<div>
{$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}
<p><span class="badge badge-success service-applied-days">{if $appliedDays == 1}{l s='Applied for 1 Day'}{else}{l s='Applied for %s Days' sprintf=$appliedDays}{/if}</span></p>
{/if}
{if $product['allow_multiple_quantity']}
<p>{l s='qty'}: {$product['quantity']}</p>
{/if}

View File

@@ -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)}
<div class="row room_service_block">
<div class="col-xs-8">
<div class="row">
@@ -68,20 +70,8 @@
</div>
<div class="col-xs-10">
<span>{$product['name']|escape:'html':'UTF-8'}</span>
{assign var='priceCalcMethod' value=$product.price_calculation_method|default:0}
{capture name='htl_pcm_tooltip'}
<div class="ui-tooltip-header">
<div class="ui-tooltip-label">{l s='Applied on:'}</div>
</div>
<div class="ui-tooltip-body">
<ul>
{foreach from=Product::getPriceCalculationMethodDaysLabel($priceCalcMethod) item='pcmDayLabel'}
<li>{$pcmDayLabel}</li>
{/foreach}
</ul>
</div>
{/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}
<div class="qty_container">
<input type="text" class="form-control qty" id="qty_{$product.id_product}" name="room_service_product_qty_{$product.id_product}" data-id-product="{$product.id_product}" data-max_quantity="{$product.max_quantity}" value="{if $serviceSelected}{$cartRoom['selected_service'][$product['id_product']]['quantity']}{else}1{/if}">
<input type="hidden" class="qty_hidden" id="qty_{$product.id_product}_hidden" value="{if $serviceSelected}{$cartRoom['selected_service'][$product['id_product']]['quantity']}{else}0{/if}">
@@ -96,10 +86,25 @@
</div>
<div class="col-xs-4">
{if ($product.show_price && !isset($restricted_country_mode)) || isset($groups)}
<span class="pull-right">{if !$priceDisplay}{convertPrice price=$product.price_tax_incl}{else}{convertPrice price=$product.price_tax_exc}{/if}</span>
{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}
<div class="pull-right text-right">
{if $serviceDayBased && $serviceTotal}
{if !$priceDisplay}{convertPrice price=$serviceTotal.total_price_tax_incl}{else}{convertPrice price=$serviceTotal.total_price_tax_excl}{/if}
<br><small>{if !$priceDisplay}{convertPrice price=$product.price_tax_incl}{else}{convertPrice price=$product.price_tax_exc}{/if} {l s='/Night'}</small>
{else}
{if !$priceDisplay}{convertPrice price=$product.price_tax_incl}{else}{convertPrice price=$product.price_tax_exc}{/if}
{/if}
</div>
{/if}
</div>
</div>
{/if}
{/foreach}
{/if}
</div>

View File

@@ -53,20 +53,7 @@
{if $additionalService['allow_multiple_quantity']}
<span class="quantity">{l s='(Quantity: %s)' sprintf=[$additionalService['quantity']|string_format:'%02d']}</span>
{/if}
{assign var='priceCalcMethod' value=$additionalService.price_calculation_method|default:0}
{capture name='htl_pcm_tooltip'}
<div class="ui-tooltip-header">
<div class="ui-tooltip-label">{l s='Applied on:'}</div>
</div>
<div class="ui-tooltip-body">
<ul>
{foreach from=Product::getPriceCalculationMethodDaysLabel($priceCalcMethod) item='pcmDayLabel'}
<li>{$pcmDayLabel}</li>
{/foreach}
</ul>
</div>
{/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']}
</div>
{hook h='displayOrderDetailServiceProductNameBlockAfter' product_order_detail=$additionalService}
</div>
@@ -78,6 +65,9 @@
{displayPrice price=$additionalService['total_price_tax_excl']|escape:'html':'UTF-8' currency=$objOrder->id_currency}
{/if}
</span>
{if ($additionalService.price_calculation_method|default:0) != Product::PRICE_CALCULATION_METHOD_ONCE_FOR_BOOKING}
<br><small>{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'}</small>
{/if}
</div>
</div>
{/foreach}

View File

@@ -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'}
<div class="ui-tooltip-header">
<div class="ui-tooltip-label">{l s='Applied on:'}</div>
</div>
<div class="ui-tooltip-body">
<ul>
{foreach from=$serviceAppliedDates key='serviceAppliedGroup' item='serviceAppliedGroupDates'}
{if $serviceAppliedGroupDates}
<li>
{if $serviceAppliedGroup == 'checkin'}{l s='Check-in day'}{elseif $serviceAppliedGroup == 'stay'}{l s='During-stay days'}{else}{l s='Check-out day'}{/if}
<ul>
{foreach from=$serviceAppliedGroupDates item='serviceAppliedDate'}
<li>{Tools::displayDate($serviceAppliedDate)}</li>
{/foreach}
</ul>
</li>
{/if}
{/foreach}
</ul>
</div>
{/capture}
{capture name='htl_applied_days_badge'}<span class="badge badge-success service-applied-days">{if $serviceAppliedDays == 1}{l s='Applied for 1 Day'}{else}{l s='Applied for %s Days' sprintf=$serviceAppliedDays}{/if}</span>{/capture}
<div class="service-applied-days-wrap">
{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}
</div>
{/if}
{/if}

View File

@@ -39,19 +39,6 @@
<div class="col-sm-12 clearfix service-product-block">
<span class="service-product-name">{$service_product['name']}</span>
{assign var='priceCalcMethod' value=$service_product.price_calculation_method|default:0}
{capture name='htl_pcm_tooltip'}
<div class="ui-tooltip-header">
<div class="ui-tooltip-label">{l s='Applied on:'}</div>
</div>
<div class="ui-tooltip-body">
<ul>
{foreach from=Product::getPriceCalculationMethodDaysLabel($priceCalcMethod) item='pcmDayLabel'}
<li>{$pcmDayLabel}</li>
{/foreach}
</ul>
</div>
{/capture}
{include file='_partials/ui-tooltip.tpl' tooltip_content=$smarty.capture.htl_pcm_tooltip allow_html=true}
</div>
{/block}
{block name='service_product_description'}
@@ -68,12 +55,17 @@
<div class="service-product-price">
{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}<span class="price-label">{l s='/Night'}</span>{/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}
<div class="service-max-quantity-info">
{l s='Maximum'} {$service_product.max_quantity} {l s='quantity can be added'}.
</div>
{/if}
{block name='displayServiceProductActionsAfter'}
{hook h='displayServiceProductActionsAfter' id_service_product=$service_product['id_product']}
{/block}
</div>
{/if}
{/block}

View File

@@ -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;
}

View File

@@ -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();
}