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 |