Commit 8d7a4921692 for woocommerce
commit 8d7a49216921305821c580184ea67396a47de4b9
Author: Peter Petrov <peter.petrov89@gmail.com>
Date: Tue Sep 8 16:17:40 2026 +0300
Fix Taxes report counting placeholder tax entries as taxable amount (#68468)
* Ignore placeholder tax entries when computing taxable amounts
* Skip non-numeric tax values in the non-compound pre-sum
diff --git a/plugins/woocommerce/src/Admin/API/Reports/Taxes/DataStore.php b/plugins/woocommerce/src/Admin/API/Reports/Taxes/DataStore.php
index a320f607e68..7bed1a044ed 100644
--- a/plugins/woocommerce/src/Admin/API/Reports/Taxes/DataStore.php
+++ b/plugins/woocommerce/src/Admin/API/Reports/Taxes/DataStore.php
@@ -690,7 +690,7 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
$non_compound_tax = 0.0;
foreach ( $taxes['total'] as $rate_id => $tax ) {
- if ( ! in_array( (int) $rate_id, $compound_rate_ids, true ) ) {
+ if ( is_numeric( $tax ) && ! in_array( (int) $rate_id, $compound_rate_ids, true ) ) {
$non_compound_tax += (float) $tax;
}
}
@@ -702,6 +702,12 @@ class DataStore extends ReportsDataStore implements DataStoreInterface {
// only mis-split the base between multiple compound rates, not the total.
$compound_running_tax = 0.0;
foreach ( $taxes['total'] as $rate_id => $tax ) {
+ // Admin saves without recalculating store '' for rates that never applied
+ // to the item. Numeric zero still counts (zero-rated sales).
+ if ( ! is_numeric( $tax ) ) {
+ continue;
+ }
+
$base = (float) $item->get_total();
if ( in_array( (int) $rate_id, $compound_rate_ids, true ) ) {
$base += $non_compound_tax + $compound_running_tax;
diff --git a/plugins/woocommerce/tests/php/src/Admin/API/Reports/Taxes/DataStoreTest.php b/plugins/woocommerce/tests/php/src/Admin/API/Reports/Taxes/DataStoreTest.php
index 1e8b912917c..38f30eb211f 100644
--- a/plugins/woocommerce/tests/php/src/Admin/API/Reports/Taxes/DataStoreTest.php
+++ b/plugins/woocommerce/tests/php/src/Admin/API/Reports/Taxes/DataStoreTest.php
@@ -557,6 +557,45 @@ class DataStoreTest extends WC_Unit_Test_Case {
$this->assertSame( 0.0, (float) $lookup_row->taxable_amount, 'A tax line applied to no order item should record a zero taxable amount.' );
}
+ /**
+ * @testdox An admin order save that leaves empty tax placeholders on items records no base for that rate.
+ */
+ public function test_sync_order_taxes_ignores_placeholder_tax_entries(): void {
+ global $wpdb;
+ WC_Helper_Reports::reset_stats_dbs();
+
+ $rate_id = $this->insert_tax_rate();
+ $other_rate_id = $this->insert_tax_rate( '7', 2 );
+ $order = $this->create_taxed_de_order();
+
+ // An admin save without recalculating stores '' for rates that never applied to the
+ // item. Blank the second rate on the fee and shipping only, so its base must come
+ // from the product line alone.
+ foreach ( $order->get_items( array( OrderItemType::FEE, OrderItemType::SHIPPING ) ) as $item ) {
+ $taxes = $item->get_taxes();
+ $taxes['total'][ $other_rate_id ] = '';
+ if ( isset( $taxes['subtotal'] ) ) {
+ $taxes['subtotal'][ $other_rate_id ] = '';
+ }
+ $item->set_taxes( $taxes );
+ $item->save();
+ }
+ $order->update_taxes();
+ $order->save();
+
+ DataStore::sync_order_taxes( $order->get_id() );
+
+ $amounts = $wpdb->get_results(
+ $wpdb->prepare(
+ "SELECT tax_rate_id, taxable_amount FROM {$wpdb->prefix}wc_order_tax_lookup WHERE order_id = %d",
+ $order->get_id()
+ ),
+ OBJECT_K
+ );
+ $this->assertSame( 200.0, (float) $amounts[ $other_rate_id ]->taxable_amount, 'Placeholder entries must not add the fee and shipping totals to the rate.' );
+ $this->assertSame( 215.0, (float) $amounts[ $rate_id ]->taxable_amount, 'The rate without placeholders must keep its full base.' );
+ }
+
/**
* @testdox Syncing an order records the base of a compound tax rate including the taxes it compounds over.
*/