PluginProbe
VikBooking Hotel Booking Engine & PMS / 1.6.4
VikBooking Hotel Booking Engine & PMS v1.6.4
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 / src / model / reservation.php

reservation.php in VikBooking Hotel Booking Engine & PMS 1.6.4, at admin/helpers/src/model/reservation.php

1,146 lines 36.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 core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2023 E4J s.r.l. All Rights Reserved.
7 * @license http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
8 * @link https://vikwp.com
9 */
10
11 // No direct access
12 defined('ABSPATH') or die('No script kiddies please!');
13
14 /**
15 * VikBooking reservation model.
16 *
17 * @since 1.16.0 (J) - 1.6.0 (WP)
18 */
19 class VBOModelReservation extends JObject
20 {
21 /**
22 * The singleton instance of the class.
23 *
24 * @var VBOModelReservation
25 */
26 private static $instance = null;
27
28 /**
29 * Proxy for immediately getting the object and bind data.
30 *
31 * @param array|object $data optional data to bind.
32 * @param boolean $anew true for forcing a new instance.
33 *
34 * @return self
35 */
36 public static function getInstance($data = [], $anew = false)
37 {
38 if (is_null(static::$instance) || $anew) {
39 static::$instance = new static($data);
40 }
41
42 return static::$instance;
43 }
44
45 /**
46 * Sets the customer information.
47 *
48 * @param array $customer the customer array.
49 *
50 * @return self
51 */
52 public function setCustomer(array $customer = [])
53 {
54 $this->set('_customer', $customer);
55
56 return $this;
57 }
58
59 /**
60 * Returns the customer information.
61 *
62 * @return array
63 */
64 public function getCustomer()
65 {
66 return (array)$this->get('_customer', []);
67 }
68
69 /**
70 * Sets the room information.
71 *
72 * @param array $room the room array.
73 *
74 * @return self
75 */
76 public function setRoom(array $room = [])
77 {
78 $this->set('_room', $room);
79
80 return $this;
81 }
82
83 /**
84 * Returns the room information.
85 *
86 * @return array
87 */
88 public function getRoom()
89 {
90 return (array)$this->get('_room', []);
91 }
92
93 /**
94 * Sets the new booking ID created.
95 *
96 * @param int $bid the newly added record ID.
97 *
98 * @return self
99 */
100 protected function setNewBookingID($bid = 0)
101 {
102 $this->set('_newBookingID', $bid);
103
104 return $this;
105 }
106
107 /**
108 * Returns the new booking ID created, or 0.
109 *
110 * @return int
111 */
112 public function getNewBookingID()
113 {
114 return (int)$this->get('_newBookingID', 0);
115 }
116
117 /**
118 * Sets the VCM action to be performed in order to sync the availability.
119 *
120 * @param string $action the VCM action, usually an HTML link.
121 *
122 * @return self
123 */
124 protected function setChannelManagerAction($action = '')
125 {
126 $this->set('_vcmAction', $action);
127
128 return $this;
129 }
130
131 /**
132 * Returns the VCM action (if any) to sync the availability.
133 *
134 * @return string
135 */
136 public function getChannelManagerAction()
137 {
138 return $this->get('_vcmAction', '');
139 }
140
141 /**
142 * Sets the check-in and check-out times with hours and minutes.
143 *
144 * @return array list of check-in and check-out hours and minutes.
145 */
146 public function loadCheckinOutTimes()
147 {
148 static $times_loaded = null;
149
150 if ($times_loaded) {
151 return [
152 $this->get('checkin_h'),
153 $this->get('checkin_m'),
154 $this->get('checkout_h'),
155 $this->get('checkout_m'),
156 ];
157 }
158
159 $timeopst = VikBooking::getTimeOpenStore();
160 if (is_array($timeopst) && $timeopst) {
161 $opent = VikBooking::getHoursMinutes($timeopst[0]);
162 $closet = VikBooking::getHoursMinutes($timeopst[1]);
163 $hcheckin = $opent[0];
164 $mcheckin = $opent[1];
165 $hcheckout = $closet[0];
166 $mcheckout = $closet[1];
167 } else {
168 $hcheckin = 0;
169 $mcheckin = 0;
170 $hcheckout = 0;
171 $mcheckout = 0;
172 }
173
174 $this->set('checkin_h', $hcheckin);
175 $this->set('checkin_m', $mcheckin);
176 $this->set('checkout_h', $hcheckout);
177 $this->set('checkout_m', $mcheckout);
178
179 $times_loaded = 1;
180
181 return [
182 $hcheckin,
183 $mcheckin,
184 $hcheckout,
185 $mcheckout,
186 ];
187 }
188
189 /**
190 * Creates a new reservation record after having constructed the
191 * object by properly injecting all the necessary booking information.
192 *
193 * @return bool
194 */
195 public function create()
196 {
197 if (!$this->canCreate()) {
198 $this->setError('Forbidden');
199 return false;
200 }
201
202 // availability helper
203 $av_helper = VikBooking::getAvailabilityInstance();
204
205 // validate mandatory fields
206 $room = $this->getRoom();
207 if (!$this->get('checkin') || !$this->get('checkout') || empty($room['id'])) {
208 $this->setError('Missing mandatory fields');
209 return false;
210 }
211
212 if ($this->get('checkin') >= $this->get('checkout')) {
213 $this->setError('Invalid dates');
214 return false;
215 }
216
217 // make sure we have the time for check-in and check-out
218 if (!$this->get('checkin_h') || !$this->get('checkout_h')) {
219 // make sure to set the times (hours/minutes) for check-in and check-out
220 $this->loadCheckinOutTimes();
221 }
222
223 // number of nights of stay
224 if (!$this->get('nights')) {
225 $this->set('nights', $av_helper->countNightsOfStay($this->get('checkin'), $this->get('checkout')));
226 }
227
228 // fetch and apply turnover time before doing anything else
229 $this->applyTurnover();
230
231 // if rate plan selected, get the tariff ID
232 $this->loadTariffID();
233
234 // get pool of rooms involved
235 $rooms_pool = $this->getRoomsPool();
236 if (!$rooms_pool) {
237 if ($this->getError() === false) {
238 // set generic error if not set already
239 $this->setError('No rooms involved in the reservation');
240 }
241 return false;
242 }
243
244 // check if the room is available
245 $room_available = $this->isRoomAvailable();
246 if (!$this->get('force_booking', 0) && !$this->get('set_closed', 0) && !$room_available) {
247 // no forcing, no closure and room fully booked results into an error message
248 $this->setError(JText::translate('VBBOOKNOTMADE'));
249 return false;
250 }
251
252 // detect if we are forcing the reservation
253 $this->detectForcedReason($room_available);
254
255 // store the customer information
256 $this->storeCustomer();
257
258 // calculate total amount and total tax
259 $this->calculateTotal();
260
261 // store booking and room-booking records
262 if (!$this->storeReservationRecords($rooms_pool)) {
263 if ($this->getError() === false) {
264 // set generic error if not set already
265 $this->setError('Could not create the reservation');
266 }
267 return false;
268 }
269
270 return true;
271 }
272
273 /**
274 * Tells whether the operation is authenticated. By default this
275 * model is only available in the administrator section of the site.
276 *
277 * @return bool
278 */
279 protected function canCreate()
280 {
281 if (!JFactory::getApplication()->isClient('administrator') && !$this->get('_isAdministrator')) {
282 return false;
283 }
284
285 return true;
286 }
287
288 /**
289 * Gets and sets the tariff ID if a rate plan was set.
290 *
291 * @return int the tariff ID or 0.
292 */
293 protected function loadTariffID()
294 {
295 $dbo = JFactory::getDbo();
296
297 $id_tariff = 0;
298 $room = $this->getRoom();
299 $daysdiff = (int)$this->get('nights', 1);
300
301 if (!empty($room['id']) && !empty($room['id_price']) && !empty($room['room_cost']) && $room['room_cost'] > 0 && !(int)$this->get('set_closed') && !$this->get('split_stay', [])) {
302 $room['id_price'] = (int)$room['id_price'];
303
304 $q = "SELECT `id` FROM `#__vikbooking_dispcost` WHERE `idroom`={$room['id']} AND `days`={$daysdiff} AND `idprice`={$room['id_price']};";
305 $dbo->setQuery($q);
306 $id_tariff = $dbo->loadResult();
307 }
308
309 $this->set('id_tariff', (int)$id_tariff);
310
311 return (int)$id_tariff;
312 }
313
314 /**
315 * Applies the turnover time to the checkout timestamp and sets its value.
316 *
317 * @return int the turnover seconds applied.
318 */
319 protected function applyTurnover()
320 {
321 $turnover_secs = 0;
322 $checkout = $this->get('checkout', 0);
323
324 if ($checkout) {
325 // turnover time
326 $turnover_secs = VikBooking::getHoursRoomAvail() * 3600;
327
328 $this->set('checkout_real', ($checkout + $turnover_secs));
329 }
330
331 $this->set('turnover_secs', $turnover_secs);
332
333 return $turnover_secs;
334 }
335
336 /**
337 * Returns the details of a specific room ID.
338 *
339 * @param int $rid the room ID.
340 *
341 * @return array the record found or empty array.
342 */
343 protected function getRoomDetails($rid = null)
344 {
345 $all_rooms = VikBooking::getAvailabilityInstance()->loadRooms();
346
347 if (!$rid) {
348 $inj_room = $this->getRoom();
349 if (!empty($inj_room['id'])) {
350 $rid = $inj_room['id'];
351 }
352 }
353
354 if ($rid && isset($all_rooms[$rid])) {
355 return $all_rooms[$rid];
356 }
357
358 return [];
359 }
360
361 /**
362 * Gets the list of rooms involved in the reservation in case of closures.
363 *
364 * @return array the list of rooms involved
365 */
366 protected function getRoomsPool()
367 {
368 $room = $this->getRoom();
369 if (empty($room['id'])) {
370 return [];
371 }
372 $room = $this->getRoomDetails($room['id']);
373
374 // gather values
375 $set_close_others = (array)$this->get('close_others', []);
376 $split_stay_data = $this->get('split_stay', []);
377 $set_closed = (int)$this->get('set_closed');
378 $turnover_secs = $this->get('turnover_secs', 0);
379 $hcheckin = $this->get('checkin_h', 12);
380 $mcheckin = $this->get('checkin_m', 0);
381 $hcheckout = $this->get('checkout_h', 10);
382 $mcheckout = $this->get('checkout_m', 0);
383
384 $av_helper = VikBooking::getAvailabilityInstance();
385 $all_rooms = $av_helper->loadRooms();
386 $rooms_pool = [];
387 $closeothers = [];
388
389 if ($set_close_others && $set_closed) {
390 // prepend current room for closing
391 array_unshift($set_close_others, $room['id']);
392 }
393 $set_close_others = array_unique($set_close_others);
394
395 foreach ($set_close_others as $closeid) {
396 if (empty($closeid)) {
397 continue;
398 }
399 if ((int)$closeid === -1) {
400 // close all rooms
401 $closeothers = [];
402 foreach ($all_rooms as $cr) {
403 array_push($closeothers, $cr);
404 }
405 break;
406 }
407 foreach ($all_rooms as $cr) {
408 if ((int)$cr['id'] == (int)$closeid) {
409 // push the main room or one of the other rooms requested for closure
410 array_push($closeothers, $cr);
411 break;
412 }
413 }
414 }
415
416 if (!$closeothers || !$set_closed) {
417 $rooms_pool = [$room];
418 } else {
419 $rooms_pool = $closeothers;
420 }
421
422 // check split stay rooms booking
423 if (!empty($split_stay_data)) {
424 // reset pool and set it with the split stay rooms
425 $rooms_pool = [];
426 foreach ($split_stay_data as $sps_k => $split_stay) {
427 if (!isset($all_rooms[$split_stay['idroom']])) {
428 continue;
429 }
430 // calculate and set the exact check-in and check-out timestamps for this split-room
431 $split_stay['checkin_ts'] = VikBooking::getDateTimestamp($split_stay['checkin'], $hcheckin, $mcheckin);
432 $split_stay['checkout_ts'] = VikBooking::getDateTimestamp($split_stay['checkout'], $hcheckout, $mcheckout);
433 $split_stay['realback_ts'] = $turnover_secs + $split_stay['checkout_ts'];
434 $split_stay['nights'] = $av_helper->countNightsOfStay($split_stay['checkin_ts'], $split_stay['checkout_ts']);
435 $split_stay_data[$sps_k] = $split_stay;
436 // push room data to pool after storing additional information
437 $room_data = $all_rooms[$split_stay['idroom']];
438 $room_data['checkin_ts'] = $split_stay['checkin_ts'];
439 $room_data['checkout_ts'] = $split_stay['checkout_ts'];
440 $rooms_pool[] = $room_data;
441 }
442 if (!$rooms_pool) {
443 $this->setError('No valid rooms for the split stay booking');
444 return [];
445 }
446 // update split stay data manipulated
447 $this->set('split_stay', $split_stay_data);
448 }
449
450 return $rooms_pool;
451 }
452
453 /**
454 * Checks that the room is available on the requested dates.
455 * Call this method only after getting the rooms pool.
456 *
457 * @return bool
458 */
459 protected function isRoomAvailable()
460 {
461 $inj_room = $this->getRoom();
462 if (empty($inj_room['id'])) {
463 return false;
464 }
465 $room = $this->getRoomDetails($inj_room['id']);
466 if (!$room) {
467 return false;
468 }
469
470 $split_stay_data = $this->get('split_stay', []);
471 $set_closed = $this->get('set_closed', 0);
472 $num_rooms = $this->get('num_rooms', 1);
473
474 $room_available = true;
475 $all_rooms = VikBooking::getAvailabilityInstance()->loadRooms();
476
477 if (empty($split_stay_data)) {
478 // make sure the rooms are available
479 $check_units = $room['units'];
480 if ($num_rooms > 1 && $num_rooms <= $room['units'] && !$set_closed) {
481 // only when non closing the room we check the availability for the units requested for booking
482 $check_units = $room['units'] - $num_rooms + 1;
483 }
484 $room_available = VikBooking::roomBookable($room['id'], $check_units, $this->get('checkin', 0), $this->get('checkout', 0));
485 } else {
486 // make sure the rooms for the split stay are available
487 foreach ($split_stay_data as $split_stay) {
488 if (!isset($all_rooms[$split_stay['idroom']])) {
489 $room_available = false;
490 break;
491 }
492 $room_available = $room_available && VikBooking::roomBookable($split_stay['idroom'], $all_rooms[$split_stay['idroom']]['units'], $split_stay['checkin_ts'], $split_stay['checkout_ts']);
493 }
494 }
495
496 return $room_available;
497 }
498
499 /**
500 * In case the reservation is forced or is a closure, we detect the
501 * forced reason to eventually attach it to the booking history.
502 *
503 * @param bool $room_available whether the room is available.
504 *
505 * @return void
506 */
507 protected function detectForcedReason($room_available = true)
508 {
509 $split_stay_data = $this->get('split_stay', []);
510 $force_booking = $this->get('force_booking', 0);
511 $set_closed = $this->get('set_closed', 0);
512
513 $forced_reason = '';
514 $all_rooms = VikBooking::getAvailabilityInstance()->loadRooms();
515
516 if (empty($split_stay_data)) {
517 // eventually build string for the description of the history event
518 if (($force_booking || $set_closed) && !$room_available) {
519 $forced_reason = JText::translate('VBO_FORCED_BOOKDATES');
520 }
521 } else {
522 // set "split stay" as the description of the history event
523 $forced_reason = JText::translate('VBO_SPLIT_STAY') . "\n";
524 foreach ($split_stay_data as $sps_k => $split_stay) {
525 // describe the split stay for each room
526 if (!isset($all_rooms[$split_stay['idroom']])) {
527 continue;
528 }
529 $room_stay_nights = $split_stay['nights'];
530 $forced_reason .= $all_rooms[$split_stay['idroom']]['name'] . ': ' . $room_stay_nights . ' ' . ($room_stay_nights > 1 ? JText::translate('VBDAYS') : JText::translate('VBDAY')) . ', ';
531 $forced_reason .= $split_stay['checkin'] . ' - ' . $split_stay['checkout'] . "\n";
532 }
533 $forced_reason = rtrim($forced_reason, "\n");
534 }
535
536 $this->set('forced_reason', $forced_reason);
537 }
538
539 /**
540 * Stores the customer information to a new or existing record.
541 * In case of success, the customer ID property is updated.
542 * The customer shall be stored before the reservation records.
543 *
544 * @return bool
545 */
546 protected function storeCustomer()
547 {
548 $dbo = JFactory::getDbo();
549
550 $inj_customer = $this->getCustomer();
551 $first_name = !empty($inj_customer['first_name']) ? $inj_customer['first_name'] : '';
552 $last_name = !empty($inj_customer['last_name']) ? $inj_customer['last_name'] : '';
553 $custdata = !empty($inj_customer['data']) ? $inj_customer['data'] : '';
554 $email = !empty($inj_customer['email']) ? $inj_customer['email'] : '';
555 $country = !empty($inj_customer['country']) ? $inj_customer['country'] : '';
556 $phone = !empty($inj_customer['phone']) ? $inj_customer['phone'] : '';
557
558 // custom fields
559 $q = "SELECT * FROM `#__vikbooking_custfields` ORDER BY `ordering` ASC;";
560 $dbo->setQuery($q);
561 $all_cfields = $dbo->loadAssocList();
562
563 $customer_cfields = [];
564 $customer_extrainfo = [];
565 $custdata_parts = explode("\n", $custdata);
566 foreach ($custdata_parts as $cdataline) {
567 if (!strlen(trim($cdataline))) {
568 continue;
569 }
570 $cdata_parts = explode(':', $cdataline);
571 if (count($cdata_parts) < 2 || !strlen(trim($cdata_parts[0])) || !strlen(trim($cdata_parts[1]))) {
572 continue;
573 }
574 foreach ($all_cfields as $cf) {
575 $needle = JText::translate($cf['name']);
576 if (!empty($needle) && strpos($cdata_parts[0], $needle) !== false && !array_key_exists($cf['id'], $customer_cfields) && $cf['type'] != 'country') {
577 $user_input_val = trim($cdata_parts[1]);
578 $customer_cfields[$cf['id']] = $user_input_val;
579 if (!empty($cf['flag'])) {
580 $customer_extrainfo[$cf['flag']] = $user_input_val;
581 } elseif ($cf['type'] == 'state') {
582 $customer_extrainfo['state'] = $user_input_val;
583 }
584 break;
585 }
586 }
587 }
588
589 $cpin = VikBooking::getCPinInstance();
590 $cpin->is_admin = true;
591 $cpin->setCustomerExtraInfo($customer_extrainfo);
592 $cpin->saveCustomerDetails($first_name, $last_name, $email, $phone, $country, $customer_cfields);
593
594 $customer_id = $cpin->getNewCustomerId();
595 if (!$customer_id) {
596 return false;
597 }
598
599 $inj_customer['id'] = $customer_id;
600 $this->setCustomer($inj_customer);
601
602 return true;
603 }
604
605 /**
606 * Returns the calculated total booking amount and total taxes.
607 * Sets the necessary properties with the calculated amounts.
608 *
609 * @return array list of booking total amount and total taxes.
610 */
611 protected function calculateTotal()
612 {
613 // the values to calculate
614 $set_total = 0;
615 $set_taxes = 0;
616
617 $dbo = JFactory::getDbo();
618
619 // get data
620 $inj_room = $this->getRoom();
621 $set_closed = $this->get('set_closed', 0);
622 $daysdiff = (int)$this->get('nights', 1);
623 $num_rooms = (int)$this->get('num_rooms', 1);
624 $totalpnight = !empty($inj_room['total_or_pnight']) ? $inj_room['total_or_pnight'] : 'total';
625 $cust_cost = !empty($inj_room['cust_cost']) ? (float)$inj_room['cust_cost'] : 0;
626 $room_cost = !empty($inj_room['room_cost']) ? (float)$inj_room['room_cost'] : 0;
627 $id_price = !empty($inj_room['id_price']) ? (int)$inj_room['id_price'] : 0;
628 $id_tax = !empty($inj_room['id_tax']) ? (int)$inj_room['id_tax'] : 0;
629
630 $split_stay_data = $this->get('split_stay', []);
631
632 $room = $this->getRoomDetails();
633 if (!$room) {
634 return [$set_total, $set_taxes];
635 }
636
637 if ($cust_cost > 0 && !$set_closed) {
638 // custom cost can be per night
639 if ($totalpnight == 'pnight') {
640 $cust_cost = $cust_cost * $daysdiff;
641 }
642 $set_total = $cust_cost;
643
644 // apply taxes, if necessary
645 if ($id_tax) {
646 $q = "SELECT `i`.`aliq`,`i`.`taxcap` FROM `#__vikbooking_iva` AS `i` WHERE `i`.`id`=" . $id_tax . ";";
647 $dbo->setQuery($q);
648 $taxdata = $dbo->loadAssoc();
649 if ($taxdata) {
650 $aliq = $taxdata['aliq'];
651 if (floatval($aliq) > 0.00) {
652 if (!VikBooking::ivaInclusa()) {
653 // add tax to the total amount
654 $subt = 100 + (float)$aliq;
655 $set_total = ($set_total * $subt / 100);
656 /**
657 * Tax Cap implementation for prices tax excluded (most common).
658 *
659 * @since 1.12 (J) - 1.2 (WP)
660 */
661 if ($taxdata['taxcap'] > 0 && ($set_total - $cust_cost) > $taxdata['taxcap']) {
662 $set_total = ($cust_cost + $taxdata['taxcap']);
663 }
664 // calculate tax
665 $set_taxes = $set_total - $cust_cost;
666 } else {
667 // calculate tax
668 $cost_minus_tax = VikBooking::sayPackageMinusIva($cust_cost, $id_tax);
669 $set_taxes += ($cust_cost - $cost_minus_tax);
670 }
671 }
672 }
673 }
674 } elseif (!empty($id_price) && $room_cost > 0 && !$set_closed && empty($split_stay_data)) {
675 // one website rate plan was selected, so we calculate total and taxes
676 $set_total = $room_cost;
677
678 // find tax rate assigned to this rate plan
679 $q = "SELECT `p`.`id`,`p`.`idiva`,`i`.`aliq`,`i`.`taxcap` FROM `#__vikbooking_prices` AS `p` LEFT JOIN `#__vikbooking_iva` AS `i` ON `p`.`idiva`=`i`.`id` WHERE `p`.`id`=" . $id_price . ";";
680 $dbo->setQuery($q);
681 $taxdata = $dbo->loadAssoc();
682 if ($taxdata) {
683 $aliq = $taxdata['aliq'];
684 if (floatval($aliq) > 0.00) {
685 if (!VikBooking::ivaInclusa()) {
686 // add tax to the total amount
687 $subt = 100 + (float)$aliq;
688 $set_total = ($set_total * $subt / 100);
689 /**
690 * Tax Cap implementation for prices tax excluded (most common).
691 *
692 * @since 1.12 (J) - 1.2 (WP)
693 */
694 if ($taxdata['taxcap'] > 0 && ($set_total - $room_cost) > $taxdata['taxcap']) {
695 $set_total = ($room_cost + $taxdata['taxcap']);
696 }
697 // calculate tax
698 $set_taxes = $set_total - $room_cost;
699 } else {
700 // calculate tax
701 $cost_minus_tax = VikBooking::sayPackageMinusIva($room_cost, $taxdata['idiva']);
702 $set_taxes += ($room_cost - $cost_minus_tax);
703 }
704 }
705 }
706
707 // total and taxes should be multiplied by the number of rooms booked when using a website rate plan
708 if ($set_closed) {
709 $set_total *= $room['units'];
710 $set_taxes *= $room['units'];
711 } elseif ($num_rooms > 1 && $num_rooms <= $room['units']) {
712 $set_total *= $num_rooms;
713 $set_taxes *= $num_rooms;
714 }
715 }
716
717 // set values
718 $this->set('_total', $set_total);
719 $this->set('_total_tax', $set_taxes);
720
721 return [$set_total, $set_taxes];
722 }
723
724 /**
725 * Stores the booking and room-booking records.
726 * If no errors, the newly generated booking id is set.
727 *
728 * @param array $rooms_pool list of rooms involved.
729 *
730 * @return bool
731 */
732 protected function storeReservationRecords(array $rooms_pool)
733 {
734 $dbo = JFactory::getDbo();
735
736 $set_closed = $this->get('set_closed', 0);
737 $daysdiff = (int)$this->get('nights', 1);
738 $num_rooms = (int)$this->get('num_rooms', 1);
739 $adults = (int)$this->get('adults', 1);
740 $children = (int)$this->get('children', 0);
741 $status = $this->get('status', 'confirmed');
742
743 $split_stay_data = $this->get('split_stay', []);
744 $room = $this->getRoomDetails();
745 if (!$room || !$rooms_pool) {
746 return false;
747 }
748
749 // get current Joomla/WordPress User ID
750 $now_user = JFactory::getUser();
751 $store_ujid = property_exists($now_user, 'id') ? (int)$now_user->id : 0;
752
753 // forced booking reason, status validation and additional data
754 $forced_reason = $this->get('forced_reason', '');
755 $valid_statuses = ['confirmed', 'standby'];
756 $status = in_array($status, $valid_statuses) ? $status : 'confirmed';
757 $paymentmeth = $this->get('id_payment', '');
758 $set_total = (float)$this->get('_total', 0);
759 $set_taxes = (float)$this->get('_total_tax', 0);
760
761 // stay dates
762 $now_ts = time();
763 $checkin_ts = $this->get('checkin');
764 $checkout_ts = $this->get('checkout');
765 $realback_ts = $this->get('checkout_real', $checkout_ts);
766
767 // room
768 $inj_room = $this->getRoom();
769 $cust_cost = !empty($inj_room['cust_cost']) ? (float)$inj_room['cust_cost'] : 0;
770 $room_cost = !empty($inj_room['room_cost']) ? (float)$inj_room['room_cost'] : 0;
771 $id_price = !empty($inj_room['id_price']) ? (int)$inj_room['id_price'] : 0;
772 $id_tax = !empty($inj_room['id_tax']) ? (int)$inj_room['id_tax'] : 0;
773 $id_tariff = $this->get('id_tariff', 0);
774
775 // custom rate modifier per night
776 $totalpnight = !empty($inj_room['total_or_pnight']) ? $inj_room['total_or_pnight'] : 'total';
777 if ($cust_cost > 0.00 && !$set_closed && $totalpnight == 'pnight') {
778 $cust_cost = $cust_cost * $daysdiff;
779 }
780
781 // customer
782 $cpin = VikBooking::getCPinInstance();
783 $inj_customer = $this->getCustomer();
784 $customer_id = !empty($inj_customer['id']) ? $inj_customer['id'] : 0;
785 $customer_pin = !empty($inj_customer['pin']) ? $inj_customer['pin'] : '';
786 $t_first_name = !empty($inj_customer['first_name']) ? $inj_customer['first_name'] : '';
787 $t_last_name = !empty($inj_customer['last_name']) ? $inj_customer['last_name'] : '';
788 $customer_data = !empty($inj_customer['data']) ? $inj_customer['data'] : '';
789 $customer_email = !empty($inj_customer['email']) ? $inj_customer['email'] : '';
790 $country_code = !empty($inj_customer['country']) ? $inj_customer['country'] : '';
791 $phone_number = !empty($inj_customer['phone']) ? $inj_customer['phone'] : '';
792
793 if ($set_closed) {
794 $customer_data = JText::translate('VBDBTEXTROOMCLOSED');
795 }
796
797 // generate booking SID
798 $sid = VikBooking::getSecretLink();
799
800 // assign room specific unit
801 $set_room_indexes = !$set_closed ? VikBooking::autoRoomUnit() : false;
802 $num_rooms = $num_rooms > 0 ? $num_rooms : 1;
803
804 // occupancy and loop limits
805 $forend = 1;
806 $or_forend = 1;
807 $adults_map = [];
808 $children_map = [];
809 if ($set_closed && empty($split_stay_data)) {
810 $forend = $room['units'];
811 } elseif ($num_rooms > 1 && $num_rooms <= $room['units'] && empty($split_stay_data)) {
812 $forend = $num_rooms;
813 $or_forend = $num_rooms;
814 // assign adults/children proportionally
815 if (($adults + $children) < $num_rooms) {
816 // the number of guests does not make much sense but we build the maps anyway
817 for ($r = 1; $r <= $or_forend; $r++) {
818 $adults_map[$r] = $adults;
819 $children_map[$r] = $children;
820 }
821 } else {
822 $adults_per_room = floor(($adults / $num_rooms));
823 $adults_left = ($adults % $num_rooms);
824 $children_per_room = floor(($children / $num_rooms));
825 $children_left = ($children % $num_rooms);
826 for ($r = 1; $r <= $or_forend; $r++) {
827 $adults_map[$r] = $adults_per_room;
828 $children_map[$r] = $children_per_room;
829 if ($r == $or_forend) {
830 $adults_map[$r] += $adults_left;
831 $children_map[$r] += $children_left;
832 }
833 }
834 }
835 }
836
837 // count total rooms booked
838 $totalrooms = ($set_closed && $status == 'confirmed' ? count($rooms_pool) : ($num_rooms > 1 && $num_rooms <= $room['units'] ? $num_rooms : 1));
839 $totalrooms = !empty($split_stay_data) ? count($split_stay_data) : $totalrooms;
840
841 // prepare booking record
842 $booking = new stdClass;
843 $booking->custdata = $customer_data;
844 $booking->ts = $now_ts;
845 $booking->status = $status;
846 $booking->days = $daysdiff;
847 $booking->checkin = $checkin_ts;
848 $booking->checkout = $checkout_ts;
849 $booking->custmail = $customer_email;
850 $booking->sid = $sid;
851 $booking->idpayment = $paymentmeth;
852 $booking->ujid = (int)$store_ujid;
853 $booking->roomsnum = $totalrooms;
854 $booking->total = $set_total > 0 ? $set_total : null;
855 $booking->lang = VikBooking::guessBookingLangFromCountry($country_code);
856 $booking->country = $country_code;
857 $booking->tot_taxes = $set_taxes > 0 ? $set_taxes : null;
858 $booking->phone = $phone_number;
859 $booking->closure = ($status == 'standby' ? 0 : ($set_closed ? 1 : 0));
860 $booking->split_stay = !empty($split_stay_data) ? 1 : 0;
861
862 // store reservation
863 if ($status == 'confirmed') {
864 // occupy the rooms
865 $insertedbusy = [];
866 if (empty($split_stay_data)) {
867 // only when closing other rooms we have an array containing multiple rooms info
868 foreach ($rooms_pool as $nowroom) {
869 $nowforend = $set_closed ? $nowroom['units'] : $forend;
870 for ($b = 1; $b <= $nowforend; $b++) {
871 $busy_record = new stdClass;
872 $busy_record->idroom = (int)$nowroom['id'];
873 $busy_record->checkin = (int)$checkin_ts;
874 $busy_record->checkout = (int)$checkout_ts;
875 $busy_record->realback = (int)$realback_ts;
876
877 // store busy record
878 $dbo->insertObject('#__vikbooking_busy', $busy_record, 'id');
879
880 if (isset($busy_record->id)) {
881 $insertedbusy[] = $busy_record->id;
882 }
883 }
884 }
885 } else {
886 // for split stay bookings we occupy the rooms on the individual stay dates
887 foreach ($split_stay_data as $split_stay) {
888 $busy_record = new stdClass;
889 $busy_record->idroom = (int)$split_stay['idroom'];
890 $busy_record->checkin = (int)$split_stay['checkin_ts'];
891 $busy_record->checkout = (int)$split_stay['checkout_ts'];
892 $busy_record->realback = (int)$split_stay['realback_ts'];
893
894 // store busy record
895 $dbo->insertObject('#__vikbooking_busy', $busy_record, 'id');
896
897 if (isset($busy_record->id)) {
898 $insertedbusy[] = $busy_record->id;
899 }
900 }
901 }
902
903 if (!$insertedbusy) {
904 $this->setError('No records were occupied');
905 return false;
906 }
907
908 // store booking record
909 $dbo->insertObject('#__vikbooking_orders', $booking, 'id');
910
911 if (!isset($booking->id)) {
912 $this->setError('Could not store the reservation record');
913 return false;
914 }
915
916 // get the newly generated booking ID
917 $newoid = $booking->id;
918
919 // set the new booking ID
920 $this->setNewBookingID($newoid);
921
922 if (!empty($split_stay_data)) {
923 // save transient on db for split stay information
924 VBOFactory::getConfig()->set('split_stay_' . $newoid, json_encode($split_stay_data));
925 }
926
927 // check if some of the rooms booked have shared calendars
928 VikBooking::updateSharedCalendars($newoid, [$room['id']], $checkin_ts, $checkout_ts);
929
930 // confirmation number
931 $confirmnumber = VikBooking::generateConfirmNumber($newoid, true);
932
933 // store busy records/booking relations
934 foreach ($insertedbusy as $lid) {
935 $obusy_record = new stdClass;
936 $obusy_record->idorder = (int)$newoid;
937 $obusy_record->idbusy = (int)$lid;
938
939 // store busy relation record
940 $dbo->insertObject('#__vikbooking_ordersbusy', $obusy_record, 'id');
941 }
942
943 // write room records
944 foreach ($rooms_pool as $rind => $nowroom) {
945 $room_indexes_usemap = [];
946 for ($r = 1; $r <= $or_forend; $r++) {
947 // Assign room specific unit
948 $info_room_avail = [
949 'id' => $newoid,
950 'checkin' => (!empty($nowroom['checkin_ts']) ? $nowroom['checkin_ts'] : $checkin_ts),
951 'checkout' => (!empty($nowroom['checkout_ts']) ? $nowroom['checkout_ts'] : $checkout_ts),
952 ];
953 $room_indexes = $set_room_indexes === true ? VikBooking::getRoomUnitNumsAvailable($info_room_avail, $nowroom['id']) : [];
954 $use_ind_key = 0;
955 if ($room_indexes) {
956 if (!array_key_exists($nowroom['id'], $room_indexes_usemap)) {
957 $room_indexes_usemap[$nowroom['id']] = $use_ind_key;
958 } else {
959 $use_ind_key = $room_indexes_usemap[$nowroom['id']];
960 }
961 }
962
963 // room custom cost
964 $or_cust_cost = $cust_cost > 0.00 ? $cust_cost : 0;
965 $or_cust_cost = $or_forend > 1 && $or_cust_cost > 0 ? round(($or_cust_cost / $or_forend), 2) : $or_cust_cost;
966 // room cost from website rate plan is always based on one room
967 $or_room_cost = $room_cost > 0.00 ? $room_cost : 0;
968 if (!empty($split_stay_data) && $cust_cost > 0) {
969 // set the average cost per room in case of split stay
970 $cost_per_room = ($cust_cost / count($split_stay_data));
971 $or_cust_cost = round($cost_per_room, 2);
972 if (isset($split_stay_data[$rind]) && isset($split_stay_data[$rind]['nights'])) {
973 // count the average cost per room depending on the number of nights of stay
974 $cost_per_room = $cust_cost / $daysdiff * $split_stay_data[$rind]['nights'];
975 $or_cust_cost = round($cost_per_room, 2);
976 }
977 }
978
979 // room guests
980 $room_adults = isset($adults_map[$r]) && empty($split_stay_data) ? $adults_map[$r] : $adults;
981 $room_children = isset($children_map[$r]) && empty($split_stay_data) ? $children_map[$r] : $children;
982
983 // store room record
984 $room_record = new stdClass;
985 $room_record->idorder = (int)$newoid;
986 $room_record->idroom = (int)$nowroom['id'];
987 $room_record->adults = $room_adults;
988 $room_record->children = $room_children;
989 $room_record->idtar = !empty($id_tariff) ? $id_tariff : null;
990 $room_record->t_first_name = $t_first_name;
991 $room_record->t_last_name = $t_last_name;
992 $room_record->roomindex = count($room_indexes) ? (int)$room_indexes[$use_ind_key] : null;
993 $room_record->cust_cost = $cust_cost > 0.00 ? $or_cust_cost : null;
994 $room_record->cust_idiva = $cust_cost > 0.00 && !empty($id_tax) ? $id_tax : null;
995 $room_record->room_cost = $room_cost > 0.00 ? $or_room_cost : null;
996
997 $dbo->insertObject('#__vikbooking_ordersrooms', $room_record, 'id');
998
999 if (!isset($room_record->id)) {
1000 $this->setError('Could not store room reservation record for booking ID ' . $room_record->idorder);
1001 continue;
1002 }
1003
1004 // Assign room specific unit
1005 if ($room_indexes) {
1006 $room_indexes_usemap[$nowroom['id']]++;
1007 }
1008 }
1009 }
1010 } elseif ($status == 'standby') {
1011 // store booking record
1012 $dbo->insertObject('#__vikbooking_orders', $booking, 'id');
1013
1014 if (!isset($booking->id)) {
1015 $this->setError('Could not store the reservation record');
1016 return false;
1017 }
1018
1019 // get the newly generated booking ID
1020 $newoid = $booking->id;
1021
1022 // set the new booking ID
1023 $this->setNewBookingID($newoid);
1024
1025 if (!empty($split_stay_data)) {
1026 // save transient on db for split stay information
1027 VBOFactory::getConfig()->set('split_stay_' . $newoid, json_encode($split_stay_data));
1028 }
1029
1030 // write room records
1031 foreach ($rooms_pool as $rind => $nowroom) {
1032 for ($r = 1; $r <= $or_forend; $r++) {
1033 // room custom cost
1034 $or_cust_cost = $cust_cost > 0.00 ? $cust_cost : 0;
1035 $or_cust_cost = $or_forend > 1 && $or_cust_cost > 0 ? round(($or_cust_cost / $or_forend), 2) : $or_cust_cost;
1036 // room cost from website rate plan is always based on one room
1037 $or_room_cost = $room_cost > 0.00 ? $room_cost : 0;
1038 if (!empty($split_stay_data) && $cust_cost > 0) {
1039 // set the average cost per room in case of split stay
1040 $cost_per_room = ($cust_cost / count($split_stay_data));
1041 $or_cust_cost = round($cost_per_room, 2);
1042 if (isset($split_stay_data[$rind]) && isset($split_stay_data[$rind]['nights'])) {
1043 // count the average cost per room depending on the number of nights of stay
1044 $cost_per_room = $cust_cost / $daysdiff * $split_stay_data[$rind]['nights'];
1045 $or_cust_cost = round($cost_per_room, 2);
1046 }
1047 }
1048
1049 // room guests
1050 $room_adults = isset($adults_map[$r]) && empty($split_stay_data) ? $adults_map[$r] : $adults;
1051 $room_children = isset($children_map[$r]) && empty($split_stay_data) ? $children_map[$r] : $children;
1052
1053 // store room record
1054 $room_record = new stdClass;
1055 $room_record->idorder = (int)$newoid;
1056 $room_record->idroom = (int)$nowroom['id'];
1057 $room_record->adults = $room_adults;
1058 $room_record->children = $room_children;
1059 $room_record->idtar = !empty($id_tariff) ? $id_tariff : null;
1060 $room_record->t_first_name = $t_first_name;
1061 $room_record->t_last_name = $t_last_name;
1062 $room_record->cust_cost = $cust_cost > 0.00 ? $or_cust_cost : null;
1063 $room_record->cust_idiva = $cust_cost > 0.00 && !empty($id_tax) ? $id_tax : null;
1064 $room_record->room_cost = $room_cost > 0.00 ? $or_room_cost : null;
1065
1066 $dbo->insertObject('#__vikbooking_ordersrooms', $room_record, 'id');
1067
1068 if (!isset($room_record->id)) {
1069 $this->setError('Could not store room reservation record for booking ID ' . $room_record->idorder);
1070 continue;
1071 }
1072
1073 if (empty($split_stay_data)) {
1074 // lock room for pending status
1075 $tmplock_record = new stdClass;
1076 $tmplock_record->idroom = (int)$room['id'];
1077 $tmplock_record->checkin = $checkin_ts;
1078 $tmplock_record->checkout = $checkout_ts;
1079 $tmplock_record->until = VikBooking::getMinutesLock(true);
1080 $tmplock_record->realback = $realback_ts;
1081 $tmplock_record->idorder = (int)$newoid;
1082
1083 $dbo->insertObject('#__vikbooking_tmplock', $tmplock_record, 'id');
1084 }
1085 }
1086 }
1087
1088 if (!empty($split_stay_data)) {
1089 // lock rooms for pending status on proper stay dates
1090 foreach ($split_stay_data as $split_stay) {
1091 $tmplock_record = new stdClass;
1092 $tmplock_record->idroom = (int)$split_stay['idroom'];
1093 $tmplock_record->checkin = (int)$split_stay['checkin_ts'];
1094 $tmplock_record->checkout = (int)$split_stay['checkout_ts'];
1095 $tmplock_record->until = VikBooking::getMinutesLock(true);
1096 $tmplock_record->realback = (int)$split_stay['realback_ts'];
1097 $tmplock_record->idorder = (int)$newoid;
1098
1099 $dbo->insertObject('#__vikbooking_tmplock', $tmplock_record, 'id');
1100 }
1101 }
1102 }
1103
1104 $newoid = $this->getNewBookingID();
1105 if (!$newoid) {
1106 return false;
1107 }
1108
1109 // assign booking to customer
1110 if (!$cpin->getNewCustomerId() && !empty($customer_id)) {
1111 $cpin->setNewPin($customer_pin);
1112 $cpin->setNewCustomerId($customer_id);
1113 }
1114 $cpin->saveCustomerBooking($newoid);
1115
1116 // Booking History
1117 $forced_reason = !empty($forced_reason) ? " {$forced_reason}" : $forced_reason;
1118 VikBooking::getBookingHistoryInstance()->setBid($newoid)->store('NB', "({$now_user->name}){$forced_reason}");
1119
1120 if ($status == 'confirmed') {
1121 // Invoke Channel Manager
1122 $vcm_autosync = VikBooking::vcmAutoUpdate();
1123 if ($vcm_autosync > 0) {
1124 $vcm_obj = VikBooking::getVcmInvoker();
1125 $vcm_obj->setOids([$newoid])->setSyncType('new');
1126 $sync_result = $vcm_obj->doSync();
1127 if ($sync_result === false) {
1128 // set error message
1129 $vcm_err = $vcm_obj->getError();
1130 $this->setError(JText::translate('VBCHANNELMANAGERRESULTKO') . (!empty($vcm_err) ? ' - ' . $vcm_err : ''));
1131
1132 // return true because the booking was actually stored
1133 return true;
1134 }
1135 } elseif (is_file(VCM_SITE_PATH . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'synch.vikbooking.php')) {
1136 // set the necessary action to invoke VCM
1137 $vcm_sync_url = 'index.php?option=com_vikbooking&task=invoke_vcm&stype=new&cid[]=' . $newoid . '&returl=' . urlencode('index.php?option=com_vikbooking&task=calendar&cid[]=' . $room['id']);
1138
1139 $this->setChannelManagerAction(JText::translate('VBCHANNELMANAGERINVOKEASK') . ' <button type="button" class="btn btn-primary" onclick="document.location.href=\'' . $vcm_sync_url . '\';">' . JText::translate('VBCHANNELMANAGERSENDRQ') . '</button>');
1140 }
1141 }
1142
1143 return true;
1144 }
1145 }
1146