PluginProbe ʕ •ᴥ•ʔ
VikAppointments Services Booking Calendar / 1.2.20
VikAppointments Services Booking Calendar v1.2.20
1.2.21 1.2.20 trunk 1.2.17 1.2.18 1.2.19
vikappointments / site / models / confirmapp.php
vikappointments / site / models Last commit date
allorders.php 1 month ago calendarweek.php 1 month ago cart.php 1 month ago confirmapp.php 1 month ago empaccountstat.php 1 month ago empattachser.php 1 month ago empcoupons.php 1 month ago empcustfields.php 1 month ago empeditcoupon.php 1 month ago empeditcustfield.php 1 month ago empeditlocation.php 1 month ago empeditpay.php 1 month ago empeditprofile.php 1 month ago empeditservice.php 1 month ago empeditwdays.php 1 month ago emplocations.php 1 month ago emplocwdays.php 1 month ago emplogin.php 1 month ago employeesearch.php 1 month ago employeeslist.php 1 month ago empmanres.php 1 month ago emppaylist.php 1 month ago empserviceslist.php 1 month ago empsettingsman.php 1 month ago empsubscrcart.php 1 month ago empsubscrhistory.php 1 month ago empsubscrorder.php 1 month ago empwdays.php 1 month ago index.html 1 month ago packages.php 1 month ago packagescart.php 1 month ago packagesconfirm.php 1 month ago packorders.php 1 month ago servicesearch.php 1 month ago serviceslist.php 1 month ago subscrcart.php 1 month ago subscrhistory.php 1 month ago subscrpayment.php 1 month ago
confirmapp.php
836 lines
1 <?php
2 /**
3 * @package VikAppointments
4 * @subpackage core
5 * @author E4J s.r.l.
6 * @copyright Copyright (C) 2021 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 VAPLoader::import('libraries.mvc.model');
15 // load cart framework
16 VikAppointments::loadCartLibrary();
17
18 /**
19 * VikAppointments appointments confirmation view model.
20 *
21 * @since 1.7
22 */
23 class VikAppointmentsModelConfirmapp extends JModelVAP
24 {
25 /**
26 * Completes the booking process by saving the booked appointments.
27 *
28 * @param array $data An array containing some booking options.
29 *
30 * @return mixed The landing page URL on success, false otherwise.
31 */
32 public function save($data)
33 {
34 $dispatcher = VAPFactory::getEventDispatcher();
35
36 $config = VAPFactory::getConfig();
37
38 // get cart model
39 $model = JModelVAP::getInstance('cart');
40 // get cart instance
41 $cart = $model->getCart();
42
43 ////////////////////////////////////////////////////////////
44 ////////////////////// INITIALIZATION //////////////////////
45 ////////////////////////////////////////////////////////////
46
47 if ($cart->isEmpty())
48 {
49 // cart is empty
50 $this->setError(JText::translate('VAPCARTEMPTYERR'));
51 return false;
52 }
53
54 try
55 {
56 /**
57 * Trigger event to manipulate the cart instance.
58 *
59 * @param mixed &$cart The cart instance.
60 *
61 * @return void
62 *
63 * @since 1.6
64 * @since 1.7.8 It is now possible to throw exceptions to abort the saving process.
65 */
66 $dispatcher->trigger('onInitSaveOrder', [&$cart]);
67 }
68 catch (Exception $e)
69 {
70 $this->setError($e->getMessage());
71 return false;
72 }
73
74 ////////////////////////////////////////////////////////////
75 //////////////////// AVAILABILITY CHECK ////////////////////
76 ////////////////////////////////////////////////////////////
77
78 try
79 {
80 // validates the availability according to the current platform
81 VAPApplication::getInstance()->checkAvailability();
82 }
83 catch (Exception $e)
84 {
85 $this->setError($e->getMessage());
86 return false;
87 }
88
89 // validates the appointments contained within the cart and
90 // obtain all the employees that have been assigned to each
91 // appointment into the cart
92 if (!$model->checkIntegrity($errors, $employeesLookup))
93 {
94 // there's at least an invalid item...
95 foreach ($errors as $error)
96 {
97 $name = $error['item']->getServiceName();
98 $at = JText::translate('VAP_AT_DATE_SEPARATOR');
99 $checkin = $error['item']->getCheckinDate(JText::translate('DATE_FORMAT_LC2'), VikAppointments::getUserTimezone());
100
101 // build item identifier string
102 $item_id = sprintf('%s %s %s', $name, $at, $checkin);
103
104 // register error message
105 $reason = JText::sprintf('VAPCARTITEMNOTAVERR', $item_id, $error['reason']);
106 $this->setError($reason);
107 }
108
109 return false;
110 }
111
112 /**
113 * Validates the "Mandatory Purchase" setting of the packages, by checking
114 * whether all the items within the cart can be redeemed.
115 *
116 * @since 1.7
117 */
118 if (VikAppointments::isCompliantWithMandatoryPackage($cart) == false)
119 {
120 // not enough packages to redeem
121 $link = JRoute::rewrite('index.php?option=com_vikappointments&view=packages');
122 $this->setError(JText::sprintf('VAPPACKAGEREQERR', $link));
123
124 return false;
125 }
126
127 /**
128 * Get rid of the options that are not available any longer.
129 *
130 * @since 1.7.7
131 */
132 $model->normalizeStockAvailability($cart);
133
134 ////////////////////////////////////////////////////////////
135 //////////////////// ZIP CODE VALIDATION ///////////////////
136 ////////////////////////////////////////////////////////////
137
138 // try to validate the specified ZIP code
139 if (!$this->validateZipCode(isset($data['zip']) ? $data['zip'] : null))
140 {
141 // the specified ZIP code is not allowed
142 $this->setError(JText::translate('VAPCONFAPPZIPERROR'));
143 return false;
144 }
145
146 ////////////////////////////////////////////////////////////
147 //////////////////// FETCH CUSTOM FIELDS ///////////////////
148 ////////////////////////////////////////////////////////////
149
150 // get cart items
151 $items = $cart->getItemsList();
152
153 // fetch all the booked services
154 $all_booked_services = VAPCartUtils::getServices($items);
155 // fetch all the employees that have been explicitly booked
156 $all_booked_employees = VAPCartUtils::getEmployees($items);
157
158 // prepare order array
159 $order = array();
160
161 // register current language tag
162 $order['langtag'] = JFactory::getLanguage()->getTag();
163
164 $user = JFactory::getUser();
165
166 // import custom fields requestor and loader (as dependency)
167 VAPLoader::import('libraries.customfields.requestor');
168
169 // get relevant custom fields only
170 $_cf = VAPCustomFieldsLoader::getInstance()
171 ->noSeparator()
172 ->setLanguageFilter($order['langtag'])
173 // extend custom fields by specifying all the booked services
174 ->forService($all_booked_services)
175 ->onPage('confirm');
176
177 if (count($all_booked_employees) == 1)
178 {
179 // obtain custom fields assigned to the selected employee only
180 // in case all the appointments have been explictly booked for
181 // the same employee
182 $_cf->ofEmployee($all_booked_employees[0]);
183 }
184
185 // load custom fields array
186 $customFields = $_cf->fetch();
187
188 try
189 {
190 // load custom fields from request
191 $order['custom_f'] = VAPCustomFieldsRequestor::loadForm($customFields, $tmp, $strict = true);
192 }
193 catch (Exception $e)
194 {
195 // catch exception and register it as error message
196 $this->setError($e->getMessage());
197 return false;
198 }
199
200 /**
201 * Trigger event to manipulate the custom fields array and the
202 * billing information of the customer, extrapolated from the rules
203 * of the custom fields.
204 *
205 * @param array &$fields The custom fields values.
206 * @param array &$args The billing array.
207 *
208 * @return void
209 *
210 * @since 1.6
211 */
212 $dispatcher->trigger('onPrepareFieldsSaveOrder', array(&$order['custom_f'], &$tmp));
213
214 // copy uploads into the apposite column
215 $order['uploads'] = $tmp['uploads'];
216
217 // register data fetched by the custom fields so that the reservation
218 // model is able to use them for saving purposes
219 $order['fields_data'] = $tmp;
220
221 if (empty($order['fields_data']['purchaser_nominative']))
222 {
223 // use name of the currently logged-in user
224 $order['fields_data']['purchaser_nominative'] = $user->name;
225 }
226
227 if (empty($order['fields_data']['purchaser_mail']))
228 {
229 // use e-mail of the currently logged-in user
230 $order['fields_data']['purchaser_mail'] = $user->email;
231 }
232
233 ////////////////////////////////////////////////////////////
234 /////////////////// FETCH ATTENDEES DATA ///////////////////
235 ////////////////////////////////////////////////////////////
236
237 $numAttendees = VAPCartUtils::getAttendees($items);
238
239 $order['attendees'] = array();
240
241 /**
242 * Recover attendees custom fields.
243 *
244 * @since 1.7
245 */
246 for ($attendee = 1; $attendee < $numAttendees; $attendee++)
247 {
248 // reset attendee array
249 $attendeeData = array();
250
251 // load custom fields from request for other attendees
252 $tmp = VAPCustomFieldsRequestor::loadFormAttendee($attendee, $customFields, $attendeeData);
253 // inject attendee custom fields within the array containing the fetched rules
254 $attendeeData['fields'] = $tmp;
255
256 // register attendee
257 $order['attendees'][] = $attendeeData;
258 }
259
260 ////////////////////////////////////////////////////////////
261 //////////////////// FETCH USER TIMEZONE ///////////////////
262 ////////////////////////////////////////////////////////////
263
264 if ($config->getBool('multitimezone'))
265 {
266 // multi-timezone enabled, we need to register the timezone
267 // that might have been selected by the user, in order to
268 // display the correct date and time also after the purchase
269 $order['user_timezone'] = VikAppointments::getUserTimezone()->getName();
270 }
271
272 ////////////////////////////////////////////////////////////
273 ///////////////////// VALIDATE PAYMENT /////////////////////
274 ////////////////////////////////////////////////////////////
275
276 $payment = null;
277
278 if ($cart->getTotalGross() > 0)
279 {
280 if (count($all_booked_employees) == 1)
281 {
282 // only one employee has been explicitly selected, use its own payments
283 $payments = VikAppointments::getAllEmployeePayments($all_booked_employees[0]);
284 }
285 else
286 {
287 // get global payments
288 $payments = VikAppointments::getAllEmployeePayments();
289 }
290
291 if (!isset($data['id_payment']))
292 {
293 $data['id_payment'] = 0;
294 }
295
296 // unset payment charge
297 $order['payment_charge'] = 0;
298 $order['payment_tax'] = 0;
299
300 /**
301 * Trigger event to manipulate the selected payment gateway.
302 *
303 * @param integer &$id_payment The ID of the selected payment.
304 * @param array &$payments The list of the available payments.
305 *
306 * @return void
307 *
308 * @since 1.6
309 */
310 $dispatcher->trigger('onSwitchPaymentSaveOrder', array(&$data['id_payment'], &$payments));
311
312 if ($payments)
313 {
314 // search for the selected gateway
315 $payments = array_filter($payments, function($gateway) use ($data)
316 {
317 return $gateway['id'] == $data['id_payment'];
318 });
319
320 // take the first payment found
321 $payment = array_shift($payments);
322
323 if (!$payment)
324 {
325 // invalid payment
326 $this->setError(JText::translate('VAPERRINVPAYMENT'));
327 return false;
328 }
329
330 // register payment ID
331 $order['id_payment'] = $payment['id'];
332
333 if ($payment['charge'] > 0)
334 {
335 VAPLoader::import('libraries.tax.factory');
336
337 $options = array();
338 $options['subject'] = 'payment';
339 $options['order'] = $order;
340 // $options['id_user'] = $user->id;
341
342 // calculate payment taxes
343 $charge = VAPTaxFactory::calculate($payment['id'], $payment['charge'], $options);
344
345 // set payment charge
346 $order['payment_charge'] = $charge->net;
347 $order['payment_tax'] = $charge->tax;
348 }
349 else if ($payment['charge'] < 0)
350 {
351 // register payment charge within the cart as discount
352 $cart->setDiscount(new VAPCartDiscount('payment', $payment['charge']));
353 }
354
355 // auto-confirm orders according to the configuration of
356 // the payment, otherwise force PENDING status to let the
357 // customers be able to start a transaction
358 if ($payment['setconfirmed'])
359 {
360 // auto-confirm order
361 $order['status'] = JHtml::fetch('vaphtml.status.confirmed', 'appointments', 'code');
362 }
363 else
364 {
365 // leave it pending
366 $order['status'] = JHtml::fetch('vaphtml.status.pending', 'appointments', 'code');
367 }
368 }
369 }
370
371 ////////////////////////////////////////////////////////////
372 ///////////////////// FETCH TOTAL COSTS ////////////////////
373 ////////////////////////////////////////////////////////////
374
375 /**
376 * Trigger event to manipulate the total cost before it is going to be calculated.
377 *
378 * The prices of the cart are strictly related to the taxes and to the discounts.
379 * For this reason, @since 1.7 it is no more possible to change the total cost and
380 * the user credit at runtime. Any surcharge/discount have now to be applied by
381 * using the apposite methods provided by the cart objects.
382 *
383 * @param VAPCart $cart The cart instance (@since 1.7).
384 * @param JUser $user The instance of the current user.
385 * @param array $order The order details (@since 1.7.8).
386 *
387 * @return void
388 *
389 * @since 1.6
390 */
391 $dispatcher->trigger('onBeforeCalculateTotalSaveOrder', array($cart, $user, $order));
392
393 // set up order totals
394 $order['total_cost'] = $cart->getTotalGross();
395 $order['total_net'] = $cart->getTotalNet();
396 $order['total_tax'] = $cart->getTotalTax();
397 $order['discount'] = $cart->getTotalDiscount();
398
399 // increase total cost by the payment charge
400 if (!empty($order['payment_charge']))
401 {
402 $order['total_cost'] += $order['payment_charge'] + $order['payment_tax'];
403 $order['total_tax'] += $order['payment_tax'];
404 }
405
406 ////////////////////////////////////////////////////////////
407 /////////////////////// ORDER STATUS ///////////////////////
408 ////////////////////////////////////////////////////////////
409
410 if (empty($order['status']))
411 {
412 // status not yet specified, use the default one set in config
413 $order['status'] = $config->get('defstatus');
414 }
415
416 $order['status_comment'] = null;
417
418 /**
419 * Trigger event to manipulate the order status at runtime.
420 *
421 * @param string &$status The currently fetched order status.
422 * @param string &$comment An optional status comment to be used.
423 *
424 * @return void
425 *
426 * @since 1.7
427 */
428 $dispatcher->trigger('onFetchStatusSaveOrder', array(&$order['status'], &$order['status_comment']));
429
430 // check whether the status has been immediately confirmed and we have an empty comment
431 if (empty($order['status_comment']) && JHtml::fetch('vaphtml.status.isconfirmed', 'appointments', $order['status']))
432 {
433 if ($order['total_cost'] == 0)
434 {
435 // no cost, automatically confirmed
436 $order['status_comment'] = 'VAP_STATUS_CONFIRMED_AS_NO_COST';
437 }
438 else if (!$payment)
439 {
440 // no configured payments
441 $order['status_comment'] = 'VAP_STATUS_CONFIRMED_AS_NO_PAYMENT';
442 }
443 else
444 {
445 // auto-approved through the configuration of the payment
446 $order['status_comment'] = 'VAP_STATUS_CONFIRMED_RESULT_OF_PAYMENT';
447 }
448 }
449
450 ////////////////////////////////////////////////////////////
451 ///////////////////// FETCH COUPON CODE ////////////////////
452 ////////////////////////////////////////////////////////////
453
454 /**
455 * Trigger event to manipulate any coupon code. It is also possible
456 * to apply additional events in case a specific coupon code is applied.
457 *
458 * @param mixed &$coupon The coupon code array, if any. Otherwise an empty string.
459 *
460 * @return void
461 *
462 * @since 1.6
463 * @since 1.7 The hook has been deactivated. Any further validations of the coupon
464 * codes should be applied by using the apposite hooks.
465 *
466 * @see onBeforeActivateCoupon
467 */
468 // $dispatcher->trigger('onBeforeCouponSaveOrder', array(&$coupon));
469
470 // check whether the coupon code was set
471 $coupon = $cart->getDiscount('coupon');
472
473 if ($coupon)
474 {
475 // assign coupon code to the order
476 $order['coupon'] = (array) $coupon->get('couponData');
477 // redeem coupon code
478 VikAppointments::couponUsed($order['coupon']);
479 }
480
481 ////////////////////////////////////////////////////////////
482 ///////////////////// USER REGISTRATION ////////////////////
483 ////////////////////////////////////////////////////////////
484
485 // save user data
486 if (!$user->guest || !empty($order['fields_data']['purchaser_mail']))
487 {
488 // create customer data
489 $customer = array(
490 'id' => 0,
491 'jid' => $user->guest ? 0 : $user->id,
492 'fields' => array_merge($order['custom_f'], $order['uploads']),
493 );
494
495 // inject fetched billing details
496 $customer = array_merge($customer, $order['fields_data']);
497
498 // get all redeemed discounts
499 $offers = $cart->getTotalDiscountPerOffer();
500
501 if (!empty($offers['credit']))
502 {
503 // registers the used credit
504 $customer['used_credit'] = (float) $offers['credit'];
505 }
506
507 // get customer model
508 $customerModel = JModelVAP::getInstance('customer');
509
510 // insert/update customer
511 if ($id_user = $customerModel->save($customer))
512 {
513 // assign reservation to saved customer
514 $order['id_user'] = $id_user;
515 }
516 }
517
518 ////////////////////////////////////////////////////////////
519 ///////////////////// SAVE PARENT ORDER ////////////////////
520 ////////////////////////////////////////////////////////////
521
522 $ordnum = $ordkey = null;
523
524 // get multi-order model
525 $multiOrderModel = JModelVAP::getInstance('multiorder');
526
527 if ($cart->getCartLength() > 1)
528 {
529 // we are booking 2 or more appointments, so we need to create the parent order first
530 if (!$multiOrderModel->save($order))
531 {
532 // something went wrong, retrieve error
533 $error = $multiOrderModel->getError($index = null, $string = true);
534 $this->setError($error);
535 return false;
536 }
537
538 // load details of the saved order
539 $parent = $multiOrderModel->getData();
540
541 // use order number/key pair of saved parent
542 $ordnum = $parent['id'];
543 $ordkey = $parent['sid'];
544
545 // unset payment charge to avoid increasing the total cost
546 // also for the children appointments
547 $order['payment_charge'] = 0;
548 $order['payment_tax'] = 0;
549 }
550 else
551 {
552 // no parent to use
553 $parent = null;
554 }
555
556 ////////////////////////////////////////////////////////////
557 //////////////////// LOAD ITEMS TOTALS /////////////////////
558 ////////////////////////////////////////////////////////////
559
560 $itemsTotals = $cart->getTotalsPerItem();
561
562 ////////////////////////////////////////////////////////////
563 /////////////////// SAVE BOOKED SERVICES ///////////////////
564 ////////////////////////////////////////////////////////////
565
566 // get appointments model
567 $appModel = JModelVAP::getInstance('reservation');
568 // get service-employee assoc model
569 $assocModel = JModelVAP::getInstance('serempassoc');
570 // get waiting list model
571 $waitModel = JModelVAP::getInstance('waitinglist');
572
573 // track all the employees that have been assigned to the reservations
574 $assigned_employees = array();
575
576 // iterate all the registered items
577 foreach ($items as $i => $item)
578 {
579 if ($parent)
580 {
581 // use the parent ID and SID
582 $order['id_parent'] = $parent['id'];
583 $order['sid'] = $parent['sid'];
584 }
585
586 // Load service overrides to fetch sleep time.
587 // NOTE: we need to use the employee ID set in the cart, because in case the
588 // employee was not selected, we should keep using the default service sleep time.
589 $assoc = $assocModel->getOverrides($item->getServiceID(), $item->getEmployeeID());
590
591 // register appointment details
592 $order['id_service'] = $item->getServiceID();
593 $order['id_employee'] = $employeesLookup[$i];
594 $order['checkin_ts'] = JFactory::getDate($item->getCheckinDate())->toSql();
595 $order['people'] = $item->getPeople();
596 $order['duration'] = $item->getDuration();
597 $order['sleep'] = $assoc ? $assoc->sleep : 0;
598 $order['view_emp'] = $assoc ? $assoc->choose_emp : 0;
599
600 // register appointment totals
601 $order['total_cost'] = $itemsTotals[$i]->subgross;
602 $order['total_net'] = $itemsTotals[$i]->subnet;
603 $order['total_tax'] = $itemsTotals[$i]->subtax;
604 $order['discount'] = $itemsTotals[$i]->subdisc;
605
606 // increase total cost by the payment charge
607 if (!empty($order['payment_charge']))
608 {
609 $order['total_cost'] += $order['payment_charge'] + $order['payment_tax'];
610 $order['total_tax'] += $order['payment_tax'];
611 }
612
613 // register service totals
614 $order['service_price'] = $itemsTotals[$i]->priceBeforeDiscount / $item->getPeople();
615 $order['service_net'] = $itemsTotals[$i]->net;
616 $order['service_tax'] = $itemsTotals[$i]->tax;
617 $order['service_gross'] = $itemsTotals[$i]->gross;
618 $order['service_discount'] = $itemsTotals[$i]->discount;
619
620 // register service tax breakdown
621 $order['tax_breakdown'] = json_encode($itemsTotals[$i]->breakdown);
622
623 $order['options'] = array();
624
625 foreach ($item->getOptionsList() as $j => $itemOption)
626 {
627 $option = array();
628
629 // init option base details
630 $option['id_option'] = $itemOption->getID();
631 $option['id_variation'] = $itemOption->getVariationID();
632 $option['quantity'] = $itemOption->getQuantity();
633 $option['inc_price'] = $itemOption->getPrice();
634
635 // register option totals
636 $option['net'] = $itemsTotals[$i]->options[$j]->net;
637 $option['tax'] = $itemsTotals[$i]->options[$j]->tax;
638 $option['gross'] = $itemsTotals[$i]->options[$j]->gross;
639 $option['discount'] = $itemsTotals[$i]->options[$j]->discount;
640
641 // register option tax breakdown
642 $option['tax_breakdown'] = json_encode($itemsTotals[$i]->options[$j]->breakdown);
643
644 /**
645 * Trigger event to manipulate the order item option before storing it.
646 *
647 * @param array &$option The option details (array @since 1.7).
648 * @param array $order The order item details (array @since 1.7).
649 * @param mixed $item The cart item instance.
650 *
651 * @return void
652 *
653 * @since 1.6
654 * @deprecated 1.8 Use onBeforeSaveResoption hook instead.
655 */
656 $dispatcher->trigger('onBeforeOptionSaveOrder', array(&$option, $order, $item));
657
658 // add option
659 $order['options'][] = $option;
660 }
661
662 /**
663 * Trigger event to manipulate the order item details before storing it.
664 *
665 * @param array &$order The order item details (array @since 1.7).
666 * @param mixed $item The cart item instance.
667 *
668 * @return void
669 *
670 * @since 1.6
671 * @deprecated 1.8 Use onBeforeSaveReservation hook instead.
672 */
673 $dispatcher->trigger('onBeforeSaveOrder', array(&$order, $item));
674
675 // save the order
676 if ($appModel->save($order))
677 {
678 // get reservation saved data
679 $appData = $appModel->getData();
680
681 if (!$ordnum)
682 {
683 // use order number/key pair of saved reservation
684 $ordnum = $appData['id'];
685 $ordkey = $appData['sid'];
686 }
687
688 // register booked employee
689 if (!in_array($appData['id_employee'], $assigned_employees))
690 {
691 $assigned_employees[] = $appData['id_employee'];
692 }
693
694 /**
695 * Trigger event after storing the order item details.
696 *
697 * @param object $order The order item details object (removed reference @since 1.7).
698 * @param mixed $item The cart item instance.
699 *
700 * @return void
701 *
702 * @since 1.6
703 * @deprecated 1.8 Use onAfterSaveReservation hook instead.
704 */
705 $dispatcher->trigger('onAfterSaveOrder', array($order, $item));
706
707 // the appointment was registered, unsubscribe the customer from
708 // the related waiting list
709 $waitModel->unsubscribe(array(
710 'jid' => $appData['createdby'],
711 'email' => $appData['purchaser_mail'],
712 'phone_number' => $appData['purchaser_phone'],
713 'timestamp' => $appData['checkin_ts'],
714 'id_service' => $appData['id_service'],
715 ));
716 }
717 }
718
719 /**
720 * In case all the appointments registration failed, abort the saving process.
721 *
722 * @since 1.7.6
723 */
724 if (!$ordnum)
725 {
726 // propagate error
727 $this->setError($appModel->getError());
728 return false;
729 }
730
731 // empty cart on success
732 $cart->emptyCart();
733 $cart->store();
734
735 ////////////////////////////////////////////////////////////
736 ///////////////////////// PACKAGES /////////////////////////
737 ////////////////////////////////////////////////////////////
738
739 // register used packages after saving all the appointments,
740 // because we need to load all the saved records
741 $redeemed = JModelVAP::getInstance('packorder')->usePackages($ordnum, $increase = true);
742
743 if ($redeemed)
744 {
745 // some packages have been redeemed, use a different status comment
746 VAPOrderStatus::getInstance()->keepTrack($order['status'], $ordnum, 'VAP_STATUS_PACKAGE_REDEEMED');
747 }
748
749 ////////////////////////////////////////////////////////////
750 ////////////////////// NOTIFICATIONS ///////////////////////
751 ////////////////////////////////////////////////////////////
752
753 $mailOptions = array();
754 // validate e-mail rules before sending
755 $mailOptions['check'] = true;
756
757 // send e-mail notification to the customer
758 $appModel->sendEmailNotification($ordnum, $mailOptions);
759
760 // send e-mail notification to the administrator(s)
761 $mailOptions['client'] = 'admin';
762 $appModel->sendEmailNotification($ordnum, $mailOptions);
763
764 // send e-mail notification to all the booked employees
765 $mailOptions['client'] = 'employee';
766
767 foreach ($assigned_employees as $id_employee)
768 {
769 $mailOptions['id_employee'] = (int) $id_employee;
770 $appModel->sendEmailNotification($ordnum, $mailOptions);
771 }
772
773 /**
774 * In case there are products with low stocks, send a notification to the administrators.
775 *
776 * @since 1.7.7
777 */
778 JModelVAP::getInstance('option')->sendEmailNotification();
779
780 // try to send SMS notifications
781 VikAppointments::sendSmsAction($ordnum);
782
783 $redirect_url = "index.php?option=com_vikappointments&view=order&ordnum={$ordnum}&ordkey={$ordkey}";
784
785 if (!empty($data['itemid']))
786 {
787 $redirect_url .= "&Itemid={$data['itemid']}";
788 }
789
790 /**
791 * Trigger event to manipulate the redirect URL after completing
792 * the appointment booking process.
793 *
794 * Use VAPOrderFactory::getAppointments($ordnum) to access the order details.
795 *
796 * @param string &$url The redirect URL (plain).
797 * @param integer $order The order id (replaced order array @since 1.7).
798 *
799 * @return void
800 *
801 * @since 1.6.4
802 */
803 $dispatcher->trigger('onRedirectOrder', array(&$redirect_url, $ordnum));
804
805 // rewrite landing page
806 return JRoute::rewrite($redirect_url, false);
807 }
808
809 /**
810 * Checks whether the specified ZIP code is accepted by the booked
811 * employees. If not specified, the ZIP code will be retrieved from
812 * the request according to the name of the assigned custom field.
813 *
814 * @param string|null $zip The ZIP code.
815 *
816 * @return boolean True if accepted, false otherwise.
817 */
818 public function validateZipCode($zip = null)
819 {
820 // get cart instance
821 $cart = JModelVAP::getInstance('cart')->getCart();
822
823 // get cart items
824 $items = $cart->getItemsList();
825
826 // load all the selected employees
827 $id_employees = VAPCartUtils::getEmployees($items);
828
829 // load all the selected services
830 $id_services = VAPCartUtils::getServices($items);
831
832 // validate ZIP code
833 return VikAppointments::validateZipCode($zip, $id_employees, $id_services);
834 }
835 }
836