Quantity Pricing Bug in VirtueMart Child Products – Fixed

Started by hazael, May 30, 2025, 16:41:00 PM

Previous topic - Next topic

hazael

In VirtueMart 4.x, child products incorrectly inherit quantity pricing rules from their parent, even when they have their own pricing rule defined with a 0–0  quantity range ( or other). This causes incorrect price calculations, such as a total of 0.00 when the cart exceeds a specific quantity.

I noticed the bug thanks to the salesPriceTt variable, which didn't update correctly in the cart when increasing quantity. It drew my attention to the fact that the wrong price rule was being applied dynamically.

Before this fix, the only workaround was to assign different shopper groups to child products to prevent them from inheriting wrong pricing rules from the parent - which was a messy and unnecessary workaround. Now, everything works as expected, even with a single universal shopper group.

Additionally, saving a product with multiple prices could previously result in random rounding issues, especially when using the gross price input (brutto) checkbox. With this override, prices now remain exactly as entered — no silent recalculations or unexpected changes after saving.

What this fix does
We override the setProduct() method in the calculationHelper class to:
Correctly select the applicable pricing rule based on quantity (start <= quantity <= end),
Respect the child product's own pricing rules, even if defined as 0–0,
Fall back to a 0–0 rule only if no other range matches,
Preserve exact values without rounding errors.

Result
Quantity pricing now works correctly for child products, including:
Proper tiered pricing logic,
Editable and save-safe price rules,
Stable value consistency between frontend and backend.


How to apply:
In /administrator/components/com_virtuemart/helpers/calculationh.php
replace the original setProduct() method (around line 300) with the code below:
public function setProduct($product, $amount = 1.0)
{
    // Note: Only if the sectedprice is not set or its index does not exist
    if (!isset($product->selectedPrice) || !isset($product->allPrices[$product->selectedPrice])) {
        $bestIndex = null;
        $fallbackIndex = null;

        foreach ($product->allPrices as $i => $priceRule) {
            if (
                isset($priceRule['price_quantity_start']) &&
                isset($priceRule['price_quantity_end'])
            ) {
                $start = (int)$priceRule['price_quantity_start'];
                $end = (int)$priceRule['price_quantity_end'];

                // Search for the perfect range
                if ($start <= $amount && ($end == 0 || $amount <= $end)) {
                    $bestIndex = $i;
                    break;
                }

                // Save Fallback to 0–0 if it appears
                if ($start === 0 && $end === 0) {
                    $fallbackIndex = $i;
                }
            }
        }

       // choose the best available index
        if ($bestIndex !== null) {
            $product->selectedPrice = $bestIndex;
        } elseif ($fallbackIndex !== null) {
            $product->selectedPrice = $fallbackIndex;
        } else {
        // do not set anything - it will stay as it is
        }
    }

    // Next original code ...
    if (!empty($product->allPrices[$product->selectedPrice])) {
        $this->productPrices = $product->allPrices[$product->selectedPrice];
        $this->productCurrency = $this->productPrices['product_currency'];
        $product->product_tax_id = $this->product_tax_id = $this->productPrices['product_tax_id'];
        $this->product_discount_id = $this->productPrices['product_discount_id'];
    }

    $productVendorId = !empty($product->virtuemart_vendor_id) ? $product->virtuemart_vendor_id : 1;
    $this->setVendorId($productVendorId);

    $this->_cats = isset($product->categories) ? $product->categories : array();
    $this->_product = $product;
    $this->_product->amount = floatval($amount);
    if (!isset($this->_product->quantity)) $this->_product->quantity = 1;

    $this->_manufacturerId = !empty($product->virtuemart_manufacturer_id) ? $product->virtuemart_manufacturer_id : 0;

    return $this->productPrices;
}

Tada! It works!  8)

ps. It would be nice if Milbo added this amendment, because this problem was never solved even though I reported it many times