File "hotel_meals.php"

Full Path: /home/romayxjt/public_html/wp-content/plugins/vikbooking/admin/helpers/report/hotel_meals.php
File size: 18.15 KB
MIME-type: text/x-php
Charset: utf-8

<?php
/**
 * @package     VikBooking
 * @subpackage  com_vikbooking
 * @author      Alessio Gaggii - e4j - Extensionsforjoomla.com
 * @copyright   Copyright (C) 2023 e4j - Extensionsforjoomla.com. All rights reserved.
 * @license     GNU General Public License version 2 or later; see LICENSE
 * @link        https://vikwp.com
 */

defined('ABSPATH') or die('No script kiddies please!');

/**
 * Hotel Meals child Class of VikBookingReport.
 * 
 * @since 	1.16.1 (J) - 1.6.1 (WP)
 */
class VikBookingReportHotelMeals extends VikBookingReport
{
	/**
	 * Property 'defaultKeySort' is used by the View that renders the report.
	 */
	public $defaultKeySort = 'day';

	/**
	 * Property 'defaultKeyOrder' is used by the View that renders the report.
	 */
	public $defaultKeyOrder = 'ASC';

	/**
	 * Property 'exportAllowed' is used by the View to display the export button.
	 */
	public $exportAllowed = 1;

	/**
	 * Debug mode is activated by passing the value 'e4j_debug' > 0
	 */
	private $debug;

	/**
	 * Class constructor should define the name of the report and
	 * other vars. Call the parent constructor to define the DB object.
	 */
	public function __construct()
	{
		$this->reportFile = basename(__FILE__, '.php');
		$this->reportName = 'Hotel - ' . JText::translate('VBO_MEALS');
		$this->reportFilters = array();

		$this->cols = array();
		$this->rows = array();
		$this->footerRow = array();

		$this->debug = (VikRequest::getInt('e4j_debug', 0, 'request') > 0);

		$this->registerExportCSVFileName();

		parent::__construct();
	}

	/**
	 * Returns the name of this report.
	 *
	 * @return 	string
	 */
	public function getName()
	{
		return $this->reportName;
	}

	/**
	 * Returns the name of this file without .php.
	 *
	 * @return 	string
	 */
	public function getFileName()
	{
		return $this->reportFile;
	}

