Commit 802793aad80 for woocommerce

commit 802793aad80465bc4efb61667b919cab5bf0f6e7
Author: Peter Petrov <peter.petrov89@gmail.com>
Date:   Fri Aug 14 16:47:59 2026 +0300

    Match truncated order statuses in the Customer History metabox (#67700)

    * Match truncated order statuses in the Customer History metabox excluded-statuses filter

    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

    * Assert average order value in long custom status tests

    Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

    ---------

    Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

diff --git a/plugins/woocommerce/changelog/fix-67697-customer-history-excluded-long-statuses b/plugins/woocommerce/changelog/fix-67697-customer-history-excluded-long-statuses
new file mode 100644
index 00000000000..894f73fd16f
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-67697-customer-history-excluded-long-statuses
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Respect excluded order statuses in the Customer History metabox when a custom status slug exceeds the 20-character storage limit
diff --git a/plugins/woocommerce/src/Internal/Admin/Orders/MetaBoxes/CustomerHistory.php b/plugins/woocommerce/src/Internal/Admin/Orders/MetaBoxes/CustomerHistory.php
index a22a2651ee6..f8721a53d53 100644
--- a/plugins/woocommerce/src/Internal/Admin/Orders/MetaBoxes/CustomerHistory.php
+++ b/plugins/woocommerce/src/Internal/Admin/Orders/MetaBoxes/CustomerHistory.php
@@ -283,7 +283,10 @@ class CustomerHistory {
 		$prefixed = array_map(
 			function ( $status ) {
 				$status = sanitize_title( $status );
-				return 'auto-draft' === $status || 'trash' === $status ? $status : 'wc-' . $status;
+				$status = 'auto-draft' === $status || 'trash' === $status ? $status : 'wc-' . $status;
+				// Status columns are varchar(20) and longer values are silently truncated
+				// on write, so truncate the same way or comparisons never match long slugs.
+				return mb_substr( $status, 0, 20 );
 			},
 			$excluded_statuses
 		);
diff --git a/plugins/woocommerce/tests/php/src/Internal/Admin/Orders/MetaBoxes/CustomerHistoryTest.php b/plugins/woocommerce/tests/php/src/Internal/Admin/Orders/MetaBoxes/CustomerHistoryTest.php
index cfc33c8b632..891a412e037 100644
--- a/plugins/woocommerce/tests/php/src/Internal/Admin/Orders/MetaBoxes/CustomerHistoryTest.php
+++ b/plugins/woocommerce/tests/php/src/Internal/Admin/Orders/MetaBoxes/CustomerHistoryTest.php
@@ -30,6 +30,13 @@ class CustomerHistoryTest extends WC_Unit_Test_Case {
 	 */
 	private bool $restore_hpos_after_test = false;

+	/**
+	 * Callback registering the long custom status, kept for removal in tearDown.
+	 *
+	 * @var callable|null
+	 */
+	private $add_long_status_callback = null;
+
 	/**
 	 * Previous HPOS state.
 	 *
@@ -87,6 +94,11 @@ class CustomerHistoryTest extends WC_Unit_Test_Case {
 		}

 		delete_option( 'woocommerce_excluded_report_order_statuses' );
+		if ( null !== $this->add_long_status_callback ) {
+			remove_filter( 'wc_order_statuses', $this->add_long_status_callback );
+			unset( $GLOBALS['wp_post_statuses']['wc-competition-completed'] );
+			$this->add_long_status_callback = null;
+		}
 		remove_filter( 'woocommerce_order_class', array( AdminOrder::class, 'order_class_name' ) );
 		remove_filter( 'wc_allow_changing_orders_storage_while_sync_is_pending', '__return_true' );
 		parent::tearDown();
@@ -229,6 +241,71 @@ class CustomerHistoryTest extends WC_Unit_Test_Case {
 		$this->assertMatchesRegularExpression( '/order-attribution-total-orders">\s*0\s*</', $output, 'Should show 0 orders when all are excluded' );
 	}

+	/**
+	 * @testdox Should not count orders in an excluded custom status whose slug exceeds the 20-char storage limit (HPOS).
+	 */
+	public function test_excluded_long_custom_status_not_counted(): void {
+		list( $order_good, $long_status ) = $this->create_orders_with_long_custom_status();
+
+		update_option( 'woocommerce_excluded_report_order_statuses', array( 'pending', 'failed', 'cancelled', $long_status ) );
+
+		ob_start();
+		$this->sut->output( $order_good );
+		$output = ob_get_clean();
+
+		$this->assertMatchesRegularExpression( '/order-attribution-total-orders">\s*1\s*</', $output, 'Should only count the completed order' );
+		$this->assertMatchesRegularExpression( '/order-attribution-total-spend">\s*.*100\.00/', $output, 'Should only sum spend from the completed order' );
+		$this->assertMatchesRegularExpression( '/order-attribution-average-order-value">\s*.*100\.00/', $output, 'Should show average order value of 100' );
+	}
+
+	/**
+	 * @testdox Should count orders in a non-excluded custom status whose slug exceeds the 20-char storage limit (HPOS).
+	 */
+	public function test_non_excluded_long_custom_status_counted(): void {
+		list( $order_good ) = $this->create_orders_with_long_custom_status();
+
+		ob_start();
+		$this->sut->output( $order_good );
+		$output = ob_get_clean();
+
+		$this->assertMatchesRegularExpression( '/order-attribution-total-orders">\s*2\s*</', $output, 'Should count both orders' );
+		$this->assertMatchesRegularExpression( '/order-attribution-total-spend">\s*.*150\.00/', $output, 'Should sum spend from both orders' );
+		$this->assertMatchesRegularExpression( '/order-attribution-average-order-value">\s*.*75\.00/', $output, 'Should show average order value of 75' );
+	}
+
+	/**
+	 * Creates a completed order and an order in a custom status whose slug is stored truncated.
+	 *
+	 * @return array{0: \WC_Order, 1: string} The completed order and the custom status slug.
+	 */
+	private function create_orders_with_long_custom_status(): array {
+		// Core warns when saving a status longer than the 20-char column; that
+		// truncated storage is the exact scenario under test.
+		$this->setExpectedIncorrectUsage( 'Abstract_WC_Order_Data_Store_CPT::get_post_status' );
+
+		$long_status = 'competition-completed';
+		register_post_status( 'wc-' . $long_status, array( 'public' => true ) );
+		$this->add_long_status_callback = function ( $statuses ) use ( $long_status ) {
+			$statuses[ 'wc-' . $long_status ] = 'Competition Completed';
+			return $statuses;
+		};
+		add_filter( 'wc_order_statuses', $this->add_long_status_callback );
+
+		$customer_id = $this->factory->user->create();
+
+		$order_good = WC_Helper_Order::create_order( $customer_id );
+		$order_good->set_status( 'completed' );
+		$order_good->set_total( 100 );
+		$order_good->save();
+
+		$order_custom = WC_Helper_Order::create_order( $customer_id );
+		$order_custom->set_status( $long_status );
+		$order_custom->set_total( 50 );
+		$order_custom->save();
+
+		return array( $order_good, $long_status );
+	}
+
 	/**
 	 * @testdox Should deduct partial refund from total spend (HPOS).
 	 */