From f08ccc57d7f8b0dad87a4f9380113b7191cd020e Mon Sep 17 00:00:00 2001 From: Adrian Duffell <9312929+adrianduffell@users.noreply.github.com> Date: Thu, 1 Oct 2020 20:09:04 +0800 Subject: [PATCH] Remove customer analytics data upon order deletion (https://github.com/woocommerce/woocommerce-admin/pull/5171) This deletes the associated customer record when an order is deleted, provided that this is the only order for the customer. --- .../src/API/Reports/Customers/DataStore.php | 53 ++++++++ .../API/Reports/Orders/Stats/DataStore.php | 9 +- .../class-wc-tests-reports-customers.php | 118 ++++++++++++++++++ 3 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-customers.php diff --git a/plugins/woocommerce-admin/src/API/Reports/Customers/DataStore.php b/plugins/woocommerce-admin/src/API/Reports/Customers/DataStore.php index 9bb0680e02e..8f7de72e676 100644 --- a/plugins/woocommerce-admin/src/API/Reports/Customers/DataStore.php +++ b/plugins/woocommerce-admin/src/API/Reports/Customers/DataStore.php @@ -85,6 +85,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface { public static function init() { add_action( 'edit_user_profile_update', array( __CLASS__, 'update_registered_customer' ) ); add_action( 'updated_user_meta', array( __CLASS__, 'update_registered_customer_via_last_active' ), 10, 3 ); + add_action( 'woocommerce_analytics_delete_order_stats', array( __CLASS__, 'sync_on_order_delete' ), 15, 2 ); } /** @@ -101,6 +102,30 @@ class DataStore extends ReportsDataStore implements DataStoreInterface { } } + /** + * Sync customers data after an order was deleted. + * + * When an order is deleted, the customer record is deleted from the + * table if the customer has no other orders. + * + * @param int $order_id Order ID. + * @param int $customer_id Customer ID. + */ + public static function sync_on_order_delete( $order_id, $customer_id ) { + $customer_id = absint( $customer_id ); + + if ( 0 === $customer_id ) { + return; + } + + // Calculate the amount of orders remaining for this customer. + $order_count = self::get_order_count( $customer_id ); + + if ( 0 === $order_count ) { + self::delete_customer( $customer_id ); + } + } + /** * Maps ordering specified by the user to columns in the database/fields in the data. * @@ -572,6 +597,34 @@ class DataStore extends ReportsDataStore implements DataStoreInterface { ); } + /** + * Retrieve the amount of orders made by a customer. + * + * @param int $customer_id Customer ID. + * @return int|null Amount of orders for customer or null on failure. + */ + public static function get_order_count( $customer_id ) { + global $wpdb; + $customer_id = absint( $customer_id ); + + if ( 0 === $customer_id ) { + return null; + } + + $result = $wpdb->get_var( + $wpdb->prepare( + "SELECT COUNT( order_id ) FROM {$wpdb->prefix}wc_order_stats WHERE customer_id = %d", + $customer_id + ) + ); + + if ( is_null( $result ) ) { + return null; + } + + return (int) $result; + } + /** * Update the database with customer data. * diff --git a/plugins/woocommerce-admin/src/API/Reports/Orders/Stats/DataStore.php b/plugins/woocommerce-admin/src/API/Reports/Orders/Stats/DataStore.php index 239aed79616..7dabdf4fa5c 100644 --- a/plugins/woocommerce-admin/src/API/Reports/Orders/Stats/DataStore.php +++ b/plugins/woocommerce-admin/src/API/Reports/Orders/Stats/DataStore.php @@ -12,6 +12,7 @@ use \Automattic\WooCommerce\Admin\API\Reports\DataStoreInterface; use \Automattic\WooCommerce\Admin\API\Reports\TimeInterval; use \Automattic\WooCommerce\Admin\API\Reports\SqlQuery; use \Automattic\WooCommerce\Admin\API\Reports\Cache as ReportsCache; +use \Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore as CustomersDataStore; /** * API\Reports\Orders\Stats\DataStore. @@ -576,13 +577,19 @@ class DataStore extends ReportsDataStore implements DataStoreInterface { return; } + // Retrieve customer details before the order is deleted. + $order = wc_get_order( $order_id ); + $customer_id = absint( CustomersDataStore::get_existing_customer_id_from_order( $order ) ); + + // Delete the order. $wpdb->delete( self::get_db_table_name(), array( 'order_id' => $order_id ) ); /** * Fires when orders stats are deleted. * * @param int $order_id Order ID. + * @param int $customer_id Customer ID. */ - do_action( 'woocommerce_analytics_delete_order_stats', $order_id ); + do_action( 'woocommerce_analytics_delete_order_stats', $order_id, $customer_id ); ReportsCache::invalidate(); } diff --git a/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-customers.php b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-customers.php new file mode 100644 index 00000000000..107747baa30 --- /dev/null +++ b/plugins/woocommerce-admin/tests/reports/class-wc-tests-reports-customers.php @@ -0,0 +1,118 @@ +set_name( 'Test Product' ); + $product->set_regular_price( 25 ); + $product->save(); + + WC_Helper_Queue::run_all_pending(); + + $customer_id = DataStore::get_customer_id_by_user_id( $customer->get_id() ); // This is the customer ID from lookup table. + + // Create 3 orders. + foreach ( range( 1, 3 ) as $i ) { + $order = WC_Helper_Order::create_order( $customer->get_id(), $product ); + $order->save(); + } + + WC_Helper_Queue::run_all_pending(); + + // Customer should have 3 orders. + $this->assertSame( 3, DataStore::get_order_count( $customer_id ) ); + + // Failure from bad customer IDs. + $this->assertSame( null, DataStore::get_order_count( 0 ) ); + $this->assertSame( null, DataStore::get_order_count( 'ABC' ) ); + $this->assertSame( null, DataStore::get_order_count( false ) ); + $this->assertSame( null, DataStore::get_order_count( null ) ); + } + + /** + * Test customer lookup tables are cleaned after deleting an order. + * + * A customer record should only be deleted if the customer has no other orders. + * + * @covers \Automattic\WooCommerce\Admin\API\Reports\Customers\DataStore::sync_on_order_delete + */ + public function test_order_deletion_removes_customer() { + WC_Helper_Reports::reset_stats_dbs(); + + // Create a customer. + $customer = WC_Helper_Customer::create_customer(); + + // Create products. + $product1 = new WC_Product_Simple(); + $product1->set_name( 'Test Product 1' ); + $product1->set_regular_price( 1 ); + $product1->save(); + + $product2 = new WC_Product_Simple(); + $product2->set_name( 'Test Product 2' ); + $product2->set_regular_price( 2 ); + $product2->save(); + + WC_Helper_Queue::run_all_pending(); + + // Create the first order. + $order1 = WC_Helper_Order::create_order( $customer->get_id(), $product1 ); + $order1->save(); + + // Create the second order. + $order2 = WC_Helper_Order::create_order( $customer->get_id(), $product2 ); + $order2->save(); + + WC_Helper_Queue::run_all_pending(); + + $customer_id = DataStore::get_customer_id_by_user_id( $customer->get_id() ); // This is the customer ID from lookup table. + + // Customer should remain in lookup table after first order deleted. + $order1->delete( true ); + $this->assertCount( 1, $this->get_customer_record( $customer_id ), 'customer remains' ); + + // Customer should be removed in lookup table after both orders are deleted. + $order2->delete( true ); + $this->assertCount( 0, $this->get_customer_record( $customer_id ), 'customer removed' ); + } + + /** + * Get a customer's record from the database. + * + * @param int $customer_id Analytics Customer ID (not WP User ID). + */ + private function get_customer_record( $customer_id ) { + global $wpdb; + + $results = $wpdb->get_results( + $wpdb->prepare( + "SELECT * FROM {$wpdb->prefix}wc_customer_lookup WHERE customer_id = %d", + $customer_id + ) + ); + + return $results; + } +}