Fixed issues

Co-authored-by: Name <sumit201@webkul.in>
This commit is contained in:
varun kewlani
2026-08-20 20:45:52 +05:30
parent e809801366
commit 11c4fa8248
12 changed files with 205 additions and 321 deletions

View File

@@ -3201,22 +3201,36 @@ class OrderCore extends ObjectModel
'SELECT o.`id_order`, o.`reference`,
CONCAT(c.`firstname`, " ", c.`lastname`) AS customer_name,
c.`email`, a.`phone`,
hbd.`room_type_name`, hbd.`room_num`,
hbd.`date_from`, hbd.`date_to`, hbd.`id_status`,
MIN(hbd.`room_type_name`) AS room_type_name,
MIN(hbd.`room_num`) AS room_num,
MIN(hbd.`date_from`) AS date_from,
MAX(hbd.`date_to`) AS date_to,
MIN(hbd.`id_status`) AS id_status,
o.`total_paid_tax_incl` / o.`conversion_rate` AS total_charges,
IFNULL(SUM(op.`amount`), 0) / o.`conversion_rate` AS total_paid,
(o.`total_paid_tax_incl` - IFNULL(SUM(op.`amount`), 0)) / o.`conversion_rate` AS balance_due,
GREATEST(0, DATEDIFF(CURDATE(), hbd.`date_to`)) AS days_overdue,
MAX(op.`date_add`) AS last_payment_date
IFNULL((
SELECT SUM(op.`amount`)
FROM `'._DB_PREFIX_.'order_payment` op
WHERE op.`order_reference` = o.`reference`
), 0) / o.`conversion_rate` AS total_paid,
(o.`total_paid_tax_incl` - IFNULL((
SELECT SUM(op.`amount`)
FROM `'._DB_PREFIX_.'order_payment` op
WHERE op.`order_reference` = o.`reference`
), 0)) / o.`conversion_rate` AS balance_due,
GREATEST(0, DATEDIFF(CURDATE(), MAX(hbd.`date_to`))) AS days_overdue,
(
SELECT MAX(op.`date_add`)
FROM `'._DB_PREFIX_.'order_payment` op
WHERE op.`order_reference` = o.`reference`
) AS last_payment_date
FROM `'._DB_PREFIX_.'orders` o
INNER JOIN `'._DB_PREFIX_.'customer` c ON (c.`id_customer` = o.`id_customer`)
INNER JOIN `'._DB_PREFIX_.'htl_booking_detail` hbd
ON (hbd.`id_order` = o.`id_order` AND hbd.`is_cancelled` = 0 AND hbd.`is_refunded` = 0)
INNER JOIN `'._DB_PREFIX_.'product` p ON (p.`id_product` = hbd.`id_product` AND p.`active` = 1 AND p.`booking_product` = 1)
LEFT JOIN `'._DB_PREFIX_.'address` a ON (a.`id_address` = o.`id_address_invoice`)
LEFT JOIN `'._DB_PREFIX_.'order_payment` op ON (op.`order_reference` = o.`reference`)
WHERE o.`valid` = 1
AND o.`invoice_date` BETWEEN "'.$dateFrom.' 00:00:00" AND "'.$dateTo.' 23:59:59"'
AND hbd.`date_from` BETWEEN "'.$dateFrom.'" AND "'.$dateTo.'"'
.($idProduct ? ' AND hbd.`id_product` = '.$idProduct : '')
.($idStatus ? ' AND hbd.`id_status` = '.$idStatus : '')
.HotelBranchInformation::addHotelRestriction($idHotel, 'hbd').'

View File

