diff --git a/admin/themes/default/sass/controllers/_order.sass b/admin/themes/default/sass/controllers/_order.sass index 246660d86..f7f30d0d2 100644 --- a/admin/themes/default/sass/controllers/_order.sass +++ b/admin/themes/default/sass/controllers/_order.sass @@ -42,6 +42,12 @@ right: 0 margin: 0 auto +#customer_cart_details .overbooked_room + background-color: #FF69B4 + color: #FFF + padding: 2px 5px + border-radius: 2px + font-weight: 600 .room_status_info_form .room_status diff --git a/admin/themes/default/sass/modules/_variables.sass b/admin/themes/default/sass/modules/_variables.sass index 4db90b44f..99b590b6d 100644 --- a/admin/themes/default/sass/modules/_variables.sass +++ b/admin/themes/default/sass/modules/_variables.sass @@ -11,7 +11,7 @@ $contrasted-lightness-threshold: 10% $bg-page-head-color : $secondary-color $bg-panel-heading-color: hsl(hue($secondary-color), 10, 90) -$badge-notif-color: $greenPrestashop +$badge-notif-color: #D27C82 $brand-addons: $pinkPrestashop $font-family-mono: 'Droid Sans Mono', Helvetica, Arial, sans-serif diff --git a/admin/themes/default/sass/partials/_content.sass b/admin/themes/default/sass/partials/_content.sass index 860ae4a67..94328c025 100644 --- a/admin/themes/default/sass/partials/_content.sass +++ b/admin/themes/default/sass/partials/_content.sass @@ -109,7 +109,11 @@ body .dropdown-menu line-height: normal .panel-danger .panel-heading - background-color: $badge-notif-color!important + background-color: #DC143C + color: #FFF + margin: -20px -20px 15px -20px + padding: 0px 10px 0px 10px + form .alert clear: both @media (max-width: $screen-phone) diff --git a/admin/themes/default/sass/partials/_kpi.sass b/admin/themes/default/sass/partials/_kpi.sass index 3d2605967..78df786ff 100644 --- a/admin/themes/default/sass/partials/_kpi.sass +++ b/admin/themes/default/sass/partials/_kpi.sass @@ -1,14 +1,90 @@ +$kpi-rows-gap: 8px +$kpi-gap: 10px + .kpi-container - padding-bottom: 10px !important - .kpi-refresh - position: absolute - z-index: 1 - right: 20px + display: flex + justify-content: space-between + padding: 12px !important + .kpis-wrap + width: 100% + white-space: nowrap + overflow-x: auto + font-size: 0 + margin-bottom: -$kpi-rows-gap + display: flex + flex-wrap: wrap + margin-left: -($kpi-gap / 2) + margin-right: -($kpi-gap / 2) + .kpi-wrap + display: inline-block + width: 20% + margin-bottom: $kpi-rows-gap + padding-left: $kpi-gap / 2 + padding-right: $kpi-gap / 2 + .actions-wrap + width: 16px + margin-left: 16px + button + padding: 0 + border: none + opacity: 0.2 + display: block + width: 100% + &:not(:first-of-type) + margin-top: 4px + &:hover + opacity: 0.5 + background-color: #fff + &:focus, + &:active + outline: none !important + i + color: #000 !important + font-size: 16px + &.icon-ellipsis-vertical + position: relative + top: 3px + .dropdown-menu + top: 25px + right: 0 + left: unset + min-width: 200px + font-size: 13px + padding: 8px 12px + label + font-weight: normal + &.no-wrapping + .kpis-wrap + white-space: normal + flex-wrap: nowrap + .kpi-wrap + @media (min-width: $screen-lg) + min-width: 20% + @media (max-width: $screen-lg) + min-width: 20% + @media (max-width: $screen-sm) + min-width: 33.33% + @media (max-width: $screen-xs) + min-width: 50% + +@media (max-width: $screen-desktop) + &.no-wrapping + .kpis-wrap + flex-wrap: nowrap !important + +@media (max-width: $screen-lg) + .kpi-container .kpis-wrap .kpi-wrap + width: 20% +@media (max-width: $screen-sm) + .kpi-container .kpis-wrap .kpi-wrap + width: 33.33% +@media (max-width: $screen-xs) + .kpi-container .kpis-wrap .kpi-wrap + width: 50% .box-stats display: block - height: 64px - margin-bottom: 10px + height: 100% .boxchart-overlay @include padding(10px, 10px, 5px, 10px) @include margin-right(10px) @@ -16,58 +92,86 @@ @include border-radius(3px) .kpi-content width: 100% + height: 100% position: relative - @include padding-left(40px) - @include float(left) - i + border: 1px solid #e7edfa + border-left-width: 3px + border-radius: 6px + padding: 4px 8px 8px 42px + display: flex + flex-direction: column + justify-content: space-between + white-space: normal + .title-subtitle + display: flex + justify-content: space-between + .title + display: block + color: #666 + font-size: 13px + i + position: absolute + top: 5px + font-size: 15px + text-align: center + color: white + line-height: 26px + background: #eee + width: 28px + height: 28px + @include left(5px) + @include border-radius(6px) + &::before + position: absolute + top: 1px + left: 8px + .subtitle + display: block + text-transform: capitalize + color: #aaa + font-size: 10px + position: relative + top: 3px + text-align: right + .value-container + .value + display: block + clear: both + font-size: 14px + font-weight: bold + color: #666 + min-height: 22px + &.loading + height: 22px + min-width: 40px + .arrow position: absolute - top: 0 - font-size: 26px - text-align: center - color: white - line-height: 26px - @include left(0) - @include border-radius(3px) - - .title - display: block - color: #666 - .subtitle - display: block - text-transform: uppercase - color: #aaa - .value - display: block - clear: both - font-size: 1.5em - white-space: nowrap - text-overflow: ellipsis - overflow: hidden - &.loading - height: 1.2em - width: 200px + bottom: 7px + right: 9px + i + color: #828282 !important &:hover text-decoration: none .color1 + .kpi-content + border-left-color: #2BA8E3 i, .boxchart-overlay color: #2BA8E3 !important - .value - color: #2BA8E3 .color2 + .kpi-content + border-left-color: #FF5450 i, .boxchart-overlay color: #FF5450 !important - .value - color: #FF5450 .color3 + .kpi-content + border-left-color: #9E5BA1 i, .boxchart-overlay color: #9E5BA1 !important - .value - color: #9E5BA1 .color4 + .kpi-content + border-left-color: #95CC6B i, .boxchart-overlay color: #95CC6B !important - .value - color: #95CC6B .data_chart rect @@ -80,4 +184,4 @@ stroke: black .area fill: rgba(white,0.30) - stroke-width: 0 + stroke-width: 0 \ No newline at end of file diff --git a/admin/themes/default/template/controllers/customer_threads/helpers/view/message.tpl b/admin/themes/default/template/controllers/customer_threads/helpers/view/message.tpl index cb3158f93..d006ce679 100644 --- a/admin/themes/default/template/controllers/customer_threads/helpers/view/message.tpl +++ b/admin/themes/default/template/controllers/customer_threads/helpers/view/message.tpl @@ -54,7 +54,7 @@ {/if}   - {dateFormat date=$message.date_add full=0} - {$message.date_add|substr:11:5} {if isset($message.file_name)}   {l s="Attachment"}{/if} - {if isset($message.product_name)}   {l s="Product:"} {$message.product_name|escape:'html':'UTF-8'} {/if} + {if isset($message.product_name)}   {l s="Room type:"} {$message.product_name|escape:'html':'UTF-8'} {/if}

{$message.message|escape:'html':'UTF-8'|nl2br}

