Skip to content

Commit 26fcbe9

Browse files
committed
Fixes for calculations with refunds involved
1 parent c2749c2 commit 26fcbe9

File tree

2 files changed

+64
-3
lines changed

2 files changed

+64
-3
lines changed

plugins/woocommerce/includes/class-wc-order-item-product.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -562,15 +562,19 @@ public function has_cogs(): bool {
562562
* @return float|null The calculated value, null if the product associated to the line item no longer exists.
563563
*/
564564
public function calculate_cogs_value_core(): ?float {
565-
// If this item has been loaded from the database and has a saved COGS value, preserve it.
565+
// Refund items should always recalculate based on their (negative) quantity.
566+
// Preserving would give incorrect values since they're cloned from original items.
567+
$is_refund_item = $this->get_quantity() < 0;
568+
569+
// If this is a regular item (not refund) that has been loaded from the database and has a saved COGS value, preserve it.
566570
// COGS values should be "snapshotted" at order creation time, not recalculated later.
567571
// This prevents historical orders from having their costs changed when product prices are updated.
568572
// Note: cogs_value will be null if the item was loaded but had no _cogs_value metadata.
569-
if ( $this->get_id() > 0 && $this->get_object_read() && isset( $this->data['cogs_value'] ) && ! is_null( $this->data['cogs_value'] ) ) {
573+
if ( ! $is_refund_item && $this->get_id() > 0 && $this->get_object_read() && isset( $this->data['cogs_value'] ) && ! is_null( $this->data['cogs_value'] ) ) {
570574
return $this->get_cogs_value( 'edit' );
571575
}
572576

573-
// Otherwise, calculate from the product.
577+
// For new items, refund items, or items without saved COGS, calculate from the product.
574578
$product = $this->get_product();
575579
if ( ! $product ) {
576580
return null;

plugins/woocommerce/tests/php/includes/data-stores/class-wc-order-data-store-cpt-test.php

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1200,4 +1200,61 @@ public function test_items_without_saved_cogs_calculate_from_product() {
12001200
$this->assertEquals( $expected_cogs, $reloaded_item->get_cogs_value(), 'Item without saved COGS should calculate from product' );
12011201
$this->assertEquals( $expected_cogs, $reloaded_order->get_cogs_total_value(), 'Order total should reflect calculated item COGS' );
12021202
}
1203+
1204+
/**
1205+
* @testDox Refund items always recalculate COGS based on their negative quantity.
1206+
*/
1207+
public function test_refund_items_recalculate_cogs() {
1208+
$this->enable_cogs_feature();
1209+
1210+
$product_cost = 20.00;
1211+
$product_qty = 10;
1212+
$refund_qty = 3;
1213+
$expected_order_cogs = $product_cost * $product_qty;
1214+
$expected_refund_cogs = -( $product_cost * $refund_qty );
1215+
1216+
// Create a product with COGS and price.
1217+
$product = WC_Helper_Product::create_simple_product();
1218+
$product->set_regular_price( $product_cost );
1219+
$product->set_cogs_value( $product_cost );
1220+
$product->save();
1221+
1222+
// Create an order with COGS.
1223+
$order = new WC_Order();
1224+
$order->add_product( $product, $product_qty );
1225+
$order->calculate_totals();
1226+
$order->save();
1227+
1228+
$this->assertEquals( $expected_order_cogs, $order->get_cogs_total_value() );
1229+
1230+
// Get the order item.
1231+
$order_items = array_values( $order->get_items( 'line_item' ) );
1232+
$order_item = $order_items[0];
1233+
1234+
// Create a refund.
1235+
$refund = wc_create_refund(
1236+
array(
1237+
'order_id' => $order->get_id(),
1238+
'amount' => $product_cost * $refund_qty,
1239+
'reason' => 'testing',
1240+
'line_items' => array(
1241+
$order_item->get_id() => array(
1242+
'qty' => $refund_qty,
1243+
'refund_total' => $product_cost * $refund_qty,
1244+
),
1245+
),
1246+
)
1247+
);
1248+
1249+
$this->assertNotInstanceOf( 'WP_Error', $refund, 'Refund creation should not return an error' );
1250+
$refund->save();
1251+
1252+
// Verify the refund has the correct COGS (negative value).
1253+
$this->assertEquals( $expected_refund_cogs, $refund->get_cogs_total_value(), 'Refund should have negative COGS' );
1254+
1255+
// Recalculate order totals and verify COGS is adjusted for the refund.
1256+
$order->calculate_totals();
1257+
$expected_final_cogs = $expected_order_cogs + $expected_refund_cogs;
1258+
$this->assertEquals( $expected_final_cogs, $order->get_cogs_total_value(), 'Order COGS should be reduced by refund amount' );
1259+
}
12031260
}

0 commit comments

Comments
 (0)