Files
hotel/modules/hotelreservationsystem/classes/ServiceProductOrderDetail.php
Rohit Jain bdb420d36f Merge pull request #1786 from varunkewlani322/gli-2942
Added: No Show and Cancellation booking status in back-office, and updated refund request management
2026-09-11 17:16:12 +05:30

896 lines
46 KiB
PHP

<?php
/**
* 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
*/
class ServiceProductOrderDetail extends ObjectModel
{
public $id_product;
public $id_order;
public $id_order_detail;
public $id_cart;
public $id_hotel;
public $id_htl_booking_detail;
public $id_product_option;
public $tax_computation_method;
public $id_tax_rules_group;
public $unit_price_tax_excl;
public $unit_price_tax_incl;
public $total_price_tax_excl;
public $total_price_tax_incl;
public $name;
public $option_name;
public $hotel_name;
public $quantity;
public $auto_added;
public $is_refunded;
public $is_cancelled;
public $date_add;
public $date_upd;
public static $definition = array(
'table' => 'service_product_order_detail',
'primary' => 'id_service_product_order_detail',
'fields' => array(
'id_product' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_order' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_order_detail' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_cart' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId', 'required' => true),
'id_hotel' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'id_htl_booking_detail' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'id_product_option' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'tax_computation_method' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'id_tax_rules_group' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'unit_price_tax_excl' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true),
'unit_price_tax_incl' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true),
'total_price_tax_excl' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true),
'total_price_tax_incl' => array('type' => self::TYPE_FLOAT, 'validate' => 'isPrice', 'required' => true),
'name' => array('type' => self::TYPE_STRING, 'required' => true),
'option_name' => array('type' => self::TYPE_STRING),
'hotel_name' => array('type' => self::TYPE_STRING),
'quantity' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt', 'required' => true),
'auto_added' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedInt'),
'is_refunded' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'is_cancelled' => array('type' => self::TYPE_INT, 'validate' => 'isUnsignedId'),
'date_add' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'),
'date_upd' => array('type' => self::TYPE_DATE, 'validate' => 'isDate'),
)
);
public function add($autodate = true, $null_values = true)
{
if (Validate::isLoadedObject($objOrder = new Order((int)$this->id_order))
&& Validate::isLoadedObject($objServiceProduct = new Product((int)$this->id_product))
&& Validate::isLoadedObject($objOrderDetail = new OrderDetail((int)$this->id_order_detail))
) {
if ($objOrderDetail->selling_preference_type == Product::SELLING_PREFERENCE_WITH_ROOM_TYPE) {
if ($this->id_htl_booking_detail
&& Validate::isLoadedObject($objHotelBookingDetail = new HotelBookingDetail((int)$this->id_htl_booking_detail))
) {
$idRoomType = $objHotelBookingDetail->id_product;
$objAddress = new Address((int)$objOrder->id_address_tax);
if ($objServiceProduct->auto_add_to_cart && $objServiceProduct->price_addition_type == Product::PRICE_ADDITION_TYPE_WITH_ROOM) {
if (Validate::isLoadedObject($objRoomTypeProduct = new Product((int)$idRoomType))) {
$this->id_tax_rules_group = $objRoomTypeProduct->id_tax_rules_group;
}
} else {
$objRoomTypeServiceProductPrice = new RoomTypeServiceProductPrice();
if ($serviceProductPriceRoomInfo = $objRoomTypeServiceProductPrice->getProductRoomTypeLinkPriceInfo(
$this->id_product,
$idRoomType,
RoomTypeServiceProduct::WK_ELEMENT_TYPE_ROOM_TYPE
)) {
//Special tax rule group for the Service product accroding to Room type
$this->id_tax_rules_group = $serviceProductPriceRoomInfo['id_tax_rules_group'];
} else {
// Use default tax rule group for the service product
$this->id_tax_rules_group = $objOrderDetail->id_tax_rules_group;
}
}
$taxCalculator = TaxManagerFactory::getManager($objAddress, $this->id_tax_rules_group)->getTaxCalculator();
$this->tax_computation_method = (int)$taxCalculator->computation_method;
}
} else {
// Use default tax rule group for the service product
$this->id_tax_rules_group = $objOrderDetail->id_tax_rules_group;
$this->tax_computation_method = $objOrderDetail->tax_computation_method;
}
return parent::add($autodate, $null_values);
}
return false;
}
public function getServiceProductsInOrder(
$idOrder,
$idOrderDetail = 0,
$idProduct = 0,
$sellingPreferenceType = 0
) {
$sql = 'SELECT spo.* FROM `'._DB_PREFIX_.'service_product_order_detail` spo';
if ($sellingPreferenceType) {
$sql .= ' INNER JOIN `'._DB_PREFIX_.'order_detail` od ON (od.`id_order_detail` = spo.`id_order_detail` AND od.`id_order` = '.(int)$idOrder.')';
}
$sql .= ' WHERE 1 AND spo.`id_order` = '.(int)$idOrder;
if ($idOrderDetail) {
$sql .= ' AND spo.`id_order_detail` = '.(int)$idOrderDetail;
}
if ($idProduct) {
$sql .= ' AND spo.`id_product` = '.(int)$idProduct;
}
if ($sellingPreferenceType) {
$sql .= ' AND od.`selling_preference_type` = '.(int)$sellingPreferenceType;
}
if ($products = Db::getInstance()->executeS($sql)) {
$objContext = Context::getContext();
$defaultImageLink = $objContext->link->getImageLink('', $objContext->language->iso_code.'-default', 'small_default');
foreach ($products as $key => $product) {
// Check if this booking as any refund history then enter refund data
if ($refundInfo = OrderReturn::getOrdersReturnDetail($idOrder, 0, 0, $product['id_service_product_order_detail'])) {
$products[$key]['refund_info'] = reset($refundInfo);
}
$products[$key]['cover_image'] = $defaultImageLink;
$products[$key]['allow_multiple_quantity'] = 0;
if (Validate::isLoadedObject($objProduct = new Product((int) $product['id_product'], Configuration::get('PS_LANG_DEFAULT')))) {
$products[$key]['allow_multiple_quantity'] = $objProduct->allow_multiple_quantity;
if ($productCoverImg = Product::getCover($product['id_product'])) {
$products[$key]['cover_image'] = $objContext->link->getImageLink(
$objProduct->link_rewrite[Configuration::get('PS_LANG_DEFAULT')],
$productCoverImg['id_image'], 'small_default'
);
}
}
}
}
return $products;
}
public function getRoomTypeServiceProducts(
$idOrder = 0,
$idProduct = 0,
$idHotel = 0,
$roomTypeIdProduct = 0,
$dateFrom = 0,
$dateTo = 0,
$idRoom = 0,
$getTotalPrice = 0,
$useTax = null,
$autoAddToCart = null,
$priceAdditionType = null,
$idOrderDetail = 0,
$idHtlBookingDetail = 0
) {
if ($useTax === null) {
$useTax = Product::$_taxCalculationMethod == PS_TAX_EXC ? false : true;
}
$sql = 'SELECT spod.*';
if (!$getTotalPrice) {
$sql .= ', hbd.`id_product` as `id_room_type`, od.`product_price_calculation_method`,
hbd.`id_room`, hbd.`adults`, hbd.`children`, hbd.`date_from`, hbd.`date_to`, hbd.`room_type_name`, p.`max_quantity`,
spod.`id_product` as id_product, od.`product_allow_multiple_quantity`, od.`product_price_calculation_method`, od.`product_auto_add`, od.`product_price_addition_type`, IF(p.`id_product`, 0, 1) as `product_deleted`';
}
$sql .= ' FROM `'._DB_PREFIX_.'htl_booking_detail` hbd
LEFT JOIN `'._DB_PREFIX_.'service_product_order_detail` spod ON(spod.`id_htl_booking_detail` = hbd.`id`)';
$sql .= ' LEFT JOIN `'._DB_PREFIX_.'order_detail` od ON(od.`id_order_detail` = spod.`id_order_detail`)';
$sql .= ' LEFT JOIN `'._DB_PREFIX_.'product` p ON(spod.`id_product` = p.`id_product`)';
$sql .= ' WHERE spod.`id_htl_booking_detail` IS NOT NULL';
if ($idOrder) {
$sql .= ' AND spod.`id_order` = '.(int)$idOrder;
}
if ($idOrderDetail) {
$sql .= ' AND spod.`id_order_detail` = '.(int)$idOrderDetail;
}
if (!is_null($autoAddToCart)) {
$sql .= ' AND od.`product_auto_add` = '. (int)$autoAddToCart;
if ($autoAddToCart == 1 && !is_null($priceAdditionType)) {
$sql .= ' AND od.`product_price_addition_type` = '.$priceAdditionType;
}
}
if ($idProduct) {
$sql .= ' AND spod.`id_product`='.(int) $idProduct;
}
if ($idHotel) {
$sql .= ' AND hbd.`id_hotel`='.(int) $idHotel;
}
if ($roomTypeIdProduct) {
$sql .= ' AND hbd.`id_product`='.(int) $roomTypeIdProduct;
}
if ($dateFrom && $dateTo) {
$sql .= ' AND hbd.`date_from` = \''.pSQL($dateFrom).'\' AND hbd.`date_to` = \''.pSQL($dateTo).'\'';
}
if ($idRoom) {
$sql .= ' AND hbd.`id_room`='.(int) $idRoom;
}
if ($idHtlBookingDetail) {
$sql .= ' AND hbd.`id` = '.(int)$idHtlBookingDetail;
}
$sql .= ' ORDER BY hbd.`id`';
if ($getTotalPrice) {
$totalPrice = 0;
}
$selectedAdditionalServices = array();
if ($additionalServices = Db::getInstance()->executeS($sql)) {
$moduleObj = Module::getInstanceByName('hotelreservationsystem');
foreach ($additionalServices as $product) {
if ($getTotalPrice) {
if ($useTax) {
$totalPrice += $product['total_price_tax_incl'];
} else {
$totalPrice += $product['total_price_tax_excl'];
}
} else {
$taxes = OrderDetailCore::getTaxListStatic($product['id_order_detail']);
$tax_temp = array();
foreach ($taxes as $tax) {
$obj = new Tax($tax['id_tax']);
$tax_temp[] = sprintf($moduleObj->l('%1$s%2$s%%'), ($obj->rate + 0), '&nbsp;');
}
$product_tax = $taxes;
$product_tax_label = implode(', ', $tax_temp);
if (isset($selectedAdditionalServices[$product['id_htl_booking_detail']])) {
$selectedAdditionalServices[$product['id_htl_booking_detail']]['total_price_tax_excl'] += $product['total_price_tax_excl'];
$selectedAdditionalServices[$product['id_htl_booking_detail']]['total_price_tax_incl'] += $product['total_price_tax_incl'];
$selectedAdditionalServices[$product['id_htl_booking_detail']]['additional_services'][] = array(
'id_service_product_order_detail' => $product['id_service_product_order_detail'],
'id_order_detail' => $product['id_order_detail'],
'id_product' => $product['id_product'],
'name' => $product['name'],
'quantity' => $product['quantity'],
'product_tax' => $product_tax,
'product_tax_label' => $product_tax_label,
'allow_multiple_quantity' => $product['product_allow_multiple_quantity'],
'tax_computation_method' => $product['tax_computation_method'],
'id_tax_rules_group' => $product['id_tax_rules_group'],
'price_calculation_method' => $product['product_price_calculation_method'],
'total_price_tax_excl' => $product['total_price_tax_excl'],
'total_price_tax_incl' => $product['total_price_tax_incl'],
'unit_price_tax_excl' => $product['unit_price_tax_excl'],
'unit_price_tax_incl' => $product['unit_price_tax_incl'],
'product_auto_add' => $product['product_auto_add'],
'product_price_addition_type' => $product['product_price_addition_type'],
'max_quantity' => (int) $product['max_quantity'],
'product_deleted' => $product['product_deleted']
);
} else {
$selectedAdditionalServices[$product['id_htl_booking_detail']]['id_order'] = $product['id_order'];
$selectedAdditionalServices[$product['id_htl_booking_detail']]['id_cart'] = $product['id_cart'];
$selectedAdditionalServices[$product['id_htl_booking_detail']]['id_htl_booking_detail'] = $product['id_htl_booking_detail'];
$selectedAdditionalServices[$product['id_htl_booking_detail']]['adults'] = $product['adults'];
$selectedAdditionalServices[$product['id_htl_booking_detail']]['children'] = $product['children'];
$selectedAdditionalServices[$product['id_htl_booking_detail']]['total_price_tax_excl'] = $product['total_price_tax_excl'];
$selectedAdditionalServices[$product['id_htl_booking_detail']]['total_price_tax_incl'] = $product['total_price_tax_incl'];
$selectedAdditionalServices[$product['id_htl_booking_detail']]['id_room_type'] = $product['id_room_type'];
$selectedAdditionalServices[$product['id_htl_booking_detail']]['id_room'] = $product['id_room'];
$selectedAdditionalServices[$product['id_htl_booking_detail']]['date_from'] = $product['date_from'];
$selectedAdditionalServices[$product['id_htl_booking_detail']]['date_to'] = $product['date_to'];
$selectedAdditionalServices[$product['id_htl_booking_detail']]['room_type_name'] = $product['room_type_name'];
$selectedAdditionalServices[$product['id_htl_booking_detail']]['additional_services'] = array(
array(
'id_service_product_order_detail' => $product['id_service_product_order_detail'],
'id_order_detail' => $product['id_order_detail'],
'id_product' => $product['id_product'],
'name' => $product['name'],
'quantity' => $product['quantity'],
'allow_multiple_quantity' => $product['product_allow_multiple_quantity'],
'tax_computation_method' => $product['tax_computation_method'],
'id_tax_rules_group' => $product['id_tax_rules_group'],
'price_calculation_method' => $product['product_price_calculation_method'],
'product_tax' => $product_tax,
'product_tax_label' => $product_tax_label,
'total_price_tax_excl' => $product['total_price_tax_excl'],
'total_price_tax_incl' => $product['total_price_tax_incl'],
'unit_price_tax_excl' => $product['unit_price_tax_excl'],
'unit_price_tax_incl' => $product['unit_price_tax_incl'],
'product_auto_add' => $product['product_auto_add'],
'product_price_addition_type' => $product['product_price_addition_type'],
'max_quantity' => (int) $product['max_quantity'],
'product_deleted' => $product['product_deleted'],
),
);
}
}
}
}
if ($getTotalPrice) {
return $totalPrice;
}
return $selectedAdditionalServices;
}
public function getSelectedServicesForRoom(
$idHotelBookingDetail,
$getTotalPrice = 0,
$useTax = null,
$autoAddToCart = null,
$priceAdditionType = null
) {
if ($useTax === null) {
$useTax = Product::$_taxCalculationMethod == PS_TAX_EXC ? false : true;
}
$sql = 'SELECT spod.*';
if (!$getTotalPrice) {
$sql .= ', hbd.`id_product` as `room_type_id_product`, hbd.`id_room`, od.`product_allow_multiple_quantity`, p.`max_quantity`,
od.`product_auto_add`, od.`product_price_calculation_method`, od.`product_price_addition_type`';
}
$sql .= ' FROM `'._DB_PREFIX_.'htl_booking_detail` hbd
INNER JOIN `'._DB_PREFIX_.'service_product_order_detail` spod ON(spod.`id_htl_booking_detail` = hbd.`id`)';
$sql .= ' LEFT JOIN `'._DB_PREFIX_.'order_detail` od ON(od.`id_order_detail` = spod.`id_order_detail`)';
$sql .= ' LEFT JOIN `'._DB_PREFIX_.'product` p ON (p.`id_product` = spod.`id_product`)';
$sql .= ' WHERE hbd.`id` = '.(int)$idHotelBookingDetail;
if (!is_null($autoAddToCart)) {
$sql .= ' AND od.`product_auto_add` = '. (int)$autoAddToCart;
if ($autoAddToCart == 1 && !is_null($priceAdditionType)) {
$sql .= ' AND od.`product_price_addition_type` = '.$priceAdditionType;
}
}
if ($getTotalPrice) {
$totalPrice = 0;
}
$selectedAdditionalServices = array();
if ($additionalServices = Db::getInstance()->executeS($sql)) {
foreach ($additionalServices as $product) {
if ($getTotalPrice) {
if ($useTax) {
$totalPrice += $product['total_price_tax_incl'];
} else {
$totalPrice += $product['total_price_tax_excl'];
}
} else {
$taxes = OrderDetailCore::getTaxListStatic($product['id_order_detail']);
$tax_temp = array();
foreach ($taxes as $tax) {
$obj = new Tax($tax['id_tax']);
$tax_temp[] = sprintf('%1$s%2$s%%', ($obj->rate + 0), '&nbsp;');
}
$product_tax_label = implode(', ', $tax_temp);
if (isset($selectedAdditionalServices['additional_services'])) {
$selectedAdditionalServices['total_price_tax_excl'] += $product['total_price_tax_excl'];
$selectedAdditionalServices['total_price_tax_incl'] += $product['total_price_tax_incl'];
$selectedAdditionalServices['additional_services'][] = array(
'id_service_product_order_detail' => $product['id_service_product_order_detail'],
'id_order_detail' => $product['id_order_detail'],
'id_product' => $product['id_product'],
'name' => $product['name'],
'id_cart' => $product['id_cart'],
'quantity' => $product['quantity'],
'allow_multiple_quantity' => $product['product_allow_multiple_quantity'],
'max_quantity' => $product['max_quantity'],
'product_auto_add' => $product['product_auto_add'],
'product_price_addition_type' => $product['product_price_addition_type'],
'price_calculation_method' => $product['product_price_calculation_method'],
'unit_price_tax_excl' => $product['unit_price_tax_excl'],
'unit_price_tax_incl' => $product['unit_price_tax_incl'],
'total_price_tax_excl' => $product['total_price_tax_excl'],
'total_price_tax_incl' => $product['total_price_tax_incl'],
'product_tax_label' => $product_tax_label,
);
} else {
$selectedAdditionalServices['id_order'] = $product['id_order'];
$selectedAdditionalServices['id_cart'] = $product['id_cart'];
$selectedAdditionalServices['id_htl_booking_detail'] = $product['id_htl_booking_detail'];
$selectedAdditionalServices['total_price_tax_excl'] = $product['total_price_tax_excl'];
$selectedAdditionalServices['total_price_tax_incl'] = $product['total_price_tax_incl'];
$selectedAdditionalServices['room_type_id_product'] = $product['room_type_id_product'];
$selectedAdditionalServices['id_room'] = $product['id_room'];
$selectedAdditionalServices['additional_services'] = array(
array(
'id_service_product_order_detail' => $product['id_service_product_order_detail'],
'id_order_detail' => $product['id_order_detail'],
'id_product' => $product['id_product'],
'name' => $product['name'],
'id_cart' => $product['id_cart'],
'quantity' => $product['quantity'],
'allow_multiple_quantity' => $product['product_allow_multiple_quantity'],
'max_quantity' => $product['max_quantity'],
'product_auto_add' => $product['product_auto_add'],
'product_price_addition_type' => $product['product_price_addition_type'],
'price_calculation_method' => $product['product_price_calculation_method'],
'unit_price_tax_excl' => $product['unit_price_tax_excl'],
'unit_price_tax_incl' => $product['unit_price_tax_incl'],
'total_price_tax_excl' => $product['total_price_tax_excl'],
'total_price_tax_incl' => $product['total_price_tax_incl'],
'product_tax_label' => $product_tax_label,
),
);
}
}
}
}
if ($getTotalPrice) {
return $totalPrice;
}
return $selectedAdditionalServices;
}
public function deleteSeviceProducts(
$idOrder = 0,
$idHotelBookingDetail = 0,
$idProduct = 0,
$idProductOption = 0
) {
$sql = 'SELECT `id_service_product_order_detail` FROM `'._DB_PREFIX_.'service_product_order_detail` WHERE 1';
if ($idOrder) {
$sql .= ' AND `id_order` = '.(int)$idOrder;
}
if ($idHotelBookingDetail) {
$sql .= ' AND `id_htl_booking_detail` = '.(int)$idHotelBookingDetail;
}
if ($idProduct) {
$sql .= ' AND `id_product` = '.(int)$idProduct;
}
if ($idProductOption) {
$sql .= ' AND `id_product_option` = '.(int)$idProductOption;
}
$result = true;
if ($services = Db::getInstance()->executeS($sql)) {
foreach ($services as $service) {
$objServiceProductOrderDetail = new self($service['id_service_product_order_detail']);
$result &= $objServiceProductOrderDetail->delete();
}
}
return $result;
}
/**
* Active (non-refunded, non-cancelled) service line ids attached to a room booking.
* Used by the tourism tax apply/exempt flow to also act on a room's own service lines.
*
* @param int $idHtlBookingDetail
* @return array int[]
*/
public static function getActiveIdsByHtlBookingDetail($idHtlBookingDetail)
{
$rows = Db::getInstance()->executeS(
'SELECT `id_service_product_order_detail` FROM `' . _DB_PREFIX_ . 'service_product_order_detail`
WHERE `id_htl_booking_detail` = ' . (int) $idHtlBookingDetail . '
AND `is_refunded` = 0
AND `is_cancelled` = 0'
);
return $rows ? array_map('intval', array_column($rows, 'id_service_product_order_detail')) : array();
}
/**
* Active (not refunded/cancelled) standalone service line ids for an order — lines with no room
* attachment (id_htl_booking_detail = 0), the counterpart to getActiveIdsByHtlBookingDetail().
*
* @param int $idOrder
* @return array int[]
*/
public static function getActiveStandaloneIdsByOrder($idOrder)
{
$rows = Db::getInstance()->executeS(
'SELECT `id_service_product_order_detail` FROM `' . _DB_PREFIX_ . 'service_product_order_detail`
WHERE `id_order` = ' . (int) $idOrder . '
AND `id_htl_booking_detail` = 0
AND `is_refunded` = 0
AND `is_cancelled` = 0'
);
return $rows ? array_map('intval', array_column($rows, 'id_service_product_order_detail')) : array();
}
// process the tables changes when a product refund/cancellation is processed
public function processRefundInTables($refundedAmountTaxIncl = null)
{
if (Validate::isLoadedObject($this)) {
$reduction_amount = array(
'total_price_tax_excl' => 0,
'total_price_tax_incl' => 0,
'total_products_tax_excl' => 0,
'total_products_tax_incl' => 0,
);
$objOrder = new Order($this->id_order);
$orderTotalPaid = $objOrder->getTotalPaid();
$orderDiscounts = $objOrder->getCartRules();
$hasOrderDiscountOrPayment = ((float)$orderTotalPaid > 0 || $orderDiscounts) ? true : false;
// things to do if order is not paid
if (!$hasOrderDiscountOrPayment) {
$objServiceProductOrderDetail = new ServiceProductOrderDetail();
$priceTaxExcl = (float) $this->total_price_tax_excl;
$priceTaxIncl = (float) $this->total_price_tax_incl;
$reduction_amount['total_price_tax_excl'] = $priceTaxExcl;
$reduction_amount['total_products_tax_excl'] = $priceTaxExcl;
$reduction_amount['total_price_tax_incl'] = $priceTaxIncl;
$reduction_amount['total_products_tax_incl'] = $priceTaxIncl;
}
// enter refunded quantity in the order detail table
$idOrderDetail = $this->id_order_detail;
if (Validate::isLoadedObject($objOrderDetail = new OrderDetail($idOrderDetail))) {
$objOrderDetail->product_quantity_refunded += $this->quantity;
if ($objOrderDetail->product_quantity_refunded > $objOrderDetail->product_quantity) {
$objOrderDetail->product_quantity_refunded = $objOrderDetail->product_quantity;
}
if (!$hasOrderDiscountOrPayment && $refundedAmountTaxIncl === null) {
// reduce room amount from order and order detail — only for
// the legacy full-cancellation callers, see comment above
$objOrderDetail->total_price_tax_incl -= Tools::processPriceRounding(
$reduction_amount['total_price_tax_incl'],
1,
$objOrder->round_type,
$objOrder->round_mode
);
$objOrderDetail->total_price_tax_excl -= Tools::processPriceRounding(
$reduction_amount['total_price_tax_excl'],
1,
$objOrder->round_type,
$objOrder->round_mode
);
if (Validate::isLoadedObject($objOrder = new Order($this->id_order))) {
$objOrder->total_paid = Tools::ps_round(
($objOrder->total_paid - $reduction_amount['total_price_tax_incl']),
_PS_PRICE_COMPUTE_PRECISION_
);
$objOrder->total_paid = $objOrder->total_paid > 0 ? $objOrder->total_paid : 0;
$objOrder->total_paid_tax_excl = Tools::ps_round(($objOrder->total_paid_tax_excl - $reduction_amount['total_price_tax_excl']),
_PS_PRICE_COMPUTE_PRECISION_
);
$objOrder->total_paid_tax_excl = $objOrder->total_paid_tax_excl > 0 ? $objOrder->total_paid_tax_excl : 0;
$objOrder->total_paid_tax_incl = Tools::ps_round(($objOrder->total_paid_tax_incl - $reduction_amount['total_price_tax_incl']),
_PS_PRICE_COMPUTE_PRECISION_
);
$objOrder->total_paid_tax_incl = $objOrder->total_paid_tax_incl > 0 ? $objOrder->total_paid_tax_incl : 0;
$objOrder->total_products = Tools::ps_round(($objOrder->total_products - $reduction_amount['total_products_tax_excl']),
_PS_PRICE_COMPUTE_PRECISION_
);
$objOrder->total_products = $objOrder->total_products > 0 ? $objOrder->total_products : 0;
$objOrder->total_products_wt = Tools::ps_round(($objOrder->total_products_wt - $reduction_amount['total_products_tax_incl']),
_PS_PRICE_COMPUTE_PRECISION_
);
$objOrder->total_products_wt = $objOrder->total_products_wt > 0 ? $objOrder->total_products_wt : 0;
$objOrder->save();
}
}
$objOrderDetail->save();
}
// as refund is completed then set the booking as refunded
$this->is_refunded = 1;
if (!$hasOrderDiscountOrPayment && $refundedAmountTaxIncl === null) {
// Reduce room amount from htl_booking_detail — legacy full-
// cancellation callers only, see comment above
$this->is_cancelled = 1;
$this->total_price_tax_excl = 0;
$this->total_price_tax_incl = 0;
}
$this->save();
return true;
}
return false;
}
// ── REPORT METHODS ────────────────────────────────────────────────────────
/**
* Total service revenue (tax excl) for a date range, filtered by invoice_date.
*
* @param array $params date_from, date_to, id_hotel, id_product, id_room, id_order,
* id_customer, id_category, id_service_product, id_lang
* @return float
*/
public static function getTotalRevenue(array $params)
{
$dateFrom = pSQL($params['date_from']);
$dateTo = pSQL(isset($params['date_to']) ? $params['date_to'] : $params['date_from']);
$idsHotel = isset($params['ids_hotel']) ? $params['ids_hotel'] : (isset($params['id_hotel']) ? $params['id_hotel'] : false);
$idProduct = isset($params['id_product']) ? (int) $params['id_product'] : 0;
$idRoom = isset($params['id_room']) ? (int) $params['id_room'] : 0;
$idOrder = isset($params['id_order']) ? (int) $params['id_order'] : 0;
$idCustomer = isset($params['id_customer']) ? (int) $params['id_customer'] : 0;
$idCategory = isset($params['id_category']) ? (int) $params['id_category'] : 0;
$idServiceProduct = isset($params['id_service_product']) ? (int) $params['id_service_product'] : 0;
$idLang = isset($params['id_lang']) ? (int) $params['id_lang'] : 0;
if (!$idLang) {
$idLang = Context::getContext()->language->id;
}
$baseFrom =
'FROM `'._DB_PREFIX_.'service_product_order_detail` spod
LEFT JOIN `'._DB_PREFIX_.'htl_booking_detail` hbd
ON (spod.`id_htl_booking_detail` = hbd.`id`)
LEFT JOIN `'._DB_PREFIX_.'product` p
ON (p.`id_product` = hbd.`id_product`)
LEFT JOIN `'._DB_PREFIX_.'orders` o
ON (o.`id_order` = hbd.`id_order`)';
$baseWhere =
'WHERE p.`active` = 1
AND o.`valid` = 1
AND hbd.`is_refunded` = 0
AND spod.`is_cancelled` = 0
AND o.`invoice_date` BETWEEN "'.$dateFrom.' 00:00:00" AND "'.$dateTo.' 23:59:59"'
.($idProduct ? ' AND hbd.`id_product` = '.$idProduct : '')
.($idRoom ? ' AND hbd.`id_room` = '.$idRoom : '')
.($idOrder ? ' AND hbd.`id_order` = '.$idOrder : '')
.($idCustomer ? ' AND hbd.`id_customer` = '.$idCustomer : '')
.($idServiceProduct ? ' AND spod.`id_product` = '.$idServiceProduct : '')
.($idCategory ? ' AND spod.`id_product` IN (SELECT `id_product` FROM `'._DB_PREFIX_.'product` WHERE `id_category_default` = '.$idCategory.')' : '')
.HotelBranchInformation::addHotelRestriction($idsHotel, 'hbd');
return (float) Db::getInstance()->getValue(
'SELECT IFNULL(SUM(spod.`total_price_tax_excl` / o.`conversion_rate`), 0)
'.$baseFrom.' '.$baseWhere
);
}
/**
* Per-service-line rows for a date range, one row per service order line.
* Used by services report tab.
*
* @param array $params date_from, date_to, id_hotel, id_product, id_room, id_order,
* id_customer, id_category, id_service_product, id_lang
* @return array
*/
public static function getServicesInfo(array $params)
{
$dateFrom = pSQL($params['date_from']);
$dateTo = pSQL(isset($params['date_to']) ? $params['date_to'] : $params['date_from']);
$idsHotel = isset($params['ids_hotel']) ? $params['ids_hotel'] : (isset($params['id_hotel']) ? $params['id_hotel'] : false);
$idProduct = isset($params['id_product']) ? (int) $params['id_product'] : 0;
$idRoom = isset($params['id_room']) ? (int) $params['id_room'] : 0;
$idOrder = isset($params['id_order']) ? (int) $params['id_order'] : 0;
$idCustomer = isset($params['id_customer']) ? (int) $params['id_customer'] : 0;
$idCategory = isset($params['id_category']) ? (int) $params['id_category'] : 0;
$idServiceProduct = isset($params['id_service_product']) ? (int) $params['id_service_product'] : 0;
$idLang = isset($params['id_lang']) ? (int) $params['id_lang'] : 0;
if (!$idLang) {
$idLang = Context::getContext()->language->id;
}
$baseFrom =
'FROM `'._DB_PREFIX_.'service_product_order_detail` spod
LEFT JOIN `'._DB_PREFIX_.'htl_booking_detail` hbd
ON (spod.`id_htl_booking_detail` = hbd.`id`)
LEFT JOIN `'._DB_PREFIX_.'product` p
ON (p.`id_product` = hbd.`id_product`)
LEFT JOIN `'._DB_PREFIX_.'orders` o
ON (o.`id_order` = hbd.`id_order`)';
$baseWhere =
'WHERE p.`active` = 1
AND o.`valid` = 1
AND hbd.`is_refunded` = 0
AND spod.`is_cancelled` = 0
AND o.`invoice_date` BETWEEN "'.$dateFrom.' 00:00:00" AND "'.$dateTo.' 23:59:59"'
.($idProduct ? ' AND hbd.`id_product` = '.$idProduct : '')
.($idRoom ? ' AND hbd.`id_room` = '.$idRoom : '')
.($idOrder ? ' AND hbd.`id_order` = '.$idOrder : '')
.($idCustomer ? ' AND hbd.`id_customer` = '.$idCustomer : '')
.($idServiceProduct ? ' AND spod.`id_product` = '.$idServiceProduct : '')
.($idCategory ? ' AND spod.`id_product` IN (SELECT `id_product` FROM `'._DB_PREFIX_.'product` WHERE `id_category_default` = '.$idCategory.')' : '')
.HotelBranchInformation::addHotelRestriction($idsHotel, 'hbd');
return Db::getInstance()->executeS(
'SELECT spod.`id_service_product_order_detail`, spod.`date_add`,
o.`id_order`, o.`reference`, o.`id_customer`,
CONCAT(c.`firstname`, " ", c.`lastname`) AS customer_name,
spod.`name` AS service_name,
IFNULL(cl.`name`, "") AS service_category,
spod.`quantity`, sp.`allow_multiple_quantity`,
(spod.`unit_price_tax_excl` / o.`conversion_rate`) AS unit_price,
spod.`hotel_name`, hbd.`room_num`, hbd.`room_type_name`,
hbd.`date_from`, hbd.`date_to`,
(spod.`total_price_tax_excl` / o.`conversion_rate`) AS total_price_tax_excl,
((spod.`total_price_tax_incl` - spod.`total_price_tax_excl`) / o.`conversion_rate`) AS tax_amount,
(spod.`total_price_tax_incl` / o.`conversion_rate`) AS total_price_tax_incl
'.$baseFrom.'
LEFT JOIN `'._DB_PREFIX_.'customer` c ON (c.`id_customer` = o.`id_customer`)
LEFT JOIN `'._DB_PREFIX_.'product` sp ON (sp.`id_product` = spod.`id_product`)
LEFT JOIN `'._DB_PREFIX_.'category_lang` cl
ON (cl.`id_category` = sp.`id_category_default` AND cl.`id_lang` = '.(int) $idLang.')
'.$baseWhere.'
ORDER BY o.`invoice_date` DESC, spod.`id_service_product_order_detail` ASC'
);
}
/**
* Daily service revenue + tax amount split, keyed by Unix timestamp.
* Used by revenue report daily table.
*
* @param array $params date_from, date_to, id_hotel, id_product, id_room, id_order, id_customer
* @return array timestamp => ['service_revenue' => float, 'total_tax' => float]
*/
public static function getDatewiseRevenue(array $params)
{
$dateFrom = $params['date_from'];
$dateTo = isset($params['date_to']) ? $params['date_to'] : $params['date_from'];
$idsHotel = isset($params['ids_hotel']) ? $params['ids_hotel'] : (isset($params['id_hotel']) ? $params['id_hotel'] : false);
$idProduct = isset($params['id_product']) ? (int) $params['id_product'] : 0;
$idRoom = isset($params['id_room']) ? (int) $params['id_room'] : 0;
$idOrder = isset($params['id_order']) ? (int) $params['id_order'] : 0;
$idCustomer = isset($params['id_customer']) ? (int) $params['id_customer'] : 0;
$result = array();
$whereFilters =
($idProduct ? ' AND hbd.`id_product` = '.$idProduct : '')
.($idRoom ? ' AND hbd.`id_room` = '.$idRoom : '')
.($idOrder ? ' AND hbd.`id_order` = '.$idOrder : '')
.($idCustomer ? ' AND hbd.`id_customer` = '.$idCustomer : '')
.HotelBranchInformation::addHotelRestriction($idsHotel, 'hbd');
$joins =
'FROM `'._DB_PREFIX_.'service_product_order_detail` spod
LEFT JOIN `'._DB_PREFIX_.'htl_booking_detail` hbd
ON (spod.`id_htl_booking_detail` = hbd.`id`)
LEFT JOIN `'._DB_PREFIX_.'product` p ON (p.`id_product` = hbd.`id_product`)
LEFT JOIN `'._DB_PREFIX_.'orders` o ON (o.`id_order` = hbd.`id_order`)';
$rows = Db::getInstance()->executeS(
'SELECT DATE(o.`date_add`) AS grp_date,
IFNULL(SUM(spod.`total_price_tax_excl` / o.`conversion_rate`), 0) AS service_revenue,
IFNULL(SUM((spod.`total_price_tax_incl` - spod.`total_price_tax_excl`) / o.`conversion_rate`), 0) AS total_tax
'.$joins.'
WHERE p.`active` = 1 AND o.`valid` = 1
AND spod.`is_cancelled` = 0
AND o.`date_add` BETWEEN "'.pSQL($dateFrom).' 00:00:00" AND "'.pSQL($dateTo).' 23:59:59"'
.$whereFilters.'
GROUP BY DATE(o.`date_add`)'
);
foreach ($rows as $row) {
$ts = strtotime($row['grp_date']);
$result[$ts] = array(
'service_revenue' => (float) $row['service_revenue'],
'total_tax' => (float) $row['total_tax'],
);
}
return $result;
}
/**
* Distinct service categories with at least one active service product.
*
* @param int $idLang
* @return array rows: id_category, name
*/
public static function getServiceCategories($idLang = 0)
{
if (!$idLang) {
$idLang = Context::getContext()->language->id;
}
return Db::getInstance()->executeS(
'SELECT DISTINCT c.`id_category`, cl.`name`
FROM `'._DB_PREFIX_.'product` p
INNER JOIN `'._DB_PREFIX_.'category` c ON (c.`id_category` = p.`id_category_default`)
INNER JOIN `'._DB_PREFIX_.'category_lang` cl
ON (cl.`id_category` = c.`id_category` AND cl.`id_lang` = '.(int) $idLang.')
WHERE p.`booking_product` = 0 AND p.`active` = 1
ORDER BY cl.`name` ASC'
);
}
/**
* Active service products, optionally filtered by category.
*
* @param int $idLang
* @param int $idCategory 0 = all categories
* @return array rows: id_product, name
*/
public static function getServiceProducts($idLang = 0, $idCategory = 0)
{
if (!$idLang) {
$idLang = Context::getContext()->language->id;
}
return Db::getInstance()->executeS(
'SELECT p.`id_product`, pl.`name`
FROM `'._DB_PREFIX_.'product` p
INNER JOIN `'._DB_PREFIX_.'product_lang` pl
ON (pl.`id_product` = p.`id_product` AND pl.`id_lang` = '.(int) $idLang.')
WHERE p.`booking_product` = 0 AND p.`active` = 1'
.($idCategory ? ' AND p.`id_category_default` = '.(int) $idCategory : '').'
ORDER BY pl.`name` ASC'
);
}
/**
* Tax breakdown for service order lines in a date range.
* Called by HotelBookingDetail::getTaxBreakdown() when revenue_source is 'service' or 'all'.
*
* @param array $params date_from, date_to, id_hotel, id_tax, id_lang
* @return array
*/
public static function getTaxBreakdown(array $params)
{
$dateFrom = pSQL($params['date_from']);
$dateTo = pSQL(isset($params['date_to']) ? $params['date_to'] : $params['date_from']);
$idsHotel = isset($params['ids_hotel']) ? $params['ids_hotel'] : (isset($params['id_hotel']) ? $params['id_hotel'] : false);
$idTax = isset($params['id_tax']) ? (int) $params['id_tax'] : 0;
$idLang = isset($params['id_lang']) ? (int) $params['id_lang'] : 0;
if (!$idLang) {
$idLang = Context::getContext()->language->id;
}
return Db::getInstance()->executeS(
'SELECT hbd.`id_order`, o.`reference`, hbd.`id_customer`, spod.`id_product`,
CONCAT(c.`firstname`, " ", c.`lastname`) AS customer_name,
spod.`name` AS room_type_name, hbd.`room_num`, spod.`date_add`,
spod.`total_price_tax_excl` / o.`conversion_rate` AS taxable_amount,
MAX(tl.`name`) AS tax_name,
MAX(t.`rate`) AS tax_rate,
(spod.`total_price_tax_incl` - spod.`total_price_tax_excl`) / o.`conversion_rate` AS tax_amount,
"service" AS revenue_source
FROM `'._DB_PREFIX_.'service_product_order_detail` spod
INNER JOIN `'._DB_PREFIX_.'htl_booking_detail` hbd ON (hbd.`id` = spod.`id_htl_booking_detail`)
INNER JOIN `'._DB_PREFIX_.'orders` o ON (o.`id_order` = hbd.`id_order` AND o.`valid` = 1)
INNER JOIN `'._DB_PREFIX_.'customer` c ON (c.`id_customer` = hbd.`id_customer`)
LEFT JOIN `'._DB_PREFIX_.'tax_rule` tr ON (tr.`id_tax_rules_group` = spod.`id_tax_rules_group`)
LEFT JOIN `'._DB_PREFIX_.'tax` t ON (t.`id_tax` = tr.`id_tax`)
LEFT JOIN `'._DB_PREFIX_.'tax_lang` tl ON (tl.`id_tax` = t.`id_tax` AND tl.`id_lang` = '.(int) $idLang.')
WHERE spod.`is_cancelled` = 0
AND hbd.`is_refunded` = 0
AND (spod.`total_price_tax_incl` - spod.`total_price_tax_excl`) > 0
AND spod.`date_add` BETWEEN "'.$dateFrom.' 00:00:00" AND "'.$dateTo.' 23:59:59"'
.($idTax ? ' AND t.`id_tax` = '.$idTax : '')
.HotelBranchInformation::addHotelRestriction($idsHotel, 'hbd').'
GROUP BY spod.`id_service_product_order_detail`
ORDER BY spod.`date_add` ASC'
);
}
}