From 2b1ef726db7382e04f4d51213418daae69d0fb05 Mon Sep 17 00:00:00 2001 From: varun kewlani Date: Tue, 31 Mar 2026 18:03:35 +0530 Subject: [PATCH 01/35] added: Powered By webkul in frontend --- .../hotelreservationsystem/views/templates/hook/copyRight.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/hotelreservationsystem/views/templates/hook/copyRight.tpl b/modules/hotelreservationsystem/views/templates/hook/copyRight.tpl index 790a4a52c..38c1cd38b 100644 --- a/modules/hotelreservationsystem/views/templates/hook/copyRight.tpl +++ b/modules/hotelreservationsystem/views/templates/hook/copyRight.tpl @@ -1,5 +1,5 @@
\ No newline at end of file From 00f93226fdc83c0ec6eedaae252a2130273b1af2 Mon Sep 17 00:00:00 2001 From: Amit sharma Date: Tue, 7 Apr 2026 12:50:01 +0530 Subject: [PATCH 02/35] Added: download and print functionality for payment receipts in the back-office --- .../controllers/orders/helpers/view/view.tpl | 12 +- classes/order/Order.php | 21 ++ classes/order/OrderPayment.php | 24 +++ classes/pdf/HTMLTemplatePaymentReceipt.php | 164 ++++++++++++++ classes/pdf/PDF.php | 1 + controllers/admin/AdminOrdersController.php | 1 + .../admin/AdminPaymentReceiptsController.php | 135 ++++++++++++ controllers/admin/AdminPdfController.php | 28 ++- install/data/db_structure.sql | 1 + install/data/xml/configuration.xml | 6 + install/data/xml/tab.xml | 3 + install/langs/en/data/configuration.xml | 3 + install/langs/en/data/tab.xml | 1 + pdf/payment-receipt.tpl | 79 +++++++ pdf/receipt.addresses-tab.tpl | 52 +++++ pdf/receipt.payment-info-tab.tpl | 63 ++++++ pdf/receipt.style-tab.tpl | 203 ++++++++++++++++++ 17 files changed, 794 insertions(+), 3 deletions(-) create mode 100644 classes/pdf/HTMLTemplatePaymentReceipt.php create mode 100644 controllers/admin/AdminPaymentReceiptsController.php create mode 100644 pdf/payment-receipt.tpl create mode 100644 pdf/receipt.addresses-tab.tpl create mode 100644 pdf/receipt.payment-info-tab.tpl create mode 100644 pdf/receipt.style-tab.tpl 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 c0c841758..11a6e1da7 100644 --- a/admin/themes/default/template/controllers/orders/helpers/view/view.tpl +++ b/admin/themes/default/template/controllers/orders/helpers/view/view.tpl @@ -419,6 +419,11 @@
 {l s="Payment"} {$order->getOrderPayments()|@count} + {if count($order->getOrderPayments()) > 0 && $receipt_management_active} + + {l s='View Payments '} + + {/if}
{if count($order->getOrderPayments()) > 0}

