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