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