PluginProbe ʕ •ᴥ•ʔ
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress / 5.6.4
Appointment Booking Plugin – LatePoint | Calendar & Scheduling for WordPress v5.6.4
5.6.9 5.6.8 5.6.7 5.6.6 5.6.5 5.6.4 5.6.3 5.6.2 5.6.1 5.6.0 5.5.2 5.5.1 5.5.0 5.4.2 trunk 5.1.0 5.1.1 5.1.2 5.1.3 5.1.4 5.1.5 5.1.6 5.1.7 5.1.8 5.1.9 5.1.91 5.1.92 5.1.93 5.1.94 5.2.0 5.2.1 5.2.10 5.2.11 5.2.2 5.2.3 5.2.4 5.2.5 5.2.6 5.2.7 5.2.8 5.2.9 5.3.0 5.3.1 5.3.2 5.4.0 5.4.1
latepoint / lib / controllers / orders_controller.php
latepoint / lib / controllers Last commit date
activities_controller.php 2 months ago auth_controller.php 4 months ago booking_form_settings_controller.php 4 months ago bookings_controller.php 1 month ago calendars_controller.php 4 months ago carts_controller.php 4 months ago controller.php 4 months ago customer_cabinet_controller.php 2 months ago customers_controller.php 4 months ago dashboard_controller.php 2 months ago default_agent_controller.php 4 months ago events_controller.php 4 months ago form_fields_controller.php 3 weeks ago integrations_controller.php 4 months ago invoices_controller.php 1 month ago manage_booking_by_key_controller.php 4 months ago manage_order_by_key_controller.php 4 months ago notifications_controller.php 4 months ago orders_controller.php 3 weeks ago pro_controller.php 1 month ago process_jobs_controller.php 4 months ago processes_controller.php 1 month ago razorpay_connect_controller.php 3 weeks ago search_controller.php 4 months ago services_controller.php 4 months ago settings_controller.php 2 months ago steps_controller.php 1 month ago stripe_connect_controller.php 3 weeks ago support_topics_controller.php 4 months ago todos_controller.php 4 months ago transactions_controller.php 4 months ago wizard_controller.php 3 weeks ago
orders_controller.php
864 lines
1 <?php
2 /*
3 * Copyright (c) 2024 LatePoint LLC. All rights reserved.
4 */
5
6 if ( ! defined( 'ABSPATH' ) ) {
7 exit; // Exit if accessed directly.
8 }
9
10
11 if ( ! class_exists( 'OsOrdersController' ) ) :
12
13
14 class OsOrdersController extends OsController {
15
16 function __construct() {
17 parent::__construct();
18
19 $this->views_folder = LATEPOINT_VIEWS_ABSPATH . 'orders/';
20 $this->vars['page_header'] = OsMenuHelper::get_menu_items_by_id( 'orders' );
21 $this->vars['breadcrumbs'][] = array(
22 'label' => __( 'Orders', 'latepoint' ),
23 'link' => OsRouterHelper::build_link( OsRouterHelper::build_route_name( 'orders', 'index' ) ),
24 );
25
26 $this->action_access['public'] = array_merge( $this->action_access['public'], [ 'continue_order_intent', 'continue_transaction_intent' ] );
27 }
28
29
30 public function view_order_log() {
31 $activities = new OsActivityModel();
32 $activities = $activities->where( [ 'order_id' => absint( $this->params['order_id'] ) ] )->order_by( 'id desc' )->get_results_as_models();
33
34 $order = new OsOrderModel( $this->params['order_id'] );
35
36 $this->vars['order'] = $order;
37 $this->vars['activities'] = $activities;
38
39 $this->format_render( __FUNCTION__ );
40 }
41
42
43 public function continue_order_intent() {
44 $order_intent_key = $this->params['order_intent_key'];
45 $order_intent = OsOrderIntentHelper::get_order_intent_by_intent_key( $order_intent_key );
46
47 if ( $order_intent->is_new_record() ) {
48 http_response_code( 400 );
49 OsDebugHelper::log( 'Order intent not found, id:' . $order_intent_key );
50 exit();
51 } else {
52
53 $order_intent->convert_to_order();
54
55 nocache_headers();
56 // Redirect with the intent key in either outcome, front.js resumes the booking flow at the confirmation
57 // step when convert_to_order succeeded, or at the payment step when it didn't (e.g. cancelled Mollie/PayPal
58 // payment). The "double payment information" issue is fixed at the cart-model layer (see
59 // OsCartModel::clear) without that fix, the resume lightbox showed every cart item twice.
60 wp_redirect( $order_intent->get_page_url_with_intent(), 302 );
61 exit;
62 }
63 }
64
65
66 public function continue_transaction_intent() {
67 $intent_key = $this->params['transaction_intent_key'];
68 $transaction_intent = OsTransactionIntentHelper::get_transaction_intent_by_intent_key( $intent_key );
69
70 if ( $transaction_intent->is_new_record() ) {
71 http_response_code( 400 );
72 OsDebugHelper::log( 'Transaction intent not found, id:' . $intent_key );
73 exit();
74 } else {
75 $transaction_intent->convert_to_transaction();
76
77 if ( $transaction_intent ) {
78 nocache_headers();
79 wp_redirect( $transaction_intent->get_page_url_with_intent(), 302 );
80 }
81 }
82 }
83
84 /*
85 Update order (used in admin on quick side form save)
86 */
87
88 public function update() {
89 $this->create_or_update();
90 }
91
92
93 /*
94 Create order (used in admin on quick side form save)
95 */
96
97 public function create() {
98 $this->create_or_update();
99 }
100
101
102 // Create/Update order from quick form in admin
103 public function create_or_update() {
104 $validation_errors = [];
105
106 if ( ! empty( $this->params['order']['id'] ) ) {
107 $this->check_nonce( 'edit_order_' . $this->params['order']['id'] );
108 } else {
109 $this->check_nonce( 'new_order' );
110 }
111
112 $order_params = $this->params['order'];
113 $customer_params = $this->params['customer'];
114
115 $order_items_params = $this->params['order_items'] ?? [];
116
117 $order = new OsOrderModel( $order_params['id'] );
118
119 // if we are updating a order - save a copy by cloning old order
120 $old_order = ( $order->is_new_record() ) ? [] : clone $order;
121 $order->set_data( $order_params );
122
123 // first validate & create a customer the customer
124 $old_customer_data = [];
125 if ( $order->customer_id ) {
126 $customer = new OsCustomerModel( $order->customer_id );
127 $old_customer_data = $customer->get_data_vars();
128 $is_new_customer = false;
129 } else {
130 $customer = new OsCustomerModel();
131 $is_new_customer = true;
132 }
133
134 // The submitted customer[...] fields may overwrite an EXISTING customer record only when
135 // (1) the current user is authorized to edit that customer
136 // (2) the customer is is an allowed customer list.
137 // Attaching any customer to an order is always permitted; new customers proceed normally.
138 if ( $is_new_customer || $customer->is_new_record() ) {
139 $can_modify_customer = true;
140 } else {
141 $wp_link_is_safe = empty( $customer->wordpress_user_id )
142 || OsCustomerHelper::is_wp_user_safe_for_customer_link( (int) $customer->wordpress_user_id );
143 $can_modify_customer = $wp_link_is_safe
144 && OsRolesHelper::can_user_make_action_on_model_record( $customer, 'edit' );
145 }
146
147 if ( $can_modify_customer ) {
148 // Set customer data only if is allowed
149 $customer->set_data( $customer_params, OsAuthHelper::is_admin_logged_in() ? LATEPOINT_PARAMS_SCOPE_ADMIN : LATEPOINT_PARAMS_SCOPE_PUBLIC );
150 if ( $customer->save() ) {
151 if ( $is_new_customer ) {
152 do_action( 'latepoint_customer_created', $customer );
153 $this->fields_to_update['order[customer_id]'] = $customer->id;
154 } else {
155 do_action( 'latepoint_customer_updated', $customer, $old_customer_data );
156 }
157
158 $order->customer_id = $customer->id;
159 } else {
160 $this->send_json(
161 [
162 'status' => LATEPOINT_STATUS_ERROR,
163 // translators: %s is the description of an error
164 'message' => sprintf( __( 'Error: %s', 'latepoint' ), implode( ', ', $customer->get_error_messages() ) ),
165 ]
166 );
167 }
168 }
169
170 // validate order items
171 foreach ( $order_items_params as $order_item_id => $order_item ) {
172 foreach ( $order_item['bookings'] as $booking_id => $booking_params ) {
173 $booking = OsOrdersHelper::create_booking_object_from_booking_data_form( $booking_params );
174 $booking->customer_id = $order->customer_id;
175 if ( ! $booking->validate( false, [ 'order_item_id' ] ) ) {
176 $validation_errors = array_merge( $validation_errors, $booking->get_error_messages() );
177 }
178 }
179 }
180
181 // check if there are errors saving bookings
182 if ( $validation_errors ) {
183 // translators: %s is the description of an error
184 $this->send_json(
185 array(
186 'status' => LATEPOINT_STATUS_ERROR,
187 'message' => sprintf( __( 'Error: %s', 'latepoint' ), implode( ', ', $validation_errors ) ),
188 )
189 );
190 }
191
192 if ( $old_order ) {
193 // make sure old order items are still there, if not - remove them
194 $order_items = $order->get_items();
195 foreach ( $order_items as $order_item ) {
196 if ( ! isset( $order_items_params[ $order_item->id ] ) ) {
197 $order_item_id_to_delete = $order_item->id;
198 /**
199 * Fires right before an order item is about to be deleted
200 *
201 * @param {integer} $order_item_id ID of the Order Item which will be deleted
202 *
203 * @since 5.0.0
204 * @hook latepoint_order_item_will_be_deleted
205 *
206 */
207 do_action( 'latepoint_order_item_will_be_deleted', $order_item_id_to_delete );
208
209 $order_item->delete();
210
211 /**
212 * Fires right after an order item has been deleted
213 *
214 * @param {integer} $order_item_id ID of the Order Item that was deleted
215 *
216 * @since 5.0.0
217 * @hook latepoint_order_item_deleted
218 *
219 */
220 do_action( 'latepoint_order_item_deleted', $order_item_id_to_delete );
221 OsActivitiesHelper::log_order_item_deleted( $order_item );
222 } else {
223 // it's a bundle order item - search for bookings that are attached to this bundle and remove them if not found in passed params list
224 if ( $order_item->is_bundle() ) {
225 $old_bundle_bookings = OsOrdersHelper::get_bookings_for_order_item( $order_item->id );
226 if ( $old_bundle_bookings ) {
227 foreach ( $old_bundle_bookings as $old_bundle_booking ) {
228 if ( empty( $order_items_params[ $order_item->id ]['bookings'][ $old_bundle_booking->id ] ) ) {
229
230 if ( OsRolesHelper::can_user_make_action_on_model_record( $old_bundle_booking, 'delete' ) ) {
231 $booking_id_to_delete = $old_bundle_booking->id;
232
233 /**
234 * Fires right before a booking is about to be deleted
235 *
236 * @param {integer} $booking_id ID of the booking that will be deleted
237 *
238 * @since 5.0.0
239 * @hook latepoint_booking_will_be_deleted
240 *
241 */
242 do_action( 'latepoint_booking_will_be_deleted', $booking_id_to_delete );
243
244 $old_bundle_booking->delete();
245 /**
246 * Fires right after a booking has been deleted
247 *
248 * @param {integer} $booking_id ID of the booking that was deleted
249 *
250 * @since 5.0.0
251 * @hook latepoint_booking_deleted
252 *
253 */
254 do_action( 'latepoint_booking_deleted', $booking_id_to_delete );
255 OsActivitiesHelper::log_booking_deleted( $old_bundle_booking );
256 } else {
257 OsDebugHelper::log( 'Not allowed: Deleting Booking', 'permissions_error' );
258 }
259 }
260 }
261 }
262 }
263 }
264 }
265 }
266
267 // Because price is not in allowed_params to bulk set, check if it's passed in params and set it, OTHERWISE CALCULATE IT
268 if ( isset( $order_params['total'] ) ) {
269 $order->total = OsParamsHelper::sanitize_param( $order_params['total'], 'money' );
270 }
271 if ( isset( $order_params['subtotal'] ) ) {
272 $order->subtotal = OsParamsHelper::sanitize_param( $order_params['subtotal'], 'money' );
273 }
274
275 // save price breakdown, we only need to save before and after subtotal, as total and subtotal values are stored on the Order record itself
276 if ( ! empty( $this->params['price_breakdown'] ) ) {
277 $order->price_breakdown = wp_json_encode( OsOrdersHelper::generate_price_breakdown_from_params( $this->params['price_breakdown'] ) );
278 }
279
280 // Check if we have to create a payment request
281 $create_payment_request = ( sanitize_text_field( $this->params['create_payment_request'] ?? '' ) == LATEPOINT_VALUE_ON );
282 if ( $create_payment_request ) {
283 $payment_request_data = $this->params['payment_request'];
284 $payment_request_data['portion'] = sanitize_text_field( $payment_request_data['portion'] );
285 $payment_request_data['charge_amount'] = sanitize_text_field( $payment_request_data[ 'charge_amount_' . $payment_request_data['portion'] ] );
286 $payment_request_data['due_at'] = OsTimeHelper::custom_datetime_utc_in_db_format( sanitize_text_field( $payment_request_data['due_at'] ) . ' 23:59:59' );
287 $order->set_initial_payment_data_value( 'time', LATEPOINT_PAYMENT_TIME_NOW, false );
288 $order->set_initial_payment_data_value( 'portion', $payment_request_data['portion'], false );
289 $order->set_initial_payment_data_value( 'charge_amount', OsMoneyHelper::convert_amount_from_money_input_to_db_format( $payment_request_data['charge_amount'] ) );
290
291 $payment_request = new OsPaymentRequestModel();
292
293 $payment_request = $payment_request->set_data( $payment_request_data );
294
295 } else {
296 $order->set_initial_payment_data_value( 'time', LATEPOINT_PAYMENT_TIME_LATER );
297 $payment_request = null;
298 }
299
300 if ( $order->save() ) {
301
302 // save transactions
303 if ( ! empty( $this->params['transactions'] ) ) {
304 foreach ( $this->params['transactions'] as $transaction_params ) {
305 if ( ! empty( $transaction_params['id'] ) && filter_var( $transaction_params['id'], FILTER_VALIDATE_INT ) ) {
306 // update existing transaction
307 $transaction = new OsTransactionModel( $transaction_params['id'] );
308 $is_new_transaction = false;
309 } else {
310 // new transaction
311 $transaction = new OsTransactionModel();
312 $is_new_transaction = true;
313 }
314 unset( $transaction_params['id'] );
315 $transaction_params['invoice_id'] = filter_var( $transaction_params['invoice_id'], FILTER_VALIDATE_INT ) ? $transaction_params['invoice_id'] : null;
316 $transaction->set_data( $transaction_params );
317 $transaction->order_id = $order->id;
318 $transaction->customer_id = $customer->id;
319 $transaction->status = LATEPOINT_TRANSACTION_STATUS_SUCCEEDED;
320 $transaction->save();
321 if ( $is_new_transaction ) {
322 /**
323 * Transaction for order was created
324 *
325 * @param {OsTransactionModel} $transaction instance of transaction model that was created
326 *
327 * @since 5.0.0
328 * @hook latepoint_transaction_created
329 *
330 */
331 do_action( 'latepoint_transaction_created', $transaction );
332 }
333 }
334 }
335 foreach ( $order_items_params as $order_item_id => $order_item ) {
336 $order_item_model = new OsOrderItemModel();
337 $order_item_model->variant = $order_item['variant'];
338 if ( strpos( $order_item_id, 'new_' ) === false ) {
339 $order_item_model->load_by_id( $order_item_id );
340 }
341 if ( $order_item_model->is_bundle() ) {
342 $order_item_model->item_data = base64_decode( $order_item['item_data'] );
343 $order_item_model->recalculate_prices();
344 }
345
346
347 $order_item_model->order_id = $order->id;
348 if ( $order_item_model->save() ) {
349 foreach ( $order_item['bookings'] as $booking_id => $booking_params ) {
350 $booking = new OsBookingModel();
351 $old_booking = false;
352 if ( strpos( $order_item_id, 'new_' ) === false ) {
353 $booking->load_by_id( $booking_id );
354 if ( ! $booking->is_new_record() ) {
355 $old_booking = clone $booking;
356 }
357 }
358
359 $booking = OsOrdersHelper::create_booking_object_from_booking_data_form( $booking_params );
360 $booking->customer_id = $order->customer_id;
361 $booking->order_item_id = $order_item_model->id;
362 $booking->form_id = $booking_id;
363 if ( $booking->save() ) {
364 if ( $order_item_model->is_booking() ) {
365 $order_item_model->item_data = $booking->generate_item_data();
366 $order_item_model->recalculate_prices();
367 $order_item_model->save();
368 }
369 if ( $old_booking ) {
370 do_action( 'latepoint_booking_updated', $booking, $old_booking );
371 if ( $old_booking->status != $booking->status ) {
372 do_action( 'latepoint_booking_change_status', $booking, $old_booking );
373 OsActivitiesHelper::log_booking_change_status( $booking, $old_booking );
374 }
375 } else {
376 do_action( 'latepoint_booking_created', $booking );
377 }
378 } else {
379 OsDebugHelper::log( 'Error saving booking (admin)', 'booking_save_error', $booking->get_error_messages() );
380 }
381 }
382 } else {
383 OsDebugHelper::log( 'Error saving order item (admin)', 'order_item_save_error', $order_item_model->get_error_messages() );
384 }
385 }
386
387
388
389 if ( $old_order ) {
390 /**
391 * Order was updated
392 *
393 * @param {OsOrderModel} $order instance of order model after it was updated
394 * @param {OsOrderModel} $old_order instance of order model before it was updated
395 *
396 * @since 5.0.0
397 * @hook latepoint_order_updated
398 *
399 */
400 do_action( 'latepoint_order_updated', $order, $old_order );
401 } else {
402 OsInvoicesHelper::create_invoices_for_new_order( $order, $payment_request );
403 /**
404 * Order was created
405 *
406 * @param {OsOrderModel} $order instance of order model that was created
407 *
408 * @since 5.0.0
409 * @hook latepoint_order_created
410 *
411 */
412 do_action( 'latepoint_order_created', $order );
413 }
414
415 $status = LATEPOINT_STATUS_SUCCESS;
416 // translators: %s is a link to the updated order
417 $response_html = sprintf( ( ( $old_order ) ? __( 'Order Updated ID: %s', 'latepoint' ) : __( 'Order Created ID: %s', 'latepoint' ) ), '<span class="os-notification-link" ' . OsOrdersHelper::quick_order_btn_html( $order->id ) . '>' . $order->id . '</span>' );
418 } else {
419 OsDebugHelper::log( 'Error saving order (admin)', 'order_save_error', $order->get_error_messages() );
420 $status = LATEPOINT_STATUS_ERROR;
421
422 // translators: %s is an error message
423 $response_html = sprintf( __( 'Error: %s', 'latepoint' ), implode( ', ', $order->get_error_messages() ) );
424 }
425
426 if ( $this->get_return_format() == 'json' ) {
427 $this->send_json(
428 array(
429 'status' => $status,
430 'message' => $response_html,
431 )
432 );
433 }
434 }
435
436
437 // reloads a section of a quick edit form that has a price breakdown
438 public function reload_price_breakdown() {
439 $order = new OsOrderModel();
440 $order->set_data( $this->params['order'] );
441
442 $order_items_params = $this->params['order_items'] ?? [];
443 foreach ( $order_items_params as $order_items_param ) {
444 $order->items[] = OsOrdersHelper::create_order_item_object( $order_items_param );
445 }
446
447 $order->subtotal = $order->recalculate_subtotal();
448 $order->total = $order->recalculate_total();
449
450 /**
451 * Reloads price breakdown rows
452 *
453 * @since 5.0.0
454 * @hook latepoint_register_role
455 *
456 * @param {OsOrderModel} $order Order for which to reload price breakdown
457 * @returns {OsOrderModel} Filtered order with updated price breakdown rows
458 */
459 $order = apply_filters( 'latepoint_order_reload_price_breakdown', $order );
460
461 // we don't need to generate balance and payments info as it is printed in a separate block
462 $this->vars['price_breakdown_rows'] = $order->generate_price_breakdown_rows( [ 'balance', 'payments' ], true );
463
464 $this->vars['order'] = $order;
465 $this->format_render( __FUNCTION__ );
466 }
467
468 function reload_balance_and_payments() {
469 $order_params = $this->params['order'];
470 $order_items_params = $this->params['order_items'] ?? [];
471
472 $order = new OsOrderModel();
473 $order->set_data( $order_params );
474
475 foreach ( $order_items_params as $order_items_param ) {
476 $order->items[] = OsOrdersHelper::create_order_item_object( $order_items_param );
477 }
478
479
480
481 // Because price is not in allowed_params to bulk set, check if it's passed in params and set it, OTHERWISE CALCULATE IT
482 if ( isset( $order_params['total'] ) ) {
483 $order->total = OsParamsHelper::sanitize_param( $order_params['total'], 'money' );
484 }
485 if ( isset( $order_params['subtotal'] ) ) {
486 $order->subtotal = OsParamsHelper::sanitize_param( $order_params['subtotal'], 'money' );
487 }
488
489
490 $this->vars['order'] = $order;
491 $this->format_render( __FUNCTION__ );
492 }
493
494 function generate_bundle_order_item_block() {
495 $bundle = new OsBundleModel( $this->params['bundle_id'] );
496
497 $order_item_id = OsUtilHelper::generate_form_id();
498 $response_html = '<div class="order-item order-item-variant-bundle" data-order-item-id="' . $order_item_id . '">';
499 $response_html .= OsOrdersHelper::generate_order_item_pill_for_bundle( $bundle, $order_item_id );
500 $response_html .= '</div>';
501
502 if ( $this->get_return_format() == 'json' ) {
503 $this->send_json(
504 array(
505 'status' => LATEPOINT_STATUS_SUCCESS,
506 'message' => $response_html,
507 )
508 );
509 }
510 }
511
512 function generate_booking_order_item_block() {
513
514 if ( ! empty( $this->params['order_item_variant'] ) && ( $this->params['order_item_variant'] == LATEPOINT_ITEM_VARIANT_BUNDLE ) ) {
515 // booking for bundle, we don't need to wrap in order-item block because order item is a bundle
516 $booking = OsBookingHelper::build_booking_model_from_item_data( json_decode( base64_decode( $this->params['booking_item_data'] ), true ) );
517 $response_html = OsOrdersHelper::booking_data_form_for_order_item_id( $this->params['order_item_id'], $booking, LATEPOINT_ITEM_VARIANT_BUNDLE, false );
518 } else {
519 // regular booking
520 $booking = OsBookingHelper::prepare_new_from_params( $this->params );
521 $order_item_id = OsUtilHelper::generate_form_id();
522 $response_html = '<div class="order-item order-item-variant-booking" data-order-item-id="' . $order_item_id . '">';
523 $response_html .= OsOrdersHelper::booking_data_form_for_order_item_id( $order_item_id, $booking, LATEPOINT_ITEM_VARIANT_BOOKING, false );
524 $response_html .= '</div>';
525 }
526
527
528 if ( $this->get_return_format() == 'json' ) {
529 $this->send_json(
530 array(
531 'status' => LATEPOINT_STATUS_SUCCESS,
532 'message' => $response_html,
533 )
534 );
535 }
536 }
537
538 function fold_booking_data_form() {
539 // input fields are formatted in customer preferred format, we need to convert that to database format Y-m-d
540 $order_item_id = $this->params['order_item_id'];
541 $booking_id = $this->params['booking_id'];
542
543 $booking_params = $this->params['order_items'][ $order_item_id ]['bookings'][ $booking_id ];
544 $booking = OsOrdersHelper::create_booking_object_from_booking_data_form( $booking_params );
545
546 if ( $this->params['order_items'][ $order_item_id ]['variant'] == LATEPOINT_ITEM_VARIANT_BUNDLE ) {
547 $response_html = OsOrdersHelper::generate_order_item_pill_for_bundle_booking( $booking, $order_item_id );
548 } else {
549 $response_html = OsOrdersHelper::generate_order_item_pill_for_booking( $booking, $order_item_id );
550 }
551 $this->send_json(
552 array(
553 'status' => LATEPOINT_STATUS_SUCCESS,
554 'message' => $response_html,
555 )
556 );
557 }
558
559 function generate_order_item_booking_data_form() {
560 $order_item = new OsOrderItemModel();
561 $order_item->variant = $this->params['order_item_variant'] ?? LATEPOINT_ITEM_VARIANT_BOOKING;
562 if ( ! empty( $this->params['order_item_id'] ) ) {
563 // existing order item
564 if ( strpos( $this->params['order_item_id'], 'new_' ) !== false ) {
565 $order_item->form_id = $this->params['order_item_id'];
566 } else {
567 $order_item->id = $this->params['order_item_id'];
568 }
569 $order_item->item_data = ! empty( $this->params['order_item_item_data'] ) ? base64_decode( $this->params['order_item_item_data'] ) : '';
570 if ( $order_item->is_bundle() ) {
571 $booking_item_data = ! empty( $this->params['booking_item_data'] ) ? base64_decode( $this->params['booking_item_data'] ) : '';
572 $booking = OsBookingHelper::build_booking_model_from_item_data( json_decode( $booking_item_data, true ) );
573 } else {
574 $booking = $order_item->build_original_object_from_item_data();
575 }
576 } else {
577 // new order item
578 $booking = OsBookingHelper::prepare_new_from_params( $this->params );
579 }
580
581 $response_html = OsOrdersHelper::booking_data_form_for_order_item_id( $order_item->get_form_id(), $booking, $order_item->variant );
582 $this->send_json(
583 array(
584 'status' => LATEPOINT_STATUS_SUCCESS,
585 'message' => $response_html,
586 )
587 );
588 }
589
590 function quick_edit() {
591
592 $customers = new OsCustomerModel();
593 // only load customers that belong to logged in agent, if any
594 if ( ! OsRolesHelper::are_all_records_allowed( 'agent' ) ) {
595 $customers->select( LATEPOINT_TABLE_CUSTOMERS . '.*' )->join( LATEPOINT_TABLE_BOOKINGS, [ 'customer_id' => LATEPOINT_TABLE_CUSTOMERS . '.id' ] )->group_by( LATEPOINT_TABLE_CUSTOMERS . '.id' )->where( [ LATEPOINT_TABLE_BOOKINGS . '.agent_id' => OsRolesHelper::get_allowed_records( 'agent' ) ] );
596 }
597
598
599 $customers_arr = $customers->order_by( 'first_name asc, last_name asc' )->set_limit( 20 )->get_results_as_models();
600 $this->vars['customers'] = $customers_arr;
601
602 // CREATING NEW ORDER
603 $order = new OsOrderModel();
604 $order_id = $this->params['id'] ?? false;
605
606 if ( ! empty( $this->params['booking_id'] ) ) {
607 $preselected_booking = new OsBookingModel( $this->params['booking_id'] );
608 $preselected_order_item = new OsOrderItemModel( $preselected_booking->order_item_id );
609 $order_id = $preselected_order_item->order_id;
610 } else {
611 $preselected_booking = false;
612 $preselected_order_item = false;
613 }
614
615 if ( $order_id ) {
616 // EDITING EXISTING ORDER
617 $order = new OsOrderModel( $order_id );
618 // TODO add this check for order
619 // if(!OsRolesHelper::can_user_make_action_on_model_record($order, 'view')){
620 // $this->send_json(array('status' => LATEPOINT_STATUS_ERROR, 'message' => 'Not Allowed'));
621 // }
622
623 $transactions = $order->get_transactions();
624
625 } else {
626 // NEW ORDER
627
628 // LOAD FROM PASSED PARAMS
629 $order->status = OsOrdersHelper::get_default_order_status();
630 $order->fulfillment_status = $order->get_default_fulfillment_status();
631
632 if ( ! empty( $this->params['customer_id'] ) ) {
633 $order->customer_id = $this->params['customer_id'];
634 }
635
636 $new_booking = OsBookingHelper::prepare_new_from_params( $this->params );
637 $new_booking = apply_filters( 'latepoint_prepare_booking_for_quick_view', $new_booking );
638 $order_item_model = new OsOrderItemModel();
639 $order_item_model->variant = LATEPOINT_ITEM_VARIANT_BOOKING;
640 $order_item_model->item_data = $new_booking->generate_item_data();
641
642 $order->items[] = $order_item_model;
643
644 $order->total = $order->recalculate_total();
645 $order->subtotal = $order->recalculate_subtotal();
646
647 $transactions = [];
648 }
649
650 $bundles = new OsBundleModel();
651 $bundles = $bundles->should_be_active()->get_results_as_models();
652 $this->vars['bundles'] = $bundles;
653
654
655 $this->vars['price_breakdown_rows'] = $order->generate_price_breakdown_rows();
656
657 $order = apply_filters( 'latepoint_prepare_order_for_quick_view', $order );
658
659 $order_bookings = $order->get_bookings_from_order_items();
660 $order_bundles = $order->get_bundles_from_order_items();
661
662
663 $this->vars['selected_customer'] = new OsCustomerModel( $order->customer_id );
664 $this->vars['order'] = $order;
665 $this->vars['preselected_booking'] = $preselected_booking;
666 $this->vars['preselected_order_item'] = $preselected_order_item;
667 $this->vars['show_only_preselected_items'] = $preselected_booking && $preselected_order_item && ( ( count( $order_bookings ) > 1 ) || ( count( $order_bundles ) ) || ( $order_bundles && $order_bookings ) );
668
669 $this->vars['order_bookings'] = $order_bookings;
670 $this->vars['order_bundles'] = $order_bundles;
671 $this->vars['transactions'] = $transactions;
672 $this->vars['default_fields_for_customer'] = OsSettingsHelper::get_default_fields_for_customer();
673 $this->format_render( __FUNCTION__ );
674 }
675
676 public function edit_form() {
677 $order = ( empty( $this->params['id'] ) ) ? new OsOrderModel() : new OsOrderModel( $this->params['id'] );
678 // legacy fix for older orders that didn't have portion column, get it from connected order
679 if ( ! $order->is_new_record() && empty( $order->payment_portion ) && ! empty( $order->booking_id ) ) {
680 $booking = new OsBookingModel( $order->booking_id );
681 if ( ! empty( $booking->id ) ) {
682 $order->payment_portion = $booking->payment_portion;
683 }
684 }
685 $this->vars['real_or_rand_id'] = ( $order->is_new_record() ) ? 'new_order_' . OsUtilHelper::random_text( 'alnum', 5 ) : $order->id;
686 $this->vars['order'] = $order;
687
688 $this->format_render( __FUNCTION__ );
689 }
690
691 public function destroy() {
692 if ( filter_var( $this->params['id'], FILTER_VALIDATE_INT ) ) {
693 $this->check_nonce( 'destroy_order_' . $this->params['id'] );
694 $order = new OsOrderModel( $this->params['id'] );
695 if ( $order->delete() ) {
696 $status = LATEPOINT_STATUS_SUCCESS;
697 $response_html = __( 'Order Removed', 'latepoint' );
698 } else {
699 $status = LATEPOINT_STATUS_ERROR;
700 $response_html = __( 'Error Removing Order', 'latepoint' );
701 }
702 } else {
703 $status = LATEPOINT_STATUS_ERROR;
704 $response_html = __( 'Error Removing Order', 'latepoint' );
705 }
706 if ( $this->get_return_format() == 'json' ) {
707 $this->send_json(
708 array(
709 'status' => $status,
710 'message' => $response_html,
711 )
712 );
713 }
714 }
715
716 /*
717 Index of orders
718 */
719
720 public function index() {
721
722 $per_page = OsSettingsHelper::get_number_of_records_per_page();
723 $page_number = isset( $this->params['page_number'] ) ? $this->params['page_number'] : 1;
724
725 $this->vars['page_header'] = false;
726
727 $orders = new OsOrderModel();
728
729
730 // TABLE SEARCH FILTERS
731 $filter = $this->params['filter'] ?? false;
732 $query_args = [];
733 if ( $filter ) {
734 if ( ! empty( $filter['id'] ) ) {
735 $query_args['id'] = $filter['id'];
736 }
737 if ( ! empty( $filter['total'] ) ) {
738 $query_args['total'] = $filter['total'];
739 }
740 if ( ! empty( $filter['status'] ) ) {
741 $query_args['status'] = $filter['status'];
742 }
743 if ( ! empty( $filter['payment_status'] ) ) {
744 $query_args['payment_status'] = $filter['payment_status'];
745 }
746 if ( ! empty( $filter['fulfillment_status'] ) ) {
747 $query_args['fulfillment_status'] = $filter['fulfillment_status'];
748 }
749 if ( ! empty( $filter['confirmation_code'] ) ) {
750 $query_args['confirmation_code LIKE'] = '%' . $filter['confirmation_code'] . '%';
751 }
752
753 if ( ! empty( $filter['customer']['full_name'] ) ) {
754 $orders->select( LATEPOINT_TABLE_ORDERS . '.*, ' . LATEPOINT_TABLE_CUSTOMERS . '.first_name, ' . LATEPOINT_TABLE_CUSTOMERS . '.last_name' );
755 $orders->join( LATEPOINT_TABLE_CUSTOMERS, [ 'id' => LATEPOINT_TABLE_ORDERS . '.customer_id' ] );
756
757 $query_args[ 'concat_ws(" ", ' . LATEPOINT_TABLE_CUSTOMERS . '.first_name,' . LATEPOINT_TABLE_CUSTOMERS . '.last_name) LIKE' ] = '%' . $filter['customer']['full_name'] . '%';
758 $this->vars['customer_name_query'] = $filter['customer']['full_name'];
759
760 }
761
762 if ( ! empty( $filter['created_at_from'] ) && ! empty( $filter['created_at_to'] ) ) {
763 $query_args['created_at >='] = $filter['created_at_from'] . ' 00:00:00';
764 $query_args['created_at <='] = $filter['created_at_to'] . ' 23:59:59';
765 }
766 }
767
768
769 // OUTPUT CSV IF REQUESTED
770 if ( isset( $this->params['download'] ) && $this->params['download'] == 'csv' ) {
771 $csv_filename = 'payments_' . OsUtilHelper::random_text() . '.csv';
772
773 header( 'Content-Type: text/csv' );
774 header( "Content-Disposition: attachment; filename={$csv_filename}" );
775
776 $labels_row = [
777 __( 'ID', 'latepoint' ),
778 __( 'Token', 'latepoint' ),
779 __( 'Booking ID', 'latepoint' ),
780 __( 'Customer', 'latepoint' ),
781 __( 'Processor', 'latepoint' ),
782 __( 'Method', 'latepoint' ),
783 __( 'Amount', 'latepoint' ),
784 __( 'Status', 'latepoint' ),
785 __( 'Type', 'latepoint' ),
786 __( 'Date', 'latepoint' ),
787 ];
788
789
790 $orders_data = [];
791 $orders_data[] = $labels_row;
792
793
794 $orders_arr = $orders->where( $query_args )->filter_allowed_records()->get_results_as_models();
795
796 if ( $orders_arr ) {
797 foreach ( $orders_arr as $order ) {
798 $values_row = [
799 $order->id,
800 $order->token,
801 $order->booking_id,
802 ( $order->customer_id ? $order->customer->full_name : 'n/a' ),
803 $order->processor,
804 $order->payment_method,
805 OsMoneyHelper::format_price( $order->amount, true, false ),
806 $order->status,
807 $order->kind,
808 $order->created_at,
809 ];
810 $values_row = apply_filters( 'latepoint_order_row_for_csv_export', $values_row, $order, $this->params );
811 $orders_data[] = $values_row;
812 }
813 }
814
815 $orders_data = apply_filters( 'latepoint_orders_data_for_csv_export', $orders_data, $this->params );
816 OsCSVHelper::array_to_csv( $orders_data );
817
818 return;
819 }
820
821 if ( $query_args ) {
822 $orders->where( $query_args );
823 }
824 $orders->filter_allowed_records();
825
826
827 $count_orders = clone $orders;
828 $total_orders = $count_orders->count();
829
830 $orders = $orders->order_by( LATEPOINT_TABLE_ORDERS . '.created_at desc' )->set_limit( $per_page );
831 if ( $page_number > 1 ) {
832 $orders = $orders->set_offset( ( $page_number - 1 ) * $per_page );
833 }
834
835 $this->vars['orders'] = $orders->get_results_as_models();
836
837 $this->vars['total_orders'] = $total_orders;
838 $this->vars['current_page_number'] = $page_number;
839 $this->vars['per_page'] = $per_page;
840 $total_pages = ceil( $total_orders / $per_page );
841 $this->vars['total_pages'] = $total_pages;
842
843 $this->vars['showing_from'] = ( ( $page_number - 1 ) * $per_page ) ? ( ( $page_number - 1 ) * $per_page ) : 1;
844 $this->vars['showing_to'] = min( $page_number * $per_page, $total_orders );
845
846 $this->format_render(
847 [
848 'json_view_name' => '_table_body',
849 'html_view_name' => __FUNCTION__,
850 ],
851 [],
852 [
853 'total_pages' => $total_pages,
854 'showing_from' => $this->vars['showing_from'],
855 'showing_to' => $this->vars['showing_to'],
856 'total_records' => $total_orders,
857 ]
858 );
859 }
860 }
861
862
863 endif;
864