Commit 763c11f6d56 for woocommerce

commit 763c11f6d56ca1d6f03cbca5fdf75d4cfd63230b
Author: Ahmed Ismail <105542236+ahmedevsaad@users.noreply.github.com>
Date:   Thu Nov 12 17:49:59 2026 +0200

    Update the order modified date when only meta-stored order data changes (#69777)

    With posts storage, record every order save in post_modified, as HPOS
    already does, so item, total and custom meta changes move the date.

    Squashed from 95324e29f36, 000597eaaba and 70aafdf7601 when rebasing
    onto trunk.

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

diff --git a/plugins/woocommerce/changelog/fix-34798-parent-modified-date b/plugins/woocommerce/changelog/fix-34798-parent-modified-date
new file mode 100644
index 00000000000..c0123fc0951
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-34798-parent-modified-date
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Update the order modified date every time an order is saved on stores using posts storage, including item and custom field changes, as HPOS does.
diff --git a/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php b/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php
index 8dde869ba80..70acacb6d0e 100644
--- a/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php
+++ b/plugins/woocommerce/includes/data-stores/abstract-wc-order-data-store-cpt.php
@@ -260,6 +260,19 @@ abstract class Abstract_WC_Order_Data_Store_CPT extends WC_Data_Store_WP impleme
 				wp_update_post( array_merge( array( 'ID' => $order->get_id() ), $post_data ) );
 			}
 			$order->read_meta_data( true ); // Refresh internal meta data, in case things were hooked into `save_post` or another WP hook.
+		} else {
+			// Record the save even when only meta or items changed, matching the HPOS data store.
+			$modified = time();
+			$order->set_date_modified( $modified );
+			$GLOBALS['wpdb']->update(
+				$GLOBALS['wpdb']->posts,
+				array(
+					'post_modified'     => get_date_from_gmt( gmdate( 'Y-m-d H:i:s', $modified ) ),
+					'post_modified_gmt' => gmdate( 'Y-m-d H:i:s', $modified ),
+				),
+				array( 'ID' => $order->get_id() )
+			);
+			clean_post_cache( $order->get_id() );
 		}
 		$this->update_post_meta( $order );
 		$order->apply_changes();