total_paid_tax_incl, 2) == round($total_paid, 2) || (isset($currentState) && $currentState->id == 6)} style="display: none;"{/if}> @@ -451,7 +456,7 @@ {l s='Transaction ID'} {l s='Amount'} {l s='Invoice'} - + @@ -463,6 +468,11 @@ {$payment['transaction_id']|escape:'html':'UTF-8'} {displayPrice price=$payment['real_paid_amount'] currency=$payment['id_currency']} {if isset($payment['invoice_number'])}{$payment['invoice_number']}{else}--{/if} + {if isset($receipt_management_active) && $receipt_management_active} + + {l s='View'} + + {/if} {l s='Details'} diff --git a/classes/order/Order.php b/classes/order/Order.php index 7bceb94cf..336fbec2e 100644 --- a/classes/order/Order.php +++ b/classes/order/Order.php @@ -1874,6 +1874,16 @@ class OrderCore extends ObjectModel $order_payment->amount = $amount_paid; $order_payment->date_add = ($date ? $date : null); + $number = Configuration::get('PS_PAYMENT_RECEIPTS_START_NUMBER', null, null, $this->id_shop); + // If payment start number has been set, you clean the value of this configuration + if ($number) { + Configuration::updateValue('PS_PAYMENT_RECEIPTS_START_NUMBER', false, false, null, $this->id_shop); + }else{ + $number = Order::getLastPaymentNumber() + 1; + } + + $order_payment->number = $number; + // Add time to the date if needed if ($order_payment->date_add != null && preg_match('/^[0-9]+-[0-9]+-[0-9]+$/', $order_payment->date_add)) { $order_payment->date_add .= ' '.date('H:i:s'); @@ -1935,6 +1945,17 @@ class OrderCore extends ObjectModel return false; } + public static function getLastPaymentNumber() + { + $sql = 'SELECT MAX(`number`) FROM `'._DB_PREFIX_.'order_payment`'; + if (Configuration::get('PS_PAYMENT_RECEIPTS_RESET')) { + $sql .= ' WHERE DATE_FORMAT(`date_add`, "%Y") = '.(int)date('Y'); + } + return Db::getInstance()->getValue($sql); + } + + + /** * Returns the correct product taxes breakdown. * diff --git a/classes/order/OrderPayment.php b/classes/order/OrderPayment.php index b3f3c981b..4ad21bf86 100644 --- a/classes/order/OrderPayment.php +++ b/classes/order/OrderPayment.php @@ -32,6 +32,7 @@ class OrderPaymentCore extends ObjectModel public $order_reference; public $id_currency; + public $number; public $amount; public $payment_method; public $payment_type; @@ -52,6 +53,7 @@ class OrderPaymentCore extends ObjectModel 'fields' => array( 'order_reference' => array('type' => self::TYPE_STRING, 'validate' => 'isAnything', 'size' => 9), 'id_currency' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true), + 'number' => array('type' => self::TYPE_STRING, 'validate' => 'isAnything', 'size' => 254), 'amount' => array('type' => self::TYPE_FLOAT, 'validate' => 'isNegativePrice', 'required' => true), 'payment_method' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName'), 'payment_type' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true), @@ -159,4 +161,26 @@ class OrderPaymentCore extends ObjectModel { return Db::getInstance()->getValue('SELECT (SUM(`amount` * `conversion_rate`) / SUM(`amount`)) FROM `'._DB_PREFIX_.'order_payment` WHERE `order_reference` = \''.pSQL($order_reference).'\' AND `id_currency` = '.(int)$idCurrency); } + + public static function getTransactionDetailByOrderid($id_order) + { + return ObjectModel::hydrateCollection('OrderPayment', Db::getInstance()->executeS(' + SELECT DISTINCT op.* + FROM `'._DB_PREFIX_.'order_payment` op + INNER JOIN `'._DB_PREFIX_.'order_payment_detail` opd ON (opd.`id_order_payment` = op.`id_order_payment`) + WHERE opd.`id_order` = '.(int) $id_order.' + ORDER BY op.`date_add` ASC, op.`id_order_payment` ASC + ')); + } + + public function getPaymentReceiptNumberFormated($id_lang, $id_shop = null) + { + $format = '%1$s%2$06d'; + + if (Configuration::get('PS_PAYMENT_RECEIPTS_USE_YEAR')) { + $format = '%1$s%2$06d/%3$s'; + } + + return sprintf($format, Configuration::get('PS_PAYMENT_RECEIPTS_PREFIX', (int)$id_lang, null, (int)$id_shop), $this->number, date('Y', strtotime($this->date_add))); + } } diff --git a/classes/pdf/HTMLTemplatePaymentReceipt.php b/classes/pdf/HTMLTemplatePaymentReceipt.php new file mode 100644 index 000000000..f6ea80f9e --- /dev/null +++ b/classes/pdf/HTMLTemplatePaymentReceipt.php @@ -0,0 +1,164 @@ + + * @copyright 2007-2017 PrestaShop SA + * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) + * International Registered Trademark & Property of PrestaShop SA + */ + +/** + * @since 1.5 + */ +class HTMLTemplatePaymentReceiptCore extends HTMLTemplate +{ + public $orderPayment; + public $order; + /** + * @param OrderPayment $orderPayment + * @param $smarty + * @throws PrestaShopException + */ + public function __construct(OrderPayment $orderPayment, $smarty, $bulk_mode = false) + { + $id_order = (int) Tools::getValue('id_order'); + $this->orderPayment = $orderPayment; + $this->order = new Order((int)$id_order); + $this->smarty = $smarty; + // header informations + $this->date = Tools::displayDate($this->orderPayment->date_add); + $id_lang = Context::getContext()->language->id; + $this->shop = new Shop((int)$this->order->id_shop); + $this->title = $this->orderPayment->getPaymentReceiptNumberFormated($id_lang, (int)$this->order->id_shop); + + } + + /** + * Returns the template's HTML header + * + * @return string HTML header + */ + public function getHeader() + { + $this->assignCommonHeaderData(); + $this->smarty->assign(array( + 'header' => HTMLTemplatePaymentReceipt::l('Receipt'), + )); + + return $this->smarty->fetch($this->getTemplate('header')); + } + + public function getFooter() + { + $shop_address = $this->getShopAddress(); + + $id_shop = (int)$this->shop->id; + + $this->smarty->assign(array( + 'shop_address' => $shop_address, + 'shop_fax' => Configuration::get('PS_SHOP_FAX', null, null, $id_shop), + 'shop_phone' => Configuration::get('PS_SHOP_PHONE', null, null, $id_shop), + 'shop_email' => Configuration::get('PS_SHOP_EMAIL', null, null, $id_shop), + 'free_text' => Configuration::get('PS_PAYMENT_RECEIPTS_FREE_TEXT', (int)Context::getContext()->language->id, null, $id_shop) + )); + + return $this->smarty->fetch($this->getTemplate('footer')); + } + + public function getContent() + { + $delivery_address = $invoice_address = new Address((int)$this->order->id_address_invoice); + $formatted_invoice_address = AddressFormat::generateAddress($invoice_address, array(), '
', ' '); + $formatted_delivery_address = ''; + + if ($this->order->id_address_delivery != $this->order->id_address_invoice) { + $delivery_address = new Address((int)$this->order->id_address_delivery); + $formatted_delivery_address = AddressFormat::generateAddress($delivery_address, array(), '
', ' '); + } + + $formattedHotelAddress = ''; + if (Module::isInstalled('hotelreservationsystem')) { + if ($idHotel = HotelBookingDetail::getIdHotelByIdOrder($this->order->id)) { + $objHotelBranchInfo = new HotelBranchInformation((int) $idHotel, Context::getContext()->language->id); + $invoiceAddressPatternRules['avoid'][] = 'lastname'; + if ($idHotelAddress = $objHotelBranchInfo->getHotelIdAddress()) { + $objHotelAddress = new Address((int) $idHotelAddress); + $objHotelAddress->firstname = $objHotelBranchInfo->hotel_name; + $formattedHotelAddress = AddressFormat::generateAddress($objHotelAddress, $invoiceAddressPatternRules, '
', ' '); + } + } + } + $customer = new Customer((int)$this->order->id_customer); + + $this->orderPayment->payment_type = $this->getPaymentType($this->orderPayment->payment_type); + + $legal_free_text = Configuration::get('PS_PAYMENT_RECEIPTS_LEGAL_FREE_TEXT', (int)Context::getContext()->language->id, null, (int)$this->order->id_shop); + + $this->smarty->assign(array( + 'order' => $this->order, + 'payment_list' => array($this->orderPayment), + 'delivery_address' => $formatted_delivery_address, + 'invoice_address' => $formatted_invoice_address, + 'addresses' => array('invoice' => $invoice_address, 'delivery' => $delivery_address), + 'customer' => $customer, + 'hotel_address' => $formattedHotelAddress, + 'legal_free_text' => $legal_free_text, + )); + + $tpls = array( + 'style_tab' => $this->smarty->fetch($this->getTemplate('receipt.style-tab')), + 'addresses_tab' => $this->smarty->fetch($this->getTemplate('receipt.addresses-tab')), + 'payment_info_tab' => $this->smarty->fetch($this->getTemplate('receipt.payment-info-tab')), + ); + $this->smarty->assign($tpls); + $html = $this->smarty->fetch($this->getTemplate('payment-receipt')); + return($html); + } + + public function getFilename() + { + return 'payment-receipt-'.sprintf('%06d', $this->orderPayment->id).'.pdf'; + } + + public function getBulkFilename() + { + return 'payment-receipts.pdf'; + } + + protected function getPaymentType($paymentType = null) + { + $paymentTypeName = null; + switch ($paymentType) { + case OrderPayment::PAYMENT_TYPE_PAY_AT_HOTEL: + $paymentTypeName = HTMLTemplatePaymentReceipt::l('Pay at hotel'); + break; + case OrderPayment::PAYMENT_TYPE_ONLINE: + $paymentTypeName = HTMLTemplatePaymentReceipt::l('Online'); + break; + case OrderPayment::PAYMENT_TYPE_REMOTE_PAYMENT: + $paymentTypeName = HTMLTemplatePaymentReceipt::l('Remote payment'); + break; + default: + $paymentTypeName = null; + break; + } + return $paymentTypeName; + } +} \ No newline at end of file diff --git a/classes/pdf/PDF.php b/classes/pdf/PDF.php index 626d3a51a..833d5b17e 100644 --- a/classes/pdf/PDF.php +++ b/classes/pdf/PDF.php @@ -41,6 +41,7 @@ class PDFCore const TEMPLATE_ORDER_SLIP = 'OrderSlip'; const TEMPLATE_DELIVERY_SLIP = 'DeliverySlip'; const TEMPLATE_SUPPLY_ORDER_FORM = 'SupplyOrderForm'; + const TEMPLATE_PAYMENT_RECEIPT = 'PaymentReceipt'; /** * @param $objects diff --git a/controllers/admin/AdminOrdersController.php b/controllers/admin/AdminOrdersController.php index 30026bcca..1817c0269 100644 --- a/controllers/admin/AdminOrdersController.php +++ b/controllers/admin/AdminOrdersController.php @@ -3676,6 +3676,7 @@ class AdminOrdersControllerCore extends AdminController 'payment_methods' => $payment_methods, 'payment_types' => $this->getPaymentsTypes(), 'invoice_management_active' => Configuration::get('PS_INVOICE', null, null, $order->id_shop), + 'receipt_management_active' => Configuration::get('PS_PAYMENT_RECEIPTS', null, null, $order->id_shop), 'display_warehouse' => (int)Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT'), 'HOOK_CONTENT_ORDER' => Hook::exec( 'displayAdminOrderContentOrder', diff --git a/controllers/admin/AdminPaymentReceiptsController.php b/controllers/admin/AdminPaymentReceiptsController.php new file mode 100644 index 000000000..44336b9f1 --- /dev/null +++ b/controllers/admin/AdminPaymentReceiptsController.php @@ -0,0 +1,135 @@ + +* @copyright 2007-2017 PrestaShop SA +* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*/ +class AdminPaymentReceiptsControllerCore extends AdminControllerCore +{ + public function __construct() + { + $this->bootstrap = true; + parent::__construct(); + $this->context = Context::getContext(); + $this->lang = false; + $this->table = 'order_payment'; + + $this->fields_options = array( + 'general' => array( + 'title' => $this->l('Payment receipts options'), + 'fields' => array( + 'PS_PAYMENT_RECEIPTS' => array( + 'title' => $this->l('Enable Receipts'), + 'desc' => $this->l('If enabled, your customers will receive a receipt for their purchase(s).'), + 'cast' => 'intval', + 'type' => 'bool' + ), + 'PS_PAYMENT_RECEIPTS_PREFIX' => array( + 'title' => $this->l('Receipt prefix'), + 'desc' => $this->l('Prefix used for receipt name (e.g. #IN00001).'), + 'size' => 6, + 'type' => 'textLang' + ), + 'PS_PAYMENT_RECEIPTS_USE_YEAR' => array( + 'title' => $this->l('Add current year to receipt number'), + 'cast' => 'intval', + 'type' => 'bool' + ), + 'PS_PAYMENT_RECEIPTS_RESET' => array( + 'title' => $this->l('Reset Receipt progressive number at beginning of the year'), + 'cast' => 'intval', + 'type' => 'bool' + ), + 'PS_PAYMENT_RECEIPTS_START_NUMBER' => array( + 'title' => $this->l('Receipt number'), + 'desc' => sprintf($this->l('The next receipt will begin with this number, and then increase with each additional receipt. Set to 0 if you want to keep the current number (which is #%s).'), Order::getLastPaymentNumber() + 1), + 'size' => 6, + 'type' => 'text', + 'cast' => 'intval' + ), + 'PS_PAYMENT_RECEIPTS_LEGAL_FREE_TEXT' => array( + 'title' => $this->l('Legal free text'), + 'desc' => $this->l('Use this field to display additional text on your receipt, like specific legal information. It will appear below the payment methods summary.'), + 'size' => 50, + 'type' => 'textareaLang', + ), + 'PS_PAYMENT_RECEIPTS_FREE_TEXT' => array( + 'title' => $this->l('Footer text'), + 'desc' => $this->l('This text will appear at the bottom of the receipt, below your company details.'), + 'size' => 50, + 'type' => 'textLang', + ), + 'PS_PDF_USE_CACHE' => array( + 'title' => $this->l('Use the disk as cache for PDF receipts'), + 'desc' => $this->l('Saves memory but slows down the PDF generation.'), + 'validation' => 'isBool', + 'cast' => 'intval', + 'type' => 'bool' + ) + ), + 'submit' => array('title' => $this->l('Save')) + ) + ); + } + + public function initContent() + { + $this->display = 'edit'; + $this->initTabModuleList(); + $this->initToolbar(); + $this->initPageHeaderToolbar(); + $this->table = 'order_payment'; + $this->content .= $this->renderOptions(); + + $this->context->smarty->assign(array( + 'content' => $this->content, + 'url_post' => self::$currentIndex.'&token='.$this->token, + 'show_page_header_toolbar' => $this->show_page_header_toolbar, + 'page_header_toolbar_title' => $this->page_header_toolbar_title, + 'page_header_toolbar_btn' => $this->page_header_toolbar_btn + )); + } + + public function initToolbarTitle() + { + $this->toolbar_title = array_unique($this->breadcrumbs); + } + + public function initPageHeaderToolbar() + { + parent::initPageHeaderToolbar(); + unset($this->page_header_toolbar_btn['cancel']); + } + + public function postProcess() + { + parent::postProcess(); + } + + public function beforeUpdateOptions() + { + if ((int)Tools::getValue('PS_PAYMENT_RECEIPTS_START_NUMBER') != 0 && (int)Tools::getValue('PS_PAYMENT_RECEIPTS_START_NUMBER') <= Order::getLastPaymentNumber()) { + $this->errors[] = $this->l('Invalid receipt number.').Order::getLastPaymentNumber().')'; + } + } +} + diff --git a/controllers/admin/AdminPdfController.php b/controllers/admin/AdminPdfController.php index 7fba0f8e1..279529b2a 100644 --- a/controllers/admin/AdminPdfController.php +++ b/controllers/admin/AdminPdfController.php @@ -93,7 +93,6 @@ class AdminPdfControllerCore extends AdminController public function processGenerateInvoicesPDF() { $order_invoice_collection = OrderInvoice::getByDateInterval(Tools::getValue('date_from'), Tools::getValue('date_to')); - if (!count($order_invoice_collection)) { die(Tools::displayError('No invoice was found.')); } @@ -150,14 +149,39 @@ class AdminPdfControllerCore extends AdminController $id_supply_order = (int)Tools::getValue('id_supply_order'); $supply_order = new SupplyOrder($id_supply_order); - if (!Validate::isLoadedObject($supply_order)) { die(Tools::displayError('The supply order cannot be found within your database.')); } $this->generatePDF($supply_order, PDF::TEMPLATE_SUPPLY_ORDER_FORM); + } + + public function processGeneratePaymentReceipt() + { + $idOrderPayment = (int) Tools::getValue('id_order_payment'); + $idOrder = (int) Tools::getValue('id_order'); + + if ($idOrderPayment <= 0 && $idOrder <= 0) { + die(Tools::displayError('The order or order payment id is missing.')); + } + + if ($idOrderPayment > 0) { + $orderPayment = new OrderPayment($idOrderPayment); + + if (!Validate::isLoadedObject($orderPayment)) { + die(Tools::displayError('The order payment cannot be found.')); + } + } else { + $orderPayment = OrderPayment::getTransactionDetailByOrderid($idOrder); + if (!$orderPayment || !count($orderPayment)) { + die(Tools::displayError('No payment found for this order.')); + } + } + + $this->generatePDF($orderPayment, PDF::TEMPLATE_PAYMENT_RECEIPT); } + public function generateDeliverySlipPDFByIdOrder($id_order) { $order = new Order((int)$id_order); diff --git a/install/data/db_structure.sql b/install/data/db_structure.sql index 7550fbcad..8cfeeb755 100644 --- a/install/data/db_structure.sql +++ b/install/data/db_structure.sql @@ -1496,6 +1496,7 @@ CREATE TABLE `PREFIX_order_payment` ( `order_reference` VARCHAR(9), `id_currency` INT UNSIGNED NOT NULL, `amount` DECIMAL(10,2) NOT NULL, + `number` varchar(255) NOT NULL, `payment_method` varchar(255) NOT NULL, `payment_type` INT UNSIGNED NOT NULL, `conversion_rate` decimal(13,6) NOT NULL DEFAULT 1, diff --git a/install/data/xml/configuration.xml b/install/data/xml/configuration.xml index 1c754ae50..dd1245e17 100644 --- a/install/data/xml/configuration.xml +++ b/install/data/xml/configuration.xml @@ -133,6 +133,9 @@ #IN + + + #IN {"avoid":[]} @@ -152,6 +155,9 @@ 1 + + 1 + 360 diff --git a/install/data/xml/tab.xml b/install/data/xml/tab.xml index 09eed80f3..8909ed603 100644 --- a/install/data/xml/tab.xml +++ b/install/data/xml/tab.xml @@ -102,6 +102,9 @@ AdminInvoices + + AdminPaymentReceipts + diff --git a/install/langs/en/data/configuration.xml b/install/langs/en/data/configuration.xml index b4bf4ef61..610649713 100644 --- a/install/langs/en/data/configuration.xml +++ b/install/langs/en/data/configuration.xml @@ -3,6 +3,9 @@ #IN + + #IN + #DE diff --git a/install/langs/en/data/tab.xml b/install/langs/en/data/tab.xml index ab5391a0f..2ccd45418 100644 --- a/install/langs/en/data/tab.xml +++ b/install/langs/en/data/tab.xml @@ -32,6 +32,7 @@ + diff --git a/pdf/payment-receipt.tpl b/pdf/payment-receipt.tpl new file mode 100644 index 000000000..470b49eae --- /dev/null +++ b/pdf/payment-receipt.tpl @@ -0,0 +1,79 @@ +{* +* 2007-2015 PrestaShop +* +* NOTICE OF LICENSE +* +* This source file is subject to the Academic Free License (AFL 3.0) +* that is bundled with this package in the file LICENSE.txt. +* It is also available through the world-wide-web at this URL: +* http://opensource.org/licenses/afl-3.0.php +* If you did not receive a copy of the license and are unable to +* obtain it through the world-wide-web, please send an email +* to license@prestashop.com so we can send you a copy immediately. +* +* DISCLAIMER +* +* Do not edit or add to this file if you wish to upgrade PrestaShop to newer +* versions in the future. If you wish to customize PrestaShop for your +* needs please refer to http://www.prestashop.com for more information. +* +* @author PrestaShop SA +* @copyright 2007-2015 PrestaShop SA +* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*} + +{$style_tab} + + + + + + + + + + + + + + + + + + + + + + + + + {if isset($HOOK_DISPLAY_PDF)} + + + + + + + + {/if} + +
+ + {$addresses_tab} + +
 
+ {$payment_info_tab} +
 
+ + + + + +
+

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

+
+ +
 
+ {$HOOK_DISPLAY_PDF} +
diff --git a/pdf/receipt.addresses-tab.tpl b/pdf/receipt.addresses-tab.tpl new file mode 100644 index 000000000..df5e70de0 --- /dev/null +++ b/pdf/receipt.addresses-tab.tpl @@ -0,0 +1,52 @@ +{* +* 2007-2015 PrestaShop +* +* NOTICE OF LICENSE +* +* This source file is subject to the Academic Free License (AFL 3.0) +* that is bundled with this package in the file LICENSE.txt. +* It is also available through the world-wide-web at this URL: +* http://opensource.org/licenses/afl-3.0.php +* If you did not receive a copy of the license and are unable to +* obtain it through the world-wide-web, please send an email +* to license@prestashop.com so we can send you a copy immediately. +* +* DISCLAIMER +* +* Do not edit or add to this file if you wish to upgrade PrestaShop to newer +* versions in the future. If you wish to customize PrestaShop for your +* needs please refer to http://www.prestashop.com for more information. +* +* @author PrestaShop SA +* @copyright 2007-2015 PrestaShop SA +* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*} + + + + + + + + +
+ {if !empty($hotel_address)} + {l s='Hotel Detail' pdf='true'}

+ {$hotel_address} + {/if} +
+ {l s='Customer Detail' pdf='true'}

+ {if $invoice_address} + {$invoice_address} + {else} + {$customer->firstname} {$customer->lastname} +
+ {$customer->phone} + {/if} +
\ No newline at end of file diff --git a/pdf/receipt.payment-info-tab.tpl b/pdf/receipt.payment-info-tab.tpl new file mode 100644 index 000000000..d39879c19 --- /dev/null +++ b/pdf/receipt.payment-info-tab.tpl @@ -0,0 +1,63 @@ +{* +* 2007-2015 PrestaShop +* +* NOTICE OF LICENSE +* +* This source file is subject to the Academic Free License (AFL 3.0) +* that is bundled with this package in the file LICENSE.txt. +* It is also available through the world-wide-web at this URL: +* http://opensource.org/licenses/afl-3.0.php +* If you did not receive a copy of the license and are unable to +* obtain it through the world-wide-web, please send an email +* to license@prestashop.com so we can send you a copy immediately. +* +* DISCLAIMER +* +* Do not edit or add to this file if you wish to upgrade PrestaShop to newer +* versions in the future. If you wish to customize PrestaShop for your +* needs please refer to http://www.prestashop.com for more information. +* +* @author PrestaShop SA +* @copyright 2007-2015 PrestaShop SA +* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*} + +{if isset($payment_list) && $payment_list} + + + + + + + + + + + + + + + {foreach from=$payment_list key=rm_k item=payment} + {cycle values=["color_line_even", "color_line_odd"] assign=bgcolor_class} + + + + + + + + {/foreach} + +
{l s='Payment Details' pdf='true'}
{l s='Payment Date' pdf='true'}{l s='Payment Method' pdf='true'}{l s='Payment Type' pdf='true'}{l s='Transaction ID' pdf='true'}{l s='Amount' pdf='true'}
+ {dateFormat date=$payment->date_add full=true} + + {$payment->payment_method} + + {$payment->payment_type} + + {$payment->transaction_id} + + {displayPrice currency=$payment->id_currency price=$payment->amount} +
+{/if} \ No newline at end of file diff --git a/pdf/receipt.style-tab.tpl b/pdf/receipt.style-tab.tpl new file mode 100644 index 000000000..e34c365c9 --- /dev/null +++ b/pdf/receipt.style-tab.tpl @@ -0,0 +1,203 @@ +{* +* 2007-2015 PrestaShop +* +* NOTICE OF LICENSE +* +* This source file is subject to the Academic Free License (AFL 3.0) +* that is bundled with this package in the file LICENSE.txt. +* It is also available through the world-wide-web at this URL: +* http://opensource.org/licenses/afl-3.0.php +* If you did not receive a copy of the license and are unable to +* obtain it through the world-wide-web, please send an email +* to license@prestashop.com so we can send you a copy immediately. +* +* DISCLAIMER +* +* Do not edit or add to this file if you wish to upgrade PrestaShop to newer +* versions in the future. If you wish to customize PrestaShop for your +* needs please refer to http://www.prestashop.com for more information. +* +* @author PrestaShop SA +* @copyright 2007-2015 PrestaShop SA +* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) +* International Registered Trademark & Property of PrestaShop SA +*} + +{assign var=color_header value="#F0F0F0"} +{assign var=color_border value="#000000"} +{assign var=color_border_lighter value="#CCCCCC"} +{assign var=color_line_even value="#FFFFFF"} +{assign var=color_line_odd value="#F9F9F9"} +{assign var=font_size_text value="9pt"} +{assign var=font_size_header value="9pt"} +{assign var=font_size_product value="9pt"} +{assign var=height_header value="20px"} +{assign var=table_padding value="4px"} + + From 17d8e63e31cd3f4e7587ba14b0107d1891b259e8 Mon Sep 17 00:00:00 2001 From: azhar bhat Date: Tue, 7 Apr 2026 17:30:01 +0530 Subject: [PATCH 03/35] composer file updated --- composer.json | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/composer.json b/composer.json index 74ff00cb0..0e5523d61 100644 --- a/composer.json +++ b/composer.json @@ -1,22 +1,32 @@ { + "name": "qloapps/qloapps", + "description": "QloApps is an Open-source and Free hotel reservation system and booking engine. With the help of QloApps, you can launch your hotel booking website without any cost and take & manage online bookings.", + "type": "project", + "homepage": "https://qloapps.com", + "license": "OSL-3.0", + "authors": [ + { + "name": "QloApps", + "email": "support@qloapps.com" + }, + { + "name": "QloApps Community", + "homepage": "https://qloapps.com" + } + ], + "support": { + "forum": "https://forums.qloapps.com/", + "issues": "https://github.com/Qloapps/QloApps/issues", + "source": "https://github.com/Qloapps/QloApps" + }, "require": { - "php": ">=5.4 <8.0", + "php": ">=8.0 <8.5", "ext-simplexml": "*", "ext-curl": "*", "ext-gd": "*", "ext-zip": "*", "ext-soap": "*" }, - "name": "webkul/qloapps", - "description": "QloApps is an Open-source and Free hotel reservation system and booking engine. With the help of QloApps, you can launch your hotel booking website without any cost and take & manage online bookings.", - "type": "project", - "license": "OSL-3.0", - "authors": [ - { - "name": "webkul", - "email": "support@qloapps.com" - } - ], "minimum-stability": "dev", "prefer-stable": true } \ No newline at end of file From 5994798d8decb8fc4e7c40385f1390b6e7742020 Mon Sep 17 00:00:00 2001 From: varun kewlani Date: Fri, 10 Apr 2026 12:28:23 +0530 Subject: [PATCH 04/35] chaned text from Webkul to Qloapps, and added qloapps hyperlink to it --- .../hotelreservationsystem/views/templates/hook/copyRight.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/hotelreservationsystem/views/templates/hook/copyRight.tpl b/modules/hotelreservationsystem/views/templates/hook/copyRight.tpl index 38c1cd38b..15906d128 100644 --- a/modules/hotelreservationsystem/views/templates/hook/copyRight.tpl +++ b/modules/hotelreservationsystem/views/templates/hook/copyRight.tpl @@ -1,5 +1,5 @@

\ No newline at end of file From 821283169194237e3a0321a2a3707c212080c9bb Mon Sep 17 00:00:00 2001 From: Amit sharma Date: Fri, 24 Apr 2026 15:42:44 +0530 Subject: [PATCH 05/35] fix: use order payment detail records as the canonical source for receipt generation --- .../controllers/orders/helpers/view/view.tpl | 7 +- classes/order/Order.php | 27 ++--- classes/order/OrderPayment.php | 23 ---- classes/order/OrderPaymentDetail.php | 47 ++++++++ classes/pdf/HTMLTemplatePaymentReceipt.php | 91 ++++++++------- .../admin/AdminPaymentReceiptsController.php | 107 ++++++++++++++---- controllers/admin/AdminPdfController.php | 39 ++++--- install/data/db_structure.sql | 2 +- pdf/payment-receipt.tpl | 23 ++-- ....tpl => payment.receipt.addresses-tab.tpl} | 23 ++-- pdf/payment.receipt.payment-info-tab.tpl | 50 ++++++++ ...-tab.tpl => payment.receipt.style-tab.tpl} | 25 ++-- pdf/receipt.payment-info-tab.tpl | 63 ----------- 13 files changed, 288 insertions(+), 239 deletions(-) rename pdf/{receipt.addresses-tab.tpl => payment.receipt.addresses-tab.tpl} (58%) create mode 100644 pdf/payment.receipt.payment-info-tab.tpl rename pdf/{receipt.style-tab.tpl => payment.receipt.style-tab.tpl} (82%) delete mode 100644 pdf/receipt.payment-info-tab.tpl 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 11a6e1da7..e507ad5ae 100644 --- a/admin/themes/default/template/controllers/orders/helpers/view/view.tpl +++ b/admin/themes/default/template/controllers/orders/helpers/view/view.tpl @@ -419,11 +419,6 @@
 {l s="Payment"} {$order->getOrderPayments()|@count} - {if count($order->getOrderPayments()) > 0 && $receipt_management_active} - - {l s='View Payments '} - - {/if}
{if count($order->getOrderPayments()) > 0}

total_paid_tax_incl, 2) == round($total_paid, 2) || (isset($currentState) && $currentState->id == 6)} style="display: none;"{/if}> @@ -470,7 +465,7 @@ {if isset($payment['invoice_number'])}{$payment['invoice_number']}{else}--{/if} {if isset($receipt_management_active) && $receipt_management_active} - {l s='View'} + {l s='View'} {/if} diff --git a/classes/order/Order.php b/classes/order/Order.php index 336fbec2e..a62293679 100644 --- a/classes/order/Order.php +++ b/classes/order/Order.php @@ -1874,16 +1874,6 @@ class OrderCore extends ObjectModel $order_payment->amount = $amount_paid; $order_payment->date_add = ($date ? $date : null); - $number = Configuration::get('PS_PAYMENT_RECEIPTS_START_NUMBER', null, null, $this->id_shop); - // If payment start number has been set, you clean the value of this configuration - if ($number) { - Configuration::updateValue('PS_PAYMENT_RECEIPTS_START_NUMBER', false, false, null, $this->id_shop); - }else{ - $number = Order::getLastPaymentNumber() + 1; - } - - $order_payment->number = $number; - // Add time to the date if needed if ($order_payment->date_add != null && preg_match('/^[0-9]+-[0-9]+-[0-9]+$/', $order_payment->date_add)) { $order_payment->date_add .= ' '.date('H:i:s'); @@ -1915,7 +1905,15 @@ class OrderCore extends ObjectModel $order_payment_detail->id_order = $this->id; $order_payment_detail->id_order_payment = (int)$payment->id; $order_payment_detail->amount = $amount; + $receipt_number = Configuration::get('PS_PAYMENT_RECEIPTS_START_NUMBER', null, null, $this->id_shop); + // If payment start number has been set, you clean the value of this configuration + if ($receipt_number) { + Configuration::updateValue('PS_PAYMENT_RECEIPTS_START_NUMBER', false, false, null, $this->id_shop); + }else{ + $receipt_number = OrderPaymentDetail::getLastPaymentReceiptNumber() + 1; + } + $order_payment_detail->receipt_number = $receipt_number; if ($payment->id_currency == $this->id_currency) { $this->total_paid_real += $order_payment_detail->amount; } else { @@ -1945,15 +1943,6 @@ class OrderCore extends ObjectModel return false; } - public static function getLastPaymentNumber() - { - $sql = 'SELECT MAX(`number`) FROM `'._DB_PREFIX_.'order_payment`'; - if (Configuration::get('PS_PAYMENT_RECEIPTS_RESET')) { - $sql .= ' WHERE DATE_FORMAT(`date_add`, "%Y") = '.(int)date('Y'); - } - return Db::getInstance()->getValue($sql); - } - /** diff --git a/classes/order/OrderPayment.php b/classes/order/OrderPayment.php index 4ad21bf86..c20215785 100644 --- a/classes/order/OrderPayment.php +++ b/classes/order/OrderPayment.php @@ -32,7 +32,6 @@ class OrderPaymentCore extends ObjectModel public $order_reference; public $id_currency; - public $number; public $amount; public $payment_method; public $payment_type; @@ -53,7 +52,6 @@ class OrderPaymentCore extends ObjectModel 'fields' => array( 'order_reference' => array('type' => self::TYPE_STRING, 'validate' => 'isAnything', 'size' => 9), 'id_currency' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true), - 'number' => array('type' => self::TYPE_STRING, 'validate' => 'isAnything', 'size' => 254), 'amount' => array('type' => self::TYPE_FLOAT, 'validate' => 'isNegativePrice', 'required' => true), 'payment_method' => array('type' => self::TYPE_STRING, 'validate' => 'isGenericName'), 'payment_type' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true), @@ -162,25 +160,4 @@ class OrderPaymentCore extends ObjectModel return Db::getInstance()->getValue('SELECT (SUM(`amount` * `conversion_rate`) / SUM(`amount`)) FROM `'._DB_PREFIX_.'order_payment` WHERE `order_reference` = \''.pSQL($order_reference).'\' AND `id_currency` = '.(int)$idCurrency); } - public static function getTransactionDetailByOrderid($id_order) - { - return ObjectModel::hydrateCollection('OrderPayment', Db::getInstance()->executeS(' - SELECT DISTINCT op.* - FROM `'._DB_PREFIX_.'order_payment` op - INNER JOIN `'._DB_PREFIX_.'order_payment_detail` opd ON (opd.`id_order_payment` = op.`id_order_payment`) - WHERE opd.`id_order` = '.(int) $id_order.' - ORDER BY op.`date_add` ASC, op.`id_order_payment` ASC - ')); - } - - public function getPaymentReceiptNumberFormated($id_lang, $id_shop = null) - { - $format = '%1$s%2$06d'; - - if (Configuration::get('PS_PAYMENT_RECEIPTS_USE_YEAR')) { - $format = '%1$s%2$06d/%3$s'; - } - - return sprintf($format, Configuration::get('PS_PAYMENT_RECEIPTS_PREFIX', (int)$id_lang, null, (int)$id_shop), $this->number, date('Y', strtotime($this->date_add))); - } } diff --git a/classes/order/OrderPaymentDetail.php b/classes/order/OrderPaymentDetail.php index f1e616bc8..72ae48635 100644 --- a/classes/order/OrderPaymentDetail.php +++ b/classes/order/OrderPaymentDetail.php @@ -26,6 +26,7 @@ class OrderPaymentDetailCore extends ObjectModel public $id_order_payment; public $id_order; public $amount; + public $receipt_number; public $date_add; public static $definition = array( @@ -35,6 +36,7 @@ class OrderPaymentDetailCore extends ObjectModel 'id_order_payment' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'), 'id_order' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'), 'amount' => array('type' => self::TYPE_FLOAT, 'validate' => 'isNegativePrice', 'required' => true), + 'receipt_number' => array('type' => self::TYPE_INT, 'validate' => 'isGenericName', 'size' => 64), 'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'), ) ); @@ -71,4 +73,49 @@ class OrderPaymentDetailCore extends ObjectModel WHERE oip.`id_order_invoice` = '.(int)$id_invoice ); } + + public static function getLastPaymentReceiptNumber() + { + $sql = 'SELECT MAX(`receipt_number`) FROM `'._DB_PREFIX_.'order_payment_detail`'; + if (Configuration::get('PS_PAYMENT_RECEIPTS_RESET')) { + $sql .= ' WHERE DATE_FORMAT(`date_add`, "%Y") = '.(int)date('Y'); + } + return Db::getInstance()->getValue($sql); + } + + public static function getPaymentDetailsByPaymentId($id_order_payment) + { + return Db::getInstance()->executeS(' + SELECT * + FROM `'._DB_PREFIX_.'order_payment_detail` + WHERE `id_order_payment` = '.(int)$id_order_payment + ); + } + + public static function getByDateInterval($date_from, $date_to) + { + $payment_list = Db::getInstance()->executeS(' + SELECT DISTINCT opd.* + FROM `'._DB_PREFIX_.'order_payment_detail` opd + LEFT JOIN `'._DB_PREFIX_.'orders` o ON (o.`id_order` = opd.`id_order`) + WHERE DATE_ADD(opd.`date_add`, INTERVAL -1 DAY) <= \''.pSQL($date_to).'\' + AND opd.`date_add` >= \''.pSQL($date_from).'\' + AND opd.`receipt_number` > 0 + '.Shop::addSqlRestriction(Shop::SHARE_ORDER, 'o').' + ORDER BY opd.`date_add` ASC, opd.`id_order_payment_detail` ASC + '); + + return ObjectModel::hydrateCollection('OrderPaymentDetail', $payment_list); + } + + public function getPaymentReceiptNumberFormated($id_lang, $id_shop = null) + { + $format = '%1$s%2$06d'; + + if (Configuration::get('PS_PAYMENT_RECEIPTS_USE_YEAR')) { + $format = '%1$s%2$06d/%3$s'; + } + + return sprintf($format, Configuration::get('PS_PAYMENT_RECEIPTS_PREFIX', (int)$id_lang, null, (int)$id_shop), $this->receipt_number, date('Y', strtotime($this->date_add))); + } } diff --git a/classes/pdf/HTMLTemplatePaymentReceipt.php b/classes/pdf/HTMLTemplatePaymentReceipt.php index f6ea80f9e..cb6762205 100644 --- a/classes/pdf/HTMLTemplatePaymentReceipt.php +++ b/classes/pdf/HTMLTemplatePaymentReceipt.php @@ -1,53 +1,60 @@ - * @copyright 2007-2017 PrestaShop SA - * @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) - * International Registered Trademark & Property of PrestaShop SA - */ +* NOTICE OF LICENSE +* +* This source file is subject to the Open Software License version 3.0 +* that is bundled with this package in the file LICENSE.md +* It is also available through the world-wide-web at this URL: +* https://opensource.org/license/osl-3-0-php +* If you did not receive a copy of the license and are unable to +* obtain it through the world-wide-web, please send an email +* to support@qloapps.com so we can send you a copy immediately. +* +* DISCLAIMER +* +* Do not edit or add to this file if you wish to upgrade this module to a newer +* versions in the future. If you wish to customize this module for your needs +* please refer to https://store.webkul.com/customisation-guidelines for more information. +* +* @author Webkul IN +* @copyright Since 2010 Webkul +* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0 +*/ /** * @since 1.5 */ class HTMLTemplatePaymentReceiptCore extends HTMLTemplate { + public $paymentReceipt; public $orderPayment; public $order; /** - * @param OrderPayment $orderPayment + * @param OrderPaymentDetail $paymentReceipt * @param $smarty * @throws PrestaShopException */ - public function __construct(OrderPayment $orderPayment, $smarty, $bulk_mode = false) + public function __construct(OrderPaymentDetail $paymentReceipt, $smarty, $bulk_mode = false) { - $id_order = (int) Tools::getValue('id_order'); - $this->orderPayment = $orderPayment; - $this->order = new Order((int)$id_order); + $this->paymentReceipt = $paymentReceipt; $this->smarty = $smarty; - // header informations - $this->date = Tools::displayDate($this->orderPayment->date_add); + $this->date = Tools::displayDate($this->paymentReceipt->date_add); $id_lang = Context::getContext()->language->id; - $this->shop = new Shop((int)$this->order->id_shop); - $this->title = $this->orderPayment->getPaymentReceiptNumberFormated($id_lang, (int)$this->order->id_shop); - + $this->orderPayment = new OrderPayment((int)$this->paymentReceipt->id_order_payment); + if (!Validate::isLoadedObject($this->orderPayment)) { + throw new PrestaShopException('Cannot load the payment associated with this payment receipt.'); + } + + $this->order = new Order((int)$this->paymentReceipt->id_order); + + if (!Validate::isLoadedObject($this->order)) { + throw new PrestaShopException('Cannot load the order associated with this payment receipt.'); + } + + $id_shop = (int)$this->order->id_shop; + $this->shop = new Shop($id_shop); + $this->orderPayment->amount = $this->paymentReceipt->amount; + $this->title = $this->paymentReceipt->getPaymentReceiptNumberFormated($id_lang, $id_shop); } /** @@ -122,19 +129,19 @@ class HTMLTemplatePaymentReceiptCore extends HTMLTemplate 'legal_free_text' => $legal_free_text, )); - $tpls = array( - 'style_tab' => $this->smarty->fetch($this->getTemplate('receipt.style-tab')), - 'addresses_tab' => $this->smarty->fetch($this->getTemplate('receipt.addresses-tab')), - 'payment_info_tab' => $this->smarty->fetch($this->getTemplate('receipt.payment-info-tab')), + $smarty_tpls = array( + 'style_tab' => $this->smarty->fetch($this->getTemplate('payment.receipt.receipt.style-tab')), + 'addresses_tab' => $this->smarty->fetch($this->getTemplate('payment.receipt.receipt.addresses-tab')), + 'payment_info_tab' => $this->smarty->fetch($this->getTemplate('payment.receipt.receipt.payment-info-tab')), ); - $this->smarty->assign($tpls); - $html = $this->smarty->fetch($this->getTemplate('payment-receipt')); - return($html); + $this->smarty->assign($smarty_tpls); + return $this->smarty->fetch($this->getTemplate('payment-receipt')); + } public function getFilename() { - return 'payment-receipt-'.sprintf('%06d', $this->orderPayment->id).'.pdf'; + return 'payment-receipt-'.sprintf('%06d', $this->paymentReceipt->id).'.pdf'; } public function getBulkFilename() @@ -161,4 +168,4 @@ class HTMLTemplatePaymentReceiptCore extends HTMLTemplate } return $paymentTypeName; } -} \ No newline at end of file +} diff --git a/controllers/admin/AdminPaymentReceiptsController.php b/controllers/admin/AdminPaymentReceiptsController.php index 44336b9f1..3643a6d5b 100644 --- a/controllers/admin/AdminPaymentReceiptsController.php +++ b/controllers/admin/AdminPaymentReceiptsController.php @@ -1,28 +1,26 @@ -* @copyright 2007-2017 PrestaShop SA -* @license http://opensource.org/licenses/osl-3.0.php Open Software License (OSL 3.0) -* International Registered Trademark & Property of PrestaShop SA +* @author Webkul IN +* @copyright Since 2010 Webkul +* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0 */ + class AdminPaymentReceiptsControllerCore extends AdminControllerCore { public function __construct() @@ -61,7 +59,7 @@ class AdminPaymentReceiptsControllerCore extends AdminControllerCore ), 'PS_PAYMENT_RECEIPTS_START_NUMBER' => array( 'title' => $this->l('Receipt number'), - 'desc' => sprintf($this->l('The next receipt will begin with this number, and then increase with each additional receipt. Set to 0 if you want to keep the current number (which is #%s).'), Order::getLastPaymentNumber() + 1), + 'desc' => sprintf($this->l('The next receipt will begin with this number, and then increase with each additional receipt. Set to 0 if you want to keep the current number (which is #%s).'), OrderPaymentDetail::getLastPaymentReceiptNumber() + 1), 'size' => 6, 'type' => 'text', 'cast' => 'intval' @@ -77,13 +75,6 @@ class AdminPaymentReceiptsControllerCore extends AdminControllerCore 'desc' => $this->l('This text will appear at the bottom of the receipt, below your company details.'), 'size' => 50, 'type' => 'textLang', - ), - 'PS_PDF_USE_CACHE' => array( - 'title' => $this->l('Use the disk as cache for PDF receipts'), - 'desc' => $this->l('Saves memory but slows down the PDF generation.'), - 'validation' => 'isBool', - 'cast' => 'intval', - 'type' => 'bool' ) ), 'submit' => array('title' => $this->l('Save')) @@ -98,6 +89,8 @@ class AdminPaymentReceiptsControllerCore extends AdminControllerCore $this->initToolbar(); $this->initPageHeaderToolbar(); $this->table = 'order_payment'; + $this->content .= $this->initFormByDate(); + $this->content .= $this->renderOptions(); $this->context->smarty->assign(array( @@ -109,6 +102,50 @@ class AdminPaymentReceiptsControllerCore extends AdminControllerCore )); } + public function initFormByDate() + { + $this->fields_form = array( + 'legend' => array( + 'title' => $this->l('By date'), + 'icon' => 'icon-calendar' + ), + 'input' => array( + array( + 'type' => 'date', + 'label' => $this->l('From'), + 'name' => 'date_from', + 'maxlength' => 10, + 'required' => true, + 'hint' => $this->l('Format: 2011-12-31 (inclusive).') + ), + array( + 'type' => 'date', + 'label' => $this->l('To'), + 'name' => 'date_to', + 'maxlength' => 10, + 'required' => true, + 'hint' => $this->l('Format: 2012-12-31 (inclusive).') + ) + ), + 'submit' => array( + 'title' => $this->l('Generate PDF file by date'), + 'id' => 'submitPrint', + 'icon' => 'process-icon-download-alt' + ) + ); + + $this->fields_value = array( + 'date_from' => date('Y-m-d'), + 'date_to' => date('Y-m-d') + ); + + $this->table = 'order_payment'; + $this->show_toolbar = false; + $this->show_form_cancel_button = false; + $this->toolbar_title = $this->l('Print PDF invoices'); + return parent::renderForm(); + } + public function initToolbarTitle() { $this->toolbar_title = array_unique($this->breadcrumbs); @@ -122,13 +159,33 @@ class AdminPaymentReceiptsControllerCore extends AdminControllerCore public function postProcess() { - parent::postProcess(); + if(Tools::isSubmit('submitAddorder_payment')) { + if (!Validate::isDate(Tools::getValue('date_from'))) { + $this->errors[] = $this->l('Invalid "From" date'); + } + + if (!Validate::isDate(Tools::getValue('date_to'))) { + $this->errors[] = $this->l('Invalid "To" date'); + } + if(Tools::getValue('date_from') > Tools::getValue('date_to')) { + $this->errors[] = $this->l('"From" date must be earlier than "To" date'); + } + if (!count($this->errors)) { + if (count(OrderPaymentDetail::getByDateInterval(Tools::getValue('date_from'), Tools::getValue('date_to')))) { + Tools::redirectAdmin($this->context->link->getAdminLink('AdminPdf').'&submitAction=generatePaymentReceiptsPDF&date_from='.urlencode(Tools::getValue('date_from')).'&date_to='.urlencode(Tools::getValue('date_to'))); + } + + $this->errors[] = $this->l('No payment receipt has been found for this period.'); + } + }else{ + parent::postProcess(); + } } public function beforeUpdateOptions() { - if ((int)Tools::getValue('PS_PAYMENT_RECEIPTS_START_NUMBER') != 0 && (int)Tools::getValue('PS_PAYMENT_RECEIPTS_START_NUMBER') <= Order::getLastPaymentNumber()) { - $this->errors[] = $this->l('Invalid receipt number.').Order::getLastPaymentNumber().')'; + if ((int)Tools::getValue('PS_PAYMENT_RECEIPTS_START_NUMBER') != 0 && (int)Tools::getValue('PS_PAYMENT_RECEIPTS_START_NUMBER') <= OrderPaymentDetail::getLastPaymentReceiptNumber()) { + $this->errors[] = $this->l('Invalid receipt number.').OrderPaymentDetail::getLastPaymentReceiptNumber().')'; } } } diff --git a/controllers/admin/AdminPdfController.php b/controllers/admin/AdminPdfController.php index 279529b2a..2e534d75b 100644 --- a/controllers/admin/AdminPdfController.php +++ b/controllers/admin/AdminPdfController.php @@ -156,29 +156,28 @@ class AdminPdfControllerCore extends AdminController $this->generatePDF($supply_order, PDF::TEMPLATE_SUPPLY_ORDER_FORM); } + public function processGeneratePaymentReceiptsPDF() + { + $order_payment_collection = OrderPaymentDetail::getByDateInterval( + Tools::getValue('date_from'), + Tools::getValue('date_to') + ); + + if (!count($order_payment_collection)) { + die(Tools::displayError('No payment receipt was found.')); + } + + $this->generatePDF($order_payment_collection, PDF::TEMPLATE_PAYMENT_RECEIPT); + } + public function processGeneratePaymentReceipt() { - $idOrderPayment = (int) Tools::getValue('id_order_payment'); - $idOrder = (int) Tools::getValue('id_order'); - - if ($idOrderPayment <= 0 && $idOrder <= 0) { - die(Tools::displayError('The order or order payment id is missing.')); + $idOrderPaymentDetail = (int) Tools::getValue('id_order_payment_detail'); + $orderPaymentDetail = new OrderPaymentDetail($idOrderPaymentDetail); + if (!Validate::isLoadedObject($orderPaymentDetail)) { + die(Tools::displayError('The order payment detail cannot be found.')); } - - if ($idOrderPayment > 0) { - $orderPayment = new OrderPayment($idOrderPayment); - - if (!Validate::isLoadedObject($orderPayment)) { - die(Tools::displayError('The order payment cannot be found.')); - } - } else { - $orderPayment = OrderPayment::getTransactionDetailByOrderid($idOrder); - if (!$orderPayment || !count($orderPayment)) { - die(Tools::displayError('No payment found for this order.')); - } - } - - $this->generatePDF($orderPayment, PDF::TEMPLATE_PAYMENT_RECEIPT); + $this->generatePDF($orderPaymentDetail, PDF::TEMPLATE_PAYMENT_RECEIPT); } diff --git a/install/data/db_structure.sql b/install/data/db_structure.sql index 8cfeeb755..71fb5e17a 100644 --- a/install/data/db_structure.sql +++ b/install/data/db_structure.sql @@ -1496,7 +1496,6 @@ CREATE TABLE `PREFIX_order_payment` ( `order_reference` VARCHAR(9), `id_currency` INT UNSIGNED NOT NULL, `amount` DECIMAL(10,2) NOT NULL, - `number` varchar(255) NOT NULL, `payment_method` varchar(255) NOT NULL, `payment_type` INT UNSIGNED NOT NULL, `conversion_rate` decimal(13,6) NOT NULL DEFAULT 1, @@ -1515,6 +1514,7 @@ CREATE TABLE `PREFIX_order_payment_detail` ( `id_order_payment` INT(10) unsigned NOT NULL, `id_order` INT(10) unsigned NOT NULL, `amount` DECIMAL(10,2) NOT NULL, + `receipt_number` INT(10) NOT NULL, `date_add` DATETIME NOT NULL, PRIMARY KEY (`id_order_payment_detail`) ) ENGINE=ENGINE_TYPE DEFAULT CHARSET=utf8 COLLATION; diff --git a/pdf/payment-receipt.tpl b/pdf/payment-receipt.tpl index 470b49eae..a0a0141d3 100644 --- a/pdf/payment-receipt.tpl +++ b/pdf/payment-receipt.tpl @@ -1,26 +1,23 @@ {* -* 2007-2015 PrestaShop -* * NOTICE OF LICENSE * -* This source file is subject to the Academic Free License (AFL 3.0) -* that is bundled with this package in the file LICENSE.txt. +* This source file is subject to the Open Software License version 3.0 +* that is bundled with this package in the file LICENSE.md * It is also available through the world-wide-web at this URL: -* http://opensource.org/licenses/afl-3.0.php +* https://opensource.org/license/osl-3-0-php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email -* to license@prestashop.com so we can send you a copy immediately. +* to support@qloapps.com so we can send you a copy immediately. * * DISCLAIMER * -* Do not edit or add to this file if you wish to upgrade PrestaShop to newer -* versions in the future. If you wish to customize PrestaShop for your -* needs please refer to http://www.prestashop.com for more information. +* Do not edit or add to this file if you wish to upgrade this module to a newer +* versions in the future. If you wish to customize this module for your needs +* please refer to https://store.webkul.com/customisation-guidelines for more information. * -* @author PrestaShop SA -* @copyright 2007-2015 PrestaShop SA -* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) -* International Registered Trademark & Property of PrestaShop SA +* @author Webkul IN +* @copyright Since 2010 Webkul +* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0 *} {$style_tab} diff --git a/pdf/receipt.addresses-tab.tpl b/pdf/payment.receipt.addresses-tab.tpl similarity index 58% rename from pdf/receipt.addresses-tab.tpl rename to pdf/payment.receipt.addresses-tab.tpl index df5e70de0..2ca023cbe 100644 --- a/pdf/receipt.addresses-tab.tpl +++ b/pdf/payment.receipt.addresses-tab.tpl @@ -1,26 +1,23 @@ {* -* 2007-2015 PrestaShop -* * NOTICE OF LICENSE * -* This source file is subject to the Academic Free License (AFL 3.0) -* that is bundled with this package in the file LICENSE.txt. +* This source file is subject to the Open Software License version 3.0 +* that is bundled with this package in the file LICENSE.md * It is also available through the world-wide-web at this URL: -* http://opensource.org/licenses/afl-3.0.php +* https://opensource.org/license/osl-3-0-php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email -* to license@prestashop.com so we can send you a copy immediately. +* to support@qloapps.com so we can send you a copy immediately. * * DISCLAIMER * -* Do not edit or add to this file if you wish to upgrade PrestaShop to newer -* versions in the future. If you wish to customize PrestaShop for your -* needs please refer to http://www.prestashop.com for more information. +* Do not edit or add to this file if you wish to upgrade this module to a newer +* versions in the future. If you wish to customize this module for your needs +* please refer to https://store.webkul.com/customisation-guidelines for more information. * -* @author PrestaShop SA -* @copyright 2007-2015 PrestaShop SA -* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) -* International Registered Trademark & Property of PrestaShop SA +* @author Webkul IN +* @copyright Since 2010 Webkul +* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0 *} diff --git a/pdf/payment.receipt.payment-info-tab.tpl b/pdf/payment.receipt.payment-info-tab.tpl new file mode 100644 index 000000000..307bc1de1 --- /dev/null +++ b/pdf/payment.receipt.payment-info-tab.tpl @@ -0,0 +1,50 @@ +{* +* NOTICE OF LICENSE +* +* This source file is subject to the Open Software License version 3.0 +* that is bundled with this package in the file LICENSE.md +* It is also available through the world-wide-web at this URL: +* https://opensource.org/license/osl-3-0-php +* If you did not receive a copy of the license and are unable to +* obtain it through the world-wide-web, please send an email +* to support@qloapps.com so we can send you a copy immediately. +* +* DISCLAIMER +* +* Do not edit or add to this file if you wish to upgrade this module to a newer +* versions in the future. If you wish to customize this module for your needs +* please refer to https://store.webkul.com/customisation-guidelines for more information. +* +* @author Webkul IN +* @copyright Since 2010 Webkul +* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0 +*} + +{if isset($payment_list) && $payment_list} +
+ + + + + + + + + + + + + + {foreach from=$payment_list key=rm_k item=payment} + {cycle values=["color_line_even", "color_line_odd"] assign=bgcolor_class} + + + + + + + + {/foreach} + +
{l s='Payment Details' pdf='true'}
{l s='Payment Method' pdf='true'}{l s='Payment Type' pdf='true'}{l s='Transaction ID' pdf='true'}{l s='Amount' pdf='true'}{l s='Payment Date' pdf='true'}
{$payment->payment_method}{$payment->payment_type}{$payment->transaction_id}{displayPrice currency=$payment->id_currency price=$payment->amount}{dateFormat date=$payment->date_add full=true}
+{/if} \ No newline at end of file diff --git a/pdf/receipt.style-tab.tpl b/pdf/payment.receipt.style-tab.tpl similarity index 82% rename from pdf/receipt.style-tab.tpl rename to pdf/payment.receipt.style-tab.tpl index e34c365c9..e6fb20ae7 100644 --- a/pdf/receipt.style-tab.tpl +++ b/pdf/payment.receipt.style-tab.tpl @@ -1,26 +1,23 @@ {* -* 2007-2015 PrestaShop -* * NOTICE OF LICENSE * -* This source file is subject to the Academic Free License (AFL 3.0) -* that is bundled with this package in the file LICENSE.txt. +* This source file is subject to the Open Software License version 3.0 +* that is bundled with this package in the file LICENSE.md * It is also available through the world-wide-web at this URL: -* http://opensource.org/licenses/afl-3.0.php +* https://opensource.org/license/osl-3-0-php * If you did not receive a copy of the license and are unable to * obtain it through the world-wide-web, please send an email -* to license@prestashop.com so we can send you a copy immediately. +* to support@qloapps.com so we can send you a copy immediately. * * DISCLAIMER * -* Do not edit or add to this file if you wish to upgrade PrestaShop to newer -* versions in the future. If you wish to customize PrestaShop for your -* needs please refer to http://www.prestashop.com for more information. +* Do not edit or add to this file if you wish to upgrade this module to a newer +* versions in the future. If you wish to customize this module for your needs +* please refer to https://store.webkul.com/customisation-guidelines for more information. * -* @author PrestaShop SA -* @copyright 2007-2015 PrestaShop SA -* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) -* International Registered Trademark & Property of PrestaShop SA +* @author Webkul IN +* @copyright Since 2010 Webkul +* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0 *} {assign var=color_header value="#F0F0F0"} @@ -150,7 +147,7 @@ } .left { - text-align: left; + text-align: left!important; } .fright { diff --git a/pdf/receipt.payment-info-tab.tpl b/pdf/receipt.payment-info-tab.tpl deleted file mode 100644 index d39879c19..000000000 --- a/pdf/receipt.payment-info-tab.tpl +++ /dev/null @@ -1,63 +0,0 @@ -{* -* 2007-2015 PrestaShop -* -* NOTICE OF LICENSE -* -* This source file is subject to the Academic Free License (AFL 3.0) -* that is bundled with this package in the file LICENSE.txt. -* It is also available through the world-wide-web at this URL: -* http://opensource.org/licenses/afl-3.0.php -* If you did not receive a copy of the license and are unable to -* obtain it through the world-wide-web, please send an email -* to license@prestashop.com so we can send you a copy immediately. -* -* DISCLAIMER -* -* Do not edit or add to this file if you wish to upgrade PrestaShop to newer -* versions in the future. If you wish to customize PrestaShop for your -* needs please refer to http://www.prestashop.com for more information. -* -* @author PrestaShop SA -* @copyright 2007-2015 PrestaShop SA -* @license http://opensource.org/licenses/afl-3.0.php Academic Free License (AFL 3.0) -* International Registered Trademark & Property of PrestaShop SA -*} - -{if isset($payment_list) && $payment_list} - - - - - - - - - - - - - - - {foreach from=$payment_list key=rm_k item=payment} - {cycle values=["color_line_even", "color_line_odd"] assign=bgcolor_class} - - - - - - - - {/foreach} - -
{l s='Payment Details' pdf='true'}
{l s='Payment Date' pdf='true'}{l s='Payment Method' pdf='true'}{l s='Payment Type' pdf='true'}{l s='Transaction ID' pdf='true'}{l s='Amount' pdf='true'}
- {dateFormat date=$payment->date_add full=true} - - {$payment->payment_method} - - {$payment->payment_type} - - {$payment->transaction_id} - - {displayPrice currency=$payment->id_currency price=$payment->amount} -
-{/if} \ No newline at end of file From 9376843be4f919d168cfcdfb69939fa9b5680f88 Mon Sep 17 00:00:00 2001 From: azhar bhat Date: Wed, 29 Apr 2026 11:03:38 +0530 Subject: [PATCH 06/35] composer updated --- composer.json | 40 ++++++++++++++++++++++++++++++++-------- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index 0e5523d61..38b986fee 100644 --- a/composer.json +++ b/composer.json @@ -4,29 +4,53 @@ "type": "project", "homepage": "https://qloapps.com", "license": "OSL-3.0", + "keywords": [ + "hotel", + "hotel-management", + "booking", + "booking-engine", + "reservation", + "hospitality", + "property-management", + "qloapps", + "cms" + ], "authors": [ { "name": "QloApps", - "email": "support@qloapps.com" + "email": "support@qloapps.com", + "homepage": "https://qloapps.com" }, { "name": "QloApps Community", - "homepage": "https://qloapps.com" + "homepage": "https://github.com/Qloapps/QloApps/graphs/contributors" } ], "support": { + "docs": "https://qloapps.com/docs/", + "email": "support@qloapps.com", "forum": "https://forums.qloapps.com/", "issues": "https://github.com/Qloapps/QloApps/issues", "source": "https://github.com/Qloapps/QloApps" }, "require": { - "php": ">=8.0 <8.5", - "ext-simplexml": "*", + "php": ">8.0 <8.5", "ext-curl": "*", + "ext-dom": "*", "ext-gd": "*", - "ext-zip": "*", - "ext-soap": "*" + "ext-intl": "*", + "ext-json": "*", + "ext-mbstring": "*", + "ext-openssl": "*", + "ext-pdo": "*", + "ext-phar": "*", + "ext-simplexml": "*", + "ext-soap": "*", + "ext-zip": "*" }, - "minimum-stability": "dev", - "prefer-stable": true + "minimum-stability": "stable", + "prefer-stable": true, + "config": { + "sort-packages": true + } } \ No newline at end of file From ca6139bc506763ce596b733949cf32d0f1207098 Mon Sep 17 00:00:00 2001 From: varun kewlani Date: Fri, 1 May 2026 12:12:31 +0530 Subject: [PATCH 09/35] Added feature: Booking Voucher PDF in order detail page Co-authored-by: Name --- classes/pdf/HTMLTemplateBookingVoucher.php | 846 ++++++++++++++++++ classes/pdf/PDF.php | 1 + controllers/admin/AdminOrdersController.php | 15 +- controllers/admin/AdminPdfController.php | 26 + pdf/booking-voucher.tpl | 428 +++++++++ themes/hotel-reservation-theme/css/global.css | 7 +- 6 files changed, 1315 insertions(+), 8 deletions(-) create mode 100644 classes/pdf/HTMLTemplateBookingVoucher.php create mode 100644 pdf/booking-voucher.tpl diff --git a/classes/pdf/HTMLTemplateBookingVoucher.php b/classes/pdf/HTMLTemplateBookingVoucher.php new file mode 100644 index 000000000..e1e4a8d7e --- /dev/null +++ b/classes/pdf/HTMLTemplateBookingVoucher.php @@ -0,0 +1,846 @@ +order = $order; + $this->smarty = $smarty; + $this->date = Tools::displayDate(date('Y-m-d')); + $this->title = sprintf(self::l('Booking Voucher - %s'), $this->order->reference); + $this->shop = new Shop((int)$this->order->id_shop); + } + + /** + * @return string HTML header + */ + public function getHeader() + { + $this->assignCommonHeaderData(); + $this->smarty->assign(array('header' => self::l('Booking Voucher'))); + + return $this->smarty->fetch($this->getTemplate('header')); + } + + /** + * Assembles all voucher data inline and renders the template. + * + * @return string HTML content + */ + public function getContent() + { + // --- Bookings --- + $objHotelBookingDetail = new HotelBookingDetail(); + $bookings = $objHotelBookingDetail->getBookingDataByOrderId((int)$this->order->id); + if (!is_array($bookings)) { + $bookings = array(); + } + + // --- Order state --- + $orderState = $this->order->getCurrentStateFull((int)$this->order->id_lang); + if (!is_array($orderState)) { + $orderState = array('name' => self::l('Confirmed')); + } elseif (empty($orderState['name'])) { + $orderState['name'] = self::l('Confirmed'); + } + + // --- Hotel / property --- + $hotelName = Configuration::get('PS_SHOP_NAME', null, null, (int)$this->order->id_shop); + $hotelPhone = Configuration::get('PS_SHOP_PHONE', null, null, (int)$this->order->id_shop); + $hotelEmail = Configuration::get('PS_SHOP_EMAIL', null, null, (int)$this->order->id_shop); + $hotelAddress = ''; + $checkInTime = '--'; + $checkOutTime = '--'; + $paymentPolicy = ''; + $cancellationPolicy = ''; + + if (!empty($bookings)) { + $firstBooking = reset($bookings); + if (!empty($firstBooking['id_hotel'])) { + $objHotel = new HotelBranchInformation((int)$firstBooking['id_hotel'], (int)$this->order->id_lang); + if (Validate::isLoadedObject($objHotel)) { + $idHotel = (int)$objHotel->id; + $hotelName = $objHotel->hotel_name ? $objHotel->hotel_name : $hotelName; + $hotelPhone = $objHotel->phone ? $objHotel->phone : $hotelPhone; + $hotelEmail = $objHotel->email ? $objHotel->email : $hotelEmail; + $checkInTime = $objHotel->check_in ?: '--'; + $checkOutTime = $objHotel->check_out ?: '--'; + $policies = is_array($objHotel->policies) ? '' : (string)$objHotel->policies; + if ($policies) { + $policyText = html_entity_decode($policies, ENT_QUOTES, 'UTF-8'); + $policyText = preg_replace('//i', "\n", $policyText); + $policyText = strip_tags($policyText); + $policyText = preg_replace("/[\r\n]+/", "\n", $policyText); + $paymentPolicy = nl2br(trim($policyText)); + } + $cancellationPolicy = $this->getFormattedCancellationPolicy($idHotel); + + if ($idHotelAddress = $objHotel->getHotelIdAddress()) { + $objHotelAddress = new Address((int)$idHotelAddress); + if (Validate::isLoadedObject($objHotelAddress)) { + $objHotelAddress->firstname = $hotelName; + $hotelAddress = AddressFormat::generateAddress( + $objHotelAddress, + array('avoid' => array('lastname')), + '
', + ' ' + ); + } + } + } + } + } + + if (!$hotelAddress) { + $shopAddress = $this->shop->getAddress(); + if ($shopAddress instanceof Address) { + $hotelAddress = AddressFormat::generateAddress($shopAddress, array(), '
', ' '); + } + } + + // --- Room and service data --- + $roomAndServiceData = $this->getRoomAndServiceData($bookings); + + // --- Guest details --- + $guestName = ''; + $guestEmail = ''; + $guestPhone = ''; + $guestAddress = ''; + + $customer = new Customer((int)$this->order->id_customer); + if (Validate::isLoadedObject($customer)) { + $guestName = trim($customer->firstname.' '.$customer->lastname); + $guestEmail = $customer->email; + $guestPhone = $customer->phone; + } + $addressId = (int)$this->order->id_address_invoice ?: (int)$this->order->id_address_delivery; + if ($addressId) { + $address = new Address($addressId); + if (Validate::isLoadedObject($address)) { + $guestPhone = $address->phone_mobile ? $address->phone_mobile : $address->phone; + $guestAddress = AddressFormat::generateAddress($address, array(), '
', ' '); + } + } + // Override with primary guest if this is a guest booking + if ($idCustomerGuestDetail = OrderCustomerGuestDetail::isCustomerGuestBooking((int)$this->order->id)) { + $traveler = new OrderCustomerGuestDetail((int)$idCustomerGuestDetail); + if (Validate::isLoadedObject($traveler)) { + $guestName = trim($traveler->firstname.' '.$traveler->lastname); + $guestEmail = $traveler->email; + $guestPhone = $traveler->phone; + } + } + + $footer = $this->getTotalsData($roomAndServiceData); + + // --- Payment --- + $paymentMethods = array(); + $payments = $this->order->getOrderPaymentDetail(); + if (is_array($payments) && $payments) { + foreach ($payments as $paymentDetail) { + $method = isset($paymentDetail['payment_method']) ? trim($paymentDetail['payment_method']) : ''; + if (!$method) { + $method = $this->order->payment; + } + $amount = isset($paymentDetail['real_paid_amount']) + ? (float)$paymentDetail['real_paid_amount'] + : (float)$paymentDetail['amount']; + $idCurrency = isset($paymentDetail['id_currency']) + ? (int)$paymentDetail['id_currency'] + : (int)$this->order->id_currency; + $paymentMethods[] = $method.' - '.Tools::displayPrice($amount, $idCurrency); + } + } + $paymentMethod = $paymentMethods ? implode('

', $paymentMethods) : $this->order->payment; + + $data = array( + 'order' => $this->order, + 'order_status' => $orderState['name'], + 'booking_date' => Tools::displayDate($this->order->date_add, null, false), + 'issue_date' => Tools::displayDate(date('Y-m-d'), null, false), + // hotel / property + 'hotel_name' => $hotelName, + 'hotel_phone' => $hotelPhone, + 'hotel_email' => $hotelEmail, + 'hotel_address' => $hotelAddress, + // guest + 'guest_name' => $guestName, + 'guest_email' => $guestEmail, + 'guest_phone' => $guestPhone, + 'guest_address' => $guestAddress, + 'total_guests' => (int)$roomAndServiceData['total_guests'], + // booking summary + 'total_rooms' => (int)$roomAndServiceData['total_rooms'], + 'total_room_nights' => (int)$roomAndServiceData['total_room_nights'], + // room and service tables + 'room_type_rows' => $roomAndServiceData['room_type_rows'], + 'service_rows' => $roomAndServiceData['service_rows'], + // payment + 'payment_method' => $paymentMethod, + 'payment_status' => $orderState['name'], + 'is_advance_payment' => (int)$this->order->is_advance_payment, + // totals + 'footer' => $footer, + // policies + 'check_in_time' => ($checkInTime && $checkInTime !== '--') + ? date('h:i a', strtotime($checkInTime)) : '--', + 'check_out_time' => ($checkOutTime && $checkOutTime !== '--') + ? date('h:i a', strtotime($checkOutTime)) : '--', + 'payment_policy' => $paymentPolicy + ? $paymentPolicy + : self::l('Please contact the property for policy details.'), + 'cancellation_policy' => $cancellationPolicy, + 'has_cancellation_policy' => (bool)trim(strip_tags($cancellationPolicy)), + // tax + 'tax_breakdown' => $this->getTaxBreakdownData(), + ); + + if (Tools::getValue('debug')) { + die(json_encode($data)); + } + $this->smarty->assign($data); + + return $this->smarty->fetch($this->getTemplate('booking-voucher')); + } + + /** + * Builds per-room and per-room-type rows, services, price breakdown, and occupancy totals. + * + * @param array $bookings + * @return array + */ + protected function getRoomAndServiceData($bookings) + { + $rooms = array(); + $serviceRows = array(); + $servicesByRoom = array(); + $priceBreakdown = array(); + $roomTypeRows = array(); + $totalRoomNights = 0; + $totalGuests = 0; + + foreach ($bookings as $booking) { + $nights = (int)HotelHelper::getNumberOfDays($booking['date_from'], $booking['date_to']); + if ($nights <= 0) { + $nights = 1; + } + + $roomReference = !empty($booking['room_num']) ? $booking['room_num'] : 'BK-'.(int)$booking['id']; + $adults = (int)$booking['adults']; + $children = (int)$booking['children']; + $totalGuests += $adults + $children; + $totalRoomNights += $nights; + + $serviceData = $this->getRoomServiceData($booking, $roomReference); + $servicesByRoom[$roomReference] = $serviceData['services']; + $serviceRows = array_merge($serviceRows, $serviceData['rows']); + + $roomTotalTaxExcl = (float)$booking['total_price_tax_excl']; + $roomTotalWithAutoServicesTaxExcl = $roomTotalTaxExcl + $serviceData['auto_added_room_total_tax_excl']; + $unitPriceTaxExcl = $nights ? Tools::ps_round( + $roomTotalWithAutoServicesTaxExcl / $nights, + _PS_PRICE_COMPUTE_PRECISION_, + $this->order->round_mode + ) : 0; + + // Group by room type + date range to aggregate multi-room rows + $roomTypeRowKey = implode('|', array( + (int)$booking['id_order_detail'], + isset($booking['room_type_name']) ? $booking['room_type_name'] : '', + isset($booking['date_from']) ? $booking['date_from'] : '', + isset($booking['date_to']) ? $booking['date_to'] : '', + )); + + if (!isset($roomTypeRows[$roomTypeRowKey])) { + $idOrderDetail = (int)$booking['id_order_detail']; + $detailTaxes = $idOrderDetail ? OrderDetail::getTaxListStatic($idOrderDetail) : null; + $orderDetailTaxLabel = self::l('No tax'); + if ($detailTaxes) { + $taxTemp = array(); + foreach ($detailTaxes as $tax) { + $objTax = new Tax($tax['id_tax']); + $taxTemp[] = sprintf(self::l('%1$s%2$s%%'), $objTax->rate + 0, ' '); + } + $orderDetailTaxLabel = implode(', ', $taxTemp); + } + + $roomTypeRows[$roomTypeRowKey] = array( + 'room_type_name' => isset($booking['room_type_name']) ? $booking['room_type_name'] : '', + 'order_detail_tax_label' => $orderDetailTaxLabel, + 'rooms' => 0, + 'adults' => 0, + 'children' => 0, + 'nights' => $nights, + 'check_in_date' => $this->formatDisplayDate($booking['date_from']), + 'check_out_date' => $this->formatDisplayDate($booking['date_to']), + 'unit_price_tax_excl' => 0, + 'total_price_tax_excl' => 0, + ); + } + + $roomTypeRows[$roomTypeRowKey]['rooms'] += 1; + $roomTypeRows[$roomTypeRowKey]['adults'] += $adults; + $roomTypeRows[$roomTypeRowKey]['children'] += $children; + $roomTypeRows[$roomTypeRowKey]['total_price_tax_excl'] += $roomTotalWithAutoServicesTaxExcl; + + $rooms[] = array( + 'room_ref' => $roomReference, + 'room_num' => isset($booking['room_num']) ? $booking['room_num'] : '', + 'room_type_name' => isset($booking['room_type_name']) ? $booking['room_type_name'] : '', + 'check_in_date' => $this->formatDisplayDate($booking['date_from']), + 'check_out_date' => $this->formatDisplayDate($booking['date_to']), + 'nights' => $nights, + 'adults' => $adults, + 'children' => $children, + 'unit_price_tax_excl' => $unitPriceTaxExcl, + 'total_price_tax_excl' => $roomTotalWithAutoServicesTaxExcl, + ); + + $priceBreakdown[] = array( + 'room_ref' => $roomReference, + 'room_type_name' => isset($booking['room_type_name']) ? $booking['room_type_name'] : '', + 'nights' => $nights, + 'unit_price_tax_excl' => $unitPriceTaxExcl, + 'room_total_tax_excl' => $roomTotalWithAutoServicesTaxExcl, + 'services_total_tax_excl' => $serviceData['total_price_tax_excl'], + 'subtotal_tax_excl' => $roomTotalWithAutoServicesTaxExcl + $serviceData['total_price_tax_excl'], + ); + } + + foreach ($roomTypeRows as &$roomTypeRow) { + $denominator = (int)$roomTypeRow['nights'] * (int)$roomTypeRow['rooms']; + if ($denominator > 0) { + $roomTypeRow['unit_price_tax_excl'] = Tools::ps_round( + $roomTypeRow['total_price_tax_excl'] / $denominator, + 6, + $this->order->round_mode + ); + } + } + unset($roomTypeRow); + + return array( + 'rooms' => $rooms, + 'room_type_rows' => array_values($roomTypeRows), + 'service_rows' => $serviceRows, + 'services_by_room' => $servicesByRoom, + 'price_breakdown' => $priceBreakdown, + 'total_rooms' => count($rooms), + 'total_room_nights' => $totalRoomNights, + 'total_guests' => $totalGuests, + ); + } + + protected function getRoomServiceData($booking, $roomReference) + { + $rows = array(); + $services = array(); + $totalPriceTaxExcl = 0; + + $objBookingDemand = new HotelBookingDemands(); + $extraDemands = $objBookingDemand->getRoomTypeBookingExtraDemands( + (int)$this->order->id, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + (int)$booking['id'], + (int)$booking['id_order_detail'] + ); + + if (is_array($extraDemands)) { + foreach ($extraDemands as $demand) { + $services[] = array( + 'room_ref' => $roomReference, + 'check_in_out' => $this->formatDisplayDate($booking['date_from']).' - '.$this->formatDisplayDate($booking['date_to']), + 'name' => $demand['name'], + 'quantity' => 1, + 'total_price_tax_excl' => (float)$demand['total_price_tax_excl'], + ); + $rows[] = array( + 'room_ref' => $roomReference, + 'check_in_date' => $this->formatDisplayDate($booking['date_from']), + 'check_out_date' => $this->formatDisplayDate($booking['date_to']), + 'name' => $demand['name'], + 'quantity' => 1, + ); + $totalPriceTaxExcl += (float)$demand['total_price_tax_excl']; + } + } + + $objServiceProductOrderDetail = new ServiceProductOrderDetail(); + $autoAddedWithRoomPriceTaxExcl = (float)$objServiceProductOrderDetail->getRoomTypeServiceProducts( + (int)$this->order->id, + 0, + 0, + isset($booking['id_product']) ? (int)$booking['id_product'] : 0, + isset($booking['date_from']) ? $booking['date_from'] : 0, + isset($booking['date_to']) ? $booking['date_to'] : 0, + isset($booking['id_room']) ? (int)$booking['id_room'] : 0, + 1, + 0, + 1, + Product::PRICE_ADDITION_TYPE_WITH_ROOM + ); + + $additionalServices = $objServiceProductOrderDetail->getRoomTypeServiceProducts( + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, null, 0, (int)$booking['id'] + ); + + if (isset($additionalServices[(int)$booking['id']]['additional_services'])) { + foreach ($additionalServices[(int)$booking['id']]['additional_services'] as $service) { + $services[] = array( + 'room_ref' => $roomReference, + 'check_in_out' => $this->formatDisplayDate($booking['date_from']).' - '.$this->formatDisplayDate($booking['date_to']), + 'name' => $service['name'], + 'quantity' => (int)$service['quantity'], + 'total_price_tax_excl' => (float)$service['total_price_tax_excl'], + 'product_tax_label' => isset($service['product_tax_label']) ? $service['product_tax_label'] : self::l('No tax'), + ); + $rows[] = array( + 'room_ref' => $roomReference, + 'check_in_date' => $this->formatDisplayDate($booking['date_from']), + 'check_out_date' => $this->formatDisplayDate($booking['date_to']), + 'name' => $service['name'], + 'quantity' => (int)$service['quantity'], + 'total_price_tax_excl' => (float)$service['total_price_tax_excl'], + 'product_tax_label' => isset($service['product_tax_label']) ? $service['product_tax_label'] : self::l('No tax'), + ); + $totalPriceTaxExcl += (float)$service['total_price_tax_excl']; + } + } + + return array( + 'services' => $services, + 'rows' => $rows, + 'total_price_tax_excl' => $totalPriceTaxExcl, + 'auto_added_room_total_tax_excl' => $autoAddedWithRoomPriceTaxExcl, + ); + } + + /** + * Computes all cost totals using Order's existing methods. + * Mirrors the $footer calculation in HTMLTemplateInvoice::getContent(). + * + * @param array $roomAndServiceData + * @return array + */ + protected function getTotalsData($roomAndServiceData) + { + $objBookingDemand = new HotelBookingDemands(); + $totalDemandsPriceTE = (float)$objBookingDemand->getRoomTypeBookingExtraDemands( + (int)$this->order->id, 0, 0, 0, 0, 1, 1, 0 + ); + $totalDemandsPriceTI = (float)$objBookingDemand->getRoomTypeBookingExtraDemands( + (int)$this->order->id, 0, 0, 0, 0, 1, 1, 1 + ); + + $idsOrderDetail = $this->getOrderDetailIds(); + + $roomsCost = 0; + $roomsCostTaxIncl = 0; + $serviceProductsCost = 0; + $serviceProductsCostTaxIncl = 0; + $additionalServicesCost = 0; + $additionalServicesCostTaxIncl = 0; + $convenienceFee = 0; + $convenienceFeeTaxIncl = 0; + $productDiscountsTaxExcl = (float)$this->order->total_discounts_tax_excl; + $productDiscountsTaxIncl = (float)$this->order->total_discounts_tax_incl; + + foreach ($this->order->getCartRules() as $cartRule) { + if (!empty($cartRule['free_shipping'])) { + $productDiscountsTaxExcl -= (float)$this->order->total_shipping_tax_excl; + $productDiscountsTaxIncl -= (float)$this->order->total_shipping_tax_incl; + } + } + + if ($productDiscountsTaxExcl < 0) { + $productDiscountsTaxExcl = 0; + } + if ($productDiscountsTaxIncl < 0) { + $productDiscountsTaxIncl = 0; + } + + if (!empty($idsOrderDetail)) { + $roomsCost = (float)$this->order->getTotalProductsWithoutTaxes( + false, true, null, null, null, $idsOrderDetail + ) + (float)$this->order->getTotalProductsWithoutTaxes( + false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, + 1, Product::PRICE_ADDITION_TYPE_WITH_ROOM, $idsOrderDetail + ); + $roomsCostTaxIncl = (float)$this->order->getTotalProductsWithTaxes( + false, true, null, null, null, $idsOrderDetail + ) + (float)$this->order->getTotalProductsWithTaxes( + false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, + 1, Product::PRICE_ADDITION_TYPE_WITH_ROOM, $idsOrderDetail + ); + + $serviceProductsCost = (float)$this->order->getTotalProductsWithoutTaxes( + false, false, Product::SELLING_PREFERENCE_STANDALONE, false, false, $idsOrderDetail + ) + (float)$this->order->getTotalProductsWithoutTaxes( + false, false, Product::SELLING_PREFERENCE_HOTEL_STANDALONE, false, false, $idsOrderDetail + ); + $serviceProductsCostTaxIncl = (float)$this->order->getTotalProductsWithTaxes( + false, false, Product::SELLING_PREFERENCE_STANDALONE, false, false, $idsOrderDetail + ) + (float)$this->order->getTotalProductsWithTaxes( + false, false, Product::SELLING_PREFERENCE_HOTEL_STANDALONE, false, false, $idsOrderDetail + ); + + $additionalServicesCost = (float)$this->order->getTotalProductsWithoutTaxes( + false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, 0, null, $idsOrderDetail + ) + $totalDemandsPriceTE; + $additionalServicesCostTaxIncl = (float)$this->order->getTotalProductsWithTaxes( + false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, 0, null, $idsOrderDetail + ) + $totalDemandsPriceTI; + + $convenienceFee = (float)$this->order->getTotalProductsWithoutTaxes( + false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, + 1, Product::PRICE_ADDITION_TYPE_INDEPENDENT, $idsOrderDetail + ); + $convenienceFeeTaxIncl = (float)$this->order->getTotalProductsWithTaxes( + false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, + 1, Product::PRICE_ADDITION_TYPE_INDEPENDENT, $idsOrderDetail + ); + } + + $grandTotal = (float)$this->order->total_paid_tax_incl; + $totalPaidReal = (float)$this->order->getTotalPaid(); + $totalWithoutDiscountTaxExcl = $roomsCost + $serviceProductsCost + $additionalServicesCost + $convenienceFee; + $totalWithoutDiscountTaxIncl = $roomsCostTaxIncl + $serviceProductsCostTaxIncl + $additionalServicesCostTaxIncl + $convenienceFeeTaxIncl; + $totalTaxes = $totalWithoutDiscountTaxIncl - $totalWithoutDiscountTaxExcl; + if ($totalTaxes < 0) { + $totalTaxes = 0; + } + + $amountDue = $grandTotal - $totalPaidReal; + if ($amountDue < 0) { + $amountDue = 0; + } + + return array( + 'total_rooms' => (int)$roomAndServiceData['total_rooms'], + 'total_room_nights' => (int)$roomAndServiceData['total_room_nights'], + 'total_guests' => (int)$roomAndServiceData['total_guests'], + 'rooms_cost' => $roomsCost, + 'room_price_tax_excl' => $roomsCost, + 'room_price_tax_incl' => $roomsCostTaxIncl, + 'service_products_price_tax_excl' => $serviceProductsCost, + 'service_products_price_tax_incl' => $serviceProductsCostTaxIncl, + 'additional_service_price_tax_excl' => $additionalServicesCost, + 'additional_service_price_tax_incl' => $additionalServicesCostTaxIncl, + 'extra_services_cost' => $additionalServicesCost + $serviceProductsCost, + 'convenience_fee' => $convenienceFee, + 'total_convenience_fee_te' => $convenienceFee, + 'total_convenience_fee_ti' => $convenienceFeeTaxIncl, + 'total_paid_tax_excl' => (float)$this->order->total_paid_tax_excl, + 'total_taxes' => $totalTaxes, + 'total_rooms_tax' => $totalTaxes, + 'extra_services_tax' => 0, + 'total_without_discount_te' => $totalWithoutDiscountTaxExcl, + 'total_without_discount_ti' => $totalWithoutDiscountTaxIncl, + 'total_tax_without_discount' => $totalTaxes, + 'product_discounts_tax_excl' => $productDiscountsTaxExcl, + 'product_discounts_tax_incl' => $productDiscountsTaxIncl, + 'grand_total' => $grandTotal, + 'total_paid' => $totalPaidReal, + 'total_paid_real' => $totalPaidReal, + 'total_paid_tax_incl' => $grandTotal, + 'amount_due' => Tools::ps_round($amountDue, _PS_PRICE_COMPUTE_PRECISION_, $this->order->round_mode), + ); + } + + /** + * Builds tax breakdown by product type, handling both order detail taxes and extra demand taxes separately. + * Extracted: two-pass algorithm with per-type bucketing. + * + * @return array + */ + protected function getTaxBreakdownData() + { + $breakdowns = array( + 'room_tax' => array(), + 'additional_services_tax' => array(), + 'convenience_fee_tax' => array(), + 'extra_demands_tax' => array(), + 'service_products_tax' => array(), + ); + + $orderDetails = OrderDetail::getList((int)$this->order->id); + if (is_array($orderDetails)) { + foreach ($orderDetails as $orderDetail) { + $breakdownType = $this->getTaxBreakdownType($orderDetail); + if (!$breakdownType) { + continue; + } + + $taxes = OrderDetail::getTaxListStatic((int)$orderDetail['id_order_detail']); + if (!$taxes) { + $taxAmount = (float)$orderDetail['total_price_tax_incl'] - (float)$orderDetail['total_price_tax_excl']; + if ($taxAmount <= 0) { + continue; + } + $taxes = array(array( + 'id_tax' => 0, + 'rate' => (float)$orderDetail['total_price_tax_excl'] > 0 + ? (($taxAmount / (float)$orderDetail['total_price_tax_excl']) * 100) : 0, + 'total_amount' => $taxAmount, + )); + } + + foreach ($taxes as $taxDetail) { + $objTax = new Tax((int)$taxDetail['id_tax']); + $rate = Validate::isLoadedObject($objTax) + ? (float)$objTax->rate + : (isset($taxDetail['rate']) ? (float)$taxDetail['rate'] : 0); + $key = sprintf('%.3f', $rate); + + if (!isset($breakdowns[$breakdownType][$key])) { + $breakdowns[$breakdownType][$key] = array( + 'total_price_tax_excl' => 0, + 'total_tax_excl' => 0, + 'total_amount' => 0, + 'id_tax' => (int)$taxDetail['id_tax'], + 'rate' => sprintf('%.3f', $rate), + 'name' => Validate::isLoadedObject($objTax) + ? $objTax->name[(int)$this->order->id_lang] : '', + ); + } + + $breakdowns[$breakdownType][$key]['total_price_tax_excl'] += (float)$orderDetail['total_price_tax_excl']; + $breakdowns[$breakdownType][$key]['total_tax_excl'] += (float)$orderDetail['total_price_tax_excl']; + $breakdowns[$breakdownType][$key]['total_amount'] += (float)$taxDetail['total_amount']; + } + } + } + + $objBookingDemand = new HotelBookingDemands(); + $extraDemandTaxes = $objBookingDemand->getExtraDemandsTaxesDetails( + (int)$this->order->id, + $this->getOrderDetailIds() + ); + + if (is_array($extraDemandTaxes)) { + foreach ($extraDemandTaxes as $taxDetail) { + $rate = isset($taxDetail['rate']) ? (float)$taxDetail['rate'] : 0; + $key = sprintf('%.3f', $rate); + + if (!isset($breakdowns['extra_demands_tax'][$key])) { + $breakdowns['extra_demands_tax'][$key] = array( + 'total_price_tax_excl' => 0, + 'total_tax_excl' => 0, + 'total_amount' => 0, + 'id_tax' => (int)$taxDetail['id_tax'], + 'rate' => sprintf('%.3f', $rate), + 'name' => isset($taxDetail['name']) ? $taxDetail['name'] : '', + ); + } + + $taxBase = isset($taxDetail['total_tax_base']) + ? (float)$taxDetail['total_tax_base'] + : (float)$taxDetail['total_price_tax_excl']; + $breakdowns['extra_demands_tax'][$key]['total_price_tax_excl'] += $taxBase; + $breakdowns['extra_demands_tax'][$key]['total_tax_excl'] += $taxBase; + $breakdowns['extra_demands_tax'][$key]['total_amount'] += (float)$taxDetail['total_amount']; + } + } + + foreach ($breakdowns as $type => &$breakdown) { + if (empty($breakdown)) { + unset($breakdowns[$type]); + continue; + } + foreach ($breakdown as &$line) { + $line['total_price_tax_excl'] = Tools::ps_round( + $line['total_price_tax_excl'], + _PS_PRICE_COMPUTE_PRECISION_, + $this->order->round_mode + ); + $line['total_tax_excl'] = $line['total_price_tax_excl']; + $line['total_amount'] = Tools::ps_round( + $line['total_amount'], + _PS_PRICE_COMPUTE_PRECISION_, + $this->order->round_mode + ); + } + unset($line); + } + unset($breakdown); + + return $breakdowns; + } + + /** + * Classifies an OrderDetail row into a tax breakdown bucket. + * Extracted: complex multi-condition classification called inside a loop. + * + * @param array $orderDetail + * @return string|false + */ + protected function getTaxBreakdownType($orderDetail) + { + if (!empty($orderDetail['is_booking_product']) + || ((int)$orderDetail['product_auto_add'] + && (int)$orderDetail['selling_preference_type'] === (int)Product::SELLING_PREFERENCE_WITH_ROOM_TYPE + && (int)$orderDetail['product_price_addition_type'] === (int)Product::PRICE_ADDITION_TYPE_WITH_ROOM) + ) { + return 'room_tax'; + } + + if (!(int)$orderDetail['is_booking_product'] + && !(int)$orderDetail['product_auto_add'] + && (int)$orderDetail['selling_preference_type'] === (int)Product::SELLING_PREFERENCE_WITH_ROOM_TYPE + ) { + return 'additional_services_tax'; + } + + if (!(int)$orderDetail['is_booking_product'] + && (int)$orderDetail['product_auto_add'] + && (int)$orderDetail['selling_preference_type'] === (int)Product::SELLING_PREFERENCE_WITH_ROOM_TYPE + && (int)$orderDetail['product_price_addition_type'] === (int)Product::PRICE_ADDITION_TYPE_INDEPENDENT + ) { + return 'convenience_fee_tax'; + } + + if (!(int)$orderDetail['is_booking_product'] + && ((int)$orderDetail['selling_preference_type'] === (int)Product::SELLING_PREFERENCE_STANDALONE + || (int)$orderDetail['selling_preference_type'] === (int)Product::SELLING_PREFERENCE_HOTEL_STANDALONE) + ) { + return 'service_products_tax'; + } + + return false; + } + + /** + * Returns order detail IDs for this order. + * Reused by getTotalsData() and getTaxBreakdownData(). + * + * @return array + */ + protected function getOrderDetailIds() + { + $orderDetails = OrderDetail::getList((int)$this->order->id); + + if (!is_array($orderDetails)) { + return array(); + } + + return array_unique(array_map('intval', array_column($orderDetails, 'id_order_detail'))); + } + + /** + * Formats a date for display; returns '--' for empty/zero dates. + * Reused across getRoomAndServiceData and getRoomServiceData loops. + * + * @param string $date + * @return string + */ + protected function formatDisplayDate($date) + { + if (!$date || $date === '0000-00-00 00:00:00') { + return '--'; + } + + return Tools::displayDate($date, null, false); + } + + /** + * Fetches and formats the hotel's cancellation/refund rules as HTML. + * + * @param int $idHotel + * @return string + */ + protected function getFormattedCancellationPolicy($idHotel) + { + if (!(int)$idHotel) { + return ''; + } + + $objBranchRefundRules = new HotelBranchRefundRules(); + $refundRules = $objBranchRefundRules->getHotelRefundRules( + (int)$idHotel, 0, 1, (int)$this->order->id_lang, 1 + ); + + if (!is_array($refundRules) || empty($refundRules)) { + return ''; + } + + $policyBlocks = array(); + foreach ($refundRules as $rule) { + $ruleName = isset($rule['name']) ? trim(strip_tags($rule['name'])) : ''; + $ruleDescription = isset($rule['description']) ? trim($rule['description']) : ''; + + if ($ruleName === '' && $ruleDescription === '') { + continue; + } + + $block = ''; + if ($ruleName !== '') { + $block .= ''.Tools::safeOutput($ruleName).''; + } + if ($ruleDescription !== '') { + if ($block !== '') { + $block .= ': '; + } + $block .= $ruleDescription; + } + $policyBlocks[] = $block; + } + + if (empty($policyBlocks)) { + return ''; + } + + return implode('

', $policyBlocks); + } + + /** + * @return string + */ + public function getBulkFilename() + { + return self::l('BookingVouchers').'.pdf'; + } + + /** + * @return string + */ + public function getFilename() + { + return sprintf(self::l('BOOKING VOUCHER - Refrence No. #%s.pdf'), $this->order->reference); + } +} diff --git a/classes/pdf/PDF.php b/classes/pdf/PDF.php index 626d3a51a..3ab61b01b 100644 --- a/classes/pdf/PDF.php +++ b/classes/pdf/PDF.php @@ -41,6 +41,7 @@ class PDFCore const TEMPLATE_ORDER_SLIP = 'OrderSlip'; const TEMPLATE_DELIVERY_SLIP = 'DeliverySlip'; const TEMPLATE_SUPPLY_ORDER_FORM = 'SupplyOrderForm'; + const TEMPLATE_BOOKING_VOUCHER = 'BookingVoucher'; /** * @param $objects diff --git a/controllers/admin/AdminOrdersController.php b/controllers/admin/AdminOrdersController.php index 30026bcca..b8095b2de 100644 --- a/controllers/admin/AdminOrdersController.php +++ b/controllers/admin/AdminOrdersController.php @@ -499,12 +499,15 @@ class AdminOrdersControllerCore extends AdminController ); } - $this->toolbar_btn['print'] = array( - 'short' => $this->l('Print'), - 'href' => 'javascript:window.print()', - 'desc' => $this->l('Print order'), - 'class' => 'icon-print', - ); + if (HotelBookingDetail::getIdHotelByIdOrder($order->id, false) && !$this->lite_display) { + $this->toolbar_btn['booking_voucher'] = array( + 'short' => $this->l('Booking Voucher'), + 'href' => $this->context->link->getAdminLink('AdminPdf').'&submitAction=generateBookingVoucherPDF&id_order='.$order->id, + 'desc' => $this->l('View booking voucher'), + 'class' => 'icon-file-text', + 'target' => true, + ); + } if ($this->tabAccess['edit'] === 1) { if (((int) $order->isReturnable()) diff --git a/controllers/admin/AdminPdfController.php b/controllers/admin/AdminPdfController.php index 7fba0f8e1..16eb82dc9 100644 --- a/controllers/admin/AdminPdfController.php +++ b/controllers/admin/AdminPdfController.php @@ -64,6 +64,15 @@ class AdminPdfControllerCore extends AdminController } } + public function processGenerateBookingVoucherPDF() + { + if (Tools::isSubmit('id_order')) { + $this->generateBookingVoucherPDFByIdOrder(Tools::getValue('id_order')); + } else { + die(Tools::displayError('The order ID is missing.')); + } + } + public function processGenerateOrderSlipPDF() { $order_slip = new OrderSlip((int)Tools::getValue('id_order_slip')); @@ -202,6 +211,23 @@ class AdminPdfControllerCore extends AdminController $this->generatePDF($order_invoice, PDF::TEMPLATE_INVOICE); } + public function generateBookingVoucherPDFByIdOrder($id_order) + { + $order = new Order((int)$id_order); + if (!Validate::isLoadedObject($order)) { + die(Tools::displayError('The order cannot be found within your database.')); + } + + $objHotelBookingDetail = new HotelBookingDetail(); + $bookingDetails = $objHotelBookingDetail->getBookingDataByOrderId((int)$order->id); + if (empty($bookingDetails)) { + die(Tools::displayError('No booking voucher is available for this order.')); + } + + $pdf = new PDF($order, PDF::TEMPLATE_BOOKING_VOUCHER, Context::getContext()->smarty); + $pdf->render('I'); + } + public function generatePDF($object, $template) { $pdf = new PDF($object, $template, Context::getContext()->smarty); diff --git a/pdf/booking-voucher.tpl b/pdf/booking-voucher.tpl new file mode 100644 index 000000000..c5b9ec805 --- /dev/null +++ b/pdf/booking-voucher.tpl @@ -0,0 +1,428 @@ +{** +* NOTICE OF LICENSE +* +* This source file is subject to the Open Software License version 3.0 +* that is bundled with this package in the file LICENSE.md +* It is also available through the world-wide-web at this URL: +* https://opensource.org/license/osl-3-0-php +* If you did not receive a copy of the license and are unable to +* obtain it through the world-wide-web, please send an email +* to support@qloapps.com so we can send you a copy immediately. +* +* DISCLAIMER +* +* Do not edit or add to this file if you wish to upgrade this module to a newer +* versions in the future. If you wish to customize this module for your needs +* please refer to https://store.webkul.com/customisation-guidelines for more information. +* +* @author Webkul IN +* @copyright Since 2010 Webkul +* @license https://opensource.org/license/osl-3-0-php Open Software License version 3.0 +*} + +{assign var=pdf_style_template value=$smarty.const._PS_PDF_DIR_|cat:'invoice.style-tab.tpl'} +{include file=$pdf_style_template} + + + + + + + + + + + + + + + + + + + + + + + + + +
{l s='BOOKING INFORMATION' pdf='true'}
{l s='Booking Reference' pdf='true'}{$order->reference|escape:'html':'UTF-8'}{l s='Booking Date' pdf='true'}{$booking_date|escape:'html':'UTF-8'}{l s='Voucher Issue Date' pdf='true'}{$issue_date|escape:'html':'UTF-8'}
{l s='Status' pdf='true'}{$order_status|escape:'html':'UTF-8'}{l s='Total Rooms' pdf='true'}{$total_rooms|intval}{l s='Total Nights' pdf='true'}{$total_room_nights|intval}
+ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{l s='PROPERTY DETAILS' pdf='true'}{l s='GUEST INFORMATION' pdf='true'}
{l s='Property' pdf='true'}{$hotel_name|escape:'html':'UTF-8'}{l s='Primary Guest' pdf='true'}{$guest_name|escape:'html':'UTF-8'}
{l s='Email' pdf='true'}{$hotel_email|escape:'html':'UTF-8'}{l s='Email' pdf='true'}{$guest_email|escape:'html':'UTF-8'}
{l s='Phone' pdf='true'}{$hotel_phone|escape:'html':'UTF-8'}{l s='Phone' pdf='true'}{$guest_phone|escape:'html':'UTF-8'}
{l s='Address' pdf='true'}{if $hotel_address}{$hotel_address nofilter}{else}--{/if}{l s='Address' pdf='true'}{if $guest_address|strip_tags|trim}{$guest_address nofilter}{else}--{/if}
{l s='Total Guests' pdf='true'}{$total_guests|intval}
+ + + + + +
+ + + + + + + + + + + + + + + + + + {if $room_type_rows} + {foreach from=$room_type_rows item=room_type_row} + {cycle values="color_line_even,color_line_odd" assign=bgcolor_class} + + + + + + + + + + {/foreach} + {else} + + + + {/if} + +
{l s='ROOM DETAILS' pdf='true'}
{l s='Room Description' pdf='true'}{l s='Tax Rate(s)' pdf='true'}{l s='Unit Price' pdf='true'}
{l s='(Tax excl.)' pdf='true'}
{l s='Rooms' pdf='true'}{l s='Check-in Date' pdf='true'}{l s='Check-out Date' pdf='true'}{l s='Total' pdf='true'}
{l s='(Tax excl.)' pdf='true'}
{$room_type_row.room_type_name|escape:'html':'UTF-8'}{$room_type_row.order_detail_tax_label}{displayPrice currency=$order->id_currency price=$room_type_row.unit_price_tax_excl} + {if $room_type_row.adults <= 9}0{/if}{$room_type_row.adults|intval} {if $room_type_row.adults > 1}{l s='Adults' pdf='true'}{else}{l s='Adult' pdf='true'}{/if}{if $room_type_row.children}, {if $room_type_row.children <= 9}0{/if}{$room_type_row.children|intval} {if $room_type_row.children > 1}{l s='Children' pdf='true'}{else}{l s='Child' pdf='true'}{/if}{/if}
{if $room_type_row.rooms <= 9}0{/if}{$room_type_row.rooms|intval} {if $room_type_row.rooms > 1}{l s='Rooms' pdf='true'}{else}{l s='Room' pdf='true'}{/if} +
{$room_type_row.check_in_date|escape:'html':'UTF-8'}{$room_type_row.check_out_date|escape:'html':'UTF-8'}{displayPrice currency=$order->id_currency price=$room_type_row.total_price_tax_excl}
{l s='No room details available.' pdf='true'}
+ +{if $service_rows} + + + + +
+ + + + + + + + + + + + + + + {foreach from=$service_rows item=service} + {cycle values="color_line_even,color_line_odd" assign=bgcolor_class} + + + + + + + {/foreach} + +
{l s='SERVICES DETAILS' pdf='true'}
{l s='Room Ref' pdf='true'}{l s='Check-in / Check-out' pdf='true'}{l s='Service Name' pdf='true'}{l s='Qty' pdf='true'}
{$service.room_ref|escape:'html':'UTF-8'}{$service.check_in_date|escape:'html':'UTF-8'} - {$service.check_out_date|escape:'html':'UTF-8'}{$service.name|escape:'html':'UTF-8'}{$service.quantity|intval}
+{/if} + + + + + +
+ +{assign var=payment_method_rendered value=false} +{assign var=payment_status_rendered value=false} + + + + + + + + + + {if isset($footer.room_price_tax_excl) && $footer.room_price_tax_excl} + + {if !$payment_method_rendered} + + + {assign var=payment_method_rendered value=true} + {elseif !$payment_status_rendered} + + + {assign var=payment_status_rendered value=true} + {else} + + + {/if} + + + + + {/if} + {if isset($footer.service_products_price_tax_excl) && $footer.service_products_price_tax_excl} + + {if !$payment_method_rendered} + + + {assign var=payment_method_rendered value=true} + {elseif !$payment_status_rendered} + + + {assign var=payment_status_rendered value=true} + {else} + + + {/if} + + + + + {/if} + {if isset($footer.additional_service_price_tax_excl) && $footer.additional_service_price_tax_excl} + + {if !$payment_method_rendered} + + + {assign var=payment_method_rendered value=true} + {elseif !$payment_status_rendered} + + + {assign var=payment_status_rendered value=true} + {else} + + + {/if} + + + + + {/if} + {if isset($footer.total_convenience_fee_te) && $footer.total_convenience_fee_te} + + {if !$payment_method_rendered} + + + {assign var=payment_method_rendered value=true} + {elseif !$payment_status_rendered} + + + {assign var=payment_status_rendered value=true} + {else} + + + {/if} + + + + + {/if} + + {if !$payment_method_rendered} + + + {assign var=payment_method_rendered value=true} + {elseif !$payment_status_rendered} + + + {assign var=payment_status_rendered value=true} + {else} + + + {/if} + + + + + {if $footer.total_tax_without_discount > 0} + + {if !$payment_method_rendered} + + + {assign var=payment_method_rendered value=true} + {elseif !$payment_status_rendered} + + + {assign var=payment_status_rendered value=true} + {else} + + + {/if} + + + + + {/if} + {if isset($footer.product_discounts_tax_incl) && $footer.product_discounts_tax_incl > 0} + + {if !$payment_method_rendered} + + + {assign var=payment_method_rendered value=true} + {elseif !$payment_status_rendered} + + + {assign var=payment_status_rendered value=true} + {else} + + + {/if} + + + + + {/if} + + {if !$payment_method_rendered} + + + {assign var=payment_method_rendered value=true} + {elseif !$payment_status_rendered} + + + {assign var=payment_status_rendered value=true} + {else} + + + {/if} + + + + + {if $footer.amount_due > 0} + + {if !$payment_method_rendered} + + + {assign var=payment_method_rendered value=true} + {elseif !$payment_status_rendered} + + + {assign var=payment_status_rendered value=true} + {else} + + + {/if} + + + + + {/if} + +
{l s='PAYMENT INFORMATION' pdf='true'}{l s='TOTAL AMOUNT' pdf='true'}
{l s='Payment Method' pdf='true'}{$payment_method nofilter}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Total Rooms Cost (tax excl.)' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.room_price_tax_excl}
{l s='Payment Method' pdf='true'}{$payment_method|escape:'html':'UTF-8'}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Total Products cost (tax excl.)' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.service_products_price_tax_excl}
{l s='Payment Method' pdf='true'}{$payment_method|escape:'html':'UTF-8'}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Extra Services Cost (tax excl.)' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.additional_service_price_tax_excl}
{l s='Payment Method' pdf='true'}{$payment_method|escape:'html':'UTF-8'}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Convenience Fee (tax excl.)' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.total_convenience_fee_te}
{l s='Payment Method' pdf='true'}{$payment_method|escape:'html':'UTF-8'}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Total (Tax excl.)' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.total_without_discount_te}
{l s='Payment Method' pdf='true'}{$payment_method|escape:'html':'UTF-8'}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Total Tax' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.total_tax_without_discount}
{l s='Payment Method' pdf='true'}{$payment_method|escape:'html':'UTF-8'}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Total Discounts' pdf='true'}- {displayPrice currency=$order->id_currency price=$footer.product_discounts_tax_incl}
{l s='Payment Method' pdf='true'}{$payment_method|escape:'html':'UTF-8'}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Final Booking Amount' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.total_paid_tax_incl}
{l s='Payment Method' pdf='true'}{$payment_method|escape:'html':'UTF-8'}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Due Amount' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.amount_due}
+ +{if $has_cancellation_policy} + + + + +
+ + + + + + + + + + + + +
{l s='CANCELLATION POLICY' pdf='true'}
{$cancellation_policy nofilter}
+{/if} + + + + + +
+ + + + + + + + + + + + + + + + + + + + + +
{l s='PROPERTY POLICIES' pdf='true'}
{l s='Check-in time' pdf='true'}{$check_in_time|escape:'html':'UTF-8'}
{l s='Check-out time' pdf='true'}{$check_out_time|escape:'html':'UTF-8'}
{l s='Policies' pdf='true'}{$payment_policy nofilter}
+ +{if isset($HOOK_DISPLAY_PDF) && $HOOK_DISPLAY_PDF} + + + + + + + +
{$HOOK_DISPLAY_PDF}
+{/if} diff --git a/themes/hotel-reservation-theme/css/global.css b/themes/hotel-reservation-theme/css/global.css index 1c8ecf2e3..7fb667b55 100644 --- a/themes/hotel-reservation-theme/css/global.css +++ b/themes/hotel-reservation-theme/css/global.css @@ -5776,10 +5776,13 @@ textarea { background: #fff; } #columns { + padding: 0; +} +#columns:has(#center_column > *) { position: relative; padding-bottom: 50px; - padding-top: 15px; } - + padding-top: 15px; +} #index .tab-content { margin-top: 35px; margin-bottom: 34px; } From fa22d23f75b667dd4e0728dc15044b3c28f61dde Mon Sep 17 00:00:00 2001 From: varun kewlani Date: Fri, 1 May 2026 13:09:48 +0530 Subject: [PATCH 10/35] removed print order button --- controllers/admin/AdminOrdersController.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/controllers/admin/AdminOrdersController.php b/controllers/admin/AdminOrdersController.php index 29bb57991..b28a6baa9 100644 --- a/controllers/admin/AdminOrdersController.php +++ b/controllers/admin/AdminOrdersController.php @@ -391,13 +391,6 @@ class AdminOrdersControllerCore extends AdminController ); } - $this->page_header_toolbar_btn['print'] = array( - 'short' => $this->l('Print'), - 'href' => 'javascript:window.print()', - 'desc' => $this->l('Print order'), - 'class' => 'icon-print', - ); - if ($this->tabAccess['edit'] === 1) { if (((int) $order->isReturnable()) && !$order->hasCompletelyRefunded(Order::ORDER_COMPLETE_CANCELLATION_OR_REFUND_REQUEST_FLAG, 0, 0) From 971df08c5f0ddf439789debf13cf866e2563df8d Mon Sep 17 00:00:00 2001 From: varun kewlani Date: Fri, 1 May 2026 13:21:27 +0530 Subject: [PATCH 11/35] fixed: removed pdf button from inittoolbar function, and added in initPageHeaderToolbar Co-authored-by: Name --- controllers/admin/AdminOrdersController.php | 62 ++++----------------- 1 file changed, 10 insertions(+), 52 deletions(-) diff --git a/controllers/admin/AdminOrdersController.php b/controllers/admin/AdminOrdersController.php index b28a6baa9..e9be3b80a 100644 --- a/controllers/admin/AdminOrdersController.php +++ b/controllers/admin/AdminOrdersController.php @@ -391,6 +391,16 @@ class AdminOrdersControllerCore extends AdminController ); } + if (HotelBookingDetail::getIdHotelByIdOrder($order->id, false) && !$this->lite_display) { + $this->page_header_toolbar_btn['booking_voucher'] = array( + 'short' => $this->l('Booking Voucher'), + 'href' => $this->context->link->getAdminLink('AdminPdf').'&submitAction=generateBookingVoucherPDF&id_order='.$order->id, + 'desc' => $this->l('View booking voucher'), + 'class' => 'icon-file-text', + 'target' => true, + ); + } + if ($this->tabAccess['edit'] === 1) { if (((int) $order->isReturnable()) && !$order->hasCompletelyRefunded(Order::ORDER_COMPLETE_CANCELLATION_OR_REFUND_REQUEST_FLAG, 0, 0) @@ -518,58 +528,6 @@ class AdminOrdersControllerCore extends AdminController public function initToolbar() { - if ($this->display == 'view') { - /** @var Order $order */ - if (Validate::isLoadedObject($order = $this->loadObject())) { - // hotel link in header - if ($idHotel = HotelBookingDetail::getIdHotelByIdOrder($order->id)) { - $this->toolbar_btn['hotel'] = array( - 'href' => $this->context->link->getAdminLink('AdminAddHotel').'&id='.$idHotel.'&updatehtl_branch_info', - 'desc' => $this->l('View Hotel'), - 'class' => 'icon-building', - 'target' => true, - ); - } - - if (Configuration::get('PS_INVOICE') && $order->hasInvoice() && !$this->lite_display) { - $this->toolbar_btn['file'] = array( - 'short' => $this->l('Invoice'), - 'href' => $this->context->link->getAdminLink('AdminPdf').'&submitAction=generateInvoicePDF&id_order='.$order->id, - 'desc' => $this->l('View invoice'), - 'class' => 'icon-file-text', - 'target' => true, - ); - } - - if (HotelBookingDetail::getIdHotelByIdOrder($order->id, false) && !$this->lite_display) { - $this->toolbar_btn['booking_voucher'] = array( - 'short' => $this->l('Booking Voucher'), - 'href' => $this->context->link->getAdminLink('AdminPdf').'&submitAction=generateBookingVoucherPDF&id_order='.$order->id, - 'desc' => $this->l('View booking voucher'), - 'class' => 'icon-file-text', - 'target' => true, - ); - } - - if ($this->tabAccess['edit'] === 1) { - if (((int) $order->isReturnable()) - && !$order->hasCompletelyRefunded(Order::ORDER_COMPLETE_CANCELLATION_OR_REFUND_REQUEST_FLAG, 0, 0) - ) { - $orderTotalPaid = $order->getTotalPaid(); - $orderDiscounts = $order->getCartRules(); - $hasOrderDiscountOrPayment = ((float)$orderTotalPaid > 0 || $orderDiscounts) ? true : false; - $this->toolbar_btn['cancel'] = array( - 'short' => ($hasOrderDiscountOrPayment) ? $this->l('Refund') : $this->l('Cancel'), - 'id' => 'desc-order-standard_refund', - 'desc' => ($hasOrderDiscountOrPayment) ? $this->l('Initiate refund') : $this->l('Cancel bookings'), - 'class' => 'icon-exchange', - 'target' => true, - ); - } - } - } - } - $res = parent::initToolbar(); if (Context::getContext()->shop->getContext() != Shop::CONTEXT_SHOP && isset($this->toolbar_btn['new']) && Shop::isFeatureActive()) { unset($this->toolbar_btn['new']); From c5e73506de11a584de16697c173dc7109af71061 Mon Sep 17 00:00:00 2001 From: Amit sharma Date: Fri, 1 May 2026 17:25:32 +0530 Subject: [PATCH 12/35] update file names --- classes/pdf/HTMLTemplatePaymentReceipt.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/classes/pdf/HTMLTemplatePaymentReceipt.php b/classes/pdf/HTMLTemplatePaymentReceipt.php index cb6762205..27374e5e3 100644 --- a/classes/pdf/HTMLTemplatePaymentReceipt.php +++ b/classes/pdf/HTMLTemplatePaymentReceipt.php @@ -130,9 +130,9 @@ class HTMLTemplatePaymentReceiptCore extends HTMLTemplate )); $smarty_tpls = array( - 'style_tab' => $this->smarty->fetch($this->getTemplate('payment.receipt.receipt.style-tab')), - 'addresses_tab' => $this->smarty->fetch($this->getTemplate('payment.receipt.receipt.addresses-tab')), - 'payment_info_tab' => $this->smarty->fetch($this->getTemplate('payment.receipt.receipt.payment-info-tab')), + 'style_tab' => $this->smarty->fetch($this->getTemplate('payment.receipt.style-tab')), + 'addresses_tab' => $this->smarty->fetch($this->getTemplate('payment.receipt.addresses-tab')), + 'payment_info_tab' => $this->smarty->fetch($this->getTemplate('payment.receipt.payment-info-tab')), ); $this->smarty->assign($smarty_tpls); return $this->smarty->fetch($this->getTemplate('payment-receipt')); From 0f76539a171ee1965b62f29a03b6e5d5b66d5830 Mon Sep 17 00:00:00 2001 From: varun kewlani Date: Sat, 2 May 2026 13:56:21 +0530 Subject: [PATCH 13/35] combined code in getcontent funciton, and fixed pdf page change issue --- classes/pdf/HTMLTemplateBookingVoucher.php | 682 ++++++--------------- pdf/booking-voucher.tpl | 4 +- 2 files changed, 179 insertions(+), 507 deletions(-) diff --git a/classes/pdf/HTMLTemplateBookingVoucher.php b/classes/pdf/HTMLTemplateBookingVoucher.php index e1e4a8d7e..af465930f 100644 --- a/classes/pdf/HTMLTemplateBookingVoucher.php +++ b/classes/pdf/HTMLTemplateBookingVoucher.php @@ -22,6 +22,10 @@ * @license https://opensource.org/license/osl-3.0-php Open Software License version 3.0 */ +/** + * Builds and renders the Booking Voucher PDF for a given Order. + * Data assembly follows the same inline pattern as HTMLTemplateInvoice::getContent(). + */ class HTMLTemplateBookingVoucherCore extends HTMLTemplate { /** @var Order */ @@ -95,6 +99,7 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate $hotelEmail = $objHotel->email ? $objHotel->email : $hotelEmail; $checkInTime = $objHotel->check_in ?: '--'; $checkOutTime = $objHotel->check_out ?: '--'; + $policies = is_array($objHotel->policies) ? '' : (string)$objHotel->policies; if ($policies) { $policyText = html_entity_decode($policies, ENT_QUOTES, 'UTF-8'); @@ -103,7 +108,31 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate $policyText = preg_replace("/[\r\n]+/", "\n", $policyText); $paymentPolicy = nl2br(trim($policyText)); } - $cancellationPolicy = $this->getFormattedCancellationPolicy($idHotel); + + if ($idHotel) { + $objBranchRefundRules = new HotelBranchRefundRules(); + $refundRules = $objBranchRefundRules->getHotelRefundRules( + $idHotel, 0, 1, (int)$this->order->id_lang, 1 + ); + if (is_array($refundRules) && !empty($refundRules)) { + $policyBlocks = array(); + foreach ($refundRules as $rule) { + $ruleName = isset($rule['name']) ? trim(strip_tags($rule['name'])) : ''; + $ruleDescription = isset($rule['description']) ? trim($rule['description']) : ''; + if ($ruleName === '' && $ruleDescription === '') { + continue; + } + $block = $ruleName !== '' ? ''.Tools::safeOutput($ruleName).'' : ''; + if ($ruleDescription !== '') { + $block .= ($block !== '' ? ': ' : '').$ruleDescription; + } + $policyBlocks[] = $block; + } + if (!empty($policyBlocks)) { + $cancellationPolicy = implode('

', $policyBlocks); + } + } + } if ($idHotelAddress = $objHotel->getHotelIdAddress()) { $objHotelAddress = new Address((int)$idHotelAddress); @@ -128,8 +157,136 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate } } - // --- Room and service data --- - $roomAndServiceData = $this->getRoomAndServiceData($bookings); + // --- Rooms, services, occupancy --- + $totalRooms = 0; + $serviceRows = array(); + $roomTypeRows = array(); + $totalRoomNights = 0; + $totalGuests = 0; + + foreach ($bookings as $booking) { + $nights = (int)HotelHelper::getNumberOfDays($booking['date_from'], $booking['date_to']); + if ($nights <= 0) { + $nights = 1; + } + + $roomReference = !empty($booking['room_num']) ? $booking['room_num'] : 'BK-'.(int)$booking['id']; + $adults = (int)$booking['adults']; + $children = (int)$booking['children']; + $totalGuests += $adults + $children; + $totalRoomNights += $nights; + + $dateFrom = ($booking['date_from'] && $booking['date_from'] !== '0000-00-00 00:00:00') + ? Tools::displayDate($booking['date_from'], null, false) : '--'; + $dateTo = ($booking['date_to'] && $booking['date_to'] !== '0000-00-00 00:00:00') + ? Tools::displayDate($booking['date_to'], null, false) : '--'; + + // Extra demands for this room + $autoAddedWithRoomPriceTaxExcl = 0; + $roomServiceRows = array(); + + $objBookingDemand = new HotelBookingDemands(); + $extraDemands = $objBookingDemand->getRoomTypeBookingExtraDemands( + (int)$this->order->id, 0, 0, 0, 0, 0, 0, 0, + (int)$booking['id'], (int)$booking['id_order_detail'] + ); + if (is_array($extraDemands)) { + foreach ($extraDemands as $demand) { + $roomServiceRows[] = array( + 'room_ref' => $roomReference, + 'check_in_date' => $dateFrom, + 'check_out_date' => $dateTo, + 'name' => $demand['name'], + 'quantity' => 1, + ); + } + } + + // Auto-added services bundled with room price + $objServiceProductOrderDetail = new ServiceProductOrderDetail(); + $autoAddedWithRoomPriceTaxExcl = (float)$objServiceProductOrderDetail->getRoomTypeServiceProducts( + (int)$this->order->id, 0, 0, + isset($booking['id_product']) ? (int)$booking['id_product'] : 0, + isset($booking['date_from']) ? $booking['date_from'] : 0, + isset($booking['date_to']) ? $booking['date_to'] : 0, + isset($booking['id_room']) ? (int)$booking['id_room'] : 0, + 1, 0, 1, Product::PRICE_ADDITION_TYPE_WITH_ROOM + ); + + // Additional services chosen by guest + $additionalServices = $objServiceProductOrderDetail->getRoomTypeServiceProducts( + 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, null, 0, (int)$booking['id'] + ); + if (isset($additionalServices[(int)$booking['id']]['additional_services'])) { + foreach ($additionalServices[(int)$booking['id']]['additional_services'] as $service) { + $roomServiceRows[] = array( + 'room_ref' => $roomReference, + 'check_in_date' => $dateFrom, + 'check_out_date' => $dateTo, + 'name' => $service['name'], + 'quantity' => (int)$service['quantity'], + ); + } + } + + $serviceRows = array_merge($serviceRows, $roomServiceRows); + + $roomTotalTaxExcl = (float)$booking['total_price_tax_excl']; + $roomTotalWithAutoServicesTaxExcl = $roomTotalTaxExcl + $autoAddedWithRoomPriceTaxExcl; + + // Aggregate per room type + date range + $roomTypeRowKey = implode('|', array( + (int)$booking['id_order_detail'], + isset($booking['room_type_name']) ? $booking['room_type_name'] : '', + isset($booking['date_from']) ? $booking['date_from'] : '', + isset($booking['date_to']) ? $booking['date_to'] : '', + )); + + if (!isset($roomTypeRows[$roomTypeRowKey])) { + $idOrderDetail = (int)$booking['id_order_detail']; + $detailTaxes = $idOrderDetail ? OrderDetail::getTaxListStatic($idOrderDetail) : null; + $orderDetailTaxLabel = self::l('No tax'); + if ($detailTaxes) { + $taxTemp = array(); + foreach ($detailTaxes as $tax) { + $objTax = new Tax($tax['id_tax']); + $taxTemp[] = sprintf(self::l('%1$s%2$s%%'), $objTax->rate + 0, ' '); + } + $orderDetailTaxLabel = implode(', ', $taxTemp); + } + + $roomTypeRows[$roomTypeRowKey] = array( + 'room_type_name' => isset($booking['room_type_name']) ? $booking['room_type_name'] : '', + 'order_detail_tax_label' => $orderDetailTaxLabel, + 'rooms' => 0, + 'adults' => 0, + 'children' => 0, + 'nights' => $nights, + 'check_in_date' => $dateFrom, + 'check_out_date' => $dateTo, + 'unit_price_tax_excl' => 0, + 'total_price_tax_excl' => 0, + ); + } + + $roomTypeRows[$roomTypeRowKey]['rooms'] += 1; + $roomTypeRows[$roomTypeRowKey]['adults'] += $adults; + $roomTypeRows[$roomTypeRowKey]['children'] += $children; + $roomTypeRows[$roomTypeRowKey]['total_price_tax_excl'] += $roomTotalWithAutoServicesTaxExcl; + $totalRooms++; + } + + foreach ($roomTypeRows as &$roomTypeRow) { + $denominator = (int)$roomTypeRow['nights'] * (int)$roomTypeRow['rooms']; + if ($denominator > 0) { + $roomTypeRow['unit_price_tax_excl'] = Tools::ps_round( + $roomTypeRow['total_price_tax_excl'] / $denominator, + 6, + $this->order->round_mode + ); + } + } + unset($roomTypeRow); // --- Guest details --- $guestName = ''; @@ -161,7 +318,8 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate } } - $footer = $this->getTotalsData($roomAndServiceData); + // --- Totals --- + $footer = $this->getTotalsData($totalRooms, $totalRoomNights, $totalGuests); // --- Payment --- $paymentMethods = array(); @@ -198,18 +356,17 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate 'guest_email' => $guestEmail, 'guest_phone' => $guestPhone, 'guest_address' => $guestAddress, - 'total_guests' => (int)$roomAndServiceData['total_guests'], + 'total_guests' => (int)$totalGuests, // booking summary - 'total_rooms' => (int)$roomAndServiceData['total_rooms'], - 'total_room_nights' => (int)$roomAndServiceData['total_room_nights'], + 'total_rooms' => (int)$totalRooms, + 'total_room_nights' => (int)$totalRoomNights, // room and service tables - 'room_type_rows' => $roomAndServiceData['room_type_rows'], - 'service_rows' => $roomAndServiceData['service_rows'], + 'room_type_rows' => array_values($roomTypeRows), + 'service_rows' => $serviceRows, // payment 'payment_method' => $paymentMethod, 'payment_status' => $orderState['name'], - 'is_advance_payment' => (int)$this->order->is_advance_payment, - // totals + // totals — key named $footer to match invoice convention 'footer' => $footer, // policies 'check_in_time' => ($checkInTime && $checkInTime !== '--') @@ -221,8 +378,6 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate : self::l('Please contact the property for policy details.'), 'cancellation_policy' => $cancellationPolicy, 'has_cancellation_policy' => (bool)trim(strip_tags($cancellationPolicy)), - // tax - 'tax_breakdown' => $this->getTaxBreakdownData(), ); if (Tools::getValue('debug')) { @@ -234,231 +389,16 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate } /** - * Builds per-room and per-room-type rows, services, price breakdown, and occupancy totals. - * - * @param array $bookings - * @return array - */ - protected function getRoomAndServiceData($bookings) - { - $rooms = array(); - $serviceRows = array(); - $servicesByRoom = array(); - $priceBreakdown = array(); - $roomTypeRows = array(); - $totalRoomNights = 0; - $totalGuests = 0; - - foreach ($bookings as $booking) { - $nights = (int)HotelHelper::getNumberOfDays($booking['date_from'], $booking['date_to']); - if ($nights <= 0) { - $nights = 1; - } - - $roomReference = !empty($booking['room_num']) ? $booking['room_num'] : 'BK-'.(int)$booking['id']; - $adults = (int)$booking['adults']; - $children = (int)$booking['children']; - $totalGuests += $adults + $children; - $totalRoomNights += $nights; - - $serviceData = $this->getRoomServiceData($booking, $roomReference); - $servicesByRoom[$roomReference] = $serviceData['services']; - $serviceRows = array_merge($serviceRows, $serviceData['rows']); - - $roomTotalTaxExcl = (float)$booking['total_price_tax_excl']; - $roomTotalWithAutoServicesTaxExcl = $roomTotalTaxExcl + $serviceData['auto_added_room_total_tax_excl']; - $unitPriceTaxExcl = $nights ? Tools::ps_round( - $roomTotalWithAutoServicesTaxExcl / $nights, - _PS_PRICE_COMPUTE_PRECISION_, - $this->order->round_mode - ) : 0; - - // Group by room type + date range to aggregate multi-room rows - $roomTypeRowKey = implode('|', array( - (int)$booking['id_order_detail'], - isset($booking['room_type_name']) ? $booking['room_type_name'] : '', - isset($booking['date_from']) ? $booking['date_from'] : '', - isset($booking['date_to']) ? $booking['date_to'] : '', - )); - - if (!isset($roomTypeRows[$roomTypeRowKey])) { - $idOrderDetail = (int)$booking['id_order_detail']; - $detailTaxes = $idOrderDetail ? OrderDetail::getTaxListStatic($idOrderDetail) : null; - $orderDetailTaxLabel = self::l('No tax'); - if ($detailTaxes) { - $taxTemp = array(); - foreach ($detailTaxes as $tax) { - $objTax = new Tax($tax['id_tax']); - $taxTemp[] = sprintf(self::l('%1$s%2$s%%'), $objTax->rate + 0, ' '); - } - $orderDetailTaxLabel = implode(', ', $taxTemp); - } - - $roomTypeRows[$roomTypeRowKey] = array( - 'room_type_name' => isset($booking['room_type_name']) ? $booking['room_type_name'] : '', - 'order_detail_tax_label' => $orderDetailTaxLabel, - 'rooms' => 0, - 'adults' => 0, - 'children' => 0, - 'nights' => $nights, - 'check_in_date' => $this->formatDisplayDate($booking['date_from']), - 'check_out_date' => $this->formatDisplayDate($booking['date_to']), - 'unit_price_tax_excl' => 0, - 'total_price_tax_excl' => 0, - ); - } - - $roomTypeRows[$roomTypeRowKey]['rooms'] += 1; - $roomTypeRows[$roomTypeRowKey]['adults'] += $adults; - $roomTypeRows[$roomTypeRowKey]['children'] += $children; - $roomTypeRows[$roomTypeRowKey]['total_price_tax_excl'] += $roomTotalWithAutoServicesTaxExcl; - - $rooms[] = array( - 'room_ref' => $roomReference, - 'room_num' => isset($booking['room_num']) ? $booking['room_num'] : '', - 'room_type_name' => isset($booking['room_type_name']) ? $booking['room_type_name'] : '', - 'check_in_date' => $this->formatDisplayDate($booking['date_from']), - 'check_out_date' => $this->formatDisplayDate($booking['date_to']), - 'nights' => $nights, - 'adults' => $adults, - 'children' => $children, - 'unit_price_tax_excl' => $unitPriceTaxExcl, - 'total_price_tax_excl' => $roomTotalWithAutoServicesTaxExcl, - ); - - $priceBreakdown[] = array( - 'room_ref' => $roomReference, - 'room_type_name' => isset($booking['room_type_name']) ? $booking['room_type_name'] : '', - 'nights' => $nights, - 'unit_price_tax_excl' => $unitPriceTaxExcl, - 'room_total_tax_excl' => $roomTotalWithAutoServicesTaxExcl, - 'services_total_tax_excl' => $serviceData['total_price_tax_excl'], - 'subtotal_tax_excl' => $roomTotalWithAutoServicesTaxExcl + $serviceData['total_price_tax_excl'], - ); - } - - foreach ($roomTypeRows as &$roomTypeRow) { - $denominator = (int)$roomTypeRow['nights'] * (int)$roomTypeRow['rooms']; - if ($denominator > 0) { - $roomTypeRow['unit_price_tax_excl'] = Tools::ps_round( - $roomTypeRow['total_price_tax_excl'] / $denominator, - 6, - $this->order->round_mode - ); - } - } - unset($roomTypeRow); - - return array( - 'rooms' => $rooms, - 'room_type_rows' => array_values($roomTypeRows), - 'service_rows' => $serviceRows, - 'services_by_room' => $servicesByRoom, - 'price_breakdown' => $priceBreakdown, - 'total_rooms' => count($rooms), - 'total_room_nights' => $totalRoomNights, - 'total_guests' => $totalGuests, - ); - } - - protected function getRoomServiceData($booking, $roomReference) - { - $rows = array(); - $services = array(); - $totalPriceTaxExcl = 0; - - $objBookingDemand = new HotelBookingDemands(); - $extraDemands = $objBookingDemand->getRoomTypeBookingExtraDemands( - (int)$this->order->id, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - (int)$booking['id'], - (int)$booking['id_order_detail'] - ); - - if (is_array($extraDemands)) { - foreach ($extraDemands as $demand) { - $services[] = array( - 'room_ref' => $roomReference, - 'check_in_out' => $this->formatDisplayDate($booking['date_from']).' - '.$this->formatDisplayDate($booking['date_to']), - 'name' => $demand['name'], - 'quantity' => 1, - 'total_price_tax_excl' => (float)$demand['total_price_tax_excl'], - ); - $rows[] = array( - 'room_ref' => $roomReference, - 'check_in_date' => $this->formatDisplayDate($booking['date_from']), - 'check_out_date' => $this->formatDisplayDate($booking['date_to']), - 'name' => $demand['name'], - 'quantity' => 1, - ); - $totalPriceTaxExcl += (float)$demand['total_price_tax_excl']; - } - } - - $objServiceProductOrderDetail = new ServiceProductOrderDetail(); - $autoAddedWithRoomPriceTaxExcl = (float)$objServiceProductOrderDetail->getRoomTypeServiceProducts( - (int)$this->order->id, - 0, - 0, - isset($booking['id_product']) ? (int)$booking['id_product'] : 0, - isset($booking['date_from']) ? $booking['date_from'] : 0, - isset($booking['date_to']) ? $booking['date_to'] : 0, - isset($booking['id_room']) ? (int)$booking['id_room'] : 0, - 1, - 0, - 1, - Product::PRICE_ADDITION_TYPE_WITH_ROOM - ); - - $additionalServices = $objServiceProductOrderDetail->getRoomTypeServiceProducts( - 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, null, 0, (int)$booking['id'] - ); - - if (isset($additionalServices[(int)$booking['id']]['additional_services'])) { - foreach ($additionalServices[(int)$booking['id']]['additional_services'] as $service) { - $services[] = array( - 'room_ref' => $roomReference, - 'check_in_out' => $this->formatDisplayDate($booking['date_from']).' - '.$this->formatDisplayDate($booking['date_to']), - 'name' => $service['name'], - 'quantity' => (int)$service['quantity'], - 'total_price_tax_excl' => (float)$service['total_price_tax_excl'], - 'product_tax_label' => isset($service['product_tax_label']) ? $service['product_tax_label'] : self::l('No tax'), - ); - $rows[] = array( - 'room_ref' => $roomReference, - 'check_in_date' => $this->formatDisplayDate($booking['date_from']), - 'check_out_date' => $this->formatDisplayDate($booking['date_to']), - 'name' => $service['name'], - 'quantity' => (int)$service['quantity'], - 'total_price_tax_excl' => (float)$service['total_price_tax_excl'], - 'product_tax_label' => isset($service['product_tax_label']) ? $service['product_tax_label'] : self::l('No tax'), - ); - $totalPriceTaxExcl += (float)$service['total_price_tax_excl']; - } - } - - return array( - 'services' => $services, - 'rows' => $rows, - 'total_price_tax_excl' => $totalPriceTaxExcl, - 'auto_added_room_total_tax_excl' => $autoAddedWithRoomPriceTaxExcl, - ); - } - - /** - * Computes all cost totals using Order's existing methods. + * Computes all cost totals using Order's own methods. * Mirrors the $footer calculation in HTMLTemplateInvoice::getContent(). + * Extracted because it is 100+ lines of calculation that would obscure the data assembly above. * - * @param array $roomAndServiceData + * @param int $totalRooms + * @param int $totalRoomNights + * @param int $totalGuests * @return array */ - protected function getTotalsData($roomAndServiceData) + protected function getTotalsData($totalRooms, $totalRoomNights, $totalGuests) { $objBookingDemand = new HotelBookingDemands(); $totalDemandsPriceTE = (float)$objBookingDemand->getRoomTypeBookingExtraDemands( @@ -468,7 +408,10 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate (int)$this->order->id, 0, 0, 0, 0, 1, 1, 1 ); - $idsOrderDetail = $this->getOrderDetailIds(); + $orderDetails = OrderDetail::getList((int)$this->order->id); + $idsOrderDetail = is_array($orderDetails) + ? array_unique(array_map('intval', array_column($orderDetails, 'id_order_detail'))) + : array(); $roomsCost = 0; $roomsCostTaxIncl = 0; @@ -478,19 +421,14 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate $additionalServicesCostTaxIncl = 0; $convenienceFee = 0; $convenienceFeeTaxIncl = 0; - $productDiscountsTaxExcl = (float)$this->order->total_discounts_tax_excl; $productDiscountsTaxIncl = (float)$this->order->total_discounts_tax_incl; foreach ($this->order->getCartRules() as $cartRule) { if (!empty($cartRule['free_shipping'])) { - $productDiscountsTaxExcl -= (float)$this->order->total_shipping_tax_excl; $productDiscountsTaxIncl -= (float)$this->order->total_shipping_tax_incl; } } - if ($productDiscountsTaxExcl < 0) { - $productDiscountsTaxExcl = 0; - } if ($productDiscountsTaxIncl < 0) { $productDiscountsTaxIncl = 0; } @@ -552,282 +490,18 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate } return array( - 'total_rooms' => (int)$roomAndServiceData['total_rooms'], - 'total_room_nights' => (int)$roomAndServiceData['total_room_nights'], - 'total_guests' => (int)$roomAndServiceData['total_guests'], - 'rooms_cost' => $roomsCost, 'room_price_tax_excl' => $roomsCost, - 'room_price_tax_incl' => $roomsCostTaxIncl, 'service_products_price_tax_excl' => $serviceProductsCost, - 'service_products_price_tax_incl' => $serviceProductsCostTaxIncl, 'additional_service_price_tax_excl' => $additionalServicesCost, - 'additional_service_price_tax_incl' => $additionalServicesCostTaxIncl, - 'extra_services_cost' => $additionalServicesCost + $serviceProductsCost, - 'convenience_fee' => $convenienceFee, 'total_convenience_fee_te' => $convenienceFee, - 'total_convenience_fee_ti' => $convenienceFeeTaxIncl, - 'total_paid_tax_excl' => (float)$this->order->total_paid_tax_excl, - 'total_taxes' => $totalTaxes, - 'total_rooms_tax' => $totalTaxes, - 'extra_services_tax' => 0, 'total_without_discount_te' => $totalWithoutDiscountTaxExcl, - 'total_without_discount_ti' => $totalWithoutDiscountTaxIncl, 'total_tax_without_discount' => $totalTaxes, - 'product_discounts_tax_excl' => $productDiscountsTaxExcl, 'product_discounts_tax_incl' => $productDiscountsTaxIncl, - 'grand_total' => $grandTotal, - 'total_paid' => $totalPaidReal, - 'total_paid_real' => $totalPaidReal, 'total_paid_tax_incl' => $grandTotal, 'amount_due' => Tools::ps_round($amountDue, _PS_PRICE_COMPUTE_PRECISION_, $this->order->round_mode), ); } - /** - * Builds tax breakdown by product type, handling both order detail taxes and extra demand taxes separately. - * Extracted: two-pass algorithm with per-type bucketing. - * - * @return array - */ - protected function getTaxBreakdownData() - { - $breakdowns = array( - 'room_tax' => array(), - 'additional_services_tax' => array(), - 'convenience_fee_tax' => array(), - 'extra_demands_tax' => array(), - 'service_products_tax' => array(), - ); - - $orderDetails = OrderDetail::getList((int)$this->order->id); - if (is_array($orderDetails)) { - foreach ($orderDetails as $orderDetail) { - $breakdownType = $this->getTaxBreakdownType($orderDetail); - if (!$breakdownType) { - continue; - } - - $taxes = OrderDetail::getTaxListStatic((int)$orderDetail['id_order_detail']); - if (!$taxes) { - $taxAmount = (float)$orderDetail['total_price_tax_incl'] - (float)$orderDetail['total_price_tax_excl']; - if ($taxAmount <= 0) { - continue; - } - $taxes = array(array( - 'id_tax' => 0, - 'rate' => (float)$orderDetail['total_price_tax_excl'] > 0 - ? (($taxAmount / (float)$orderDetail['total_price_tax_excl']) * 100) : 0, - 'total_amount' => $taxAmount, - )); - } - - foreach ($taxes as $taxDetail) { - $objTax = new Tax((int)$taxDetail['id_tax']); - $rate = Validate::isLoadedObject($objTax) - ? (float)$objTax->rate - : (isset($taxDetail['rate']) ? (float)$taxDetail['rate'] : 0); - $key = sprintf('%.3f', $rate); - - if (!isset($breakdowns[$breakdownType][$key])) { - $breakdowns[$breakdownType][$key] = array( - 'total_price_tax_excl' => 0, - 'total_tax_excl' => 0, - 'total_amount' => 0, - 'id_tax' => (int)$taxDetail['id_tax'], - 'rate' => sprintf('%.3f', $rate), - 'name' => Validate::isLoadedObject($objTax) - ? $objTax->name[(int)$this->order->id_lang] : '', - ); - } - - $breakdowns[$breakdownType][$key]['total_price_tax_excl'] += (float)$orderDetail['total_price_tax_excl']; - $breakdowns[$breakdownType][$key]['total_tax_excl'] += (float)$orderDetail['total_price_tax_excl']; - $breakdowns[$breakdownType][$key]['total_amount'] += (float)$taxDetail['total_amount']; - } - } - } - - $objBookingDemand = new HotelBookingDemands(); - $extraDemandTaxes = $objBookingDemand->getExtraDemandsTaxesDetails( - (int)$this->order->id, - $this->getOrderDetailIds() - ); - - if (is_array($extraDemandTaxes)) { - foreach ($extraDemandTaxes as $taxDetail) { - $rate = isset($taxDetail['rate']) ? (float)$taxDetail['rate'] : 0; - $key = sprintf('%.3f', $rate); - - if (!isset($breakdowns['extra_demands_tax'][$key])) { - $breakdowns['extra_demands_tax'][$key] = array( - 'total_price_tax_excl' => 0, - 'total_tax_excl' => 0, - 'total_amount' => 0, - 'id_tax' => (int)$taxDetail['id_tax'], - 'rate' => sprintf('%.3f', $rate), - 'name' => isset($taxDetail['name']) ? $taxDetail['name'] : '', - ); - } - - $taxBase = isset($taxDetail['total_tax_base']) - ? (float)$taxDetail['total_tax_base'] - : (float)$taxDetail['total_price_tax_excl']; - $breakdowns['extra_demands_tax'][$key]['total_price_tax_excl'] += $taxBase; - $breakdowns['extra_demands_tax'][$key]['total_tax_excl'] += $taxBase; - $breakdowns['extra_demands_tax'][$key]['total_amount'] += (float)$taxDetail['total_amount']; - } - } - - foreach ($breakdowns as $type => &$breakdown) { - if (empty($breakdown)) { - unset($breakdowns[$type]); - continue; - } - foreach ($breakdown as &$line) { - $line['total_price_tax_excl'] = Tools::ps_round( - $line['total_price_tax_excl'], - _PS_PRICE_COMPUTE_PRECISION_, - $this->order->round_mode - ); - $line['total_tax_excl'] = $line['total_price_tax_excl']; - $line['total_amount'] = Tools::ps_round( - $line['total_amount'], - _PS_PRICE_COMPUTE_PRECISION_, - $this->order->round_mode - ); - } - unset($line); - } - unset($breakdown); - - return $breakdowns; - } - - /** - * Classifies an OrderDetail row into a tax breakdown bucket. - * Extracted: complex multi-condition classification called inside a loop. - * - * @param array $orderDetail - * @return string|false - */ - protected function getTaxBreakdownType($orderDetail) - { - if (!empty($orderDetail['is_booking_product']) - || ((int)$orderDetail['product_auto_add'] - && (int)$orderDetail['selling_preference_type'] === (int)Product::SELLING_PREFERENCE_WITH_ROOM_TYPE - && (int)$orderDetail['product_price_addition_type'] === (int)Product::PRICE_ADDITION_TYPE_WITH_ROOM) - ) { - return 'room_tax'; - } - - if (!(int)$orderDetail['is_booking_product'] - && !(int)$orderDetail['product_auto_add'] - && (int)$orderDetail['selling_preference_type'] === (int)Product::SELLING_PREFERENCE_WITH_ROOM_TYPE - ) { - return 'additional_services_tax'; - } - - if (!(int)$orderDetail['is_booking_product'] - && (int)$orderDetail['product_auto_add'] - && (int)$orderDetail['selling_preference_type'] === (int)Product::SELLING_PREFERENCE_WITH_ROOM_TYPE - && (int)$orderDetail['product_price_addition_type'] === (int)Product::PRICE_ADDITION_TYPE_INDEPENDENT - ) { - return 'convenience_fee_tax'; - } - - if (!(int)$orderDetail['is_booking_product'] - && ((int)$orderDetail['selling_preference_type'] === (int)Product::SELLING_PREFERENCE_STANDALONE - || (int)$orderDetail['selling_preference_type'] === (int)Product::SELLING_PREFERENCE_HOTEL_STANDALONE) - ) { - return 'service_products_tax'; - } - - return false; - } - - /** - * Returns order detail IDs for this order. - * Reused by getTotalsData() and getTaxBreakdownData(). - * - * @return array - */ - protected function getOrderDetailIds() - { - $orderDetails = OrderDetail::getList((int)$this->order->id); - - if (!is_array($orderDetails)) { - return array(); - } - - return array_unique(array_map('intval', array_column($orderDetails, 'id_order_detail'))); - } - - /** - * Formats a date for display; returns '--' for empty/zero dates. - * Reused across getRoomAndServiceData and getRoomServiceData loops. - * - * @param string $date - * @return string - */ - protected function formatDisplayDate($date) - { - if (!$date || $date === '0000-00-00 00:00:00') { - return '--'; - } - - return Tools::displayDate($date, null, false); - } - - /** - * Fetches and formats the hotel's cancellation/refund rules as HTML. - * - * @param int $idHotel - * @return string - */ - protected function getFormattedCancellationPolicy($idHotel) - { - if (!(int)$idHotel) { - return ''; - } - - $objBranchRefundRules = new HotelBranchRefundRules(); - $refundRules = $objBranchRefundRules->getHotelRefundRules( - (int)$idHotel, 0, 1, (int)$this->order->id_lang, 1 - ); - - if (!is_array($refundRules) || empty($refundRules)) { - return ''; - } - - $policyBlocks = array(); - foreach ($refundRules as $rule) { - $ruleName = isset($rule['name']) ? trim(strip_tags($rule['name'])) : ''; - $ruleDescription = isset($rule['description']) ? trim($rule['description']) : ''; - - if ($ruleName === '' && $ruleDescription === '') { - continue; - } - - $block = ''; - if ($ruleName !== '') { - $block .= ''.Tools::safeOutput($ruleName).''; - } - if ($ruleDescription !== '') { - if ($block !== '') { - $block .= ': '; - } - $block .= $ruleDescription; - } - $policyBlocks[] = $block; - } - - if (empty($policyBlocks)) { - return ''; - } - - return implode('

', $policyBlocks); - } - /** * @return string */ diff --git a/pdf/booking-voucher.tpl b/pdf/booking-voucher.tpl index c5b9ec805..68aab5051 100644 --- a/pdf/booking-voucher.tpl +++ b/pdf/booking-voucher.tpl @@ -189,14 +189,12 @@ {assign var=payment_method_rendered value=false} {assign var=payment_status_rendered value=false} - + - - {if isset($footer.room_price_tax_excl) && $footer.room_price_tax_excl} {if !$payment_method_rendered} From 3ae1dd20a9af99f0bb446aa1068cc47ce6858d10 Mon Sep 17 00:00:00 2001 From: Amit sharma Date: Mon, 4 May 2026 12:01:10 +0530 Subject: [PATCH 14/35] update desc hint in configuration --- controllers/admin/AdminPaymentReceiptsController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/admin/AdminPaymentReceiptsController.php b/controllers/admin/AdminPaymentReceiptsController.php index 3643a6d5b..2d76d03ab 100644 --- a/controllers/admin/AdminPaymentReceiptsController.php +++ b/controllers/admin/AdminPaymentReceiptsController.php @@ -37,7 +37,7 @@ class AdminPaymentReceiptsControllerCore extends AdminControllerCore 'fields' => array( 'PS_PAYMENT_RECEIPTS' => array( 'title' => $this->l('Enable Receipts'), - 'desc' => $this->l('If enabled, your customers will receive a receipt for their purchase(s).'), + 'desc' => $this->l('If enabled, receipts will be generated for orders.'), 'cast' => 'intval', 'type' => 'bool' ), From fef0be689a24511950dd371622e7296eb2183f38 Mon Sep 17 00:00:00 2001 From: azhar bhat Date: Mon, 4 May 2026 12:56:56 +0530 Subject: [PATCH 15/35] composer updated --- composer.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/composer.json b/composer.json index 38b986fee..0878ca817 100644 --- a/composer.json +++ b/composer.json @@ -48,6 +48,11 @@ "ext-soap": "*", "ext-zip": "*" }, + "require-dev": { + }, + "autoload": { + "classmap": ["classes/", "Core/", "Adapter/"] + }, "minimum-stability": "stable", "prefer-stable": true, "config": { From 114d9498d83234ccd0309ccc65c22b26a79af970 Mon Sep 17 00:00:00 2001 From: Amit sharma Date: Tue, 5 May 2026 11:19:59 +0530 Subject: [PATCH 16/35] Fixed: Fix code review changes Co-authored-by: Name --- .../controllers/orders/helpers/view/view.tpl | 2 +- classes/order/Order.php | 22 +++++++++++ classes/pdf/HTMLTemplatePaymentReceipt.php | 12 +++--- controllers/admin/AdminOrdersController.php | 28 +++----------- .../admin/AdminPaymentReceiptsController.php | 2 +- ....tpl => payment-receipt.addresses-tab.tpl} | 0 ...l => payment-receipt.payment-info-tab.tpl} | 38 +++++++++---------- ...-tab.tpl => payment-receipt.style-tab.tpl} | 0 8 files changed, 54 insertions(+), 50 deletions(-) rename pdf/{payment.receipt.addresses-tab.tpl => payment-receipt.addresses-tab.tpl} (100%) rename pdf/{payment.receipt.payment-info-tab.tpl => payment-receipt.payment-info-tab.tpl} (53%) rename pdf/{payment.receipt.style-tab.tpl => payment-receipt.style-tab.tpl} (100%) 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 e507ad5ae..15d63afd7 100644 --- a/admin/themes/default/template/controllers/orders/helpers/view/view.tpl +++ b/admin/themes/default/template/controllers/orders/helpers/view/view.tpl @@ -465,7 +465,7 @@ {if isset($receipt_management_active) && $receipt_management_active} {/if} + + + {foreach from=$payment_list key=rm_k item=payment} + {cycle values=["color_line_even", "color_line_odd"] assign=bgcolor_class} + + + + + + + + {/foreach} + +
{l s='PAYMENT INFORMATION' pdf='true'} {l s='TOTAL AMOUNT' pdf='true'}
{if isset($payment['invoice_number'])}{$payment['invoice_number']}{else}--{/if} - {l s='View'} + {l s='Receipt'} diff --git a/classes/order/Order.php b/classes/order/Order.php index a62293679..e5ed30602 100644 --- a/classes/order/Order.php +++ b/classes/order/Order.php @@ -3158,4 +3158,26 @@ class OrderCore extends ObjectModel return $totalOrder; } + + public function getPaymentsTypes() + { + $module = Module::getInstanceByName('hotelreservationsystem'); + return array( + OrderPayment::PAYMENT_TYPE_PAY_AT_HOTEL => array( + 'key' => 'PAYMENT_TYPE_PAY_AT_HOTEL', + 'value' => OrderPayment::PAYMENT_TYPE_PAY_AT_HOTEL, + 'name' => $module->l('Pay at hotel') + ), + OrderPayment::PAYMENT_TYPE_ONLINE => array( + 'key' => 'PAYMENT_TYPE_ONLINE', + 'value' => OrderPayment::PAYMENT_TYPE_ONLINE, + 'name' => $module->l('Online') + ), + OrderPayment::PAYMENT_TYPE_REMOTE_PAYMENT => array( + 'key' => 'PAYMENT_TYPE_REMOTE_PAYMENT', + 'value' => OrderPayment::PAYMENT_TYPE_REMOTE_PAYMENT, + 'name' => $module->l('Remote payment') + ), + ); + } } diff --git a/classes/pdf/HTMLTemplatePaymentReceipt.php b/classes/pdf/HTMLTemplatePaymentReceipt.php index 27374e5e3..ec5ab1c87 100644 --- a/classes/pdf/HTMLTemplatePaymentReceipt.php +++ b/classes/pdf/HTMLTemplatePaymentReceipt.php @@ -113,9 +113,9 @@ class HTMLTemplatePaymentReceiptCore extends HTMLTemplate } } $customer = new Customer((int)$this->order->id_customer); - - $this->orderPayment->payment_type = $this->getPaymentType($this->orderPayment->payment_type); - + $getPaymentTypes = $this->order->getPaymentsTypes(); + $this->orderPayment->payment_type = isset($getPaymentTypes[$this->orderPayment->payment_type]) ? $getPaymentTypes[$this->orderPayment->payment_type] : null; + $legal_free_text = Configuration::get('PS_PAYMENT_RECEIPTS_LEGAL_FREE_TEXT', (int)Context::getContext()->language->id, null, (int)$this->order->id_shop); $this->smarty->assign(array( @@ -130,9 +130,9 @@ class HTMLTemplatePaymentReceiptCore extends HTMLTemplate )); $smarty_tpls = array( - 'style_tab' => $this->smarty->fetch($this->getTemplate('payment.receipt.style-tab')), - 'addresses_tab' => $this->smarty->fetch($this->getTemplate('payment.receipt.addresses-tab')), - 'payment_info_tab' => $this->smarty->fetch($this->getTemplate('payment.receipt.payment-info-tab')), + 'style_tab' => $this->smarty->fetch($this->getTemplate('payment-receipt.style-tab')), + 'addresses_tab' => $this->smarty->fetch($this->getTemplate('payment-receipt.addresses-tab')), + 'payment_info_tab' => $this->smarty->fetch($this->getTemplate('payment-receipt.payment-info-tab')), ); $this->smarty->assign($smarty_tpls); return $this->smarty->fetch($this->getTemplate('payment-receipt')); diff --git a/controllers/admin/AdminOrdersController.php b/controllers/admin/AdminOrdersController.php index 1817c0269..f35954d26 100644 --- a/controllers/admin/AdminOrdersController.php +++ b/controllers/admin/AdminOrdersController.php @@ -419,7 +419,9 @@ class AdminOrdersControllerCore extends AdminController } $paymentTypes = array(); - foreach ($this->getPaymentsTypes() as $paymentType) { + $objOrder = new Order(); + + foreach ($objOrder->getPaymentsTypes() as $paymentType) { if ($paymentType['value'] != OrderPayment::PAYMENT_TYPE_REMOTE_PAYMENT) { $paymentTypes[] = $paymentType; } @@ -683,7 +685,7 @@ class AdminOrdersControllerCore extends AdminController 'order' => $objOrder, 'currencies' => $currencies, 'payment_methods' => $payment_methods, - 'payment_types' => $this->getPaymentsTypes(), + 'payment_types' => $objOrder->getPaymentsTypes(), 'invoices_collection' => $objOrder->getInvoicesCollection(), 'current_id_lang' => $this->context->language->id, ) @@ -3674,7 +3676,7 @@ class AdminOrdersControllerCore extends AdminController 'invoices_collection' => $order->getInvoicesCollection(), 'not_paid_invoices_collection' => $order->getNotPaidInvoicesCollection(), 'payment_methods' => $payment_methods, - 'payment_types' => $this->getPaymentsTypes(), + 'payment_types' => $order->getPaymentsTypes(), 'invoice_management_active' => Configuration::get('PS_INVOICE', null, null, $order->id_shop), 'receipt_management_active' => Configuration::get('PS_PAYMENT_RECEIPTS', null, null, $order->id_shop), 'display_warehouse' => (int)Configuration::get('PS_ADVANCED_STOCK_MANAGEMENT'), @@ -7137,26 +7139,6 @@ class AdminOrdersControllerCore extends AdminController return $products; } - protected function getPaymentsTypes() - { - return array( - OrderPayment::PAYMENT_TYPE_PAY_AT_HOTEL => array( - 'key' => 'PAYMENT_TYPE_PAY_AT_HOTEL', - 'value' => OrderPayment::PAYMENT_TYPE_PAY_AT_HOTEL, - 'name' => $this->l('Pay at hotel') - ), - OrderPayment::PAYMENT_TYPE_ONLINE => array( - 'key' => 'PAYMENT_TYPE_ONLINE', - 'value' => OrderPayment::PAYMENT_TYPE_ONLINE, - 'name' => $this->l('Online') - ), - OrderPayment::PAYMENT_TYPE_REMOTE_PAYMENT => array( - 'key' => 'PAYMENT_TYPE_REMOTE_PAYMENT', - 'value' => OrderPayment::PAYMENT_TYPE_REMOTE_PAYMENT, - 'name' => $this->l('Remote payment') - ), - ); - } /** * @param OrderInvoice $order_invoice diff --git a/controllers/admin/AdminPaymentReceiptsController.php b/controllers/admin/AdminPaymentReceiptsController.php index 2d76d03ab..1ca890269 100644 --- a/controllers/admin/AdminPaymentReceiptsController.php +++ b/controllers/admin/AdminPaymentReceiptsController.php @@ -37,7 +37,7 @@ class AdminPaymentReceiptsControllerCore extends AdminControllerCore 'fields' => array( 'PS_PAYMENT_RECEIPTS' => array( 'title' => $this->l('Enable Receipts'), - 'desc' => $this->l('If enabled, receipts will be generated for orders.'), + 'desc' => $this->l('If enabled, receipts will be generated for order payments.'), 'cast' => 'intval', 'type' => 'bool' ), diff --git a/pdf/payment.receipt.addresses-tab.tpl b/pdf/payment-receipt.addresses-tab.tpl similarity index 100% rename from pdf/payment.receipt.addresses-tab.tpl rename to pdf/payment-receipt.addresses-tab.tpl diff --git a/pdf/payment.receipt.payment-info-tab.tpl b/pdf/payment-receipt.payment-info-tab.tpl similarity index 53% rename from pdf/payment.receipt.payment-info-tab.tpl rename to pdf/payment-receipt.payment-info-tab.tpl index 307bc1de1..048dd3171 100644 --- a/pdf/payment.receipt.payment-info-tab.tpl +++ b/pdf/payment-receipt.payment-info-tab.tpl @@ -21,30 +21,30 @@ *} {if isset($payment_list) && $payment_list} - - +
+ - - + + - - - - {foreach from=$payment_list key=rm_k item=payment} - {cycle values=["color_line_even", "color_line_odd"] assign=bgcolor_class} - - - - - - - - {/foreach} - -
{l s='Payment Details' pdf='true'}
{l s='Payment Method' pdf='true'} {l s='Payment Type' pdf='true'} {l s='Transaction ID' pdf='true'} {l s='Amount' pdf='true'} {l s='Payment Date' pdf='true'}
{$payment->payment_method}{$payment->payment_type}{$payment->transaction_id}{displayPrice currency=$payment->id_currency price=$payment->amount}{dateFormat date=$payment->date_add full=true}
+
{$payment->payment_method|default:'-'}{$payment->payment_type.name|default:'-'}{$payment->transaction_id|default:'-'}{displayPrice currency=$payment->id_currency price=$payment->amount}{dateFormat date=$payment->date_add full=true}
{/if} \ No newline at end of file diff --git a/pdf/payment.receipt.style-tab.tpl b/pdf/payment-receipt.style-tab.tpl similarity index 100% rename from pdf/payment.receipt.style-tab.tpl rename to pdf/payment-receipt.style-tab.tpl From 860669d4a9e088dd25af8dcfdf703ec503811e3c Mon Sep 17 00:00:00 2001 From: varun kewlani Date: Wed, 13 May 2026 17:21:07 +0530 Subject: [PATCH 17/35] Fixed: Fixed the table structure of booking voucher pdf, removed unnecessary varaible and function calls Co-authored-by: Name --- classes/pdf/HTMLTemplateBookingVoucher.php | 408 +++++--------- pdf/booking-voucher.tpl | 615 +++++++++------------ 2 files changed, 401 insertions(+), 622 deletions(-) diff --git a/classes/pdf/HTMLTemplateBookingVoucher.php b/classes/pdf/HTMLTemplateBookingVoucher.php index af465930f..c4e5d17df 100644 --- a/classes/pdf/HTMLTemplateBookingVoucher.php +++ b/classes/pdf/HTMLTemplateBookingVoucher.php @@ -72,97 +72,74 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate // --- Order state --- $orderState = $this->order->getCurrentStateFull((int)$this->order->id_lang); - if (!is_array($orderState)) { - $orderState = array('name' => self::l('Confirmed')); - } elseif (empty($orderState['name'])) { - $orderState['name'] = self::l('Confirmed'); - } // --- Hotel / property --- - $hotelName = Configuration::get('PS_SHOP_NAME', null, null, (int)$this->order->id_shop); - $hotelPhone = Configuration::get('PS_SHOP_PHONE', null, null, (int)$this->order->id_shop); - $hotelEmail = Configuration::get('PS_SHOP_EMAIL', null, null, (int)$this->order->id_shop); - $hotelAddress = ''; - $checkInTime = '--'; - $checkOutTime = '--'; - $paymentPolicy = ''; - $cancellationPolicy = ''; - - if (!empty($bookings)) { - $firstBooking = reset($bookings); - if (!empty($firstBooking['id_hotel'])) { - $objHotel = new HotelBranchInformation((int)$firstBooking['id_hotel'], (int)$this->order->id_lang); - if (Validate::isLoadedObject($objHotel)) { - $idHotel = (int)$objHotel->id; - $hotelName = $objHotel->hotel_name ? $objHotel->hotel_name : $hotelName; - $hotelPhone = $objHotel->phone ? $objHotel->phone : $hotelPhone; - $hotelEmail = $objHotel->email ? $objHotel->email : $hotelEmail; - $checkInTime = $objHotel->check_in ?: '--'; - $checkOutTime = $objHotel->check_out ?: '--'; - - $policies = is_array($objHotel->policies) ? '' : (string)$objHotel->policies; - if ($policies) { - $policyText = html_entity_decode($policies, ENT_QUOTES, 'UTF-8'); - $policyText = preg_replace('//i', "\n", $policyText); - $policyText = strip_tags($policyText); - $policyText = preg_replace("/[\r\n]+/", "\n", $policyText); - $paymentPolicy = nl2br(trim($policyText)); - } - - if ($idHotel) { - $objBranchRefundRules = new HotelBranchRefundRules(); - $refundRules = $objBranchRefundRules->getHotelRefundRules( - $idHotel, 0, 1, (int)$this->order->id_lang, 1 - ); - if (is_array($refundRules) && !empty($refundRules)) { - $policyBlocks = array(); - foreach ($refundRules as $rule) { - $ruleName = isset($rule['name']) ? trim(strip_tags($rule['name'])) : ''; - $ruleDescription = isset($rule['description']) ? trim($rule['description']) : ''; - if ($ruleName === '' && $ruleDescription === '') { - continue; - } - $block = $ruleName !== '' ? ''.Tools::safeOutput($ruleName).'' : ''; - if ($ruleDescription !== '') { - $block .= ($block !== '' ? ': ' : '').$ruleDescription; - } - $policyBlocks[] = $block; - } - if (!empty($policyBlocks)) { - $cancellationPolicy = implode('

', $policyBlocks); - } - } - } - - if ($idHotelAddress = $objHotel->getHotelIdAddress()) { - $objHotelAddress = new Address((int)$idHotelAddress); - if (Validate::isLoadedObject($objHotelAddress)) { - $objHotelAddress->firstname = $hotelName; - $hotelAddress = AddressFormat::generateAddress( - $objHotelAddress, - array('avoid' => array('lastname')), - '
', - ' ' - ); - } - } - } + $objHotel = null; + $idHotel = HotelBookingDetail::getIdHotelByIdOrder((int)$this->order->id); + if ($idHotel) { + $objHotel = new HotelBranchInformation((int)$idHotel, (int)$this->order->id_lang); + if (!Validate::isLoadedObject($objHotel)) { + $objHotel = null; } } - if (!$hotelAddress) { - $shopAddress = $this->shop->getAddress(); - if ($shopAddress instanceof Address) { - $hotelAddress = AddressFormat::generateAddress($shopAddress, array(), '
', ' '); + $hotelAddress = ''; + $paymentPolicy = ''; + $cancellationPolicy = ''; + + if ($objHotel) { + $policies = is_array($objHotel->policies) ? '' : (string)$objHotel->policies; + if ($policies) { + $policyText = html_entity_decode($policies, ENT_QUOTES, 'UTF-8'); + $policyText = preg_replace('//i', "\n", $policyText); + $policyText = strip_tags($policyText); + $policyText = preg_replace("/[\r\n]+/", "\n", $policyText); + $paymentPolicy = nl2br(trim($policyText)); + } + + $objBranchRefundRules = new HotelBranchRefundRules(); + $refundRules = $objBranchRefundRules->getHotelRefundRules( + (int)$objHotel->id, 0, 1, (int)$this->order->id_lang, 1 + ); + if (is_array($refundRules) && !empty($refundRules)) { + $policyBlocks = array(); + foreach ($refundRules as $rule) { + $ruleName = isset($rule['name']) ? trim(strip_tags($rule['name'])) : ''; + $ruleDescription = isset($rule['description']) ? trim($rule['description']) : ''; + if ($ruleName === '' && $ruleDescription === '') { + continue; + } + $block = $ruleName !== '' ? ''.Tools::safeOutput($ruleName).'' : ''; + if ($ruleDescription !== '') { + $block .= ($block !== '' ? ': ' : '').$ruleDescription; + } + $policyBlocks[] = $block; + } + if (!empty($policyBlocks)) { + $cancellationPolicy = implode('

', $policyBlocks); + } + } + + if ($idHotelAddress = $objHotel->getHotelIdAddress()) { + $objHotelAddress = new Address((int)$idHotelAddress); + if (Validate::isLoadedObject($objHotelAddress)) { + $objHotelAddress->firstname = $objHotel->hotel_name; + $hotelAddress = AddressFormat::generateAddress( + $objHotelAddress, + array('avoid' => array('lastname')), + '
', + ' ' + ); + } } } // --- Rooms, services, occupancy --- $totalRooms = 0; - $serviceRows = array(); - $roomTypeRows = array(); $totalRoomNights = 0; $totalGuests = 0; + $serviceGroups = array(); + $roomTypeRows = array(); foreach ($bookings as $booking) { $nights = (int)HotelHelper::getNumberOfDays($booking['date_from'], $booking['date_to']); @@ -170,20 +147,17 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate $nights = 1; } - $roomReference = !empty($booking['room_num']) ? $booking['room_num'] : 'BK-'.(int)$booking['id']; - $adults = (int)$booking['adults']; - $children = (int)$booking['children']; - $totalGuests += $adults + $children; + $totalGuests += (int)$booking['adults'] + (int)$booking['children']; $totalRoomNights += $nights; + $totalRooms++; $dateFrom = ($booking['date_from'] && $booking['date_from'] !== '0000-00-00 00:00:00') ? Tools::displayDate($booking['date_from'], null, false) : '--'; $dateTo = ($booking['date_to'] && $booking['date_to'] !== '0000-00-00 00:00:00') ? Tools::displayDate($booking['date_to'], null, false) : '--'; - // Extra demands for this room - $autoAddedWithRoomPriceTaxExcl = 0; - $roomServiceRows = array(); + // Services for this booking + $bookingServices = array(); $objBookingDemand = new HotelBookingDemands(); $extraDemands = $objBookingDemand->getRoomTypeBookingExtraDemands( @@ -192,17 +166,11 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate ); if (is_array($extraDemands)) { foreach ($extraDemands as $demand) { - $roomServiceRows[] = array( - 'room_ref' => $roomReference, - 'check_in_date' => $dateFrom, - 'check_out_date' => $dateTo, - 'name' => $demand['name'], - 'quantity' => 1, - ); + $bookingServices[] = array('name' => $demand['name'], 'quantity' => 1); } } - // Auto-added services bundled with room price + // Auto-added services bundled with room price (for cost calculation only) $objServiceProductOrderDetail = new ServiceProductOrderDetail(); $autoAddedWithRoomPriceTaxExcl = (float)$objServiceProductOrderDetail->getRoomTypeServiceProducts( (int)$this->order->id, 0, 0, @@ -219,20 +187,21 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate ); if (isset($additionalServices[(int)$booking['id']]['additional_services'])) { foreach ($additionalServices[(int)$booking['id']]['additional_services'] as $service) { - $roomServiceRows[] = array( - 'room_ref' => $roomReference, - 'check_in_date' => $dateFrom, - 'check_out_date' => $dateTo, - 'name' => $service['name'], - 'quantity' => (int)$service['quantity'], - ); + $bookingServices[] = array('name' => $service['name'], 'quantity' => (int)$service['quantity']); } } - $serviceRows = array_merge($serviceRows, $roomServiceRows); + if (!empty($bookingServices)) { + $serviceGroups[] = array( + 'room_type_name' => isset($booking['room_type_name']) ? $booking['room_type_name'] : '', + 'check_in_date' => $dateFrom, + 'check_out_date' => $dateTo, + 'room_count' => $totalRooms, + 'services' => $bookingServices, + ); + } - $roomTotalTaxExcl = (float)$booking['total_price_tax_excl']; - $roomTotalWithAutoServicesTaxExcl = $roomTotalTaxExcl + $autoAddedWithRoomPriceTaxExcl; + $roomTotalWithAutoServicesTaxExcl = (float)$booking['total_price_tax_excl'] + $autoAddedWithRoomPriceTaxExcl; // Aggregate per room type + date range $roomTypeRowKey = implode('|', array( @@ -270,10 +239,9 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate } $roomTypeRows[$roomTypeRowKey]['rooms'] += 1; - $roomTypeRows[$roomTypeRowKey]['adults'] += $adults; - $roomTypeRows[$roomTypeRowKey]['children'] += $children; + $roomTypeRows[$roomTypeRowKey]['adults'] += (int)$booking['adults']; + $roomTypeRows[$roomTypeRowKey]['children'] += (int)$booking['children']; $roomTypeRows[$roomTypeRowKey]['total_price_tax_excl'] += $roomTotalWithAutoServicesTaxExcl; - $totalRooms++; } foreach ($roomTypeRows as &$roomTypeRow) { @@ -288,40 +256,81 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate } unset($roomTypeRow); - // --- Guest details --- - $guestName = ''; - $guestEmail = ''; - $guestPhone = ''; $guestAddress = ''; + $guest = new Customer((int)$this->order->id_customer); - $customer = new Customer((int)$this->order->id_customer); - if (Validate::isLoadedObject($customer)) { - $guestName = trim($customer->firstname.' '.$customer->lastname); - $guestEmail = $customer->email; - $guestPhone = $customer->phone; - } $addressId = (int)$this->order->id_address_invoice ?: (int)$this->order->id_address_delivery; if ($addressId) { $address = new Address($addressId); if (Validate::isLoadedObject($address)) { - $guestPhone = $address->phone_mobile ? $address->phone_mobile : $address->phone; $guestAddress = AddressFormat::generateAddress($address, array(), '
', ' '); } } - // Override with primary guest if this is a guest booking + if ($idCustomerGuestDetail = OrderCustomerGuestDetail::isCustomerGuestBooking((int)$this->order->id)) { - $traveler = new OrderCustomerGuestDetail((int)$idCustomerGuestDetail); - if (Validate::isLoadedObject($traveler)) { - $guestName = trim($traveler->firstname.' '.$traveler->lastname); - $guestEmail = $traveler->email; - $guestPhone = $traveler->phone; + $objGuest = new OrderCustomerGuestDetail((int)$idCustomerGuestDetail); + if (Validate::isLoadedObject($objGuest)) { + $guest = $objGuest; } } - // --- Totals --- - $footer = $this->getTotalsData($totalRooms, $totalRoomNights, $totalGuests); + $objBookingDemand = new HotelBookingDemands(); + $totalDemandsPriceTE = (float)$objBookingDemand->getRoomTypeBookingExtraDemands( + (int)$this->order->id, 0, 0, 0, 0, 1, 1, 0 + ); + $totalDemandsPriceTI = (float)$objBookingDemand->getRoomTypeBookingExtraDemands( + (int)$this->order->id, 0, 0, 0, 0, 1, 1, 1 + ); + + $orderDetails = OrderDetail::getList((int)$this->order->id); + $idsOrderDetail = is_array($orderDetails) + ? array_unique(array_map('intval', array_column($orderDetails, 'id_order_detail'))) + : array(); + + $footer = array( + 'room_price_tax_excl' => 0, + 'service_products_price_tax_excl' => 0, + 'additional_service_price_tax_excl' => 0, + 'total_convenience_fee_te' => 0, + ); + $roomsCostTaxIncl = 0; + $serviceProductsCostTaxIncl = 0; + $additionalServicesCostTaxIncl = 0; + $convenienceFeeTaxIncl = 0; + + if (!empty($idsOrderDetail)) { + $footer['room_price_tax_excl'] = $this->order->getTotalProductsWithoutTaxes(false, true, null, null, null, $idsOrderDetail) + $this->order->getTotalProductsWithoutTaxes(false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, 1, Product::PRICE_ADDITION_TYPE_WITH_ROOM, $idsOrderDetail); + $roomsCostTaxIncl = $this->order->getTotalProductsWithTaxes(false, true, null, null, null, $idsOrderDetail) + $this->order->getTotalProductsWithTaxes(false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, 1, Product::PRICE_ADDITION_TYPE_WITH_ROOM, $idsOrderDetail); + $footer['service_products_price_tax_excl'] = $this->order->getTotalProductsWithoutTaxes(false, false, Product::SELLING_PREFERENCE_STANDALONE, false, false, $idsOrderDetail) + $this->order->getTotalProductsWithoutTaxes(false, false, Product::SELLING_PREFERENCE_HOTEL_STANDALONE, false, false, $idsOrderDetail); + $serviceProductsCostTaxIncl = $this->order->getTotalProductsWithTaxes(false, false, Product::SELLING_PREFERENCE_STANDALONE, false, false, $idsOrderDetail) + $this->order->getTotalProductsWithTaxes(false, false, Product::SELLING_PREFERENCE_HOTEL_STANDALONE, false, false, $idsOrderDetail); + $footer['additional_service_price_tax_excl'] = $this->order->getTotalProductsWithoutTaxes(false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, 0, null, $idsOrderDetail) + $totalDemandsPriceTE; + $additionalServicesCostTaxIncl = $this->order->getTotalProductsWithTaxes(false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, 0, null, $idsOrderDetail) + $totalDemandsPriceTI; + $footer['total_convenience_fee_te'] = $this->order->getTotalProductsWithoutTaxes(false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, 1, Product::PRICE_ADDITION_TYPE_INDEPENDENT, $idsOrderDetail); + $convenienceFeeTaxIncl = $this->order->getTotalProductsWithTaxes(false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, 1, Product::PRICE_ADDITION_TYPE_INDEPENDENT, $idsOrderDetail); + } + + $productDiscountsTaxIncl = (float)$this->order->total_discounts_tax_incl; + foreach ($this->order->getCartRules() as $cartRule) { + if (!empty($cartRule['free_shipping'])) { + $productDiscountsTaxIncl -= (float)$this->order->total_shipping_tax_incl; + } + } + if ($productDiscountsTaxIncl < 0) { + $productDiscountsTaxIncl = 0; + } + + $footer['total_without_discount_te'] = $footer['room_price_tax_excl'] + $footer['total_convenience_fee_te'] + $footer['additional_service_price_tax_excl'] + $footer['service_products_price_tax_excl']; + $totalWithoutDiscountTaxIncl = $roomsCostTaxIncl + $serviceProductsCostTaxIncl + $additionalServicesCostTaxIncl + $convenienceFeeTaxIncl; + $footer['total_tax_without_discount'] = max(0, $totalWithoutDiscountTaxIncl - $footer['total_without_discount_te']); + $grandTotal = max(0.0, $totalWithoutDiscountTaxIncl - $productDiscountsTaxIncl); + $footer['product_discounts_tax_incl'] = $productDiscountsTaxIncl; + $footer['total_paid_tax_incl'] = $grandTotal; + $footer['amount_due'] = Tools::ps_round( + max(0, $grandTotal - (float)$this->order->getTotalPaid()), + _PS_PRICE_COMPUTE_PRECISION_, + $this->order->round_mode + ); - // --- Payment --- $paymentMethods = array(); $payments = $this->order->getOrderPaymentDetail(); if (is_array($payments) && $payments) { @@ -336,48 +345,29 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate $idCurrency = isset($paymentDetail['id_currency']) ? (int)$paymentDetail['id_currency'] : (int)$this->order->id_currency; - $paymentMethods[] = $method.' - '.Tools::displayPrice($amount, $idCurrency); + $paymentMethods[] = $method.': '.Tools::displayPrice($amount, $idCurrency); } } $paymentMethod = $paymentMethods ? implode('

', $paymentMethods) : $this->order->payment; $data = array( 'order' => $this->order, - 'order_status' => $orderState['name'], + 'order_state' => $orderState, 'booking_date' => Tools::displayDate($this->order->date_add, null, false), 'issue_date' => Tools::displayDate(date('Y-m-d'), null, false), - // hotel / property - 'hotel_name' => $hotelName, - 'hotel_phone' => $hotelPhone, - 'hotel_email' => $hotelEmail, + 'hotel' => $objHotel, 'hotel_address' => $hotelAddress, - // guest - 'guest_name' => $guestName, - 'guest_email' => $guestEmail, - 'guest_phone' => $guestPhone, + 'guest' => $guest, 'guest_address' => $guestAddress, 'total_guests' => (int)$totalGuests, - // booking summary 'total_rooms' => (int)$totalRooms, 'total_room_nights' => (int)$totalRoomNights, - // room and service tables 'room_type_rows' => array_values($roomTypeRows), - 'service_rows' => $serviceRows, - // payment + 'service_groups' => $serviceGroups, 'payment_method' => $paymentMethod, - 'payment_status' => $orderState['name'], - // totals — key named $footer to match invoice convention 'footer' => $footer, - // policies - 'check_in_time' => ($checkInTime && $checkInTime !== '--') - ? date('h:i a', strtotime($checkInTime)) : '--', - 'check_out_time' => ($checkOutTime && $checkOutTime !== '--') - ? date('h:i a', strtotime($checkOutTime)) : '--', - 'payment_policy' => $paymentPolicy - ? $paymentPolicy - : self::l('Please contact the property for policy details.'), + 'payment_policy' => $paymentPolicy, 'cancellation_policy' => $cancellationPolicy, - 'has_cancellation_policy' => (bool)trim(strip_tags($cancellationPolicy)), ); if (Tools::getValue('debug')) { @@ -388,120 +378,6 @@ class HTMLTemplateBookingVoucherCore extends HTMLTemplate return $this->smarty->fetch($this->getTemplate('booking-voucher')); } - /** - * Computes all cost totals using Order's own methods. - * Mirrors the $footer calculation in HTMLTemplateInvoice::getContent(). - * Extracted because it is 100+ lines of calculation that would obscure the data assembly above. - * - * @param int $totalRooms - * @param int $totalRoomNights - * @param int $totalGuests - * @return array - */ - protected function getTotalsData($totalRooms, $totalRoomNights, $totalGuests) - { - $objBookingDemand = new HotelBookingDemands(); - $totalDemandsPriceTE = (float)$objBookingDemand->getRoomTypeBookingExtraDemands( - (int)$this->order->id, 0, 0, 0, 0, 1, 1, 0 - ); - $totalDemandsPriceTI = (float)$objBookingDemand->getRoomTypeBookingExtraDemands( - (int)$this->order->id, 0, 0, 0, 0, 1, 1, 1 - ); - - $orderDetails = OrderDetail::getList((int)$this->order->id); - $idsOrderDetail = is_array($orderDetails) - ? array_unique(array_map('intval', array_column($orderDetails, 'id_order_detail'))) - : array(); - - $roomsCost = 0; - $roomsCostTaxIncl = 0; - $serviceProductsCost = 0; - $serviceProductsCostTaxIncl = 0; - $additionalServicesCost = 0; - $additionalServicesCostTaxIncl = 0; - $convenienceFee = 0; - $convenienceFeeTaxIncl = 0; - $productDiscountsTaxIncl = (float)$this->order->total_discounts_tax_incl; - - foreach ($this->order->getCartRules() as $cartRule) { - if (!empty($cartRule['free_shipping'])) { - $productDiscountsTaxIncl -= (float)$this->order->total_shipping_tax_incl; - } - } - - if ($productDiscountsTaxIncl < 0) { - $productDiscountsTaxIncl = 0; - } - - if (!empty($idsOrderDetail)) { - $roomsCost = (float)$this->order->getTotalProductsWithoutTaxes( - false, true, null, null, null, $idsOrderDetail - ) + (float)$this->order->getTotalProductsWithoutTaxes( - false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, - 1, Product::PRICE_ADDITION_TYPE_WITH_ROOM, $idsOrderDetail - ); - $roomsCostTaxIncl = (float)$this->order->getTotalProductsWithTaxes( - false, true, null, null, null, $idsOrderDetail - ) + (float)$this->order->getTotalProductsWithTaxes( - false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, - 1, Product::PRICE_ADDITION_TYPE_WITH_ROOM, $idsOrderDetail - ); - - $serviceProductsCost = (float)$this->order->getTotalProductsWithoutTaxes( - false, false, Product::SELLING_PREFERENCE_STANDALONE, false, false, $idsOrderDetail - ) + (float)$this->order->getTotalProductsWithoutTaxes( - false, false, Product::SELLING_PREFERENCE_HOTEL_STANDALONE, false, false, $idsOrderDetail - ); - $serviceProductsCostTaxIncl = (float)$this->order->getTotalProductsWithTaxes( - false, false, Product::SELLING_PREFERENCE_STANDALONE, false, false, $idsOrderDetail - ) + (float)$this->order->getTotalProductsWithTaxes( - false, false, Product::SELLING_PREFERENCE_HOTEL_STANDALONE, false, false, $idsOrderDetail - ); - - $additionalServicesCost = (float)$this->order->getTotalProductsWithoutTaxes( - false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, 0, null, $idsOrderDetail - ) + $totalDemandsPriceTE; - $additionalServicesCostTaxIncl = (float)$this->order->getTotalProductsWithTaxes( - false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, 0, null, $idsOrderDetail - ) + $totalDemandsPriceTI; - - $convenienceFee = (float)$this->order->getTotalProductsWithoutTaxes( - false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, - 1, Product::PRICE_ADDITION_TYPE_INDEPENDENT, $idsOrderDetail - ); - $convenienceFeeTaxIncl = (float)$this->order->getTotalProductsWithTaxes( - false, false, Product::SELLING_PREFERENCE_WITH_ROOM_TYPE, - 1, Product::PRICE_ADDITION_TYPE_INDEPENDENT, $idsOrderDetail - ); - } - - $grandTotal = (float)$this->order->total_paid_tax_incl; - $totalPaidReal = (float)$this->order->getTotalPaid(); - $totalWithoutDiscountTaxExcl = $roomsCost + $serviceProductsCost + $additionalServicesCost + $convenienceFee; - $totalWithoutDiscountTaxIncl = $roomsCostTaxIncl + $serviceProductsCostTaxIncl + $additionalServicesCostTaxIncl + $convenienceFeeTaxIncl; - $totalTaxes = $totalWithoutDiscountTaxIncl - $totalWithoutDiscountTaxExcl; - if ($totalTaxes < 0) { - $totalTaxes = 0; - } - - $amountDue = $grandTotal - $totalPaidReal; - if ($amountDue < 0) { - $amountDue = 0; - } - - return array( - 'room_price_tax_excl' => $roomsCost, - 'service_products_price_tax_excl' => $serviceProductsCost, - 'additional_service_price_tax_excl' => $additionalServicesCost, - 'total_convenience_fee_te' => $convenienceFee, - 'total_without_discount_te' => $totalWithoutDiscountTaxExcl, - 'total_tax_without_discount' => $totalTaxes, - 'product_discounts_tax_incl' => $productDiscountsTaxIncl, - 'total_paid_tax_incl' => $grandTotal, - 'amount_due' => Tools::ps_round($amountDue, _PS_PRICE_COMPUTE_PRECISION_, $this->order->round_mode), - ); - } - /** * @return string */ diff --git a/pdf/booking-voucher.tpl b/pdf/booking-voucher.tpl index 68aab5051..cf6d0b966 100644 --- a/pdf/booking-voucher.tpl +++ b/pdf/booking-voucher.tpl @@ -23,362 +23,273 @@ {assign var=pdf_style_template value=$smarty.const._PS_PDF_DIR_|cat:'invoice.style-tab.tpl'} {include file=$pdf_style_template} - - +
+ + + + + + + + + + + + {* *} + + + + + + + + + + + + {if $service_groups} - - - - - - - - - - - + - - - - - - + - + {/if} +
+ + + + + + + + + + + + + + + + + + + + + + +
{l s='BOOKING INFORMATION' pdf='true'}
{l s='Booking Reference' pdf='true'}{$order->reference|escape:'html':'UTF-8'}{l s='Booking Date' pdf='true'}{$booking_date|escape:'html':'UTF-8'}{l s='Voucher Issue Date' pdf='true'}{$issue_date|escape:'html':'UTF-8'}
{l s='Status' pdf='true'}{if $order_state && isset($order_state.name)}{$order_state.name|escape:'html':'UTF-8'}{/if}{l s='Total Rooms' pdf='true'}{$total_rooms|intval}{l s='Total Nights' pdf='true'}{$total_room_nights|intval}
+
 
+ + + + + + + + + + + + + + + + + + + + + + +
{l s='PROPERTY DETAILS' pdf='true'}
{l s='Property' pdf='true'}{if $hotel}{$hotel->hotel_name|escape:'html':'UTF-8'}{/if}
{l s='Email' pdf='true'}{if $hotel}{$hotel->email|escape:'html':'UTF-8'}{/if}
{l s='Phone' pdf='true'}{if $hotel}{$hotel->phone|escape:'html':'UTF-8'}{/if}
{l s='Address' pdf='true'}{if $hotel_address}{$hotel_address nofilter}{else}--{/if}
+
  + + + + + + + + + + + + + + + + + + + + + + + + + + +
{l s='GUEST INFORMATION' pdf='true'}
{l s='Primary Guest' pdf='true'}{$guest->firstname|escape:'html':'UTF-8'} {$guest->lastname|escape:'html':'UTF-8'}
{l s='Email' pdf='true'}{$guest->email|escape:'html':'UTF-8'}
{l s='Phone' pdf='true'}{$guest->phone|escape:'html':'UTF-8'}
{l s='Address' pdf='true'}{if $guest_address|strip_tags|trim}{$guest_address nofilter}{else}--{/if}
{l s='Total Guests' pdf='true'}{$total_guests|intval}
+
 
+ + + + + + + + + + + + + + + + + {if $room_type_rows} + {foreach from=$room_type_rows item=room_type_row} + {cycle values="color_line_even,color_line_odd" assign=bgcolor_class} + + + + + + + + + + {/foreach} + {else} + + + + {/if} + +
{l s='ROOM DETAILS' pdf='true'}
{l s='Room Description' pdf='true'}{l s='Tax Rate(s)' pdf='true'}{l s='Unit Price' pdf='true'}
{l s='(Tax excl.)' pdf='true'}
{l s='Rooms' pdf='true'}{l s='Check-in Date' pdf='true'}{l s='Check-out Date' pdf='true'}{l s='Total' pdf='true'}
{l s='(Tax excl.)' pdf='true'}
{$room_type_row.room_type_name|escape:'html':'UTF-8'}{$room_type_row.order_detail_tax_label}{displayPrice currency=$order->id_currency price=$room_type_row.unit_price_tax_excl} + {if $room_type_row.adults <= 9}0{/if}{$room_type_row.adults|intval} {if $room_type_row.adults > 1}{l s='Adults' pdf='true'}{else}{l s='Adult' pdf='true'}{/if}{if $room_type_row.children}, {if $room_type_row.children <= 9}0{/if}{$room_type_row.children|intval} {if $room_type_row.children > 1}{l s='Children' pdf='true'}{else}{l s='Child' pdf='true'}{/if}{/if}
{if $room_type_row.rooms <= 9}0{/if}{$room_type_row.rooms|intval} {if $room_type_row.rooms > 1}{l s='Rooms' pdf='true'}{else}{l s='Room' pdf='true'}{/if} +
{$room_type_row.check_in_date|escape:'html':'UTF-8'}{$room_type_row.check_out_date|escape:'html':'UTF-8'}{displayPrice currency=$order->id_currency price=$room_type_row.total_price_tax_excl}
{l s='No room details available.' pdf='true'}
+
{l s='BOOKING INFORMATION' pdf='true'}
{l s='Booking Reference' pdf='true'}{$order->reference|escape:'html':'UTF-8'}{l s='Booking Date' pdf='true'}{$booking_date|escape:'html':'UTF-8'}{l s='Voucher Issue Date' pdf='true'}{$issue_date|escape:'html':'UTF-8'} 
{l s='Status' pdf='true'}{$order_status|escape:'html':'UTF-8'}{l s='Total Rooms' pdf='true'}{$total_rooms|intval}{l s='Total Nights' pdf='true'}{$total_room_nights|intval} + + + + + + + + + + + {foreach from=$service_groups item=group} + {foreach from=$group.services item=service name=service_loop} + + {if $smarty.foreach.service_loop.first} + + {/if} + + + + {/foreach} + {/foreach} + +
{l s='SERVICES DETAILS' pdf='true'}
{l s='Room Type' pdf='true'}{l s='Service Name' pdf='true'}{l s='Qty' pdf='true'}
{$group.room_type_name|escape:'html':'UTF-8'}
+ {$group.check_in_date|escape:'html':'UTF-8'} - {$group.check_out_date|escape:'html':'UTF-8'}
+ {l s='Room' pdf='true'} - {$group.room_count|intval} +
{$service.name|escape:'html':'UTF-8'}{$service.quantity|intval}
+
- +
 
+ + - + +
+ + + + + + + + + + + + + + +
{l s='PAYMENT INFORMATION' pdf='true'}
{l s='Payment Method' pdf='true'}{$payment_method nofilter}
{l s='Payment Status' pdf='true'}{if $order_state && isset($order_state.name)}{$order_state.name|escape:'html':'UTF-8'}{/if}
+
+ + + + + + {if isset($footer.room_price_tax_excl) && $footer.room_price_tax_excl} + + + + + {/if} + {if isset($footer.service_products_price_tax_excl) && $footer.service_products_price_tax_excl} + + + + + {/if} + {if isset($footer.additional_service_price_tax_excl) && $footer.additional_service_price_tax_excl} + + + + + {/if} + {if isset($footer.total_convenience_fee_te) && $footer.total_convenience_fee_te} + + + + + {/if} + + + + + {if $footer.total_tax_without_discount > 0} + + + + + {/if} + {if isset($footer.product_discounts_tax_incl) && $footer.product_discounts_tax_incl > 0} + + + + + {/if} + + + + + {if $footer.amount_due > 0} + + + + + {/if} + +
{l s='TOTAL AMOUNT' pdf='true'}
{l s='Total Rooms Cost (tax excl.)' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.room_price_tax_excl}
{l s='Total Products cost (tax excl.)' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.service_products_price_tax_excl}
{l s='Extra Services Cost (tax excl.)' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.additional_service_price_tax_excl}
{l s='Convenience Fee (tax excl.)' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.total_convenience_fee_te}
{l s='Total (Tax excl.)' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.total_without_discount_te}
{l s='Total Tax' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.total_tax_without_discount}
{l s='Total Discounts' pdf='true'}- {displayPrice currency=$order->id_currency price=$footer.product_discounts_tax_incl}
{l s='Final Booking Amount' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.total_paid_tax_incl}
{l s='Due Amount' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.amount_due}
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{l s='PROPERTY DETAILS' pdf='true'}{l s='GUEST INFORMATION' pdf='true'}
{l s='Property' pdf='true'}{$hotel_name|escape:'html':'UTF-8'}{l s='Primary Guest' pdf='true'}{$guest_name|escape:'html':'UTF-8'}
{l s='Email' pdf='true'}{$hotel_email|escape:'html':'UTF-8'}{l s='Email' pdf='true'}{$guest_email|escape:'html':'UTF-8'}
{l s='Phone' pdf='true'}{$hotel_phone|escape:'html':'UTF-8'}{l s='Phone' pdf='true'}{$guest_phone|escape:'html':'UTF-8'}
{l s='Address' pdf='true'}{if $hotel_address}{$hotel_address nofilter}{else}--{/if}{l s='Address' pdf='true'}{if $guest_address|strip_tags|trim}{$guest_address nofilter}{else}--{/if}
{l s='Total Guests' pdf='true'}{$total_guests|intval}
- - - - - -
- - - - - - - - - - - - - - - - - - {if $room_type_rows} - {foreach from=$room_type_rows item=room_type_row} - {cycle values="color_line_even,color_line_odd" assign=bgcolor_class} - - - - - - - - - - {/foreach} - {else} - - - - {/if} - -
{l s='ROOM DETAILS' pdf='true'}
{l s='Room Description' pdf='true'}{l s='Tax Rate(s)' pdf='true'}{l s='Unit Price' pdf='true'}
{l s='(Tax excl.)' pdf='true'}
{l s='Rooms' pdf='true'}{l s='Check-in Date' pdf='true'}{l s='Check-out Date' pdf='true'}{l s='Total' pdf='true'}
{l s='(Tax excl.)' pdf='true'}
{$room_type_row.room_type_name|escape:'html':'UTF-8'}{$room_type_row.order_detail_tax_label}{displayPrice currency=$order->id_currency price=$room_type_row.unit_price_tax_excl} - {if $room_type_row.adults <= 9}0{/if}{$room_type_row.adults|intval} {if $room_type_row.adults > 1}{l s='Adults' pdf='true'}{else}{l s='Adult' pdf='true'}{/if}{if $room_type_row.children}, {if $room_type_row.children <= 9}0{/if}{$room_type_row.children|intval} {if $room_type_row.children > 1}{l s='Children' pdf='true'}{else}{l s='Child' pdf='true'}{/if}{/if}
{if $room_type_row.rooms <= 9}0{/if}{$room_type_row.rooms|intval} {if $room_type_row.rooms > 1}{l s='Rooms' pdf='true'}{else}{l s='Room' pdf='true'}{/if} -
{$room_type_row.check_in_date|escape:'html':'UTF-8'}{$room_type_row.check_out_date|escape:'html':'UTF-8'}{displayPrice currency=$order->id_currency price=$room_type_row.total_price_tax_excl}
{l s='No room details available.' pdf='true'}
- -{if $service_rows} - - - - -
- +{if $cancellation_policy} +
 
- - - - - - - - - - - - {foreach from=$service_rows item=service} - {cycle values="color_line_even,color_line_odd" assign=bgcolor_class} - - - - - - - {/foreach} - -
{l s='SERVICES DETAILS' pdf='true'}
{l s='Room Ref' pdf='true'}{l s='Check-in / Check-out' pdf='true'}{l s='Service Name' pdf='true'}{l s='Qty' pdf='true'}
{$service.room_ref|escape:'html':'UTF-8'}{$service.check_in_date|escape:'html':'UTF-8'} - {$service.check_out_date|escape:'html':'UTF-8'}{$service.name|escape:'html':'UTF-8'}{$service.quantity|intval}
-{/if} - - - - - -
- -{assign var=payment_method_rendered value=false} -{assign var=payment_status_rendered value=false} - - - - - - - - {if isset($footer.room_price_tax_excl) && $footer.room_price_tax_excl} - - {if !$payment_method_rendered} - - - {assign var=payment_method_rendered value=true} - {elseif !$payment_status_rendered} - - - {assign var=payment_status_rendered value=true} - {else} - - - {/if} - - - - - {/if} - {if isset($footer.service_products_price_tax_excl) && $footer.service_products_price_tax_excl} - - {if !$payment_method_rendered} - - - {assign var=payment_method_rendered value=true} - {elseif !$payment_status_rendered} - - - {assign var=payment_status_rendered value=true} - {else} - - - {/if} - - - - - {/if} - {if isset($footer.additional_service_price_tax_excl) && $footer.additional_service_price_tax_excl} - - {if !$payment_method_rendered} - - - {assign var=payment_method_rendered value=true} - {elseif !$payment_status_rendered} - - - {assign var=payment_status_rendered value=true} - {else} - - - {/if} - - - - - {/if} - {if isset($footer.total_convenience_fee_te) && $footer.total_convenience_fee_te} - - {if !$payment_method_rendered} - - - {assign var=payment_method_rendered value=true} - {elseif !$payment_status_rendered} - - - {assign var=payment_status_rendered value=true} - {else} - - - {/if} - - - - - {/if} - - {if !$payment_method_rendered} - - - {assign var=payment_method_rendered value=true} - {elseif !$payment_status_rendered} - - - {assign var=payment_status_rendered value=true} - {else} - - - {/if} - - - - - {if $footer.total_tax_without_discount > 0} - - {if !$payment_method_rendered} - - - {assign var=payment_method_rendered value=true} - {elseif !$payment_status_rendered} - - - {assign var=payment_status_rendered value=true} - {else} - - - {/if} - - - - - {/if} - {if isset($footer.product_discounts_tax_incl) && $footer.product_discounts_tax_incl > 0} - - {if !$payment_method_rendered} - - - {assign var=payment_method_rendered value=true} - {elseif !$payment_status_rendered} - - - {assign var=payment_status_rendered value=true} - {else} - - - {/if} - - - - - {/if} - - {if !$payment_method_rendered} - - - {assign var=payment_method_rendered value=true} - {elseif !$payment_status_rendered} - - - {assign var=payment_status_rendered value=true} - {else} - - - {/if} - - - - - {if $footer.amount_due > 0} - - {if !$payment_method_rendered} - - - {assign var=payment_method_rendered value=true} - {elseif !$payment_status_rendered} - - - {assign var=payment_status_rendered value=true} - {else} - - - {/if} - - - - - {/if} - -
{l s='PAYMENT INFORMATION' pdf='true'}{l s='TOTAL AMOUNT' pdf='true'}
{l s='Payment Method' pdf='true'}{$payment_method nofilter}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Total Rooms Cost (tax excl.)' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.room_price_tax_excl}
{l s='Payment Method' pdf='true'}{$payment_method|escape:'html':'UTF-8'}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Total Products cost (tax excl.)' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.service_products_price_tax_excl}
{l s='Payment Method' pdf='true'}{$payment_method|escape:'html':'UTF-8'}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Extra Services Cost (tax excl.)' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.additional_service_price_tax_excl}
{l s='Payment Method' pdf='true'}{$payment_method|escape:'html':'UTF-8'}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Convenience Fee (tax excl.)' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.total_convenience_fee_te}
{l s='Payment Method' pdf='true'}{$payment_method|escape:'html':'UTF-8'}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Total (Tax excl.)' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.total_without_discount_te}
{l s='Payment Method' pdf='true'}{$payment_method|escape:'html':'UTF-8'}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Total Tax' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.total_tax_without_discount}
{l s='Payment Method' pdf='true'}{$payment_method|escape:'html':'UTF-8'}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Total Discounts' pdf='true'}- {displayPrice currency=$order->id_currency price=$footer.product_discounts_tax_incl}
{l s='Payment Method' pdf='true'}{$payment_method|escape:'html':'UTF-8'}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Final Booking Amount' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.total_paid_tax_incl}
{l s='Payment Method' pdf='true'}{$payment_method|escape:'html':'UTF-8'}{l s='Payment Status' pdf='true'}{$payment_status|escape:'html':'UTF-8'}{l s='Due Amount' pdf='true'}{displayPrice currency=$order->id_currency price=$footer.amount_due}
- -{if $has_cancellation_policy} - - - - -
- - - - - @@ -386,26 +297,20 @@
{l s='CANCELLATION POLICY' pdf='true'}
{$cancellation_policy nofilter}
{/if} - - - - -
+
 
- + - - - + - + @@ -414,11 +319,9 @@
{l s='PROPERTY POLICIES' pdf='true'}
{l s='Check-in time' pdf='true'}{$check_in_time|escape:'html':'UTF-8'}{if $hotel && $hotel->check_in}{$hotel->check_in|escape:'html':'UTF-8'}{/if}
{l s='Check-out time' pdf='true'}{$check_out_time|escape:'html':'UTF-8'}{if $hotel && $hotel->check_out}{$hotel->check_out|escape:'html':'UTF-8'}{/if}
{l s='Policies' pdf='true'}
-{if isset($HOOK_DISPLAY_PDF) && $HOOK_DISPLAY_PDF} - - - - +{if isset($HOOK_DISPLAY_PDF) && $HOOK_DISPLAY_PDF|strip_tags|trim} +
 
+ From 476d3b5756bd26983e3ad6ac7c2ebc54e6cb78fb Mon Sep 17 00:00:00 2001 From: varun kewlani Date: Fri, 29 May 2026 20:06:53 +0530 Subject: [PATCH 18/35] Fixed: Show correct check-in and check-out time in order detail page and invoice based on date format selected from languages from back-office Co-authored-by: Name --- classes/pdf/HTMLTemplateInvoice.php | 15 +++++++++++++-- controllers/admin/AdminOrdersController.php | 20 ++++++++++++++++++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/classes/pdf/HTMLTemplateInvoice.php b/classes/pdf/HTMLTemplateInvoice.php index c23fdd280..7fd8aefae 100644 --- a/classes/pdf/HTMLTemplateInvoice.php +++ b/classes/pdf/HTMLTemplateInvoice.php @@ -363,6 +363,17 @@ class HTMLTemplateInvoiceCore extends HTMLTemplate $objBookingDemand = new HotelBookingDemands(); foreach ($order_bk_data as $data_k => $data_v) { $date_join = strtotime($data_v['date_from']).strtotime($data_v['date_to']); + $display_date_from = $data_v['date_from']; + $display_date_to = $data_v['date_to']; + $objHotelBranchInformation = new HotelBranchInformation($data_v['id_hotel']); + if ($objHotelBranchInformation->check_in) { + $display_date_from = date('Y-m-d', strtotime($data_v['date_from'])) + . ' ' . date('H:i:s', strtotime($objHotelBranchInformation->check_in)); + } + if ($objHotelBranchInformation->check_out) { + $display_date_to = date('Y-m-d', strtotime($data_v['date_to'])) + . ' ' . date('H:i:s', strtotime($objHotelBranchInformation->check_out)); + } /*Product price when order was created*/ $order_details_obj = new OrderDetail($data_v['id_order_detail']); @@ -474,8 +485,8 @@ class HTMLTemplateInvoiceCore extends HTMLTemplate $cart_htl_data[$type_key]['date_diff'][$date_join]['num_rm'] = 1; $fullDate = (isset($context->controller->show_full_date) && $context->controller->show_full_date && (date('Y-m-d', strtotime($data_v['date_from'])) == date('Y-m-d', strtotime($data_v['date_to'])))) ? true : false; - $cart_htl_data[$type_key]['date_diff'][$date_join]['data_form'] = Tools::displayDate($data_v['date_from'], null, $fullDate); - $cart_htl_data[$type_key]['date_diff'][$date_join]['data_to'] = Tools::displayDate($data_v['date_to'], null, $fullDate); + $cart_htl_data[$type_key]['date_diff'][$date_join]['data_form'] = Tools::displayDate($display_date_from, null, $fullDate); + $cart_htl_data[$type_key]['date_diff'][$date_join]['data_to'] = Tools::displayDate($display_date_to, null, $fullDate); $cart_htl_data[$type_key]['date_diff'][$date_join]['num_days'] = $num_days; $cart_htl_data[$type_key]['date_diff'][$date_join]['adults'] = $data_v['adults']; $cart_htl_data[$type_key]['date_diff'][$date_join]['children'] = $data_v['children']; diff --git a/controllers/admin/AdminOrdersController.php b/controllers/admin/AdminOrdersController.php index 8a3722b84..affff25da 100644 --- a/controllers/admin/AdminOrdersController.php +++ b/controllers/admin/AdminOrdersController.php @@ -3395,6 +3395,16 @@ class AdminOrdersControllerCore extends AdminController $objBookingDemand = new HotelBookingDemands(); $objHotelRoomType = new HotelRoomType(); foreach ($order_detail_data as $key => $value) { + $objHotelBranchInformation = new HotelBranchInformation($value['id_hotel']); + if ($objHotelBranchInformation->check_in) { + $order_detail_data[$key]['date_from'] = date('Y-m-d', strtotime($value['date_from'])) + . ' ' . date('H:i:s', strtotime($objHotelBranchInformation->check_in)); + } + if ($objHotelBranchInformation->check_out) { + $order_detail_data[$key]['date_to'] = date('Y-m-d', strtotime($value['date_to'])) + . ' ' . date('H:i:s', strtotime($objHotelBranchInformation->check_out)); + } + $order_detail_data[$key]['total_room_price_te'] = $value['total_price_tax_excl']; $order_detail_data[$key]['total_room_price_ti'] = $value['total_price_tax_incl']; @@ -3624,6 +3634,16 @@ class AdminOrdersControllerCore extends AdminController // get booking information by order $bookingOrderInfo = $objBookingDetail->getBookingDataByOrderId($order->id); foreach ($bookingOrderInfo as &$bookingOrderRoomInfo) { + $objHotelBranchInformation = new HotelBranchInformation($bookingOrderRoomInfo['id_hotel']); + if ($objHotelBranchInformation->check_in) { + $bookingOrderRoomInfo['date_from'] = date('Y-m-d', strtotime($bookingOrderRoomInfo['date_from'])) + . ' ' . date('H:i:s', strtotime($objHotelBranchInformation->check_in)); + } + if ($objHotelBranchInformation->check_out) { + $bookingOrderRoomInfo['date_to'] = date('Y-m-d', strtotime($bookingOrderRoomInfo['date_to'])) + . ' ' . date('H:i:s', strtotime($objHotelBranchInformation->check_out)); + } + // Get last refund request for booking if ($bookingRefundDetail = OrderReturn::getOrdersReturnDetail($bookingOrderRoomInfo['id_order'], 0, $bookingOrderRoomInfo['id'])) { $bookingRefundDetail = reset($bookingRefundDetail); From c3abb0d067c7f69c8775db208f447f08f378ce78 Mon Sep 17 00:00:00 2001 From: varun kewlani Date: Tue, 2 Jun 2026 16:30:34 +0530 Subject: [PATCH 19/35] Updated: spacing issue --- .../hotelreservationsystem/views/templates/hook/copyRight.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/hotelreservationsystem/views/templates/hook/copyRight.tpl b/modules/hotelreservationsystem/views/templates/hook/copyRight.tpl index 15906d128..eb3541799 100644 --- a/modules/hotelreservationsystem/views/templates/hook/copyRight.tpl +++ b/modules/hotelreservationsystem/views/templates/hook/copyRight.tpl @@ -1,5 +1,5 @@
\ No newline at end of file From d3d0ba26ee336871319cb47abe3ce7fc8d7a5c5e Mon Sep 17 00:00:00 2001 From: varun kewlani Date: Tue, 2 Jun 2026 18:56:53 +0530 Subject: [PATCH 20/35] Fixed: Display dynamic check-in and check-out times in invoice PDF based on room booking status, and remove changes from order detail page Co-authored-by: Name --- classes/pdf/HTMLTemplateInvoice.php | 40 +++++++++++++-------- controllers/admin/AdminOrdersController.php | 20 ----------- 2 files changed, 26 insertions(+), 34 deletions(-) diff --git a/classes/pdf/HTMLTemplateInvoice.php b/classes/pdf/HTMLTemplateInvoice.php index 7fd8aefae..7c79514f7 100644 --- a/classes/pdf/HTMLTemplateInvoice.php +++ b/classes/pdf/HTMLTemplateInvoice.php @@ -363,17 +363,6 @@ class HTMLTemplateInvoiceCore extends HTMLTemplate $objBookingDemand = new HotelBookingDemands(); foreach ($order_bk_data as $data_k => $data_v) { $date_join = strtotime($data_v['date_from']).strtotime($data_v['date_to']); - $display_date_from = $data_v['date_from']; - $display_date_to = $data_v['date_to']; - $objHotelBranchInformation = new HotelBranchInformation($data_v['id_hotel']); - if ($objHotelBranchInformation->check_in) { - $display_date_from = date('Y-m-d', strtotime($data_v['date_from'])) - . ' ' . date('H:i:s', strtotime($objHotelBranchInformation->check_in)); - } - if ($objHotelBranchInformation->check_out) { - $display_date_to = date('Y-m-d', strtotime($data_v['date_to'])) - . ' ' . date('H:i:s', strtotime($objHotelBranchInformation->check_out)); - } /*Product price when order was created*/ $order_details_obj = new OrderDetail($data_v['id_order_detail']); @@ -484,9 +473,32 @@ class HTMLTemplateInvoiceCore extends HTMLTemplate $num_days = HotelHelper::getNumberOfDays($data_v['date_from'], $data_v['date_to']); $cart_htl_data[$type_key]['date_diff'][$date_join]['num_rm'] = 1; - $fullDate = (isset($context->controller->show_full_date) && $context->controller->show_full_date && (date('Y-m-d', strtotime($data_v['date_from'])) == date('Y-m-d', strtotime($data_v['date_to'])))) ? true : false; - $cart_htl_data[$type_key]['date_diff'][$date_join]['data_form'] = Tools::displayDate($display_date_from, null, $fullDate); - $cart_htl_data[$type_key]['date_diff'][$date_join]['data_to'] = Tools::displayDate($display_date_to, null, $fullDate); + $display_date_from = $data_v['date_from']; + $display_date_to = $data_v['date_to']; + if ((int)$data_v['id_status'] === HotelBookingDetail::STATUS_CHECKED_OUT) { + if ($data_v['check_in']) { + $display_date_from = $data_v['check_in']; + } + if ($data_v['check_out']) { + $display_date_to = $data_v['check_out']; + } + } elseif ((int)$data_v['id_status'] === HotelBookingDetail::STATUS_CHECKED_IN) { + if ($data_v['check_in']) { + $display_date_from = $data_v['check_in']; + } + if ($data_v['check_out_time']) { + $display_date_to = date('Y-m-d', strtotime($data_v['date_to'])) . ' ' . date('H:i:s', strtotime($data_v['check_out_time'])); + } + } else { + if ($data_v['check_in_time']) { + $display_date_from = date('Y-m-d', strtotime($data_v['date_from'])) . ' ' . date('H:i:s', strtotime($data_v['check_in_time'])); + } + if ($data_v['check_out_time']) { + $display_date_to = date('Y-m-d', strtotime($data_v['date_to'])) . ' ' . date('H:i:s', strtotime($data_v['check_out_time'])); + } + } + ppp($cart_htl_data[$type_key]['date_diff'][$date_join]['data_form'] = Tools::displayDate($display_date_from, null, true)); + ppp($cart_htl_data[$type_key]['date_diff'][$date_join]['data_to'] = Tools::displayDate($display_date_to, null, true)); $cart_htl_data[$type_key]['date_diff'][$date_join]['num_days'] = $num_days; $cart_htl_data[$type_key]['date_diff'][$date_join]['adults'] = $data_v['adults']; $cart_htl_data[$type_key]['date_diff'][$date_join]['children'] = $data_v['children']; diff --git a/controllers/admin/AdminOrdersController.php b/controllers/admin/AdminOrdersController.php index affff25da..8a3722b84 100644 --- a/controllers/admin/AdminOrdersController.php +++ b/controllers/admin/AdminOrdersController.php @@ -3395,16 +3395,6 @@ class AdminOrdersControllerCore extends AdminController $objBookingDemand = new HotelBookingDemands(); $objHotelRoomType = new HotelRoomType(); foreach ($order_detail_data as $key => $value) { - $objHotelBranchInformation = new HotelBranchInformation($value['id_hotel']); - if ($objHotelBranchInformation->check_in) { - $order_detail_data[$key]['date_from'] = date('Y-m-d', strtotime($value['date_from'])) - . ' ' . date('H:i:s', strtotime($objHotelBranchInformation->check_in)); - } - if ($objHotelBranchInformation->check_out) { - $order_detail_data[$key]['date_to'] = date('Y-m-d', strtotime($value['date_to'])) - . ' ' . date('H:i:s', strtotime($objHotelBranchInformation->check_out)); - } - $order_detail_data[$key]['total_room_price_te'] = $value['total_price_tax_excl']; $order_detail_data[$key]['total_room_price_ti'] = $value['total_price_tax_incl']; @@ -3634,16 +3624,6 @@ class AdminOrdersControllerCore extends AdminController // get booking information by order $bookingOrderInfo = $objBookingDetail->getBookingDataByOrderId($order->id); foreach ($bookingOrderInfo as &$bookingOrderRoomInfo) { - $objHotelBranchInformation = new HotelBranchInformation($bookingOrderRoomInfo['id_hotel']); - if ($objHotelBranchInformation->check_in) { - $bookingOrderRoomInfo['date_from'] = date('Y-m-d', strtotime($bookingOrderRoomInfo['date_from'])) - . ' ' . date('H:i:s', strtotime($objHotelBranchInformation->check_in)); - } - if ($objHotelBranchInformation->check_out) { - $bookingOrderRoomInfo['date_to'] = date('Y-m-d', strtotime($bookingOrderRoomInfo['date_to'])) - . ' ' . date('H:i:s', strtotime($objHotelBranchInformation->check_out)); - } - // Get last refund request for booking if ($bookingRefundDetail = OrderReturn::getOrdersReturnDetail($bookingOrderRoomInfo['id_order'], 0, $bookingOrderRoomInfo['id'])) { $bookingRefundDetail = reset($bookingRefundDetail); From 757c72de09d6ab1c732dc75082468488586b0439 Mon Sep 17 00:00:00 2001 From: varun kewlani Date: Tue, 2 Jun 2026 19:05:51 +0530 Subject: [PATCH 21/35] Removed: extra code for debugging Co-authored-by: Name --- classes/pdf/HTMLTemplateInvoice.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/classes/pdf/HTMLTemplateInvoice.php b/classes/pdf/HTMLTemplateInvoice.php index 7c79514f7..e5b36b518 100644 --- a/classes/pdf/HTMLTemplateInvoice.php +++ b/classes/pdf/HTMLTemplateInvoice.php @@ -473,6 +473,7 @@ class HTMLTemplateInvoiceCore extends HTMLTemplate $num_days = HotelHelper::getNumberOfDays($data_v['date_from'], $data_v['date_to']); $cart_htl_data[$type_key]['date_diff'][$date_join]['num_rm'] = 1; + $fullDate = (isset($context->controller->show_full_date) && $context->controller->show_full_date && (date('Y-m-d', strtotime($data_v['date_from'])) == date('Y-m-d', strtotime($data_v['date_to'])))) ? true : false; $display_date_from = $data_v['date_from']; $display_date_to = $data_v['date_to']; if ((int)$data_v['id_status'] === HotelBookingDetail::STATUS_CHECKED_OUT) { @@ -497,8 +498,8 @@ class HTMLTemplateInvoiceCore extends HTMLTemplate $display_date_to = date('Y-m-d', strtotime($data_v['date_to'])) . ' ' . date('H:i:s', strtotime($data_v['check_out_time'])); } } - ppp($cart_htl_data[$type_key]['date_diff'][$date_join]['data_form'] = Tools::displayDate($display_date_from, null, true)); - ppp($cart_htl_data[$type_key]['date_diff'][$date_join]['data_to'] = Tools::displayDate($display_date_to, null, true)); + $cart_htl_data[$type_key]['date_diff'][$date_join]['data_form'] = Tools::displayDate($display_date_from, null, true); + $cart_htl_data[$type_key]['date_diff'][$date_join]['data_to'] = Tools::displayDate($display_date_to, null, true); $cart_htl_data[$type_key]['date_diff'][$date_join]['num_days'] = $num_days; $cart_htl_data[$type_key]['date_diff'][$date_join]['adults'] = $data_v['adults']; $cart_htl_data[$type_key]['date_diff'][$date_join]['children'] = $data_v['children']; From 65cd0ff1edb22fb15f479ecf1697029c09340289 Mon Sep 17 00:00:00 2001 From: Amit sharma Date: Wed, 3 Jun 2026 19:38:26 +0530 Subject: [PATCH 22/35] fix: resolve invalid price display on product page for catalog rule base price --- classes/Cart.php | 2 +- classes/SpecificPrice.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/classes/Cart.php b/classes/Cart.php index ad5ee4302..f12f41249 100644 --- a/classes/Cart.php +++ b/classes/Cart.php @@ -1833,7 +1833,7 @@ class CartCore extends ObjectModel $this->id, [], 0, - isset($product['id_hotel_cart_booking']) ? $product['id_hotel_cart_booking'] : null, + null, null, (int)$product['id_product'], null, diff --git a/classes/SpecificPrice.php b/classes/SpecificPrice.php index 1dd3829a2..79c63995e 100644 --- a/classes/SpecificPrice.php +++ b/classes/SpecificPrice.php @@ -367,12 +367,12 @@ class SpecificPriceCore extends ObjectModel `id_shop` '.self::formatIntInQuery(0, $id_shop).' AND `id_currency` '.self::formatIntInQuery(0, $id_currency).' AND `id_country` '.self::formatIntInQuery(0, $id_country). - ' AND `id_htl_cart_booking`='.(int)$id_htl_cart_booking.' '. + ' AND `id_htl_cart_booking` '.self::formatIntInQuery(0, $id_htl_cart_booking).' '. (Group::isFeatureActive() ? ' AND `id_group` '.self::formatIntInQuery(0, $id_group) : '').' '.$query_extra.' AND IF(`from_quantity` > 1, `from_quantity`, 0) <= '; $query .= (Configuration::get('PS_QTY_DISCOUNT_ON_COMBINATION') || !$id_cart || !$real_quantity) ? (int)$quantity : max(1, (int)$real_quantity); - $query .= ' ORDER BY `id_product_attribute` DESC, `from_quantity` DESC, `score` DESC, `to` DESC, `from` DESC'; + $query .= ' ORDER BY `id_htl_cart_booking` DESC, `id_product_attribute` DESC, `from_quantity` DESC, `score` DESC, `to` DESC, `from` DESC'; SpecificPrice::$_specificPriceCache[$key] = Db::getInstance(_PS_USE_SQL_SLAVE_)->getRow($query); } From 2bd11e0e99427a5836728b41c6030000cf9cfa0a Mon Sep 17 00:00:00 2001 From: varun kewlani Date: Thu, 4 Jun 2026 16:23:01 +0530 Subject: [PATCH 23/35] Fixed: advance payment was added for the number of rooms added in cart in for loop which lead to Incorrect Total Amount Calculation Co-authored-by: Name --- classes/Cart.php | 90 +++++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 44 deletions(-) diff --git a/classes/Cart.php b/classes/Cart.php index ad5ee4302..9cf5a1caf 100644 --- a/classes/Cart.php +++ b/classes/Cart.php @@ -1862,53 +1862,55 @@ class CartCore extends ObjectModel // by webkul to calculate rates of the product from hotelreservation syatem tables with feature prices.... $totalPriceByProduct = 0; $priceDisplay = Group::getPriceDisplayMethod(Group::getCurrent()->id); - foreach ($roomTypesByIdProduct as $key => $cartRoomInfo) { - $occupancy = array( - array( - 'adults' => $cartRoomInfo['adults'], - 'children' => $cartRoomInfo['children'], - 'child_ages' => json_decode($cartRoomInfo['child_ages']) - ) + + if ($type == Cart::ADVANCE_PAYMENT || $type == Cart::ADVANCE_PAYMENT_ONLY_PRODUCTS) { + // getProductMinAdvPaymentAmountByIdCart already aggregates all rooms for this product internally, + // so call it once and add once — not inside the per-room loop. + $advProductPrice = $objAdvPayment->getProductMinAdvPaymentAmountByIdCart( + $this->id, + $product['id_product'], + 0, + 0, + $with_taxes ); - // get the real price of the room type - $roomTotalPrice = HotelRoomTypeFeaturePricing::getRoomTypeTotalPrice( - $cartRoomInfo['id_product'], - $cartRoomInfo['date_from'], - $cartRoomInfo['date_to'], - $occupancy, - Group::getCurrent()->id, - $cartRoomInfo['id_cart'], - $cartRoomInfo['id_guest'], - $cartRoomInfo['id_room'], - 0 - ); - if ($with_taxes) { - $totalPriceByProduct = $roomTotalPrice['total_price_tax_incl']; - } else { - $totalPriceByProduct = $roomTotalPrice['total_price_tax_excl']; - } - - // If customer has selected advance payment of the cart - if ($type == Cart::ADVANCE_PAYMENT || $type == Cart::ADVANCE_PAYMENT_ONLY_PRODUCTS) { - $advProductPrice = $objAdvPayment->getProductMinAdvPaymentAmountByIdCart( - $this->id, - $cartRoomInfo['id_product'], - 0, - 0, - $with_taxes - ); - - // set advance payment price only if it is greater than real price - if ($advProductPrice < $totalPriceByProduct) { - $totalPriceByProduct = $advProductPrice; - } - } - - // Rounding as per configurations if ($ps_round_type == Order::ROUND_TOTAL) { - $products_total[$id_tax_rules_group.'_'.$id_address] += Tools::processPriceRounding($totalPriceByProduct); + $products_total[$id_tax_rules_group.'_'.$id_address] += Tools::processPriceRounding($advProductPrice); } else { - $products_total[$id_tax_rules_group] += Tools::processPriceRounding($totalPriceByProduct); + $products_total[$id_tax_rules_group] += Tools::processPriceRounding($advProductPrice); + } + } else { + foreach ($roomTypesByIdProduct as $key => $cartRoomInfo) { + $occupancy = array( + array( + 'adults' => $cartRoomInfo['adults'], + 'children' => $cartRoomInfo['children'], + 'child_ages' => json_decode($cartRoomInfo['child_ages']) + ) + ); + // get the real price of the room type + $roomTotalPrice = HotelRoomTypeFeaturePricing::getRoomTypeTotalPrice( + $cartRoomInfo['id_product'], + $cartRoomInfo['date_from'], + $cartRoomInfo['date_to'], + $occupancy, + Group::getCurrent()->id, + $cartRoomInfo['id_cart'], + $cartRoomInfo['id_guest'], + $cartRoomInfo['id_room'], + 0 + ); + if ($with_taxes) { + $totalPriceByProduct = $roomTotalPrice['total_price_tax_incl']; + } else { + $totalPriceByProduct = $roomTotalPrice['total_price_tax_excl']; + } + + // Rounding as per configurations + if ($ps_round_type == Order::ROUND_TOTAL) { + $products_total[$id_tax_rules_group.'_'.$id_address] += Tools::processPriceRounding($totalPriceByProduct); + } else { + $products_total[$id_tax_rules_group] += Tools::processPriceRounding($totalPriceByProduct); + } } } } From 0dc272fbff950b51f40f4b2db087d478290ac5e0 Mon Sep 17 00:00:00 2001 From: Amit sharma Date: Thu, 4 Jun 2026 17:00:19 +0530 Subject: [PATCH 24/35] fix: preserve service product price in cart total after occupancy update --- controllers/admin/AdminCartsController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/admin/AdminCartsController.php b/controllers/admin/AdminCartsController.php index f76331422..8252c7fa7 100644 --- a/controllers/admin/AdminCartsController.php +++ b/controllers/admin/AdminCartsController.php @@ -1121,7 +1121,7 @@ class AdminCartsControllerCore extends AdminController if ($bookingInfo['id'] == $idBookingData) { $amtWithQty = $bookingInfo['amt_with_qty']; $bookingInfo['amt_with_qty'] = Tools::displayPrice($amtWithQty); - $bookingInfo['total_price'] = Tools::displayPrice($amtWithQty + $bookingInfo['demand_price']); + $bookingInfo['total_price'] = Tools::displayPrice($amtWithQty + $bookingInfo['demand_price'] + $bookingInfo['additional_service_price'] + $bookingInfo['additional_services_auto_add_price']); $response = array( 'curr_booking_info' => $bookingInfo, 'cart_info' => $this->ajaxReturnVars(), From 041fa626b0a9460401e698871f0f642f0905f1b8 Mon Sep 17 00:00:00 2001 From: varun kewlani Date: Thu, 4 Jun 2026 17:25:14 +0530 Subject: [PATCH 25/35] Fixed: show serial wise room number in Room Extra Services Detail in invoice pdf Co-authored-by: Name --- pdf/invoice.extra-demands-tab.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pdf/invoice.extra-demands-tab.tpl b/pdf/invoice.extra-demands-tab.tpl index e7edf7fa4..cba5a8aed 100644 --- a/pdf/invoice.extra-demands-tab.tpl +++ b/pdf/invoice.extra-demands-tab.tpl @@ -114,8 +114,8 @@ {/foreach} {/if} + {assign var=roomCount value=$roomCount+1} {/foreach} - {assign var=roomCount value=$roomCount+1}
{$HOOK_DISPLAY_PDF}
From 4b1f340b319d7bd856a02cb9695de3410d7a2d0b Mon Sep 17 00:00:00 2001 From: Amit sharma Date: Thu, 4 Jun 2026 18:24:10 +0530 Subject: [PATCH 26/35] fix: make rating field optional for hotels --- .../hotelreservationsystem/classes/HotelBranchInformation.php | 4 ++-- .../controllers/admin/AdminAddHotelController.php | 4 ---- .../views/templates/admin/add_hotel/helpers/form/form.tpl | 4 ++-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/modules/hotelreservationsystem/classes/HotelBranchInformation.php b/modules/hotelreservationsystem/classes/HotelBranchInformation.php index b4fb0ada8..0797c7035 100644 --- a/modules/hotelreservationsystem/classes/HotelBranchInformation.php +++ b/modules/hotelreservationsystem/classes/HotelBranchInformation.php @@ -51,7 +51,7 @@ class HotelBranchInformation extends ObjectModel 'fields' => array( 'id_category' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'), 'email' => array('type' => self::TYPE_STRING,'validate' => 'isEmail', 'size' => 255, 'required' => true), - 'rating' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true), + 'rating' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'), 'check_in' => array('type' => self::TYPE_STRING, 'required' => true), 'check_out' => array('type' => self::TYPE_STRING, 'required' => true), 'active' => array('type' => self::TYPE_BOOL, 'validate' => 'isBool'), @@ -1160,7 +1160,7 @@ class HotelBranchInformation extends ObjectModel { // set additional hotel address and restriction fields validations here if (isset($this->webservice_validation) && $this->webservice_validation) { - if ($this->rating < 1 || $this->rating > 5) { + if ($this->rating && ($this->rating < 1 || $this->rating > 5)) { $message = Tools::displayError('Rating must be between 1 and 5.'); } elseif (!strtotime($this->check_in)) { $message = Tools::displayError('Check In time is invalid.'); diff --git a/modules/hotelreservationsystem/controllers/admin/AdminAddHotelController.php b/modules/hotelreservationsystem/controllers/admin/AdminAddHotelController.php index f804b11ba..868012630 100644 --- a/modules/hotelreservationsystem/controllers/admin/AdminAddHotelController.php +++ b/modules/hotelreservationsystem/controllers/admin/AdminAddHotelController.php @@ -391,10 +391,6 @@ class AdminAddHotelController extends ModuleAdminController $this->errors[] = $this->l('Check Out time must be before Check In time.'); } - if (!$rating) { - $this->errors[] = $this->l('Rating is required field.'); - } - if (!$address = trim($address)) { $this->errors[] = $this->l('Address is required field.'); } diff --git a/modules/hotelreservationsystem/views/templates/admin/add_hotel/helpers/form/form.tpl b/modules/hotelreservationsystem/views/templates/admin/add_hotel/helpers/form/form.tpl index 22ae63e61..978872548 100644 --- a/modules/hotelreservationsystem/views/templates/admin/add_hotel/helpers/form/form.tpl +++ b/modules/hotelreservationsystem/views/templates/admin/add_hotel/helpers/form/form.tpl @@ -214,11 +214,11 @@

- +
+ {if !empty($tab.has_kpi)} + +   » {l s='KPI'} + + + + + + + + + + + {/if} {foreach $tabs AS $child} {if $child.id_parent === $tab.id_tab} {if isset($access[$child.id_tab])} @@ -372,6 +439,32 @@ + {if !empty($child.has_kpi)} + +   » {l s='KPI'} + + + + + + + + + + + {/if} {/if} {/if} {/foreach} @@ -438,4 +531,4 @@
{/foreach} -
+
\ No newline at end of file diff --git a/admin/themes/default/template/controllers/customer_threads/helpers/view/view.tpl b/admin/themes/default/template/controllers/customer_threads/helpers/view/view.tpl index b42bea915..471eeb0ba 100644 --- a/admin/themes/default/template/controllers/customer_threads/helpers/view/view.tpl +++ b/admin/themes/default/template/controllers/customer_threads/helpers/view/view.tpl @@ -36,6 +36,7 @@ {/if} + {if $can_edit}
{foreach $actions as $action} @@ -48,6 +49,7 @@
+ {/if}
@@ -97,6 +99,7 @@ {/foreach}
+{if $can_edit}

{l s="Your answer to"} {if isset($customer->firstname)}{$customer->firstname|escape:'html':'UTF-8'} {$customer->lastname|escape:'html':'UTF-8'} {else} {$thread->email}{/if}

@@ -125,7 +128,7 @@
- +{/if} {if count($timeline_items)}

diff --git a/admin/themes/default/template/controllers/modules/js.tpl b/admin/themes/default/template/controllers/modules/js.tpl index d91c1b3bb..79097c46a 100644 --- a/admin/themes/default/template/controllers/modules/js.tpl +++ b/admin/themes/default/template/controllers/modules/js.tpl @@ -48,21 +48,65 @@ function modules_management(action) { var modules = document.getElementsByName('modules'); - var module_list = ''; + var module_list = []; for (var i = 0; i < modules.length; i++) { - if (modules[i].checked == true) + if (modules[i].checked) { - rel = modules[i].getAttribute('data-rel'); + var rel = modules[i].getAttribute('data-rel'); if (rel != "false" && action == "uninstall") { if (!confirm(rel)) return false; } - module_list += '|'+modules[i].value; + module_list.push(modules[i].value); } } - document.location.href=currentIndex+'&token='+token+'&'+action+'='+module_list.substring(1, module_list.length); + + module_list = encodeURIComponent(module_list.join('|')); + if (action == 'enable' || action == 'disable') { + var type = action == 'disable' ? 0 : 1; + document.location.href = currentIndex+'&token='+token+'&enable='+type+'&module_name='+module_list; + return false; + } + + document.location.href = currentIndex+'&token='+token+'&'+action+'='+module_list; + return false; + } + + function filter_modules_by_name(table, moduleName) + { + var moduleNames = moduleName.split('|'); + var filteredNames = []; + for (var i = 0; i < moduleNames.length; i++) { + moduleNames[i] = $.trim(moduleNames[i]); + if (moduleNames[i] !== '') { + filteredNames.push(moduleNames[i].toLowerCase()); + } + } + + if (!filteredNames.length) { + return; + } + + if (filteredNames.length === 1) { + $.uiTableFilter(table, filteredNames[0]); + return; + } + + table.find('tbody tr').each(function() { + var rowText = $(this).text().toLowerCase(); + var isMatch = false; + + for (var i = 0; i < filteredNames.length; i++) { + if (rowText.indexOf(filteredNames[i]) !== -1) { + isMatch = true; + break; + } + } + + $(this).toggle(isMatch); + }); } $('document').ready( function() { @@ -71,7 +115,7 @@ $.uiTableFilter($('#moduleContainer').find('table'), anchor); if (module_name != '') - $.uiTableFilter($('#moduleContainer').find('table'), module_name); + filter_modules_by_name($('#moduleContainer').find('table'), module_name); $('#moduleQuicksearch').on('keyup', function(){ val = this.value; diff --git a/admin/themes/default/template/controllers/modules/list.tpl b/admin/themes/default/template/controllers/modules/list.tpl index 6166962a4..9fab8630b 100644 --- a/admin/themes/default/template/controllers/modules/list.tpl +++ b/admin/themes/default/template/controllers/modules/list.tpl @@ -190,6 +190,19 @@

@@ -44,7 +44,7 @@
{include file="controllers/products/multishop/checkbox.tpl" field="meta_description" type="default" multilang="true"}
@@ -53,7 +53,7 @@ languages=$languages input_name='meta_description' input_value=$product->meta_description - maxchar=160 + maxchar=255 } @@ -91,16 +91,11 @@ -
-
- {foreach from=$languages item=language} -
- {l s='The product link will look like this:'}
- {if isset($rewritten_links[$language.id_lang][0])}{$rewritten_links[$language.id_lang][0]|escape:'html':'UTF-8'}{/if}{if isset($product->id)}{$product->link_rewrite[$language.id_lang]|escape:'html':'UTF-8'}{/if}{if isset($rewritten_links[$language.id_lang][1])}{$rewritten_links[$language.id_lang][1]|escape:'html':'UTF-8'}{/if} -
- {/foreach} -
-
+ {include file="seo_preview.tpl" + languages = $languages + preview_link = $rewritten_links|default:'' + inputs = ['meta_title'=>$product->meta_title ,'meta_description' => $product->meta_description,'link_rewrite' => $product->link_rewrite, 'name' => $product->name, 'description_short' => $product->description_short] + }
@@ -61,6 +61,10 @@ {/foreach} }); {/if} - $(".textarea-autosize").autosize(); + $(".textarea-autosize").each(function() { + var $ta = $(this), val = $ta.val(); + $ta.val('').autosize(); + if (val) { $ta.val(val).trigger('input'); } + }); diff --git a/admin/themes/default/template/controllers/orders/_cart_booking_demands.tpl b/admin/themes/default/template/controllers/orders/_cart_booking_demands.tpl index c07968f9d..fad6ca46e 100644 --- a/admin/themes/default/template/controllers/orders/_cart_booking_demands.tpl +++ b/admin/themes/default/template/controllers/orders/_cart_booking_demands.tpl @@ -32,9 +32,7 @@ {if isset($selectedRoomDemands) && $selectedRoomDemands} {/if} - {if isset($serviceProducts) && $serviceProducts} -
  • {l s='Services'}
  • - {/if} +
  • {l s='Services'}
  • {if isset($selectedRoomDemands) && $selectedRoomDemands} diff --git a/admin/themes/default/template/controllers/orders/form.tpl b/admin/themes/default/template/controllers/orders/form.tpl index 0451cd31d..1482462e9 100644 --- a/admin/themes/default/template/controllers/orders/form.tpl +++ b/admin/themes/default/template/controllers/orders/form.tpl @@ -1572,6 +1572,7 @@ id_room: idRoom, id_cart: idCart, id_product: idProduct, + id_customer: id_customer, id_hotel_cart_booking: idHotelCartBooking, action: 'getRoomTypeCartDemands', ajax: true @@ -1877,7 +1878,7 @@ {l s='Customer'}
    -
    +
    id_customer) && $cart->id_customer && isset($customer) && $customer }style="display:none;"{/if} class="form-group">
    -
    +
    + {if isset($cart->id_customer) && $cart->id_customer && isset($customer) && $customer } +
    +
    +
    + {$customer->firstname} {$customer->lastname} #{$customer->id} +
    + {$customer->email}
    +
    + +
    +
    + {/if} +
    {*
    {include file='controllers/orders/_current_cart_details_data.tpl'} - @@ -451,7 +456,7 @@ {l s='Transaction ID'} {l s='Amount'} {l s='Invoice'} - + @@ -463,6 +468,11 @@ {$payment['transaction_id']|escape:'html':'UTF-8'} {displayPrice price=$payment['real_paid_amount'] currency=$payment['id_currency']} {if isset($payment['invoice_number'])}{$payment['invoice_number']}{else}--{/if} + {if isset($receipt_management_active) && $receipt_management_active} + + {l s='Receipt'} + + {/if} {l s='Details'} @@ -707,7 +717,7 @@ {if isset($messages) && $messages}
    -  {l s='Order Private Notes'} +  {l s='Order Notes'}
    {foreach from=$messages item=message name=customerMessage} @@ -720,11 +730,11 @@ {$message['elastname']|escape:'html':'UTF-8'}{else}{$message['cfirstname']|escape:'html':'UTF-8'} {$message['clastname']|escape:'html':'UTF-8'} {/if},   - {dateFormat date=$message['date_add']} + {dateFormat date=$message.date_add full=1} - {if ($message['private'] == 1)} + {* {if ($message['private'] == 1)} {l s='Private'} - {/if} + {/if} *}

    {if !$smarty.foreach.customerMessage.last}
    {/if} @@ -1302,7 +1312,30 @@ {/if}
    - + {* Css for handling order disable status modal *} + + {strip} {addJsDefL name=no_rm_avail_txt}{l s='No room available.' js=1}{/addJsDefL} {addJsDefL name=no_realloc_rm_avail_txt}{l s='No room available for reallocation.' js=1}{/addJsDefL} @@ -1335,7 +1368,6 @@ {addJsDefL name='invalid_occupancy_txt'}{l s='Invalid occupancy(adults/children) found.' js=1}{/addJsDefL} {addJsDefL name='select_room_txt'}{l s='Select room' js=1}{/addJsDefL} {addJsDef max_child_age=$max_child_age|escape:'quotes':'UTF-8'} - {addJsDef max_child_in_room=$max_child_in_room|escape:'quotes':'UTF-8'} {addJsDef ROOM_STATUS_CHECKED_IN=$ROOM_STATUS_CHECKED_IN|escape:'quotes':'UTF-8'} {addJsDef ROOM_STATUS_CHECKED_OUT=$ROOM_STATUS_CHECKED_OUT|escape:'quotes':'UTF-8'} {addJsDef ALLOTMENT_MANUAL=$ALLOTMENT_MANUAL|escape:'quotes':'UTF-8'} @@ -1346,4 +1378,4 @@ {addJsDefL name=txt_booking_document_delete_success}{l s='Document deleted successfully.' js=1}{/addJsDefL} {addJsDefL name='error_found_txt'}{l s='Errors found' js=1}{/addJsDefL} {/strip} -{/block} +{/block} \ No newline at end of file diff --git a/admin/themes/default/template/controllers/orders/modals/_add_room_booking.tpl b/admin/themes/default/template/controllers/orders/modals/_add_room_booking.tpl index b9e6524cf..c9d897d84 100644 --- a/admin/themes/default/template/controllers/orders/modals/_add_room_booking.tpl +++ b/admin/themes/default/template/controllers/orders/modals/_add_room_booking.tpl @@ -128,6 +128,12 @@ {/if} +
    +
    + + +
    +
    {if isset($invoices_collection) && sizeof($invoices_collection)} +
    +
    + + +
    +
    + +
    +
    + + +
    +
    - \ No newline at end of file diff --git a/admin/themes/default/template/controllers/products/helpers/form/form.tpl b/admin/themes/default/template/controllers/products/helpers/form/form.tpl index 5a97a1cec..6b1030882 100644 --- a/admin/themes/default/template/controllers/products/helpers/form/form.tpl +++ b/admin/themes/default/template/controllers/products/helpers/form/form.tpl @@ -218,6 +218,7 @@ $('.product-tab-content').on('displayed', function(e) { $('#toolbar-footer').appendTo($(this).children('.product-tab')); + $(window).trigger('resize.autosize'); }); }); diff --git a/admin/themes/default/template/controllers/products/informations.tpl b/admin/themes/default/template/controllers/products/informations.tpl index cb97d2652..c8716ea36 100644 --- a/admin/themes/default/template/controllers/products/informations.tpl +++ b/admin/themes/default/template/controllers/products/informations.tpl @@ -104,7 +104,7 @@
    {include file="controllers/products/multishop/checkbox.tpl" field="name" type="default" multilang="true"}
    diff --git a/admin/themes/default/template/controllers/products/input_text_lang.tpl b/admin/themes/default/template/controllers/products/input_text_lang.tpl index 81f63038a..7f92a362c 100644 --- a/admin/themes/default/template/controllers/products/input_text_lang.tpl +++ b/admin/themes/default/template/controllers/products/input_text_lang.tpl @@ -39,7 +39,6 @@ class="form-control {if isset($input_class)}{$input_class} {/if}" name="{$input_name}_{$language.id_lang}" value="{$input_value[$language.id_lang]|default:''|escape:'html'}" - onkeyup="if (isArrowKey(event)) return ;updateFriendlyURL();" onblur="updateLinkRewrite();" {if isset($required)} required="required"{/if} {if isset($maxchar)} data-maxchar="{$maxchar}"{/if} diff --git a/admin/themes/default/template/controllers/products/seo.tpl b/admin/themes/default/template/controllers/products/seo.tpl index 8544f75a1..062d3ee5a 100644 --- a/admin/themes/default/template/controllers/products/seo.tpl +++ b/admin/themes/default/template/controllers/products/seo.tpl @@ -30,7 +30,7 @@
    {include file="controllers/products/multishop/checkbox.tpl" field="meta_title" type="default" multilang="true"}
    @@ -39,7 +39,7 @@ languages=$languages input_name='meta_title' input_value=$product->meta_title - maxchar=70 + maxchar=128 }
    @@ -47,7 +47,7 @@
    {include file="controllers/products/multishop/checkbox.tpl" field="meta_description" type="default" multilang="true"}
    @@ -56,7 +56,7 @@ languages=$languages input_name='meta_description' input_value=$product->meta_description - maxchar=160 + maxchar=255 } @@ -94,16 +94,11 @@ -
    -
    - {foreach from=$languages item=language} -
    - {l s='The product link will look like this:'}
    - {if isset($rewritten_links[$language.id_lang][0])}{$rewritten_links[$language.id_lang][0]|escape:'html':'UTF-8'}{/if}{if isset($product->id)}{$product->link_rewrite[$language.id_lang]|escape:'html':'UTF-8'}{/if}{if isset($rewritten_links[$language.id_lang][1])}{$rewritten_links[$language.id_lang][1]|escape:'html':'UTF-8'}{/if} -
    - {/foreach} -
    -
    + {include file="seo_preview.tpl" + languages = $languages + preview_link = $rewritten_links|default:'' + inputs = ['meta_title'=>$product->meta_title ,'meta_description' => $product->meta_description,'link_rewrite' => $product->link_rewrite, 'name' => $product->name, 'description_short' => $product->description_short] + } @@ -64,6 +64,10 @@ {/foreach} }); {/if} - $(".textarea-autosize").autosize(); + $(".textarea-autosize").each(function() { + var $ta = $(this), val = $ta.val(); + $ta.val('').autosize(); + if (val) { $ta.val(val).trigger('input'); } + }); diff --git a/admin/themes/default/template/header.tpl b/admin/themes/default/template/header.tpl index 148be94a4..bb6f6f97f 100644 --- a/admin/themes/default/template/header.tpl +++ b/admin/themes/default/template/header.tpl @@ -298,6 +298,18 @@ {/if}