PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.12
VikBooking Hotel Booking Engine & PMS v1.8.12
1.8.16 1.8.15 1.8.14 1.8.13 1.8.12 1.8.11 1.8.10 1.8.9 1.8.6 1.8.7 1.8.8 trunk 1.6.0 1.6.1 1.6.2 1.6.3 1.6.4 1.6.5 1.6.6 1.6.7 1.6.8 1.6.9 1.7.0 1.7.1 1.7.2 All 37 releases
vikbooking / admin / helpers / report / rplans_revenue.php

rplans_revenue.php in VikBooking Hotel Booking Engine & PMS 1.8.12, at admin/helpers/report/rplans_revenue.php

783 lines 24.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * @package VikBooking
4 * @subpackage com_vikbooking
5 * @author Alessio Gaggii - e4j - Extensionsforjoomla.com
6 * @copyright Copyright (C) 2018 e4j - Extensionsforjoomla.com. All rights reserved.
7 * @license GNU General Public License version 2 or later; see LICENSE
8 * @link https://vikwp.com
9 */
10
11 defined('ABSPATH') or die('No script kiddies please!');
12
13 /**
14 * Rate Plans Revenue child Class of VikBookingReport
15 */
16 class VikBookingReportRplansRevenue extends VikBookingReport
17 {
18 /**
19 * Property 'defaultKeySort' is used by the View that renders the report.
20 */
21 public $defaultKeySort = 'revenue';
22
23 /**
24 * Property 'defaultKeyOrder' is used by the View that renders the report.
25 */
26 public $defaultKeyOrder = 'DESC';
27
28 /**
29 * Property 'exportAllowed' is used by the View to display the export button.
30 */
31 public $exportAllowed = 1;
32
33 /**
34 * Debug mode is activated by passing the value 'e4j_debug' > 0
35 */
36 private $debug;
37
38 /**
39 * Class constructor should define the name of the report and
40 * other vars. Call the parent constructor to define the DB object.
41 */
42 public function __construct()
43 {
44 $this->reportFile = basename(__FILE__, '.php');
45 $this->reportName = JText::translate('VBOREPORT'.strtoupper(str_replace('_', '', $this->reportFile)));
46 $this->reportFilters = [];
47
48 $this->cols = [];
49 $this->rows = [];
50 $this->footerRow = [];
51
52 $this->debug = (VikRequest::getInt('e4j_debug', 0, 'request') > 0);
53
54 $this->registerExportCSVFileName();
55
56 parent::__construct();
57 }
58
59 /**
60 * Returns the name of this report.
61 *
62 * @return string
63 */
64 public function getName()
65 {
66 return $this->reportName;
67 }
68
69 /**
70 * Returns the name of this file without .php.
71 *
72 * @return string
73 */
74 public function getFileName()
75 {
76 return $this->reportFile;
77 }
78
79 /**
80 * Returns the filters of this report.
81 *
82 * @return array
83 */
84 public function getFilters()
85 {
86 if (count($this->reportFilters)) {
87 //do not run this method twice, as it could load JS and CSS files.
88 return $this->reportFilters;
89 }
90
91 //get VBO Application Object
92 $vbo_app = VikBooking::getVboApplication();
93
94 //load the jQuery UI Datepicker
95 $this->loadDatePicker();
96
97 //From Date Filter
98 $filter_opt = array(
99 'label' => '<label for="fromdate">'.JText::translate('VBOREPORTSDATEFROM').'</label>',
100 'html' => '<input type="text" id="fromdate" name="fromdate" value="" class="vbo-report-datepicker vbo-report-datepicker-from" />',
101 'type' => 'calendar',
102 'name' => 'fromdate'
103 );
104 array_push($this->reportFilters, $filter_opt);
105
106 //To Date Filter
107 $filter_opt = array(
108 'label' => '<label for="todate">'.JText::translate('VBOREPORTSDATETO').'</label>',
109 'html' => '<input type="text" id="todate" name="todate" value="" class="vbo-report-datepicker vbo-report-datepicker-to" />',
110 'type' => 'calendar',
111 'name' => 'todate'
112 );
113 array_push($this->reportFilters, $filter_opt);
114
115 //Room ID filter
116 $pidroom = VikRequest::getInt('idroom', '', 'request');
117 $all_rooms = $this->getRooms();
118 $rooms = [];
119 foreach ($all_rooms as $room) {
120 $rooms[$room['id']] = $room['name'];
121 }
122 if (count($rooms)) {
123 $rooms_sel_html = $vbo_app->getNiceSelect($rooms, $pidroom, 'idroom', JText::translate('VBOSTATSALLROOMS'), JText::translate('VBOSTATSALLROOMS'), '', '', 'idroom');
124 $filter_opt = array(
125 'label' => '<label for="idroom">'.JText::translate('VBOREPORTSROOMFILT').'</label>',
126 'html' => $rooms_sel_html,
127 'type' => 'select',
128 'name' => 'idroom'
129 );
130 array_push($this->reportFilters, $filter_opt);
131 }
132
133 // channel filter
134 $all_channels = [];
135 $pchannel = VikRequest::getString('channel', '', 'request');
136 $q = "SELECT `channel` FROM `#__vikbooking_orders` WHERE `channel` IS NOT NULL GROUP BY `channel`;";
137 $this->dbo->setQuery($q);
138 $this->dbo->execute();
139 if ($this->dbo->getNumRows()) {
140 $ord_channels = $this->dbo->loadAssocList();
141 // push website as first option
142 $all_channels['-1'] = JText::translate('VBORDFROMSITE');
143 // push all channel names
144 foreach ($ord_channels as $o_channel) {
145 $channel_parts = explode('_', $o_channel['channel']);
146 $channel_name = count($channel_parts) > 1 ? trim($channel_parts[1]) : trim($channel_parts[0]);
147 if (isset($all_channels[$channel_name])) {
148 continue;
149 }
150 $all_channels[$channel_name] = $channel_name;
151 }
152 // push filter
153 $channels_sel_html = $vbo_app->getNiceSelect($all_channels, $pchannel, 'channel', '- - - -', '- - - -', '', '', 'channel');
154 $filter_opt = array(
155 'label' => '<label for="channel">'.JText::translate('VBCHANNELFILTER').'</label>',
156 'html' => $channels_sel_html,
157 'type' => 'select',
158 'name' => 'channel'
159 );
160 array_push($this->reportFilters, $filter_opt);
161 }
162
163 // full stay count filter
164 $pfull_stay_count = VikRequest::getInt('full_stay_count', 0, 'request');
165 $fullsc_sel_html = $vbo_app->getNiceSelect([JText::translate('VBO_STAYED_NIGHTS'), JText::translate('VBO_FULL_STAY_VALUES')], $pfull_stay_count, 'full_stay_count', '', '', '', '', 'full_stay_count');
166 $filter_opt = array(
167 'label' => '<label for="full_stay_count">'.JText::translate('VBO_DATA_CALCULATION').'</label>',
168 'html' => $fullsc_sel_html,
169 'type' => 'select',
170 'name' => 'full_stay_count'
171 );
172 array_push($this->reportFilters, $filter_opt);
173
174 // get minimum check-in and maximum check-out for dates filters
175 $df = $this->getDateFormat();
176 $mincheckin = 0;
177 $maxcheckout = 0;
178 $q = "SELECT MIN(`checkin`) AS `mincheckin`, MAX(`checkout`) AS `maxcheckout` FROM `#__vikbooking_orders` WHERE `status`='confirmed' AND `closure`=0;";
179 $this->dbo->setQuery($q);
180 $this->dbo->execute();
181 if ($this->dbo->getNumRows()) {
182 $data = $this->dbo->loadAssoc();
183 if (!empty($data['mincheckin']) && !empty($data['maxcheckout'])) {
184 $mincheckin = $data['mincheckin'];
185 $maxcheckout = $data['maxcheckout'];
186 }
187 }
188 //
189
190 //jQuery code for the datepicker calendars and select2
191 $pfromdate = VikRequest::getString('fromdate', '', 'request');
192 $ptodate = VikRequest::getString('todate', '', 'request');
193 $js = 'jQuery(function() {
194 jQuery(".vbo-report-datepicker:input").datepicker({
195 '.(!empty($mincheckin) ? 'minDate: "'.date($df, $mincheckin).'", ' : '').'
196 '.(!empty($maxcheckout) ? 'maxDate: "'.date($df, $maxcheckout).'", ' : '').'
197 '.(!empty($mincheckin) && !empty($maxcheckout) ? 'yearRange: "'.(date('Y', $mincheckin)).':'.date('Y', $maxcheckout).'", changeMonth: true, changeYear: true, ' : '').'
198 dateFormat: "'.$this->getDateFormat('jui').'",
199 onSelect: vboReportCheckDates
200 });
201 '.(!empty($pfromdate) ? 'jQuery(".vbo-report-datepicker-from").datepicker("setDate", "'.$pfromdate.'");' : '').'
202 '.(!empty($ptodate) ? 'jQuery(".vbo-report-datepicker-to").datepicker("setDate", "'.$ptodate.'");' : '').'
203 });
204 function vboReportCheckDates(selectedDate, inst) {
205 if (selectedDate === null || inst === null) {
206 return;
207 }
208 var cur_from_date = jQuery(this).val();
209 if (jQuery(this).hasClass("vbo-report-datepicker-from") && cur_from_date.length) {
210 var nowstart = jQuery(this).datepicker("getDate");
211 var nowstartdate = new Date(nowstart.getTime());
212 jQuery(".vbo-report-datepicker-to").datepicker("option", {minDate: nowstartdate});
213 }
214 }';
215 $this->setScript($js);
216
217 return $this->reportFilters;
218 }
219
220 /**
221 * Loads the report data from the DB.
222 * Returns true in case of success, false otherwise.
223 * Sets the columns and rows for the report to be displayed.
224 *
225 * @return boolean
226 */
227 public function getReportData()
228 {
229 if (strlen($this->getError())) {
230 //Export functions may set errors rather than exiting the process, and the View may continue the execution to attempt to render the report.
231 return false;
232 }
233 //Input fields and other vars
234 $pfromdate = VikRequest::getString('fromdate', '', 'request');
235 $ptodate = VikRequest::getString('todate', '', 'request');
236 $pidroom = VikRequest::getInt('idroom', '', 'request');
237 $pchannel = VikRequest::getString('channel', '', 'request');
238 $pfull_stay_count = VikRequest::getInt('full_stay_count', 0, 'request');
239 $pkrsort = VikRequest::getString('krsort', $this->defaultKeySort, 'request');
240 $pkrsort = empty($pkrsort) ? $this->defaultKeySort : $pkrsort;
241 $pkrorder = VikRequest::getString('krorder', $this->defaultKeyOrder, 'request');
242 $pkrorder = empty($pkrorder) ? $this->defaultKeyOrder : $pkrorder;
243 $pkrorder = $pkrorder == 'DESC' ? 'DESC' : 'ASC';
244 $currency_symb = VikBooking::getCurrencySymb();
245 $df = $this->getDateFormat();
246 $datesep = VikBooking::getDateSeparator();
247 if (empty($ptodate)) {
248 $ptodate = $pfromdate;
249 }
250 //Get dates timestamps
251 $from_ts = VikBooking::getDateTimestamp($pfromdate, 0, 0);
252 $to_ts = VikBooking::getDateTimestamp($ptodate, 23, 59, 59);
253 if (empty($pfromdate) || empty($from_ts) || empty($to_ts)) {
254 $this->setError(JText::translate('VBOREPORTSERRNODATES'));
255 return false;
256 }
257
258 //Query to obtain the records
259 $records = [];
260 $q = "SELECT `o`.`id`,`o`.`ts`,`o`.`status`,`o`.`days`,`o`.`checkin`,`o`.`checkout`,`o`.`totpaid`,`o`.`roomsnum`,`o`.`total`,`o`.`idorderota`,`o`.`channel`,`o`.`country`,`o`.`tot_taxes`," .
261 "`o`.`tot_city_taxes`,`o`.`tot_fees`,`o`.`cmms`,`o`.`canc_fee`,`or`.`idorder`,`or`.`idroom`,`or`.`idtar`,`or`.`optionals`,`or`.`pkg_id`,`or`.`pkg_name`,`or`.`cust_cost`,`or`.`cust_idiva`,`or`.`extracosts`,`or`.`room_cost`,`or`.`otarplan`,`d`.`idprice` " .
262 "FROM `#__vikbooking_orders` AS `o` LEFT JOIN `#__vikbooking_ordersrooms` AS `or` ON `or`.`idorder`=`o`.`id` " .
263 "LEFT JOIN `#__vikbooking_dispcost` AS `d` ON `d`.`id`=`or`.`idtar` " .
264 "WHERE (`o`.`status`='confirmed' OR (`o`.`status`='cancelled' AND `o`.`canc_fee` > 0)) AND `o`.`closure`=0 AND `o`.`checkout`>={$from_ts} AND `o`.`checkin`<={$to_ts} ".(!empty($pidroom) ? "AND `or`.`idroom`=".(int)$pidroom." " : "") .
265 (strlen($pchannel) ? "AND `o`.`channel` " . ($pchannel == '-1' ? 'IS NULL' : "LIKE " . $this->dbo->quote("%{$pchannel}%")) . ' ' : '') .
266 "ORDER BY `o`.`checkin` ASC, `o`.`id` ASC, `or`.`id` ASC;";
267 $this->dbo->setQuery($q);
268 $records = $this->dbo->loadAssocList();
269
270 if (!$records) {
271 $this->setError(JText::translate('VBOREPORTSERRNORESERV'));
272 return false;
273 }
274
275 // nest records with multiple rooms booked inside sub-array
276 $bookings = [];
277 foreach ($records as $v) {
278 if (!isset($bookings[$v['id']])) {
279 $bookings[$v['id']] = [];
280 }
281
282 // calculate the from_ts and to_ts values for later comparison
283 $in_info = getdate($v['checkin']);
284 $out_info = getdate($v['checkout']);
285 $v['from_ts'] = mktime(0, 0, 0, $in_info['mon'], $in_info['mday'], $in_info['year']);
286 $v['to_ts'] = mktime(23, 59, 59, $out_info['mon'], ($out_info['mday'] - 1), $out_info['year']);
287
288 if ($v['status'] == 'cancelled' && count($bookings[$v['id']])) {
289 // one room is sufficient for a cancelled booking with cancellation fees
290 continue;
291 }
292
293 // push nested room-booking
294 array_push($bookings[$v['id']], $v);
295 }
296
297 // define the columns of the report
298 $this->cols = array(
299 //date
300 array(
301 'key' => 'rplan',
302 'sortable' => 1,
303 'label' => JText::translate('VBOROVWSELRPLAN')
304 ),
305 //rooms sold
306 array(
307 'key' => 'rooms_sold',
308 'attr' => array(
309 'class="center"'
310 ),
311 'sortable' => 1,
312 'label' => JText::translate('VBOREPORTREVENUERSOLD')
313 ),
314 //total bookings
315 array(
316 'key' => 'tot_bookings',
317 'attr' => array(
318 'class="center"'
319 ),
320 'sortable' => 1,
321 'label' => JText::translate('VBOREPORTREVENUETOTB')
322 ),
323 //IBE revenue
324 array(
325 'key' => 'ibe_revenue',
326 'attr' => array(
327 'class="center"'
328 ),
329 'sortable' => 1,
330 'label' => JText::translate('VBOREPORTREVENUEREVWEB')
331 ),
332 //OTAs revenue
333 array(
334 'key' => 'ota_revenue',
335 'attr' => array(
336 'class="center"'
337 ),
338 'sortable' => 1,
339 'label' => JText::translate('VBOREPORTREVENUEREVOTA')
340 ),
341 //ADR
342 array(
343 'key' => 'adr',
344 'attr' => array(
345 'class="center"'
346 ),
347 'sortable' => 1,
348 'label' => JText::translate('VBOREPORTREVENUEADR'),
349 'tip' => JText::translate('VBOREPORTREVENUEADRHELP')
350 ),
351 //RevPAR
352 array(
353 'key' => 'revpar',
354 'attr' => array(
355 'class="center"'
356 ),
357 'sortable' => 1,
358 'label' => JText::translate('VBOREPORTREVENUEREVPAR'),
359 'tip' => JText::translate('VBOREPORTREVENUEREVPARH')
360 ),
361 //Taxes
362 array(
363 'key' => 'taxes',
364 'attr' => array(
365 'class="center"'
366 ),
367 'sortable' => 1,
368 'label' => JText::translate('VBOREPORTREVENUETAX')
369 ),
370 // Commissions
371 array(
372 'key' => 'cmms',
373 'attr' => array(
374 'class="center"'
375 ),
376 'sortable' => 1,
377 'label' => JText::translate('VBTOTALCOMMISSIONS')
378 ),
379 // Cancellation fees
380 array(
381 'key' => 'canc_fees',
382 'attr' => array(
383 'class="center"'
384 ),
385 'sortable' => 1,
386 'label' => JText::translate('VBO_CANC_FEE')
387 ),
388 //Revenue
389 array(
390 'key' => 'revenue',
391 'attr' => array(
392 'class="center"'
393 ),
394 'sortable' => 1,
395 'label' => JText::translate('VBOREPORTREVENUEREV'),
396 'tip' => JText::translate('VBO_DATA_CALCULATION_HELP'),
397 'tip_pos' => 'left',
398 )
399 );
400
401 $total_rooms_units = $this->countRooms($pidroom) ?: 1;
402
403 // loop over the bookings to build the data for each rate plan involved
404 $from_info = getdate($from_ts);
405 $to_info = getdate($to_ts);
406 $rplans_pool = [];
407 $website_rplans_map = [];
408 foreach ($bookings as $gbook) {
409 // count nights affected
410 $nights_affected = $gbook[0]['days'];
411 if (!$pfull_stay_count && ($gbook[0]['from_ts'] < $from_info[0] || $gbook[0]['to_ts'] > $to_info[0])) {
412 /**
413 * We count the average revenue per nights "stayed" only if the filter "full_stay_count" is off.
414 *
415 * @since 1.15.0 (J) - 1.5.0 (WP)
416 */
417 $nights_affected = $this->countNightsInvolved($gbook[0]['from_ts'], $gbook[0]['to_ts'], $from_info[0], $to_info[0], $gbook[0]['days']);
418 }
419
420 // parse rooms booked
421 foreach ($gbook as $rk => $book) {
422 // identify rate plan ("unknown" by default)
423 $rplan_identifier = JText::translate('VBOREPORTTOPCUNKNC');
424 if (!empty($book['pkg_id']) && !empty($book['pkg_name'])) {
425 // package
426 $rplan_identifier = $book['pkg_name'];
427 } elseif (!empty($book['cust_cost'])) {
428 // website custom cost
429 $rplan_identifier = JText::translate('VBOROOMCUSTRATEPLAN');
430 if (!empty($book['otarplan'])) {
431 // OTA Rate Plan
432 $rplan_identifier = ucwords($book['otarplan']);
433 // prepend the name of the channel
434 if (!empty($book['channel']) && !empty($book['idorderota'])) {
435 $channel_parts = explode('_', $book['channel']);
436 $channel_name = count($channel_parts) > 1 ? trim($channel_parts[1]) : trim($channel_parts[0]);
437 $rplan_identifier = $channel_name . ' - ' . $rplan_identifier;
438 }
439 }
440 } elseif (!empty($book['idprice'])) {
441 // website rate plan
442 $website_rplan_name = isset($website_rplans_map[$book['idprice']]) ? $website_rplans_map[$book['idprice']] : VikBooking::getPriceName($book['idprice']);
443 if (!empty($website_rplan_name)) {
444 $rplan_identifier = $website_rplan_name;
445 // cache rate plan ID/name to not make any other queries for the next loops
446 $website_rplans_map[$book['idprice']] = $website_rplan_name;
447 }
448 }
449
450 // collect information
451 if (!isset($rplans_pool[$rplan_identifier])) {
452 $rplans_pool[$rplan_identifier] = [
453 'rooms_sold' => 0,
454 'tot_bookings' => 0,
455 'ibe_revenue' => 0,
456 'ota_revenue' => 0,
457 'taxes' => 0,
458 'cmms' => 0,
459 'canc_fees' => 0,
460 'revenue' => 0,
461 ];
462 }
463
464 // immediately check for cancellation fees
465 if ($book['status'] == 'cancelled') {
466 // increase value
467 $rplans_pool[$rplan_identifier]['canc_fees'] += $book['canc_fee'];
468
469 // do not proceed to increase anything else
470 continue;
471 }
472
473 // increase rooms sold
474 $rplans_pool[$rplan_identifier]['rooms_sold']++;
475
476 // increase tot bookings, IBE/OTA revenue, taxes and commissions just once per booking
477 if ($rk < 1) {
478 $rplans_pool[$rplan_identifier]['tot_bookings']++;
479
480 // calculate net revenue and taxes for the affected nights of stay
481 $tot_net = $book['total'] - (float)$book['tot_taxes'] - (float)$book['tot_city_taxes'] - (float)$book['tot_fees'] - (float)$book['cmms'];
482 $tot_net = ($tot_net / (int)$book['days']) * $nights_affected;
483 $rplans_pool[$rplan_identifier]['revenue'] += $tot_net;
484 if (!empty($book['idorderota']) && !empty($book['channel'])) {
485 $rplans_pool[$rplan_identifier]['ota_revenue'] += $tot_net;
486 } else {
487 $rplans_pool[$rplan_identifier]['ibe_revenue'] += $tot_net;
488 }
489 $rplans_pool[$rplan_identifier]['taxes'] += (((float)$book['tot_taxes'] + (float)$book['tot_city_taxes'] + (float)$book['tot_fees']) / (int)$book['days']) * $nights_affected;
490 $rplans_pool[$rplan_identifier]['cmms'] += ((float)$book['cmms'] / (int)$book['days']) * $nights_affected;
491 }
492 }
493 }
494
495 // parse all rate plans found to build the rows
496 foreach ($rplans_pool as $rplan_name => $rplan_stats) {
497 // push fields in the rows array as a new row
498 array_push($this->rows, array(
499 array(
500 'key' => 'rplan',
501 'value' => $rplan_name,
502 ),
503 array(
504 'key' => 'rooms_sold',
505 'attr' => array(
506 'class="center"'
507 ),
508 'value' => $rplan_stats['rooms_sold'],
509 ),
510 array(
511 'key' => 'tot_bookings',
512 'attr' => array(
513 'class="center"'
514 ),
515 'value' => $rplan_stats['tot_bookings'],
516 ),
517 array(
518 'key' => 'ibe_revenue',
519 'attr' => array(
520 'class="center"'
521 ),
522 'callback' => function ($val) use ($currency_symb) {
523 return $currency_symb . ' ' . VikBooking::numberFormat($val);
524 },
525 'value' => $rplan_stats['ibe_revenue'],
526 ),
527 array(
528 'key' => 'ota_revenue',
529 'attr' => array(
530 'class="center"'
531 ),
532 'callback' => function ($val) use ($currency_symb) {
533 return $currency_symb . ' ' . VikBooking::numberFormat($val);
534 },
535 'value' => $rplan_stats['ota_revenue'],
536 ),
537 array(
538 'key' => 'adr',
539 'attr' => array(
540 'class="center"'
541 ),
542 'callback' => function ($val) use ($currency_symb) {
543 return $currency_symb . ' ' . VikBooking::numberFormat($val);
544 },
545 'value' => ($rplan_stats['rooms_sold'] > 0 ? ($rplan_stats['revenue'] / $rplan_stats['rooms_sold']) : 0),
546 ),
547 array(
548 'key' => 'revpar',
549 'attr' => array(
550 'class="center"'
551 ),
552 'callback' => function ($val) use ($currency_symb) {
553 return $currency_symb . ' ' . VikBooking::numberFormat($val);
554 },
555 'value' => ($rplan_stats['revenue'] / $total_rooms_units),
556 ),
557 array(
558 'key' => 'taxes',
559 'attr' => array(
560 'class="center"'
561 ),
562 'callback' => function ($val) use ($currency_symb) {
563 return $currency_symb . ' ' . VikBooking::numberFormat($val);
564 },
565 'value' => $rplan_stats['taxes'],
566 ),
567 array(
568 'key' => 'cmms',
569 'attr' => array(
570 'class="center"'
571 ),
572 'callback' => function ($val) use ($currency_symb) {
573 return $currency_symb . ' ' . VikBooking::numberFormat($val);
574 },
575 'value' => $rplan_stats['cmms'],
576 ),
577 array(
578 'key' => 'canc_fees',
579 'attr' => array(
580 'class="center"'
581 ),
582 'callback' => function ($val) use ($currency_symb) {
583 return $currency_symb . ' ' . VikBooking::numberFormat($val);
584 },
585 'value' => $rplan_stats['canc_fees'],
586 ),
587 array(
588 'key' => 'revenue',
589 'attr' => array(
590 'class="center"'
591 ),
592 'callback' => function ($val) use ($currency_symb) {
593 return $currency_symb . ' ' . VikBooking::numberFormat($val);
594 },
595 'value' => ($rplan_stats['revenue'] + $rplan_stats['canc_fees']),
596 )
597 ));
598 }
599
600 // sort rows
601 $this->sortRows($pkrsort, $pkrorder);
602
603 // loop over the rows to build the footer row with the totals
604 $foot_rooms_sold = 0;
605 $foot_tot_bookings = 0;
606 $foot_ibe_revenue = 0;
607 $foot_ota_revenue = 0;
608 $foot_taxes = 0;
609 $foot_cmms = 0;
610 $foot_canc_fees = 0;
611 $foot_revenue = 0;
612
613 foreach ($this->rows as $row) {
614 $foot_rooms_sold += $row[1]['value'];
615 $foot_tot_bookings += $row[2]['value'];
616 $foot_ibe_revenue += $row[3]['value'];
617 $foot_ota_revenue += $row[4]['value'];
618 $foot_taxes += $row[7]['value'];
619 $foot_cmms += $row[8]['value'];
620 $foot_canc_fees += $row[9]['value'];
621 $foot_revenue += $row[10]['value'];
622 }
623
624 array_push($this->footerRow, array(
625 array(
626 'attr' => array(
627 'class="vbo-report-total"'
628 ),
629 'value' => '<h3>'.JText::translate('VBOREPORTSTOTALROW').'</h3>',
630 ),
631 array(
632 'attr' => array(
633 'class="center"'
634 ),
635 'value' => $foot_rooms_sold,
636 ),
637 array(
638 'attr' => array(
639 'class="center"'
640 ),
641 'value' => $foot_tot_bookings,
642 ),
643 array(
644 'attr' => array(
645 'class="center"'
646 ),
647 'callback' => function ($val) use ($currency_symb) {
648 return $currency_symb . ' ' . VikBooking::numberFormat($val);
649 },
650 'value' => $foot_ibe_revenue,
651 ),
652 array(
653 'attr' => array(
654 'class="center"'
655 ),
656 'callback' => function ($val) use ($currency_symb) {
657 return $currency_symb . ' ' . VikBooking::numberFormat($val);
658 },
659 'value' => $foot_ota_revenue,
660 ),
661 array(
662 'value' => '',
663 ),
664 array(
665 'value' => '',
666 ),
667 array(
668 'attr' => array(
669 'class="center"'
670 ),
671 'callback' => function ($val) use ($currency_symb) {
672 return $currency_symb . ' ' . VikBooking::numberFormat($val);
673 },
674 'value' => $foot_taxes,
675 ),
676 array(
677 'attr' => array(
678 'class="center"'
679 ),
680 'callback' => function ($val) use ($currency_symb) {
681 return $currency_symb . ' ' . VikBooking::numberFormat($val);
682 },
683 'value' => $foot_cmms,
684 ),
685 array(
686 'attr' => array(
687 'class="center"'
688 ),
689 'callback' => function ($val) use ($currency_symb) {
690 return $currency_symb . ' ' . VikBooking::numberFormat($val);
691 },
692 'value' => $foot_canc_fees,
693 ),
694 array(
695 'attr' => array(
696 'class="center"'
697 ),
698 'callback' => function ($val) use ($currency_symb) {
699 return $currency_symb . ' ' . VikBooking::numberFormat($val);
700 },
701 'value' => $foot_revenue,
702 )
703 ));
704
705 // Debug
706 if ($this->debug) {
707 $this->setWarning('path to report file = '.urlencode(dirname(__FILE__)).'<br/>');
708 $this->setWarning('$total_rooms_units = '.$total_rooms_units.'<br/>');
709 $this->setWarning('$bookings:<pre>'.print_r($bookings, true).'</pre><br/>');
710 }
711
712 return true;
713 }
714
715 /**
716 * Registers the name to give to the CSV file being exported.
717 *
718 * @return void
719 *
720 * @since 1.16.1 (J) - 1.6.1 (WP)
721 */
722 private function registerExportCSVFileName()
723 {
724 $pfromdate = VikRequest::getString('fromdate', '', 'request');
725 $ptodate = VikRequest::getString('todate', '', 'request');
726
727 $report_extraname = '';
728 $pchannel = VikRequest::getString('channel', '', 'request');
729 if (strlen($pchannel)) {
730 // set channel name for exported file
731 if ($pchannel == '-1') {
732 $report_extraname = JText::translate('VBORDFROMSITE');
733 } else {
734 $report_extraname = $pchannel;
735 }
736 }
737
738 $this->setExportCSVFileName($this->reportName . (!empty($report_extraname) ? '-' . $report_extraname : '') . '-' . str_replace('/', '_', $pfromdate) . '-' . str_replace('/', '_', $ptodate) . '.csv');
739 }
740
741 /**
742 * Private method that counts the number of nights involved for a reservation given some filter dates.
743 *
744 * @param int $bfrom_ts booking check-in timestamp.
745 * @param int $bto_ts booking check-out timestamp.
746 * @param int $fstart_ts filter start timestamp.
747 * @param int $fend_ts filter end timestamp.
748 * @param int $nights booking total nights.
749 *
750 * @return int number of nights involved.
751 */
752 private function countNightsInvolved($bfrom_ts, $bto_ts, $fstart_ts, $fend_ts, $nights)
753 {
754 $nights = (int)$nights;
755 $nights = $nights > 0 ? $nights : 1;
756
757 if (empty($bfrom_ts) || empty($bto_ts) || empty($fstart_ts) || empty($fend_ts)) {
758 // rollback to number of nights
759 return $nights;
760 }
761
762 $affected = 0;
763 if ($fend_ts < $fstart_ts) {
764 return $affected;
765 }
766
767 $from_info = getdate($fstart_ts);
768 while ($from_info[0] <= $fend_ts) {
769 if ($from_info[0] >= $bfrom_ts && $from_info[0] <= $bto_ts) {
770 $affected++;
771 }
772 if ($from_info[0] > $bto_ts) {
773 // useless to continue
774 break;
775 }
776 // go to next day
777 $from_info = getdate(mktime(0, 0, 0, $from_info['mon'], ($from_info['mday'] + 1), $from_info['year']));
778 }
779
780 return $affected;
781 }
782 }
783