From 7056b6e4e1484d02a9c82b90b257d03bc3883bd7 Mon Sep 17 00:00:00 2001 From: Sumit Panwar Date: Fri, 29 Sep 2023 14:51:08 +0530 Subject: [PATCH 1/6] Improved overbooking workflow and resolve bugs in overbooking management --- .../default/sass/controllers/_order.sass | 7 + .../default/sass/modules/_variables.sass | 2 +- .../default/sass/partials/_content.sass | 6 +- .../helpers/options/options.tpl | 7 +- .../controllers/orders/_overbookings.tpl | 93 ++++ .../orders/_resolvable_overbookings.tpl | 69 +++ .../controllers/orders/helpers/view/view.tpl | 10 +- classes/PaymentModule.php | 172 ++++++- classes/order/Order.php | 4 + classes/order/OrderState.php | 9 + .../admin/AdminOrderPreferencesController.php | 80 ++- controllers/admin/AdminOrdersController.php | 88 ++-- controllers/front/HistoryController.php | 1 + controllers/front/OrderDetailController.php | 1 + .../data/img/os/Overbooking_partial_paid.gif | Bin 0 -> 1357 bytes install/data/xml/configuration.xml | 9 + install/data/xml/order_state.xml | 6 +- install/langs/bg/data/order_state.xml | 8 +- install/langs/bn/data/order_state.xml | 8 +- install/langs/br/data/order_state.xml | 8 +- install/langs/cs/data/order_state.xml | 8 +- install/langs/de/data/order_state.xml | 8 +- install/langs/en/data/order_state.xml | 8 +- install/langs/es/data/order_state.xml | 8 +- install/langs/fa/data/order_state.xml | 8 +- install/langs/fr/data/order_state.xml | 8 +- install/langs/he/data/order_state.xml | 8 +- install/langs/hr/data/order_state.xml | 8 +- install/langs/hu/data/order_state.xml | 8 +- install/langs/id/data/order_state.xml | 8 +- install/langs/it/data/order_state.xml | 8 +- install/langs/lt/data/order_state.xml | 8 +- install/langs/mk/data/order_state.xml | 8 +- install/langs/nl/data/order_state.xml | 8 +- install/langs/no/data/order_state.xml | 8 +- install/langs/pl/data/order_state.xml | 8 +- install/langs/pt/data/order_state.xml | 8 +- install/langs/qc/data/order_state.xml | 8 +- install/langs/ro/data/order_state.xml | 8 +- install/langs/ru/data/order_state.xml | 8 +- install/langs/si/data/order_state.xml | 8 +- install/langs/sr/data/order_state.xml | 8 +- install/langs/sv/data/order_state.xml | 8 +- install/langs/tw/data/order_state.xml | 8 +- install/langs/vn/data/order_state.xml | 8 +- install/langs/zh/data/order_state.xml | 8 +- js/admin.js | 8 + js/admin/orders.js | 5 + mails/en/overbooking.html | 381 ++++++++++++++ mails/en/overbooking.txt | 60 +++ mails/en/overbooking_admin.html | 353 +++++++++++++ mails/en/overbooking_admin.txt | 51 ++ .../bankwire/controllers/front/validation.php | 5 +- .../cheque/controllers/front/validation.php | 5 +- .../classes/HotelBookingDetail.php | 482 ++++++++++++++---- themes/hotel-reservation-theme/history.tpl | 6 +- .../hotel-reservation-theme/order-detail.tpl | 10 +- 57 files changed, 1911 insertions(+), 251 deletions(-) create mode 100644 admin/themes/default/template/controllers/orders/_overbookings.tpl create mode 100644 admin/themes/default/template/controllers/orders/_resolvable_overbookings.tpl create mode 100644 install/data/img/os/Overbooking_partial_paid.gif create mode 100755 mails/en/overbooking.html create mode 100644 mails/en/overbooking.txt create mode 100755 mails/en/overbooking_admin.html create mode 100644 mails/en/overbooking_admin.txt diff --git a/admin/themes/default/sass/controllers/_order.sass b/admin/themes/default/sass/controllers/_order.sass index c5ccaa579..7a73756f5 100644 --- a/admin/themes/default/sass/controllers/_order.sass +++ b/admin/themes/default/sass/controllers/_order.sass @@ -16,3 +16,10 @@ img max-height: 55px max-width: 55px + +#customer_cart_details .overbooked_room + background-color: #FF69B4 + color: #FFF + padding: 2px 5px + border-radius: 2px + font-weight: 600 \ No newline at end of file diff --git a/admin/themes/default/sass/modules/_variables.sass b/admin/themes/default/sass/modules/_variables.sass index 8289f4f1d..58e03e7f9 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 2a765f62e..bf3385e32 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/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/_resolvable_overbookings.tpl b/admin/themes/default/template/controllers/orders/_resolvable_overbookings.tpl new file mode 100644 index 000000000..785d67bdd --- /dev/null +++ b/admin/themes/default/template/controllers/orders/_resolvable_overbookings.tpl @@ -0,0 +1,69 @@ +{* +* 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'} +
+
+
+

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