@@ -1370,15 +1370,16 @@ class AdminStatsControllerCore extends AdminStatsTabController
);
}
public static function getTotalRooms($idHotel = null, $active = null)
public static function getTotalRooms($idHotel = null, $active = null, $idProduct = null)
{
$sql = 'SELECT COUNT(hri.`id`)
FROM `'._DB_PREFIX_.'htl_room_information` hri
INNER JOIN `'._DB_PREFIX_.'product` p
ON (p.`id_product` = hri.`id_product`)
WHERE p.`booking_product` = 1 '.
(!is_null($active) ? ' AND p.`active` ='.(int) $active : ' ').
(!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hri') : '');
(!is_null($active) ? ' AND p.`active` = '.(int) $active : '').
(!is_null($idProduct) ? ' AND hri.`id_product` = '.(int) $idProduct : '').
(!is_null($idHotel) ? HotelBranchInformation::addHotelRestriction($idHotel, 'hri') : '');
return Db::getInstance()->getValue($sql);
}

View File

@@ -201,10 +201,17 @@ abstract class AdminStatsTabControllerCore extends AdminPreferencesControllerCor
'stats_tabs' => &$statsTabs
));
$currentModuleName = Tools::getValue('module') ?: array_key_first($statsTabs);
$currentTab = Tools::getValue('tab', '');
if (!$currentTab && isset($statsTabs[$currentModuleName]['tabs'])) {
$firstTab = reset($statsTabs[$currentModuleName]['tabs']);
$currentTab = $firstTab['key'];
}
$tpl->assign(array(
'current' => self::$currentIndex,
'current_module_name' => Tools::getValue('module', 'statsforecast'),
'current_tab' => Tools::getValue('tab', ''), // active sub-tab key
'current_module_name' => $currentModuleName,
'current_tab' => $currentTab,
'token' => $this->token,
'module_tabs' => $statsTabs,
));
@@ -225,12 +232,30 @@ abstract class AdminStatsTabControllerCore extends AdminPreferencesControllerCor
return Db::getInstance()->executeS($sql);
}
protected function getDefaultModuleName()
{
$modules = $this->getModules();
if (empty($modules)) {
return '';
}
$sorted = [];
foreach ($modules as $mod) {
$instance = Module::getInstanceByName($mod['name']);
$pos = ($instance && property_exists($instance, 'stats_position'))
? (int) $instance->stats_position
: PHP_INT_MAX;
$sorted[] = ['name' => $mod['name'], 'pos' => $pos];
}
usort($sorted, fn($a, $b) => $a['pos'] <=> $b['pos']);
return $sorted[0]['name'];
}
public function displayStats()
{
$tpl = $this->createTemplate('stats.tpl');
if ((!($module_name = Tools::getValue('module')) || !Validate::isModuleName($module_name)) && ($module_instance = Module::getInstanceByName('statsforecast')) && $module_instance->active) {
$module_name = 'statsforecast';
if (!($module_name = Tools::getValue('module')) || !Validate::isModuleName($module_name)) {
$module_name = $this->getDefaultModuleName();
}
if ($module_name) {

View File

@@ -3953,8 +3953,7 @@ class HotelBookingDetail extends ObjectModel
.($idCustomer ? ' AND hbd.`id_customer` = '.$idCustomer : '')
.HotelBranchInformation::addHotelRestriction($idHotel, 'hbd');
$loopEnd = ($dateFrom === $dateTo) ? $dateTo : date('Y-m-d', strtotime('-1 day', strtotime($dateTo)));
while ($current <= $loopEnd) {
while ($current <= $dateTo) {
$nextDay = date('Y-m-d', strtotime('+1 day', strtotime($current)));
$ts = strtotime($current);
$row = Db::getInstance()->getRow(
@@ -4302,15 +4301,16 @@ class HotelBookingDetail extends ObjectModel
self::ALLOTMENT_AUTO => $moduleInstance->l('Online / Direct', 'hotelreservationsystem'),
self::ALLOTMENT_MANUAL => $moduleInstance->l('Walk-in / Admin', 'hotelreservationsystem'),
);
$totalBookings = array_sum(array_column($rows, 'bookings'));
$totalRevExcl = array_sum(array_column($rows, 'revenue_excl'));
foreach ($rows as &$row) {
$type = (int) $row['booking_type'];
$row['channel_label'] = isset($labels[$type]) ? $labels[$type] : $moduleInstance->l('Other', 'hotelreservationsystem');
$row['tax_amount'] = (float) $row['revenue_incl'] - (float) $row['revenue_excl'];
$row['net_revenue'] = (float) $row['revenue_excl'] - (float) $row['discount_amount'];
$row['adr'] = (float) $row['room_nights'] > 0
? (float) $row['revenue_excl'] / (float) $row['room_nights'] : 0.0;
$row['contribution_pct'] = $totalBookings > 0
? round((float) $row['bookings'] / $totalBookings * 100, 1) : 0.0;
$row['contribution_pct'] = $totalRevExcl > 0
? round((float) $row['revenue_excl'] / $totalRevExcl * 100, 1) : 0.0;
$row['cancel_rate_pct'] = (int) $row['bookings'] > 0
? round((float) $row['cancellations'] / (int) $row['bookings'] * 100, 1) : 0.0;
}
@@ -4595,8 +4595,12 @@ class HotelBookingDetail extends ObjectModel
return $result;
}
$countExpr = (isset($params['count_rows']) && $params['count_rows'])
? 'hbd.`id`'
: 'DISTINCT hbd.`id_order`';
return (int) Db::getInstance()->getValue(
'SELECT COUNT(DISTINCT hbd.`id_order`)
'SELECT COUNT('.$countExpr.')
FROM `'._DB_PREFIX_.'htl_booking_detail` hbd
WHERE hbd.`date_add` BETWEEN "'.$dateFrom.' 00:00:00" AND "'.$dateTo.' 23:59:59"'
.($idProduct ? ' AND hbd.`id_product` = '.$idProduct : '')
@@ -4633,37 +4637,6 @@ class HotelBookingDetail extends ObjectModel
);
}
/**
* Total no-shows: rooms expected to arrive (date_from in range) never checked in and past arrival.
*
* @param array $params date_from, date_to, id_hotel, id_product
* @return int
*/
public static function getTotalNoShows(array $params)
{
$dateFrom = pSQL($params['date_from']);
$dateTo = pSQL(isset($params['date_to']) ? $params['date_to'] : $params['date_from']);
$idHotel = isset($params['id_hotel']) ? $params['id_hotel'] : false;
$idProduct = isset($params['id_product']) ? (int) $params['id_product'] : 0;
return (int) Db::getInstance()->getValue(
'SELECT COUNT(hbd.`id`)
FROM `'._DB_PREFIX_.'htl_booking_detail` hbd
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`)
WHERE p.`active` = 1
AND o.`valid` = 1
AND hbd.`is_cancelled` = 0
AND hbd.`is_refunded` = 0
AND hbd.`id_status` = '.(int) self::STATUS_ALLOTED.'
AND hbd.`date_from` < CURDATE()
AND hbd.`date_from` >= "'.$dateFrom.'"
AND hbd.`date_from` <= "'.$dateTo.'"'
.($idProduct ? ' AND hbd.`id_product` = '.$idProduct : '')
.HotelBranchInformation::addHotelRestriction($idHotel, 'hbd')
);
}
/**
* New booking counts per day across a date range.
*
@@ -4742,7 +4715,7 @@ class HotelBookingDetail extends ObjectModel
$dateTo = isset($params['date_to']) ? $params['date_to'] : $params['date_from'];
$idHotel = isset($params['id_hotel']) ? $params['id_hotel'] : false;
$numNights = max(1, (int)(new DateTime($dateTo))->diff(new DateTime($dateFrom))->days);
$numNights = max(1, (int)(new DateTime($dateTo))->diff(new DateTime($dateFrom))->days + 1);
$totalRooms = (int) AdminStatsController::getTotalRooms($idHotel !== false ? $idHotel : null, 1);
if (!$totalRooms) {
@@ -4767,7 +4740,7 @@ class HotelBookingDetail extends ObjectModel
$dateTo = isset($params['date_to']) ? $params['date_to'] : $params['date_from'];
$idHotel = isset($params['id_hotel']) ? $params['id_hotel'] : false;
$numNights = max(1, (int)(new DateTime($dateTo))->diff(new DateTime($dateFrom))->days);
$numNights = max(1, (int)(new DateTime($dateTo))->diff(new DateTime($dateFrom))->days + 1);
$totalRooms = (int) AdminStatsController::getTotalRooms($idHotel !== false ? $idHotel : null, 1);
if (!$totalRooms || !$numNights) {
@@ -4810,21 +4783,24 @@ class HotelBookingDetail extends ObjectModel
*/
public static function getOccupiedRoomsForDiscreteDates(array $params)
{
$dateFrom = $params['date_from'];
$dateTo = isset($params['date_to']) ? $params['date_to'] : $params['date_from'];
$idHotel = isset($params['id_hotel']) ? $params['id_hotel'] : null;
$idStatus = isset($params['id_status']) ? (int) $params['id_status'] : null;
$dateFrom = $params['date_from'];
$dateTo = isset($params['date_to']) ? $params['date_to'] : $params['date_from'];
$idHotel = isset($params['id_hotel']) ? $params['id_hotel'] : null;
$idStatus = isset($params['id_status']) ? (int) $params['id_status'] : null;
$idProduct = isset($params['id_product']) ? (int) $params['id_product'] : 0;
// ponytail: no-status case handled by AdminStatsController to reuse its cache
if ($idStatus === null) {
// Delegate to AdminStatsController only when no additional filters need own SQL
if ($idStatus === null && !$idProduct) {
return AdminStatsController::getOccupiedRoomsForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
}
$hotelFilter = HotelBranchInformation::addHotelRestriction($idHotel, 'hbd');
$result = array();
$dateTemp = $dateFrom;
$hotelFilter = HotelBranchInformation::addHotelRestriction($idHotel, 'hbd');
$statusFilter = $idStatus !== null ? ' AND hbd.`id_status` = '.$idStatus : '';
$productFilter = $idProduct ? ' AND hri.`id_product` = '.$idProduct : '';
$result = array();
$dateTemp = $dateFrom;
while ($dateTemp <= $dateTo) {
$dateNext = date('Y-m-d', strtotime('+1 day', strtotime($dateTemp)));
$dateNext = date('Y-m-d', strtotime('+1 day', strtotime($dateTemp)));
$result[strtotime($dateTemp)] = (int) Db::getInstance()->getValue(
'SELECT COUNT(DISTINCT hbd.`id_room`)
FROM `'._DB_PREFIX_.'htl_booking_detail` hbd
@@ -4832,9 +4808,10 @@ class HotelBookingDetail extends ObjectModel
INNER JOIN `'._DB_PREFIX_.'product` p ON (p.`id_product` = hri.`id_product`)
WHERE p.`active` = 1
AND hbd.`is_cancelled` = 0
AND hbd.`id_status` = '.$idStatus.'
AND hbd.`date_from` < "'.pSQL($dateNext).' 00:00:00"
AND hbd.`date_to` > "'.pSQL($dateTemp).' 00:00:00"'
.$statusFilter
.$productFilter
.$hotelFilter
);
$dateTemp = $dateNext;

View File

@@ -391,12 +391,13 @@ class HotelRoomInformation extends ObjectModel
return array();
}
$dateToNext = pSQL(date('Y-m-d', strtotime('+1 day', strtotime($dateTo))));
$bookingsRaw = Db::getInstance()->executeS(
'SELECT hbd.`id_product`, hbd.`id_room`, hbd.`date_from`, hbd.`date_to`
FROM `'._DB_PREFIX_.'htl_booking_detail` hbd
INNER JOIN `'._DB_PREFIX_.'orders` o ON (o.`id_order` = hbd.`id_order` AND o.`valid` = 1)
WHERE hbd.`is_refunded` = 0 AND hbd.`is_cancelled` = 0
AND hbd.`date_from` < "'.$dateTo.'" AND hbd.`date_to` > "'.$dateFrom.'"'
AND hbd.`date_from` < "'.$dateToNext.'" AND hbd.`date_to` > "'.$dateFrom.'"'
.HotelBranchInformation::addHotelRestriction($idHotel, 'hbd')
.($idProduct ? ' AND hbd.`id_product` = '.$idProduct : '')
);
@@ -408,7 +409,7 @@ class HotelRoomInformation extends ObjectModel
$result = array();
$current = $dateFrom;
while ($current < $dateTo) {
while ($current <= $dateTo) {
$next = date('Y-m-d', strtotime('+1 day', strtotime($current)));
foreach ($roomTypes as $rt) {
$idProd = $rt['id_product'];
@@ -451,6 +452,7 @@ class HotelRoomInformation extends ObjectModel
{
$dateFrom = pSQL($params['date_from']);
$dateTo = pSQL(isset($params['date_to']) ? $params['date_to'] : $params['date_from']);
$dateToNext = pSQL(date('Y-m-d', strtotime('+1 day', strtotime($dateTo))));
$idHotel = isset($params['id_hotel']) ? $params['id_hotel'] : false;
$idProduct = isset($params['id_product']) ? (int) $params['id_product'] : 0;
$idLang = isset($params['id_lang']) ? (int) $params['id_lang'] : 0;
@@ -481,7 +483,7 @@ class HotelRoomInformation extends ObjectModel
LEFT JOIN (
SELECT `id_room`
FROM `'._DB_PREFIX_.'htl_room_disable_dates`
WHERE `date_from` < "'.$dateTo.'" AND `date_to` > "'.$dateFrom.'"
WHERE `date_from` < "'.$dateToNext.'" AND `date_to` > "'.$dateFrom.'"
GROUP BY `id_room`
) hrdd ON (hrdd.`id_room` = hri.`id`)
LEFT JOIN (
@@ -493,7 +495,7 @@ class HotelRoomInformation extends ObjectModel
MIN(hbd.`id_customer`) AS `id_customer`
FROM `'._DB_PREFIX_.'htl_booking_detail` hbd
WHERE hbd.`is_refunded` = 0
AND hbd.`date_from` < "'.$dateTo.'" AND hbd.`date_to` > "'.$dateFrom.'"
AND hbd.`date_from` < "'.$dateToNext.'" AND hbd.`date_to` > "'.$dateFrom.'"
GROUP BY hbd.`id_room`
) bkgs ON (bkgs.`id_room` = hri.`id`)
LEFT JOIN `'._DB_PREFIX_.'customer` c ON (c.`id_customer` = bkgs.`id_customer`)

View File

@@ -490,8 +490,7 @@ class HotelRoomType extends ObjectModel
IFNULL(SUM(CASE WHEN hbd.`is_cancelled` = 0 THEN DATEDIFF(hbd.`date_to`, hbd.`date_from`) ELSE 0 END), 0) AS room_nights,
IFNULL(SUM(CASE WHEN hbd.`is_cancelled` = 0 THEN hbd.`total_price_tax_excl` / o.`conversion_rate` ELSE 0 END), 0) AS room_revenue,
IFNULL(SUM(CASE WHEN hbd.`is_cancelled` = 0 THEN (hbd.`total_price_tax_incl` - hbd.`total_price_tax_excl`) / o.`conversion_rate` ELSE 0 END), 0) AS tax_amount,
COUNT(CASE WHEN hbd.`is_cancelled` = 1 THEN hbd.`id` END) AS cancel_count,
COUNT(CASE WHEN hbd.`is_cancelled` = 0 AND hbd.`id_status` = 1 AND hbd.`date_from` < CURDATE() THEN hbd.`id` END) AS no_show_count
COUNT(CASE WHEN hbd.`is_cancelled` = 1 THEN hbd.`id` END) AS cancel_count
FROM `'._DB_PREFIX_.'htl_room_type` hrt
INNER JOIN `'._DB_PREFIX_.'product` p ON (p.`id_product` = hrt.`id_product`)
INNER JOIN `'._DB_PREFIX_.'product_lang` pl

View File

@@ -36,24 +36,24 @@ class QloHotelReports extends Module
* Labels are plain English; call $this->l($config['label']) at render time.
*/
private static $reports = array(
'reservation' => array('label' => 'Reservations', 'tpl' => 'group-bookings.tpl'),
'reservation' => array('label' => 'Room Reservations', 'tpl' => 'group-bookings.tpl'),
'arrivals' => array('label' => 'Arrivals', 'tpl' => 'group-bookings.tpl'),
'in-house' => array('label' => 'In-House', 'tpl' => 'group-bookings.tpl'),
'departures' => array('label' => 'Departures', 'tpl' => 'group-bookings.tpl'),
'no-show' => array('label' => 'No-Shows', 'tpl' => 'group-bookings.tpl'),
'cancellation' => array('label' => 'Cancellations', 'tpl' => 'group-bookings.tpl'),
'revenue' => array('label' => 'Revenue', 'tpl' => 'group-revenue.tpl'),
'revenue' => array('label' => 'Daily Revenue', 'tpl' => 'group-revenue.tpl'),
'refund' => array('label' => 'Refunds', 'tpl' => 'group-revenue.tpl'),
'payment' => array('label' => 'Payments', 'tpl' => 'group-revenue.tpl'),
'tax' => array('label' => 'Tax', 'tpl' => 'group-revenue.tpl'),
'outstanding' => array('label' => 'Outstanding', 'tpl' => 'group-revenue.tpl'),
'occupancy' => array('label' => 'Occupancy', 'tpl' => 'group-occupancy.tpl'),
'occupancy' => array('label' => 'Day-wise Occupancy', 'tpl' => 'group-occupancy.tpl'),
'availability' => array('label' => 'Availability', 'tpl' => 'group-occupancy.tpl'),
'room-status' => array('label' => 'Room Status', 'tpl' => 'group-occupancy.tpl'),
'room-perf' => array('label' => 'Room Type Performance', 'tpl' => 'group-occupancy.tpl'),
'source' => array('label' => 'Booking Source', 'tpl' => 'group-channels.tpl'),
'payment-method' => array('label' => 'Payment Methods', 'tpl' => 'group-channels.tpl'),
'services' => array('label' => 'Services', 'tpl' => 'group-guests.tpl'),
'services' => array('label' => 'Booked Services', 'tpl' => 'group-guests.tpl'),
'guest-directory' => array('label' => 'Guest Directory', 'tpl' => 'group-guests.tpl'),
'daily-summary' => array('label' => 'Daily Summary', 'tpl' => 'group-property.tpl'),
'hotel-comparison' => array('label' => 'Hotel Comparison', 'tpl' => 'group-property.tpl'),
@@ -73,6 +73,7 @@ class QloHotelReports extends Module
parent::__construct();
$this->displayName = $this->l('Hotel Reports');
$this->stats_position = 1;
$this->description = $this->l('Comprehensive hotel reports: reservations, revenue, occupancy, channels, guests, and more.');
$this->confirmUninstall = $this->l('Are you sure you want to uninstall?');
}
@@ -122,7 +123,7 @@ class QloHotelReports extends Module
$currency = new Currency(Configuration::get('PS_CURRENCY_DEFAULT'));
if (Tools::getValue('export')) {
$bookingReports = array('reservation', 'arrivals', 'departures', 'in-house', 'no-show', 'cancellation');
$bookingReports = array('reservation', 'arrivals', 'departures', 'in-house', 'cancellation');
$revenueReports = array('revenue', 'refund', 'payment', 'tax', 'outstanding');
$occupancyReports = array('occupancy', 'availability', 'room-status', 'room-perf');
@@ -156,7 +157,7 @@ class QloHotelReports extends Module
. self::$reports[$report]['tpl'],
));
if (in_array($report, array('reservation', 'arrivals', 'in-house', 'departures', 'no-show', 'cancellation'))) {
if (in_array($report, array('reservation', 'arrivals', 'in-house', 'departures', 'cancellation'))) {
$idProduct = (int) Tools::getValue('id_product', 0);
$idStatus = (int) Tools::getValue('booking_status', 0);
$bookingType = (int) Tools::getValue('booking_type', 0);
@@ -253,29 +254,6 @@ class QloHotelReports extends Module
'cancellations' => $cancellations,
'total_refunded' => $totalRefunded,
));
} elseif ($report === 'no-show') {
// TODO: No-show logic will be replaced by a dedicated PR. Kept below for reference.
// $noShows = HotelBookingDetail::getArrivalsInfo(array(
// 'date_from' => $dateFrom,
// 'date_to' => $dateTo,
// 'id_hotel' => $idHotel ?: false,
// 'id_product' => $idProduct,
// 'id_status' => HotelBookingDetail::STATUS_ALLOTED,
// ));
// $this->attachCurrencyToRows($noShows, $currencyMap, $currency->iso_code);
// $noShowTotals = array('los' => 0, 'adults' => 0, 'children' => 0, 'total_price_tax_incl' => 0.0);
// foreach ($noShows as $row) {
// $rate = (float) $row['conversion_rate'];
// if ($rate <= 0) { $rate = 1.0; }
// $noShowTotals['los'] += (int) $row['los'];
// $noShowTotals['adults'] += (int) $row['adults'];
// $noShowTotals['children'] += (int) $row['children'];
// $noShowTotals['total_price_tax_incl'] += (float) $row['total_price_tax_incl'] / $rate;
// }
$this->context->smarty->assign(array(
'no_shows' => array(),
'no_show_totals' => array('los' => 0, 'adults' => 0, 'children' => 0, 'total_price_tax_incl' => 0.0),
));
} elseif ($report === 'arrivals') {
$arrivals = HotelBookingDetail::getArrivalsInfo(array(
'date_from' => $dateFrom,
@@ -609,38 +587,51 @@ class QloHotelReports extends Module
));
if ($report === 'occupancy') {
$occupancyData = AdminStatsController::getOccupancyData($dateFrom, $dateTo, $idHotel ?: false);
$dailyOccupied = HotelBookingDetail::getOccupiedRoomsForDiscreteDates($baseParams);
$dailyBooked = HotelBookingDetail::getOccupiedRoomsForDiscreteDates(
$dailyOccupied = HotelBookingDetail::getOccupiedRoomsForDiscreteDates($baseParams);
$dailyBooked = HotelBookingDetail::getOccupiedRoomsForDiscreteDates(
array_merge($baseParams, array('id_status' => HotelBookingDetail::STATUS_ALLOTED))
);
$dailyCheckedIn = HotelBookingDetail::getOccupiedRoomsForDiscreteDates(
$dailyCheckedIn = HotelBookingDetail::getOccupiedRoomsForDiscreteDates(
array_merge($baseParams, array('id_status' => HotelBookingDetail::STATUS_CHECKED_IN))
);
$dailyAvailable = AdminStatsController::getAvailableRoomsForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
$dailyRevenue = AdminStatsController::getRoomsRevenueForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
$dailyAdr = AdminStatsController::getAverageDailyRateForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
$numNights = max(1, (int)(new DateTime($dateTo))->diff(new DateTime($dateFrom))->days);
$totalRooms = $numNights ? (int) round($occupancyData['count_total'] / $numNights) : 0;
if ($idProduct) {
$totalRooms = (int) AdminStatsController::getTotalRooms($idHotel ?: null, 1, $idProduct);
$dailyRevenue = HotelBookingDetail::getDatewiseRoomRevenue($baseParams);
$dailyAvailable = array();
$dailyAdr = array();
foreach ($dailyOccupied as $ts => $occ) {
$dailyAvailable[$ts] = max(0, $totalRooms - $occ);
$rev = isset($dailyRevenue[$ts]) ? (float) $dailyRevenue[$ts] : 0.0;
$dailyAdr[$ts] = $occ ? round($rev / $occ, 2) : 0.0;
}
} else {
$occupancyData = AdminStatsController::getOccupancyData($dateFrom, $dateTo, $idHotel ?: false);
$numNights = max(1, (int)(new DateTime($dateTo))->diff(new DateTime($dateFrom))->days);
$totalRooms = $numNights ? (int) round($occupancyData['count_total'] / $numNights) : 0;
$dailyAvailable = AdminStatsController::getAvailableRoomsForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
$dailyRevenue = AdminStatsController::getRoomsRevenueForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
$dailyAdr = AdminStatsController::getAverageDailyRateForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
}
$dailyRows = array();
foreach ($dailyOccupied as $timestamp=> $occupied) {
$available = isset($dailyAvailable[$timestamp]) ? (int) $dailyAvailable[$timestamp] : 0;
$unavailable = max(0, $totalRooms - $occupied - $available);
$revenue = isset($dailyRevenue[$timestamp]) ? (float) $dailyRevenue[$timestamp] : 0.0;
$adr = isset($dailyAdr[$timestamp]) ? round((float) $dailyAdr[$timestamp], 2) : 0.0;
$revpar = $totalRooms ? round($revenue / $totalRooms, 2) : 0.0;
foreach ($dailyOccupied as $timestamp => $occupied) {
$available = isset($dailyAvailable[$timestamp]) ? (int) $dailyAvailable[$timestamp] : 0;
$unavailable = max(0, $totalRooms - $occupied - $available);
$revenue = isset($dailyRevenue[$timestamp]) ? (float) $dailyRevenue[$timestamp] : 0.0;
$adr = isset($dailyAdr[$timestamp]) ? round((float) $dailyAdr[$timestamp], 2) : 0.0;
$revpar = $totalRooms ? round($revenue / $totalRooms, 2) : 0.0;
$dailyRows[] = array(
'date' => date('Y-m-d', $timestamp),
'total' => $totalRooms,
'available' => $available,
'rooms_booked' => isset($dailyBooked[$timestamp]) ? (int) $dailyBooked[$timestamp] : 0,
'rooms_occupied' => isset($dailyCheckedIn[$timestamp]) ? (int) $dailyCheckedIn[$timestamp] : 0,
'out_of_order' => $unavailable,
'occupancy_pct' => $totalRooms ? round($occupied / $totalRooms * 100, 1) : 0.0,
'adr' => $adr,
'revpar' => $revpar,
'total_room_revenue'=> round($revenue, 2),
'date' => date('Y-m-d', $timestamp),
'total' => $totalRooms,
'available' => $available,
'rooms_booked' => isset($dailyBooked[$timestamp]) ? (int) $dailyBooked[$timestamp] : 0,
'rooms_occupied' => isset($dailyCheckedIn[$timestamp]) ? (int) $dailyCheckedIn[$timestamp] : 0,
'out_of_order' => $unavailable,
'occupancy_pct' => $totalRooms ? round($occupied / $totalRooms * 100, 1) : 0.0,
'adr' => $adr,
'revpar' => $revpar,
'total_room_revenue' => round($revenue, 2),
);
}
@@ -813,7 +804,7 @@ class QloHotelReports extends Module
'bookings' => 0, 'room_nights' => 0,
'revenue_excl' => 0.0, 'revenue_incl' => 0.0,
'discount_amount' => 0.0, 'refund_amount' => 0.0,
'tax_amount' => 0.0, 'cancellations' => 0,
'tax_amount' => 0.0, 'net_revenue' => 0.0, 'cancellations' => 0,
);
foreach ($sourceRows as $row) {
$sourceTotals['bookings'] += (int) $row['bookings'];
@@ -823,6 +814,7 @@ class QloHotelReports extends Module
$sourceTotals['discount_amount'] += (float) $row['discount_amount'];
$sourceTotals['refund_amount'] += (float) $row['refund_amount'];
$sourceTotals['tax_amount'] += (float) $row['tax_amount'];
$sourceTotals['net_revenue'] += (float) $row['net_revenue'];
$sourceTotals['cancellations'] += (int) $row['cancellations'];
}
$this->context->smarty->assign(array(
@@ -924,7 +916,7 @@ class QloHotelReports extends Module
foreach ($hotels as $hotel) {
$hotelId = (int) $hotel['id'];
$hotelParams = array_merge($baseParams, array('id_hotel' => $hotelId));
$bookings = HotelBookingDetail::getTotalBookings($hotelParams);
$bookings = HotelBookingDetail::getTotalBookings(array_merge($hotelParams, array('count_rows' => true)));
$cancels = HotelBookingDetail::getTotalCancellations($hotelParams);
$roomNights = HotelBookingDetail::getTotalRoomNights($hotelParams);
$roomRevenue = HotelBookingDetail::getTotalRoomRevenue($hotelParams);
@@ -943,7 +935,6 @@ class QloHotelReports extends Module
'bookings' => $bookings,
'cancellations' => $cancels,
'cancel_rate_pct' => $bookings > 0 ? round($cancels / $bookings * 100, 1) : 0.0,
'no_shows' => HotelBookingDetail::getTotalNoShows($hotelParams),
'avg_los' => ($bookings > 0 && $roomNights > 0) ? round($roomNights / $bookings, 1) : 0.0,
'outstanding_balance' => $obRows ? array_sum(array_column($obRows, 'balance_due')) : 0.0,
);
@@ -1000,37 +991,6 @@ class QloHotelReports extends Module
$defaultCurrency = new Currency(Configuration::get('PS_CURRENCY_DEFAULT'));
$csvCurrencyMap = $this->currencySignMap();
if ($report === 'no-show') {
// TODO: No-show logic will be replaced by a dedicated PR. Kept below for reference.
// $rows = HotelBookingDetail::getArrivalsInfo(array(
// 'date_from' => $dateFrom,
// 'date_to' => $dateTo,
// 'id_hotel' => $idHotel ?: false,
// 'id_product' => $idProduct,
// 'id_status' => HotelBookingDetail::STATUS_ALLOTED,
// ));
// $this->attachCurrencyToRows($rows, $csvCurrencyMap, $defaultCurrency->iso_code);
// foreach ($rows as $row) {
// fputcsv($out, array(
// $row['id_order'], $row['customer_name'],
// $row['room_type_name'], $row['room_num'], $row['actual_checkin'],
// number_format((float) $row['total_price_tax_incl'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
// '',
// $row['currency_iso'],
// ));
// }
header('Content-Disposition: attachment; filename="no-show-report-'.$dateFrom.'-to-'.$dateTo.'.csv"');
$out = fopen('php://output', 'w');
fputs($out, "\xEF\xBB\xBF");
fputcsv($out, array(
$this->l('Booking ID'), $this->l('Guest Name'), $this->l('Room Type'),
$this->l('Room No.'), $this->l('Check-in Date'),
$this->l('Total Amount'), $this->l('Penalty Charged'), $this->l('Currency'),
));
fclose($out);
exit;
}
if ($report === 'arrivals' || $report === 'departures') {
if ($report === 'arrivals') {
$rows = HotelBookingDetail::getArrivalsInfo(array(
@@ -1057,21 +1017,15 @@ class QloHotelReports extends Module
$this->l('Booking ID'), $this->l('Guest Name'), $this->l('Hotel'),
$this->l('Room Type'), $this->l('Room No.'), $this->l('Check-in'),
$this->l('Check-out'), $this->l('Nights'), $this->l('Adults'),
$this->l('Children'), $this->l('Total (incl. Tax)'), $this->l('Status'),
$this->l('Children'), $this->l('Total (incl. Tax)'),
$this->l('Currency'),
));
$statusLabels = array(
HotelBookingDetail::STATUS_ALLOTED => $this->l('Allotted'),
HotelBookingDetail::STATUS_CHECKED_IN => $this->l('Checked In'),
HotelBookingDetail::STATUS_CHECKED_OUT => $this->l('Checked Out'),
);
foreach ($rows as $row) {
fputcsv($out, array(
$row['id_order'], $row['customer_name'], $row['hotel_name'],
$row['room_type_name'], $row['room_num'], $row['actual_checkin'],
$row['actual_checkout'], $row['los'], $row['adults'], $row['children'],
$row['total_price_tax_incl'],
isset($statusLabels[$row['id_status']]) ? $statusLabels[$row['id_status']] : $row['id_status'],
$row['currency_iso'],
));
}
@@ -1128,7 +1082,6 @@ class QloHotelReports extends Module
$this->l('Check-in Date'),
$this->l('Cancellation Date'),
$this->l('Cancellation Reason'),
$this->l('Cancellation Remark'),
$this->l('Refund Amount'),
$this->l('Refund Status'),
$this->l('Booking Date'),
@@ -1143,7 +1096,6 @@ class QloHotelReports extends Module
$row['hotel_check_in'],
isset($row['cancellation_date']) ? $row['cancellation_date'] : '',
isset($row['cancellation_reason']) ? $row['cancellation_reason'] : '',
'',
isset($row['refunded_amount']) ? number_format((float) $row['refunded_amount'], _PS_PRICE_DISPLAY_PRECISION_, '.', '') : '0.00',
isset($row['refund_status']) ? $row['refund_status'] : '',
isset($row['booking_date']) ? $row['booking_date'] : '',
@@ -1265,25 +1217,27 @@ class QloHotelReports extends Module
$out = fopen('php://output', 'w');
fputs($out, "\xEF\xBB\xBF");
fputcsv($out, array(
$this->l('Date'), $this->l('Booking ID'), $this->l('Reference'),
$this->l('Guest Name'), $this->l('Payment Method'), $this->l('Payment Type'),
$this->l('Amount'), $this->l('Currency'), $this->l('Transaction ID'),
$this->l('Payment Status'),
$this->l('Payment Date'), $this->l('Payment ID'), $this->l('Booking ID'),
$this->l('Booking Ref.'), $this->l('Guest Name'),
$this->l('Payment Method'), $this->l('Payment Type'),
$this->l('Amount'), $this->l('Payment Status'), $this->l('Transaction Reference'),
$this->l('Currency'),
));
foreach ($rows as $row) {
$rate = isset($row['conversion_rate']) ? (float) $row['conversion_rate'] : 1.0;
if ($rate <= 0) { $rate = 1.0; }
fputcsv($out, array(
isset($row['date_add']) ? $row['date_add'] : '',
isset($row['date_add']) ? $row['date_add'] : '',
isset($row['id_order_payment']) ? (int) $row['id_order_payment'] : '',
$row['id_order'],
$row['reference'],
$row['customer_name'],
$row['payment_method'],
isset($row['payment_type']) ? $row['payment_type'] : '',
isset($row['payment_type']) ? $row['payment_type'] : '',
number_format((float) $row['amount'] / $rate, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
$currencyIso,
isset($row['transaction_id']) ? $row['transaction_id'] : '',
$this->l('Success'),
isset($row['transaction_id']) ? $row['transaction_id'] : '',
$currencyIso,
));
}
} elseif ($report === 'refund') {
@@ -1299,21 +1253,20 @@ class QloHotelReports extends Module
$out = fopen('php://output', 'w');
fputs($out, "\xEF\xBB\xBF");
fputcsv($out, array(
$this->l('Booking ID'), $this->l('Guest Name'), $this->l('Booking Date'),
$this->l('Cancellation Date'), $this->l('Check-in'), $this->l('Check-out'),
$this->l('Original Booking Amount'), $this->l('Refunded Amount'),
$this->l('Refund Status'), $this->l('Reason'), $this->l('Currency'),
$this->l('Refund Date'), $this->l('Refund ID'), $this->l('Booking ID'),
$this->l('Guest Name'), $this->l('Original Booking Amount'), $this->l('Refund Amount'),
$this->l('Refund Method'), $this->l('Refund Status'), $this->l('Refund Remark'),
$this->l('Currency'),
));
foreach ($rows as $row) {
fputcsv($out, array(
isset($row['cancellation_date']) ? $row['cancellation_date'] : '',
isset($row['id_order_return']) ? (int) $row['id_order_return'] : '',
$row['id_order'],
$row['customer_name'],
isset($row['booking_date']) ? $row['booking_date'] : '',
isset($row['cancellation_date']) ? $row['cancellation_date'] : '',
$row['date_from'],
$row['date_to'],
number_format((float) $row['total_price_tax_incl'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
number_format((float) $row['refunded_amount'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
isset($row['refund_method']) ? $row['refund_method'] : '',
isset($row['refund_status']) ? $row['refund_status'] : '',
isset($row['cancellation_reason']) ? $row['cancellation_reason'] : '',
$currencyIso,
@@ -1367,12 +1320,12 @@ class QloHotelReports extends Module
$out = fopen('php://output', 'w');
fputs($out, "\xEF\xBB\xBF");
fputcsv($out, array(
$this->l('Booking ID'), $this->l('Reference'), $this->l('Guest Name'),
$this->l('Email'), $this->l('Phone'),
$this->l('Booking ID'), $this->l('Guest Name'),
$this->l('Guest Email'), $this->l('Guest Phone'),
$this->l('Room Type'), $this->l('Room No.'),
$this->l('Check-in'), $this->l('Check-out'),
$this->l('Total Charges'), $this->l('Total Paid'), $this->l('Balance Due'),
$this->l('Days Overdue'), $this->l('Last Payment Date'), $this->l('Status'),
$this->l('Check-in Date'), $this->l('Check-out Date'),
$this->l('Total Charges'), $this->l('Total Paid'), $this->l('Outstanding Balance'),
$this->l('Days Overdue'), $this->l('Last Payment Date'), $this->l('Booking Status'),
$this->l('Currency'),
));
$bookingStatusLabels = array(
@@ -1385,7 +1338,6 @@ class QloHotelReports extends Module
? $bookingStatusLabels[(int) $row['id_status']] : '';
fputcsv($out, array(
$row['id_order'],
$row['reference'],
$row['customer_name'],
isset($row['email']) ? $row['email'] : '',
isset($row['phone']) ? $row['phone'] : '',
@@ -1475,9 +1427,10 @@ class QloHotelReports extends Module
$currencyIso = (new Currency(Configuration::get('PS_CURRENCY_DEFAULT')))->iso_code;
$idProduct = (int) Tools::getValue('id_product', 0);
$baseParams = array(
'date_from' => $dateFrom,
'date_to' => $dateTo,
'id_hotel' => $idHotel ?: false,
'date_from' => $dateFrom,
'date_to' => $dateTo,
'id_hotel' => $idHotel ?: false,
'id_product' => $idProduct ?: 0,
);
header('Content-Type: text/csv; charset=utf-8');
@@ -1564,7 +1517,7 @@ class QloHotelReports extends Module
$this->l('Total Revenue (excl. Tax)'), $this->l('Tax Amount'),
$this->l('Total Revenue (incl. Tax)'),
$this->l('ADR'), $this->l('RevPAR'),
$this->l('Cancellation Count'), $this->l('No-Show Count'), $this->l('Avg. LOS'),
$this->l('Cancellation Count'), $this->l('Avg. LOS'),
$this->l('Currency'),
));
foreach ($rows as $row) {
@@ -1581,13 +1534,12 @@ class QloHotelReports extends Module
number_format((float) $row['adr'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
number_format((float) $row['revpar'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
(int) $row['cancel_count'],
(int) $row['no_show_count'],
number_format((float) $row['avg_los'], 1, '.', ''),
$currencyIso,
));
}
} else {
$totalRoomsOcc = AdminStatsController::getTotalRooms($idHotel ?: null, 1);
$totalRoomsOcc = (int) AdminStatsController::getTotalRooms($idHotel ?: null, 1, $idProduct ?: null);
$dailyAllOcc = HotelBookingDetail::getOccupiedRoomsForDiscreteDates($baseParams);
$dailyBooked = HotelBookingDetail::getOccupiedRoomsForDiscreteDates(
array_merge($baseParams, array('id_status' => HotelBookingDetail::STATUS_ALLOTED))
@@ -1595,9 +1547,20 @@ class QloHotelReports extends Module
$dailyCheckedIn = HotelBookingDetail::getOccupiedRoomsForDiscreteDates(
array_merge($baseParams, array('id_status' => HotelBookingDetail::STATUS_CHECKED_IN))
);
$dailyAvailable = AdminStatsController::getAvailableRoomsForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
$dailyRevenue = AdminStatsController::getRoomsRevenueForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
$dailyAdrOcc = AdminStatsController::getAverageDailyRateForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
if ($idProduct) {
$dailyRevenue = HotelBookingDetail::getDatewiseRoomRevenue($baseParams);
$dailyAvailable = array();
$dailyAdrOcc = array();
foreach ($dailyAllOcc as $ts => $occ) {
$dailyAvailable[$ts] = max(0, $totalRoomsOcc - $occ);
$rev = isset($dailyRevenue[$ts]) ? (float) $dailyRevenue[$ts] : 0.0;
$dailyAdrOcc[$ts] = $occ ? round($rev / $occ, 2) : 0.0;
}
} else {
$dailyAvailable = AdminStatsController::getAvailableRoomsForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
$dailyRevenue = AdminStatsController::getRoomsRevenueForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
$dailyAdrOcc = AdminStatsController::getAverageDailyRateForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
}
header('Content-Disposition: attachment; filename="occupancy-report-'.$dateFrom.'-to-'.$dateTo.'.csv"');
$out = fopen('php://output', 'w');
@@ -1605,7 +1568,7 @@ class QloHotelReports extends Module
fputcsv($out, array(
$this->l('Date'), $this->l('Total Rooms (Inventory)'), $this->l('Available Rooms'),
$this->l('Rooms Booked'), $this->l('Rooms Occupied'), $this->l('Out of Order'),
$this->l('Complimentary'), $this->l('Occupancy Rate %'),
$this->l('Occupancy Rate %'),
$this->l('ADR'), $this->l('RevPAR'), $this->l('Total Room Revenue'),
$this->l('Currency'),
));
@@ -1625,7 +1588,6 @@ class QloHotelReports extends Module
$booked,
$occupied,
$outOfOrder,
'—',
number_format($occupancyPct, 1, '.', '').'%',
number_format($adr, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
number_format($revpar, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
@@ -1871,7 +1833,7 @@ class QloHotelReports extends Module
$this->l('Date'), $this->l('Total Rooms'), $this->l('Rooms Sold'),
$this->l('Occupancy %'), $this->l('ADR'), $this->l('RevPAR'),
$this->l('Total Revenue'), $this->l('Arrivals'), $this->l('Departures'),
$this->l('In-house Guests'), $this->l('Cancellations'), $this->l('No-Shows'),
$this->l('In-house Guests'), $this->l('Cancellations'),
$this->l('Currency'),
));
$current = $dateFrom;
@@ -1891,7 +1853,6 @@ class QloHotelReports extends Module
isset($departuresByDay[$timestamp]) ? (int) $departuresByDay[$timestamp] : 0,
isset($checkedInByDay[$timestamp]) ? (int) $checkedInByDay[$timestamp] : 0,
isset($cancsByDay[$timestamp]) ? (int) $cancsByDay[$timestamp] : 0,
'—',
$currencyIso,
));
$current = date('Y-m-d', strtotime('+1 day', strtotime($current)));
@@ -1906,8 +1867,8 @@ class QloHotelReports extends Module
$this->l('Hotel Name'), $this->l('Total Rooms'), $this->l('Rooms Sold'),
$this->l('Occupancy %'), $this->l('Room Revenue'), $this->l('Extra Service Revenue'),
$this->l('Gross Revenue'), $this->l('ADR'), $this->l('RevPAR'),
$this->l('Total Bookings'), $this->l('Total Cancellations'), $this->l('Cancel Rate %'),
$this->l('No-Shows'), $this->l('Avg LOS'), $this->l('Outstanding Balance'),
$this->l('Total Room Bookings'), $this->l('Total Cancellations'), $this->l('Cancel Rate %'),
$this->l('Avg LOS'), $this->l('Due Amount'),
$this->l('Currency'),
));
foreach ($hotels as $hotel) {
@@ -1921,7 +1882,6 @@ class QloHotelReports extends Module
$extraRev = (float) ServiceProductOrderDetail::getTotalServiceRevenue($hotelParams);
$grossRevenue = $roomRevenue + $extraRev;
$occupancy = round(HotelBookingDetail::getOccupancyRate($hotelParams), 1);
$noShows = (int) HotelBookingDetail::getTotalNoShows($hotelParams);
$avgLos = ($bookings > 0 && $roomNights > 0) ? round($roomNights / $bookings, 1) : 0.0;
$obRows = Order::getOutstandingBalance($hotelParams);
$outstanding = is_array($obRows)
@@ -1939,7 +1899,6 @@ class QloHotelReports extends Module
$bookings,
$cancels,
number_format($cancelRate, 1, '.', '').'%',
$noShows,
number_format($avgLos, 1, '.', ''),
number_format((float) $outstanding, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
$currencyIso,
@@ -1962,9 +1921,8 @@ class QloHotelReports extends Module
fputcsv($out, array(
$this->l('Room No.'), $this->l('Floor'), $this->l('Room Type'),
$this->l('OOO Status'), $this->l('Reason'),
$this->l('Start Date'), $this->l('Expected End Date'), $this->l('Actual End Date'),
$this->l('Start Date'), $this->l('End Date'),
$this->l('Duration (Days)'), $this->l('Current Status'),
$this->l('Marked By'), $this->l('Resolved By'), $this->l('Est. Revenue Loss'),
));
$today = date('Y-m-d');
foreach ($rows as $row) {
@@ -1980,12 +1938,8 @@ class QloHotelReports extends Module
$row['reason'] ? $row['reason'] : '',
$row['disabled_from'],
$row['disabled_to'],
'—',
(int) $row['disabled_days'],
$currentStatus,
'—',
'—',
'—',
));
}
} else {

View File

@@ -218,7 +218,6 @@
<th>{l s='Check-in Date' mod='qlohotelreports'}</th>
<th>{l s='Cancellation Date' mod='qlohotelreports'}</th>
<th>{l s='Cancellation Reason' mod='qlohotelreports'}</th>
<th>{l s='Cancellation Remark' mod='qlohotelreports'}</th>
<th class="text-right">{l s='Refund Amount' mod='qlohotelreports'}</th>
<th>{l s='Refund Status' mod='qlohotelreports'}</th>
<th>{l s='Booking Date' mod='qlohotelreports'}</th>
@@ -235,7 +234,6 @@
<td>{$cancellation.hotel_check_in|escape:'html':'UTF-8'}</td>
<td>{if $cancellation.cancellation_date}{$cancellation.cancellation_date|date_format:'%d-%m-%Y'}{else}<span class="text-muted">—</span>{/if}</td>
<td>{if $cancellation.cancellation_reason}{$cancellation.cancellation_reason|escape:'html':'UTF-8'}{else}<span class="text-muted">—</span>{/if}</td>
<td><span class="text-muted">—</span></td>
<td class="text-right">{displayPrice price=$cancellation.refunded_amount currency=$cancellation.id_currency}</td>
<td>{if $cancellation.refund_status}{$cancellation.refund_status|escape:'html':'UTF-8'}{else}<span class="text-muted">—</span>{/if}</td>
<td>{if $cancellation.booking_date}{$cancellation.booking_date|escape:'html':'UTF-8'}{else}<span class="text-muted">—</span>{/if}</td>
@@ -243,7 +241,7 @@
{/foreach}
{else}
<tr>
<td class="list-empty" colspan="11">
<td class="list-empty" colspan="10">
<div class="list-empty-msg">
<i class="icon-warning-sign list-empty-icon"></i>
{l s='No cancellations found for the selected date range.' mod='qlohotelreports'}
@@ -267,62 +265,6 @@
</div>
{/if}
{* ── No-Show Report ──────────────────────────────────────────────── *}
{if $active_report == 'no-show'}
<div class="row">
<div class="col-lg-12">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>{l s='Booking ID' mod='qlohotelreports'}</th>
<th>{l s='Guest Name' mod='qlohotelreports'}</th>
<th>{l s='Room Type' mod='qlohotelreports'}</th>
<th>{l s='Room No.' mod='qlohotelreports'}</th>
<th>{l s='Check-in Date' mod='qlohotelreports'}</th>
<th class="text-right">{l s='Total Amount' mod='qlohotelreports'}</th>
<th class="text-right">{l s='Penalty Charged' mod='qlohotelreports'}</th>
</tr>
</thead>
<tbody>
{if $no_shows}
{foreach $no_shows as $noShow}
<tr>
<td>{$noShow.id_order|intval}</td>
<td>{$noShow.customer_name|escape:'html':'UTF-8'}</td>
<td>{$noShow.room_type_name|escape:'html':'UTF-8'}</td>
<td>{$noShow.room_num|escape:'html':'UTF-8'}</td>
<td>{$noShow.actual_checkin|escape:'html':'UTF-8'}</td>
<td class="text-right">{displayPrice price=$noShow.total_price_tax_incl currency=$noShow.id_currency}</td>
<td class="text-right"><span class="text-muted">—</span></td>
</tr>
{/foreach}
{else}
<tr>
<td class="list-empty" colspan="7">
<div class="list-empty-msg">
<i class="icon-warning-sign list-empty-icon"></i>
{l s='No no-shows found for the selected date range.' mod='qlohotelreports'}
</div>
</td>
</tr>
{/if}
</tbody>
{if $no_shows}
<tfoot>
<tr class="qlo-report-totals">
<td colspan="5"><strong>{l s='Totals' mod='qlohotelreports'}</strong></td>
<td class="text-right"><strong>{displayPrice price=$no_show_totals.total_price_tax_incl currency=$id_currency}</strong></td>
<td></td>
</tr>
</tfoot>
{/if}
</table>
</div>
</div>
</div>
{/if}
{* ── Arrivals Report ─────────────────────────────────────────────── *}
{if $active_report == 'arrivals'}
<div class="row">

View File

@@ -78,7 +78,6 @@
<th class="text-center">{l s='Total Bookings' mod='qlohotelreports'}</th>
<th class="text-center">{l s='Nights Sold' mod='qlohotelreports'}</th>
<th class="text-right">{l s='Room Revenue (excl. Tax)' mod='qlohotelreports'}</th>
<th class="text-right">{l s='Extra Services Revenue' mod='qlohotelreports'}</th>
<th class="text-right">{l s='Discount Amount' mod='qlohotelreports'}</th>
<th class="text-right">{l s='Refund Amount' mod='qlohotelreports'}</th>
<th class="text-right">{l s='Tax Amount' mod='qlohotelreports'}</th>
@@ -98,12 +97,11 @@
<td class="text-center">{$sourceRow.bookings|intval}</td>
<td class="text-center">{$sourceRow.room_nights|intval}</td>
<td class="text-right">{displayPrice price=$sourceRow.revenue_excl currency=$id_currency}</td>
<td class="text-right">{displayPrice price=0 currency=$id_currency}</td>
<td class="text-right">{displayPrice price=$sourceRow.discount_amount currency=$id_currency}</td>
<td class="text-right">{displayPrice price=$sourceRow.refund_amount currency=$id_currency}</td>
<td class="text-right">{displayPrice price=$sourceRow.tax_amount currency=$id_currency}</td>
<td class="text-right">{displayPrice price=$sourceRow.revenue_incl currency=$id_currency}</td>
<td class="text-right">{displayPrice price=$sourceRow.revenue_excl currency=$id_currency}</td>
<td class="text-right">{displayPrice price=$sourceRow.net_revenue currency=$id_currency}</td>
<td class="text-center">{$sourceRow.cancellations|intval}</td>
<td class="text-center">{$sourceRow.cancel_rate_pct|string_format:'%.1f'}%</td>
<td class="text-right">{displayPrice price=$sourceRow.adr currency=$id_currency}</td>
@@ -112,7 +110,7 @@
{/foreach}
{else}
<tr>
<td class="list-empty" colspan="14">
<td class="list-empty" colspan="13">
<div class="list-empty-msg">
<i class="icon-warning-sign list-empty-icon"></i>
{l s='No bookings found for the selected period.' mod='qlohotelreports'}
@@ -128,12 +126,11 @@
<td class="text-center"><strong>{$source_totals.bookings|intval}</strong></td>
<td class="text-center"><strong>{$source_totals.room_nights|intval}</strong></td>
<td class="text-right"><strong>{displayPrice price=$source_totals.revenue_excl currency=$id_currency}</strong></td>
<td class="text-right"><strong>{displayPrice price=0 currency=$id_currency}</strong></td>
<td class="text-right"><strong>{displayPrice price=$source_totals.discount_amount currency=$id_currency}</strong></td>
<td class="text-right"><strong>{displayPrice price=$source_totals.refund_amount currency=$id_currency}</strong></td>
<td class="text-right"><strong>{displayPrice price=$source_totals.tax_amount currency=$id_currency}</strong></td>
<td class="text-right"><strong>{displayPrice price=$source_totals.revenue_incl currency=$id_currency}</strong></td>
<td class="text-right"><strong>{displayPrice price=$source_totals.revenue_excl currency=$id_currency}</strong></td>
<td class="text-right"><strong>{displayPrice price=$source_totals.net_revenue currency=$id_currency}</strong></td>
<td class="text-center"><strong>{$source_totals.cancellations|intval}</strong></td>
<td colspan="3"></td>
</tr>

View File

@@ -99,7 +99,6 @@
<th class="text-center">{l s='Rooms Booked' mod='qlohotelreports'}</th>
<th class="text-center">{l s='Rooms Occupied' mod='qlohotelreports'}</th>
<th class="text-center">{l s='Out of Order' mod='qlohotelreports'}</th>
<th class="text-center">{l s='Complimentary' mod='qlohotelreports'}</th>
<th class="text-center">{l s='Occupancy Rate %' mod='qlohotelreports'}</th>
<th class="text-right">{l s='ADR (Avg. Daily Rate)' mod='qlohotelreports'}</th>
<th class="text-right">{l s='RevPAR (Rev. Per Avail. Room)' mod='qlohotelreports'}</th>
@@ -116,7 +115,6 @@
<td class="text-center">{$occupancyRow.rooms_booked|intval}</td>
<td class="text-center">{$occupancyRow.rooms_occupied|intval}</td>
<td class="text-center">{$occupancyRow.out_of_order|intval}</td>
<td class="text-center">&mdash;</td>
<td class="text-center">{$occupancyRow.occupancy_pct|string_format:'%.1f'}%</td>
<td class="text-right">{displayPrice price=$occupancyRow.adr currency=$id_currency}</td>
<td class="text-right">{displayPrice price=$occupancyRow.revpar currency=$id_currency}</td>
@@ -125,7 +123,7 @@
{/foreach}
{else}
<tr>
<td class="list-empty" colspan="11">
<td class="list-empty" colspan="10">
<div class="list-empty-msg">
<i class="icon-warning-sign list-empty-icon"></i>
{l s='No data for the selected period.' mod='qlohotelreports'}
@@ -391,7 +389,6 @@
<th class="text-right">{l s='ADR (Avg. Daily Rate)' mod='qlohotelreports'}</th>
<th class="text-right">{l s='RevPAR (Rev. Per Avail. Room)' mod='qlohotelreports'}</th>
<th class="text-center">{l s='Cancellation Count' mod='qlohotelreports'}</th>
<th class="text-center">{l s='No-Show Count' mod='qlohotelreports'}</th>
<th class="text-center">{l s='Avg. Length of Stay' mod='qlohotelreports'}</th>
</tr>
</thead>
@@ -411,13 +408,12 @@
<td class="text-right">{displayPrice price=$perfRow.adr currency=$id_currency}</td>
<td class="text-right">{displayPrice price=$perfRow.revpar currency=$id_currency}</td>
<td class="text-center">{$perfRow.cancel_count|intval}</td>
<td class="text-center">{$perfRow.no_show_count|intval}</td>
<td class="text-center">{$perfRow.avg_los|string_format:'%.1f'}</td>
</tr>
{/foreach}
{else}
<tr>
<td class="list-empty" colspan="14">
<td class="list-empty" colspan="13">
<div class="list-empty-msg">
<i class="icon-warning-sign list-empty-icon"></i>
{l s='No data for the selected period.' mod='qlohotelreports'}
@@ -435,7 +431,6 @@
{assign var="tot_tax" value=0}
{assign var="tot_total_rev" value=0}
{assign var="tot_cancels" value=0}
{assign var="tot_noshows" value=0}
{foreach $roomTypePerformance_rows as $perfRow}
{assign var="tot_rooms" value=$tot_rooms+$perfRow.total_rooms}
{assign var="tot_nights_avail" value=$tot_nights_avail+$perfRow.total_nights_available}
@@ -444,7 +439,6 @@
{assign var="tot_tax" value=$tot_tax+$perfRow.tax_amount}
{assign var="tot_total_rev" value=$tot_total_rev+$perfRow.total_revenue}
{assign var="tot_cancels" value=$tot_cancels+$perfRow.cancel_count}
{assign var="tot_noshows" value=$tot_noshows+$perfRow.no_show_count}
{/foreach}
<tr>
<td colspan="2"><strong>{l s='Total' mod='qlohotelreports'}</strong></td>
@@ -458,7 +452,6 @@
<td class="text-right">&mdash;</td>
<td class="text-right">&mdash;</td>
<td class="text-center"><strong>{$tot_cancels|intval}</strong></td>
<td class="text-center"><strong>{$tot_noshows|intval}</strong></td>
<td class="text-center">&mdash;</td>
</tr>
</tfoot>

View File

@@ -105,7 +105,6 @@
<th class="text-center">{l s='Departures' mod='qlohotelreports'}</th>
<th class="text-center">{l s='In-house Guests' mod='qlohotelreports'}</th>
<th class="text-center">{l s='Cancellations' mod='qlohotelreports'}</th>
<th class="text-center">{l s='No-Shows' mod='qlohotelreports'}</th>
</tr>
</thead>
<tbody>
@@ -123,12 +122,11 @@
<td class="text-center">{$dailySummaryRow.departures|intval}</td>
<td class="text-center">{$dailySummaryRow.inhouse_guests|intval}</td>
<td class="text-center">{$dailySummaryRow.cancels|intval}</td>
<td class="text-center">&mdash;</td>
</tr>
{/foreach}
{else}
<tr>
<td class="list-empty" colspan="12">
<td class="list-empty" colspan="11">
<div class="list-empty-msg">
<i class="icon-warning-sign list-empty-icon"></i>
{l s='No data for the selected period.' mod='qlohotelreports'}
@@ -162,12 +160,11 @@
<th class="text-right">{l s='Gross Revenue' mod='qlohotelreports'}</th>
<th class="text-right">{l s='ADR (Avg. Daily Rate)' mod='qlohotelreports'}</th>
<th class="text-right">{l s='RevPAR (Rev. Per Avail. Room)' mod='qlohotelreports'}</th>
<th class="text-center">{l s='Total Bookings' mod='qlohotelreports'}</th>
<th class="text-center">{l s='Total Room Bookings' mod='qlohotelreports'}</th>
<th class="text-center">{l s='Total Cancellations' mod='qlohotelreports'}</th>
<th class="text-center">{l s='Cancel Rate %' mod='qlohotelreports'}</th>
<th class="text-center">{l s='No-Shows' mod='qlohotelreports'}</th>
<th class="text-center">{l s='Avg LOS' mod='qlohotelreports'}</th>
<th class="text-right">{l s='Outstanding Balance' mod='qlohotelreports'}</th>
<th class="text-right">{l s='Due Amount' mod='qlohotelreports'}</th>
</tr>
</thead>
<tbody>
@@ -186,14 +183,13 @@
<td class="text-center">{$hotelRow.bookings|intval}</td>
<td class="text-center">{$hotelRow.cancellations|intval}</td>
<td class="text-center">{$hotelRow.cancel_rate_pct|string_format:'%.1f'}%</td>
<td class="text-center">{$hotelRow.no_shows|intval}</td>
<td class="text-center">{$hotelRow.avg_los|string_format:'%.1f'}</td>
<td class="text-right">{displayPrice price=$hotelRow.outstanding_balance currency=$id_currency}</td>
</tr>
{/foreach}
{else}
<tr>
<td class="list-empty" colspan="15">
<td class="list-empty" colspan="14">
<div class="list-empty-msg">
<i class="icon-warning-sign list-empty-icon"></i>
{l s='No hotels found.' mod='qlohotelreports'}
@@ -224,13 +220,9 @@
<th class="text-center">{l s='OOO Status' mod='qlohotelreports'}</th>
<th>{l s='Reason' mod='qlohotelreports'}</th>
<th>{l s='Start Date' mod='qlohotelreports'}</th>
<th>{l s='Expected End Date' mod='qlohotelreports'}</th>
<th>{l s='Actual End Date' mod='qlohotelreports'}</th>
<th>{l s='End Date' mod='qlohotelreports'}</th>
<th class="text-center">{l s='Duration (Days)' mod='qlohotelreports'}</th>
<th class="text-center">{l s='Current Status' mod='qlohotelreports'}</th>
<th>{l s='Marked By' mod='qlohotelreports'}</th>
<th>{l s='Resolved By' mod='qlohotelreports'}</th>
<th class="text-right">{l s='Est. Revenue Loss' mod='qlohotelreports'}</th>
</tr>
</thead>
<tbody>
@@ -249,7 +241,6 @@
<td>{if $outOfOrderRow.reason}{$outOfOrderRow.reason|escape:'html':'UTF-8'}{else}&mdash;{/if}</td>
<td>{if $outOfOrderRow.disabled_from}{$outOfOrderRow.disabled_from|escape:'html':'UTF-8'}{else}&mdash;{/if}</td>
<td>{if $outOfOrderRow.disabled_to}{$outOfOrderRow.disabled_to|escape:'html':'UTF-8'}{else}&mdash;{/if}</td>
<td>&mdash;</td>
<td class="text-center">{if $outOfOrderRow.disabled_days}{$outOfOrderRow.disabled_days|intval}{else}&mdash;{/if}</td>
<td class="text-center">
{if !$outOfOrderRow.disabled_to || $outOfOrderRow.disabled_to >= $today}
@@ -258,14 +249,11 @@
<span class="label label-success">{l s='Resolved' mod='qlohotelreports'}</span>
{/if}
</td>
<td>&mdash;</td>
<td>&mdash;</td>
<td class="text-right">&mdash;</td>
</tr>
{/foreach}
{else}
<tr>
<td class="list-empty" colspan="13">
<td class="list-empty" colspan="9">
<div class="list-empty-msg">
<i class="icon-warning-sign list-empty-icon"></i>
{l s='No out of order rooms for the selected period.' mod='qlohotelreports'}

View File

@@ -169,7 +169,7 @@
<td class="text-right">{displayPrice price=$revenueRow.service_revenue currency=$id_currency}</td>
<td class="text-right">{displayPrice price=$revenueRow.discounts currency=$id_currency}</td>
<td class="text-right">{displayPrice price=$revenueRow.tax_amount currency=$id_currency}</td>
<td class="text-right">{displayPrice price=0 currency=$id_currency}</td>
<td class="text-right">{displayPrice price=$revenueRow.refund_amount currency=$id_currency}</td>
<td class="text-right">{displayPrice price=$revenueRow.total_collection currency=$id_currency}</td>
<td class="text-right">{displayPrice price=$revenueRow.net_revenue currency=$id_currency}</td>
<td class="text-right">{displayPrice price=$revenueRow.adr currency=$id_currency}</td>
@@ -198,7 +198,7 @@
<td class="text-right"><strong>{displayPrice price=$revenue_totals.service_revenue currency=$id_currency}</strong></td>
<td class="text-right"><strong>{displayPrice price=$revenue_totals.discounts currency=$id_currency}</strong></td>
<td class="text-right"><strong>{displayPrice price=$revenue_totals.tax_amount currency=$id_currency}</strong></td>
<td class="text-right"><strong>{displayPrice price=0 currency=$id_currency}</strong></td>
<td class="text-right"><strong>{displayPrice price=$revenue_totals.refund_amount currency=$id_currency}</strong></td>
<td class="text-right"><strong>{displayPrice price=$revenue_totals.total_collection currency=$id_currency}</strong></td>
<td class="text-right"><strong>{displayPrice price=$revenue_totals.net_revenue currency=$id_currency}</strong></td>
<td colspan="3"></td>
@@ -227,10 +227,7 @@
<th class="text-right">{l s='Refund Amount' mod='qlohotelreports'}</th>
<th>{l s='Refund Method' mod='qlohotelreports'}</th>
<th>{l s='Refund Status' mod='qlohotelreports'}</th>
<th>{l s='Processed Date' mod='qlohotelreports'}</th>
<th>{l s='Processed By' mod='qlohotelreports'}</th>
<th>{l s='Refund Reason' mod='qlohotelreports'}</th>
<th>{l s='Remarks' mod='qlohotelreports'}</th>
<th>{l s='Refund Remark' mod='qlohotelreports'}</th>
</tr>
</thead>
<tbody>
@@ -245,15 +242,12 @@
<td class="text-right">{displayPrice price=$refund.refunded_amount currency=$refund.id_currency}</td>
<td>{if $refund.refund_method}{$refund.refund_method|escape:'html':'UTF-8'}{else}<span class="text-muted">—</span>{/if}</td>
<td>{if $refund.refund_status}{$refund.refund_status|escape:'html':'UTF-8'}{else}<span class="text-muted">—</span>{/if}</td>
<td>{if $refund.processed_date}{$refund.processed_date|date_format:'%d-%m-%Y'}{else}<span class="text-muted">—</span>{/if}</td>
<td>{if $refund.processed_by}{$refund.processed_by|escape:'html':'UTF-8'}{else}<span class="text-muted">—</span>{/if}</td>
<td>{if $refund.cancellation_reason}{$refund.cancellation_reason|escape:'html':'UTF-8'}{else}<span class="text-muted">—</span>{/if}</td>
<td><span class="text-muted">—</span></td>
</tr>
{/foreach}
{else}
<tr>
<td class="list-empty" colspan="12">
<td class="list-empty" colspan="9">
<div class="list-empty-msg">
<i class="icon-warning-sign list-empty-icon"></i>
{l s='No refunds found for the selected date range.' mod='qlohotelreports'}
@@ -267,7 +261,7 @@
<tr class="qlo-report-totals">
<td colspan="5"><strong>{l s='Total Refunded' mod='qlohotelreports'}</strong></td>
<td class="text-right"><strong>{displayPrice price=$total_refunded currency=$id_currency}</strong></td>
<td colspan="6"></td>
<td colspan="3"></td>
</tr>
</tfoot>
{/if}
@@ -295,7 +289,6 @@
<th class="text-right">{l s='Amount' mod='qlohotelreports'}</th>
<th>{l s='Payment Status' mod='qlohotelreports'}</th>
<th>{l s='Transaction Reference' mod='qlohotelreports'}</th>
<th>{l s='Received By' mod='qlohotelreports'}</th>
</tr>
</thead>
<tbody>
@@ -314,12 +307,11 @@
<td class="text-right">{displayPrice price=$payment.amount currency=$payment.id_currency}</td>
<td>{l s='Success' mod='qlohotelreports'}</td>
<td>{if $payment.transaction_id}{$payment.transaction_id|escape:'html':'UTF-8'}{else}<span class="text-muted">—</span>{/if}</td>
<td><span class="text-muted">—</span></td>
</tr>
{/foreach}
{else}
<tr>
<td class="list-empty" colspan="11">
<td class="list-empty" colspan="10">
<div class="list-empty-msg">
<i class="icon-warning-sign list-empty-icon"></i>
{l s='No payments found for the selected date range.' mod='qlohotelreports'}
@@ -333,7 +325,7 @@
<tr class="qlo-report-totals">
<td colspan="7"><strong>{l s='Total Collected' mod='qlohotelreports'}</strong></td>
<td class="text-right"><strong>{displayPrice price=$total_payments currency=$id_currency}</strong></td>
<td colspan="3"></td>
<td colspan="2"></td>
</tr>
</tfoot>
{/if}