diff --git a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php
index c1140d06c25..64046e91f86 100644
--- a/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php
+++ b/plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php
@@ -14,9 +14,15 @@ use Automattic\WooCommerce\Tests\Helpers\InterleavedStatusOrdersTrait;
  * @group order-query-tests
  */
 class WC_Order_Data_Store_CPT_Test extends WC_Unit_Test_Case {
+
 	use CogsAwareUnitTestSuiteTrait;
 	use InterleavedStatusOrdersTrait;

+	/**
+	 * A modified date in the past, used to check that saving an order moves it.
+	 */
+	private const PAST_MODIFIED_GMT = '2020-01-01 00:00:00';
+
 	/**
 	 * Store the COT state before the test class.
 	 *
@@ -1727,6 +1733,112 @@ class WC_Order_Data_Store_CPT_Test extends WC_Unit_Test_Case {
 		}
 	}

+	/**
+	 * Create an order whose stored modified date is in 2020.
+	 *
+	 * @return WC_Order The order, read back from the database.
+	 */
+	private function create_order_modified_in_the_past(): WC_Order {
+		$order = OrderHelper::create_order();
+		$GLOBALS['wpdb']->update(
+			$GLOBALS['wpdb']->posts,
+			array(
+				'post_modified'     => get_date_from_gmt( self::PAST_MODIFIED_GMT ),
+				'post_modified_gmt' => self::PAST_MODIFIED_GMT,
+			),
+			array( 'ID' => $order->get_id() )
+		);
+		clean_post_cache( $order->get_id() );
+
+		$order = wc_get_order( $order->get_id() );
+		$this->assertSame( strtotime( self::PAST_MODIFIED_GMT ), $order->get_date_modified()->getTimestamp(), 'The order should start with the backdated modified date.' );
+
+		return $order;
+	}
+
+	/**
+	 * Assert that both the order object and the stored order have a modified date after the backdated one.
+	 *
+	 * @param WC_Order $order The saved order.
+	 */
+	private function assert_date_modified_moved( WC_Order $order ): void {
+		$this->assertGreaterThan( strtotime( self::PAST_MODIFIED_GMT ), $order->get_date_modified()->getTimestamp(), 'The order object should have the new modified date.' );
+
+		$stored = wc_get_order( $order->get_id() );
+		$this->assertGreaterThan( strtotime( self::PAST_MODIFIED_GMT ), $stored->get_date_modified()->getTimestamp(), 'The stored order should have the new modified date.' );
+		$this->assertSame(
+			$stored->get_date_modified()->getTimestamp(),
+			strtotime( get_post_field( 'post_modified_gmt', $order->get_id(), 'raw' ) . ' UTC' ),
+			'post_modified_gmt should match the order modified date.'
+		);
+	}
+
+	/**
+	 * @testdox Saving an order whose changes are all stored in meta, like new totals, updates its modified date.
+	 */
+	public function test_update_with_meta_only_changes_updates_date_modified(): void {
+		$order = $this->create_order_modified_in_the_past();
+
+		$order->set_total( (float) $order->get_total() + 5 );
+		$order->save();
+
+		$this->assert_date_modified_moved( $order );
+	}
+
+	/**
+	 * @testdox Saving an order after changing only custom meta updates its modified date.
+	 */
+	public function test_update_with_custom_meta_only_updates_date_modified(): void {
+		$order = $this->create_order_modified_in_the_past();
+
+		$order->update_meta_data( '_test_custom_meta', 'value' );
+		$order->save();
+
+		$this->assert_date_modified_moved( $order );
+		$this->assertSame( 'value', wc_get_order( $order->get_id() )->get_meta( '_test_custom_meta' ), 'The custom meta should be saved.' );
+	}
+
+	/**
+	 * @testdox Saving an order after renaming one of its items updates its modified date.
+	 */
+	public function test_update_after_item_rename_updates_date_modified(): void {
+		$order = $this->create_order_modified_in_the_past();
+
+		$items = $order->get_items();
+		$item  = reset( $items );
+		$item->set_name( 'Renamed item' );
+		$order->save();
+
+		$this->assert_date_modified_moved( $order );
+		$this->assertSame( 'Renamed item', WC_Order_Factory::get_order_item( $item->get_id() )->get_name(), 'The item rename should be saved.' );
+	}
+
+	/**
+	 * @testdox Saving an order after adding a zero-amount fee updates its modified date.
+	 */
+	public function test_update_after_adding_zero_fee_updates_date_modified(): void {
+		$order = $this->create_order_modified_in_the_past();
+
+		$fee = new WC_Order_Item_Fee();
+		$fee->set_name( 'Free gift wrap' );
+		$fee->set_total( '0' );
+		$order->add_item( $fee );
+		$order->save();
+
+		$this->assert_date_modified_moved( $order );
+	}
+
+	/**
+	 * @testdox Saving an order without changes updates its modified date, as the HPOS data store does.
+	 */
+	public function test_update_without_changes_updates_date_modified(): void {
+		$order = $this->create_order_modified_in_the_past();
+
+		$order->save();
+
+		$this->assert_date_modified_moved( $order );
+	}
+
 	/**
 	 * @testdox CPT order queries return matching orders when the "$filter" filter replaces an unknown status with a registered one.
 	 *
diff --git a/plugins/woocommerce/tests/php/includes/wc-stock-functions-tests.php b/plugins/woocommerce/tests/php/includes/wc-stock-functions-tests.php
index 2704cea10b1..97589c059f2 100644
--- a/plugins/woocommerce/tests/php/includes/wc-stock-functions-tests.php
+++ b/plugins/woocommerce/tests/php/includes/wc-stock-functions-tests.php
@@ -554,4 +554,32 @@ class WC_Stock_Functions_Tests extends \WC_Unit_Test_Case {

 		$this->assertIsIntAndEquals( $site_wide_low_stock_amount, wc_get_low_stock_amount( $var1 ) );
 	}
+
+	/**
+	 * @testdox Reducing stock for an order updates the modified date of the product that holds the stock.
+	 */
+	public function test_reducing_order_stock_updates_product_date_modified(): void {
+		$product = WC_Helper_Product::create_simple_product();
+		$product->set_manage_stock( true );
+		$product->set_stock_quantity( 10 );
+		$product->save();
+
+		$past = '2020-01-01 00:00:00';
+		$GLOBALS['wpdb']->update(
+			$GLOBALS['wpdb']->posts,
+			array(
+				'post_modified'     => $past,
+				'post_modified_gmt' => $past,
+			),
+			array( 'ID' => $product->get_id() )
+		);
+		clean_post_cache( $product->get_id() );
+
+		$order = WC_Helper_Order::create_order( 0, $product );
+		wc_reduce_stock_levels( $order );
+
+		$product = wc_get_product( $product->get_id() );
+		$this->assertSame( 6, $product->get_stock_quantity() );
+		$this->assertGreaterThan( strtotime( $past ), $product->get_date_modified()->getTimestamp() );
+	}
 }