+

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

+
+
+ + + + + + + + + + {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/helpers/view/view.tpl b/admin/themes/default/template/controllers/orders/helpers/view/view.tpl index b72c4e4cb..5b6f4ba02 100644 --- a/admin/themes/default/template/controllers/orders/helpers/view/view.tpl +++ b/admin/themes/default/template/controllers/orders/helpers/view/view.tpl @@ -104,6 +104,10 @@ + + {* Overbookings information of the order *} + {include file='controllers/orders/_overbookings.tpl'} +
@@ -1509,6 +1513,7 @@ +
@@ -1549,6 +1554,7 @@ +
@@ -1979,6 +1985,7 @@ $('#mySwappigModal').on('shown.bs.modal', function (e) { + $(".modal_id_htl_booking").val(e.relatedTarget.dataset.id_htl_booking); $(".modal_id_order").val(e.relatedTarget.dataset.id_order); $(".modal_date_from").val(e.relatedTarget.dataset.date_from); $(".modal_date_to").val(e.relatedTarget.dataset.date_to); @@ -1993,7 +2000,8 @@ html = ''; $(".swap_avail_rooms_container").empty().append(html); diff --git a/classes/PaymentModule.php b/classes/PaymentModule.php index 3e89319f2..0d8eb5d19 100644 --- a/classes/PaymentModule.php +++ b/classes/PaymentModule.php @@ -776,15 +776,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) { @@ -792,8 +793,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; @@ -805,12 +865,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, @@ -1006,17 +1063,45 @@ abstract class PaymentModuleCore extends Module $new_history = new OrderHistory(); $new_history->id_order = (int)$order->id; if ($order_status->logable && $order->is_advance_payment && $order->advance_paid_amount < $order->total_paid_tax_incl) { - $new_history->changeIdOrderState((int)Configuration::get('PS_OS_PARTIAL_PAYMENT'), $order, true); - } else { - $new_history->changeIdOrderState((int)$id_order_state, $order, true); + $id_order_state = (int)Configuration::get('PS_OS_PARTIAL_PAYMENT'); } + $new_history->changeIdOrderState((int)$id_order_state, $order, true); $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(); } @@ -1190,8 +1275,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; @@ -1199,8 +1297,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, @@ -1221,8 +1319,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, @@ -1239,6 +1337,15 @@ abstract class PaymentModuleCore extends Module // send superadmin information if (Validate::isLoadedObject($superAdmin = new Employee(_PS_ADMIN_PROFILE_))) { if (Validate::isEmail($superAdmin->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'; + } + $data['{customer_name}'] = $this->context->customer->firstname.' '.$this->context->customer->lastname; $data['{customer_email}'] = $this->context->customer->email; $data['{firstname}'] = $superAdmin->firstname; @@ -1246,8 +1353,8 @@ abstract class PaymentModuleCore extends Module $data['{email}'] = $superAdmin->email; Mail::Send( (int)$order->id_lang, - 'order_conf_admin', - Mail::l('Order confirmation', (int)$order->id_lang), + $template, + $subject, $data, $superAdmin->email, $superAdmin->firstname.' '.$superAdmin->lastname, @@ -1263,6 +1370,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}'] = ''; @@ -1272,8 +1387,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, @@ -1288,6 +1403,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['id_profile'] != _PS_ADMIN_PROFILE_) { if ($htlEmployees = Employee::getEmployeesByProfile($access['id_profile'])) { @@ -1299,8 +1424,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'], @@ -1318,7 +1443,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/order/Order.php b/classes/order/Order.php index 79ba75e60..eef2498c0 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; + /** @var int Delivery address id */ public $id_address_delivery; 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/AdminOrderPreferencesController.php b/controllers/admin/AdminOrderPreferencesController.php index 21b31f6a7..78ae5fd9e 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') + ) + ); + $this->fields_options = array( 'order_restrict' => array( 'title' => $this->l('Order Restrict'), @@ -163,33 +175,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' diff --git a/controllers/admin/AdminOrdersController.php b/controllers/admin/AdminOrdersController.php index 63a271ecd..504f3d75f 100644 --- a/controllers/admin/AdminOrdersController.php +++ b/controllers/admin/AdminOrdersController.php @@ -251,8 +251,10 @@ class AdminOrdersControllerCore extends AdminController $this->access_where = ' WHERE hbd.id_hotel IN ('.implode(',', $acsHtls).')'; } - parent::__construct(); + + // overbooking success status + $this->_conf[51] = $this->l('Overbooking is successfully resolved'); } public static function setOrderCurrency($echo, $tr) @@ -261,6 +263,18 @@ class AdminOrdersControllerCore extends AdminController return Tools::displayPrice($echo, (int)$order->id_currency); } + public function initContent() + { + if (!$this->display) { + $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'); + } + } + parent::initContent(); + } + public function renderForm() { if (Context::getContext()->shop->getContext() != Shop::CONTEXT_SHOP && Shop::isFeatureActive()) { @@ -536,33 +550,23 @@ class AdminOrdersControllerCore extends AdminController // by webkul for reallocation of rooms if (Tools::isSubmit('realloc_allocated_rooms')) { if ($this->tabAccess['edit'] === '1') { - $order_id = Tools::getValue('id_order'); - $current_room_id = Tools::getValue('modal_id_room'); - $current_room = Tools::getValue('modal_curr_room_num'); - $date_from = Tools::getValue('modal_date_from'); - $date_to = Tools::getValue('modal_date_to'); - $realloc_room_id = Tools::getValue('realloc_avail_rooms'); + $idOrder = Tools::getValue('id_order'); + $idHtlBookingFrom = Tools::getValue('id_htl_booking'); + $idRoomToReallocate = Tools::getValue('realloc_avail_rooms'); - if ($realloc_room_id == 0) { + if ($idRoomToReallocate == 0) { $this->errors[] = Tools::displayError('Please select a room to swap with this room.'); } - if ($current_room_id == 0) { - $this->errors[] = Tools::displayError('Cuurent room is missing.'); - } - if ($date_from == 0) { - $this->errors[] = Tools::displayError('Check In date is missing.'); - } - if ($date_to == 0) { - $this->errors[] = Tools::displayError('Check Out date is missing.'); + if ($idHtlBookingFrom == 0) { + $this->errors[] = Tools::displayError('Current room information is missing.'); } if (!count($this->errors)) { $objBookingDetail = new HotelBookingDetail(); - $room_swapped = $objBookingDetail->reallocateRoomWithAvailableSameRoomType($current_room_id, $date_from, $date_to, $realloc_room_id); - if (!$room_swapped) { - $this->errors[] = Tools::displayError('Some error occured. Please try again.'); + if ($objBookingDetail->reallocateBooking($idHtlBookingFrom, $idRoomToReallocate)) { + Tools::redirectAdmin(self::$currentIndex.'&id_order='.(int) $idOrder.'&vieworder&conf=4&token='.$this->token); } else { - Tools::redirectAdmin(self::$currentIndex.'&id_order='.(int) $order_id.'&vieworder&token='.$this->token); + $this->errors[] = Tools::displayError('Some error occured. Please try again.'); } } } else { @@ -571,33 +575,23 @@ class AdminOrdersControllerCore extends AdminController } if (Tools::isSubmit('swap_allocated_rooms')) { if ($this->tabAccess['edit'] === '1') { - $order_id = Tools::getValue('id_order'); - $current_room_id = Tools::getValue('modal_id_room'); - $current_room = Tools::getValue('modal_curr_room_num'); - $date_from = Tools::getValue('modal_date_from'); - $date_to = Tools::getValue('modal_date_to'); - $swapped_room_id = Tools::getValue('swap_avail_rooms'); + $idOrder = Tools::getValue('id_order'); + $idHtlBookingFrom = Tools::getValue('id_htl_booking'); + $idHtlBookingToSwap = Tools::getValue('swap_avail_rooms'); - if ($swapped_room_id == 0) { - $this->errors[] = Tools::displayError('Please select a room to swap with this room.'); + if (!$idHtlBookingToSwap) { + $this->errors[] = Tools::displayError('Please select a room to swap with this room booking.'); } - if ($current_room_id == 0) { - $this->errors[] = Tools::displayError('Cuurent room is missing.'); - } - if ($date_from == 0) { - $this->errors[] = Tools::displayError('Check In date is missing.'); - } - if ($date_to == 0) { - $this->errors[] = Tools::displayError('Check Out date is missing.'); + if (!$idHtlBookingFrom) { + $this->errors[] = Tools::displayError('Current room information is missing.'); } if (!count($this->errors)) { $objBookingDetail = new HotelBookingDetail(); - $room_swapped = $objBookingDetail->swapRoomWithAvailableSameRoomType($current_room_id, $date_from, $date_to, $swapped_room_id); - if (!$room_swapped) { - $this->errors[] = Tools::displayError('Some error occured. Please try again.'); + if ($objBookingDetail->swapBooking($idHtlBookingFrom, $idHtlBookingToSwap)) { + Tools::redirectAdmin(self::$currentIndex.'&id_order='.(int)$idOrder.'&vieworder&conf=4&token='.$this->token); } else { - Tools::redirectAdmin(self::$currentIndex.'&id_order='.(int) $order_id.'&vieworder&token='.$this->token); + $this->errors[] = Tools::displayError('Some error occured. Please try again.'); } } } else { @@ -1354,7 +1348,16 @@ 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'))) { + Tools::redirectAdmin(self::$currentIndex.'&id_order='.$order->id.'&vieworder&conf=51&token='.$this->token); + } else { + $this->errors = Tools::displayError('An error occurred while resolving overbooking.'); + } } + parent::postProcess(); } @@ -1751,8 +1754,13 @@ 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), diff --git a/controllers/front/HistoryController.php b/controllers/front/HistoryController.php index 397325c41..fbef91e5a 100644 --- a/controllers/front/HistoryController.php +++ b/controllers/front/HistoryController.php @@ -64,6 +64,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 196091216..f012bb100 100644 --- a/controllers/front/OrderDetailController.php +++ b/controllers/front/OrderDetailController.php @@ -647,6 +647,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 0000000000000000000000000000000000000000..7a8f831fc26d38cf232001c90152ac3d4eae3f21 GIT binary patch literal 1357 zcmbVMdsq}z6kiI+!it36R>NS1#q7M9ospGbXBW0y*^m^o*X+zJOfWOs%m53SrKy1^ z+57!4>19s~dl^Y)(Oc49P?<>&DvGqA#OgMKEcFMUUti~snS0MYzw$Z!fz~e2I{?;p z16!1K*ME~NkX8{2L%d)K_CLQM9uvfB*@7Z+rJ{M^!A+ZCNRhnod^&`MVm`S_DQPt1 zxs7wm#l~upmEeL|P=1_?t1(r!1Sqc7XeJl;!h)nnWX@hYY7~Y>O{{7!T<9D?4yR8y zWQcWRh=^er#ISCh!9{|J3Xxohzz71xJSd)r5Clik9EC$;0^5X!6y?hJz!;sq^TJh@ z72{B}zP{dFPr7xZ62)1TMKJ;;2*kEP%m&R8;)rHuCp_@7DH=-5QgjV+JPHxL*7Cx3 zum`eGW1-NvW6gAr=HwQ26^#oq6nA5&syeBSTANmx{7)DYTASq!F&QnBO}*9-W!pSE z0k+F`a3aUhj)p5W6#HI;8eSJ`Ravuwycf1l+>#=3I402^O3Xt%QCvbOIl>?UBQOX_ z<55f!SUf^S5`HGa`~58C3$T8VpCobI&ll1><0AtM!T5X(UqmFZLCv%TO_USdip@QS z2s{HHk(DSLv2+R_QT_Al)>S6B0y4b2KxYTRM<5FB?joECmc*g+|NF zDHDW%vlL4U7=_?0BOsJVj3S6>#JK{>D>Hs$4i@DeBt@$p555~ z%+nj5dh&_KAA5BDBM(3H-~(;z?q7S~z4zRG*PVCVe%r0L+mj6`ox%%bP!wV?9hBO_2nSp>8DitTSiioH70MY1wcV zblRz>Og;Ie6Hhq)xMMRjrW})g^ifAnPCMf8Nr$DTBp-T6lFOB~FWLTPxN@Bb%6a8n zDHXe>t(F$7jIPfs9igUoPAyf*v~X&85VUOHUA!#0uC>i*&e>SLu98a0$xJN^&LhGl t$t_JgGBcAWt=iB%drez#VP_GP)D&!9v3Og5@#OH5DLG5CGqPMW{|1hG?B)Of literal 0 HcmV?d00001 diff --git a/install/data/xml/configuration.xml b/install/data/xml/configuration.xml index 935de20ba..dfdb8030b 100644 --- a/install/data/xml/configuration.xml +++ b/install/data/xml/configuration.xml @@ -335,6 +335,12 @@ 0 + + 1 + + + 0 + 1 @@ -434,6 +440,9 @@ 11 + + 12 + 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 @@ - -