diff --git a/admin/themes/default/template/controllers/modules/page_header_toolbar.tpl b/admin/themes/default/template/controllers/modules/page_header_toolbar.tpl index eecd802b8..614cbfe40 100644 --- a/admin/themes/default/template/controllers/modules/page_header_toolbar.tpl +++ b/admin/themes/default/template/controllers/modules/page_header_toolbar.tpl @@ -64,7 +64,7 @@
  • - {l s='Recommended Modules and Services'} + {l s='Recommendations'}
  • {if $add_permission eq '1'} diff --git a/admin/themes/default/template/controllers/modules_catalog/page.tpl b/admin/themes/default/template/controllers/modules_catalog/page.tpl index fcdd22e60..7ed4b5485 100644 --- a/admin/themes/default/template/controllers/modules_catalog/page.tpl +++ b/admin/themes/default/template/controllers/modules_catalog/page.tpl @@ -109,7 +109,7 @@
    - {l s='Explore all themes of Qloapps'}  + {l s='Explore all themes of QloApps'}  {l s='QloApps Themes'}
    {/if} diff --git a/admin/themes/default/template/controllers/normal_products/prices.tpl b/admin/themes/default/template/controllers/normal_products/prices.tpl index ecd8fbbcb..9fc108e17 100644 --- a/admin/themes/default/template/controllers/normal_products/prices.tpl +++ b/admin/themes/default/template/controllers/normal_products/prices.tpl @@ -116,20 +116,20 @@ $(document).ready(function () {
    {include file="controllers/products/multishop/checkbox.tpl" field="wholesale_price" type="default"}
    {$currency->prefix}{$currency->suffix}
    - {if isset($pack) && $pack->isPack($product->id)}

    {l s='The sum of wholesale prices of the product in the pack is %s%s%s' sprintf=[$currency->prefix,{toolsConvertPrice price=$pack->noPackWholesalePrice($product->id)|string_format:$priceDisplayPrecisionFormat},$currency->suffix]}

    {/if} + {if isset($pack) && $pack->isPack($product->id)}

    {l s='The sum of rack rates of the service in the pack is %s%s%s' sprintf=[$currency->prefix,{toolsConvertPrice price=$pack->noPackWholesalePrice($product->id)|string_format:$priceDisplayPrecisionFormat},$currency->suffix]}

    {/if}
    {include file="controllers/products/multishop/checkbox.tpl" field="price" type="price"}
    diff --git a/admin/themes/default/template/controllers/order_preferences/helpers/options/options.tpl b/admin/themes/default/template/controllers/order_preferences/helpers/options/options.tpl index 23b7ffc5e..eba33ee11 100644 --- a/admin/themes/default/template/controllers/order_preferences/helpers/options/options.tpl +++ b/admin/themes/default/template/controllers/order_preferences/helpers/options/options.tpl @@ -24,4 +24,9 @@ *} {extends file="helpers/options/options.tpl"} -{block name="after"}{/block} +{block name="after"} + +{/block} diff --git a/admin/themes/default/template/controllers/orders/_overbookings.tpl b/admin/themes/default/template/controllers/orders/_overbookings.tpl new file mode 100644 index 000000000..b2b89fa6c --- /dev/null +++ b/admin/themes/default/template/controllers/orders/_overbookings.tpl @@ -0,0 +1,93 @@ +{* +* 2010-2023 Webkul. +* +* NOTICE OF LICENSE +* +* All right is reserved, +* Please go through this link for complete license : https://store.webkul.com/license.html +* +* DISCLAIMER +* +* Do not edit or add to this file if you wish to upgrade this module to 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 2010-2023 Webkul IN +* @license https://store.webkul.com/license.html +*} + +{if isset($orderOverBookings) && $orderOverBookings} +
    +
    + {l s='Overbookings found in this order !!'} +
    +
    +
    + + + + + + + + + + + {foreach from=$orderOverBookings item=data} + {if !$data.is_refunded} + + + + + + + + + + {/if} + {/foreach} +
    {l s='Room No.'}{l s='Room type.'}{l s='Duration'}{l s='Occupancy'}{l s='Confirmed booking order'}{l s='Reallocate/Swap'}{l s='Resolve'}
    {$data['room_num']}{$data['room_type_name']}{dateFormat date=$data['date_from']} {l s='To'} {dateFormat date=$data['date_to']} + {if $order->with_occupancy && $data['children']} + + {else} + {if $data['adults']}{$data['adults']}{/if} {if $data['adults'] > 1}{l s='Adults'}{else}{l s='Adult'}{/if}{if {$data['children']}}, {$data['children']} {if $data['children'] > 1}{l s='Children'}{else}{l s='Child'}{/if}{/if} + {/if} + + {if isset($data['booked_room_info']['id_order']) && $data['booked_room_info']['id_order']} + #{$data['booked_room_info']['id_order']} + {else} + {l s='N/A'} + {/if} + + {l s='Reallocate/Swap Room'} + + {if isset($data['booked_room_info']) && $data['booked_room_info']} + {l s='Already booked'} + {else} + {l s='Resolve'} + {/if} +
    +
    + +
    +

    - {l s='You can resolve room overbooking with \'Resolve\' column only when the overbooked room is now free for booked duration.'}

    +

    - {l s='You can also reallocate an overbooked room with an available room to resolve room\'s overbooking.'}

    +
    +
    +
    +{/if} \ No newline at end of file diff --git a/admin/themes/default/template/controllers/orders/_product_line.tpl b/admin/themes/default/template/controllers/orders/_product_line.tpl index ce198686e..4685230f5 100644 --- a/admin/themes/default/template/controllers/orders/_product_line.tpl +++ b/admin/themes/default/template/controllers/orders/_product_line.tpl @@ -30,7 +30,10 @@ {/if} - {$data.room_num} +

    {$data.room_num}

    + {if $data.is_back_order} + {l s='overbooked'} + {/if} diff --git a/admin/themes/default/template/controllers/orders/_resolvable_overbookings.tpl b/admin/themes/default/template/controllers/orders/_resolvable_overbookings.tpl new file mode 100644 index 000000000..b2f5cba79 --- /dev/null +++ b/admin/themes/default/template/controllers/orders/_resolvable_overbookings.tpl @@ -0,0 +1,77 @@ +{* +* 2010-2023 Webkul. +* +* NOTICE OF LICENSE +* +* All right is reserved, +* Please go through this link for complete license : https://store.webkul.com/license.html +* +* DISCLAIMER +* +* Do not edit or add to this file if you wish to upgrade this module to 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 2010-2023 Webkul IN +* @license https://store.webkul.com/license.html +*} + +{if isset($resolvableOverBookings) && $resolvableOverBookings} +
    +
    + {l s='Resolvable Overbookings'} +
    +
    + {if isset($smarty.get.resolvable_overbooked_orders) && $smarty.get.resolvable_overbooked_orders} +
    + {l s='Orders with resolvable overbookings are filtered.'} {l s='See all orders'} +
    + {/if} +
    +

    {l s='Some overbookings are now available to be resolved. You can directly resolve overbookings from below list as per your choice.'}

    + {if !isset($smarty.get.resolvable_overbooked_orders) || !$smarty.get.resolvable_overbooked_orders} +
    +

    {l s='You can also filter orders which overbookings are now available to be resolved.'} {l s='Filter orders with resolvable overbookings'}

    + {/if} +
    +
    + + + + + + + + + + {foreach from=$resolvableOverBookings item=data} + {if !$data.is_refunded} + + + + + + + + + {/if} + {/foreach} +
    {l s='Room No.'}{l s='Room type'}{l s='Hotel'}{l s='Duration'}{l s='Order'}{l s='Resolve'}
    {$data['room_num']} + {$data['room_type_name']|escape:'html':'UTF-8'} + + {$data['hotel_name']} + {dateFormat date=$data['date_from']|escape:'html':'UTF-8'} {l s='To'} {dateFormat date=$data['date_to']|escape:'html':'UTF-8'} + #{$data['id_order']|escape:'html':'UTF-8'} + + {if isset($data['booked_room_info']) && $data['booked_room_info']} + {l s='Already booked'} + {else} + {l s='Resolve'} + {/if} +
    +
    +
    +
    + +{/if} diff --git a/admin/themes/default/template/controllers/orders/_room_facilities_block.tpl b/admin/themes/default/template/controllers/orders/_room_facilities_block.tpl index 001ab6c69..aa2aeb619 100644 --- a/admin/themes/default/template/controllers/orders/_room_facilities_block.tpl +++ b/admin/themes/default/template/controllers/orders/_room_facilities_block.tpl @@ -40,13 +40,16 @@ {if isset($extraDemands) && $extraDemands} {foreach $extraDemands as $roomDemand} {foreach $roomDemand['extra_demands'] as $demand} - + {$demand['name']} - {displayPrice price=$demand['unit_price_tax_excl'] currency=$orderCurrency} - {if $demand['price_calc_method'] == HotelRoomTypeGlobalDemand::WK_PRICE_CALC_METHOD_EACH_DAY} - {l s='/ night'} - {/if} +
    + {$currencySign} + + {if $demand['price_calc_method'] == HotelRoomTypeGlobalDemand::WK_PRICE_CALC_METHOD_EACH_DAY} + {l s='/ night'} + {/if} +
    {displayPrice price=$demand['total_price_tax_excl'] currency=$orderCurrency} @@ -74,7 +77,7 @@ {l s='Name'} {l s='Option'} - {l s='Price'} + {l s='Unit Price'} @@ -103,12 +106,21 @@ {/if} - - {if isset($selected_adv_option) && isset($demand['adv_option'][$selected_adv_option]['price_tax_excl'])}{convertPrice price = $demand['adv_option'][$selected_adv_option]['price_tax_excl']|escape:'html':'UTF-8'}{else if isset($demand['adv_option']) && $demand['adv_option']}{convertPrice price = $demand['adv_option'][$demand['adv_option']|@key]['price_tax_excl']}{else}{convertPrice price = $demand['price_tax_excl']|escape:'html':'UTF-8'}{/if} - - {if $demand['price_calc_method'] == HotelRoomTypeGlobalDemand::WK_PRICE_CALC_METHOD_EACH_DAY} - {l s='/ night'} - {/if} + +
    + {$currencySign} + {if isset($selected_adv_option) && isset($demand['adv_option'][$selected_adv_option]['price_tax_excl'])} + {assign var=demand_price value=$demand['adv_option'][$selected_adv_option]['price_tax_excl']} + {else if isset($demand['adv_option']) && $demand['adv_option']} + {assign var=demand_price value=$demand['adv_option'][$demand['adv_option']|@key]['price_tax_excl']} + {else} + {assign var=demand_price value=$demand['price_tax_excl']} + {/if} + + {if $demand['price_calc_method'] == HotelRoomTypeGlobalDemand::WK_PRICE_CALC_METHOD_EACH_DAY} + {l s='/ night'} + {/if} +
    {/foreach} diff --git a/admin/themes/default/template/controllers/orders/_room_services_block.tpl b/admin/themes/default/template/controllers/orders/_room_services_block.tpl index d096a0125..aefc09648 100644 --- a/admin/themes/default/template/controllers/orders/_room_services_block.tpl +++ b/admin/themes/default/template/controllers/orders/_room_services_block.tpl @@ -31,8 +31,8 @@ {l s='Name'} - - {l s='Quantity'} + + {l s='Quantity'} {l s='Unit Price'} {l s='Total Price'} {l s='Action'} @@ -41,7 +41,7 @@ {if isset($additionalServices) && $additionalServices} {foreach $additionalServices['additional_services'] as $service} - +
    {$service['name']|escape:'html':'UTF-8'}
    @@ -53,10 +53,10 @@ {l s='Convenience fee'} {/if} - + {if $service['allow_multiple_quantity']}
    - +

    {l s='Maximum allowed quantity: %s' sprintf=$service['max_quantity']}

    @@ -66,10 +66,16 @@ {/if} - {displayPrice price=$service['unit_price_tax_excl'] currency=$orderCurrency} - {if $service['product_price_calculation_method'] == Product::PRICE_CALCULATION_METHOD_PER_DAY} +
    + {$currencySign} + + {if Product::PRICE_CALCULATION_METHOD_PER_DAY == $service.product_price_calculation_method} + {l s='/ night'} + {/if} +
    + {* {if $service['product_price_calculation_method'] == Product::PRICE_CALCULATION_METHOD_PER_DAY} {l s='/ night'} - {/if} + {/if} *} {displayPrice price=$service['total_price_tax_excl']|escape:'html':'UTF-8' currency=$orderCurrency} @@ -93,9 +99,9 @@ {l s='Name'} - - {l s='Quantity'} - {l s='Price'} + + {l s='Quantity'} + {l s='Unit Price'} @@ -125,10 +131,13 @@ {/if} - {convertPrice price=$product['price_tax_exc']} - {if $product['price_calculation_method'] == Product::PRICE_CALCULATION_METHOD_PER_DAY} - {l s='/ night'} - {/if} +
    + {$currencySign} + + {if Product::PRICE_CALCULATION_METHOD_PER_DAY == $product['price_calculation_method']} + {l s='/ night'} + {/if} +
    {/foreach} diff --git a/admin/themes/default/template/controllers/orders/helpers/view/view.tpl b/admin/themes/default/template/controllers/orders/helpers/view/view.tpl index 480b84455..d72232f78 100644 --- a/admin/themes/default/template/controllers/orders/helpers/view/view.tpl +++ b/admin/themes/default/template/controllers/orders/helpers/view/view.tpl @@ -68,43 +68,9 @@
    {$hook_invoice}
    {/if} -
    -
    -
    -
    - - {l s='Date'} - {dateFormat date=$order->date_add full=false} -
    -
    -
    -
    - - {l s='Total'} - {displayPrice price=$order->total_paid_tax_incl currency=$currency->id} -
    -
    -
    -
    - - {l s='Messages'} - {sizeof($customer_thread_message)} -
    -
    - -
    -
    + {* Overbookings information of the order *} + {include file='controllers/orders/_overbookings.tpl'} +
    @@ -1830,11 +1796,11 @@ }); $(document).on('focusout', '#rooms_type_extra_demands .room_ordered_services .qty', function(e) { - var qty_wntd = $(this).val(); - if (qty_wntd == '' || !$.isNumeric(qty_wntd) || qty_wntd < 1) { - $(this).val(1); - } - updateAdditionalServices($(this)); + updateAdditionalServices($(this).closest('tr')); + }); + + $(document).on('focusout', '#rooms_type_extra_demands .room_ordered_services .unit_price', function(e) { + updateAdditionalServices($(this).closest('tr')); }); $(document).on('focusout', '#rooms_type_extra_demands #add_room_services_form .qty', function(e) { @@ -1844,6 +1810,52 @@ } }); + function updateAdditionalServices(element) + { + var id_room_type_service_product_order_detail = $(element).data('id_room_type_service_product_order_detail'); + if ($(element).find('.qty').length) { + var qty = $(element).find('.qty').val(); + if (qty == '' || !$.isNumeric(qty) || qty < 1) { + $(element).find('.qty').val(1); + qty = 1; + } + } else { + var qty = 1; + } + + var unit_price = $(element).find('.unit_price').val(); + if ($.isNumeric(qty)) { + $.ajax({ + type: 'POST', + headers: { + "cache-control": "no-cache" + }, + url: "{$link->getAdminLink('AdminOrders')|addslashes}", + dataType: 'JSON', + cache: false, + data: { + id_room_type_service_product_order_detail: id_room_type_service_product_order_detail, + qty: qty, + unit_price: unit_price, + action: 'updateRoomAdditionalServices', + ajax: true + }, + success: function(jsonData) { + if (!jsonData.hasError) { + if (jsonData.service_panel) { + $('#room_type_service_product_desc').replaceWith(jsonData.service_panel); + } + showSuccessMessage(txtExtraDemandSucc); + } else { + showErrorMessage(jsonData.errors); + + } + } + }); + } + + } + $(document).on('submit', '#add_room_services_form', function(e) { e.preventDefault(); var form_data = new FormData(this); @@ -1885,7 +1897,8 @@ $(this).closest('#room_extra_demand_content').find('input:checkbox.id_room_type_demand:checked').each(function () { roomDemands.push({ 'id_global_demand':$(this).val(), - 'id_option': $(this).closest('.room_demand_block').find('.id_option').val() + 'id_option': $(this).closest('.room_demand_block').find('.id_option').val(), + 'unit_price': $(this).closest('.room_demand_block').find('.unit_price').val() }); }); @@ -1901,15 +1914,17 @@ data: { id_htl_booking: idHtlBooking, room_demands: JSON.stringify(roomDemands), - action: 'EditRoomExtraDemands', + action: 'addRoomExtraDemands', ajax: true }, success: function(jsonData) { - if (jsonData.success) { + if (!jsonData.hasError) { showSuccessMessage(txtExtraDemandSucc); if (jsonData.facilities_panel) { $('#room_type_demands_desc').replaceWith(jsonData.facilities_panel); } + } else if (jsonData.errors) { + showErrorMessage(jsonData.errors); } else { showErrorMessage(txtSomeErr); } @@ -1921,6 +1936,44 @@ } }); + {* edit room extra demand*} + $(document).on('focusout', '#rooms_type_extra_demands .room_ordered_demands .unit_price', function(e) { + updateRoomDemand($(this).closest('tr')); + }); + + function updateRoomDemand(element) + { + var id_booking_demand = $(element).data('id_booking_demand'); + var unit_price = $(element).find('.unit_price').val(); + $.ajax({ + type: 'POST', + headers: { + "cache-control": "no-cache" + }, + url: "{$link->getAdminLink('AdminOrders')|addslashes}", + dataType: 'JSON', + cache: false, + data: { + id_booking_demand: id_booking_demand, + unit_price: unit_price, + action: 'updateRoomExtraDemands', + ajax: true + }, + success: function(jsonData) { + if (!jsonData.hasError) { + if (jsonData.facilities_panel) { + $('#room_type_demands_desc').replaceWith(jsonData.facilities_panel); + } + showSuccessMessage(txtExtraDemandSucc); + } else { + showErrorMessage(jsonData.errors); + + } + } + }); + + } + {* Delete ordered room booking demand *} $(document).on('click', '.del-order-room-demand', function(e) { e.preventDefault(); @@ -2001,8 +2054,8 @@ var option_selected = $(this).find('option:selected'); var extra_demand_price = option_selected.attr("optionPrice") extra_demand_price = parseFloat(extra_demand_price); - extra_demand_price = formatCurrency(extra_demand_price, currency_format, currency_sign, currency_blank); - $(this).closest('.room_demand_block').find('.extra_demand_option_price').text(extra_demand_price); + // extra_demand_price = formatCurrency(extra_demand_price, currency_format, currency_sign, currency_blank); + $(this).closest('.room_demand_block').find('.unit_price').val(extra_demand_price); }); $('#mySwappigModal').on('hidden.bs.modal', function (e) diff --git a/admin/themes/default/template/controllers/products/additionalfacilities.tpl b/admin/themes/default/template/controllers/products/additionalfacilities.tpl index 1296aa87e..c3a58eb34 100644 --- a/admin/themes/default/template/controllers/products/additionalfacilities.tpl +++ b/admin/themes/default/template/controllers/products/additionalfacilities.tpl @@ -24,7 +24,7 @@ {l s='Room Type Additional Facilities'}
    - {l s='To create new Additional facilities'} {l s='click here.'} + {l s='To create new additional facilities please visit'} {l s='Additional facilities'} {l s='page.'}
    {if isset($allDemands) && $allDemands} @@ -148,10 +148,6 @@ {l s='Save and stay'}
    - {else} -
    - {l s='No additional facilities created yet. To create please visit'} {l s='Additional facilities'} {l s='page'}. -
    {/if}
    {/if} \ No newline at end of file diff --git a/admin/themes/default/template/helpers/kpi/kpi.tpl b/admin/themes/default/template/helpers/kpi/kpi.tpl index e29ae28c4..4134acc2f 100644 --- a/admin/themes/default/template/helpers/kpi/kpi.tpl +++ b/admin/themes/default/template/helpers/kpi/kpi.tpl @@ -23,23 +23,39 @@ * International Registered Trademark & Property of PrestaShop SA *} -<{if isset($href) && $href}a style="display:block" href="{$href|escape:'html':'UTF-8'}"{else}div{/if} id="{$id|escape:'html':'UTF-8'}" data-toggle="tooltip" class="box-stats label-tooltip {$color|escape}" data-original-title="{$tooltip|escape}"> +<{if isset($href) && $href}a style="display:block" href="{$href|escape:'html':'UTF-8'}"{else}div{/if} id="{$id|escape:'html':'UTF-8'}" data-toggle="tooltip" class="box-stats label-tooltip {$color|escape}" data-original-title="{$tooltip|escape}" {if $target}target="_blank"{/if}>
    - {if isset($icon) && $icon} - - {/if} - {$title|escape} - {$subtitle|escape} +
    +
    + + {if isset($icon) && $icon}{/if} + {$title|escape} + +
    + {if $subtitle} +
    + + {$subtitle|escape} + +
    + {/if} +
    {if isset($chart) && $chart}
    {/if} - {if isset($source) && $source} - - {elseif isset($value) && $value != ''} - {$value|escape:'html':'UTF-8'} +
    + {if isset($source) && $source} + + {elseif isset($value) && $value !== ''} + {$value|escape:'html':'UTF-8'} + {/if} +
    + + {if isset($href) && $href} + {/if}
    diff --git a/admin/themes/default/template/helpers/kpi/row.tpl b/admin/themes/default/template/helpers/kpi/row.tpl index fc481cbd3..50090b691 100644 --- a/admin/themes/default/template/helpers/kpi/row.tpl +++ b/admin/themes/default/template/helpers/kpi/row.tpl @@ -22,20 +22,42 @@ * @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) * International Registered Trademark & Property of PrestaShop SA *} -
    - {if $refresh} -
    - {/if} - {if ($kpis|count) > 0} -
    - {assign var='col' value=(int)(12 / $kpis|count)} - {foreach from=$kpis item=i name=kpi} - {if $smarty.foreach.kpi.iteration > $col+1} -
    -
    - {/if} -
    {$i}
    + +
    +
    + {foreach from=$kpis item=kpi} +
    visible) && !$kpi->visible}style="display: none;"{/if}> + {$kpi->generate()} +
    {/foreach}
    - {/if} + +
    + {if $refresh} + + {/if} + + + + +
    diff --git a/admin/themes/default/template/helpers/modules_list/list.tpl b/admin/themes/default/template/helpers/modules_list/list.tpl index de8129f0a..29f885864 100644 --- a/admin/themes/default/template/helpers/modules_list/list.tpl +++ b/admin/themes/default/template/helpers/modules_list/list.tpl @@ -54,7 +54,7 @@
    {if $controller_name == 'AdminPayment'} {l s='It seems there are no recommended payment solutions for your country.'}
    - {l s='Do you think there should be one? Let us know!'} + {l s='Do you think there should be one? Let us know!'} {else}{l s='No modules available in this section.'}{/if}
    diff --git a/classes/PaymentModule.php b/classes/PaymentModule.php index 298e12914..d7e75b7d2 100644 --- a/classes/PaymentModule.php +++ b/classes/PaymentModule.php @@ -761,15 +761,16 @@ abstract class PaymentModuleCore extends Module // update order in htl tables $objRoomType = new HotelRoomType(); - $objAdvancedPayment = new HotelAdvancedPayment(); - + $objCartBookingData = new HotelCartBookingData(); + $objBookingDetail = new HotelBookingDetail(); $vatAddress = new Address((int)$order->id_address_tax); $idLang = (int)$this->context->cart->id_lang; $normalProducts = array(); + // variable to check max overbookings are created in the room in any date of the booking duration + $maxOverbookingCount = 0; foreach ($order->product_list as $product) { - $objCartBookingData = new HotelCartBookingData(); $idProduct = $product['id_product']; $cartBookingData = $objCartBookingData->getOnlyCartBookingData($this->context->cart->id, $this->context->cart->id_guest, $idProduct); if ($cartBookingData) { @@ -777,8 +778,67 @@ abstract class PaymentModuleCore extends Module $objCartBookingData = new HotelCartBookingData($bookingInfo['id']); $objCartBookingData->id_order = $order->id; $objCartBookingData->id_customer = $this->context->customer->id; + + // check if still room available for this booking or not + $isRoomOverBooked = 1; + + // get all the available rooms right now + $bookingParams = array( + 'date_from' => $objCartBookingData->date_from, + 'date_to' => $objCartBookingData->date_to, + 'hotel_id' => $objCartBookingData->id_hotel, + 'id_room_type' => $idProduct, + 'only_search_data' => 1 + ); + + if ($searchRoomsInfo = $objBookingDetail->getBookingData($bookingParams)) { + if (isset($searchRoomsInfo['rm_data'][$idProduct]['data']['available']) + && $searchRoomsInfo['rm_data'][$idProduct]['data']['available'] + ) { + $availableRoomsInfo = $searchRoomsInfo['rm_data'][$idProduct]['data']['available']; + if ($roomIdsAvailable = array_column($availableRoomsInfo, 'id_room')) { + // If room is still there in the available rooms list then it is not in back order + if (in_array($objCartBookingData->id_room, $roomIdsAvailable)) { + $isRoomOverBooked = 0; + } else { + $isRoomOverBooked = 1; + foreach ($roomIdsAvailable as $idRoom) { + if ($idRoom != $objCartBookingData->id_room) { + $objCartBookingData->id_room = $idRoom; + $isRoomOverBooked = 0; + break; + } + } + } + } + } + } + + // if room is not available right now the set it to back order + if ($isRoomOverBooked) { + $objCartBookingData->is_back_order = 1; + + // max overbookings in a room datewise breakup. So that we can know no. of overbookings in every date + if ($roomOverBookings = $objBookingDetail->getOverbookedRooms( + 0, + $objCartBookingData->id_hotel, + $objCartBookingData->date_from, + $objCartBookingData->date_to, + 0, + 1 + )) { + // check in the bokking duration what is the max overbookings in ay date in the date range + // replace $maxOverbookingCount with $maxOverbookingsInDate if it is greater + if ($maxOverbookingCount < $roomOverBookings) { + $maxOverbookingCount = $roomOverBookings; + } + } + } else { + $objCartBookingData->is_back_order = 0; + } $objCartBookingData->save(); + // Lets set data to the hotel booking detail table $objBookingDetail = new HotelBookingDetail(); $id_order_detail = $objBookingDetail->getPsOrderDetailIdByIdProduct($idProduct, $order->id); $objBookingDetail->id_product = $idProduct; @@ -790,12 +850,9 @@ abstract class PaymentModuleCore extends Module $objBookingDetail->id_customer = $this->context->customer->id; $objBookingDetail->booking_type = $objCartBookingData->booking_type; $objBookingDetail->id_status = 1; + $objBookingDetail->is_back_order = $objCartBookingData->is_back_order; $objBookingDetail->comment = $objCartBookingData->comment; - // For Back Order(Because of cart lock) - if ($objCartBookingData->is_back_order) { - $objBookingDetail->is_back_order = 1; - } $total_price = HotelRoomTypeFeaturePricing::getRoomTypeTotalPrice( $idProduct, $objCartBookingData->date_from, @@ -999,10 +1056,39 @@ abstract class PaymentModuleCore extends Module $new_history->addWithemail(true, $extra_vars); // Switch to back order if needed - if (Configuration::get('PS_STOCK_MANAGEMENT') && ($order_detail->getStockState() || $order_detail->product_quantity_in_stock <= 0)) { + $objHotelBookingDetail = new HotelBookingDetail(); + if ($objHotelBookingDetail->getOverbookedRooms($order->id)) { + $cancelOrder = 0; + // Take overbooking action as per order->overbooking Preferences else set order for cancellation + if (Configuration::get('PS_OVERBOOKING_ORDER_ACTION') == Order::OVERBOOKING_ORDER_CANCEL_ACTION) { + $cancelOrder = 1; + } else { + // check conditions for maximun overbookings allowed for the hotel else set order for cancellation + $maxAllowedOverbookings = Configuration::get('PS_MAX_OVERBOOKING_PER_HOTEL_PER_DAY'); + // >= condition applied because equal case means already overbooked rooms limit exceeded before this booking + if ($maxAllowedOverbookings && ($maxOverbookingCount >= $maxAllowedOverbookings)) { + $cancelOrder = 1; + } + } + + // set order status after all conditions checks + if ($cancelOrder) { + $id_order_state = Configuration::get('PS_OS_CANCELED'); + } else { + if ($order->valid) { + if ($order->is_advance_payment && $order->advance_paid_amount < $order->total_paid_tax_incl) { + $id_order_state = Configuration::get('PS_OS_OVERBOOKING_PARTIAL_PAID'); + } else { + $id_order_state = Configuration::get('PS_OS_OVERBOOKING_PAID'); + } + } else { + $id_order_state = Configuration::get('PS_OS_OVERBOOKING_UNPAID'); + } + } + $history = new OrderHistory(); $history->id_order = (int)$order->id; - $history->changeIdOrderState(Configuration::get($order->valid ? 'PS_OS_OUTOFSTOCK_PAID' : 'PS_OS_OUTOFSTOCK_UNPAID'), $order, true); + $history->changeIdOrderState($id_order_state, $order, true); $history->addWithemail(); } @@ -1148,8 +1234,21 @@ abstract class PaymentModuleCore extends Module PrestaShopLogger::addLog('PaymentModule::validateOrder - Mail is about to be sent', 1, null, 'Cart', (int)$id_cart, true); } - // Send order confirmation mails to the reciepients according to the order mail configuration + $orderStatusObj = new OrderState((int)$id_order_state, (int)$this->context->language->id); + $data['{order_status}'] = $orderStatusObj->name; + $data['{order_status_color}'] = $orderStatusObj->color; + // Send order confirmation/overbooking mails to the reciepients according to the order mail configuration + $overBookingStates = OrderState::getOverBookingStates(); + $isOverBookingStatus = in_array($id_order_state, $overBookingStates); if (Configuration::get('PS_ORDER_CONF_MAIL_TO_CUSTOMER')){ + // If order currenct state is overbooking, the send overbooking email or send order confirmation email + if ($isOverBookingStatus) { + $subject = Mail::l('Order Not Confirmed', (int)$order->id_lang); + $template = 'overbooking'; + } else { + $subject = Mail::l('Order confirmation', (int)$order->id_lang); + $template = 'order_conf'; + } if (Validate::isEmail($this->context->customer->email)) { // send customer information $data['{firstname}'] = $this->context->customer->firstname; @@ -1157,8 +1256,8 @@ abstract class PaymentModuleCore extends Module $data['{email}'] = $this->context->customer->email; Mail::Send( (int)$order->id_lang, - 'order_conf', - Mail::l('Order confirmation', (int)$order->id_lang), + $template, + $subject, $data, $this->context->customer->email, $this->context->customer->firstname.' '.$this->context->customer->lastname, @@ -1179,8 +1278,8 @@ abstract class PaymentModuleCore extends Module $data['{email}'] = $objOrderCustomerGuestDetail->email; Mail::Send( (int)$order->id_lang, - 'order_conf', - Mail::l('Order confirmation', (int)$order->id_lang), + $template, + $subject, $data, $objOrderCustomerGuestDetail->email, $objOrderCustomerGuestDetail->firstname.' '.$objOrderCustomerGuestDetail->lastname, @@ -1194,8 +1293,17 @@ abstract class PaymentModuleCore extends Module } } if (Configuration::get('PS_ORDER_CONF_MAIL_TO_SUPERADMIN')){ - // send superadmin information + // get superadmin employees if ($superAdminEmployees = Employee::getEmployeesByProfile(_PS_ADMIN_PROFILE_, true)) { + // If order currenct state is overbooking, the send overbooking email or send order confirmation email + if ($isOverBookingStatus) { + $subject = Mail::l('Order Not Confirmed', (int)$order->id_lang); + $template = 'overbooking_admin'; + } else { + $subject = Mail::l('Order confirmation', (int)$order->id_lang); + $template = 'order_conf_admin'; + } + foreach ($superAdminEmployees as $superAdminEmployee) { if (Validate::isEmail($superAdminEmployee['email'])) { $data['{customer_name}'] = $this->context->customer->firstname.' '.$this->context->customer->lastname; @@ -1205,8 +1313,8 @@ abstract class PaymentModuleCore extends Module $data['{email}'] = $superAdminEmployee['email']; Mail::Send( (int)$order->id_lang, - 'order_conf_admin', - Mail::l('Order confirmation', (int)$order->id_lang), + $template, + $subject, $data, $superAdminEmployee['email'], $superAdminEmployee['firstname'].' '.$superAdminEmployee['lastname'], @@ -1223,6 +1331,14 @@ abstract class PaymentModuleCore extends Module && Validate::isLoadedObject($objHotel = new HotelBranchInformation($idHotel)) ) { if (Configuration::get('PS_ORDER_CONF_MAIL_TO_HOTEL_MANAGER')){ + // If order currenct state is overbooking, the send overbooking email or send order confirmation email + if ($isOverBookingStatus) { + $subject = Mail::l('Order Not Confirmed', (int)$order->id_lang); + $template = 'overbooking_admin'; + } else { + $subject = Mail::l('Order confirmation', (int)$order->id_lang); + $template = 'order_conf_admin'; + } // send hotel information $data['{firstname}'] = ''; $data['{lastname}'] = ''; @@ -1232,8 +1348,8 @@ abstract class PaymentModuleCore extends Module if (Validate::isEmail($objHotel->email)) { Mail::Send( (int)$order->id_lang, - 'order_conf_admin', - Mail::l('Order confirmation', (int)$order->id_lang), + $template, + $subject, $data, $objHotel->email, null, @@ -1248,6 +1364,16 @@ abstract class PaymentModuleCore extends Module if ($htlAccesses = $objHotel->getHotelAccess($idHotel)) { $data['{customer_name}'] = $this->context->customer->firstname.' '.$this->context->customer->lastname; $data['{customer_email}'] = $this->context->customer->email; + + // If order currenct state is overbooking, the send overbooking email or send order confirmation email + if ($isOverBookingStatus) { + $subject = Mail::l('Order Not Confirmed', (int)$order->id_lang); + $template = 'overbooking_admin'; + } else { + $subject = Mail::l('Order confirmation', (int)$order->id_lang); + $template = 'order_conf_admin'; + } + foreach ($htlAccesses as $access) { if ($access['access'] && $access['id_profile'] != _PS_ADMIN_PROFILE_) { if ($htlEmployees = Employee::getEmployeesByProfile($access['id_profile'])) { @@ -1259,8 +1385,8 @@ abstract class PaymentModuleCore extends Module $data['{email}'] = $empl['email']; Mail::Send( (int)$order->id_lang, - 'order_conf_admin', - Mail::l('Order confirmation', (int)$order->id_lang), + $template, + $subject, $data, $empl['email'], $empl['firstname'].' '.$empl['lastname'], @@ -1278,7 +1404,6 @@ abstract class PaymentModuleCore extends Module } } } - // updates stock in shops if (Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT')) { $product_list = $order->getProducts(); diff --git a/classes/controller/AdminController.php b/classes/controller/AdminController.php index b4a1d9241..9ee0ef05e 100644 --- a/classes/controller/AdminController.php +++ b/classes/controller/AdminController.php @@ -760,7 +760,7 @@ class AdminControllerCore extends Controller } if (count($filters)) { - return sprintf($this->l('filter by %s'), implode(', ', $filters)); + return sprintf($this->l('Filter by %s'), implode(', ', $filters)); } } } @@ -990,6 +990,18 @@ class AdminControllerCore extends Controller return true; } + protected function ajaxProcessChangeKpiVisibility() + { + $cookieKey = 'kpi_visibility_'.$this->context->controller->className.'_'.Tools::getValue('kpi_id'); + $this->context->cookie->$cookieKey = (int) Tools::getValue('is_visible'); + } + + protected function ajaxProcessSaveKpiView() + { + $cookieKey = 'kpi_wrapping_'.$this->context->controller->className; + $this->context->cookie->$cookieKey = (int) Tools::getValue('no_wrapping'); + } + /** * @TODO uses redirectAdmin only if !$this->ajax * @return bool @@ -2232,7 +2244,7 @@ class AdminControllerCore extends Controller ) { $this->page_header_toolbar_btn['modules-list'] = array( 'href' => '#', - 'desc' => $this->l('Recommended Modules and Services') + 'desc' => $this->l('Recommendations') ); } } diff --git a/classes/controller/FrontController.php b/classes/controller/FrontController.php index 21aa82c4d..6378c8570 100644 --- a/classes/controller/FrontController.php +++ b/classes/controller/FrontController.php @@ -1135,9 +1135,7 @@ class FrontControllerCore extends Controller */ public function initHeader() { - /** @see P3P Policies (http://www.w3.org/TR/2002/REC-P3P-20020416/#compact_policies) */ - header('P3P: CP="IDC DSP COR CURa ADMa OUR IND PHY ONL COM STA"'); - header('Powered-By: PrestaShop'); + header('Powered-By: QloApps'); // Hooks are voluntary out the initialize array (need those variables already assigned) $this->context->smarty->assign(array( diff --git a/classes/helper/HelperKpi.php b/classes/helper/HelperKpi.php index a1b54a474..179ac3045 100644 --- a/classes/helper/HelperKpi.php +++ b/classes/helper/HelperKpi.php @@ -39,7 +39,9 @@ class HelperKpiCore extends Helper public $data; public $source; public $href; + public $target; public $tooltip; + public $visible; public function generate() { @@ -56,7 +58,9 @@ class HelperKpiCore extends Helper 'data' => $this->data, 'source' => $this->source, 'href' => $this->href, - 'tooltip' => $this->tooltip + 'target' => $this->target, + 'tooltip' => $this->tooltip, + 'visible' => $this->visible, )); return $this->tpl->fetch(); diff --git a/classes/helper/HelperKpiRow.php b/classes/helper/HelperKpiRow.php index 7f1d4e594..f47022bf7 100644 --- a/classes/helper/HelperKpiRow.php +++ b/classes/helper/HelperKpiRow.php @@ -36,8 +36,26 @@ class HelperKpiRowCore extends Helper { $this->tpl = $this->createTemplate($this->base_tpl); + // set visiblity for each KPI + $countVisible = 0; + $cookieKeyPrefix = 'kpi_visibility_'.$this->context->controller->className.'_'; + foreach ($this->kpis as &$kpi) { + $cookieKey = $cookieKeyPrefix.$kpi->id; + if (isset($this->context->cookie->$cookieKey)) { + $kpi->visible = (bool) $this->context->cookie->$cookieKey; + } else { + $kpi->visible = true; + } + + $countVisible = $kpi->visible ? ++$countVisible : $countVisible; + } + + $cookieKeyView = 'kpi_wrapping_'.$this->context->controller->className; + $this->tpl->assign('kpis', $this->kpis); $this->tpl->assign('refresh', $this->refresh); + $this->tpl->assign('no_wrapping', (int) $this->context->cookie->$cookieKeyView); + $this->tpl->assign('count_visible', $countVisible); return $this->tpl->fetch(); } diff --git a/classes/order/Order.php b/classes/order/Order.php index bf9a33801..a4e7a5e6b 100644 --- a/classes/order/Order.php +++ b/classes/order/Order.php @@ -34,6 +34,10 @@ class OrderCore extends ObjectModel const ORDER_PAYMENT_TYPE_FULL = 1; const ORDER_PAYMENT_TYPE_ADVANCE = 2; + // actions when overbooking will be created + const OVERBOOKING_ORDER_CANCEL_ACTION = 1; + const OVERBOOKING_ORDER_NO_ACTION = 2; + //Consts for: In the order list in which currency prices should be displayed const ORDER_LIST_PRICE_DISPLAY_IN_PAYMENT_CURRENCY = 1; const ORDER_LIST_PRICE_DISPLAY_IN_DEFAULT_CURRENCY = 2; diff --git a/classes/order/OrderState.php b/classes/order/OrderState.php index 22e3d8db4..cd14adbf3 100644 --- a/classes/order/OrderState.php +++ b/classes/order/OrderState.php @@ -155,4 +155,13 @@ class OrderStateCore extends ObjectModel { return !($this->unremovable); } + + public static function getOverBookingStates() + { + return array( + Configuration::get('PS_OS_OVERBOOKING_PARTIAL_PAID'), + Configuration::get('PS_OS_OVERBOOKING_PAID'), + Configuration::get('PS_OS_OVERBOOKING_UNPAID'), + ); + } } diff --git a/controllers/admin/AdminCartsController.php b/controllers/admin/AdminCartsController.php index 129c0b9ee..dde43445b 100644 --- a/controllers/admin/AdminCartsController.php +++ b/controllers/admin/AdminCartsController.php @@ -172,7 +172,7 @@ class AdminCartsControllerCore extends AdminController $helper->data = ConfigurationKPI::get('CONVERSION_RATE_CHART'); } $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=conversion_rate'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-carts'; @@ -184,7 +184,7 @@ class AdminCartsControllerCore extends AdminController $helper->subtitle = sprintf($this->l('From %s to %s', null, null, false), $date_from, $date_to); $helper->href = $this->context->link->getAdminLink('AdminCarts').'&action=filterOnlyAbandonedCarts'; $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=abandoned_cart'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-average-order'; @@ -193,7 +193,7 @@ class AdminCartsControllerCore extends AdminController $helper->title = $this->l('Average Order Value', null, null, false); $helper->subtitle = $this->l('30 days', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=average_order_value'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-net-profit-visitor'; @@ -202,7 +202,11 @@ class AdminCartsControllerCore extends AdminController $helper->title = $this->l('Net Profit per Visitor', null, null, false); $helper->subtitle = $this->l('30 days', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=netprofit_visit'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; + + Hook::exec('action'.$this->controller_name.'KPIListingModifier', array( + 'kpis' => &$kpis, + )); $helper = new HelperKpiRow(); $helper->kpis = $kpis; diff --git a/controllers/admin/AdminCustomerPreferencesController.php b/controllers/admin/AdminCustomerPreferencesController.php index 9acd1961b..197e0b7e9 100644 --- a/controllers/admin/AdminCustomerPreferencesController.php +++ b/controllers/admin/AdminCustomerPreferencesController.php @@ -90,7 +90,8 @@ class AdminCustomerPreferencesControllerCore extends AdminController 'cast' => 'intval', 'size' => 5, 'type' => 'text', - 'suffix' => $this->l('minutes') + 'suffix' => $this->l('minutes'), + 'class' => 'fixed-width-xl', ), 'PS_B2B_ENABLE' => array( 'title' => $this->l('Enable B2B mode'), @@ -120,6 +121,48 @@ class AdminCustomerPreferencesControllerCore extends AdminController 'cast' => 'intval', 'type' => 'bool' ), + 'PS_CUSTOMER_ADDRESS_CREATION' => array( + 'title' => $this->l('Address creation during checkout'), + 'hint' => $this->l('If disabled, Address form will not appear to user during checkout process. So by default hotel address, which rooms guest has in his cart will be considered as user address. Else user has to fill adress details.'), + 'validation' => 'isBool', + 'cast' => 'intval', + 'type' => 'bool' + ), + 'PS_KPI_FREQUENT_CUSTOMER_NB_ORDERS' => array( + 'title' => $this->l('Number of orders to use to calculate frequent customers'), + 'hint' => $this->l('Set the number of orders to use to calculate frequent customers.'), + 'validation' => 'isUnsignedInt', + 'cast' => 'intval', + 'type' => 'text', + 'class' => 'fixed-width-xxl', + ), + 'PS_KPI_REVPAC_NB_DAYS' => array( + 'title' => $this->l('Number of days to use to calculate RevPAC'), + 'hint' => $this->l('Set the number of days to use to calculate Revenue Per Available Customer.'), + 'validation' => 'isUnsignedInt', + 'cast' => 'intval', + 'type' => 'text', + 'suffix' => $this->l('days'), + 'class' => 'fixed-width-xl', + ), + 'PS_KPI_CONVERSION_RATE_NB_DAYS' => array( + 'title' => $this->l('Number of days to use to calculate Conversion Rate'), + 'hint' => $this->l('Set the number of days to use to calculate Conversion Rate.'), + 'validation' => 'isUnsignedInt', + 'cast' => 'intval', + 'type' => 'text', + 'suffix' => $this->l('days'), + 'class' => 'fixed-width-xl', + ), + 'PS_KPI_NEW_CUSTOMERS_NB_DAYS' => array( + 'title' => $this->l('Number of days for which a customer is considered \'new\''), + 'hint' => $this->l('Set the number of days for which a customer is considered \'new\' after creation date.'), + 'validation' => 'isUnsignedInt', + 'cast' => 'intval', + 'type' => 'text', + 'suffix' => $this->l('days'), + 'class' => 'fixed-width-xl', + ), ), 'submit' => array('title' => $this->l('Save')), ), diff --git a/controllers/admin/AdminCustomerThreadsController.php b/controllers/admin/AdminCustomerThreadsController.php index cd692602e..c7a52fc5e 100644 --- a/controllers/admin/AdminCustomerThreadsController.php +++ b/controllers/admin/AdminCustomerThreadsController.php @@ -524,7 +524,7 @@ class AdminCustomerThreadsControllerCore extends AdminController $helper->color = 'color1'; $helper->title = $this->l('Pending Discussion Threads', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=pending_messages'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-age'; @@ -533,7 +533,7 @@ class AdminCustomerThreadsControllerCore extends AdminController $helper->title = $this->l('Average Response Time', null, null, false); $helper->subtitle = $this->l('30 days', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=avg_msg_response_time'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-messages-per-thread'; @@ -542,7 +542,11 @@ class AdminCustomerThreadsControllerCore extends AdminController $helper->title = $this->l('Messages per Thread', null, null, false); $helper->subtitle = $this->l('30 day', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=messages_per_thread'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; + + Hook::exec('action'.$this->controller_name.'KPIListingModifier', array( + 'kpis' => &$kpis, + )); $helper = new HelperKpiRow(); $helper->kpis = $kpis; @@ -721,7 +725,7 @@ class AdminCustomerThreadsControllerCore extends AdminController $content .= $this->l('Message to: ').' '.(!$message['id_employee'] ? $message['subject'] : $message['customer_name']).'
    '; } if (Validate::isLoadedObject($product)) { - $content .= '
    '.$this->l('Product: ').''.$product->name.'

    '; + $content .= '
    '.$this->l('Room type: ').''.$product->name.'

    '; } $content .= Tools::safeOutput($message['message']); diff --git a/controllers/admin/AdminCustomersController.php b/controllers/admin/AdminCustomersController.php index 5e7c632f7..1572a1be4 100644 --- a/controllers/admin/AdminCustomersController.php +++ b/controllers/admin/AdminCustomersController.php @@ -650,41 +650,92 @@ class AdminCustomersControllerCore extends AdminController $time = time(); $kpis = array(); - $helper = new HelperKpi(); - $helper->id = 'box-gender'; - $helper->icon = 'icon-male'; - $helper->color = 'color1'; - $helper->title = $this->l('Customers', null, null, false); - $helper->subtitle = $this->l('All Time', null, null, false); - $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=customer_main_gender'; - $kpis[] = $helper->generate(); - - $helper = new HelperKpi(); - $helper->id = 'box-age'; - $helper->icon = 'icon-calendar'; - $helper->color = 'color2'; - $helper->title = $this->l('Average Age', 'AdminTab', null, false); - $helper->subtitle = $this->l('All Time', null, null, false); - $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=avg_customer_age'; - $kpis[] = $helper->generate(); - $helper = new HelperKpi(); $helper->id = 'box-orders'; - $helper->icon = 'icon-retweet'; - $helper->color = 'color3'; + $helper->icon = 'icon-shopping-cart'; + $helper->color = 'color1'; $helper->title = $this->l('Orders per Customer', null, null, false); $helper->subtitle = $this->l('All Time', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=orders_per_customer'; - $kpis[] = $helper->generate(); + $helper->tooltip = $this->l('The average number of orders placed per customer in given period of time.', null, null, false); + $kpis[] = $helper; $helper = new HelperKpi(); - $helper->id = 'box-newsletter'; + $helper->id = 'box-total-frequent-customers'; + $helper->icon = 'icon-star'; + $helper->color = 'color2'; + $helper->title = $this->l('Total Frequent Customers', null, null, false); + $helper->subtitle = $this->l('All Time', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=total_frequent_customers'; + $helper->tooltip = $this->l('The total number of frequent customers in given period of time.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-revenue-per-available-customer'; + $helper->icon = 'icon-dollar'; + $helper->color = 'color3'; + $helper->title = $this->l('RevPAC', null, null, false); + $nbDaysRevPac = Validate::isUnsignedInt(Configuration::get('PS_KPI_REVPAC_NB_DAYS')) ? Configuration::get('PS_KPI_REVPAC_NB_DAYS') : 30; + $helper->subtitle = sprintf($this->l('%d Days', null, null, false), (int) $nbDaysRevPac); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=revenue_per_available_customer'; + $helper->tooltip = $this->l('Revenue per Available Customer (RevPAC) in given period of time.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-total-newsletter-registrations'; $helper->icon = 'icon-envelope'; $helper->color = 'color4'; - $helper->title = $this->l('Newsletter Registrations', null, null, false); + $helper->title = $this->l('Total Newsletter Registrations', null, null, false); $helper->subtitle = $this->l('All Time', null, null, false); - $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=newsletter_registrations'; - $kpis[] = $helper->generate(); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=total_newsletter_registrations'; + $helper->tooltip = $this->l('The total number of newsletter registrations in given period of time.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-conversion-rate'; + $helper->icon = 'icon-refresh'; + $helper->color = 'color1'; + $helper->title = $this->l('Conversion Rate', null, null, false); + $nbDaysConversionRate = Validate::isUnsignedInt(Configuration::get('PS_KPI_CONVERSION_RATE_NB_DAYS')) ? Configuration::get('PS_KPI_CONVERSION_RATE_NB_DAYS') : 30; + $helper->subtitle = sprintf($this->l('%d Days', null, null, false), (int) $nbDaysConversionRate); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=conversion_rate'; + $helper->tooltip = $this->l('The percentage of visitors who created a booking in given period of time.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-total-new-customers'; + $helper->icon = 'icon-plus-circle'; + $helper->color = 'color4'; + $helper->title = $this->l('New Customers', null, null, false); + $nbDaysNewCustomers = Validate::isUnsignedInt(Configuration::get('PS_KPI_NEW_CUSTOMERS_NB_DAYS')) ? Configuration::get('PS_KPI_NEW_CUSTOMERS_NB_DAYS') : 30; + $helper->subtitle = sprintf($this->l('%d Days', null, null, false), (int) $nbDaysNewCustomers); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=total_new_customers'; + $helper->tooltip = $this->l('The total number of new customers who registered in given period of time.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-total-banned-customers'; + $helper->icon = 'icon-ban'; + $helper->color = 'color2'; + $helper->title = $this->l('Banned Customers', null, null, false); + $helper->subtitle = $this->l('All Time', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=total_banned_customers'; + $helper->tooltip = $this->l('The total number of banned customers.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-gender'; + $helper->icon = 'icon-male'; + $helper->color = 'color3'; + $helper->title = $this->l('Customers', null, null, false); + $helper->subtitle = $this->l('All Time', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=customer_main_gender'; + $helper->tooltip = $this->l('The main gender from all the customers.', null, null, false); + $kpis[] = $helper; + + Hook::exec('action'.$this->controller_name.'KPIListingModifier', array( + 'kpis' => &$kpis, + )); $helper = new HelperKpiRow(); $helper->kpis = $kpis; diff --git a/controllers/admin/AdminMetaController.php b/controllers/admin/AdminMetaController.php index cb47df23b..676bd7a64 100644 --- a/controllers/admin/AdminMetaController.php +++ b/controllers/admin/AdminMetaController.php @@ -477,7 +477,7 @@ class AdminMetaControllerCore extends AdminController )); // PS Comments - fwrite($write_fd, "# robots.txt automaticaly generated by qloapps e-commerce open-source solution\n"); + fwrite($write_fd, "# robots.txt automaticaly generated by QloApps - open-source hotel management solution.\n"); fwrite($write_fd, "# https://www.qloapps.com - https://forums.qloapps.com/\n"); fwrite($write_fd, "# This file is to prevent the crawling and indexing of certain parts\n"); fwrite($write_fd, "# of your site by web crawlers and spiders run by sites like Yahoo!\n"); diff --git a/controllers/admin/AdminModulesController.php b/controllers/admin/AdminModulesController.php index 6c2e766b6..a71362676 100644 --- a/controllers/admin/AdminModulesController.php +++ b/controllers/admin/AdminModulesController.php @@ -1333,7 +1333,7 @@ class AdminModulesControllerCore extends AdminController $helper->color = 'color1'; $helper->title = $this->l('Installed Modules', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=installed_modules'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-disabled-modules'; @@ -1341,7 +1341,7 @@ class AdminModulesControllerCore extends AdminController $helper->color = 'color2'; $helper->title = $this->l('Disabled Modules', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=disabled_modules'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-update-modules'; @@ -1349,7 +1349,11 @@ class AdminModulesControllerCore extends AdminController $helper->color = 'color3'; $helper->title = $this->l('Modules to Update', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=update_modules'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; + + Hook::exec('action'.$this->controller_name.'KPIListingModifier', array( + 'kpis' => &$kpis, + )); $helper = new HelperKpiRow(); $helper->kpis = $kpis; diff --git a/controllers/admin/AdminOrderPreferencesController.php b/controllers/admin/AdminOrderPreferencesController.php index 52c131480..67e25d177 100644 --- a/controllers/admin/AdminOrderPreferencesController.php +++ b/controllers/admin/AdminOrderPreferencesController.php @@ -58,6 +58,18 @@ class AdminOrderPreferencesControllerCore extends AdminController ) ); + // actions when overbooking will be created + $overbookingAction = array( + array( + 'value' => Order::OVERBOOKING_ORDER_CANCEL_ACTION, + 'name' => $this->l('Cancel the booking') + ), + array( + 'value' => Order::OVERBOOKING_ORDER_NO_ACTION, + 'name' => $this->l('Take the overbooking') + ) + ); + // Options to display currency in order list $displayCurrencyOptions = array( array( @@ -76,8 +88,8 @@ class AdminOrderPreferencesControllerCore extends AdminController 'icon' => 'icon-cogs', 'fields' => array( 'MAX_GLOBAL_BOOKING_DATE' => array( - 'title' => $this->l('Maximum Global Date to book a room'), - 'hint' => $this->l('Maximum date by which rooms of your hotels can be booked.'), + 'title' => $this->l('Maximum Global Check-out Date to book a room'), + 'hint' => $this->l('Maximum date of check-out for which rooms of your hotels can be booked.'), 'type' => 'text', 'id' => 'max_global_book_date', 'class' => 'fixed-width-xxl readonly', @@ -176,8 +188,8 @@ class AdminOrderPreferencesControllerCore extends AdminController ), 'PS_ROOM_PRICE_AUTO_ADD_BREAKDOWN' => array( 'title' => $this->l('Show room price breakdown'), - 'hint' => $this->l('Show a summary for prices of the auto added service add with room on shopping cart list.'), - 'desc' => $this->l('This breakdown is always visible on room that have cart rule applied for that specific room.'), + 'hint' => $this->l('Show price breakdown for rooms with auto added services on checkout page.'), + 'desc' => $this->l('This room price breakdown will be shown if cart rule for specific room type is applied on that room.'), 'cast' => 'intval', 'type' => 'bool' ), @@ -195,33 +207,83 @@ class AdminOrderPreferencesControllerCore extends AdminController 'submit' => array('title' => $this->l('Save')) ), 'email' => array( - 'title' => $this->l('Email'), + 'title' => $this->l('Order Confirmation / Overbooking Email'), 'icon' => 'icon-cogs', 'fields' => array( 'PS_ORDER_CONF_MAIL_TO_CUSTOMER' => array( - 'title' => $this->l('Order confirmation mail to customer'), - 'hint' => $this->l('Enable, if you want to send order confirmation email to the customer.'), + 'title' => $this->l('Send email to customer'), + 'hint' => $this->l('Enable, if you want to send order confirmation or overbooking or overbooking email to the customer.'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool' ), 'PS_ORDER_CONF_MAIL_TO_SUPERADMIN' => array( - 'title' => $this->l('Order confirmation mail to super admin'), - 'hint' => $this->l('Enable, if you want to send order confirmation email to the super admin.'), + 'title' => $this->l('Send email to super admin'), + 'hint' => $this->l('Enable, if you want to send order confirmation or overbooking email to the super admin.'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool' ), 'PS_ORDER_CONF_MAIL_TO_HOTEL_MANAGER' => array( - 'title' => $this->l('Order confirmation mail to hotelier'), - 'hint' => $this->l('Enable, if you want to send order confirmation email to the hotelier. Mail will be sent to the email saved while creating the hotel.'), + 'title' => $this->l('Send email to hotelier'), + 'hint' => $this->l('Enable, if you want to send order confirmation or overbooking email to the hotelier. Mail will be sent to the email saved while creating the hotel.'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool' ), 'PS_ORDER_CONF_MAIL_TO_EMPLOYEE' => array( - 'title' => $this->l('Order confirmation mail to employees'), - 'hint' => $this->l('Enable, if you want to send order confirmation email to employees having permission to manage the hotel.'), + 'title' => $this->l('Send email to employees'), + 'hint' => $this->l('Enable, if you want to send order confirmation or overbooking email to employees having permission to manage the hotel.'), + 'validation' => 'isBool', + 'cast' => 'intval', + 'type' => 'bool' + ), + ), + 'submit' => array('title' => $this->l('Save')) + ), + 'overbooking' => array( + 'title' => $this->l('Overbooking'), + 'icon' => 'icon-cogs', + 'fields' => array( + 'PS_OVERBOOKING_ORDER_ACTION' => array( + 'title' => $this->l('Overbooking order action'), + 'hint' => $this->l('Please choose the action to take when overbooking is created in any order.'), + 'validation' => 'isInt', + 'type' => 'select', + 'cast' => 'intval', + 'validation' => 'isInt', + 'list' => $overbookingAction, + 'identifier' => 'value', + 'js' => "changeOverbookingOrderAction()" + ), + 'PS_MAX_OVERBOOKING_PER_HOTEL_PER_DAY' => array( + 'title' => $this->l('Maximun hotel overbookings per date'), + 'hint' => $this->l('Enter the maximum number of the overbookings on any date can be created for a hotel.(Set 0 for no limit)'), + 'desc' => $this->l('When maximun number of the overbookings on any date in the booking duration is reached, then the new overbooking order will be cancelled.(Set 0 for no limit)'), + 'validation' => 'isInt', + 'type' => 'text', + 'class' => 'fixed-width-xxl', + 'validation' => 'isInt', + 'id' => 'PS_MAX_OVERBOOKING_PER_HOTEL_PER_DAY', + ), + 'OVERBOOKING_ORDER_CANCEL_ACTION' => array( + 'title' => $this->l('Overbooking order cancel action value'), + 'type' => 'hidden', + 'value' => Order::OVERBOOKING_ORDER_CANCEL_ACTION, + 'auto_value' => false, + ), + 'OVERBOOKING_ORDER_NO_ACTION' => array( + 'title' => $this->l('Overbooking order no action value'), + 'validation' => 'isInt', + 'cast' => 'intval', + 'type' => 'hidden', + 'value' => Order::OVERBOOKING_ORDER_NO_ACTION, + 'auto_value' => false + ), + 'PS_OVERBOOKING_AUTO_RESOLVE' => array( + 'title' => $this->l('Resolve overbooking automatically'), + 'hint' => $this->l('Enable, if you want to resolve overbooking automatically when rooms are available to be replaced with overbooked rooms.'), + 'desc' => $this->l('If enabled, the overbookings in the order will be resolved automatically when rooms are available to be replaced with all overbooked rooms in the order.'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool' @@ -256,11 +318,11 @@ class AdminOrderPreferencesControllerCore extends AdminController $maxGlobalBookingDateFormatted = date('Y-m-d', strtotime($maxGlobalBookingDate)); if ($maxGlobalBookingDate == '') { - $this->errors[] = Tools::displayError('Field \'Maximum Global Date to book a room\' can not be empty.'); + $this->errors[] = Tools::displayError('Field \'Maximum Global Check-out Date to book a room\' can not be empty.'); } elseif (!Validate::isDate($maxGlobalBookingDateFormatted)) { - $this->errors[] = Tools::displayError('Field \'Maximum Global Date to book a room\' is invalid.'); + $this->errors[] = Tools::displayError('Field \'Maximum Global Check-out Date to book a room\' is invalid.'); } elseif (strtotime($maxGlobalBookingDateFormatted) < strtotime(date('Y-m-d'))) { - $this->errors[] = Tools::displayError('Field \'Maximum Global Date to book a room\' can not be a past date. Please use a future date.'); + $this->errors[] = Tools::displayError('Field \'Maximum Global Check-out Date to book a room\' can not be a past date. Please use a future date.'); } if ($globalPreparationTime === '') { diff --git a/controllers/admin/AdminOrdersController.php b/controllers/admin/AdminOrdersController.php index 0086ed1ea..8b1d545c9 100644 --- a/controllers/admin/AdminOrdersController.php +++ b/controllers/admin/AdminOrdersController.php @@ -96,6 +96,22 @@ class AdminOrdersControllerCore extends AdminController $this->_group = ' GROUP BY hbd.`id_order`'; + // if request for resolvable overbooked orders filter comes the set condition for this + if (Tools::getIsset('resolvable_overbooked_orders') && Tools::getValue('resolvable_overbooked_orders')) { + $objHotelBookingDetail = new HotelBookingDetail(); + $resolvableOrders = array(); + // Overbookings information of the order + if ($orderOverBookings = $objHotelBookingDetail->getOverbookedRooms()) { + foreach ($orderOverBookings as $overBookedRoom) { + if (!in_array($overBookedRoom['id_order'], $resolvableOrders)) { + $resolvableOrders[$overBookedRoom['id_order']] = $overBookedRoom['id_order']; + } + } + } + $this->_where = ' AND a.`id_order` IN ('.implode(',', $resolvableOrders).')'; + } + + $statuses = OrderState::getOrderStates((int)$this->context->language->id); foreach ($statuses as $status) { $this->statuses_array[$status['id_order_state']] = $status['name']; @@ -326,6 +342,8 @@ class AdminOrdersControllerCore extends AdminController parent::__construct(); + // overbooking success status + $this->_conf[51] = $this->l('Overbooking is successfully resolved'); $this->_conf[52] = $this->l('Room in the booking is successfully reallocated'); $this->_conf[53] = $this->l('Room in the booking is successfully swapped'); } @@ -623,6 +641,13 @@ class AdminOrdersControllerCore extends AdminController public function renderList() { + // Show all resolvable overbookings if available + $objHotelBookingDetail = new HotelBookingDetail(); + if ($resolvableOverBookings = $objHotelBookingDetail->getOverbookedRooms(0, 0, '', '', 0, 0, 2)) { + $this->context->smarty->assign('resolvableOverBookings', $resolvableOverBookings); + $this->content .= $this->context->smarty->fetch('controllers/orders/_resolvable_overbookings.tpl'); + } + if (Tools::isSubmit('submitBulkupdateOrderStatus'.$this->table)) { if (Tools::getIsset('cancel')) { Tools::redirectAdmin(self::$currentIndex.'&token='.$this->token); @@ -1819,10 +1844,24 @@ class AdminOrdersControllerCore extends AdminController } else { $this->errors[] = Tools::displayError('You do not have permission to edit this.'); } + } elseif(Tools::getIsset('resolve_overbooking') && Tools::getValue('resolve_overbooking')) { + $objHotelBookingDetail = new HotelBookingDetail(); + // resolve an overbooking manually + if ($objHotelBookingDetail->resolveOverbookings(Tools::getValue('resolve_overbooking'))) { + if (Tools::getValue('id_order')) { + Tools::redirectAdmin(self::$currentIndex.'&id_order='.$order->id.'&vieworder&conf=51&token='.$this->token); + } else { + Tools::redirectAdmin(self::$currentIndex.'&conf=51&token='.$this->token); + } + } else { + $this->errors = Tools::displayError('An error occurred while resolving overbooking.'); + } } + parent::postProcess(); } + // KPIs on listing page // public function renderKpis() // { // $time = time(); @@ -1874,6 +1913,60 @@ class AdminOrdersControllerCore extends AdminController // return $helper->generate(); // } + // KPIs on view page + public function renderKpis() + { + $objOrder = new Order(Tools::getValue('id_order')); + if (Validate::isLoadedObject($objOrder)) { + $kpis = array(); + + $helper = new HelperKpi(); + $helper->id = 'box-order-date'; + $helper->icon = 'icon-calendar'; + $helper->color = 'color3'; + $helper->title = $this->l('Order Date'); + $helper->value = Tools::displayDate($objOrder->date_add); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-order-total'; + $helper->icon = 'icon-money'; + $helper->color = 'color4'; + $helper->title = $this->l('Total'); + $helper->value = Tools::displayPrice($objOrder->total_paid_tax_incl, new Currency($objOrder->id_currency)); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-messages'; + $helper->icon = 'icon-comments'; + $helper->color = 'color2'; + $helper->title = $this->l('Messages'); + $helper->href = $this->context->link->getAdminLink('AdminCustomerThreads').'&id_order='.$objOrder->id; + $helper->value = count(CustomerThread::getCustomerMessages($objOrder->id_customer, null, $objOrder->id)); + $kpis[] = $helper; + + $objHotelBookingDetail = new HotelBookingDetail(); + $numRooms = count($objHotelBookingDetail->getBookingDataByOrderId($objOrder->id)); + $helper = new HelperKpi(); + $helper->id = 'box-total-rooms'; + $helper->icon = 'icon-home'; + $helper->color = 'color1'; + $helper->title = $this->l('Total Rooms'); + $helper->href = '#start_products'; + $helper->value = $numRooms; + $kpis[] = $helper; + + Hook::exec('action'.$this->controller_name.'KPIListingModifier', array( + 'kpis' => &$kpis, + )); + + $helper = new HelperKpiRow(); + $helper->kpis = $kpis; + + return $helper->generate(); + } + } + public function renderView() { $order = new Order(Tools::getValue('id_order')); @@ -1881,6 +1974,8 @@ class AdminOrdersControllerCore extends AdminController $this->errors[] = Tools::displayError('The order cannot be found within your database.'); } + $this->content .= $this->renderKpis(); + $customer = new Customer($order->id_customer); $carrier = new Carrier($order->id_carrier); $products = $this->getProducts($order); @@ -2229,8 +2324,12 @@ class AdminOrdersControllerCore extends AdminController // applicable refund policies $applicableRefundPolicies = HotelOrderRefundRules::getApplicableRefundRules($order->id); + // Overbookings information of the order + $orderOverBookings = $objHotelBookingDetail->getOverbookedRooms($order->id, 0, '', '', 0, 0, 1); + $this->tpl_view_vars = array( // refund info + 'orderOverBookings' => $orderOverBookings, 'refund_allowed' => (int) $order->isReturnable(), 'applicable_refund_policies' => $applicableRefundPolicies, 'returns' => OrderReturn::getOrdersReturn($order->id_customer, $order->id), @@ -3357,6 +3456,37 @@ class AdminOrdersControllerCore extends AdminController $this->context->cart = $cart; $this->context->customer = new Customer($order->id_customer); + // always add taxes even if not displayed to the customer + $use_taxes = true; + $this->context->currency = new Currency($order->id_currency); + + $initial_product_price_tax_incl = Product::getPriceStatic( + $product->id, + $use_taxes, + isset($combination) ? $combination->id : null, + 2, + null, + false, + true, + 1, + false, + $order->id_customer, + $cart->id, + $order->id_address_tax + ); + + // create feature price if needed + $createFeaturePrice = $product_informations['product_price_tax_incl'] != $initial_product_price_tax_incl; + $featurePriceParams = array(); + if ($createFeaturePrice) { + $featurePriceParams = array( + 'id_cart' => $this->context->cart->id, + 'id_guest' => $this->context->cookie->id_guest, + 'price' => $product_informations['product_price_tax_excl'], + 'id_product' => $product->id, + ); + } + /*By Webkul to make entries in HotelCartBookingData */ $hotel_room_info_arr = $hotel_room_data['rm_data'][$idProduct]['data']['available']; $chkQty = 0; @@ -3414,49 +3544,6 @@ class AdminOrdersControllerCore extends AdminController } } /*END*/ - // always add taxes even if there are not displayed to the customer - $use_taxes = true; - $this->context->currency = new Currency($order->id_currency); - - $initial_product_price_tax_incl = Product::getPriceStatic( - $product->id, - $use_taxes, - isset($combination) ? $combination->id : null, - 2, - null, - false, - true, - 1, - false, - $order->id_customer, - $cart->id, - $order->id_address_tax - ); - - // Creating specific price if needed - if ($product_informations['product_price_tax_incl'] != $initial_product_price_tax_incl) { - $specific_price = new SpecificPrice(); - $specific_price->id_shop = 0; - $specific_price->id_shop_group = 0; - $specific_price->id_currency = $order->id_currency; - $specific_price->id_country = 0; - $specific_price->id_group = 0; - $specific_price->id_customer = $order->id_customer; - $specific_price->id_product = $product->id; - if (isset($combination)) { - $specific_price->id_product_attribute = $combination->id; - } else { - $specific_price->id_product_attribute = 0; - } - $specific_price->price = $product_informations['product_price_tax_excl']; - $specific_price->from_quantity = 1; - $specific_price->reduction = 0; - $specific_price->reduction_type = 'amount'; - $specific_price->reduction_tax = 0; - $specific_price->from = '0000-00-00 00:00:00'; - $specific_price->to = '0000-00-00 00:00:00'; - $specific_price->add(); - } // Add product to cart $update_quantity = $cart->updateQty( @@ -3827,7 +3914,8 @@ class AdminOrdersControllerCore extends AdminController } // delete cart feature prices after room addition success - HotelRoomTypeFeaturePricing::deleteByIdCart($cart->id); + HotelRoomTypeFeaturePricing::deleteByIdCart($this->context->cart->id); + die(json_encode(array( 'result' => true, //'view' => $this->createTemplate('_product_line.tpl')->fetch(), @@ -5433,7 +5521,9 @@ class AdminOrdersControllerCore extends AdminController ) { $smartyVars = array(); $objOrder = new Order($idOrder); + $objCurrency = new Currency($objOrder->id_currency); $smartyVars['orderCurrency'] = $objOrder->id_currency; + $smartyVars['currencySign'] = $objCurrency->sign; $smartyVars['link'] = $this->context->link; $objBookingDemand = new HotelBookingDetail(); @@ -5450,7 +5540,7 @@ class AdminOrdersControllerCore extends AdminController $objBookingDemand = new HotelBookingDemands(); // set context currency So that we can get prices in the order currency - $this->context->currency = new Currency($objOrder->id_currency); + $this->context->currency = $objCurrency; if ($extraDemands = $objBookingDemand->getRoomTypeBookingExtraDemands( $idOrder, @@ -5514,6 +5604,12 @@ class AdminOrdersControllerCore extends AdminController public function processRenderServicesPanel($idOrder, $idProduct, $dateFrom, $dateTo, $idRoom, $orderEdit) { $smartyVars = array(); + $objOrder = new Order($idOrder); + $objCurrency = new Currency($objOrder->id_currency); + $smartyVars['orderCurrency'] = $objOrder->id_currency; + $smartyVars['currencySign'] = $objCurrency->sign; + $smartyVars['link'] = $this->context->link; + $objBookingDemand = new HotelBookingDetail(); $htlBookingDetail = $objBookingDemand->getRowByIdOrderIdProductInDateRange( $idOrder, @@ -5532,9 +5628,8 @@ class AdminOrdersControllerCore extends AdminController } if ($orderEdit) { - $objOrder = new Order($idOrder); // set context currency So that we can get prices in the order currency - $this->context->currency = new Currency($objOrder->id_currency); + $this->context->currency = $objCurrency; $smartyVars['orderEdit'] = $orderEdit; @@ -5562,7 +5657,10 @@ class AdminOrdersControllerCore extends AdminController { $smartyVars = array(); $objOrder = new Order($idOrder); + $objCurrency = new Currency($objOrder->id_currency); $smartyVars['orderCurrency'] = $objOrder->id_currency; + $smartyVars['currencySign'] = $objCurrency->sign; + $smartyVars['link'] = $this->context->link; $objBookingDemand = new HotelBookingDetail(); $htlBookingDetail = $objBookingDemand->getRowByIdOrderIdProductInDateRange( @@ -5619,61 +5717,82 @@ class AdminOrdersControllerCore extends AdminController $response = array('hasError' => false); $id_room_type_service_product_order_detail = Tools::getValue('id_room_type_service_product_order_detail'); $quantity = Tools::getValue('qty'); + $unitPrice = Tools::getValue('unit_price'); if (Validate::isLoadedObject($objRoomTypeServiceProductOrderDetail = new RoomTypeServiceProductOrderDetail($id_room_type_service_product_order_detail))) { - if (Validate::isLoadedObject($objHotelBookingDetail = new HotelBookingDetail((int) $objRoomTypeServiceProductOrderDetail->id_htl_booking_detail))) { - $numDays = 1; - if (Product::getProductPriceCalculation($objRoomTypeServiceProductOrderDetail->id_product) == Product::PRICE_CALCULATION_METHOD_PER_DAY) { - $numDays = HotelHelper::getNumberOfDays($objHotelBookingDetail->date_from, $objHotelBookingDetail->date_to); - } - - $objOrderDetail = new OrderDetail($objRoomTypeServiceProductOrderDetail->id_order_detail); - if ($objOrderDetail->product_allow_multiple_quantity) { - if (!Validate::isUnsignedInt($quantity)) { - $response['hasError'] = true; - $response['errors'] = $this->l('Invalid quantity provided'); - } else { - $oldQuantity = $objRoomTypeServiceProductOrderDetail->quantity; - $objRoomTypeServiceProductOrderDetail->quantity = $quantity; - $oldPriceTaxExcl = $objRoomTypeServiceProductOrderDetail->total_price_tax_excl; - $oldPriceTaxIncl = $objRoomTypeServiceProductOrderDetail->total_price_tax_incl; - $objRoomTypeServiceProductOrderDetail->total_price_tax_excl = $objRoomTypeServiceProductOrderDetail->unit_price_tax_excl * $quantity * $numDays; - $objRoomTypeServiceProductOrderDetail->total_price_tax_incl = $objRoomTypeServiceProductOrderDetail->unit_price_tax_incl * $quantity * $numDays; - $res = true; - if ($res &= $objRoomTypeServiceProductOrderDetail->save()) { - $order = new Order($objRoomTypeServiceProductOrderDetail->id_order); - $priceDiffTaxExcl = $objRoomTypeServiceProductOrderDetail->total_price_tax_excl - $oldPriceTaxExcl; - $priceDiffTaxIncl = $objRoomTypeServiceProductOrderDetail->total_price_tax_incl - $oldPriceTaxIncl; - - $objOrderDetail->product_quantity += (($quantity - $oldQuantity) * $numDays); - $objOrderDetail->total_price_tax_excl += $priceDiffTaxExcl; - $objOrderDetail->total_price_tax_incl += $priceDiffTaxIncl; - - $res &= $objOrderDetail->updateTaxAmount($order); - - $res &= $objOrderDetail->update(); - - $order->total_paid_tax_excl += $priceDiffTaxExcl; - $order->total_paid_tax_incl += $priceDiffTaxIncl; - $order->total_paid += $priceDiffTaxIncl; - - $res &= $order->update(); - } - } - if (!$res) { - $response['hasError'] = true; - $response['errors'] = $this->l('Error while updating service, please try refresing the page'); - } - } else { - $response['hasError'] = true; - $response['errors'] = $this->l('cannot order multiple quanitity for this service'); - } - } else { + $objOrderDetail = new OrderDetail($objRoomTypeServiceProductOrderDetail->id_order_detail); + if (!$objOrderDetail->product_allow_multiple_quantity && $quantity > 1) { $response['hasError'] = true; - $response['errors'] = $this->l('Additional service not found'); + $response['errors'] = $this->l('cannot order multiple quanitity for this service'); + } else if (!Validate::isUnsignedInt($quantity)) { + $response['hasError'] = true; + $response['errors'] = $this->l('Invalid quantity provided'); } + if (!ValidateCore::isPrice($unitPrice)) { + $response['hasError'] = true; + $response['errors'] = $this->l('Invalid unit price'); + } $objHotelBookingDetail = new HotelBookingDetail($objRoomTypeServiceProductOrderDetail->id_htl_booking_detail); + $res = true; + if (!$response['hasError']) { + $oldPriceTaxExcl = $objRoomTypeServiceProductOrderDetail->unit_price_tax_excl; + $oldPriceTaxIncl = $objRoomTypeServiceProductOrderDetail->unit_price_tax_incl; + $oldQuantity = $objRoomTypeServiceProductOrderDetail->quantity; + if ($oldPriceTaxExcl > 0) { + $oldTaxMultiplier = $oldPriceTaxIncl / $oldPriceTaxExcl; + } else { + $oldTaxMultiplier = 1; + } + if ($quantity <= 0) { + $quantity = 1; + } + $objRoomTypeServiceProductOrderDetail->quantity = $quantity; + $objRoomTypeServiceProductOrderDetail->unit_price_tax_excl = Tools::ps_round($unitPrice, 6); + $objRoomTypeServiceProductOrderDetail->unit_price_tax_incl = Tools::ps_round($unitPrice * $oldTaxMultiplier, 6); + if ($objOrderDetail->product_price_calculation_method == Product::PRICE_CALCULATION_METHOD_PER_DAY) { + $quantity = $quantity * HotelHelper::getNumberOfDays( + $objHotelBookingDetail->date_from, + $objHotelBookingDetail->date_to + ); + } + $objRoomTypeServiceProductOrderDetail->total_price_tax_excl = Tools::ps_round($objRoomTypeServiceProductOrderDetail->unit_price_tax_excl * $quantity, 6); + $objRoomTypeServiceProductOrderDetail->total_price_tax_incl = Tools::ps_round($objRoomTypeServiceProductOrderDetail->unit_price_tax_incl * $quantity, 6); + if ($res &= $objRoomTypeServiceProductOrderDetail->save()) { + $order = new Order($objRoomTypeServiceProductOrderDetail->id_order); + $priceDiffTaxExcl = $objRoomTypeServiceProductOrderDetail->total_price_tax_excl - $oldPriceTaxExcl; + $priceDiffTaxIncl = $objRoomTypeServiceProductOrderDetail->total_price_tax_incl - $oldPriceTaxIncl; + $quantityDiff = $objRoomTypeServiceProductOrderDetail->quantity - $oldQuantity; + + $objOrderDetail->product_quantity += $quantityDiff; + $objOrderDetail->total_price_tax_excl += $priceDiffTaxExcl; + $objOrderDetail->total_price_tax_incl += $priceDiffTaxIncl; + $objOrderDetail->unit_price_tax_excl = ($objOrderDetail->total_price_tax_excl / $objOrderDetail->product_quantity); + $objOrderDetail->unit_price_tax_incl = ($objOrderDetail->total_price_tax_incl / $objOrderDetail->product_quantity); + $res &= $objOrderDetail->updateTaxAmount($order); + + $res &= $objOrderDetail->update(); + + if ($objOrderDetail->id_order_invoice != 0) { + // values changes as values are calculated accoding to the quantity of the product by webkul + $order_invoice = new OrderInvoice($objOrderDetail->id_order_invoice); + $order_invoice->total_paid_tax_excl += $priceDiffTaxExcl; + $order_invoice->total_paid_tax_incl += $priceDiffTaxIncl; + $res &= $order_invoice->update(); + } + + $order->total_paid_tax_excl += $priceDiffTaxExcl; + $order->total_paid_tax_incl += $priceDiffTaxIncl; + $order->total_paid += $priceDiffTaxIncl; + + $res &= $order->update(); + } + } + if (!$res) { + $response['hasError'] = true; + $response['errors'] = $this->l('Error while updating service, please try refresing the page'); + } + $response['service_panel']= $servicesBlock = $this->processRenderServicesPanel( $objOrderDetail->id_order, $objHotelBookingDetail->id_product, @@ -5755,6 +5874,7 @@ class AdminOrdersControllerCore extends AdminController $objRoomTypeServiceProduct = new RoomTypeServiceProduct(); $qty = Tools::getValue('service_qty'); + $price = Tools::getValue('service_price'); foreach ($selectedServices as $key => $service) { if ($objRoomTypeServiceProduct->isRoomTypeLinkedWithProduct($objHotelBookingDetail->id_product, $service)) { $objProduct = new Product($service, false, $this->context->language->id); @@ -5766,9 +5886,14 @@ class AdminOrdersControllerCore extends AdminController } else { $qty[$service] = 1; } + if (!ValidateCore::isPrice($price[$service])) { + $response['hasError'] = true; + $response['errors'][] = sprintf($this->l('Invalid unit price for %s.'), $objProduct->name); + } $selectedServices[$key] = array( 'id' => $service, 'qty' => $qty[$service], + 'price' => $price[$service], 'name' => $objProduct->name, ); } else { @@ -5809,6 +5934,37 @@ class AdminOrdersControllerCore extends AdminController $this->context->cart = $cart; $this->context->customer = new Customer($order->id_customer); + $objRoomTypeServiceProductPrice = new RoomTypeServiceProductPrice(); + $initialServicePrice = $objRoomTypeServiceProductPrice->getServicePrice( + (int)$service['id'], + $roomHtlCartInfo['id_product'], + 1, + null, + null, + false + ); + + if ($initialServicePrice != $service['price']) { + $specific_price = new SpecificPrice(); + $specific_price->id_shop = 0; + $specific_price->id_shop_group = 0; + $specific_price->id_cart = $cart->id; + $specific_price->id_currency = $order->id_currency; + $specific_price->id_country = 0; + $specific_price->id_group = 0; + $specific_price->id_customer = $order->id_customer; + $specific_price->id_product = $service['id']; + $specific_price->id_product_attribute = 0; + $specific_price->price = $service['price']; + $specific_price->from_quantity = 1; + $specific_price->reduction = 0; + $specific_price->reduction_type = 'amount'; + $specific_price->reduction_tax = 0; + $specific_price->from = '0000-00-00 00:00:00'; + $specific_price->to = '0000-00-00 00:00:00'; + $specific_price->add(); + } + $objRoomTypeServiceProductCartDetail = new RoomTypeServiceProductCartDetail(); $objRoomTypeServiceProductCartDetail->addServiceProductInCart( $service['id'], @@ -5817,7 +5973,6 @@ class AdminOrdersControllerCore extends AdminController $roomHtlCartInfo['id'] ); $productList = $cart->getProducts(); - $objRoomTypeServiceProductPrice = new RoomTypeServiceProductPrice(); $objHotelRoomType = new HotelRoomType(); $roomInfo = $objHotelRoomType->getRoomTypeInfoByIdProduct($objHotelBookingDetail->id_product); $totalPriceChangeTaxExcl = 0; @@ -5834,20 +5989,23 @@ class AdminOrdersControllerCore extends AdminController $objRoomTypeServiceProductCartDetail = new RoomTypeServiceProductCartDetail((int) $id_room_type_service_product_cart_detail); $totalPriceChangeTaxExcl += $totalPriceTaxExcl = $objRoomTypeServiceProductPrice->getServicePrice( (int)$product['id_product'], - $roomInfo['id'], + 0, $objRoomTypeServiceProductCartDetail->quantity, $objHotelBookingDetail->date_from, $objHotelBookingDetail->date_to, - false + false, + $cart->id ); $totalPriceChangeTaxIncl += $totalPriceTaxIncl = $objRoomTypeServiceProductPrice->getServicePrice( (int)$product['id_product'], - $roomInfo['id'], + 0, $objRoomTypeServiceProductCartDetail->quantity, $objHotelBookingDetail->date_from, $objHotelBookingDetail->date_to, - true + true, + $cart->id ); + $numDays = 1; if (Product::getProductPriceCalculation($product['id_product']) == Product::PRICE_CALCULATION_METHOD_PER_DAY) { $numDays = HotelHelper::getNumberOfDays($objHotelBookingDetail->date_from, $objHotelBookingDetail->date_to); @@ -5856,19 +6014,21 @@ class AdminOrdersControllerCore extends AdminController $unitPriceTaxExcl = $objRoomTypeServiceProductPrice->getServicePrice( (int)$product['id_product'], - $roomInfo['id'], + 0, 1, $objHotelBookingDetail->date_from, $objHotelBookingDetail->date_to, - false + false, + $cart->id )/ $numDays; $unitPriceTaxIncl = $objRoomTypeServiceProductPrice->getServicePrice( (int)$product['id_product'], - $roomInfo['id'], + 0, 1, $objHotelBookingDetail->date_from, $objHotelBookingDetail->date_to, - true + true, + $cart->id )/ $numDays; switch (Configuration::get('PS_ROUND_TYPE')) { case Order::ROUND_TOTAL: @@ -5926,9 +6086,13 @@ class AdminOrdersControllerCore extends AdminController $order->total_discounts_tax_incl += (float)abs($cart->getOrderTotal(true, Cart::ONLY_DISCOUNTS)); // Save changes of order $order->update(); + if (Validate::isLoadedObject($specific_price)) { + $specific_price->delete(); + unset($specific_price); + } } } - $response['service_panel'] = $servicesBlock = $this->processRenderServicesPanel( + $response['service_panel'] = $this->processRenderServicesPanel( $order->id, $objHotelBookingDetail->id_product, $objHotelBookingDetail->date_from, @@ -5963,9 +6127,9 @@ class AdminOrdersControllerCore extends AdminController } // Process when admin edit rooms and edit rooms additional facilities - public function ajaxProcessEditRoomExtraDemands() + public function ajaxProcessAddRoomExtraDemands() { - $response = array('success' => false); + $response = array('success' => false, 'hasError' => false); if ($idHtlBooking = Tools::getValue('id_htl_booking')) { if (Validate::isLoadedObject($objBookingDetail = new HotelBookingDetail($idHtlBooking))) { $roomDemands = Tools::getValue('room_demands'); @@ -5980,72 +6144,150 @@ class AdminOrdersControllerCore extends AdminController $objHtlBkDtl = new HotelBookingDetail(); $objRoomDemandPrice = new HotelRoomTypeDemandPrice(); foreach ($roomDemands as $demand) { - $idGlobalDemand = $demand['id_global_demand']; - $idOption = $demand['id_option']; - $objBookingDemand = new HotelBookingDemands(); - $objBookingDemand->id_htl_booking = $idHtlBooking; - $objGlobalDemand = new HotelRoomTypeGlobalDemand($idGlobalDemand, $idLang); - if ($idOption) { - $objOption = new HotelRoomTypeGlobalDemandAdvanceOption($idOption, $idLang); - $objBookingDemand->name = $objOption->name; - } else { - $idOption = 0; - $objBookingDemand->name = $objGlobalDemand->name; - } - $objBookingDemand->unit_price_tax_excl = HotelRoomTypeDemand::getPriceStatic( - $idProduct, - $idGlobalDemand, - $idOption, - 0 - ); - $objBookingDemand->unit_price_tax_incl = HotelRoomTypeDemand::getPriceStatic( - $idProduct, - $idGlobalDemand, - $idOption, - 1 - ); - $qty = 1; - if ($objGlobalDemand->price_calc_method == HotelRoomTypeGlobalDemand::WK_PRICE_CALC_METHOD_EACH_DAY) { - $numDays = $objHtlBkDtl->getNumberOfDays( - $objBookingDetail->date_from, - $objBookingDetail->date_to - ); - if ($numDays > 1) { - $qty *= $numDays; - } - } - $objBookingDemand->total_price_tax_excl = $objBookingDemand->unit_price_tax_excl * $qty; - $objBookingDemand->total_price_tax_incl = $objBookingDemand->unit_price_tax_incl * $qty; - - $order_detail = new OrderDetail($objBookingDetail->id_order_detail); - // Update OrderInvoice of this OrderDetail - if ($order_detail->id_order_invoice != 0) { - // values changes as values are calculated accoding to the quantity of the product by webkul - $order_invoice = new OrderInvoice($order_detail->id_order_invoice); - $order_invoice->total_paid_tax_excl += $objBookingDemand->total_price_tax_excl; - $order_invoice->total_paid_tax_incl += $objBookingDemand->total_price_tax_incl; - $res &= $order_invoice->update(); - } - - // change order total - $order->total_paid_tax_excl += $objBookingDemand->total_price_tax_excl; - $order->total_paid_tax_incl += $objBookingDemand->total_price_tax_incl; - $order->total_paid += $objBookingDemand->total_price_tax_incl; - - $objBookingDemand->price_calc_method = $objGlobalDemand->price_calc_method; - $objBookingDemand->id_tax_rules_group = $objGlobalDemand->id_tax_rules_group; - $taxManager = TaxManagerFactory::getManager( - $vatAddress, - $objGlobalDemand->id_tax_rules_group - ); - $taxCalc = $taxManager->getTaxCalculator(); - $objBookingDemand->tax_computation_method = (int)$taxCalc->computation_method; - if ($objBookingDemand->save()) { - $objBookingDemand->tax_calculator = $taxCalc; - // Now save tax details of the extra demand - $objBookingDemand->setBookingDemandTaxDetails(); + if (!ValidateCore::isPrice($demand['unit_price'])) { + $objGlobalDemand = new HotelRoomTypeGlobalDemand($demand['id_global_demand'], $idLang); + $response['hasError'] = true; + $response['errors'][] = sprintf($this->l('Invalid unit price for %s.'), $objGlobalDemand->name); } } + if (!$response['hasError']) { + foreach ($roomDemands as $demand) { + $idGlobalDemand = $demand['id_global_demand']; + $idOption = $demand['id_option']; + $objBookingDemand = new HotelBookingDemands(); + $objBookingDemand->id_htl_booking = $idHtlBooking; + $objGlobalDemand = new HotelRoomTypeGlobalDemand($idGlobalDemand, $idLang); + if ($idOption) { + $objOption = new HotelRoomTypeGlobalDemandAdvanceOption($idOption, $idLang); + $objBookingDemand->name = $objOption->name; + } else { + $idOption = 0; + $objBookingDemand->name = $objGlobalDemand->name; + } + $objBookingDemand->unit_price_tax_excl = $demand['unit_price']; + $objBookingDemand->unit_price_tax_incl = HotelRoomTypeDemand::getPriceStatic( + $idProduct, + $idGlobalDemand, + $idOption, + 1, + 6, + null, + null, + null, + null, + $demand['unit_price'] + ); + $qty = 1; + if ($objGlobalDemand->price_calc_method == HotelRoomTypeGlobalDemand::WK_PRICE_CALC_METHOD_EACH_DAY) { + $numDays = $objHtlBkDtl->getNumberOfDays( + $objBookingDetail->date_from, + $objBookingDetail->date_to + ); + if ($numDays > 1) { + $qty *= $numDays; + } + } + $objBookingDemand->total_price_tax_excl = $objBookingDemand->unit_price_tax_excl * $qty; + $objBookingDemand->total_price_tax_incl = $objBookingDemand->unit_price_tax_incl * $qty; + + $order_detail = new OrderDetail($objBookingDetail->id_order_detail); + // Update OrderInvoice of this OrderDetail + if ($order_detail->id_order_invoice != 0) { + // values changes as values are calculated accoding to the quantity of the product by webkul + $order_invoice = new OrderInvoice($order_detail->id_order_invoice); + $order_invoice->total_paid_tax_excl += $objBookingDemand->total_price_tax_excl; + $order_invoice->total_paid_tax_incl += $objBookingDemand->total_price_tax_incl; + $res &= $order_invoice->update(); + } + + // change order total + $order->total_paid_tax_excl += $objBookingDemand->total_price_tax_excl; + $order->total_paid_tax_incl += $objBookingDemand->total_price_tax_incl; + $order->total_paid += $objBookingDemand->total_price_tax_incl; + + $objBookingDemand->price_calc_method = $objGlobalDemand->price_calc_method; + $objBookingDemand->id_tax_rules_group = $objGlobalDemand->id_tax_rules_group; + $taxManager = TaxManagerFactory::getManager( + $vatAddress, + $objGlobalDemand->id_tax_rules_group + ); + $taxCalc = $taxManager->getTaxCalculator(); + $objBookingDemand->tax_computation_method = (int)$taxCalc->computation_method; + if ($objBookingDemand->save()) { + $objBookingDemand->tax_calculator = $taxCalc; + // Now save tax details of the extra demand + $objBookingDemand->setBookingDemandTaxDetails(); + } + } + if ($order->save()) { + $response['facilities_panel'] = $this->processRenderFacilitiesBlock( + $order->id, + $objBookingDetail->id_product, + $objBookingDetail->date_from, + $objBookingDetail->date_to, + $objBookingDetail->id_room, + true + ); + $response['success'] = true; + } + } + } + } + } + die(json_encode($response)); + } + + public function ajaxProcessUpdateRoomExtraDemands() + { + $response = array('success' => false); + if ($idBookingDemand = Tools::getValue('id_booking_demand')) { + if (Validate::isLoadedObject($objBookingDemand = new HotelBookingDemands($idBookingDemand))) { + $unitPrice = Tools::getValue('unit_price'); + if (!$unitPrice || !ValidateCore::isPrice($unitPrice)) { + $response['hasError'] = true; + $response['errors'] = $this->l('Invalid unit price'); + } + $oldPriceTaxExcl = $objBookingDemand->unit_price_tax_excl; + $oldPriceTaxIncl = $objBookingDemand->unit_price_tax_incl; + if ($oldPriceTaxExcl > 0) { + $oldTaxMultiplier = $oldPriceTaxIncl / $oldPriceTaxExcl; + } else { + $oldTaxMultiplier = 1; + } + $objBookingDemand->unit_price_tax_excl = $unitPrice; + $objBookingDemand->unit_price_tax_incl = $unitPrice * $oldTaxMultiplier; + $objBookingDetail = new HotelBookingDetail($objBookingDemand->id_htl_booking); + if ($objBookingDemand->price_calc_method == HotelRoomTypeGlobalDemand::WK_PRICE_CALC_METHOD_EACH_DAY) { + $numDays = $objBookingDetail->getNumberOfDays( + $objBookingDetail->date_from, + $objBookingDetail->date_to + ); + $objBookingDemand->total_price_tax_excl = $objBookingDemand->unit_price_tax_excl * $numDays; + $objBookingDemand->total_price_tax_incl = $objBookingDemand->unit_price_tax_incl * $numDays; + } else { + $objBookingDemand->total_price_tax_excl = $objBookingDemand->unit_price_tax_excl; + $objBookingDemand->total_price_tax_incl = $objBookingDemand->unit_price_tax_incl; + } + if ($objBookingDemand->save()) { + $objBookingDemand->setBookingDemandTaxDetails(1); + + $priceDiffTaxExcl = $objBookingDemand->total_price_tax_excl - $oldPriceTaxExcl; + $priceDiffTaxIncl = $objBookingDemand->total_price_tax_incl - $oldPriceTaxIncl; + + + $order_detail = new OrderDetail($objBookingDetail->id_order_detail); + // Update OrderInvoice of this OrderDetail + if ($order_detail->id_order_invoice != 0) { + // values changes as values are calculated accoding to the quantity of the product by webkul + $order_invoice = new OrderInvoice($order_detail->id_order_invoice); + $order_invoice->total_paid_tax_excl += $priceDiffTaxExcl; + $order_invoice->total_paid_tax_incl += $priceDiffTaxIncl; + $res &= $order_invoice->update(); + } + $order = new Order($objBookingDetail->id_order); + $order->total_paid_tax_excl += $priceDiffTaxExcl; + $order->total_paid_tax_incl += $priceDiffTaxIncl; + $order->total_paid += $priceDiffTaxIncl; if ($order->save()) { $response['facilities_panel'] = $this->processRenderFacilitiesBlock( $order->id, diff --git a/controllers/admin/AdminPPreferencesController.php b/controllers/admin/AdminPPreferencesController.php index ea77232a8..98ec9c7eb 100644 --- a/controllers/admin/AdminPPreferencesController.php +++ b/controllers/admin/AdminPPreferencesController.php @@ -66,7 +66,8 @@ class AdminPPreferencesControllerCore extends AdminController 'hint' => $this->l('The counting of days starts from room type creation date.'), 'validation' => 'isUnsignedInt', 'cast' => 'intval', - 'type' => 'text' + 'type' => 'text', + 'class' => 'fixed-width-xxl', ), /*'PS_CART_REDIRECT' => array( 'title' => $this->l('Redirect after adding product to cart'), @@ -88,6 +89,7 @@ class AdminPPreferencesControllerCore extends AdminController 'cast' => 'intval', 'type' => 'text', 'suffix' => $this->l('characters'), + 'class' => 'fixed-width-xl', ), /*'PS_QTY_DISCOUNT_ON_COMBINATION' => array( 'title' => $this->l('Quantity discounts based on'), @@ -109,7 +111,16 @@ class AdminPPreferencesControllerCore extends AdminController 'cast' => 'intval', 'required' => false, 'type' => 'bool' - ) + ), + 'PS_KPI_BEST_SELLING_ROOM_TYPE_NB_DAYS' => array( + 'title' => $this->l('Number of days to use to calculate best selling room type'), + 'hint' => $this->l('Set the number of days to use to calculate best selling room type.'), + 'validation' => 'isUnsignedInt', + 'cast' => 'intval', + 'type' => 'text', + 'suffix' => $this->l('days'), + 'class' => 'fixed-width-xl', + ), ), 'submit' => array('title' => $this->l('Save')) ), @@ -214,7 +225,8 @@ class AdminPPreferencesControllerCore extends AdminController 'hint' => $this->l('Number of room types displayed per page. Default is 10.'), 'validation' => 'isUnsignedInt', 'cast' => 'intval', - 'type' => 'text' + 'type' => 'text', + 'class' => 'fixed-width-xxl', ), 'PS_PRODUCTS_ORDER_BY' => array( 'title' => $this->l('Default order by'), @@ -309,7 +321,8 @@ class AdminPPreferencesControllerCore extends AdminController ), 'PS_SERVICE_PRODUCT_CATEGORY_FILTER' => array( - 'title' => $this->l('Show category filter for service products'), + 'title' => $this->l('Display services by category'), + 'hint' => $this->l('If enabled, services will be grouped and displayed as per their default category. Else all services will be displayed together.'), 'validation' => 'isBool', 'cast' => 'intval', 'required' => false, @@ -321,7 +334,8 @@ class AdminPPreferencesControllerCore extends AdminController 'hint' => $this->l('Number of hotel images displayed per page. Default is 9.'), 'validation' => 'isUnsignedInt', 'cast' => 'intval', - 'type' => 'text' + 'type' => 'text', + 'class' => 'fixed-width-xxl', ), ), 'submit' => array('title' => $this->l('Save')) diff --git a/controllers/admin/AdminPreferencesController.php b/controllers/admin/AdminPreferencesController.php index 248762512..0881b5616 100644 --- a/controllers/admin/AdminPreferencesController.php +++ b/controllers/admin/AdminPreferencesController.php @@ -95,8 +95,8 @@ class AdminPreferencesControllerCore extends AdminController $fields = array( 'PS_SSL_ENABLED' => array( 'title' => $this->l('Enable SSL'), - 'desc' => $this->l('If you own an SSL certificate for your shop\'s domain name, you can activate SSL encryption (https://) for customer account identification and order processing.'), - 'hint' => $this->l('If you want to enable SSL on all the pages of your shop, activate the "Enable on all the pages" option below.'), + 'desc' => $this->l('If you own an SSL certificate for your website\'s domain name, you can activate SSL encryption (https://) for customer account identification and order processing.'), + 'hint' => $this->l('If you want to enable SSL on all the pages of your website, activate the "Enable on all the pages" option below.'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool', @@ -106,7 +106,7 @@ class AdminPreferencesControllerCore extends AdminController $fields['PS_SSL_ENABLED_EVERYWHERE'] = array( 'title' => $this->l('Enable SSL on all pages'), - 'desc' => $this->l('When enabled, all the pages of your shop will be SSL-secured.'), + 'desc' => $this->l('When enabled, all the pages of your website will be SSL-secured.'), 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool', @@ -221,7 +221,7 @@ class AdminPreferencesControllerCore extends AdminController if (!Tools::usingSecureMode() && !Configuration::get('PS_SSL_ENABLED')) { $fields['PS_SSL_ENABLED']['type'] = 'disabled'; $fields['PS_SSL_ENABLED']['disabled'] = ''. - $this->l('Please click here to check if your shop supports HTTPS.').''; + $this->l('Please click here to check if your website supports HTTPS.').''; } $this->fields_options = array( diff --git a/controllers/admin/AdminProductsController.php b/controllers/admin/AdminProductsController.php index 8d57d805d..3763fb057 100644 --- a/controllers/admin/AdminProductsController.php +++ b/controllers/admin/AdminProductsController.php @@ -2442,61 +2442,104 @@ class AdminProductsControllerCore extends AdminController $time = time(); $kpis = array(); - // if (Configuration::get('PS_STOCK_MANAGEMENT')) { - // $helper = new HelperKpi(); - // $helper->id = 'box-products-stock'; - // $helper->icon = 'icon-archive'; - // $helper->color = 'color1'; - // $helper->title = $this->l('Out of stock items', null, null, false); - // if (ConfigurationKPI::get('PERCENT_PRODUCT_OUT_OF_STOCK') !== false) { - // $helper->value = ConfigurationKPI::get('PERCENT_PRODUCT_OUT_OF_STOCK'); - // } - // $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=percent_product_out_of_stock'; - // $helper->tooltip = $this->l('X% of your room types for sale are out of stock.', null, null, false); - // $helper->refresh = (bool)(ConfigurationKPI::get('PERCENT_PRODUCT_OUT_OF_STOCK_EXPIRE') < $time); - // $helper->href = Context::getContext()->link->getAdminLink('AdminProducts').'&productFilter_sav!quantity=0&productFilter_active=1&submitFilterproduct=1'; - // $kpis[] = $helper->generate(); - // } - $helper = new HelperKpi(); - $helper->id = 'box-avg-gross-margin'; - $helper->icon = 'icon-tags'; - $helper->color = 'color2'; - $helper->title = $this->l('Average Gross Margin %', null, null, false); - $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=product_avg_gross_margin'; - $helper->tooltip = $this->l('Gross margin expressed in percentage assesses how cost-effectively you sell your room types / products. Out of $100, you will retain $X to cover profit and expenses.', null, null, false); - $kpis[] = $helper->generate(); - - $helper = new HelperKpi(); - $helper->id = 'box-8020-sales-catalog'; - $helper->icon = 'icon-beaker'; + $helper->id = 'box-total-rooms'; + $helper->icon = 'icon-bed'; $helper->color = 'color3'; - $helper->title = $this->l('Purchased References', null, null, false); - $helper->subtitle = $this->l('30 days', null, null, false); - $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=8020_sales_catalog'; - $helper->tooltip = $this->l('X% of your references have been purchased for the past 30 days.', null, null, false); - if (Module::isInstalled('statsbestproducts')) { - $helper->href = Context::getContext()->link->getAdminLink('AdminStats').'&module=statsbestproducts&datepickerFrom='.date('Y-m-d', strtotime('-30 days')).'&datepickerTo='.date('Y-m-d'); - } - $kpis[] = $helper->generate(); + $helper->title = $this->l('Total Rooms', null, null, false); + $helper->subtitle = $this->l('Today', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=total_rooms'; + $helper->tooltip = $this->l('The total number of rooms in all hotels.', null, null, false); + $kpis[] = $helper; $helper = new HelperKpi(); - $helper->id = 'box-disabled-products'; - $helper->icon = 'icon-off'; + $helper->id = 'box-occupied-rooms'; + $helper->icon = 'icon-user'; + $helper->color = 'color1'; + $helper->title = $this->l('Occupied Rooms', null, null, false); + $helper->subtitle = $this->l('Today', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=occupied_rooms'; + $helper->tooltip = $this->l('The current count of rooms that are currently occupied by guests.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-vacant-rooms'; + $helper->icon = 'icon-check-empty'; + $helper->color = 'color3'; + $helper->title = $this->l('Vacant Rooms', null, null, false); + $helper->subtitle = $this->l('Today', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=vacant_rooms'; + $helper->tooltip = $this->l('The count of rooms that are currently unoccupied and available for booking.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-reserved-rooms'; + $helper->icon = 'icon-calendar'; $helper->color = 'color4'; - $helper->href = $this->context->link->getAdminLink('AdminProducts'); + $helper->title = $this->l('Booked Rooms', null, null, false); + $helper->subtitle = $this->l('Today', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=booked_rooms'; + $helper->tooltip = $this->l('The number of rooms that are currently booked but not yet occupied.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-disabled-rooms'; + $helper->icon = 'icon-ban'; + $helper->color = 'color2'; + $helper->title = $this->l('Disabled Rooms', null, null, false); + $helper->subtitle = $this->l('Today', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=disabled_rooms'; + $helper->tooltip = $this->l('The number of rooms that are currently disabled.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-online-bookable-rooms'; + $helper->icon = 'icon-globe'; + $helper->color = 'color4'; + $helper->title = $this->l('Online Bookable Rooms', null, null, false); + $helper->subtitle = $this->l('Today', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=online_bookable_rooms'; + $helper->tooltip = $this->l('The total number of rooms that can be booked only using website.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-offline-bookable-rooms'; + $helper->icon = 'icon-building'; + $helper->color = 'color1'; + $helper->title = $this->l('Offline Bookable Rooms', null, null, false); + $helper->subtitle = $this->l('Today', null, null, false); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=offline_bookable_rooms'; + $helper->tooltip = $this->l('The number of rooms that can be booked either through website or offline channels (e.g., phone or in-person).', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-best-selling-room-type'; + $helper->icon = 'icon-star'; + $helper->color = 'color1'; + $helper->title = $this->l('Best Selling', null, null, false); + $nbDaysBestSelling = Validate::isUnsignedInt(Configuration::get('PS_KPI_BEST_SELLING_ROOM_TYPE_NB_DAYS')) ? Configuration::get('PS_KPI_BEST_SELLING_ROOM_TYPE_NB_DAYS') : 30; + $helper->subtitle = sprintf($this->l('%d Days', null, null, false), (int) $nbDaysBestSelling); + $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=best_selling_room_type'; + $helper->tooltip = $this->l('Displays the best selling room type based on the last 30 days of sales.', null, null, false); + $kpis[] = $helper; + + $helper = new HelperKpi(); + $helper->id = 'box-disbled-room-types'; + $helper->icon = 'icon-ban'; + $helper->color = 'color2'; $helper->title = $this->l('Disabled Room Types', null, null, false); - if (ConfigurationKPI::get('DISABLED_ROOM_TYPES') !== false) { - $helper->value = ConfigurationKPI::get('DISABLED_ROOM_TYPES'); - } $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=disabled_room_types'; - $helper->refresh = (bool)(ConfigurationKPI::get('DISABLED_ROOM_TYPES_EXPIRE') < $time); - $helper->tooltip = $this->l('X% of your room types are disabled and not visible to your customers', null, null, false); $helper->href = Context::getContext()->link->getAdminLink('AdminProducts').'&productFilter_sa!active=0&submitFilterproduct=1'; - $kpis[] = $helper->generate(); + $helper->tooltip = $this->l('The total number of room types that are currently disabled.', null, null, false); + $kpis[] = $helper; + + Hook::exec('action'.$this->controller_name.'KPIListingModifier', array( + 'kpis' => &$kpis, + )); $helper = new HelperKpiRow(); $helper->kpis = $kpis; + return $helper->generate(); } diff --git a/controllers/admin/AdminStatsController.php b/controllers/admin/AdminStatsController.php index 16670b951..4b0f4ea0e 100644 --- a/controllers/admin/AdminStatsController.php +++ b/controllers/admin/AdminStatsController.php @@ -176,22 +176,29 @@ class AdminStatsControllerCore extends AdminStatsTabController '.Shop::addSqlAssociation('category', 'c')); } - public static function getDisabledRoomTypes() + public static function getDisabledRoomTypes($idHotel = null) { return (int)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(' SELECT COUNT(*) FROM `'._DB_PREFIX_.'product` p + INNER JOIN `'._DB_PREFIX_.'htl_room_type` hrt + ON (hrt.`id_product` = p.`id_product`) '.Shop::addSqlAssociation('product', 'p').' - WHERE product_shop.active = 0 AND p.`booking_product` = 1'); + WHERE product_shop.active = 0 AND p.`booking_product` = 1'. + (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hrt') : '')); } - public static function getTotalRoomTypes() + public static function getTotalRoomTypes($idHotel = null) { return (int)Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(' SELECT COUNT(*) FROM `'._DB_PREFIX_.'product` p + INNER JOIN `'._DB_PREFIX_.'htl_room_type` hrt + ON (hrt.`id_product` = p.`id_product`) '.Shop::addSqlAssociation('product', 'p').' - WHERE p.`booking_product` = 1'); + WHERE p.`booking_product` = 1'. + (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hrt') : '') + ); } public static function getDisabledProducts() @@ -341,7 +348,7 @@ class AdminStatsControllerCore extends AdminStatsTabController SELECT SUM(IF(c.id_gender IS NOT NULL, 1, 0)) as total, SUM(IF(type = 0, 1, 0)) as male, SUM(IF(type = 1, 1, 0)) as female, SUM(IF(type = 2, 1, 0)) as neutral FROM `'._DB_PREFIX_.'customer` c LEFT JOIN `'._DB_PREFIX_.'gender` g ON c.id_gender = g.id_gender - WHERE c.active = 1 '.Shop::addSqlRestriction()); + WHERE c.active = 1 AND c.deleted = 0 '.Shop::addSqlRestriction()); if (!$row['total']) { return false; @@ -397,16 +404,6 @@ class AdminStatsControllerCore extends AdminStatsTabController return $row; } - public static function getAverageCustomerAge() - { - $value = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(' - SELECT AVG(DATEDIFF("'.date('Y-m-d').' 00:00:00", birthday)) - FROM `'._DB_PREFIX_.'customer` c - WHERE active = 1 - AND birthday IS NOT NULL AND birthday != "0000-00-00" '.Shop::addSqlRestriction()); - return round($value / 365); - } - public static function getPendingMessages() { return CustomerThread::getTotalCustomerThreads('status LIKE "%pending%" OR status = "open"'.Shop::addSqlRestriction()); @@ -547,6 +544,28 @@ class AdminStatsControllerCore extends AdminStatsTabController return $expenses; } + public static function getBestSellingRoomType($dateFrom, $dateTo, $idHotel = null) + { + $sql = 'SELECT p.`id_product`, + ( + SELECT IFNULL(SUM(ROUND((DATEDIFF(LEAST(hbd.`date_to`, "'.pSQL($dateTo).'"), GREATEST(hbd.`date_from`, "'.pSQL($dateFrom).'")) / DATEDIFF(hbd.`date_to`, hbd.`date_from`)) * hbd.`total_price_tax_excl`, 2)), 0) + FROM `'._DB_PREFIX_.'htl_booking_detail` hbd + LEFT JOIN `'._DB_PREFIX_.'orders` o + ON (o.`id_order` = hbd.`id_order`) + WHERE hbd.`id_product` = p.`id_product` AND o.`valid` = 1 + AND hbd.`date_to` > "'.pSQL($dateFrom).'" AND hbd.`date_from` < "'.pSQL($dateTo).'" + ) AS totalRevenue + FROM `'._DB_PREFIX_.'product` p + INNER JOIN `'._DB_PREFIX_.'htl_room_type` hrt + ON (hrt.`id_product` = p.`id_product`) + WHERE p.`active` = 1 AND p.`booking_product` = 1'. + (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hrt') : '').' + HAVING totalRevenue > 0 + ORDER BY totalRevenue DESC'; + + return Db::getInstance()->getValue($sql); + } + public function displayAjaxGetKpi() { $value = $this->getLatestKpiValue(Tools::getValue('kpi')); @@ -566,17 +585,20 @@ class AdminStatsControllerCore extends AdminStatsTabController $value = false; switch ($kpi) { case 'conversion_rate': - $visitors = AdminStatsController::getVisits(true, date('Y-m-d', strtotime('-31 day')), date('Y-m-d', strtotime('-1 day')), false /*'day'*/); - $orders = AdminStatsController::getOrders(date('Y-m-d', strtotime('-31 day')), date('Y-m-d', strtotime('-1 day')), false /*'day'*/); + $nbDaysConversionRate = Validate::isUnsignedInt(Configuration::get('PS_KPI_CONVERSION_RATE_NB_DAYS')) ? Configuration::get('PS_KPI_CONVERSION_RATE_NB_DAYS') : 30; - // $data = array(); - // $from = strtotime(date('Y-m-d 00:00:00', strtotime('-31 day'))); - // $to = strtotime(date('Y-m-d 23:59:59', strtotime('-1 day'))); - // for ($date = $from; $date <= $to; $date = strtotime('+1 day', $date)) - // if (isset($visitors[$date]) && $visitors[$date]) - // $data[$date] = round(100 * ((isset($orders[$date]) && $orders[$date]) ? $orders[$date] : 0) / $visitors[$date], 2); - // else - // $data[$date] = 0; + $visitors = AdminStatsController::getVisits( + true, + date('Y-m-d', strtotime('-'.($nbDaysConversionRate + 1).' day')), + date('Y-m-d', strtotime('-1 day')), + false /*'day'*/ + ); + + $orders = AdminStatsController::getOrders( + date('Y-m-d', strtotime('-'.($nbDaysConversionRate + 1).' day')), + date('Y-m-d', strtotime('-1 day')), + false /*'day'*/ + ); $visits_sum = $visitors; //array_sum($visitors); $orders_sum = $orders; //array_sum($orders); @@ -629,11 +651,7 @@ class AdminStatsControllerCore extends AdminStatsTabController break; case 'disabled_room_types': - if (AdminStatsController::getTotalRoomTypes()) { - $value = round(100 * AdminStatsController::getDisabledRoomTypes() / AdminStatsController::getTotalRoomTypes(), 2).'%'; - } else { - $value = '0%'; - } + $value = AdminStatsController::getDisabledRoomTypes(0); ConfigurationKPI::updateValue('DISABLED_ROOM_TYPES', $value); ConfigurationKPI::updateValue('DISABLED_ROOM_TYPES_EXPIRE', strtotime('+2 hour')); break; @@ -670,10 +688,6 @@ class AdminStatsControllerCore extends AdminStatsTabController break; - case 'avg_customer_age': - $value = sprintf($this->l('%d years', null, null, false), AdminStatsController::getAverageCustomerAge(), 1); - break; - case 'pending_messages': $value = (int)AdminStatsController::getPendingMessages(); break; @@ -696,22 +710,6 @@ class AdminStatsControllerCore extends AdminStatsTabController $value = round(AdminStatsController::getMessagesPerThread(date('Y-m-d', strtotime('-31 day')), date('Y-m-d', strtotime('-1 day'))), 1); break; - case 'newsletter_registrations': - $value = Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(' - SELECT COUNT(*) - FROM `'._DB_PREFIX_.'customer` - WHERE newsletter = 1 - '.Shop::addSqlRestriction(Shop::SHARE_ORDER)); - if (Module::isInstalled('blocknewsletter')) { - $value += Db::getInstance(_PS_USE_SQL_SLAVE_)->getValue(' - SELECT COUNT(*) - FROM `'._DB_PREFIX_.'newsletter` - WHERE active = 1 - '.Shop::addSqlRestriction(Shop::SHARE_ORDER)); - } - - break; - case 'enabled_languages': $value = Language::countActiveLanguages(); break; @@ -798,17 +796,106 @@ class AdminStatsControllerCore extends AdminStatsTabController } break; + + case 'best_selling_room_type': + $nbDaysBestSelling = Validate::isUnsignedInt(Configuration::get('PS_KPI_BEST_SELLING_ROOM_TYPE_NB_DAYS')) ? Configuration::get('PS_KPI_BEST_SELLING_ROOM_TYPE_NB_DAYS') : 30; + + if (!($idProduct = AdminStatsController::getBestSellingRoomType( + date('Y-m-d', strtotime('-'.($nbDaysBestSelling + 1).' day')), + date('Y-m-d', strtotime('-1 day')), + 0 + ))) { + $value = $this->l('--', null, null, false); + } else { + $objProduct = new Product($idProduct, false, $this->context->language->id); + $value = $objProduct->name; + } + + break; + + case 'total_rooms': + $value = AdminStatsController::getTotalRooms(0); + + break; + + case 'occupied_rooms': + $value = AdminStatsController::getOccupiedRooms(0); + + break; + + case 'vacant_rooms': + $totalAvailableRooms = AdminStatsController::getAvailableRoomsForDiscreteDates(date('Y-m-d'), null, 0); + $totalAvailableRooms = $totalAvailableRooms[strtotime(date('Y-m-d'))]; + $totalOccupiedRooms = AdminStatsController::getOccupiedRooms(0); + $value = $totalAvailableRooms - $totalOccupiedRooms; + + break; + + case 'booked_rooms': + $value = AdminStatsController::getBookedRooms(0); + + break; + + case 'disabled_rooms': + $value = AdminStatsController::getDisabledRoomsForDiscreteDates(date('Y-m-d'), null, 0); + $value = $value[strtotime(date('Y-m-d'))]; + + break; + + case 'online_bookable_rooms': + $value = AdminStatsController::getAvailableRoomsForDiscreteDates(date('Y-m-d'), null, 0, 1); + $value = $value[strtotime(date('Y-m-d'))]; + + break; + + case 'offline_bookable_rooms': + $value = AdminStatsController::getAvailableRoomsForDiscreteDates(date('Y-m-d'), null, 0); + $value = $value[strtotime(date('Y-m-d'))]; + + break; + + case 'total_frequent_customers': + $nbOrdersFrequentCustomers = Configuration::get('PS_KPI_FREQUENT_CUSTOMER_NB_ORDERS'); + + $value = AdminStatsController::getTotalFrequentCustomers($nbOrdersFrequentCustomers, 0); + + break; + + case 'revenue_per_available_customer': + $nbDaysRevPac = Configuration::get('PS_KPI_REVPAC_NB_DAYS'); + + $value = AdminStatsController::getRevenuePerAvailableCustomer( + date('Y-m-d', strtotime('-'.($nbDaysRevPac + 1).' day')), + date('Y-m-d', strtotime('-1 day')), + 0 + ); + + $value = Tools::displayPrice($value, $currency); + + break; + + case 'total_newsletter_registrations': + $value = AdminStatsController::getTotalNewsletterRegistrations(); + + break; + + case 'total_new_customers': + $nbDaysNewCustomers = Validate::isUnsignedInt(Configuration::get('PS_KPI_NEW_CUSTOMERS_NB_DAYS')) ? Configuration::get('PS_KPI_NEW_CUSTOMERS_NB_DAYS') : 30; + + $value = AdminStatsController::getTotalNewCustomers($nbDaysNewCustomers); + + break; + + case 'total_banned_customers': + $value = AdminStatsController::getTotalBannedCustomers(); + + break; + default: $value = false; } - if ($value !== false) { - $array = array('value' => $value); - if (isset($data)) { - $array['data'] = $data; - } - die(json_encode($array)); - } - die(json_encode(array('has_errors' => true))); + + return $value; } public static function getFrontOfficeTranslations() @@ -936,12 +1023,16 @@ class AdminStatsControllerCore extends AdminStatsTabController ); } - public static function getTotalRooms($idHotel = false) + public static function getTotalRooms($idHotel = null) { - return Db::getInstance()->getValue( - 'SELECT COUNT(`id`) FROM `'._DB_PREFIX_.'htl_room_information` hri - WHERE 1'.HotelBranchInformation::addHotelRestriction($idHotel, 'hri') - ); + $sql = 'SELECT COUNT(hri.`id`) + FROM `'._DB_PREFIX_.'htl_room_information` hri + INNER JOIN `'._DB_PREFIX_.'product` p + ON (p.`id_product` = hri.`id_product`) + WHERE p.`booking_product` = 1 '. + (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hri') : ''); + + return Db::getInstance()->getValue($sql); } public static function getOccupancyData($dateFrom, $dateTo, $idsHotel = false) @@ -1099,12 +1190,12 @@ class AdminStatsControllerCore extends AdminStatsTabController public static function getRevenue($dateFrom, $dateTo, $idHotel = false, $orderSource = '') { $sql = 'SELECT SUM(total_paid_tax_excl - refunded_amount) - FROM (SELECT o.`total_paid_tax_excl` / o.`conversion_rate` AS total_paid_tax_excl, - IFNULL(orr.`refunded_amount`, 0) AS refunded_amount, - (SELECT hbd.`id_hotel` FROM`'._DB_PREFIX_.'htl_booking_detail` hbd WHERE hbd.`id_order` = o.`id_order` LIMIT 1) AS id_hotel - FROM `'._DB_PREFIX_.'orders` o - LEFT JOIN `' ._DB_PREFIX_.'order_return` orr ON orr.`id_order` = o.`id_order` - WHERE o.`valid` = 1 AND o.`invoice_date` BETWEEN "'.pSQL($dateFrom).' 00:00:00" AND "'.pSQL($dateTo).' 23:59:59"'; + FROM (SELECT o.`total_paid_tax_excl` / o.`conversion_rate` AS total_paid_tax_excl, + (SELECT IFNULL(SUM(orr.`refunded_amount`), 0) FROM`'._DB_PREFIX_.'order_return` orr WHERE orr.`id_order` = o.`id_order`) AS refunded_amount, + (SELECT hbd.`id_hotel` FROM`'._DB_PREFIX_.'htl_booking_detail` hbd WHERE hbd.`id_order` = o.`id_order` LIMIT 1) AS id_hotel + FROM `'._DB_PREFIX_.'orders` o + + WHERE o.`valid` = 1 AND o.`invoice_date` BETWEEN "'.pSQL($dateFrom).' 00:00:00" AND "'.pSQL($dateTo).' 23:59:59"'; if ($orderSource) { $sql .= ' AND o.`source` = "'.pSQL($orderSource).'"'; @@ -1482,7 +1573,7 @@ class AdminStatsControllerCore extends AdminStatsTabController /** * total available (unoccupied) for each date */ - public static function getAvailableRoomsForDiscreteDates($dateFrom, $dateTo = null, $idHotel = null, $useCache = true) + public static function getAvailableRoomsForDiscreteDates($dateFrom, $dateTo = null, $idHotel = null, $showAtFront = null, $useCache = true) { $dateTo = !$dateTo ? date('Y-m-d', strtotime('+1 day', strtotime($dateFrom))) : $dateTo; $discreteDates = array(); @@ -1500,7 +1591,7 @@ class AdminStatsControllerCore extends AdminStatsTabController $result = array(); foreach ($discreteDates as $discreteDate) { $cacheKey = 'AdminStats::getAvailableRoomsForDiscreteDates'.'_'.(int) $discreteDate['timestamp_from'].'_'. - (!is_array($idHotel) ? (int) $idHotel : implode('_', $idHotel)); + (is_null($showAtFront) ? 'null_' : (int) $showAtFront).(!is_array($idHotel) ? (int) $idHotel : implode('_', $idHotel)); if (!Cache::isStored($cacheKey) || !$useCache) { $sql = 'SELECT (num_total - num_booked - num_inactive - num_temporarily_inactive) AS num_available FROM ( @@ -1510,6 +1601,7 @@ class AdminStatsControllerCore extends AdminStatsTabController LEFT JOIN `'._DB_PREFIX_.'product` p ON (p.`id_product` = hri.`id_product`) WHERE p.`active` = 1'. + (!is_null($showAtFront) ? ' AND p.`show_at_front` = '.(int) $showAtFront : ''). (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hri') : '').' ) AS num_total, ( @@ -1519,11 +1611,77 @@ class AdminStatsControllerCore extends AdminStatsTabController ON (hri.`id` = hbd.`id_room`) LEFT JOIN `'._DB_PREFIX_.'product` p ON (p.`id_product` = hri.`id_product`) - WHERE p.`active` = 1 + WHERE p.`active` = 1'. + (!is_null($showAtFront) ? ' AND p.`show_at_front` = '.(int) $showAtFront : '').' AND hbd.`is_refunded` = 0 AND hbd.`date_from` < "'.pSQL($discreteDate['date_to']).' 00:00:00" AND hbd.`date_to` > "'.pSQL($discreteDate['date_from']).' 00:00:00"'. (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hbd') : '').' ) AS num_booked, + ( + SELECT IFNULL(COUNT(hri.`id`), 0) + FROM `'._DB_PREFIX_.'htl_room_information` hri + LEFT JOIN `'._DB_PREFIX_.'product` p + ON (p.`id_product` = hri.`id_product`) + WHERE hri.`id_status` = '.(int) HotelRoomInformation::STATUS_INACTIVE.' + AND p.`active` = 1'. + (!is_null($showAtFront) ? ' AND p.`show_at_front` = '.(int) $showAtFront : ''). + (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hri') : '').' + ) AS num_inactive, + ( + SELECT IFNULL(COUNT(hri.`id`), 0) + FROM `'._DB_PREFIX_.'htl_room_information` hri + LEFT JOIN `'._DB_PREFIX_.'htl_room_disable_dates` hrdd + ON (hrdd.`id_room` = hri.`id`) + LEFT JOIN `'._DB_PREFIX_.'product` p + ON (p.`id_product` = hri.`id_product`) + WHERE hri.`id_status` = '.(int) HotelRoomInformation::STATUS_TEMPORARY_INACTIVE.' + AND ("'.pSQL($discreteDate['date_from']).'" >= hrdd.`date_from` AND "'.pSQL($discreteDate['date_from']).'" < hrdd.`date_to`) + AND p.`active` = 1'. + (!is_null($showAtFront) ? ' AND p.`show_at_front` = '.(int) $showAtFront : ''). + (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hri') : '').' + ) AS num_temporarily_inactive + ) AS t'; + + $value = Db::getInstance()->getValue($sql); + Cache::store($cacheKey, $value); + } + + $result[$discreteDate['timestamp_from']] = Cache::retrieve($cacheKey); + } + + return $result; + } + + public static function getDisabledRoomsForDiscreteDates($dateFrom, $dateTo = null, $idHotel = null, $useCache = true) + { + $dateTo = !$dateTo ? date('Y-m-d', strtotime('+1 day', strtotime($dateFrom))) : $dateTo; + $discreteDates = array(); + $dateTemp = $dateFrom; + while ($dateTemp <= $dateTo) { + $dateNext = date('Y-m-d', strtotime('+1 day', strtotime($dateTemp))); + $discreteDates[] = array( + 'date_from' => $dateTemp, + 'date_to' => $dateNext, + 'timestamp_from' => strtotime($dateTemp), + ); + $dateTemp = $dateNext; + }; + + $result = array(); + foreach ($discreteDates as $discreteDate) { + $cacheKey = 'AdminStats::getDisabledRoomsForDiscreteDates'.'_'.(int) $discreteDate['timestamp_from'].'_'. + (!is_array($idHotel) ? (int) $idHotel : implode('_', $idHotel)); + if (!Cache::isStored($cacheKey) || !$useCache) { + $sql = 'SELECT (num_room_type_disabled + num_inactive + num_temporarily_inactive) AS num_disabled + FROM ( + SELECT ( + SELECT IFNULL(COUNT(hri.`id`), 0) + FROM `'._DB_PREFIX_.'htl_room_information` hri + LEFT JOIN `'._DB_PREFIX_.'product` p + ON (p.`id_product` = hri.`id_product`) + WHERE p.`active` = 0'. + (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hri') : '').' + ) AS num_room_type_disabled, ( SELECT IFNULL(COUNT(hri.`id`), 0) FROM `'._DB_PREFIX_.'htl_room_information` hri @@ -1557,6 +1715,121 @@ class AdminStatsControllerCore extends AdminStatsTabController return $result; } + /** + * Returns the number of rooms that have been checked-in. + */ + public static function getOccupiedRooms($idHotel = null) + { + $sql = 'SELECT COUNT(DISTINCT hbd.`id_room`) + FROM `'._DB_PREFIX_.'htl_booking_detail` hbd + LEFT JOIN `'._DB_PREFIX_.'htl_room_information` hri + ON (hri.`id` = hbd.`id_room`) + LEFT JOIN `'._DB_PREFIX_.'product` p + ON (p.`id_product` = hri.`id_product`) + WHERE p.`active` = 1 + AND hbd.`is_refunded` = 0 + AND hbd.`is_cancelled` = 0 + AND hbd.`id_status` = '.(int) HotelBookingDetail::STATUS_CHECKED_IN. + (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hbd') : ''); + $result = Db::getInstance()->getValue($sql); + + return $result; + } + + /** + * Returns the number of rooms that have been booked but not checked-in yet. + */ + public static function getBookedRooms($idHotel = null) + { + $dateToday = date('Y-m-d'); + + $sql = 'SELECT COUNT(DISTINCT hbd.`id_room`) + FROM `'._DB_PREFIX_.'htl_booking_detail` hbd + WHERE hbd.`is_refunded` = 0 + AND hbd.`is_cancelled` = 0 + AND hbd.`id_status` = '.(int) HotelBookingDetail::STATUS_ALLOTED.' + AND hbd.`date_from` = "'.pSQL($dateToday).' 00:00:00"'. + (!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hbd') : ''); + $result = Db::getInstance()->getValue($sql); + + return $result; + } + + public static function getTotalFrequentCustomers($nbOrders = 5, $idHotel = null) + { + $sql = 'SELECT COUNT(t.`id_customer`) + FROM ( + SELECT o.`id_customer`, COUNT(o.`id_order`) AS nb_orders, + ( + SELECT hbd.`id_hotel` + FROM `'._DB_PREFIX_.'htl_booking_detail` hbd + WHERE hbd.`id_order` = o.`id_order` LIMIT 1 + ) AS id_hotel + FROM `'._DB_PREFIX_.'orders` o + WHERE o.`valid` = 1 + GROUP BY o.`id_customer` + HAVING 1 '.(!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel) : '').' + ) AS t + WHERE t.`nb_orders` >= '.(int) $nbOrders; + $result = Db::getInstance()->getValue($sql); + + return $result; + } + + public static function getRevenuePerAvailableCustomer($dateFrom, $dateTo, $idHotel = null) + { + $totalRevenue = self::getRevenue($dateFrom, $dateTo, $idHotel); + + $totalCustomers = Db::getInstance()->getValue( + 'SELECT COUNT(c.`id_customer`) + FROM `'._DB_PREFIX_.'customer` c + WHERE c.`date_add` <= "'.pSQL($dateTo).' 23:59:59"' + ); + + return $totalCustomers ? $totalRevenue / $totalCustomers : 0; + } + + public static function getTotalNewsletterRegistrations() + { + $customerRegistrations = Db::getInstance()->getValue( + 'SELECT COUNT(c.`id_customer`) + FROM `'._DB_PREFIX_.'customer` c + WHERE c.`newsletter` = 1' + ); + + $visitorRegistrations = 0; + if (Module::isInstalled('blocknewsletter')) { + $visitorRegistrations = Db::getInstance()->getValue( + 'SELECT COUNT(n.`id`) + FROM `'._DB_PREFIX_.'newsletter` n + WHERE n.`active` = 1' + ); + } + + return $customerRegistrations + $visitorRegistrations; + } + + public static function getTotalNewCustomers($nbDaysNewCustomers) + { + $maxDateAdd = date('Y-m-d H:i:s', strtotime('-'.$nbDaysNewCustomers.' day')); + $sql = 'SELECT COUNT(c.`id_customer`) + FROM `'._DB_PREFIX_.'customer` c + WHERE c.`date_add` >= "'.pSQL($maxDateAdd).'" AND c.`deleted` = 0'; + $result = Db::getInstance()->getValue($sql); + + return $result; + } + + public static function getTotalBannedCustomers() + { + $sql = 'SELECT COUNT(c.`id_customer`) + FROM `'._DB_PREFIX_.'customer` c + WHERE c.`deleted` = 1'; + $result = Db::getInstance()->getValue($sql); + + return $result; + } + public static function getOccupancyRateForDiscreteDates($dateFrom, $dateTo, $idHotel = null) { if ($dateFrom == $dateTo) { diff --git a/controllers/admin/AdminTranslationsController.php b/controllers/admin/AdminTranslationsController.php index 73d3addbf..5f275f62d 100644 --- a/controllers/admin/AdminTranslationsController.php +++ b/controllers/admin/AdminTranslationsController.php @@ -1366,7 +1366,7 @@ class AdminTranslationsControllerCore extends AdminController $helper->href = $this->context->link->getAdminLink('AdminLanguages'); $helper->title = $this->l('Enabled Languages', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=enabled_languages'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-fo-translations'; @@ -1374,7 +1374,7 @@ class AdminTranslationsControllerCore extends AdminController $helper->color = 'color3'; $helper->title = $this->l('Front office Translations', null, null, false); $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=frontoffice_translations'; - $kpis[] = $helper->generate(); + $kpis[] = $helper; $helper = new HelperKpi(); $helper->id = 'box-bo-translations'; @@ -1386,7 +1386,11 @@ class AdminTranslationsControllerCore extends AdminController } $helper->source = $this->context->link->getAdminLink('AdminStats').'&ajax=1&action=getKpi&kpi=backoffice_translations'; $helper->refresh = (bool)(ConfigurationKPI::get('BACKOFFICE_TRANSLATIONS_EXPIRE') < $time); - $kpis[] = $helper->generate(); + $kpis[] = $helper; + + Hook::exec('action'.$this->controller_name.'KPIListingModifier', array( + 'kpis' => &$kpis, + )); $helper = new HelperKpiRow(); $helper->kpis = $kpis; diff --git a/controllers/front/HistoryController.php b/controllers/front/HistoryController.php index e3a8210e4..8e41935c8 100644 --- a/controllers/front/HistoryController.php +++ b/controllers/front/HistoryController.php @@ -66,6 +66,7 @@ class HistoryControllerCore extends FrontController } $this->context->smarty->assign(array( 'orders' => $orders, + 'overbooking_order_states' => OrderState::getOverBookingStates(), 'invoiceAllowed' => (int)Configuration::get('PS_INVOICE'), 'reorderingAllowed' => !(bool)Configuration::get('PS_DISALLOW_HISTORY_REORDERING'), 'slowValidation' => Tools::isSubmit('slowvalidation') diff --git a/controllers/front/OrderDetailController.php b/controllers/front/OrderDetailController.php index 96a3656f3..3f9f89187 100644 --- a/controllers/front/OrderDetailController.php +++ b/controllers/front/OrderDetailController.php @@ -679,6 +679,7 @@ class OrderDetailControllerCore extends FrontController 'invoice' => (OrderState::invoiceAvailable($id_order_state) && count($order->getInvoicesCollection())), 'logable' => (bool) $order_status->logable, 'order_history' => $order->getHistory($this->context->language->id, false, true), + 'overbooking_order_states' => OrderState::getOverBookingStates(), 'products' => $products, 'discounts' => $order->getCartRules(), 'carrier' => $carrier, diff --git a/install/data/img/os/Overbooking_partial_paid.gif b/install/data/img/os/Overbooking_partial_paid.gif new file mode 100644 index 000000000..7a8f831fc Binary files /dev/null and b/install/data/img/os/Overbooking_partial_paid.gif differ diff --git a/install/data/xml/configuration.xml b/install/data/xml/configuration.xml index 67b8602c1..007ea37e4 100644 --- a/install/data/xml/configuration.xml +++ b/install/data/xml/configuration.xml @@ -95,6 +95,9 @@ 20 + + 30 + 0 @@ -332,6 +335,12 @@ 0 + + 1 + + + 0 + 1 @@ -431,6 +440,9 @@ 11 + + 12 + @@ -884,6 +896,18 @@ 1 + + 5 + + + 30 + + + 30 + + + 30 + 0 diff --git a/install/data/xml/order_state.xml b/install/data/xml/order_state.xml index a1f70e4ec..cc016ffcd 100644 --- a/install/data/xml/order_state.xml +++ b/install/data/xml/order_state.xml @@ -26,7 +26,7 @@ - -