1890 lines
100 KiB
PHP
1890 lines
100 KiB
PHP
<?php
|
|
|
|
/**
|
|
* NOTICE OF LICENSE
|
|
*
|
|
* This source file is subject to the Open Software License version 3.0
|
|
* that is bundled with this package in the file LICENSE.md
|
|
* It is also available through the world-wide-web at this URL:
|
|
* https://opensource.org/license/osl-3.0-php
|
|
* If you did not receive a copy of the license and are unable to
|
|
* obtain it through the world-wide-web, please send an email
|
|
* to support@qloapps.com so we can send you a copy immediately.
|
|
*
|
|
* DISCLAIMER
|
|
*
|
|
* Do not edit or add to this file if you wish to upgrade this module to a newer
|
|
* versions in the future. If you wish to customize this module for your needs
|
|
* please refer to https://store.webkul.com/customisation-guidelines for more information.
|
|
*
|
|
* @author Webkul IN
|
|
* @copyright Since 2010 Webkul
|
|
* @license https://opensource.org/license/osl-3.0-php Open Software License version 3.0
|
|
*/
|
|
|
|
if (!defined('_PS_VERSION_')) {
|
|
exit;
|
|
}
|
|
|
|
class QloHotelReports extends Module
|
|
{
|
|
public const REPORT_DEFAULT = 'reservation';
|
|
|
|
/**
|
|
* Flat report registry: each report is its own tab.
|
|
* key => array('label' => translatable string, 'tpl' => template filename)
|
|
* Labels are plain English; call $this->l($config['label']) at render time.
|
|
*/
|
|
private static $reports = array(
|
|
'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'),
|
|
|
|
'cancellation' => array('label' => 'Cancellations', 'tpl' => 'group-bookings.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' => 'Order Due Amount', 'tpl' => 'group-revenue.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' => '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'),
|
|
'out-of-order' => array('label' => 'Out of Order Rooms', 'tpl' => 'group-property.tpl'),
|
|
);
|
|
|
|
public function __construct()
|
|
{
|
|
$this->name = 'qlohotelreports';
|
|
$this->tab = 'analytics_stats';
|
|
$this->version = '1.0.0';
|
|
$this->ps_versions_compliancy = array('min' => '1.6', 'max' => '1.6');
|
|
$this->author = 'Webkul';
|
|
$this->need_instance = 0;
|
|
$this->bootstrap = true;
|
|
|
|
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?');
|
|
}
|
|
|
|
public function install()
|
|
{
|
|
return parent::install()
|
|
&& $this->registerHook('AdminStatsModules')
|
|
&& $this->registerHook('actionAdminControllerSetMedia');
|
|
}
|
|
|
|
/**
|
|
* Returns sidebar tab definitions for AdminStatsTabController.
|
|
* Each entry becomes a separate sidebar link in the Stats left nav.
|
|
*
|
|
* @return array array of ['key' => string, 'label' => string]
|
|
*/
|
|
public function getStatsTabs()
|
|
{
|
|
$tabs = array();
|
|
foreach (self::$reports as $key => $config) {
|
|
$tabs[] = array('key' => $key, 'label' => $this->l($config['label']));
|
|
}
|
|
return $tabs;
|
|
}
|
|
|
|
public function hookActionAdminControllerSetMedia()
|
|
{
|
|
if (get_class($this->context->controller) == 'AdminStatsController') {
|
|
$this->context->controller->addCSS($this->_path . 'views/css/' . $this->name . '.css');
|
|
$this->context->controller->addJs($this->_path . 'views/js/' . $this->name . '.js');
|
|
}
|
|
}
|
|
|
|
public function hookAdminStatsModules($params)
|
|
{
|
|
$report = Tools::getValue('tab', self::REPORT_DEFAULT);
|
|
if (!array_key_exists($report, self::$reports)) {
|
|
$report = self::REPORT_DEFAULT;
|
|
}
|
|
|
|
$dateFrom = $this->context->employee->stats_date_from;
|
|
$dateTo = $this->context->employee->stats_date_to;
|
|
$idHotel = (int) Tools::getValue('id_hotel', 0);
|
|
$idLang = (int) $this->context->language->id;
|
|
$hotels = (new HotelBranchInformation())->hotelsNameAndId();
|
|
$currency = new Currency(Configuration::get('PS_CURRENCY_DEFAULT'));
|
|
|
|
if (Tools::getValue('export')) {
|
|
$bookingReports = array('reservation', 'arrivals', 'departures', 'in-house', 'cancellation');
|
|
$revenueReports = array('revenue', 'refund', 'payment', 'tax', 'outstanding');
|
|
$occupancyReports = array('occupancy', 'availability', 'room-status', 'room-perf');
|
|
|
|
if (in_array($report, $bookingReports)) {
|
|
$this->exportBookingsCsv($dateFrom, $dateTo, $idHotel, $report);
|
|
} elseif (in_array($report, $revenueReports)) {
|
|
$this->exportRevenueCsv($dateFrom, $dateTo, $idHotel, (int) Tools::getValue('id_product', 0), $report);
|
|
} elseif (in_array($report, $occupancyReports)) {
|
|
$this->exportOccupancyCsv($dateFrom, $dateTo, $idHotel, $idLang, $report);
|
|
} elseif ($report === 'services' || $report === 'guest-directory') {
|
|
$this->exportGuestsCsv($dateFrom, $dateTo, $idHotel, $report);
|
|
} elseif ($report === 'source' || $report === 'payment-method') {
|
|
$this->exportChannelsCsv($dateFrom, $dateTo, $idHotel, $report);
|
|
} else {
|
|
$this->exportPropertyCsv($dateFrom, $dateTo, $idHotel, $idLang, $report);
|
|
}
|
|
return '';
|
|
}
|
|
|
|
$baseUrl = $this->context->link->getAdminLink('AdminStats') . '&module=' . $this->name;
|
|
|
|
$this->context->smarty->assign(array(
|
|
'active_report' => $report,
|
|
'report_label' => $this->l(self::$reports[$report]['label']),
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'hotels' => $hotels,
|
|
'id_hotel' => $idHotel,
|
|
'id_currency' => $currency->id,
|
|
'group_tpl_path' => _PS_MODULE_DIR_ . $this->name . '/views/templates/hook/'
|
|
. self::$reports[$report]['tpl'],
|
|
'order_link' => $this->context->link->getAdminLink('AdminOrders'),
|
|
'customer_link' => $this->context->link->getAdminLink('AdminCustomers'),
|
|
'hotel_link' => $this->context->link->getAdminLink('AdminAddHotel'),
|
|
'room_type_link' => $this->context->link->getAdminLink('AdminProducts'),
|
|
'service_link' => $this->context->link->getAdminLink('AdminNormalProducts'),
|
|
));
|
|
|
|
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);
|
|
$idOrderState = (int) Tools::getValue('id_order_state', 0);
|
|
|
|
$filterBaseUrl = $baseUrl . '&tab=' . $report
|
|
. ($idHotel ? '&id_hotel=' . $idHotel : '')
|
|
. ($idProduct ? '&id_product=' . $idProduct : '');
|
|
|
|
$this->context->smarty->assign(array(
|
|
'room_types' => HotelRoomType::getRoomTypes(array(
|
|
'id_hotel' => $idHotel,
|
|
'id_lang' => $idLang,
|
|
)),
|
|
'filter_id_product' => $idProduct,
|
|
'filter_booking_status' => $idStatus,
|
|
'filter_booking_type' => $bookingType,
|
|
'filter_id_order_state' => $idOrderState,
|
|
'booking_statuses' => array(
|
|
HotelBookingDetail::STATUS_ALLOTED => $this->l('Allotted'),
|
|
HotelBookingDetail::STATUS_CHECKED_IN => $this->l('Checked In'),
|
|
HotelBookingDetail::STATUS_CHECKED_OUT => $this->l('Checked Out'),
|
|
),
|
|
'booking_sources' => array(
|
|
HotelBookingDetail::ALLOTMENT_AUTO => $this->l('Online / Direct'),
|
|
HotelBookingDetail::ALLOTMENT_MANUAL => $this->l('Walk-in / Admin'),
|
|
),
|
|
'order_states' => OrderState::getOrderStates($idLang),
|
|
'filter_base_url' => $filterBaseUrl,
|
|
'export_url' => $filterBaseUrl
|
|
. ($idProduct ? '&id_product=' . $idProduct : '')
|
|
. ($idStatus ? '&booking_status=' . $idStatus : '')
|
|
. ($bookingType ? '&booking_type=' . $bookingType : '')
|
|
. ($idOrderState ? '&id_order_state=' . $idOrderState : '')
|
|
. '&export=1',
|
|
));
|
|
|
|
$currencyMap = $this->currencySignMap();
|
|
|
|
if ($report === 'reservation') {
|
|
$reservations = HotelBookingDetail::getBookingsInfo(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
'id_status' => $idStatus,
|
|
'booking_type' => $bookingType,
|
|
'id_order_state' => $idOrderState,
|
|
));
|
|
$this->attachCurrencyToRows($reservations, $currencyMap, $currency->iso_code);
|
|
|
|
$totals = array('nights' => 0, 'adults' => 0, 'children' => 0,
|
|
'total_price_tax_excl' => 0.0, 'tax_amount' => 0.0,
|
|
'total_price_tax_incl' => 0.0);
|
|
foreach ($reservations as $row) {
|
|
$rate = (float) $row['conversion_rate'];
|
|
if ($rate <= 0) {
|
|
$rate = 1.0;
|
|
}
|
|
$totals['nights'] += (int) $row['nights'];
|
|
$totals['adults'] += (int) $row['adults'];
|
|
$totals['children'] += (int) $row['children'];
|
|
$totals['total_price_tax_excl'] += (float) $row['total_price_tax_excl'] / $rate;
|
|
$totals['tax_amount'] += (float) $row['tax_amount'] / $rate;
|
|
$totals['total_price_tax_incl'] += (float) $row['total_price_tax_incl'] / $rate;
|
|
}
|
|
|
|
$this->context->smarty->assign(array(
|
|
'reservations' => $reservations,
|
|
'reservation_totals' => $totals,
|
|
));
|
|
} elseif ($report === 'cancellation') {
|
|
$cancellations = HotelBookingDetail::getCancellations(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: null,
|
|
'id_product' => $idProduct,
|
|
'detailed_info' => true,
|
|
));
|
|
$this->attachCurrencyToRows($cancellations, $currencyMap, $currency->iso_code);
|
|
|
|
$totalRefunded = 0.0;
|
|
foreach ($cancellations as $row) {
|
|
$totalRefunded += (float) $row['refunded_amount'];
|
|
}
|
|
|
|
$this->context->smarty->assign(array(
|
|
'cancellations' => $cancellations,
|
|
'total_refunded' => $totalRefunded,
|
|
));
|
|
} elseif ($report === 'arrivals') {
|
|
$arrivals = HotelBookingDetail::getArrivalsInfo(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
));
|
|
$this->attachCurrencyToRows($arrivals, $currencyMap, $currency->iso_code);
|
|
|
|
$arrivalTotals = array('adults' => 0, 'children' => 0, 'los' => 0, 'total_price_tax_incl' => 0.0);
|
|
foreach ($arrivals as $row) {
|
|
$rate = (float) $row['conversion_rate'];
|
|
if ($rate <= 0) { $rate = 1.0; }
|
|
$arrivalTotals['adults'] += (int) $row['adults'];
|
|
$arrivalTotals['children'] += (int) $row['children'];
|
|
$arrivalTotals['los'] += (int) $row['los'];
|
|
$arrivalTotals['total_price_tax_incl'] += (float) $row['total_price_tax_incl'] / $rate;
|
|
}
|
|
|
|
$this->context->smarty->assign(array(
|
|
'arrivals' => $arrivals,
|
|
'arrival_totals' => $arrivalTotals,
|
|
));
|
|
} elseif ($report === 'departures') {
|
|
$departures = HotelBookingDetail::getDeparturesInfo(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
));
|
|
$this->attachCurrencyToRows($departures, $currencyMap, $currency->iso_code);
|
|
|
|
$departureTotals = array('adults' => 0, 'children' => 0, 'los' => 0, 'total_price_tax_incl' => 0.0);
|
|
foreach ($departures as $row) {
|
|
$rate = (float) $row['conversion_rate'];
|
|
if ($rate <= 0) { $rate = 1.0; }
|
|
$departureTotals['adults'] += (int) $row['adults'];
|
|
$departureTotals['children'] += (int) $row['children'];
|
|
$departureTotals['los'] += (int) $row['los'];
|
|
$departureTotals['total_price_tax_incl'] += (float) $row['total_price_tax_incl'] / $rate;
|
|
}
|
|
|
|
$this->context->smarty->assign(array(
|
|
'departures' => $departures,
|
|
'departure_totals' => $departureTotals,
|
|
));
|
|
} elseif ($report === 'in-house') {
|
|
$inHouse = HotelBookingDetail::getInHouseInfo(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
));
|
|
$this->attachCurrencyToRows($inHouse, $currencyMap, $currency->iso_code);
|
|
|
|
$inHouseTotals = array('adults' => 0, 'children' => 0, 'los' => 0, 'total_price_tax_incl' => 0.0);
|
|
foreach ($inHouse as $row) {
|
|
$rate = (float) $row['conversion_rate'];
|
|
if ($rate <= 0) { $rate = 1.0; }
|
|
$inHouseTotals['adults'] += (int) $row['adults'];
|
|
$inHouseTotals['children'] += (int) $row['children'];
|
|
$inHouseTotals['los'] += (int) $row['los'];
|
|
$inHouseTotals['total_price_tax_incl'] += (float) $row['total_price_tax_incl'] / $rate;
|
|
}
|
|
|
|
$this->context->smarty->assign(array(
|
|
'in_house' => $inHouse,
|
|
'in_house_totals' => $inHouseTotals,
|
|
));
|
|
}
|
|
}
|
|
|
|
if (in_array($report, array('revenue', 'refund', 'payment', 'tax', 'outstanding'))) {
|
|
$idProduct = (int) Tools::getValue('id_product', 0);
|
|
$refundStatus = (int) Tools::getValue('refund_status', 0);
|
|
$outstandingStatus = pSQL(Tools::getValue('outstanding_status', ''));
|
|
$paymentMethod = pSQL(Tools::getValue('payment_method', ''));
|
|
$idTax = (int) Tools::getValue('id_tax', 0);
|
|
$revenueSource = Tools::getValue('revenue_source', 'all');
|
|
if (!in_array($revenueSource, array('room', 'service', 'all'))) {
|
|
$revenueSource = 'room';
|
|
}
|
|
$filterBaseUrl = $baseUrl . '&tab=' . $report
|
|
. ($idHotel ? '&id_hotel=' . $idHotel : '')
|
|
. ($idProduct ? '&id_product=' . $idProduct : '');
|
|
|
|
$baseParams = array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
);
|
|
|
|
$this->context->smarty->assign(array(
|
|
'filter_id_product' => $idProduct,
|
|
'filter_refund_status' => $refundStatus,
|
|
|
|
'filter_payment_method' => $paymentMethod,
|
|
'payment_methods' => OrderPayment::getDistinctPaymentMethods($idHotel ?: false),
|
|
'payment_types' => array(
|
|
OrderPayment::PAYMENT_TYPE_ONLINE => $this->l('Online'),
|
|
OrderPayment::PAYMENT_TYPE_PAY_AT_HOTEL => $this->l('Pay at Hotel'),
|
|
OrderPayment::PAYMENT_TYPE_REMOTE_PAYMENT => $this->l('Remote Payment'),
|
|
),
|
|
'booking_statuses' => array(
|
|
HotelBookingDetail::STATUS_ALLOTED => $this->l('Allotted'),
|
|
HotelBookingDetail::STATUS_CHECKED_IN => $this->l('Checked In'),
|
|
HotelBookingDetail::STATUS_CHECKED_OUT => $this->l('Checked Out'),
|
|
),
|
|
'filter_id_tax' => $idTax,
|
|
'filter_revenue_source' => $revenueSource,
|
|
'tax_names' => Tax::getTaxes($idLang),
|
|
'refund_states' => OrderReturnState::getOrderReturnStates($idLang),
|
|
'room_types' => HotelRoomType::getRoomTypes(array(
|
|
'id_hotel' => $idHotel,
|
|
'id_lang' => $idLang,
|
|
)),
|
|
'filter_base_url' => $filterBaseUrl,
|
|
'export_url' => $filterBaseUrl
|
|
. '&id_product=' . $idProduct
|
|
. ($report === 'refund' && $refundStatus ? '&refund_status=' . $refundStatus : '')
|
|
. ($report === 'outstanding' && $outstandingStatus ? '&outstanding_status=' . $outstandingStatus : '')
|
|
. ($report === 'payment' && $paymentMethod ? '&payment_method=' . urlencode($paymentMethod) : '')
|
|
. ($report === 'tax' && $idTax ? '&id_tax=' . $idTax : '')
|
|
. ($report === 'tax' ? '&revenue_source=' . urlencode($revenueSource) : '')
|
|
. '&export=1',
|
|
));
|
|
|
|
if ($report === 'revenue') {
|
|
$dailyRoomDetailed = HotelBookingDetail::getDatewiseSummary($baseParams);
|
|
$dailyServiceData = ServiceProductOrderDetail::getDatewiseRevenue($baseParams);
|
|
$dailyBookings = HotelBookingDetail::getDatewiseBookings(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
));
|
|
$dailyRefunds = HotelBookingDetail::getDatewiseRefunds($baseParams);
|
|
|
|
$dailyRows = array();
|
|
$totals = array(
|
|
'rooms_sold' => 0, 'bookings' => 0, 'room_revenue' => 0.0,
|
|
'service_revenue' => 0.0,
|
|
'tax_amount' => 0.0, 'refund_amount' => 0.0,
|
|
'total_collection' => 0.0, 'net_revenue' => 0.0,
|
|
);
|
|
|
|
$curDate = $dateFrom;
|
|
while ($curDate <= $dateTo) {
|
|
$timestamp = strtotime($curDate);
|
|
$roomData = isset($dailyRoomDetailed[$timestamp]) ? $dailyRoomDetailed[$timestamp]
|
|
: array('total_price_tax_excl' => 0.0, 'total_tax' => 0.0, 'rooms_booked' => 0, 'room_nights' => 0);
|
|
$svcDay = isset($dailyServiceData[$timestamp]) ? $dailyServiceData[$timestamp] : array('service_revenue' => 0.0, 'total_tax' => 0.0);
|
|
$roomRev = $roomData['total_price_tax_excl'];
|
|
$taxAmt = $roomData['total_tax'] + $svcDay['total_tax'];
|
|
$svcRev = $svcDay['service_revenue'];
|
|
$refund = isset($dailyRefunds[$timestamp]) ? (float) $dailyRefunds[$timestamp] : 0.0;
|
|
$bkgs = isset($dailyBookings[$timestamp]) ? $dailyBookings[$timestamp] : 0;
|
|
$roomsBooked = (int) $roomData['rooms_booked'];
|
|
$netRev = $roomRev + $svcRev;
|
|
$totalCol = $netRev + $taxAmt;
|
|
$adrDay = (int) $roomData['room_nights'] > 0 ? round($roomRev / (int) $roomData['room_nights'], 2) : 0.0;
|
|
|
|
$dailyRows[] = array(
|
|
'date' => $curDate,
|
|
'rooms_sold' => $roomsBooked,
|
|
'bookings' => $bkgs,
|
|
'room_revenue' => $roomRev,
|
|
'service_revenue' => $svcRev,
|
|
'tax_amount' => $taxAmt,
|
|
'refund_amount' => $refund,
|
|
'total_collection' => $totalCol,
|
|
'net_revenue' => $netRev,
|
|
'adr' => $adrDay,
|
|
);
|
|
|
|
$totals['rooms_sold'] += $roomsBooked;
|
|
$totals['bookings'] += $bkgs;
|
|
$totals['room_revenue'] += $roomRev;
|
|
$totals['service_revenue'] += $svcRev;
|
|
$totals['refund_amount'] += $refund;
|
|
$totals['tax_amount'] += $taxAmt;
|
|
$totals['total_collection'] += $totalCol;
|
|
$totals['net_revenue'] += $netRev;
|
|
$curDate = date('Y-m-d', strtotime('+1 day', $timestamp));
|
|
}
|
|
|
|
$this->context->smarty->assign(array(
|
|
'daily_rows' => $dailyRows,
|
|
'revenue_totals' => $totals,
|
|
));
|
|
} elseif ($report === 'refund') {
|
|
$refunds = HotelBookingDetail::getCancellations(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: null,
|
|
'id_product' => $idProduct,
|
|
'detailed_info' => true,
|
|
));
|
|
$revCurrencyMap = $this->currencySignMap();
|
|
$this->attachCurrencyToRows($refunds, $revCurrencyMap, $currency->iso_code);
|
|
$totalRefunded = 0.0;
|
|
foreach ($refunds as $row) {
|
|
$totalRefunded += (float) $row['refunded_amount'];
|
|
}
|
|
$refunds = array_filter($refunds, function ($row) { return (float) $row['refunded_amount'] > 0; });
|
|
if ($refundStatus > 0) {
|
|
$refunds = array_filter($refunds, function ($r) use ($refundStatus) {
|
|
return (int) $r['refund_status'] === $refundStatus;
|
|
});
|
|
}
|
|
|
|
$this->context->smarty->assign(array(
|
|
'refunds' => array_values($refunds),
|
|
'total_refunded' => $totalRefunded,
|
|
));
|
|
} elseif ($report === 'payment') {
|
|
$payments = OrderPayment::getPaymentsInfo(
|
|
array_merge($baseParams, array('payment_method' => $paymentMethod))
|
|
);
|
|
$totalPayments = 0.0;
|
|
foreach ($payments as $row) {
|
|
$rate = (float) $row['conversion_rate'];
|
|
if ($rate <= 0) { $rate = 1.0; }
|
|
$totalPayments += (float) $row['amount'] / $rate;
|
|
}
|
|
$this->context->smarty->assign(array(
|
|
'payments' => $payments,
|
|
'total_payments' => $totalPayments,
|
|
));
|
|
} elseif ($report === 'tax') {
|
|
$taxRows = HotelBookingDetail::getTaxBreakdown(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
'id_tax' => $idTax,
|
|
'revenue_source' => $revenueSource,
|
|
'id_lang' => $idLang,
|
|
));
|
|
$taxByName = array();
|
|
$totalTax = 0.0;
|
|
$totalExcl = 0.0;
|
|
foreach ($taxRows as $row) {
|
|
$totalTax += (float) $row['tax_amount'];
|
|
$totalExcl += (float) $row['taxable_amount'];
|
|
$name = $row['tax_name'] ? $row['tax_name'] : 'Unknown';
|
|
if (!isset($taxByName[$name])) {
|
|
$taxByName[$name] = array(
|
|
'tax_name' => $name,
|
|
'tax_rate' => (float) $row['tax_rate'],
|
|
'taxable_amount' => 0.0,
|
|
'tax_amount' => 0.0,
|
|
);
|
|
}
|
|
$taxByName[$name]['taxable_amount'] += (float) $row['taxable_amount'];
|
|
$taxByName[$name]['tax_amount'] += (float) $row['tax_amount'];
|
|
}
|
|
$this->context->smarty->assign(array(
|
|
'tax_rows' => $taxRows,
|
|
'tax_by_name' => array_values($taxByName),
|
|
'tax_totals' => array(
|
|
'taxable_amount' => $totalExcl,
|
|
'tax_amount' => $totalTax,
|
|
'grand_total' => $totalExcl + $totalTax,
|
|
),
|
|
));
|
|
} elseif ($report === 'outstanding') {
|
|
$outstanding = Order::getOutstandingBalance($baseParams);
|
|
$totalOutstanding = 0.0;
|
|
foreach ($outstanding as $row) {
|
|
$totalOutstanding += (float) $row['balance_due'];
|
|
}
|
|
$this->context->smarty->assign(array(
|
|
'outstanding' => $outstanding,
|
|
'total_outstanding' => $totalOutstanding,
|
|
));
|
|
}
|
|
}
|
|
|
|
if (in_array($report, array('occupancy', 'availability', 'room-status', 'room-perf'))) {
|
|
$idProduct = (int) Tools::getValue('id_product', 0);
|
|
$floor = pSQL(Tools::getValue('floor', ''));
|
|
$filterBaseUrl = $baseUrl . '&tab=' . $report
|
|
. ($idHotel ? '&id_hotel=' . $idHotel : '')
|
|
. ($idProduct ? '&id_product=' . $idProduct : '');
|
|
|
|
|
|
$baseParams = array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
);
|
|
|
|
$this->context->smarty->assign(array(
|
|
'filter_id_product' => $idProduct,
|
|
'filter_floor' => $floor,
|
|
'available_floors' => HotelRoomInformation::getDistinctFloors(array(
|
|
'id_hotel' => $idHotel,
|
|
'id_product' => $idProduct,
|
|
)),
|
|
'room_types' => HotelRoomType::getRoomTypes(array(
|
|
'id_hotel' => $idHotel,
|
|
'id_lang' => $idLang,
|
|
)),
|
|
'room_statuses' => array(
|
|
HotelRoomInformation::STATUS_INACTIVE => array('label' => $this->l('Out of Order'), 'class' => 'label-default'),
|
|
HotelRoomInformation::STATUS_TEMPORARY_INACTIVE => array('label' => $this->l('Under Maintenance'), 'class' => 'label-warning'),
|
|
),
|
|
'filter_base_url' => $filterBaseUrl,
|
|
'export_url' => $filterBaseUrl
|
|
. '&id_product=' . $idProduct
|
|
. ($floor ? '&floor=' . urlencode($floor) : '')
|
|
. '&export=1',
|
|
));
|
|
|
|
if ($report === 'occupancy') {
|
|
$dailyOccupied = HotelBookingDetail::getDatewiseOccupiedRooms($baseParams);
|
|
$dailyBooked = HotelBookingDetail::getDatewiseOccupiedRooms(
|
|
array_merge($baseParams, array('id_status' => HotelBookingDetail::STATUS_ALLOTED))
|
|
);
|
|
$dailyCheckedIn = HotelBookingDetail::getDatewiseOccupiedRooms(
|
|
array_merge($baseParams, array('id_status' => HotelBookingDetail::STATUS_CHECKED_IN))
|
|
);
|
|
|
|
if ($idProduct) {
|
|
$totalRooms = (int) AdminStatsController::getTotalRooms($idHotel ?: null, 1, $idProduct);
|
|
$dailyRevenue = HotelBookingDetail::getDatewiseRevenue($baseParams);
|
|
$dailyAvailable = array();
|
|
$dailyAdr = array();
|
|
foreach ($dailyOccupied as $ts => $occ) {
|
|
$dailyAvailable[$ts] = max(0, $totalRooms - $occ);
|
|
$rev = isset($dailyRevenue[$ts]) ? (float) $dailyRevenue[$ts]['total_price_tax_excl'] : 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;
|
|
$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),
|
|
);
|
|
}
|
|
|
|
$this->context->smarty->assign(array(
|
|
'daily_rows' => $dailyRows,
|
|
));
|
|
} elseif ($report === 'availability') {
|
|
$availabilityRows = HotelRoomInformation::getAvailabilityReport(array_merge($baseParams, array(
|
|
'id_product' => (int) Tools::getValue('id_product', 0),
|
|
'id_lang' => $idLang,
|
|
)));
|
|
|
|
$this->context->smarty->assign(array(
|
|
'availability_rows' => $availabilityRows,
|
|
));
|
|
} elseif ($report === 'room-status') {
|
|
$showHousekeeping = Module::isInstalled('qlohousekeeping') && Module::isEnabled('qlohousekeeping');
|
|
$housekeepingStatuses = array();
|
|
if ($showHousekeeping) {
|
|
$hkStatus = new QhkHouseKeepingTaskStatus();
|
|
$housekeepingStatuses = $hkStatus->getHousekeepingTaskStatusList(false, $idLang);
|
|
}
|
|
|
|
$perPage = 20;
|
|
$page = max(1, (int) Tools::getValue('rooms_page', 1));
|
|
$allRooms = HotelRoomInformation::getRoomStatus(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
'id_lang' => $idLang,
|
|
'housekeeping_installed' => $showHousekeeping,
|
|
));
|
|
$roomsTotal = count($allRooms);
|
|
$roomsTotalPages = max(1, (int) ceil($roomsTotal / $perPage));
|
|
$page = min($page, $roomsTotalPages);
|
|
$kpiOccupied = 0;
|
|
$kpiOutOfOrder = 0;
|
|
foreach ($allRooms as $room) {
|
|
if ($room['id_order']) {
|
|
$kpiOccupied++;
|
|
} elseif (in_array((int) $room['id_status'], array(
|
|
HotelRoomInformation::STATUS_INACTIVE,
|
|
HotelRoomInformation::STATUS_TEMPORARY_INACTIVE,
|
|
))) {
|
|
$kpiOutOfOrder++;
|
|
}
|
|
}
|
|
$this->context->smarty->assign(array(
|
|
'rooms' => array_slice($allRooms, ($page - 1) * $perPage, $perPage),
|
|
'rooms_page' => $page,
|
|
'rooms_total_pages' => $roomsTotalPages,
|
|
'rooms_total' => $roomsTotal,
|
|
'rooms_page_base_url' => $filterBaseUrl . '&rooms_page=',
|
|
'rooms_offset_start' => $roomsTotal ? ($page - 1) * $perPage + 1 : 0,
|
|
'rooms_offset_end' => min($page * $perPage, $roomsTotal),
|
|
'kpi_total' => $roomsTotal,
|
|
'kpi_occupied' => $kpiOccupied,
|
|
'kpi_available' => $roomsTotal - $kpiOccupied - $kpiOutOfOrder,
|
|
'kpi_out_of_order' => $kpiOutOfOrder,
|
|
'show_housekeeping' => $showHousekeeping,
|
|
'housekeeping_statuses' => $housekeepingStatuses,
|
|
));
|
|
} elseif ($report === 'room-perf') {
|
|
$roomTypePerformanceRows = HotelRoomType::getRoomTypePerformance(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
'id_lang' => $idLang,
|
|
));
|
|
$this->context->smarty->assign(array(
|
|
'roomTypePerformance_rows' => $roomTypePerformanceRows,
|
|
));
|
|
}
|
|
}
|
|
|
|
if ($report === 'services' || $report === 'guest-directory') {
|
|
$guestType = pSQL(Tools::getValue('guest_type', ''));
|
|
$idCategory = (int) Tools::getValue('id_category', 0);
|
|
$idServiceProduct = (int) Tools::getValue('id_service_product', 0);
|
|
$filterBaseUrl = $baseUrl . '&tab=' . $report
|
|
. ($idHotel ? '&id_hotel=' . $idHotel : '')
|
|
. ($guestType ? '&guest_type=' . urlencode($guestType) : '');
|
|
|
|
|
|
$baseParams = array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_lang' => $idLang,
|
|
);
|
|
|
|
$this->context->smarty->assign(array(
|
|
'filter_base_url' => $filterBaseUrl,
|
|
'filter_guest_type' => $guestType,
|
|
'filter_id_category' => $idCategory,
|
|
'filter_id_service' => $idServiceProduct,
|
|
'service_categories' => ServiceProductOrderDetail::getServiceCategories($idLang),
|
|
'service_products' => ServiceProductOrderDetail::getServiceProducts($idLang, $idCategory),
|
|
'export_url' => $filterBaseUrl
|
|
. ($report === 'services' && $idCategory ? '&id_category=' . $idCategory : '')
|
|
. ($report === 'services' && $idServiceProduct ? '&id_service_product=' . $idServiceProduct : '')
|
|
. ($report === 'guest-directory' && $guestType ? '&guest_type=' . urlencode($guestType) : '')
|
|
. '&export=1',
|
|
));
|
|
|
|
if ($report === 'services') {
|
|
$serviceRows = ServiceProductOrderDetail::getServicesInfo(array_merge($baseParams, array(
|
|
'id_category' => $idCategory,
|
|
'id_service_product' => $idServiceProduct,
|
|
)));
|
|
$totalExcl = 0.0;
|
|
$totalTax = 0.0;
|
|
$totalIncl = 0.0;
|
|
foreach ($serviceRows as $row) {
|
|
$totalExcl += (float) $row['total_price_tax_excl'];
|
|
$totalTax += (float) $row['tax_amount'];
|
|
$totalIncl += (float) $row['total_price_tax_incl'];
|
|
}
|
|
$this->context->smarty->assign(array(
|
|
'service_rows' => $serviceRows,
|
|
'service_totals' => array(
|
|
'total_excl' => $totalExcl,
|
|
'total_tax' => $totalTax,
|
|
'total_incl' => $totalIncl,
|
|
),
|
|
));
|
|
} elseif ($report === 'guest-directory') {
|
|
$guests = Order::getGuestDirectory($baseParams);
|
|
if ($guestType === 'new') {
|
|
$guests = array_values(array_filter($guests, function ($guest) {
|
|
return (int) $guest['total_stays'] === 1;
|
|
}));
|
|
} elseif ($guestType === 'returning') {
|
|
$guests = array_values(array_filter($guests, function ($guest) {
|
|
return (int) $guest['total_stays'] > 1;
|
|
}));
|
|
}
|
|
$this->context->smarty->assign(array(
|
|
'guests' => $guests,
|
|
));
|
|
}
|
|
}
|
|
|
|
if ($report === 'source' || $report === 'payment-method') {
|
|
$bookingSource = pSQL(Tools::getValue('booking_source', ''));
|
|
$filterBaseUrl = $baseUrl . '&tab=' . $report
|
|
. ($idHotel ? '&id_hotel=' . $idHotel : '')
|
|
. ($bookingSource ? '&booking_source=' . $bookingSource : '');
|
|
|
|
$baseParams = array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'booking_source' => $bookingSource,
|
|
);
|
|
|
|
$this->context->smarty->assign(array(
|
|
'filter_base_url' => $filterBaseUrl,
|
|
'filter_booking_source' => $bookingSource,
|
|
'export_url' => $filterBaseUrl . '&export=1',
|
|
));
|
|
|
|
if ($report === 'source') {
|
|
$sourceRows = HotelBookingDetail::getChannelStats($baseParams);
|
|
$sourceTotals = array(
|
|
'bookings' => 0, 'rooms_booked' => 0, 'room_nights' => 0,
|
|
'revenue_excl' => 0.0, 'revenue_incl' => 0.0,
|
|
'discount_amount' => 0.0, 'refund_amount' => 0.0,
|
|
'tax_amount' => 0.0, 'net_revenue' => 0.0, 'cancellations' => 0,
|
|
);
|
|
foreach ($sourceRows as $row) {
|
|
$sourceTotals['bookings'] += (int) $row['bookings'];
|
|
$sourceTotals['rooms_booked'] += (int) $row['rooms_booked'];
|
|
$sourceTotals['room_nights'] += (int) $row['room_nights'];
|
|
$sourceTotals['revenue_excl'] += (float) $row['revenue_excl'];
|
|
$sourceTotals['revenue_incl'] += (float) $row['revenue_incl'];
|
|
$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(
|
|
'source_rows' => $sourceRows,
|
|
'source_totals' => $sourceTotals,
|
|
));
|
|
} elseif ($report === 'payment-method') {
|
|
$paymentRows = HotelBookingDetail::getPaymentMethodStats($baseParams);
|
|
$totalBookings = 0;
|
|
$totalRevenue = 0.0;
|
|
foreach ($paymentRows as $row) {
|
|
$totalBookings += (int) $row['bookings'];
|
|
$totalRevenue += (float) $row['revenue_incl'];
|
|
}
|
|
$this->context->smarty->assign(array(
|
|
'payment_rows' => $paymentRows,
|
|
'total_bookings' => $totalBookings,
|
|
'total_revenue' => $totalRevenue,
|
|
));
|
|
}
|
|
}
|
|
|
|
if (in_array($report, array('daily-summary', 'hotel-comparison', 'out-of-order'))) {
|
|
$idProduct = (int) Tools::getValue('id_product', 0);
|
|
$floor = pSQL(Tools::getValue('floor', ''));
|
|
$filterBaseUrl = $baseUrl . '&tab=' . $report
|
|
. ($idHotel ? '&id_hotel=' . $idHotel : '')
|
|
. ($idProduct ? '&id_product=' . $idProduct : '');
|
|
|
|
|
|
$baseParams = array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
);
|
|
|
|
$this->context->smarty->assign(array(
|
|
'filter_base_url' => $filterBaseUrl,
|
|
'filter_id_product' => $idProduct,
|
|
'filter_floor' => $floor,
|
|
'available_floors' => HotelRoomInformation::getDistinctFloors(array(
|
|
'id_hotel' => $idHotel,
|
|
'id_product' => $idProduct,
|
|
)),
|
|
'room_types' => HotelRoomType::getRoomTypes(array(
|
|
'id_hotel' => $idHotel,
|
|
'id_lang' => $idLang,
|
|
)),
|
|
'room_statuses' => array(
|
|
HotelRoomInformation::STATUS_INACTIVE => array('label' => $this->l('Out of Order'), 'class' => 'label-default'),
|
|
HotelRoomInformation::STATUS_TEMPORARY_INACTIVE => array('label' => $this->l('Under Maintenance'), 'class' => 'label-warning'),
|
|
),
|
|
'export_url' => $filterBaseUrl
|
|
. ($floor ? '&floor=' . urlencode($floor) : '')
|
|
. '&export=1',
|
|
));
|
|
|
|
if ($report === 'daily-summary') {
|
|
$bookingsByDay = HotelBookingDetail::getDatewiseBookings($baseParams);
|
|
$arrivalsByDay = HotelBookingDetail::getDatewiseArrivals($baseParams);
|
|
$departuresByDay = HotelBookingDetail::getDatewiseDepartures($baseParams);
|
|
$cancsByDay = HotelBookingDetail::getDatewiseCancellations($baseParams);
|
|
$occupancyByDay = AdminStatsController::getOccupancyRateForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
|
|
$adrByDay = AdminStatsController::getAverageDailyRateForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
|
|
$revenueByDay = AdminStatsController::getRoomsRevenueForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
|
|
$roomsSoldByDay = HotelBookingDetail::getDatewiseOccupiedRooms($baseParams);
|
|
$checkedInByDay = HotelBookingDetail::getDatewiseOccupiedRooms(
|
|
array_merge($baseParams, array('id_status' => HotelBookingDetail::STATUS_CHECKED_IN))
|
|
);
|
|
$totalRooms = AdminStatsController::getTotalRooms($idHotel ?: null, 1);
|
|
|
|
$dailyRows = array();
|
|
$current = $dateFrom;
|
|
while ($current <= $dateTo) {
|
|
$timestamp= strtotime($current);
|
|
$dailyRows[] = array(
|
|
'date' => $current,
|
|
'total_rooms' => $totalRooms,
|
|
'rooms_sold' => isset($roomsSoldByDay[$timestamp]) ? (int) $roomsSoldByDay[$timestamp] : 0,
|
|
'occupancy' => isset($occupancyByDay[$timestamp]) ? (float) $occupancyByDay[$timestamp] * 100 : 0.0,
|
|
'adr' => isset($adrByDay[$timestamp]) ? (float) $adrByDay[$timestamp] : 0.0,
|
|
'revpar' => $totalRooms ? round((isset($revenueByDay[$timestamp]) ? (float) $revenueByDay[$timestamp] : 0.0) / $totalRooms, 2) : 0.0,
|
|
'revenue' => isset($revenueByDay[$timestamp]) ? (float) $revenueByDay[$timestamp] : 0.0,
|
|
'arrivals' => isset($arrivalsByDay[$timestamp]) ? (int) $arrivalsByDay[$timestamp] : 0,
|
|
'departures' => isset($departuresByDay[$timestamp]) ? (int) $departuresByDay[$timestamp] : 0,
|
|
'inhouse_guests'=> isset($checkedInByDay[$timestamp]) ? (int) $checkedInByDay[$timestamp] : 0,
|
|
'cancels' => isset($cancsByDay[$timestamp]) ? (int) $cancsByDay[$timestamp] : 0,
|
|
'bookings' => isset($bookingsByDay[$timestamp]) ? (int) $bookingsByDay[$timestamp] : 0,
|
|
);
|
|
$current = date('Y-m-d', strtotime('+1 day', strtotime($current)));
|
|
}
|
|
$this->context->smarty->assign(array('daily_rows' => $dailyRows));
|
|
|
|
} elseif ($report === 'hotel-comparison') {
|
|
$hotels = (new HotelBranchInformation())->hotelsNameAndId();
|
|
$hotelRows = array();
|
|
foreach ($hotels as $hotel) {
|
|
$hotelId = (int) $hotel['id'];
|
|
$hotelParams = array_merge($baseParams, array('id_hotel' => $hotelId));
|
|
$bookings = HotelBookingDetail::getTotalBookings(array_merge($hotelParams, array('count_rows' => true)));
|
|
$cancels = HotelBookingDetail::getTotalCancellations($hotelParams);
|
|
$roomNights = HotelBookingDetail::getTotalRoomNights($hotelParams);
|
|
$roomRevenue = array_sum(AdminStatsController::getRoomsRevenueForDiscreteDates($dateFrom, $dateTo, $hotelId));
|
|
$extraRev = ServiceProductOrderDetail::getTotalRevenue($hotelParams);
|
|
$obRows = Order::getOutstandingBalance($hotelParams);
|
|
$hotelRows[] = array(
|
|
'id_hotel' => $hotelId,
|
|
'hotel_name' => $hotel['hotel_name'],
|
|
'total_rooms' => AdminStatsController::getTotalRooms($hotelId, 1),
|
|
'rooms_sold' => $roomNights,
|
|
'occupancy' => AdminStatsController::getAverageOccupancyRate($dateFrom, $dateTo, $hotelId),
|
|
'room_revenue' => $roomRevenue,
|
|
'extra_service_rev' => $extraRev,
|
|
'gross_revenue' => $roomRevenue + $extraRev,
|
|
'adr' => AdminStatsController::getAverageDailyRate($dateFrom, $dateTo, $hotelId),
|
|
'revpar' => AdminStatsController::getRevenuePerAvailableRoom($dateFrom, $dateTo, $hotelId),
|
|
'bookings' => $bookings,
|
|
'cancellations' => $cancels,
|
|
'cancel_rate_pct' => $bookings > 0 ? round($cancels / $bookings * 100, 1) : 0.0,
|
|
'avg_los' => ($bookings > 0 && $roomNights > 0) ? round($roomNights / $bookings, 1) : 0.0,
|
|
'outstanding_balance' => $obRows ? array_sum(array_column($obRows, 'balance_due')) : 0.0,
|
|
);
|
|
}
|
|
$this->context->smarty->assign(array('hotel_rows' => $hotelRows));
|
|
|
|
} elseif ($report === 'out-of-order') {
|
|
$outOfOrderRows = HotelRoomDisableDates::getDisabledRooms(
|
|
array_merge($baseParams, array('id_lang' => $idLang, 'floor' => $floor))
|
|
);
|
|
$this->context->smarty->assign(array('outOfOrder_rows' => $outOfOrderRows));
|
|
}
|
|
}
|
|
|
|
return $this->display(__FILE__, 'admin-stats-modules.tpl');
|
|
}
|
|
|
|
/**
|
|
* Return array of currency iso_code indexed by id_currency.
|
|
* Loaded once; used to attach currency_iso per booking row for CSV exports.
|
|
*/
|
|
private function currencySignMap()
|
|
{
|
|
$map = array();
|
|
$rows = Currency::getCurrencies(false, false, true);
|
|
foreach ($rows as $row) {
|
|
$map[(int) $row['id_currency']] = array(
|
|
'iso_code' => $row['iso_code'],
|
|
);
|
|
}
|
|
return $map;
|
|
}
|
|
|
|
/**
|
|
* Attach currency_iso to each row using id_currency field.
|
|
* Falls back to the default store currency iso_code when id_currency is not in the map.
|
|
*/
|
|
private function attachCurrencyToRows(array &$rows, array $currencyMap, $defaultIso)
|
|
{
|
|
foreach ($rows as &$row) {
|
|
$idCur = (int) $row['id_currency'];
|
|
$row['currency_iso'] = isset($currencyMap[$idCur]) ? $currencyMap[$idCur]['iso_code'] : $defaultIso;
|
|
}
|
|
unset($row);
|
|
}
|
|
|
|
/**
|
|
* Stream CSV for a Bookings sub-report and exit.
|
|
*/
|
|
private function exportBookingsCsv($dateFrom, $dateTo, $idHotel, $report)
|
|
{
|
|
header('Content-Type: text/csv; charset=utf-8');
|
|
$idProduct = (int) Tools::getValue('id_product', 0);
|
|
$defaultCurrency = new Currency(Configuration::get('PS_CURRENCY_DEFAULT'));
|
|
$csvCurrencyMap = $this->currencySignMap();
|
|
|
|
if ($report === 'arrivals' || $report === 'departures') {
|
|
if ($report === 'arrivals') {
|
|
$rows = HotelBookingDetail::getArrivalsInfo(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
));
|
|
$filename = 'arrivals-report-'.$dateFrom.'-to-'.$dateTo.'.csv';
|
|
} else {
|
|
$rows = HotelBookingDetail::getDeparturesInfo(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
));
|
|
$filename = 'departures-report-'.$dateFrom.'-to-'.$dateTo.'.csv';
|
|
}
|
|
$this->attachCurrencyToRows($rows, $csvCurrencyMap, $defaultCurrency->iso_code);
|
|
header('Content-Disposition: attachment; filename="'.$filename.'"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$this->l('Room Type'), $this->l('Room No.'), $this->l('Check-in'), $this->l('Check-out'),
|
|
$this->l('Order ID'), $this->l('Guest Name'), $this->l('Hotel'),
|
|
$this->l('Nights'), $this->l('Adults'), $this->l('Children'),
|
|
$this->l('Total (incl. Tax)'), $this->l('Currency'),
|
|
));
|
|
foreach ($rows as $row) {
|
|
fputcsv($out, array(
|
|
$row['room_type_name'], $row['room_num'], $row['actual_checkin'], $row['actual_checkout'],
|
|
$row['id_order'], $row['customer_name'], $row['hotel_name'],
|
|
$row['los'], $row['adults'], $row['children'],
|
|
$row['total_price_tax_incl'], $row['currency_iso'],
|
|
));
|
|
}
|
|
fclose($out);
|
|
exit;
|
|
}
|
|
|
|
if ($report === 'in-house') {
|
|
$rows = HotelBookingDetail::getInHouseInfo(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
));
|
|
$this->attachCurrencyToRows($rows, $csvCurrencyMap, $defaultCurrency->iso_code);
|
|
header('Content-Disposition: attachment; filename="in-house-report-'.date('Y-m-d').'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$this->l('Room Type'), $this->l('Room No.'), $this->l('Check-in'), $this->l('Check-out'),
|
|
$this->l('Order ID'), $this->l('Guest Name'), $this->l('Hotel'),
|
|
$this->l('Nights'), $this->l('Adults'), $this->l('Children'),
|
|
$this->l('Total (incl. Tax)'), $this->l('Currency'),
|
|
));
|
|
foreach ($rows as $row) {
|
|
fputcsv($out, array(
|
|
$row['room_type_name'], $row['room_num'], $row['actual_checkin'], $row['actual_checkout'],
|
|
$row['id_order'], $row['customer_name'], $row['hotel_name'],
|
|
$row['los'], $row['adults'], $row['children'],
|
|
$row['total_price_tax_incl'], $row['currency_iso'],
|
|
));
|
|
}
|
|
fclose($out);
|
|
exit;
|
|
}
|
|
|
|
if ($report === 'cancellation') {
|
|
$rows = HotelBookingDetail::getCancellations(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: null,
|
|
'id_product' => (int) Tools::getValue('id_product', 0),
|
|
'detailed_info' => true,
|
|
));
|
|
$this->attachCurrencyToRows($rows, $csvCurrencyMap, $defaultCurrency->iso_code);
|
|
header('Content-Disposition: attachment; filename="cancellation-report-'.$dateFrom.'-to-'.$dateTo.'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$this->l('Room Type'),
|
|
$this->l('Room No.'),
|
|
$this->l('Check-in Date'),
|
|
$this->l('Order ID'),
|
|
$this->l('Guest Name'),
|
|
$this->l('Cancellation Date'),
|
|
$this->l('Cancellation Reason'),
|
|
$this->l('Refund Amount'),
|
|
$this->l('Refund Status'),
|
|
$this->l('Booking Date'),
|
|
$this->l('Currency'),
|
|
));
|
|
foreach ($rows as $row) {
|
|
fputcsv($out, array(
|
|
$row['room_type_name'],
|
|
$row['room_num'],
|
|
$row['hotel_check_in'],
|
|
$row['id_order'],
|
|
$row['customer_name'],
|
|
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'] : '',
|
|
$row['currency_iso'],
|
|
));
|
|
}
|
|
} else {
|
|
$idStatus = (int) Tools::getValue('booking_status', 0);
|
|
$bookingType = (int) Tools::getValue('booking_type', 0);
|
|
$idOrderState = (int) Tools::getValue('id_order_state', 0);
|
|
|
|
$rows = HotelBookingDetail::getBookingsInfo(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
'id_status' => $idStatus,
|
|
'booking_type' => $bookingType,
|
|
'id_order_state' => $idOrderState,
|
|
));
|
|
$this->attachCurrencyToRows($rows, $csvCurrencyMap, $defaultCurrency->iso_code);
|
|
|
|
$statusLabels = array(
|
|
HotelBookingDetail::STATUS_ALLOTED => $this->l('Allotted'),
|
|
HotelBookingDetail::STATUS_CHECKED_IN => $this->l('Checked In'),
|
|
HotelBookingDetail::STATUS_CHECKED_OUT => $this->l('Checked Out'),
|
|
);
|
|
header('Content-Disposition: attachment; filename="reservation-report-'.$dateFrom.'-to-'.$dateTo.'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$this->l('Room Type'),
|
|
$this->l('Room No.'),
|
|
$this->l('Check-in Date'),
|
|
$this->l('Check-out Date'),
|
|
$this->l('Order ID'),
|
|
$this->l('Guest Name'),
|
|
$this->l('Guest Contact'),
|
|
$this->l('Nights'),
|
|
$this->l('Adults'),
|
|
$this->l('Children'),
|
|
$this->l('Room Rate Per Night'),
|
|
$this->l('Booking Source'),
|
|
$this->l('Booking Status'),
|
|
$this->l('Total (excl. Tax)'),
|
|
$this->l('Tax Amount'),
|
|
$this->l('Grand Total'),
|
|
$this->l('Created By'),
|
|
$this->l('Booking Date'),
|
|
$this->l('Currency'),
|
|
));
|
|
foreach ($rows as $row) {
|
|
fputcsv($out, array(
|
|
$row['room_type_name'],
|
|
$row['room_num'],
|
|
$row['hotel_check_in'],
|
|
$row['hotel_check_out'],
|
|
$row['id_order'],
|
|
$row['customer_name'],
|
|
isset($row['phone']) ? $row['phone'] : '',
|
|
$row['nights'],
|
|
$row['adults'],
|
|
$row['children'],
|
|
number_format((float) $row['unit_price_tax_excl'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
$row['order_source'],
|
|
isset($statusLabels[$row['id_status']]) ? $statusLabels[$row['id_status']] : $row['id_status'],
|
|
number_format((float) $row['total_price_tax_excl'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format((float) $row['tax_amount'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format((float) $row['total_price_tax_incl'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
isset($row['created_by']) ? $row['created_by'] : '',
|
|
isset($row['date_add']) ? $row['date_add'] : '',
|
|
$row['currency_iso'],
|
|
));
|
|
}
|
|
}
|
|
|
|
fclose($out);
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Stream CSV for a Revenue & Finance sub-report and exit.
|
|
*/
|
|
private function exportRevenueCsv($dateFrom, $dateTo, $idHotel, $idProduct, $report)
|
|
{
|
|
$currencyIso = (new Currency(Configuration::get('PS_CURRENCY_DEFAULT')))->iso_code;
|
|
$baseParams = array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
);
|
|
|
|
header('Content-Type: text/csv; charset=utf-8');
|
|
|
|
if ($report === 'payment') {
|
|
$csvPaymentMethod = pSQL(Tools::getValue('payment_method', ''));
|
|
$rows = OrderPayment::getPaymentsInfo(
|
|
array_merge($baseParams, array('payment_method' => $csvPaymentMethod))
|
|
);
|
|
header('Content-Disposition: attachment; filename="payment-report-'.$dateFrom.'-to-'.$dateTo.'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$this->l('Payment Date'), $this->l('Payment ID'), $this->l('Order 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['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'] : '',
|
|
number_format((float) $row['amount'] / $rate, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
$this->l('Success'),
|
|
isset($row['transaction_id']) ? $row['transaction_id'] : '',
|
|
$currencyIso,
|
|
));
|
|
}
|
|
} elseif ($report === 'refund') {
|
|
$rows = HotelBookingDetail::getCancellations(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: null,
|
|
'id_product' => $idProduct,
|
|
'detailed_info' => true,
|
|
));
|
|
$rows = array_filter($rows, function ($row) { return (float) $row['refunded_amount'] > 0; });
|
|
header('Content-Disposition: attachment; filename="refund-report-'.$dateFrom.'-to-'.$dateTo.'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$this->l('Refund Date'), $this->l('Refund ID'), $this->l('Order 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'],
|
|
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,
|
|
));
|
|
}
|
|
} elseif ($report === 'tax') {
|
|
$idLangRev = Context::getContext()->language->id;
|
|
$csvRevSrc = Tools::getValue('revenue_source', 'room');
|
|
if (!in_array($csvRevSrc, array('room', 'service', 'all'))) {
|
|
$csvRevSrc = 'room';
|
|
}
|
|
$csvIdTax = (int) Tools::getValue('id_tax', 0);
|
|
$rows = HotelBookingDetail::getTaxBreakdown(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
'id_tax' => $csvIdTax,
|
|
'revenue_source' => $csvRevSrc,
|
|
'id_lang' => $idLangRev,
|
|
));
|
|
header('Content-Disposition: attachment; filename="tax-report-'.$dateFrom.'-to-'.$dateTo.'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$this->l('Booking Date'), $this->l('Booking Ref.'), $this->l('Guest Name'),
|
|
$this->l('Revenue Source'), $this->l('Room Type'),
|
|
$this->l('Taxable Amount'), $this->l('Tax Name'),
|
|
$this->l('Tax Rate %'), $this->l('Tax Amount'), $this->l('Currency'),
|
|
));
|
|
foreach ($rows as $row) {
|
|
$serviceLabel = (isset($row['revenue_source']) && $row['revenue_source'] === 'service')
|
|
? $this->l('Service Charge')
|
|
: $this->l('Room');
|
|
fputcsv($out, array(
|
|
isset($row['date_add']) ? $row['date_add'] : '',
|
|
$row['reference'],
|
|
$row['customer_name'],
|
|
$serviceLabel,
|
|
$row['room_type_name'],
|
|
number_format((float) $row['taxable_amount'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
isset($row['tax_name']) ? $row['tax_name'] : '',
|
|
number_format((float) $row['tax_rate'], 2, '.', ''),
|
|
number_format((float) $row['tax_amount'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
$currencyIso,
|
|
));
|
|
}
|
|
} elseif ($report === 'outstanding') {
|
|
$rows = Order::getOutstandingBalance($baseParams);
|
|
header('Content-Disposition: attachment; filename="outstanding-report-'.$dateFrom.'-to-'.$dateTo.'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$this->l('Order ID'), $this->l('Guest Name'),
|
|
$this->l('Guest Email'), $this->l('Guest Phone'),
|
|
$this->l('Total Charges'), $this->l('Total Paid'), $this->l('Outstanding Balance'),
|
|
$this->l('Last Payment Date'), $this->l('Currency'),
|
|
));
|
|
foreach ($rows as $row) {
|
|
fputcsv($out, array(
|
|
$row['id_order'],
|
|
$row['customer_name'],
|
|
isset($row['email']) ? $row['email'] : '',
|
|
isset($row['phone']) ? $row['phone'] : '',
|
|
number_format((float) $row['total_charges'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format((float) $row['total_paid'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format((float) $row['balance_due'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
isset($row['last_payment_date']) ? $row['last_payment_date'] : '',
|
|
$currencyIso,
|
|
));
|
|
}
|
|
} else {
|
|
$dailyRoomDetailed = HotelBookingDetail::getDatewiseSummary($baseParams);
|
|
$dailyServiceData = ServiceProductOrderDetail::getDatewiseRevenue($baseParams);
|
|
$dailyBookings = HotelBookingDetail::getDatewiseBookings(array(
|
|
'date_from' => $dateFrom, 'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false, 'id_product' => $idProduct,
|
|
));
|
|
$dailyRefunds = HotelBookingDetail::getDatewiseRefunds($baseParams);
|
|
|
|
header('Content-Disposition: attachment; filename="revenue-report-'.$dateFrom.'-to-'.$dateTo.'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$this->l('Date'), $this->l('Rooms Booked'), $this->l('Total Bookings'),
|
|
$this->l('Room Revenue (excl. Tax)'), $this->l('Extra Services Revenue'),
|
|
$this->l('Tax Amount'), $this->l('Refund Amount'),
|
|
$this->l('Total Collection'), $this->l('Net Revenue'),
|
|
$this->l('ADR'), $this->l('Currency'),
|
|
));
|
|
$csvDate = $dateFrom;
|
|
while ($csvDate <= $dateTo) {
|
|
$timestamp = strtotime($csvDate);
|
|
$roomData = isset($dailyRoomDetailed[$timestamp]) ? $dailyRoomDetailed[$timestamp]
|
|
: array('total_price_tax_excl' => 0.0, 'total_tax' => 0.0, 'rooms_booked' => 0, 'room_nights' => 0);
|
|
$svcDay = isset($dailyServiceData[$timestamp]) ? $dailyServiceData[$timestamp] : array('service_revenue' => 0.0, 'total_tax' => 0.0);
|
|
$svcRev = $svcDay['service_revenue'];
|
|
$refundAmt = isset($dailyRefunds[$timestamp]) ? (float) $dailyRefunds[$timestamp] : 0.0;
|
|
$roomRevExcl = (float) $roomData['total_price_tax_excl'];
|
|
$taxAmt = (float) $roomData['total_tax'] + $svcDay['total_tax'];
|
|
$collection = $roomRevExcl + $svcRev;
|
|
$netRevenue = $collection;
|
|
$roomsBooked = (int) $roomData['rooms_booked'];
|
|
$roomNights = (int) $roomData['room_nights'];
|
|
$adr = $roomNights > 0 ? round($roomRevExcl / $roomNights, 2) : 0.0;
|
|
fputcsv($out, array(
|
|
"\t" . $csvDate,
|
|
$roomsBooked,
|
|
isset($dailyBookings[$timestamp]) ? (int) $dailyBookings[$timestamp] : 0,
|
|
number_format($roomRevExcl, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format($svcRev, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format($taxAmt, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format($refundAmt, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format($collection, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format($netRevenue, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format($adr, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
$currencyIso,
|
|
));
|
|
$csvDate = date('Y-m-d', strtotime('+1 day', $timestamp));
|
|
}
|
|
}
|
|
|
|
fclose($out);
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Stream CSV for an Occupancy sub-report and exit.
|
|
*/
|
|
private function exportOccupancyCsv($dateFrom, $dateTo, $idHotel, $idLang, $report)
|
|
{
|
|
$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,
|
|
'id_product' => $idProduct ?: 0,
|
|
);
|
|
|
|
header('Content-Type: text/csv; charset=utf-8');
|
|
|
|
if ($report === 'availability') {
|
|
$availRows = HotelRoomInformation::getAvailabilityReport(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
'id_lang' => $idLang,
|
|
));
|
|
header('Content-Disposition: attachment; filename="availability-report-'.$dateFrom.'-to-'.$dateTo.'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$this->l('Date'), $this->l('Room Type'), $this->l('Total Rooms (Inventory)'),
|
|
$this->l('Rooms Booked'), $this->l('Out of Order / Maintenance'),
|
|
$this->l('Rooms Available'), $this->l('Rate'), $this->l('Occupancy %'),
|
|
));
|
|
foreach ($availRows as $row) {
|
|
fputcsv($out, array(
|
|
$row['date'],
|
|
$row['room_type_name'],
|
|
(int) $row['total_rooms'],
|
|
(int) $row['rooms_booked'],
|
|
(int) $row['out_of_order'],
|
|
(int) $row['available'],
|
|
'—',
|
|
number_format((float) $row['occupancy_pct'], 1, '.', '').'%',
|
|
));
|
|
}
|
|
} elseif ($report === 'room-status') {
|
|
$rows = HotelRoomInformation::getRoomStatus(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_product' => $idProduct,
|
|
'id_lang' => $idLang,
|
|
));
|
|
header('Content-Disposition: attachment; filename="room-status-report-'.$dateFrom.'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$this->l('Room No.'), $this->l('Floor'), $this->l('Room Type'),
|
|
$this->l('Status'), $this->l('Housekeeping Status'),
|
|
$this->l('Current Guest'), $this->l('Check-out Date'),
|
|
));
|
|
foreach ($rows as $row) {
|
|
if ($row['id_order']) {
|
|
$statusLabel = $this->l('Occupied');
|
|
} elseif ((int) $row['id_status'] === HotelRoomInformation::STATUS_INACTIVE
|
|
|| (int) $row['id_status'] === HotelRoomInformation::STATUS_TEMPORARY_INACTIVE) {
|
|
$statusLabel = $this->l('Under Maintenance');
|
|
} else {
|
|
$statusLabel = $this->l('Vacant');
|
|
}
|
|
fputcsv($out, array(
|
|
$row['room_num'],
|
|
isset($row['floor']) ? $row['floor'] : '',
|
|
$row['room_type_name'],
|
|
$statusLabel,
|
|
'—',
|
|
isset($row['guest_name']) ? $row['guest_name'] : '',
|
|
isset($row['date_to']) ? $row['date_to'] : '',
|
|
));
|
|
}
|
|
} elseif ($report === 'room-perf') {
|
|
$rows = HotelRoomType::getRoomTypePerformance(array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
'id_lang' => $idLang,
|
|
));
|
|
header('Content-Disposition: attachment; filename="room-type-performance-'.$dateFrom.'-to-'.$dateTo.'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$this->l('Room Type'), $this->l('Hotel'), $this->l('Total Rooms'),
|
|
$this->l('Total Room Nights Available'), $this->l('Total Room Nights Sold'),
|
|
$this->l('Occupancy Rate %'),
|
|
$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('Avg. LOS'),
|
|
$this->l('Currency'),
|
|
));
|
|
foreach ($rows as $row) {
|
|
fputcsv($out, array(
|
|
$row['room_type_name'],
|
|
$row['hotel_name'],
|
|
(int) $row['total_rooms'],
|
|
(int) $row['total_nights_available'],
|
|
(int) $row['room_nights'],
|
|
number_format((float) $row['occupancy_pct'], 1, '.', '').'%',
|
|
number_format((float) $row['room_revenue'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format((float) $row['tax_amount'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format((float) $row['total_revenue'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format((float) $row['adr'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format((float) $row['revpar'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
(int) $row['cancel_count'],
|
|
number_format((float) $row['avg_los'], 1, '.', ''),
|
|
$currencyIso,
|
|
));
|
|
}
|
|
} else {
|
|
$totalRoomsOcc = (int) AdminStatsController::getTotalRooms($idHotel ?: null, 1, $idProduct ?: null);
|
|
$dailyAllOcc = HotelBookingDetail::getDatewiseOccupiedRooms($baseParams);
|
|
$dailyBooked = HotelBookingDetail::getDatewiseOccupiedRooms(
|
|
array_merge($baseParams, array('id_status' => HotelBookingDetail::STATUS_ALLOTED))
|
|
);
|
|
$dailyCheckedIn = HotelBookingDetail::getDatewiseOccupiedRooms(
|
|
array_merge($baseParams, array('id_status' => HotelBookingDetail::STATUS_CHECKED_IN))
|
|
);
|
|
if ($idProduct) {
|
|
$dailyRevenue = HotelBookingDetail::getDatewiseRevenue($baseParams);
|
|
$dailyAvailable = array();
|
|
$dailyAdrOcc = array();
|
|
foreach ($dailyAllOcc as $ts => $occ) {
|
|
$dailyAvailable[$ts] = max(0, $totalRoomsOcc - $occ);
|
|
$rev = isset($dailyRevenue[$ts]) ? (float) $dailyRevenue[$ts]['total_price_tax_excl'] : 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');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
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('Occupancy Rate %'),
|
|
$this->l('ADR'), $this->l('RevPAR'), $this->l('Total Room Revenue'),
|
|
$this->l('Currency'),
|
|
));
|
|
foreach ($dailyAllOcc as $timestamp=> $allOcc) {
|
|
$available = isset($dailyAvailable[$timestamp]) ? (int) $dailyAvailable[$timestamp] : 0;
|
|
$booked = isset($dailyBooked[$timestamp]) ? (int) $dailyBooked[$timestamp] : 0;
|
|
$occupied = isset($dailyCheckedIn[$timestamp]) ? (int) $dailyCheckedIn[$timestamp] : 0;
|
|
$outOfOrder = max(0, $totalRoomsOcc - $available - (int) $allOcc);
|
|
$revenue = isset($dailyRevenue[$timestamp]) ? (float) $dailyRevenue[$timestamp] : 0.0;
|
|
$adr = isset($dailyAdrOcc[$timestamp]) ? (float) $dailyAdrOcc[$timestamp] : 0.0;
|
|
$revpar = $totalRoomsOcc ? round($revenue / $totalRoomsOcc, 2) : 0.0;
|
|
$occupancyPct = $totalRoomsOcc ? round((int) $allOcc / $totalRoomsOcc * 100, 1) : 0.0;
|
|
fputcsv($out, array(
|
|
"\t" . date('Y-m-d', $timestamp),
|
|
$totalRoomsOcc,
|
|
$available,
|
|
$booked,
|
|
$occupied,
|
|
$outOfOrder,
|
|
number_format($occupancyPct, 1, '.', '').'%',
|
|
number_format($adr, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format($revpar, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format($revenue, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
$currencyIso,
|
|
));
|
|
}
|
|
}
|
|
|
|
fclose($out);
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Stream CSV for a Guests sub-report and exit.
|
|
*/
|
|
private function exportGuestsCsv($dateFrom, $dateTo, $idHotel, $report)
|
|
{
|
|
header('Content-Type: text/csv; charset=utf-8');
|
|
$currencyIso = (new Currency(Configuration::get('PS_CURRENCY_DEFAULT')))->iso_code;
|
|
$baseParams = array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
);
|
|
|
|
if ($report === 'services') {
|
|
$idLangSvc = Context::getContext()->language->id;
|
|
$csvIdCategory = (int) Tools::getValue('id_category', 0);
|
|
$csvIdServiceProd = (int) Tools::getValue('id_service_product', 0);
|
|
$rows = ServiceProductOrderDetail::getServicesInfo(
|
|
array_merge($baseParams, array(
|
|
'id_lang' => $idLangSvc,
|
|
'id_category' => $csvIdCategory,
|
|
'id_service_product' => $csvIdServiceProd,
|
|
))
|
|
);
|
|
header('Content-Disposition: attachment; filename="services-report-'.$dateFrom.'-to-'.$dateTo.'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$this->l('Date'), $this->l('Booking Ref. No.'), $this->l('Guest Name'),
|
|
$this->l('Room No.'), $this->l('Service Name'), $this->l('Service Category'),
|
|
$this->l('Quantity'), $this->l('Unit Price'),
|
|
$this->l('Total Price (excl. Tax)'), $this->l('Tax Amount'), $this->l('Grand Total'),
|
|
$this->l('Currency'),
|
|
));
|
|
foreach ($rows as $row) {
|
|
fputcsv($out, array(
|
|
isset($row['date_add']) ? "\t" . date('Y-m-d', strtotime($row['date_add'])) : '',
|
|
isset($row['reference']) ? $row['reference'] : '',
|
|
$row['customer_name'],
|
|
isset($row['room_num']) ? $row['room_num'] : '',
|
|
$row['service_name'],
|
|
isset($row['service_category']) ? $row['service_category'] : '',
|
|
(int) $row['quantity'],
|
|
number_format((float) $row['unit_price'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format((float) $row['total_price_tax_excl'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format((float) $row['tax_amount'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format((float) $row['total_price_tax_incl'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
$currencyIso,
|
|
));
|
|
}
|
|
} elseif ($report === 'guest-directory') {
|
|
$idLangDir = Context::getContext()->language->id;
|
|
$csvGuestType = pSQL(Tools::getValue('guest_type', ''));
|
|
$guests = Order::getGuestDirectory(
|
|
array_merge($baseParams, array('id_lang' => $idLangDir))
|
|
);
|
|
if ($csvGuestType === 'new') {
|
|
$guests = array_values(array_filter($guests, function ($guest) {
|
|
return (int) $guest['total_stays'] === 1;
|
|
}));
|
|
} elseif ($csvGuestType === 'returning') {
|
|
$guests = array_values(array_filter($guests, function ($guest) {
|
|
return (int) $guest['total_stays'] > 1;
|
|
}));
|
|
}
|
|
header('Content-Disposition: attachment; filename="guest-directory-'.$dateFrom.'-to-'.$dateTo.'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$this->l('Guest ID'), $this->l('Guest Name'), $this->l('Email'),
|
|
$this->l('Phone'), $this->l('Country'), $this->l('State'), $this->l('City'),
|
|
$this->l('Company'), $this->l('VAT Number'), $this->l('Address'),
|
|
$this->l('Postcode'), $this->l('Guest Type'),
|
|
$this->l('Total Stays'), $this->l('Total Nights'), $this->l('Last Stay Date'),
|
|
$this->l('Lifetime Revenue'), $this->l('Avg Spend Per Stay'), $this->l('Currency'),
|
|
));
|
|
foreach ($guests as $guest) {
|
|
$address = isset($guest['address1']) ? $guest['address1'] : '';
|
|
if (!empty($guest['address2'])) {
|
|
$address .= ', '.$guest['address2'];
|
|
}
|
|
$guestType = ((int) $guest['total_stays'] === 1) ? $this->l('New') : $this->l('Returning');
|
|
fputcsv($out, array(
|
|
(int) $guest['id_customer'],
|
|
$guest['customer_name'],
|
|
$guest['email'],
|
|
isset($guest['phone']) ? $guest['phone'] : '',
|
|
isset($guest['country']) ? $guest['country'] : '',
|
|
isset($guest['state']) ? $guest['state'] : '',
|
|
isset($guest['city']) ? $guest['city'] : '',
|
|
isset($guest['company']) ? $guest['company'] : '',
|
|
isset($guest['vat_number']) ? $guest['vat_number'] : '',
|
|
$address,
|
|
isset($guest['postcode']) ? $guest['postcode'] : '',
|
|
$guestType,
|
|
(int) $guest['total_stays'],
|
|
(int) $guest['total_nights'],
|
|
isset($guest['last_stay']) ? $guest['last_stay'] : '',
|
|
number_format((float) $guest['lifetime_revenue'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format((float) $guest['avg_spend_per_stay'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
$currencyIso,
|
|
));
|
|
}
|
|
} else {
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
}
|
|
|
|
fclose($out);
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Stream CSV for a Channels sub-report and exit.
|
|
*/
|
|
private function exportChannelsCsv($dateFrom, $dateTo, $idHotel, $report)
|
|
{
|
|
header('Content-Type: text/csv; charset=utf-8');
|
|
$currencyIso = (new Currency(Configuration::get('PS_CURRENCY_DEFAULT')))->iso_code;
|
|
$baseParams = array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
);
|
|
|
|
if ($report === 'source') {
|
|
$rows = HotelBookingDetail::getChannelStats($baseParams);
|
|
$totalRevExcl = 0.0;
|
|
foreach ($rows as $row) {
|
|
$totalRevExcl += (float) $row['revenue_excl'];
|
|
}
|
|
header('Content-Disposition: attachment; filename="booking-source-'.$dateFrom.'-to-'.$dateTo.'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$this->l('Channel'), $this->l('Total Orders'), $this->l('Rooms Booked'), $this->l('Room Nights'),
|
|
$this->l('Room Revenue (excl. Tax)'), $this->l('Discount Amount'),
|
|
$this->l('Refund Amount'), $this->l('Tax Amount'),
|
|
$this->l('Total Collection'), $this->l('Net Revenue'),
|
|
$this->l('Cancellations'), $this->l('Cancel Rate %'),
|
|
$this->l('ADR'), $this->l('Contribution %'), $this->l('Currency'),
|
|
));
|
|
foreach ($rows as $row) {
|
|
$revExcl = (float) $row['revenue_excl'];
|
|
$revIncl = (float) $row['revenue_incl'];
|
|
$taxAmt = $revIncl - $revExcl;
|
|
$discAmt = isset($row['discount_amount']) ? (float) $row['discount_amount'] : 0.0;
|
|
$netRev = $revExcl - $discAmt;
|
|
$bookings = (int) $row['bookings'];
|
|
$cancels = isset($row['cancellations']) ? (int) $row['cancellations'] : 0;
|
|
$cancelRate = $bookings > 0 ? round($cancels / $bookings * 100, 1) : 0.0;
|
|
$roomNights = (int) $row['room_nights'];
|
|
$adr = $roomNights > 0 ? round($revExcl / $roomNights, 2) : 0.0;
|
|
$contrib = $totalRevExcl > 0 ? round($revExcl / $totalRevExcl * 100, 1) : 0.0;
|
|
fputcsv($out, array(
|
|
$row['channel_label'],
|
|
$bookings,
|
|
isset($row['rooms_booked']) ? (int) $row['rooms_booked'] : 0,
|
|
$roomNights,
|
|
number_format($revExcl, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format($discAmt, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format(isset($row['refund_amount']) ? (float) $row['refund_amount'] : 0.0, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format($taxAmt, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format($revIncl, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format($netRev, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
$cancels,
|
|
number_format($cancelRate, 1, '.', '').'%',
|
|
number_format($adr, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format($contrib, 1, '.', '').'%',
|
|
$currencyIso,
|
|
));
|
|
}
|
|
} elseif ($report === 'payment-method') {
|
|
$rows = HotelBookingDetail::getPaymentMethodStats($baseParams);
|
|
header('Content-Disposition: attachment; filename="payment-methods-'.$dateFrom.'-to-'.$dateTo.'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$this->l('Payment Method'), $this->l('Module'),
|
|
$this->l('Bookings'), $this->l('Revenue (excl. Tax)'), $this->l('Revenue (incl. Tax)'),
|
|
$this->l('Currency'),
|
|
));
|
|
foreach ($rows as $row) {
|
|
fputcsv($out, array(
|
|
$row['payment_method'], $row['module'],
|
|
$row['bookings'],
|
|
number_format((float) $row['revenue_excl'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format((float) $row['revenue_incl'], _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
$currencyIso,
|
|
));
|
|
}
|
|
} else {
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
}
|
|
|
|
fclose($out);
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Stream CSV for a Property sub-report and exit.
|
|
*/
|
|
private function exportPropertyCsv($dateFrom, $dateTo, $idHotel, $idLang, $report)
|
|
{
|
|
header('Content-Type: text/csv; charset=utf-8');
|
|
$currencyIso = (new Currency(Configuration::get('PS_CURRENCY_DEFAULT')))->iso_code;
|
|
$baseParams = array(
|
|
'date_from' => $dateFrom,
|
|
'date_to' => $dateTo,
|
|
'id_hotel' => $idHotel ?: false,
|
|
);
|
|
|
|
if ($report === 'daily-summary') {
|
|
$totalRoomsProp = AdminStatsController::getTotalRooms($idHotel ?: null, 1);
|
|
$roomsSoldByDay = HotelBookingDetail::getDatewiseOccupiedRooms($baseParams);
|
|
$checkedInByDay = HotelBookingDetail::getDatewiseOccupiedRooms(
|
|
array_merge($baseParams, array('id_status' => HotelBookingDetail::STATUS_CHECKED_IN))
|
|
);
|
|
$arrivalsByDay = HotelBookingDetail::getDatewiseArrivals($baseParams);
|
|
$departuresByDay = HotelBookingDetail::getDatewiseDepartures($baseParams);
|
|
$cancsByDay = HotelBookingDetail::getDatewiseCancellations($baseParams);
|
|
$adrByDay = AdminStatsController::getAverageDailyRateForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
|
|
$revenueByDay = AdminStatsController::getRoomsRevenueForDiscreteDates($dateFrom, $dateTo, $idHotel ?: null);
|
|
|
|
header('Content-Disposition: attachment; filename="daily-summary-'.$dateFrom.'-to-'.$dateTo.'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$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('Currency'),
|
|
));
|
|
$current = $dateFrom;
|
|
while ($current <= $dateTo) {
|
|
$timestamp = strtotime($current);
|
|
$roomsSold = isset($roomsSoldByDay[$timestamp]) ? (int) $roomsSoldByDay[$timestamp] : 0;
|
|
$occupancy = $totalRoomsProp ? round($roomsSold / $totalRoomsProp * 100, 1) : 0.0;
|
|
fputcsv($out, array(
|
|
"\t" . date('Y-m-d', $timestamp),
|
|
$totalRoomsProp,
|
|
$roomsSold,
|
|
number_format($occupancy, 1, '.', '').'%',
|
|
number_format(isset($adrByDay[$timestamp]) ? (float) $adrByDay[$timestamp] : 0.0, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format($totalRoomsProp ? round((isset($revenueByDay[$timestamp]) ? (float) $revenueByDay[$timestamp] : 0.0) / $totalRoomsProp, 2) : 0.0, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format(isset($revenueByDay[$timestamp]) ? (float) $revenueByDay[$timestamp] : 0.0, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
isset($arrivalsByDay[$timestamp]) ? (int) $arrivalsByDay[$timestamp] : 0,
|
|
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)));
|
|
}
|
|
|
|
} elseif ($report === 'hotel-comparison') {
|
|
$hotels = (new HotelBranchInformation())->hotelsNameAndId();
|
|
header('Content-Disposition: attachment; filename="hotel-comparison-'.$dateFrom.'-to-'.$dateTo.'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
fputcsv($out, array(
|
|
$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 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) {
|
|
$hotelParams = array_merge($baseParams, array('id_hotel' => (int) $hotel['id']));
|
|
$totalRoomsH = AdminStatsController::getTotalRooms((int) $hotel['id'], 1);
|
|
$bookings = (int) HotelBookingDetail::getTotalBookings($hotelParams);
|
|
$cancels = (int) HotelBookingDetail::getTotalCancellations($hotelParams);
|
|
$cancelRate = $bookings > 0 ? round($cancels / $bookings * 100, 1) : 0.0;
|
|
$roomNights = (int) HotelBookingDetail::getTotalRoomNights($hotelParams);
|
|
$roomRevenue = array_sum(AdminStatsController::getRoomsRevenueForDiscreteDates($dateFrom, $dateTo, (int) $hotel['id']));
|
|
$extraRev = (float) ServiceProductOrderDetail::getTotalRevenue($hotelParams);
|
|
$grossRevenue = $roomRevenue + $extraRev;
|
|
$occupancy = round(AdminStatsController::getAverageOccupancyRate($dateFrom, $dateTo, (int) $hotel['id']), 1);
|
|
$avgLos = ($bookings > 0 && $roomNights > 0) ? round($roomNights / $bookings, 1) : 0.0;
|
|
$obRows = Order::getOutstandingBalance($hotelParams);
|
|
$outstanding = is_array($obRows)
|
|
? array_sum(array_column($obRows, 'balance_due')) : 0.0;
|
|
fputcsv($out, array(
|
|
$hotel['hotel_name'],
|
|
$totalRoomsH,
|
|
$roomNights,
|
|
number_format($occupancy, 1, '.', '').'%',
|
|
number_format($roomRevenue, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format($extraRev, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format($grossRevenue, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format((float) AdminStatsController::getAverageDailyRate($dateFrom, $dateTo, (int) $hotel['id']), _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
number_format((float) AdminStatsController::getRevenuePerAvailableRoom($dateFrom, $dateTo, (int) $hotel['id']), _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
$bookings,
|
|
$cancels,
|
|
number_format($cancelRate, 1, '.', '').'%',
|
|
number_format($avgLos, 1, '.', ''),
|
|
number_format((float) $outstanding, _PS_PRICE_DISPLAY_PRECISION_, '.', ''),
|
|
$currencyIso,
|
|
));
|
|
}
|
|
|
|
} elseif ($report === 'out-of-order') {
|
|
$csvIdProductProp = (int) Tools::getValue('id_product', 0);
|
|
$csvFloorProp = pSQL(Tools::getValue('floor', ''));
|
|
$rows = HotelRoomDisableDates::getDisabledRooms(
|
|
array_merge($baseParams, array(
|
|
'id_lang' => $idLang,
|
|
'id_product' => $csvIdProductProp,
|
|
'floor' => $csvFloorProp,
|
|
))
|
|
);
|
|
header('Content-Disposition: attachment; filename="out-of-order-'.$dateFrom.'-to-'.$dateTo.'.csv"');
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
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('End Date'),
|
|
$this->l('Duration (Days)'), $this->l('Current Status'),
|
|
));
|
|
$today = date('Y-m-d');
|
|
foreach ($rows as $row) {
|
|
$oooStatus = ((int) $row['id_status'] === 2)
|
|
? $this->l('Out of Order') : $this->l('Under Maintenance');
|
|
$currentStatus = (!$row['disabled_to'] || $row['disabled_to'] >= $today)
|
|
? $this->l('Ongoing') : $this->l('Resolved');
|
|
fputcsv($out, array(
|
|
$row['room_num'],
|
|
isset($row['floor']) ? $row['floor'] : '',
|
|
$row['room_type_name'],
|
|
$oooStatus,
|
|
$row['reason'] ? $row['reason'] : '',
|
|
$row['disabled_from'],
|
|
$row['disabled_to'],
|
|
(int) $row['disabled_days'],
|
|
$currentStatus,
|
|
));
|
|
}
|
|
} else {
|
|
$out = fopen('php://output', 'w');
|
|
fputs($out, "\xEF\xBB\xBF");
|
|
}
|
|
|
|
fclose($out);
|
|
exit;
|
|
}
|
|
}
|