Add/Remove order coupon actions logged in notes (#30642)

* log admin coupon code actions

* added documentation, fixed return value

* formatting

* included user to note logs

* Add changelog

* Address PHPCS issues

* Make WC_Abstract_Order::remove_coupon() return a bool

* Move addition of coupon-related order notes from WC_Order to AJAX callbacks

* Address PHPCS problems

* Pass coupon order notes through `esc_html()`

* Add AJAX update of notes when adding/removing coupons via admin

* Fix tests (maybe?)

---------

Co-authored-by: Jorge A. Torres <jorge.torres@automattic.com>
This commit is contained in:
witlock 2023-03-15 18:33:46 +02:00 committed by GitHub
parent 562fede1e2
commit c8f7a564c5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 50 additions and 10 deletions

View File

@ -0,0 +1,4 @@
Significance: minor
Type: add
Log to order notes when coupons are removed or applied.

View File

@ -488,6 +488,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 {
@ -524,6 +531,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

@ -1285,9 +1285,11 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
* Manual discounts are not affected; those are separate and do not affect
* stored line totals.
*
* @since 3.2.0
* @since 3.2.0
* @since 7.6.0 Returns a boolean indicating success.
*
* @param string $code Coupon code.
* @return void
* @return bool TRUE if coupon was removed, FALSE otherwise.
*/
public function remove_coupon( $code ) {
$coupons = $this->get_items( 'coupon' );
@ -1299,9 +1301,12 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
$coupon_object = new WC_Coupon( $code );
$coupon_object->decrease_usage_count( $this->get_user_id() );
$this->recalculate_coupons();
break;
return true;
}
}
return false;
}
/**

View File

@ -1255,13 +1255,23 @@ class WC_AJAX {
throw new Exception( __( 'Invalid coupon', 'woocommerce' ) );
}
$order->remove_coupon( wc_format_coupon_code( wp_unslash( $coupon ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$code = wc_format_coupon_code( wp_unslash( $coupon ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
if ( $order->remove_coupon( $code ) ) {
// translators: %s coupon code.
$order->add_order_note( esc_html( sprintf( __( 'Coupon removed: "%s".', 'woocommerce' ), $code ) ), 0, true );
}
$order->calculate_taxes( $calculate_tax_args );
$order->calculate_totals( false );
ob_start();
include __DIR__ . '/admin/meta-boxes/views/html-order-items.php';
$response['html'] = ob_get_clean();
ob_start();
$notes = wc_get_order_notes( array( 'order_id' => $order_id ) );
include __DIR__ . '/admin/meta-boxes/views/html-order-notes.php';
$response['notes_html'] = ob_get_clean();
} catch ( Exception $e ) {
wp_send_json_error( array( 'error' => $e->getMessage() ) );
}

View File

@ -31,6 +31,11 @@ class CouponsController {
ob_start();
include __DIR__ . '/../../../includes/admin/meta-boxes/views/html-order-items.php';
$response['html'] = ob_get_clean();
ob_start();
$notes = wc_get_order_notes( array( 'order_id' => $order->get_id() ) );
include __DIR__ . '/../../../includes/admin/meta-boxes/views/html-order-notes.php';
$response['notes_html'] = ob_get_clean();
} catch ( Exception $e ) {
wp_send_json_error( array( 'error' => $e->getMessage() ) );
}
@ -79,12 +84,16 @@ class CouponsController {
$order->calculate_taxes( $calculate_tax_args );
$order->calculate_totals( false );
$result = $order->apply_coupon( wc_format_coupon_code( wp_unslash( $coupon ) ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$code = wc_format_coupon_code( wp_unslash( $coupon ) ); // phpcs:ignore WordPress.Security.NonceVerification.Missing, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$result = $order->apply_coupon( $code );
if ( is_wp_error( $result ) ) {
throw new Exception( html_entity_decode( wp_strip_all_tags( $result->get_error_message() ) ) );
}
// translators: %s coupon code.
$order->add_order_note( esc_html( sprintf( __( 'Coupon applied: "%s".', 'woocommerce' ), $code ) ), 0, true );
return $order;
}
}

View File

@ -95,7 +95,7 @@ test.describe( 'WooCommerce Orders > Apply Coupon', () => {
page.on( 'dialog', ( dialog ) => dialog.accept( couponCode ) );
await page.click( 'button.add-coupon' );
await expect( page.locator( `text=${ couponCode }` ) ).toBeVisible();
await expect( page.locator( '.wc_coupon_list li', { hasText: couponCode } ) ).toBeVisible();
await expect(
page.locator( '.wc-order-totals td.label >> nth=1' )
).toContainText( 'Coupon(s)' );
@ -113,7 +113,7 @@ test.describe( 'WooCommerce Orders > Apply Coupon', () => {
test( 'can remove a coupon', async ( { page } ) => {
await page.goto( `/wp-admin/post.php?post=${ orderId }&action=edit` );
// assert that there is a coupon on the order
await expect( page.locator( `text=${ couponCode }` ) ).toBeVisible();
await expect( page.locator( '.wc_coupon_list li', { hasText: couponCode } ) ).toBeVisible();
await expect(
page.locator( '.wc-order-totals td.label >> nth=1' )
).toContainText( 'Coupon(s)' );
@ -130,9 +130,7 @@ test.describe( 'WooCommerce Orders > Apply Coupon', () => {
await page.dispatchEvent( 'a.remove-coupon', 'click' ); // have to use dispatchEvent because nothing visible to click on
// make sure the coupon was removed
await expect(
page.locator( `text=${ couponCode }` )
).not.toBeVisible();
await expect( page.locator( '.wc_coupon_list li', { hasText: couponCode } ) ).not.toBeVisible();
await expect(
page.locator( '.wc-order-totals td.label >> nth=1' )
).toContainText( 'Order Total' );