@@ -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').'
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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`)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">—</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">—</td>
|
||||
<td class="text-right">—</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">—</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
|
||||
@@ -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">—</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}—{/if}</td>
|
||||
<td>{if $outOfOrderRow.disabled_from}{$outOfOrderRow.disabled_from|escape:'html':'UTF-8'}{else}—{/if}</td>
|
||||
<td>{if $outOfOrderRow.disabled_to}{$outOfOrderRow.disabled_to|escape:'html':'UTF-8'}{else}—{/if}</td>
|
||||
<td>—</td>
|
||||
<td class="text-center">{if $outOfOrderRow.disabled_days}{$outOfOrderRow.disabled_days|intval}{else}—{/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>—</td>
|
||||
<td>—</td>
|
||||
<td class="text-right">—</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'}
|
||||
|
||||
@@ -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}
|
||||
|
||||
Reference in New Issue
Block a user