Commit 91c1afe3cce for woocommerce

commit 91c1afe3cce56e045bb9707189d1b666720d0e5b
Author: Mike Jolley <mike.jolley@me.com>
Date:   Thu Oct 22 13:52:29 2026 +0100

    Don't record a status change when an auto-draft order stays auto-draft (#70126)

    * Fix status change note when an auto-draft order is saved as auto-draft

    Only treat an auto-draft order as pending when its status is actually
    changing. Re-saving it as auto-draft, as REST clients like the mobile
    app do, no longer records a pending to auto-draft transition, so no
    order note is added and the status change hooks don't fire.

    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

    * Add changelog entry for auto-draft status note fix

    Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

    ---------

    Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

diff --git a/plugins/woocommerce/changelog/53304-rest-auto-draft-status-note b/plugins/woocommerce/changelog/53304-rest-auto-draft-status-note
new file mode 100644
index 00000000000..098d5676599
--- /dev/null
+++ b/plugins/woocommerce/changelog/53304-rest-auto-draft-status-note
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Stop adding a status change note each time an auto-draft order is saved again through the REST API.
diff --git a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
index 40b5dcd1428..aa99e70e986 100644
--- a/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
+++ b/plugins/woocommerce/includes/abstracts/abstract-wc-order.php
@@ -929,9 +929,10 @@ abstract class WC_Abstract_Order extends WC_Abstract_Legacy_Order {
 				$new_status = OrderStatus::PENDING;
 			}

-			// If the old status is set but unknown (e.g. draft) assume its pending for action usage.
+			// If the status is changing from auto-draft or an unknown status (e.g. draft), assume it was pending for action usage.
 			if (
 				$old_status
+				&& $old_status !== $new_status
 				&& (
 					OrderStatus::AUTO_DRAFT === $old_status
 					|| (
diff --git a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller-tests.php b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller-tests.php
index 004e1d37182..7be45c27e4c 100644
--- a/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller-tests.php
+++ b/plugins/woocommerce/tests/php/includes/rest-api/Controllers/Version3/class-wc-rest-orders-controller-tests.php
@@ -391,6 +391,88 @@ class WC_REST_Orders_Controller_Tests extends WC_REST_Unit_Test_Case {
 		$this->assertSame( $requested_status, $persisted_order->get_status(), 'The updated order should persist the requested status.' );
 	}

+	/**
+	 * Create an auto-draft order the same way the admin "Add order" screen does.
+	 *
+	 * @return int Order ID.
+	 */
+	private function create_admin_auto_draft_order(): int {
+		$order = new WC_Order();
+		$order->set_object_read( false );
+		$order->set_status( OrderStatus::AUTO_DRAFT );
+		$order->set_created_via( 'admin' );
+
+		return $order->save();
+	}
+
+	/**
+	 * Update an order's status through the V3 route and record the status changes it fires.
+	 *
+	 * @param int    $order_id         Order ID.
+	 * @param string $requested_status Status supplied in the request.
+	 * @return array<int, array{string, string}> The from and to statuses of each `woocommerce_order_status_changed` call.
+	 */
+	private function update_order_status_and_record_changes( int $order_id, string $requested_status ): array {
+		$status_changes = array();
+		add_action(
+			'woocommerce_order_status_changed',
+			function ( $changed_order_id, $from, $to ) use ( &$status_changes ) {
+				$status_changes[] = array( $from, $to );
+			},
+			10,
+			3
+		);
+
+		$request = new WP_REST_Request( 'PUT', '/wc/v3/orders/' . $order_id );
+		$request->set_body_params( array( 'status' => $requested_status ) );
+		$response = $this->server->dispatch( $request );
+
+		$this->assertSame( 200, $response->get_status(), 'Updating the order should return HTTP 200.' );
+		$this->assertSame( $requested_status, $response->get_data()['status'], 'The update response should contain the requested order status.' );
+
+		return $status_changes;
+	}
+
+	/**
+	 * @testdox Saving an auto-draft order as auto-draft again through the V3 route adds no order note and fires no status change.
+	 *
+	 * @testWith [true]
+	 *           [false]
+	 *
+	 * @param bool $hpos_enabled True to store orders in the HPOS tables, false to store them as posts.
+	 */
+	public function test_orders_update_auto_draft_order_as_auto_draft_records_no_status_change( bool $hpos_enabled ): void {
+		$this->toggle_cot_feature_and_usage( $hpos_enabled );
+		$order_id = $this->create_admin_auto_draft_order();
+
+		$status_changes = $this->update_order_status_and_record_changes( $order_id, OrderStatus::AUTO_DRAFT );
+
+		$this->assertSame( array(), $status_changes, 'An auto-draft order saved as auto-draft should not fire woocommerce_order_status_changed.' );
+		$this->assertSame( array(), wc_get_order_notes( array( 'order_id' => $order_id ) ), 'An auto-draft order saved as auto-draft should not get a status change note.' );
+	}
+
+	/**
+	 * @testdox Moving an auto-draft order to processing through the V3 route records the change as coming from pending.
+	 *
+	 * @testWith [true]
+	 *           [false]
+	 *
+	 * @param bool $hpos_enabled True to store orders in the HPOS tables, false to store them as posts.
+	 */
+	public function test_orders_update_auto_draft_order_to_processing_records_change_from_pending( bool $hpos_enabled ): void {
+		$this->toggle_cot_feature_and_usage( $hpos_enabled );
+		$order_id = $this->create_admin_auto_draft_order();
+
+		$status_changes = $this->update_order_status_and_record_changes( $order_id, OrderStatus::PROCESSING );
+
+		$this->assertSame( array( array( OrderStatus::PENDING, OrderStatus::PROCESSING ) ), $status_changes, 'An auto-draft order should move to processing as if it were pending.' );
+		$this->assertContains(
+			'Order status changed from Pending payment to Processing.',
+			wp_list_pluck( wc_get_order_notes( array( 'order_id' => $order_id ) ), 'content' ),
+			'The status change note should name pending as the old status.'
+		);
+	}
+
 	/**
 	 * Search terms accepted by the V3 orders collection route.
 	 *