diff --git a/plugins/woocommerce/changelog/fix-refunds_backfill b/plugins/woocommerce/changelog/fix-refunds_backfill new file mode 100644 index 00000000000..c79dfc94309 --- /dev/null +++ b/plugins/woocommerce/changelog/fix-refunds_backfill @@ -0,0 +1,4 @@ +Significance: patch +Type: fix + +Use correct datastore when backfilling orders. diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php index 74069a01f2d..c3f031fe34f 100644 --- a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php +++ b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php @@ -539,6 +539,18 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order { return wc_string_to_bool( $this->get_prop( 'recorded_coupon_usage_counts', $context ) ); } + /** + * Get basic order data in array format. + * + * @return array + */ + public function get_base_data() { + return array_merge( + array( 'id' => $this->get_id() ), + $this->data + ); + } + /* |-------------------------------------------------------------------------- | Setters diff --git a/plugins/woocommerce/includes/class-wc-order-factory.php b/plugins/woocommerce/includes/class-wc-order-factory.php index 94e01d390e7..3d023d6a7fb 100644 --- a/plugins/woocommerce/includes/class-wc-order-factory.php +++ b/plugins/woocommerce/includes/class-wc-order-factory.php @@ -187,7 +187,7 @@ class WC_Order_Factory { * * @return array Array of order_id => class_name. */ - private static function get_class_names_for_order_ids( $order_ids ) { + public static function get_class_names_for_order_ids( $order_ids ) { $order_data_store = WC_Data_Store::load( 'order' ); if ( $order_data_store->has_callable( 'get_orders_type' ) ) { $order_types = $order_data_store->get_orders_type( $order_ids ); diff --git a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php index a3c11b4f816..7729691a62d 100644 --- a/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php +++ b/plugins/woocommerce/src/Internal/DataStores/Orders/OrdersTableDataStore.php @@ -497,9 +497,9 @@ class OrdersTableDataStore extends \Abstract_WC_Order_Data_Store_CPT implements * * @return \WC_Order_Data_Store_CPT Data store instance. */ - private function get_cpt_data_store_instance() { + public function get_cpt_data_store_instance() { if ( ! isset( $this->cpt_data_store ) ) { - $this->cpt_data_store = new \WC_Order_Data_Store_CPT(); + $this->cpt_data_store = $this->get_post_data_store_for_backfill(); } return $this->cpt_data_store; } @@ -985,7 +985,7 @@ WHERE $data_sync_enabled = $data_synchronizer->data_sync_is_enabled() && 0 === $data_synchronizer->get_current_orders_pending_sync_count_cached(); $load_posts_for = array_diff( $order_ids, self::$reading_order_ids ); - $post_orders = $data_sync_enabled ? $this->get_post_orders_for_ids( $load_posts_for ) : array(); + $post_orders = $data_sync_enabled ? $this->get_post_orders_for_ids( array_intersect_key( $orders, array_flip( $load_posts_for ) ) ) : array(); foreach ( $data as $order_data ) { $order_id = absint( $order_data->id ); @@ -1084,31 +1084,46 @@ WHERE /** * Helper function to get posts data for an order in bullk. We use to this to compute posts object in bulk so that we can compare it with COT data. * - * @param array $order_ids List of order IDs. + * @param array $orders List of orders mapped by $order_id. * * @return array List of posts. */ - private function get_post_orders_for_ids( array $order_ids ): array { - $cpt_data_store = $this->get_cpt_data_store_instance(); + private function get_post_orders_for_ids( array $orders ): array { + $order_ids = array_keys( $orders ); // We have to bust meta cache, otherwise we will just get the meta cached by OrderTableDataStore. foreach ( $order_ids as $order_id ) { wp_cache_delete( WC_Order::generate_meta_cache_key( $order_id, 'orders' ), 'orders' ); } - $query_vars = array( - 'include' => $order_ids, - 'type' => wc_get_order_types(), - 'status' => 'any', - 'limit' => count( $order_ids ), - ); - $cpt_data_store->prime_caches_for_orders( $order_ids, $query_vars ); - $orders = array(); - foreach ( $order_ids as $order_id ) { - $order = new WC_Order(); - $order->set_id( $order_id ); - $cpt_data_store->read( $order ); - $orders[ $order_id ] = $order; + + $cpt_stores = array(); + $cpt_store_orders = array(); + foreach ( $orders as $order_id => $order ) { + $table_data_store = $order->get_data_store(); + $cpt_data_store = $table_data_store->get_cpt_data_store_instance(); + $cpt_store_class_name = get_class( $cpt_data_store ); + if ( ! isset( $cpt_stores[ $cpt_store_class_name ] ) ) { + $cpt_stores[ $cpt_store_class_name ] = $cpt_data_store; + $cpt_store_orders[ $cpt_store_class_name ] = array(); + } + $cpt_store_orders[ $cpt_store_class_name ][ $order_id ] = $order; } - return $orders; + + $cpt_orders = array(); + foreach ( $cpt_stores as $cpt_store_name => $cpt_store ) { + // Prime caches if we can. + if ( method_exists( $cpt_store, 'prime_caches_for_orders' ) ) { + $cpt_store->prime_caches_for_orders( array_keys( $cpt_store_orders[ $cpt_store_name ] ), array() ); + } + + foreach ( $cpt_store_orders[ $cpt_store_name ] as $order_id => $order ) { + $cpt_order_class_name = wc_get_order_type( $order->get_type() )['class_name']; + $cpt_order = new $cpt_order_class_name(); + $cpt_order->set_id( $order_id ); + $cpt_store->read( $cpt_order ); + $cpt_orders[ $order_id ] = $cpt_order; + } + } + return $cpt_orders; } /** @@ -1221,12 +1236,12 @@ WHERE /** * Migrate post record from a given order object. * - * @param \WC_Order $order Order object. - * @param \WC_Order $post_order Order object read from posts. + * @param \WC_Abstract_Order $order Order object. + * @param \WC_Abstract_Order $post_order Order object read from posts. * * @return void */ - private function migrate_post_record( \WC_Order &$order, \WC_Order $post_order ): void { + private function migrate_post_record( \WC_Abstract_Order &$order, \WC_Abstract_Order $post_order ): void { $this->migrate_meta_data_from_post_order( $order, $post_order ); $post_order_base_data = $post_order->get_base_data(); foreach ( $post_order_base_data as $key => $value ) { diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php index 15883632971..7625a57d01c 100644 --- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php +++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableDataStoreTests.php @@ -1120,7 +1120,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { $this->enable_cot_sync(); $order = $this->create_complex_cot_order(); $post_order_comparison_closure = function ( $order ) { - $post_order = $this->get_post_orders_for_ids( array( $order->get_id() ) )[ $order->get_id() ]; + $post_order = $this->get_post_orders_for_ids( array( $order->get_id() => $order ) )[ $order->get_id() ]; return $this->is_post_different_from_order( $order, $post_order ); }; @@ -1135,6 +1135,7 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { $r_order = new WC_Order(); $r_order->set_id( $order->get_id() ); + $this->switch_data_store( $r_order, $this->sut ); // Reading again will make a call to migrate_post_record. $this->sut->read( $r_order ); $this->assertFalse( $post_order_comparison_closure->call( $this->sut, $r_order ) ); @@ -1806,6 +1807,8 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { * @testDox Test that multiple calls to read don't try to sync again. */ public function test_read_multiple_dont_sync_again_for_same_order() { + $this->toggle_cot( true ); + $this->enable_cot_sync(); $order = $this->create_complex_cot_order(); $order_id = $order->get_id(); @@ -1814,7 +1817,6 @@ class OrdersTableDataStoreTests extends WC_Unit_Test_Case { return $this->should_sync_order( $order ); }; - $this->enable_cot_sync(); $order = new WC_Order(); $order->set_id( $order_id ); $orders = array( $order_id => $order ); diff --git a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableRefundDataStoreTests.php b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableRefundDataStoreTests.php index d720011f887..21bb0ddd481 100644 --- a/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableRefundDataStoreTests.php +++ b/plugins/woocommerce/tests/php/src/Internal/DataStores/Orders/OrdersTableRefundDataStoreTests.php @@ -12,6 +12,7 @@ use Automattic\WooCommerce\RestApi\UnitTests\Helpers\OrderHelper; * Class OrdersTableRefundDataStoreTests. */ class OrdersTableRefundDataStoreTests extends WC_Unit_Test_Case { + use \Automattic\WooCommerce\RestApi\UnitTests\HPOSToggleTrait; /** * @var PostsToOrdersMigrationController @@ -73,4 +74,31 @@ class OrdersTableRefundDataStoreTests extends WC_Unit_Test_Case { $this->assertEquals( 'Test', $refreshed_refund->get_reason() ); } + /** + * @testDox Test that refunds can be backfilled correctly. + */ + public function test_refunds_backfill() { + $this->enable_cot_sync(); + $this->toggle_cot( true ); + $order = OrderHelper::create_complex_data_store_order( $this->order_data_store ); + $refund = wc_create_refund( + array( + 'order_id' => $order->get_id(), + 'amount' => 10, + 'reason' => 'Test', + ) + ); + $refund->save(); + $this->assertTrue( $refund->get_id() > 0 ); + + // Check that data was saved. + $refreshed_refund = new WC_Order_Refund(); + $cpt_store = $this->sut->get_cpt_data_store_instance(); + $refreshed_refund->set_id( $refund->get_id() ); + $cpt_store->read( $refreshed_refund ); + $this->assertEquals( $refund->get_id(), $refreshed_refund->get_id() ); + $this->assertEquals( 10, $refreshed_refund->get_amount() ); + $this->assertEquals( 'Test', $refreshed_refund->get_reason() ); + } + }