	/**
	 * Returns the filters of this report.
	 *
	 * @return 	array
	 */
	public function getFilters()
	{
		if (count($this->reportFilters)) {
			// do not run this method twice, as it could load JS and CSS files.
			return $this->reportFilters;
		}

		//load the jQuery UI Datepicker
		$this->loadDatePicker();

		//From Date Filter
		$filter_opt = array(
			'label' => '<label for="fromdate">'.JText::translate('VBOREPORTSDATEFROM').'</label>',
			'html' => '<input type="text" id="fromdate" name="fromdate" value="" class="vbo-report-datepicker vbo-report-datepicker-from" />',
			'type' => 'calendar',
			'name' => 'fromdate'
		);
		array_push($this->reportFilters, $filter_opt);

		//To Date Filter
		$filter_opt = array(
			'label' => '<label for="todate">'.JText::translate('VBOREPORTSDATETO').'</label>',
			'html' => '<input type="text" id="todate" name="todate" value="" class="vbo-report-datepicker vbo-report-datepicker-to" />',
			'type' => 'calendar',
			'name' => 'todate'
		);
		array_push($this->reportFilters, $filter_opt);

		// get minimum check-in and maximum check-out for dates filters
		$df = $this->getDateFormat();
		$mincheckin = 0;
		$maxcheckout = 0;
		$q = "SELECT MIN(`checkin`) AS `mincheckin`, MAX(`checkout`) AS `maxcheckout` FROM `#__vikbooking_orders` WHERE `status`='confirmed' AND `closure`=0;";
		$this->dbo->setQuery($q);
		$this->dbo->execute();
		if ($this->dbo->getNumRows()) {
			$data = $this->dbo->loadAssoc();
			if (!empty($data['mincheckin']) && !empty($data['maxcheckout'])) {
				$mincheckin = $data['mincheckin'];
				$maxcheckout = $data['maxcheckout'];
			}
		}

		// set up datepicker calendars
		$pfromdate = VikRequest::getString('fromdate', '', 'request');
		$ptodate = VikRequest::getString('todate', '', 'request');
		$js = 'jQuery(function() {
			jQuery(".vbo-report-datepicker:input").datepicker({
				'.(!empty($mincheckin) ? 'minDate: "'.date($df, $mincheckin).'", ' : '').'
				'.(!empty($maxcheckout) ? 'maxDate: "'.date($df, $maxcheckout).'", ' : '').'
				'.(!empty($mincheckin) && !empty($maxcheckout) ? 'yearRange: "'.(date('Y', $mincheckin)).':'.date('Y', $maxcheckout).'", changeMonth: true, changeYear: true, ' : '').'
				dateFormat: "'.$this->getDateFormat('jui').'",
				onSelect: vboReportCheckDates
			});
			'.(!empty($pfromdate) ? 'jQuery(".vbo-report-datepicker-from").datepicker("setDate", "'.$pfromdate.'");' : '').'
			'.(!empty($ptodate) ? 'jQuery(".vbo-report-datepicker-to").datepicker("setDate", "'.$ptodate.'");' : '').'
		});
		function vboReportCheckDates(selectedDate, inst) {
			if (selectedDate === null || inst === null) {
				return;
			}
			var cur_from_date = jQuery(this).val();
			if (jQuery(this).hasClass("vbo-report-datepicker-from") && cur_from_date.length) {
				var nowstart = jQuery(this).datepicker("getDate");
				var nowstartdate = new Date(nowstart.getTime());
				jQuery(".vbo-report-datepicker-to").datepicker("option", {minDate: nowstartdate});
			}
		}';
		$this->setScript($js);

		return $this->reportFilters;
	}

	/**
	 * Loads the report data from the DB.
	 * Returns true in case of success, false otherwise.
	 * Sets the columns and rows for the report to be displayed.
	 *
	 * @return 	boolean
	 */
	public function getReportData()
	{
		if (strlen($this->getError())) {
			// export functions may set errors rather than exiting the process, and the View may continue the execution to attempt to render the report.
			return false;
		}

		// input fields and other vars
		$pfromdate = VikRequest::getString('fromdate', '', 'request');
		$ptodate = VikRequest::getString('todate', '', 'request');
		$pkrsort = VikRequest::getString('krsort', $this->defaultKeySort, 'request');
		$pkrsort = empty($pkrsort) ? $this->defaultKeySort : $pkrsort;
		$pkrorder = VikRequest::getString('krorder', $this->defaultKeyOrder, 'request');
		$pkrorder = empty($pkrorder) ? $this->defaultKeyOrder : $pkrorder;
		$pkrorder = $pkrorder == 'DESC' ? 'DESC' : 'ASC';

		$df = $this->getDateFormat();
		$datesep = VikBooking::getDateSeparator();
		if (empty($ptodate)) {
			$ptodate = $pfromdate;
		}

		// get date timestamps
		$from_ts = VikBooking::getDateTimestamp($pfromdate, 0, 0);
		$to_ts = VikBooking::getDateTimestamp($ptodate, 23, 59, 59);
		if (empty($pfromdate) || empty($from_ts) || empty($to_ts) || $to_ts < $from_ts) {
			$this->setError(JText::translate('VBOREPORTSERRNODATES'));
			return false;
		}

		// query to obtain the records
		$q = "SELECT `o`.`id`,`o`.`custdata`,`o`.`ts`,`o`.`days`,`o`.`checkin`,`o`.`checkout`,`o`.`roomsnum`,`o`.`idorderota`,`o`.`channel`,`o`.`country`,`o`.`adminnotes`,".
			"`or`.`idorder`,`or`.`idroom`,`or`.`adults`,`or`.`children`,`or`.`pets`,`or`.`idtar`,`or`.`t_first_name`,`or`.`t_last_name`,`or`.`roomindex`,`or`.`pkg_name`,`or`.`otarplan`,".
			"`or`.`meals`,`r`.`name`,`r`.`params`,`co`.`idcustomer`,`c`.`first_name`,`c`.`last_name`,`c`.`country` AS `customer_country` ".
			"FROM `#__vikbooking_orders` AS `o` LEFT JOIN `#__vikbooking_ordersrooms` AS `or` ON `or`.`idorder`=`o`.`id` LEFT JOIN `#__vikbooking_rooms` AS `r` ON `or`.`idroom`=`r`.`id` ".
			"LEFT JOIN `#__vikbooking_customers_orders` AS `co` ON `co`.`idorder`=`o`.`id` LEFT JOIN `#__vikbooking_customers` AS `c` ON `c`.`id`=`co`.`idcustomer` ".
			"WHERE `o`.`status`='confirmed' AND `o`.`closure`=0 AND `o`.`checkout` >= {$from_ts} AND `o`.`checkin` <= {$to_ts} ".
			"ORDER BY `o`.`checkin` ASC, `o`.`id` ASC, `or`.`id` ASC;";
		$this->dbo->setQuery($q);
		$records = $this->dbo->loadAssocList();
		if (!$records) {
			$this->setError(JText::translate('VBOREPORTSERRNORESERV'));
			return false;
		}

		// nest records with multiple rooms booked inside sub-array
		$bookings = array();
		foreach ($records as $v) {
			if (!isset($bookings[$v['id']])) {
				$bookings[$v['id']] = array();
			}

			// calculate the from_ts and to_ts values for later comparison
			$in_info = getdate($v['checkin']);
			$out_info = getdate($v['checkout']);
			$v['from_ts'] = mktime(0, 0, 0, $in_info['mon'], $in_info['mday'], $in_info['year']);
			$v['to_ts'] = mktime(23, 59, 59, $out_info['mon'], $out_info['mday'], $out_info['year']);

			array_push($bookings[$v['id']], $v);
		}

		// define the columns of the report
		$this->cols = array(
			// date
			array(
				'key' => 'day',
				'sortable' => 1,
				'label' => JText::translate('VBOREPORTREVENUEDAY')
			),
			// arrivals
			array(
				'key' => 'arrivals',
				'attr' => array(
					'class="center"'
				),
				'sortable' => 1,
				'label' => JText::translate('VBOARRIVING')
			),
			// departures
			array(
				'key' => 'departures',
				'attr' => array(
					'class="center"'
				),
				'sortable' => 1,
				'label' => JText::translate('VBODEPARTING')
			),
			// stayover
			array(
				'key' => 'stayover',
				'attr' => array(
					'class="center"'
				),
				'sortable' => 1,
				'label' => JText::translate('VBOTYPESTAYOVER')
			),
			// breakfast
			array(
				'key' => 'breakfast',
				'attr' => array(
					'class="center"'
				),
				'sortable' => 1,
				'label' => JText::translate('VBO_MEAL_BREAKFAST')
			),
			// lunch
			array(
				'key' => 'lunch',
				'attr' => array(
					'class="center"'
				),
				'sortable' => 1,
				'label' => JText::translate('VBO_MEAL_LUNCH')
			),
			// dinner
			array(
				'key' => 'dinner',
				'attr' => array(
					'class="center"'
				),
				'sortable' => 1,
				'label' => JText::translate('VBO_MEAL_DINNER')
			),
			// website
			array(
				'key' => 'website',
				'attr' => array(
					'class="center"'
				),
				'sortable' => 1,
				'label' => JText::translate('VBORDFROMSITE')
			),
			// channels
			array(
				'key' => 'channels',
				'attr' => array(
					'class="center"'
				),
				'sortable' => 1,
				'label' => JText::translate('VBOCHANNELS'),
				'tip' => JText::translate('VBO_CRON_OTA_RES'),
			),
		);

		// loop over the dates of the report to build the rows
		$from_info = getdate($from_ts);
		$to_info = getdate($to_ts);
		while ($from_info[0] <= $to_info[0]) {
			// prepare default fields for this row
			$day_ts = $from_info[0];
			$day_ymd = date('Y-m-d', $day_ts);
			$curwday = $this->getWdayString($from_info['wday'], 'short');

			$tot_arrivals 	= 0;
			$gst_arrivals 	= 0;
			$tot_departures = 0;
			$gst_departures = 0;
			$tot_stayover   = 0;
			$gst_stayover   = 0;
			$tot_breakfast  = 0;
			$tot_lunch 		= 0;
			$tot_dinner 	= 0;
			$website_res 	= 0;
			$channel_res 	= 0;

			// calculate the report details for this day
			foreach ($bookings as $gbook) {
				// make sure the booking affects the current day
				if (!($from_info[0] >= $gbook[0]['from_ts'] && $from_info[0] <= $gbook[0]['to_ts'])) {
					// skip reservation for the current day
					continue;
				}

				// check stay type for today
				if ($day_ymd == date('Y-m-d', $gbook[0]['checkin'])) {
					// arrival
					$raw_type = 'arriving';
					$tot_arrivals++;
				} elseif ($day_ymd == date('Y-m-d', $gbook[0]['checkout'])) {
					// departure
					$raw_type = 'departing';
					$tot_departures++;
				} else {
					// stayover
					$raw_type = 'stayover';
					$tot_stayover++;
				}

				// check reservation provenience
				if (!empty($gbook[0]['idorderota']) && !empty($gbook[0]['channel'])) {
					// OTA reservation
					$channel_res++;
				} else {
					// website reservation
					$website_res++;
				}

				// check meal plans for each room record of this booking
				foreach ($gbook as $roomres) {
					// rate plan name and ID, if any
					$active_rplan_id = 0;
					if (!empty($roomres['otarplan'])) {
						$rplan = $roomres['otarplan'];
					} else {
						list($rplan, $active_rplan_id) = $this->getPriceName($roomres['idtar']);
					}

					// room guests number
					$room_guests_numb = ($roomres['adults'] + $roomres['children']);

					// count number of guests per stay type
					if ($raw_type == 'arriving') {
						// arrival
						$gst_arrivals += $room_guests_numb;
					} elseif ($raw_type == 'departing') {
						// departure
						$gst_departures += $room_guests_numb;
					} else {
						// stayover
						$gst_stayover += $room_guests_numb;
					}

					// meals included in the room rate
					$included_meals = [];
					if (!empty($roomres['meals'])) {
						// display included meals defined at room-reservation record
						$included_meals = VBOMealplanManager::getInstance()->roomRateIncludedMeals($roomres);
					} else {
						// fetch default included meals in the selected rate plan
						$included_meals = $active_rplan_id ? VBOMealplanManager::getInstance()->ratePlanIncludedMeals($active_rplan_id) : [];
					}
					if (!$included_meals && empty($roomres['meals']) && !empty($roomres['idorderota']) && !empty($roomres['channel']) && !empty($roomres['custdata'])) {
						// attempt to fetch the included meal plans from the raw customer data or OTA reservation and room
						$included_meals = VBOMealplanManager::getInstance()->otaDataIncludedMeals($roomres, $roomres);
					}
					foreach ($included_meals as $meal_enum => $meal_name) {
						if ($raw_type == 'arriving') {
							// no breakfast for those who arrive
							if ($meal_enum == 'breakfast') {
								continue;
							}
							// increase pax for this meal
							if ($meal_enum == 'lunch') {
								$tot_lunch += $room_guests_numb;
							} elseif ($meal_enum == 'dinner') {
								$tot_dinner += $room_guests_numb;
							}
						} elseif ($raw_type == 'departing') {
							// only breakfast for those who depart
							if ($meal_enum == 'breakfast') {
								// increase pax for this meal
								$tot_breakfast += $room_guests_numb;
							}
						} else {
							// increase pax for this meal (any kind of meal for those who are staying)
							if ($meal_enum == 'breakfast') {
								$tot_breakfast += $room_guests_numb;
							} elseif ($meal_enum == 'lunch') {
								$tot_lunch += $room_guests_numb;
							} elseif ($meal_enum == 'dinner') {
								$tot_dinner += $room_guests_numb;
							}
						}
					}
				}
			}

			// push fields in the rows array as a new row
			array_push($this->rows, array(
				array(
					'key' => 'day',
					'callback' => function ($val) use ($df, $datesep, $curwday) {
						return $curwday.', '.date(str_replace("/", $datesep, $df), $val);
					},
					'value' => $day_ts,
				),
				array(
					'key' => 'arrivals',
					'attr' => array(
						'class="center"'
					),
					'value' => $gst_arrivals,
				),
				array(
					'key' => 'departures',
					'attr' => array(
						'class="center"'
					),
					'value' => $gst_departures,
				),
				array(
					'key' => 'stayover',
					'attr' => array(
						'class="center"'
					),
					'value' => $gst_stayover,
				),
				array(
					'key' => 'breakfast',
					'attr' => array(
						'class="center"'
					),
					'value' => $tot_breakfast,
				),
				array(
					'key' => 'lunch',
					'attr' => array(
						'class="center"'
					),
					'value' => $tot_lunch,
				),
				array(
					'key' => 'dinner',
					'attr' => array(
						'class="center"'
					),
					'value' => $tot_dinner,
				),
				array(
					'key' => 'website',
					'attr' => array(
						'class="center"'
					),
					'value' => $website_res,
				),
				array(
					'key' => 'channels',
					'attr' => array(
						'class="center"'
					),
					'value' => $channel_res,
				),
			));

			// next day iteration
			$from_info = getdate(mktime(0, 0, 0, $from_info['mon'], ($from_info['mday'] + 1), $from_info['year']));
		}

		// sort rows
		$this->sortRows($pkrsort, $pkrorder);

		// loop over the rows to build the footer row with the totals
		$foot_arrivals 	  = 0;
		$foot_departures  = 0;
		$foot_stayover 	  = 0;
		$foot_breakfast   = 0;
		$foot_lunch 	  = 0;
		$foot_dinner 	  = 0;
		$foot_website_res = 0;
		$foot_channel_res = 0;

		foreach ($this->rows as $row) {
			$foot_arrivals 	  += $row[1]['value'];
			$foot_departures  += $row[2]['value'];
			$foot_stayover 	  += $row[3]['value'];
			$foot_breakfast   += $row[4]['value'];
			$foot_lunch 	  += $row[5]['value'];
			$foot_dinner 	  += $row[6]['value'];
			$foot_website_res += $row[7]['value'];
			$foot_channel_res += $row[8]['value'];
		}

		array_push($this->footerRow, array(
			array(
				'attr' => array(
					'class="vbo-report-total"'
				),
				'value' => '<h3>' . JText::translate('VBOREPORTSTOTALROW') . '</h3>',
			),
			array(
				'attr' => array(
					'class="center"'
				),
				'value' => $foot_arrivals,
			),
			array(
				'attr' => array(
					'class="center"'
				),
				'value' => $foot_departures,
			),
			array(
				'attr' => array(
					'class="center"'
				),
				'value' => $foot_stayover,
			),
			array(
				'attr' => array(
					'class="center"'
				),
				'value' => $foot_breakfast,
			),
			array(
				'attr' => array(
					'class="center"'
				),
				'value' => $foot_lunch,
			),
			array(
				'attr' => array(
					'class="center"'
				),
				'value' => $foot_dinner,
			),
			array(
				'attr' => array(
					'class="center"'
				),
				'value' => $foot_website_res,
			),
			array(
				'attr' => array(
					'class="center"'
				),
				'value' => $foot_channel_res,
			),
		));

		// Debug
		if ($this->debug) {
			$this->setWarning('path to report file = '.urlencode(dirname(__FILE__)).'<br/>');
			$this->setWarning('$total_rooms_units = '.$total_rooms_units.'<br/>');
			$this->setWarning('$bookings:<pre>'.print_r($bookings, true).'</pre><br/>');
		}

		return true;
	}

	/**
	 * Registers the name to give to the CSV file being exported.
	 * 
	 * @return 	void
	 */
	private function registerExportCSVFileName()
	{
		$pfromdate = VikRequest::getString('fromdate', '', 'request');
		$ptodate = VikRequest::getString('todate', '', 'request');

		$this->setExportCSVFileName($this->reportName . '-' . str_replace('/', '_', $pfromdate) . '-' . str_replace('/', '_', $ptodate) . '.csv');
	}

	/**
	 * Finds the ID and name of the rate plan from the given tariff ID.
	 *
	 * @param 	int  	$idtar	the ID of the tariff.
	 *
	 * @return 	array 			list of rate plan name (or an empty string) and ID.
	 */
	private function getPriceName($idtar)
	{
		static $tariffs_map = [];

		$idtar = (int)$idtar;
		if (!$idtar) {
			return [JText::translate('VBOROOMCUSTRATEPLAN'), 0];
		}

		if (isset($tariffs_map[$idtar])) {
			return $tariffs_map[$idtar];
		}

		$q = "SELECT `p`.`id`, `p`.`name` FROM `#__vikbooking_prices` AS `p`
			LEFT JOIN `#__vikbooking_dispcost` AS `t` ON `p`.`id`=`t`.`idprice` WHERE `t`.`id`={$idtar}";
		$this->dbo->setQuery($q, 0, 1);
		$price_record = $this->dbo->loadAssoc();

		if ($price_record) {
			// cache value and return it
			$tariffs_map[$idtar] = [$price_record['name'], $price_record['id']];

			return $tariffs_map[$idtar];
		}

		// cache value and return it
		$tariffs_map[$idtar] = ['', 0];

		return $tariffs_map[$idtar];
	}
}