PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.8.8
VikBooking Hotel Booking Engine & PMS v1.8.8
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 / hotel_meals.php

hotel_meals.php in VikBooking Hotel Booking Engine & PMS 1.8.8, at admin/helpers/report/hotel_meals.php

646 lines 18.1 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) 2023 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 * Hotel Meals child Class of VikBookingReport.
15 *
16 * @since 1.16.1 (J) - 1.6.1 (WP)
17 */
18 class VikBookingReportHotelMeals extends VikBookingReport
19 {
20 /**
21 * Property 'defaultKeySort' is used by the View that renders the report.
22 */
23 public $defaultKeySort = 'day';
24
25 /**
26 * Property 'defaultKeyOrder' is used by the View that renders the report.
27 */
28 public $defaultKeyOrder = 'ASC';
29
30 /**
31 * Property 'exportAllowed' is used by the View to display the export button.
32 */
33 public $exportAllowed = 1;
34
35 /**
36 * Debug mode is activated by passing the value 'e4j_debug' > 0
37 */
38 private $debug;
39
40 /**
41 * Class constructor should define the name of the report and
42 * other vars. Call the parent constructor to define the DB object.
43 */
44 public function __construct()
45 {
46 $this->reportFile = basename(__FILE__, '.php');
47 $this->reportName = 'Hotel - ' . JText::translate('VBO_MEALS');
48 $this->reportFilters = array();
49
50 $this->cols = array();
51 $this->rows = array();
52 $this->footerRow = array();
53
54 $this->debug = (VikRequest::getInt('e4j_debug', 0, 'request') > 0);
55
56 $this->registerExportCSVFileName();
57
58 parent::__construct();
59 }
60
61 /**
62 * Returns the name of this report.
63 *
64 * @return string
65 */
66 public function getName()
67 {
68 return $this->reportName;
69 }
70
71 /**
72 * Returns the name of this file without .php.
73 *
74 * @return string
75 */
76 public function getFileName()
77 {
78 return $this->reportFile;
79 }
80
81 /**
82 * Returns the filters of this report.
83 *
84 * @return array
85 */
86 public function getFilters()
87 {
88 if (count($this->reportFilters)) {
89 // do not run this method twice, as it could load JS and CSS files.
90 return $this->reportFilters;
91 }
92
93 //load the jQuery UI Datepicker
94 $this->loadDatePicker();
95
96 //From Date Filter
97 $filter_opt = array(
98 'label' => '<label for="fromdate">'.JText::translate('VBOREPORTSDATEFROM').'</label>',
99 'html' => '<input type="text" id="fromdate" name="fromdate" value="" class="vbo-report-datepicker vbo-report-datepicker-from" />',
100 'type' => 'calendar',
101 'name' => 'fromdate'
102 );
103 array_push($this->reportFilters, $filter_opt);
104
105 //To Date Filter
106 $filter_opt = array(
107 'label' => '<label for="todate">'.JText::translate('VBOREPORTSDATETO').'</label>',
108 'html' => '<input type="text" id="todate" name="todate" value="" class="vbo-report-datepicker vbo-report-datepicker-to" />',
109 'type' => 'calendar',
110 'name' => 'todate'
111 );
112 array_push($this->reportFilters, $filter_opt);
113
114 // get minimum check-in and maximum check-out for dates filters
115 $df = $this->getDateFormat();
116 $mincheckin = 0;
117 $maxcheckout = 0;
118 $q = "SELECT MIN(`checkin`) AS `mincheckin`, MAX(`checkout`) AS `maxcheckout` FROM `#__vikbooking_orders` WHERE `status`='confirmed' AND `closure`=0;";
119 $this->dbo->setQuery($q);
120 $this->dbo->execute();
121 if ($this->dbo->getNumRows()) {
122 $data = $this->dbo->loadAssoc();
123 if (!empty($data['mincheckin']) && !empty($data['maxcheckout'])) {
124 $mincheckin = $data['mincheckin'];
125 $maxcheckout = $data['maxcheckout'];
126 }
127 }
128
129 // set up datepicker calendars
130 $pfromdate = VikRequest::getString('fromdate', '', 'request');
131 $ptodate = VikRequest::getString('todate', '', 'request');
132 $js = 'jQuery(function() {
133 jQuery(".vbo-report-datepicker:input").datepicker({
134 '.(!empty($mincheckin) ? 'minDate: "'.date($df, $mincheckin).'", ' : '').'
135 '.(!empty($maxcheckout) ? 'maxDate: "'.date($df, $maxcheckout).'", ' : '').'
136 '.(!empty($mincheckin) && !empty($maxcheckout) ? 'yearRange: "'.(date('Y', $mincheckin)).':'.date('Y', $maxcheckout).'", changeMonth: true, changeYear: true, ' : '').'
137 dateFormat: "'.$this->getDateFormat('jui').'",
138 onSelect: vboReportCheckDates
139 });
140 '.(!empty($pfromdate) ? 'jQuery(".vbo-report-datepicker-from").datepicker("setDate", "'.$pfromdate.'");' : '').'
141 '.(!empty($ptodate) ? 'jQuery(".vbo-report-datepicker-to").datepicker("setDate", "'.$ptodate.'");' : '').'
142 });
143 function vboReportCheckDates(selectedDate, inst) {
144 if (selectedDate === null || inst === null) {
145 return;
146 }
147 var cur_from_date = jQuery(this).val();
148 if (jQuery(this).hasClass("vbo-report-datepicker-from") && cur_from_date.length) {
149 var nowstart = jQuery(this).datepicker("getDate");
150 var nowstartdate = new Date(nowstart.getTime());
151 jQuery(".vbo-report-datepicker-to").datepicker("option", {minDate: nowstartdate});
152 }
153 }';
154 $this->setScript($js);
155
156 return $this->reportFilters;
157 }
158
159 /**
160 * Loads the report data from the DB.
161 * Returns true in case of success, false otherwise.
162 * Sets the columns and rows for the report to be displayed.
163 *
164 * @return boolean
165 */
166 public function getReportData()
167 {
168 if (strlen($this->getError())) {
169 // export functions may set errors rather than exiting the process, and the View may continue the execution to attempt to render the report.
170 return false;
171 }
172
173 // input fields and other vars
174 $pfromdate = VikRequest::getString('fromdate', '', 'request');
175 $ptodate = VikRequest::getString('todate', '', 'request');
176 $pkrsort = VikRequest::getString('krsort', $this->defaultKeySort, 'request');
177 $pkrsort = empty($pkrsort) ? $this->defaultKeySort : $pkrsort;
178 $pkrorder = VikRequest::getString('krorder', $this->defaultKeyOrder, 'request');
179 $pkrorder = empty($pkrorder) ? $this->defaultKeyOrder : $pkrorder;
180 $pkrorder = $pkrorder == 'DESC' ? 'DESC' : 'ASC';
181
182 $df = $this->getDateFormat();
183 $datesep = VikBooking::getDateSeparator();
184 if (empty($ptodate)) {
185 $ptodate = $pfromdate;
186 }
187
188 // get date timestamps
189 $from_ts = VikBooking::getDateTimestamp($pfromdate, 0, 0);
190 $to_ts = VikBooking::getDateTimestamp($ptodate, 23, 59, 59);
191 if (empty($pfromdate) || empty($from_ts) || empty($to_ts) || $to_ts < $from_ts) {
192 $this->setError(JText::translate('VBOREPORTSERRNODATES'));
193 return false;
194 }
195
196 // query to obtain the records
197 $q = "SELECT `o`.`id`,`o`.`custdata`,`o`.`ts`,`o`.`days`,`o`.`checkin`,`o`.`checkout`,`o`.`roomsnum`,`o`.`idorderota`,`o`.`channel`,`o`.`country`,`o`.`adminnotes`,".
198 "`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`,".
199 "`or`.`meals`,`r`.`name`,`r`.`params`,`co`.`idcustomer`,`c`.`first_name`,`c`.`last_name`,`c`.`country` AS `customer_country` ".
200 "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` ".
201 "LEFT JOIN `#__vikbooking_customers_orders` AS `co` ON `co`.`idorder`=`o`.`id` LEFT JOIN `#__vikbooking_customers` AS `c` ON `c`.`id`=`co`.`idcustomer` ".
202 "WHERE `o`.`status`='confirmed' AND `o`.`closure`=0 AND `o`.`checkout` >= {$from_ts} AND `o`.`checkin` <= {$to_ts} ".
203 "ORDER BY `o`.`checkin` ASC, `o`.`id` ASC, `or`.`id` ASC;";
204 $this->dbo->setQuery($q);
205 $records = $this->dbo->loadAssocList();
206 if (!$records) {
207 $this->setError(JText::translate('VBOREPORTSERRNORESERV'));
208 return false;
209 }
210
211 // nest records with multiple rooms booked inside sub-array
212 $bookings = array();
213 foreach ($records as $v) {
214 if (!isset($bookings[$v['id']])) {
215 $bookings[$v['id']] = array();
216 }
217
218 // calculate the from_ts and to_ts values for later comparison
219 $in_info = getdate($v['checkin']);
220 $out_info = getdate($v['checkout']);
221 $v['from_ts'] = mktime(0, 0, 0, $in_info['mon'], $in_info['mday'], $in_info['year']);
222 $v['to_ts'] = mktime(23, 59, 59, $out_info['mon'], $out_info['mday'], $out_info['year']);
223
224 array_push($bookings[$v['id']], $v);
225 }
226
227 // define the columns of the report
228 $this->cols = array(
229 // date
230 array(
231 'key' => 'day',
232 'sortable' => 1,
233 'label' => JText::translate('VBOREPORTREVENUEDAY')
234 ),
235 // arrivals
236 array(
237 'key' => 'arrivals',
238 'attr' => array(
239 'class="center"'
240 ),
241 'sortable' => 1,
242 'label' => JText::translate('VBOARRIVING')
243 ),
244 // departures
245 array(
246 'key' => 'departures',
247 'attr' => array(
248 'class="center"'
249 ),
250 'sortable' => 1,
251 'label' => JText::translate('VBODEPARTING')
252 ),
253 // stayover
254 array(
255 'key' => 'stayover',
256 'attr' => array(
257 'class="center"'
258 ),
259 'sortable' => 1,
260 'label' => JText::translate('VBOTYPESTAYOVER')
261 ),
262 // breakfast
263 array(
264 'key' => 'breakfast',
265 'attr' => array(
266 'class="center"'
267 ),
268 'sortable' => 1,
269 'label' => JText::translate('VBO_MEAL_BREAKFAST')
270 ),
271 // lunch
272 array(
273 'key' => 'lunch',
274 'attr' => array(
275 'class="center"'
276 ),
277 'sortable' => 1,
278 'label' => JText::translate('VBO_MEAL_LUNCH')
279 ),
280 // dinner
281 array(
282 'key' => 'dinner',
283 'attr' => array(
284 'class="center"'
285 ),
286 'sortable' => 1,
287 'label' => JText::translate('VBO_MEAL_DINNER')
288 ),
289 // website
290 array(
291 'key' => 'website',
292 'attr' => array(
293 'class="center"'
294 ),
295 'sortable' => 1,
296 'label' => JText::translate('VBORDFROMSITE')
297 ),
298 // channels
299 array(
300 'key' => 'channels',
301 'attr' => array(
302 'class="center"'
303 ),
304 'sortable' => 1,
305 'label' => JText::translate('VBOCHANNELS'),
306 'tip' => JText::translate('VBO_CRON_OTA_RES'),
307 ),
308 );
309
310 // loop over the dates of the report to build the rows
311 $from_info = getdate($from_ts);
312 $to_info = getdate($to_ts);
313 while ($from_info[0] <= $to_info[0]) {
314 // prepare default fields for this row
315 $day_ts = $from_info[0];
316 $day_ymd = date('Y-m-d', $day_ts);
317 $curwday = $this->getWdayString($from_info['wday'], 'short');
318
319 $tot_arrivals = 0;
320 $gst_arrivals = 0;
321 $tot_departures = 0;
322 $gst_departures = 0;
323 $tot_stayover = 0;
324 $gst_stayover = 0;
325 $tot_breakfast = 0;
326 $tot_lunch = 0;
327 $tot_dinner = 0;
328 $website_res = 0;
329 $channel_res = 0;
330
331 // calculate the report details for this day
332 foreach ($bookings as $gbook) {
333 // make sure the booking affects the current day
334 if (!($from_info[0] >= $gbook[0]['from_ts'] && $from_info[0] <= $gbook[0]['to_ts'])) {
335 // skip reservation for the current day
336 continue;
337 }
338
339 // check stay type for today
340 if ($day_ymd == date('Y-m-d', $gbook[0]['checkin'])) {
341 // arrival
342 $raw_type = 'arriving';
343 $tot_arrivals++;
344 } elseif ($day_ymd == date('Y-m-d', $gbook[0]['checkout'])) {
345 // departure
346 $raw_type = 'departing';
347 $tot_departures++;
348 } else {
349 // stayover
350 $raw_type = 'stayover';
351 $tot_stayover++;
352 }
353
354 // check reservation provenience
355 if (!empty($gbook[0]['idorderota']) && !empty($gbook[0]['channel'])) {
356 // OTA reservation
357 $channel_res++;
358 } else {
359 // website reservation
360 $website_res++;
361 }
362
363 // check meal plans for each room record of this booking
364 foreach ($gbook as $roomres) {
365 // rate plan name and ID, if any
366 $active_rplan_id = 0;
367 if (!empty($roomres['otarplan'])) {
368 $rplan = $roomres['otarplan'];
369 } else {
370 list($rplan, $active_rplan_id) = $this->getPriceName($roomres['idtar']);
371 }
372
373 // room guests number
374 $room_guests_numb = ($roomres['adults'] + $roomres['children']);
375
376 // count number of guests per stay type
377 if ($raw_type == 'arriving') {
378 // arrival
379 $gst_arrivals += $room_guests_numb;
380 } elseif ($raw_type == 'departing') {
381 // departure
382 $gst_departures += $room_guests_numb;
383 } else {
384 // stayover
385 $gst_stayover += $room_guests_numb;
386 }
387
388 // meals included in the room rate
389 $included_meals = [];
390 if (!empty($roomres['meals'])) {
391 // display included meals defined at room-reservation record
392 $included_meals = VBOMealplanManager::getInstance()->roomRateIncludedMeals($roomres);
393 } else {
394 // fetch default included meals in the selected rate plan
395 $included_meals = $active_rplan_id ? VBOMealplanManager::getInstance()->ratePlanIncludedMeals($active_rplan_id) : [];
396 }
397 if (!$included_meals && empty($roomres['meals']) && !empty($roomres['idorderota']) && !empty($roomres['channel']) && !empty($roomres['custdata'])) {
398 // attempt to fetch the included meal plans from the raw customer data or OTA reservation and room
399 $included_meals = VBOMealplanManager::getInstance()->otaDataIncludedMeals($roomres, $roomres);
400 }
401 foreach ($included_meals as $meal_enum => $meal_name) {
402 if ($raw_type == 'arriving') {
403 // no breakfast for those who arrive
404 if ($meal_enum == 'breakfast') {
405 continue;
406 }
407 // increase pax for this meal
408 if ($meal_enum == 'lunch') {
409 $tot_lunch += $room_guests_numb;
410 } elseif ($meal_enum == 'dinner') {
411 $tot_dinner += $room_guests_numb;
412 }
413 } elseif ($raw_type == 'departing') {
414 // only breakfast for those who depart
415 if ($meal_enum == 'breakfast') {
416 // increase pax for this meal
417 $tot_breakfast += $room_guests_numb;
418 }
419 } else {
420 // increase pax for this meal (any kind of meal for those who are staying)
421 if ($meal_enum == 'breakfast') {
422 $tot_breakfast += $room_guests_numb;
423 } elseif ($meal_enum == 'lunch') {
424 $tot_lunch += $room_guests_numb;
425 } elseif ($meal_enum == 'dinner') {
426 $tot_dinner += $room_guests_numb;
427 }
428 }
429 }
430 }
431 }
432
433 // push fields in the rows array as a new row
434 array_push($this->rows, array(
435 array(
436 'key' => 'day',
437 'callback' => function ($val) use ($df, $datesep, $curwday) {
438 return $curwday.', '.date(str_replace("/", $datesep, $df), $val);
439 },
440 'value' => $day_ts,
441 ),
442 array(
443 'key' => 'arrivals',
444 'attr' => array(
445 'class="center"'
446 ),
447 'value' => $gst_arrivals,
448 ),
449 array(
450 'key' => 'departures',
451 'attr' => array(
452 'class="center"'
453 ),
454 'value' => $gst_departures,
455 ),
456 array(
457 'key' => 'stayover',
458 'attr' => array(
459 'class="center"'
460 ),
461 'value' => $gst_stayover,
462 ),
463 array(
464 'key' => 'breakfast',
465 'attr' => array(
466 'class="center"'
467 ),
468 'value' => $tot_breakfast,
469 ),
470 array(
471 'key' => 'lunch',
472 'attr' => array(
473 'class="center"'
474 ),
475 'value' => $tot_lunch,
476 ),
477 array(
478 'key' => 'dinner',
479 'attr' => array(
480 'class="center"'
481 ),
482 'value' => $tot_dinner,
483 ),
484 array(
485 'key' => 'website',
486 'attr' => array(
487 'class="center"'
488 ),
489 'value' => $website_res,
490 ),
491 array(
492 'key' => 'channels',
493 'attr' => array(
494 'class="center"'
495 ),
496 'value' => $channel_res,
497 ),
498 ));
499
500 // next day iteration
501 $from_info = getdate(mktime(0, 0, 0, $from_info['mon'], ($from_info['mday'] + 1), $from_info['year']));
502 }
503
504 // sort rows
505 $this->sortRows($pkrsort, $pkrorder);
506
507 // loop over the rows to build the footer row with the totals
508 $foot_arrivals = 0;
509 $foot_departures = 0;
510 $foot_stayover = 0;
511 $foot_breakfast = 0;
512 $foot_lunch = 0;
513 $foot_dinner = 0;
514 $foot_website_res = 0;
515 $foot_channel_res = 0;
516
517 foreach ($this->rows as $row) {
518 $foot_arrivals += $row[1]['value'];
519 $foot_departures += $row[2]['value'];
520 $foot_stayover += $row[3]['value'];
521 $foot_breakfast += $row[4]['value'];
522 $foot_lunch += $row[5]['value'];
523 $foot_dinner += $row[6]['value'];
524 $foot_website_res += $row[7]['value'];
525 $foot_channel_res += $row[8]['value'];
526 }
527
528 array_push($this->footerRow, array(
529 array(
530 'attr' => array(
531 'class="vbo-report-total"'
532 ),
533 'value' => '<h3>' . JText::translate('VBOREPORTSTOTALROW') . '</h3>',
534 ),
535 array(
536 'attr' => array(
537 'class="center"'
538 ),
539 'value' => $foot_arrivals,
540 ),
541 array(
542 'attr' => array(
543 'class="center"'
544 ),
545 'value' => $foot_departures,
546 ),
547 array(
548 'attr' => array(
549 'class="center"'
550 ),
551 'value' => $foot_stayover,
552 ),
553 array(
554 'attr' => array(
555 'class="center"'
556 ),
557 'value' => $foot_breakfast,
558 ),
559 array(
560 'attr' => array(
561 'class="center"'
562 ),
563 'value' => $foot_lunch,
564 ),
565 array(
566 'attr' => array(
567 'class="center"'
568 ),
569 'value' => $foot_dinner,
570 ),
571 array(
572 'attr' => array(
573 'class="center"'
574 ),
575 'value' => $foot_website_res,
576 ),
577 array(
578 'attr' => array(
579 'class="center"'
580 ),
581 'value' => $foot_channel_res,
582 ),
583 ));
584
585 // Debug
586 if ($this->debug) {
587 $this->setWarning('path to report file = '.urlencode(dirname(__FILE__)).'<br/>');
588 $this->setWarning('$total_rooms_units = '.$total_rooms_units.'<br/>');
589 $this->setWarning('$bookings:<pre>'.print_r($bookings, true).'</pre><br/>');
590 }
591
592 return true;
593 }
594
595 /**
596 * Registers the name to give to the CSV file being exported.
597 *
598 * @return void
599 */
600 private function registerExportCSVFileName()
601 {
602 $pfromdate = VikRequest::getString('fromdate', '', 'request');
603 $ptodate = VikRequest::getString('todate', '', 'request');
604
605 $this->setExportCSVFileName($this->reportName . '-' . str_replace('/', '_', $pfromdate) . '-' . str_replace('/', '_', $ptodate) . '.csv');
606 }
607
608 /**
609 * Finds the ID and name of the rate plan from the given tariff ID.
610 *
611 * @param int $idtar the ID of the tariff.
612 *
613 * @return array list of rate plan name (or an empty string) and ID.
614 */
615 private function getPriceName($idtar)
616 {
617 static $tariffs_map = [];
618
619 $idtar = (int)$idtar;
620 if (!$idtar) {
621 return [JText::translate('VBOROOMCUSTRATEPLAN'), 0];
622 }
623
624 if (isset($tariffs_map[$idtar])) {
625 return $tariffs_map[$idtar];
626 }
627
628 $q = "SELECT `p`.`id`, `p`.`name` FROM `#__vikbooking_prices` AS `p`
629 LEFT JOIN `#__vikbooking_dispcost` AS `t` ON `p`.`id`=`t`.`idprice` WHERE `t`.`id`={$idtar}";
630 $this->dbo->setQuery($q, 0, 1);
631 $price_record = $this->dbo->loadAssoc();
632
633 if ($price_record) {
634 // cache value and return it
635 $tariffs_map[$idtar] = [$price_record['name'], $price_record['id']];
636
637 return $tariffs_map[$idtar];
638 }
639
640 // cache value and return it
641 $tariffs_map[$idtar] = ['', 0];
642
643 return $tariffs_map[$idtar];
644 }
645 }
646