Merge pull request #22329 from woocommerce/enhancement-manual-stock-changes/21754

Product stock adjustments with manual order line item changes
This commit is contained in:
Mike Jolley 2019-02-19 10:00:49 +00:00 committed by GitHub
commit 5397e4ec1a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 283 additions and 101 deletions

View File

@ -560,6 +560,13 @@ jQuery( function ( $ ) {
if ( response.success ) {
$( '#woocommerce-order-items' ).find( '.inside' ).empty();
$( '#woocommerce-order-items' ).find( '.inside' ).append( response.data.html );
// Update notes.
if ( response.data.notes_html ) {
$( 'ul.order_notes' ).empty();
$( 'ul.order_notes' ).append( $( response.data.notes_html ).find( 'li' ) );
}
wc_meta_boxes_order_items.reloaded_items();
wc_meta_boxes_order_items.unblock();
} else {
@ -641,7 +648,7 @@ jQuery( function ( $ ) {
items: $( 'table.woocommerce_order_items :input[name], .wc-order-totals-items :input[name]' ).serialize(),
security: woocommerce_admin_meta_boxes.calc_totals_nonce
} );
$( document.body ).trigger( 'order-totals-recalculate-before', data );
$.ajax({
@ -680,10 +687,22 @@ jQuery( function ( $ ) {
data: data,
type: 'POST',
success: function( response ) {
$( '#woocommerce-order-items' ).find( '.inside' ).empty();
$( '#woocommerce-order-items' ).find( '.inside' ).append( response );
wc_meta_boxes_order_items.reloaded_items();
wc_meta_boxes_order_items.unblock();
if ( response.success ) {
$( '#woocommerce-order-items' ).find( '.inside' ).empty();
$( '#woocommerce-order-items' ).find( '.inside' ).append( response.data.html );
// Update notes.
if ( response.data.notes_html ) {
$( 'ul.order_notes' ).empty();
$( 'ul.order_notes' ).append( $( response.data.notes_html ).find( 'li' ) );
}
wc_meta_boxes_order_items.reloaded_items();
wc_meta_boxes_order_items.unblock();
} else {
wc_meta_boxes_order_items.unblock();
window.alert( response.data.error );
}
}
});
@ -978,6 +997,13 @@ jQuery( function ( $ ) {
if ( response.success ) {
$( '#woocommerce-order-items' ).find( '.inside' ).empty();
$( '#woocommerce-order-items' ).find( '.inside' ).append( response.data.html );
// Update notes.
if ( response.data.notes_html ) {
$( 'ul.order_notes' ).empty();
$( 'ul.order_notes' ).append( $( response.data.notes_html ).find( 'li' ) );
}
wc_meta_boxes_order_items.reloaded_items();
wc_meta_boxes_order_items.unblock();
} else {

View File

@ -2,14 +2,11 @@
/**
* Order Notes
*
* @author WooThemes
* @category Admin
* @package WooCommerce/Admin/Meta Boxes
* @version 2.1.0
* @package WooCommerce/Admin/Meta Boxes
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
exit;
}
/**
@ -20,7 +17,7 @@ class WC_Meta_Box_Order_Notes {
/**
* Output the metabox.
*
* @param WP_Post $post
* @param WP_Post $post Post object.
*/
public static function output( $post ) {
global $post;
@ -31,52 +28,20 @@ class WC_Meta_Box_Order_Notes {
$notes = wc_get_order_notes( $args );
echo '<ul class="order_notes">';
if ( $notes ) {
foreach ( $notes as $note ) {
$note_classes = array( 'note' );
$note_classes[] = $note->customer_note ? 'customer-note' : '';
$note_classes[] = 'system' === $note->added_by ? 'system-note' : '';
$note_classes = apply_filters( 'woocommerce_order_note_class', array_filter( $note_classes ), $note );
?>
<li rel="<?php echo absint( $note->id ); ?>" class="<?php echo esc_attr( implode( ' ', $note_classes ) ); ?>">
<div class="note_content">
<?php echo wpautop( wptexturize( wp_kses_post( make_clickable( $note->content ) ) ) ); ?>
</div>
<p class="meta">
<abbr class="exact-date" title="<?php echo $note->date_created->date( 'y-m-d h:i:s' ); ?>"><?php printf( __( 'added on %1$s at %2$s', 'woocommerce' ), $note->date_created->date_i18n( wc_date_format() ), $note->date_created->date_i18n( wc_time_format() ) ); ?></abbr>
<?php
if ( 'system' !== $note->added_by ) :
/* translators: %s: note author */
printf( ' ' . __( 'by %s', 'woocommerce' ), $note->added_by );
endif;
?>
<a href="#" class="delete_note" role="button"><?php _e( 'Delete note', 'woocommerce' ); ?></a>
</p>
</li>
<?php
}
} else {
echo '<li>' . __( 'There are no notes yet.', 'woocommerce' ) . '</li>';
}
echo '</ul>';
include 'views/html-order-notes.php';
?>
<div class="add_note">
<p>
<label for="add_order_note"><?php _e( 'Add note', 'woocommerce' ); ?> <?php echo wc_help_tip( __( 'Add a note for your reference, or add a customer note (the user will be notified).', 'woocommerce' ) ); ?></label>
<label for="add_order_note"><?php esc_html_e( 'Add note', 'woocommerce' ); ?> <?php echo wc_help_tip( __( 'Add a note for your reference, or add a customer note (the user will be notified).', 'woocommerce' ) ); ?></label>
<textarea type="text" name="order_note" id="add_order_note" class="input-text" cols="20" rows="5"></textarea>
</p>
<p>
<label for="order_note_type" class="screen-reader-text"><?php _e( 'Note type', 'woocommerce' ); ?></label>
<label for="order_note_type" class="screen-reader-text"><?php esc_html_e( 'Note type', 'woocommerce' ); ?></label>
<select name="order_note_type" id="order_note_type">
<option value=""><?php _e( 'Private note', 'woocommerce' ); ?></option>
<option value="customer"><?php _e( 'Note to customer', 'woocommerce' ); ?></option>
<option value=""><?php esc_html_e( 'Private note', 'woocommerce' ); ?></option>
<option value="customer"><?php esc_html_e( 'Note to customer', 'woocommerce' ); ?></option>
</select>
<button type="button" class="add_note button"><?php _e( 'Add', 'woocommerce' ); ?></button>
<button type="button" class="add_note button"><?php esc_html_e( 'Add', 'woocommerce' ); ?></button>
</p>
</div>
<?php

View File

@ -0,0 +1,48 @@
<?php
/**
* Order notes HTML for meta box.
*
* @package WooCommerce/Admin
*/
defined( 'ABSPATH' ) || exit;
?>
<ul class="order_notes">
<?php
if ( $notes ) {
foreach ( $notes as $note ) {
$css_class = array( 'note' );
$css_class[] = $note->customer_note ? 'customer-note' : '';
$css_class[] = 'system' === $note->added_by ? 'system-note' : '';
$css_class = apply_filters( 'woocommerce_order_note_class', array_filter( $css_class ), $note );
?>
<li rel="<?php echo absint( $note->id ); ?>" class="<?php echo esc_attr( implode( ' ', $css_class ) ); ?>">
<div class="note_content">
<?php echo wpautop( wptexturize( wp_kses_post( $note->content ) ) ); // @codingStandardsIgnoreLine ?>
</div>
<p class="meta">
<abbr class="exact-date" title="<?php echo esc_attr( $note->date_created->date( 'y-m-d h:i:s' ) ); ?>">
<?php
/* translators: %1$s: note date %2$s: note time */
echo esc_html( sprintf( __( '%1$s at %2$s', 'woocommerce' ), $note->date_created->date_i18n( wc_date_format() ), $note->date_created->date_i18n( wc_time_format() ) ) );
?>
</abbr>
<?php
if ( 'system' !== $note->added_by ) :
/* translators: %s: note author */
echo esc_html( sprintf( ' ' . __( 'by %s', 'woocommerce' ), $note->added_by ) );
endif;
?>
<a href="#" class="delete_note" role="button"><?php esc_html_e( 'Delete note', 'woocommerce' ); ?></a>
</p>
</li>
<?php
}
} else {
?>
<li><?php esc_html_e( 'There are no notes yet.', 'woocommerce' ); ?></li>
<?php
}
?>
</ul>

View File

@ -2,14 +2,12 @@
/**
* WooCommerce Admin Functions
*
* @author WooThemes
* @category Core
* @package WooCommerce/Admin/Functions
* @version 2.4.0
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
exit;
}
/**
@ -46,7 +44,9 @@ function wc_get_screen_ids() {
$screen_ids[] = 'edit-' . $type;
}
if ( $attributes = wc_get_attribute_taxonomies() ) {
$attributes = wc_get_attribute_taxonomies();
if ( $attributes ) {
foreach ( $attributes as $attribute ) {
$screen_ids[] = 'edit-' . wc_attribute_taxonomy_name( $attribute->attribute_name );
}
@ -58,21 +58,23 @@ function wc_get_screen_ids() {
/**
* Create a page and store the ID in an option.
*
* @param mixed $slug Slug for the new page
* @param string $option Option name to store the page's ID
* @param string $page_title (default: '') Title for the new page
* @param string $page_content (default: '') Content for the new page
* @param int $post_parent (default: 0) Parent for the new page
* @return int page ID
* @param mixed $slug Slug for the new page.
* @param string $option Option name to store the page's ID.
* @param string $page_title (default: '') Title for the new page.
* @param string $page_content (default: '') Content for the new page.
* @param int $post_parent (default: 0) Parent for the new page.
* @return int page ID.
*/
function wc_create_page( $slug, $option = '', $page_title = '', $page_content = '', $post_parent = 0 ) {
global $wpdb;
$option_value = get_option( $option );
if ( $option_value > 0 && ( $page_object = get_post( $option_value ) ) ) {
if ( 'page' === $page_object->post_type && ! in_array( $page_object->post_status, array( 'pending', 'trash', 'future', 'auto-draft' ) ) ) {
// Valid page is already in place
if ( $option_value > 0 ) {
$page_object = get_post( $option_value );
if ( $page_object && 'page' === $page_object->post_type && ! in_array( $page_object->post_status, array( 'pending', 'trash', 'future', 'auto-draft' ), true ) ) {
// Valid page is already in place.
return $page_object->ID;
}
}
@ -82,7 +84,7 @@ function wc_create_page( $slug, $option = '', $page_title = '', $page_content =
$shortcode = str_replace( array( '<!-- wp:shortcode -->', '<!-- /wp:shortcode -->' ), '', $page_content );
$valid_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status NOT IN ( 'pending', 'trash', 'future', 'auto-draft' ) AND post_content LIKE %s LIMIT 1;", "%{$shortcode}%" ) );
} else {
// Search for an existing page with the specified page slug
// Search for an existing page with the specified page slug.
$valid_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status NOT IN ( 'pending', 'trash', 'future', 'auto-draft' ) AND post_name = %s LIMIT 1;", $slug ) );
}
@ -95,12 +97,12 @@ function wc_create_page( $slug, $option = '', $page_title = '', $page_content =
return $valid_page_found;
}
// Search for a matching valid trashed page
// Search for a matching valid trashed page.
if ( strlen( $page_content ) > 0 ) {
// Search for an existing page with the specified page content (typically a shortcode)
// Search for an existing page with the specified page content (typically a shortcode).
$trashed_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status = 'trash' AND post_content LIKE %s LIMIT 1;", "%{$page_content}%" ) );
} else {
// Search for an existing page with the specified page slug
// Search for an existing page with the specified page slug.
$trashed_page_found = $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type='page' AND post_status = 'trash' AND post_name = %s LIMIT 1;", $slug ) );
}
@ -137,7 +139,7 @@ function wc_create_page( $slug, $option = '', $page_title = '', $page_content =
*
* Loops though the woocommerce options array and outputs each field.
*
* @param array $options Opens array to output
* @param array $options Opens array to output.
*/
function woocommerce_admin_fields( $options ) {
@ -151,8 +153,8 @@ function woocommerce_admin_fields( $options ) {
/**
* Update all settings which are passed.
*
* @param array $options
* @param array $data
* @param array $options Option fields to save.
* @param array $data Passed data.
*/
function woocommerce_update_options( $options, $data = null ) {
@ -166,8 +168,8 @@ function woocommerce_update_options( $options, $data = null ) {
/**
* Get a setting from the settings API.
*
* @param mixed $option_name
* @param mixed $default
* @param mixed $option_name Option name to save.
* @param mixed $default Default value to save.
* @return string
*/
function woocommerce_settings_get_option( $option_name, $default = '' ) {
@ -179,17 +181,61 @@ function woocommerce_settings_get_option( $option_name, $default = '' ) {
return WC_Admin_Settings::get_option( $option_name, $default );
}
/**
* Sees if line item stock has already reduced stock, and whether those values need adjusting e.g. after changing item qty.
*
* @since 3.6.0
* @param WC_Order_Item $item Item object.
* @param integer $item_quantity Optional quantity to check against. Read from object if not passed.
* @return boolean|array|WP_Error Array of changes or error object when stock is updated (@see wc_update_product_stock). False if nothing changes.
*/
function wc_maybe_adjust_line_item_product_stock( $item, $item_quantity = -1 ) {
if ( 'line_item' !== $item->get_type() ) {
return false;
}
$product = $item->get_product();
$item_quantity = wc_stock_amount( $item_quantity >= 0 ? $item_quantity : $item->get_quantity() );
$already_reduced_stock = wc_stock_amount( $item->get_meta( '_reduced_stock', true ) );
if ( ! $product || ! $product->managing_stock() || ! $already_reduced_stock || $item_quantity === $already_reduced_stock ) {
return false;
}
$diff = $item_quantity - $already_reduced_stock;
if ( $diff < 0 ) {
$new_stock = wc_update_product_stock( $product, $diff * -1, 'increase' );
} else {
$new_stock = wc_update_product_stock( $product, $diff, 'decrease' );
}
if ( is_wp_error( $new_stock ) ) {
return $new_stock;
}
$item->update_meta_data( '_reduced_stock', $item_quantity );
$item->save();
return array(
'from' => $new_stock + $diff,
'to' => $new_stock,
);
}
/**
* Save order items. Uses the CRUD.
*
* @since 2.2
* @param int $order_id Order ID
* @param array $items Order items to save
* @param int $order_id Order ID.
* @param array $items Order items to save.
*/
function wc_save_order_items( $order_id, $items ) {
// Allow other plugins to check change in order items before they are saved.
do_action( 'woocommerce_before_save_order_items', $order_id, $items );
$qty_change_order_notes = array();
// Line items and fees.
if ( isset( $items['order_item_id'] ) ) {
$data_keys = array(
@ -202,7 +248,9 @@ function wc_save_order_items( $order_id, $items ) {
'line_subtotal' => null,
);
foreach ( $items['order_item_id'] as $item_id ) {
if ( ! $item = WC_Order_Factory::get_order_item( absint( $item_id ) ) ) {
$item = WC_Order_Factory::get_order_item( absint( $item_id ) );
if ( ! $item ) {
continue;
}
@ -213,6 +261,10 @@ function wc_save_order_items( $order_id, $items ) {
}
if ( '0' === $item_data['order_item_qty'] ) {
$changed_stock = wc_maybe_adjust_line_item_product_stock( $item, 0 );
if ( $changed_stock && ! is_wp_error( $changed_stock ) ) {
$qty_change_order_notes[] = $item->get_name() . ' &ndash; ' . $changed_stock['from'] . '&rarr;' . $changed_stock['to'];
}
$item->delete();
continue;
}
@ -256,10 +308,15 @@ function wc_save_order_items( $order_id, $items ) {
do_action( 'woocommerce_before_save_order_item', $item );
$item->save();
$changed_stock = wc_maybe_adjust_line_item_product_stock( $item );
if ( $changed_stock && ! is_wp_error( $changed_stock ) ) {
$qty_change_order_notes[] = $item->get_name() . ' (' . $changed_stock['from'] . '&rarr;' . $changed_stock['to'] . ')';
}
}
}
// Shipping Rows
// Shipping Rows.
if ( isset( $items['shipping_method_id'] ) ) {
$data_keys = array(
'shipping_method' => null,
@ -269,7 +326,9 @@ function wc_save_order_items( $order_id, $items ) {
);
foreach ( $items['shipping_method_id'] as $item_id ) {
if ( ! $item = WC_Order_Factory::get_order_item( absint( $item_id ) ) ) {
$item = WC_Order_Factory::get_order_item( absint( $item_id ) );
if ( ! $item ) {
continue;
}
@ -311,10 +370,16 @@ function wc_save_order_items( $order_id, $items ) {
}
$order = wc_get_order( $order_id );
if ( ! empty( $qty_change_order_notes ) ) {
/* translators: %s item name. */
$order->add_order_note( sprintf( __( 'Adjusted stock: %s', 'woocommerce' ), implode( ', ', $qty_change_order_notes ) ), false, true );
}
$order->update_taxes();
$order->calculate_totals( false );
// Inform other plugins that the items have been saved
// Inform other plugins that the items have been saved.
do_action( 'woocommerce_saved_order_items', $order_id, $items );
}

View File

@ -842,7 +842,7 @@ class WC_AJAX {
}
/**
* Add order item via ajax.
* Add order item via ajax. Used on the edit order screen in WP Admim.
*/
public static function add_order_item() {
check_ajax_referer( 'order-item', 'security' );
@ -858,26 +858,25 @@ class WC_AJAX {
throw new Exception( __( 'Invalid order', 'woocommerce' ) );
}
$order_id = absint( wp_unslash( $_POST['order_id'] ) ); // WPCS: input var ok.
$order = wc_get_order( $order_id );
$order_id = absint( wp_unslash( $_POST['order_id'] ) ); // WPCS: input var ok.
$order = wc_get_order( $order_id );
if ( ! $order ) {
throw new Exception( __( 'Invalid order', 'woocommerce' ) );
}
// If we passed through items it means we need to save first before adding a new one.
$items = ( ! empty( $_POST['items'] ) ) ? $_POST['items'] : '';
if ( ! empty( $items ) ) {
if ( ! empty( $_POST['items'] ) ) {
$save_items = array();
parse_str( $items, $save_items );
// Save order items.
parse_str( wp_unslash( $_POST['items'] ), $save_items );
wc_save_order_items( $order->get_id(), $save_items );
}
$items_to_add = array_filter( wp_unslash( (array) $_POST['data'] ) );
// Add items to order.
$order_notes = array();
foreach ( $items_to_add as $item ) {
if ( ! isset( $item['id'], $item['qty'] ) || empty( $item['id'] ) ) {
continue;
@ -893,17 +892,41 @@ class WC_AJAX {
$item_id = $order->add_product( $product, $qty );
$item = apply_filters( 'woocommerce_ajax_order_item', $order->get_item( $item_id ), $item_id );
$added_items[ $item_id ] = $item;
$order_notes[ $item_id ] = $product->get_formatted_name();
if ( $product->managing_stock() ) {
$new_stock = wc_update_product_stock( $product, $qty, 'decrease' );
$order_notes[ $item_id ] = $product->get_formatted_name() . ' &ndash; ' . ( $new_stock + $qty ) . '&rarr;' . $new_stock;
$item->add_meta_data( '_reduced_stock', $qty, true );
$item->save();
}
do_action( 'woocommerce_ajax_add_order_item_meta', $item_id, $item, $order );
}
/* translators: %s item name. */
$order->add_order_note( sprintf( __( 'Added line items: %s', 'woocommerce' ), implode( ', ', $order_notes ) ), false, true );
do_action( 'woocommerce_ajax_order_items_added', $added_items, $order );
$data = get_post_meta( $order_id );
// Get HTML to return.
ob_start();
include 'admin/meta-boxes/views/html-order-items.php';
$response['html'] = ob_get_clean();
$items_html = ob_get_clean();
ob_start();
$notes = wc_get_order_notes( array( 'order_id' => $order_id ) );
include 'admin/meta-boxes/views/html-order-notes.php';
$notes_html = ob_get_clean();
wp_send_json_success(
array(
'html' => $items_html,
'notes_html' => $notes_html,
)
);
} catch ( Exception $e ) {
wp_send_json_error( array( 'error' => $e->getMessage() ) );
}
@ -1114,14 +1137,23 @@ class WC_AJAX {
$response = array();
try {
$order_id = absint( $_POST['order_id'] );
$order_item_ids = $_POST['order_item_ids'];
$items = ( ! empty( $_POST['items'] ) ) ? $_POST['items'] : '';
$order_id = absint( $_POST['order_id'] );
$order = wc_get_order( $order_id );
if ( ! $order ) {
throw new Exception( __( 'Invalid order', 'woocommerce' ) );
}
if ( ! isset( $_POST['order_item_ids'] ) ) {
throw new Exception( __( 'Invalid items', 'woocommerce' ) );
}
$order_item_ids = wp_unslash( $_POST['order_item_ids'] );
$calculate_tax_args = array(
'country' => strtoupper( wc_clean( $_POST['country'] ) ),
'state' => strtoupper( wc_clean( $_POST['state'] ) ),
'postcode' => strtoupper( wc_clean( $_POST['postcode'] ) ),
'city' => strtoupper( wc_clean( $_POST['city'] ) ),
'country' => strtoupper( wc_clean( wp_unslash( $_POST['country'] ) ) ),
'state' => strtoupper( wc_clean( wp_unslash( $_POST['state'] ) ) ),
'postcode' => strtoupper( wc_clean( wp_unslash( $_POST['postcode'] ) ) ),
'city' => strtoupper( wc_clean( wp_unslash( $_POST['city'] ) ) ),
);
if ( ! is_array( $order_item_ids ) && is_numeric( $order_item_ids ) ) {
@ -1129,16 +1161,33 @@ class WC_AJAX {
}
// If we passed through items it means we need to save first before deleting.
if ( ! empty( $items ) ) {
if ( ! empty( $_POST['items'] ) ) {
$save_items = array();
parse_str( $items, $save_items );
// Save order items
wc_save_order_items( $order_id, $save_items );
parse_str( wp_unslash( $_POST['items'] ), $save_items );
wc_save_order_items( $order->get_id(), $save_items );
}
if ( sizeof( $order_item_ids ) > 0 ) {
foreach ( $order_item_ids as $id ) {
wc_delete_order_item( absint( $id ) );
if ( ! empty( $order_item_ids ) ) {
$order_notes = array();
foreach ( $order_item_ids as $item_id ) {
$item_id = absint( $item_id );
$item = $order->get_item( $item_id );
// Before deleting the item, adjust any stock values already reduced.
if ( $item->is_type( 'line_item' ) ) {
$changed_stock = wc_maybe_adjust_line_item_product_stock( $item, 0 );
if ( $changed_stock && ! is_wp_error( $changed_stock ) ) {
/* translators: %1$s: item name %2$s: stock change */
$order->add_order_note( sprintf( __( 'Deleted %1$s and adjusted stock (%2$s)', 'woocommerce' ), $item->get_name(), $changed_stock['from'] . '&rarr;' . $changed_stock['to'] ), false, true );
} else {
/* translators: %s item name. */
$order->add_order_note( sprintf( __( 'Deleted %s', 'woocommerce' ), $item->get_name() ), false, true );
}
}
wc_delete_order_item( $item_id );
}
}
@ -1146,9 +1195,22 @@ class WC_AJAX {
$order->calculate_taxes( $calculate_tax_args );
$order->calculate_totals( false );
// Get HTML to return.
ob_start();
include 'admin/meta-boxes/views/html-order-items.php';
$response['html'] = ob_get_clean();
$items_html = ob_get_clean();
ob_start();
$notes = wc_get_order_notes( array( 'order_id' => $order_id ) );
include 'admin/meta-boxes/views/html-order-notes.php';
$notes_html = ob_get_clean();
wp_send_json_success(
array(
'html' => $items_html,
'notes_html' => $notes_html,
)
);
} catch ( Exception $e ) {
wp_send_json_error( array( 'error' => $e->getMessage() ) );
}
@ -1244,7 +1306,23 @@ class WC_AJAX {
// Return HTML items
$order = wc_get_order( $order_id );
// Get HTML to return.
ob_start();
include 'admin/meta-boxes/views/html-order-items.php';
$items_html = ob_get_clean();
ob_start();
$notes = wc_get_order_notes( array( 'order_id' => $order_id ) );
include 'admin/meta-boxes/views/html-order-notes.php';
$notes_html = ob_get_clean();
wp_send_json_success(
array(
'html' => $items_html,
'notes_html' => $notes_html,
)
);
}
wp_die();
}