PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.9
VikBooking Hotel Booking Engine & PMS v1.6.9
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.6.9, at admin/helpers/report/rplans_revenue.php

785 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;";
267 $this->dbo->setQuery($q);
268 $this->dbo->execute();
269 if ($this->dbo->getNumRows()) {
270 $records = $this->dbo->loadAssocList();
271 }
272 if (!count($records)) {
273 $this->setError(JText::translate('VBOREPORTSERRNORESERV'));
274 return false;
275 }
276
277 // nest records with multiple rooms booked inside sub-array
278 $bookings = [];
279 foreach ($records as $v) {
280 if (!isset($bookings[$v['id']])) {
281 $bookings[$v['id']] = [];
282 }
283
284 // calculate the from_ts and to_ts values for later comparison
285 $in_info = getdate($v['checkin']);
286 $out_info = getdate($v['checkout']);
287 $v['from_ts'] = mktime(0, 0, 0, $in_info['mon'], $in_info['mday'], $in_info['year']);
288 $v['to_ts'] = mktime(23, 59, 59, $out_info['mon'], ($out_info['mday'] - 1), $out_info['year']);
289
290 if ($v['status'] == 'cancelled' && count($bookings[$v['id']])) {
291 // one room is sufficient for a cancelled booking with cancellation fees
292 continue;
293 }
294
295 // push nested room-booking
296 array_push($bookings[$v['id']], $v);
297 }
298
299 // define the columns of the report
300 $this->cols = array(
301 //date
302 array(
303 'key' => 'rplan',
304 'sortable' => 1,
305 'label' => JText::translate('VBOROVWSELRPLAN')
306 ),
307 //rooms sold
308 array(
309 'key' => 'rooms_sold',
310 'attr' => array(
311 'class="center"'
312 ),
313 'sortable' => 1,
314 'label' => JText::translate('VBOREPORTREVENUERSOLD')
315 ),
316 //total bookings
317 array(
318 'key' => 'tot_bookings',
319 'attr' => array(
320 'class="center"'
321 ),
322 'sortable' => 1,
323 'label' => JText::translate('VBOREPORTREVENUETOTB')
324 ),
325 //IBE revenue
326 array(
327 'key' => 'ibe_revenue',
328 'attr' => array(
329 'class="center"'
330 ),
331 'sortable' => 1,
332 'label' => JText::translate('VBOREPORTREVENUEREVWEB')
333 ),
334 //OTAs revenue
335 array(
336 'key' => 'ota_revenue',
337 'attr' => array(
338 'class="center"'
339 ),
340 'sortable' => 1,
341 'label' => JText::translate('VBOREPORTREVENUEREVOTA')
342 ),
343 //ADR
344 array(
345 'key' => 'adr',
346 'attr' => array(
347 'class="center"'
348 ),
349 'sortable' => 1,
350 'label' => JText::translate('VBOREPORTREVENUEADR'),
351 'tip' => JText::translate('VBOREPORTREVENUEADRHELP')
352 ),
353 //RevPAR
354 array(
355 'key' => 'revpar',
356 'attr' => array(
357 'class="center"'
358 ),
359 'sortable' => 1,
360 'label' => JText::translate('VBOREPORTREVENUEREVPAR'),
361 'tip' => JText::translate('VBOREPORTREVENUEREVPARH')
362 ),
363 //Taxes
364 array(
365 'key' => 'taxes',
366 'attr' => array(
367 'class="center"'
368 ),
369 'sortable' => 1,
370 'label' => JText::translate('VBOREPORTREVENUETAX')
371 ),
372 // Commissions
373 array(
374 'key' => 'cmms',
375 'attr' => array(
376 'class="center"'
377 ),
378 'sortable' => 1,
379 'label' => JText::translate('VBTOTALCOMMISSIONS')
380 ),
381 // Cancellation fees
382 array(
383 'key' => 'canc_fees',
384 'attr' => array(
385 'class="center"'
386 ),
387 'sortable' => 1,
388 'label' => JText::translate('VBO_CANC_FEE')
389 ),
390 //Revenue
391 array(
392 'key' => 'revenue',
393 'attr' => array(
394 'class="center"'
395 ),
396 'sortable' => 1,
397 'label' => JText::translate('VBOREPORTREVENUEREV'),
398 'tip' => JText::translate('VBO_DATA_CALCULATION_HELP'),
399 'tip_pos' => 'left',
400 )
401 );
402
403 $total_rooms_units = $this->countRooms($pidroom);
404
405 // loop over the bookings to build the data for each rate plan involved
406 $from_info = getdate($from_ts);
407 $to_info = getdate($to_ts);
408 $rplans_pool = [];
409 $website_rplans_map = [];
410 foreach ($bookings as $gbook) {
411 // count nights affected
412 $nights_affected = $gbook[0]['days'];
413 if (!$pfull_stay_count && ($gbook[0]['from_ts'] < $from_info[0] || $gbook[0]['to_ts'] > $to_info[0])) {
414 /**
415 * We count the average revenue per nights "stayed" only if the filter "full_stay_count" is off.
416 *
417 * @since 1.15.0 (J) - 1.5.0 (WP)
418 */
419 $nights_affected = $this->countNightsInvolved($gbook[0]['from_ts'], $gbook[0]['to_ts'], $from_info[0], $to_info[0], $gbook[0]['days']);
420 }
421
422 // parse rooms booked
423 foreach ($gbook as $rk => $book) {
424 // identify rate plan ("unknown" by default)
425 $rplan_identifier = JText::translate('VBOREPORTTOPCUNKNC');
426 if (!empty($book['pkg_id']) && !empty($book['pkg_name'])) {
427 // package
428 $rplan_identifier = $book['pkg_name'];
429 } elseif (!empty($book['cust_cost'])) {
430 // website custom cost
431 $rplan_identifier = JText::translate('VBOROOMCUSTRATEPLAN');
432 if (!empty($book['otarplan'])) {
433 // OTA Rate Plan
434 $rplan_identifier = ucwords($book['otarplan']);
435 // prepend the name of the channel
436 if (!empty($book['channel']) && !empty($book['idorderota'])) {
437 $channel_parts = explode('_', $book['channel']);
438 $channel_name = count($channel_parts) > 1 ? trim($channel_parts[1]) : trim($channel_parts[0]);
439 $rplan_identifier = $channel_name . ' - ' . $rplan_identifier;
440 }
441 }
442 } elseif (!empty($book['idprice'])) {
443 // website rate plan
444 $website_rplan_name = isset($website_rplans_map[$book['idprice']]) ? $website_rplans_map[$book['idprice']] : VikBooking::getPriceName($book['idprice']);
445 if (!empty($website_rplan_name)) {
446 $rplan_identifier = $website_rplan_name;
447 // cache rate plan ID/name to not make any other queries for the next loops
448 $website_rplans_map[$book['idprice']] = $website_rplan_name;
449 }
450 }
451
452 // collect information
453 if (!isset($rplans_pool[$rplan_identifier])) {
454 $rplans_pool[$rplan_identifier] = [
455 'rooms_sold' => 0,
456 'tot_bookings' => 0,
457 'ibe_revenue' => 0,
458 'ota_revenue' => 0,
459 'taxes' => 0,
460 'cmms' => 0,
461 'canc_fees' => 0,
462 'revenue' => 0,
463 ];
464 }
465
466 // immediately check for cancellation fees
467 if ($book['status'] == 'cancelled') {
468 // increase value
469 $rplans_pool[$rplan_identifier]['canc_fees'] += $book['canc_fee'];
470
471 // do not proceed to increase anything else
472 continue;
473 }
474
475 // increase rooms sold
476 $rplans_pool[$rplan_identifier]['rooms_sold']++;
477
478 // increase tot bookings, IBE/OTA revenue, taxes and commissions just once per booking
479 if ($rk < 1) {
480 $rplans_pool[$rplan_identifier]['tot_bookings']++;
481
482 // calculate net revenue and taxes for the affected nights of stay
483 $tot_net = $book['total'] - (float)$book['tot_taxes'] - (float)$book['tot_city_taxes'] - (float)$book['tot_fees'] - (float)$book['cmms'];
484 $tot_net = ($tot_net / (int)$book['days']) * $nights_affected;
485 $rplans_pool[$rplan_identifier]['revenue'] += $tot_net;
486 if (!empty($book['idorderota']) && !empty($book['channel'])) {
487 $rplans_pool[$rplan_identifier]['ota_revenue'] += $tot_net;
488 } else {
489 $rplans_pool[$rplan_identifier]['ibe_revenue'] += $tot_net;
490 }
491 $rplans_pool[$rplan_identifier]['taxes'] += (((float)$book['tot_taxes'] + (float)$book['tot_city_taxes'] + (float)$book['tot_fees']) / (int)$book['days']) * $nights_affected;
492 $rplans_pool[$rplan_identifier]['cmms'] += ((float)$book['cmms'] / (int)$book['days']) * $nights_affected;
493 }
494 }
495 }
496
497 // parse all rate plans found to build the rows
498 foreach ($rplans_pool as $rplan_name => $rplan_stats) {
499 // push fields in the rows array as a new row
500 array_push($this->rows, array(
501 array(
502 'key' => 'rplan',
503 'value' => $rplan_name,
504 ),
505 array(
506 'key' => 'rooms_sold',
507 'attr' => array(
508 'class="center"'
509 ),
510 'value' => $rplan_stats['rooms_sold'],
511 ),
512 array(
513 'key' => 'tot_bookings',
514 'attr' => array(
515 'class="center"'
516 ),
517 'value' => $rplan_stats['tot_bookings'],
518 ),
519 array(
520 'key' => 'ibe_revenue',
521 'attr' => array(
522 'class="center"'
523 ),
524 'callback' => function ($val) use ($currency_symb) {
525 return $currency_symb . ' ' . VikBooking::numberFormat($val);
526 },
527 'value' => $rplan_stats['ibe_revenue'],
528 ),
529 array(
530 'key' => 'ota_revenue',
531 'attr' => array(
532 'class="center"'
533 ),
534 'callback' => function ($val) use ($currency_symb) {
535 return $currency_symb . ' ' . VikBooking::numberFormat($val);
536 },
537 'value' => $rplan_stats['ota_revenue'],
538 ),
539 array(
540 'key' => 'adr',
541 'attr' => array(
542 'class="center"'
543 ),
544 'callback' => function ($val) use ($currency_symb) {
545 return $currency_symb . ' ' . VikBooking::numberFormat($val);
546 },
547 'value' => ($rplan_stats['rooms_sold'] > 0 ? ($rplan_stats['revenue'] / $rplan_stats['rooms_sold']) : 0),
548 ),
549 array(
550 'key' => 'revpar',
551 'attr' => array(
552 'class="center"'
553 ),
554 'callback' => function ($val) use ($currency_symb) {
555 return $currency_symb . ' ' . VikBooking::numberFormat($val);
556 },
557 'value' => ($rplan_stats['revenue'] / $total_rooms_units),
558 ),
559 array(
560 'key' => 'taxes',
561 'attr' => array(
562 'class="center"'
563 ),
564 'callback' => function ($val) use ($currency_symb) {
565 return $currency_symb . ' ' . VikBooking::numberFormat($val);
566 },
567 'value' => $rplan_stats['taxes'],
568 ),
569 array(
570 'key' => 'cmms',
571 'attr' => array(
572 'class="center"'
573 ),
574 'callback' => function ($val) use ($currency_symb) {
575 return $currency_symb . ' ' . VikBooking::numberFormat($val);
576 },
577 'value' => $rplan_stats['cmms'],
578 ),
579 array(
580 'key' => 'canc_fees',
581 'attr' => array(
582 'class="center"'
583 ),
584 'callback' => function ($val) use ($currency_symb) {
585 return $currency_symb . ' ' . VikBooking::numberFormat($val);
586 },
587 'value' => $rplan_stats['canc_fees'],
588 ),
589 array(
590 'key' => 'revenue',
591 'attr' => array(
592 'class="center"'
593 ),
594 'callback' => function ($val) use ($currency_symb) {
595 return $currency_symb . ' ' . VikBooking::numberFormat($val);
596 },
597 'value' => ($rplan_stats['revenue'] + $rplan_stats['canc_fees']),
598 )
599 ));
600 }
601
602 // sort rows
603 $this->sortRows($pkrsort, $pkrorder);
604
605 // loop over the rows to build the footer row with the totals
606 $foot_rooms_sold = 0;
607 $foot_tot_bookings = 0;
608 $foot_ibe_revenue = 0;
609 $foot_ota_revenue = 0;
610 $foot_taxes = 0;
611 $foot_cmms = 0;
612 $foot_canc_fees = 0;
613 $foot_revenue = 0;
614
615 foreach ($this->rows as $row) {
616 $foot_rooms_sold += $row[1]['value'];
617 $foot_tot_bookings += $row[2]['value'];
618 $foot_ibe_revenue += $row[3]['value'];
619 $foot_ota_revenue += $row[4]['value'];
620 $foot_taxes += $row[7]['value'];
621 $foot_cmms += $row[8]['value'];
622 $foot_canc_fees += $row[9]['value'];
623 $foot_revenue += $row[10]['value'];
624 }
625
626 array_push($this->footerRow, array(
627 array(
628 'attr' => array(
629 'class="vbo-report-total"'
630 ),
631 'value' => '<h3>'.JText::translate('VBOREPORTSTOTALROW').'</h3>',
632 ),
633 array(
634 'attr' => array(
635 'class="center"'
636 ),
637 'value' => $foot_rooms_sold,
638 ),
639 array(
640 'attr' => array(
641 'class="center"'
642 ),
643 'value' => $foot_tot_bookings,
644 ),
645 array(
646 'attr' => array(
647 'class="center"'
648 ),
649 'callback' => function ($val) use ($currency_symb) {
650 return $currency_symb . ' ' . VikBooking::numberFormat($val);
651 },
652 'value' => $foot_ibe_revenue,
653 ),
654 array(
655 'attr' => array(
656 'class="center"'
657 ),
658 'callback' => function ($val) use ($currency_symb) {
659 return $currency_symb . ' ' . VikBooking::numberFormat($val);
660 },
661 'value' => $foot_ota_revenue,
662 ),
663 array(
664 'value' => '',
665 ),
666 array(
667 'value' => '',
668 ),
669 array(
670 'attr' => array(
671 'class="center"'
672 ),
673 'callback' => function ($val) use ($currency_symb) {
674 return $currency_symb . ' ' . VikBooking::numberFormat($val);
675 },
676 'value' => $foot_taxes,
677 ),
678 array(
679 'attr' => array(
680 'class="center"'
681 ),
682 'callback' => function ($val) use ($currency_symb) {
683 return $currency_symb . ' ' . VikBooking::numberFormat($val);
684 },
685 'value' => $foot_cmms,
686 ),
687 array(
688 'attr' => array(
689 'class="center"'
690 ),
691 'callback' => function ($val) use ($currency_symb) {
692 return $currency_symb . ' ' . VikBooking::numberFormat($val);
693 },
694 'value' => $foot_canc_fees,
695 ),
696 array(
697 'attr' => array(
698 'class="center"'
699 ),
700 'callback' => function ($val) use ($currency_symb) {
701 return $currency_symb . ' ' . VikBooking::numberFormat($val);
702 },
703 'value' => $foot_revenue,
704 )
705 ));
706
707 // Debug
708 if ($this->debug) {
709 $this->setWarning('path to report file = '.urlencode(dirname(__FILE__)).'<br/>');
710 $this->setWarning('$total_rooms_units = '.$total_rooms_units.'<br/>');
711 $this->setWarning('$bookings:<pre>'.print_r($bookings, true).'</pre><br/>');
712 }
713
714 return true;
715 }
716
717 /**
718 * Registers the name to give to the CSV file being exported.
719 *
720 * @return void
721 *
722 * @since 1.16.1 (J) - 1.6.1 (WP)
723 */
724 private function registerExportCSVFileName()
725 {
726 $pfromdate = VikRequest::getString('fromdate', '', 'request');
727 $ptodate = VikRequest::getString('todate', '', 'request');
728
729 $report_extraname = '';
730 $pchannel = VikRequest::getString('channel', '', 'request');
731 if (strlen($pchannel)) {
732 // set channel name for exported file
733 if ($pchannel == '-1') {
734 $report_extraname = JText::translate('VBORDFROMSITE');
735 } else {
736 $report_extraname = $pchannel;
737 }
738 }
739
740 $this->setExportCSVFileName($this->reportName . (!empty($report_extraname) ? '-' . $report_extraname : '') . '-' . str_replace('/', '_', $pfromdate) . '-' . str_replace('/', '_', $ptodate) . '.csv');
741 }
742
743 /**
744 * Private method that counts the number of nights involved for a reservation given some filter dates.
745 *
746 * @param int $bfrom_ts booking check-in timestamp.
747 * @param int $bto_ts booking check-out timestamp.
748 * @param int $fstart_ts filter start timestamp.
749 * @param int $fend_ts filter end timestamp.
750 * @param int $nights booking total nights.
751 *
752 * @return int number of nights involved.
753 */
754 private function countNightsInvolved($bfrom_ts, $bto_ts, $fstart_ts, $fend_ts, $nights)
755 {
756 $nights = (int)$nights;
757 $nights = $nights > 0 ? $nights : 1;
758
759 if (empty($bfrom_ts) || empty($bto_ts) || empty($fstart_ts) || empty($fend_ts)) {
760 // rollback to number of nights
761 return $nights;
762 }
763
764 $affected = 0;
765 if ($fend_ts < $fstart_ts) {
766 return $affected;
767 }
768
769 $from_info = getdate($fstart_ts);
770 while ($from_info[0] <= $fend_ts) {
771 if ($from_info[0] >= $bfrom_ts && $from_info[0] <= $bto_ts) {
772 $affected++;
773 }
774 if ($from_info[0] > $bto_ts) {
775 // useless to continue
776 break;
777 }
778 // go to next day
779 $from_info = getdate(mktime(0, 0, 0, $from_info['mon'], ($from_info['mday'] + 1), $from_info['year']));
780 }
781
782 return $affected;
783 }
784 }
785