News:

Looking for documentation? Take a look on our wiki

Main Menu

Recent posts

#2
I have a very strange and weird bug. The stock gets automatically reverted back to it's old value. My client tells me it isn't that they do it by themselves, it gets done by the system. Any idea how this happens?

I checked all the plugins and components I have and none of them have anything to do with the stock. I'm very confused why this happens.

I think the best solution here is to add logging to the stock changes. I added in 2 places logging. Maybe a checkbox needs to be added so the logging only gets done when the user actually wants it, but for now I just hardcode it.

@Milbo

      public function updateStockInDB ($product, $amount, $signInStock, $signOrderedStock) {
           

        // Get the currently logged-in user
        $user = Factory::getUser();
        $userId = $user->id;
        $username = $user->username;
   
        // Log the start of the stock update process
        JLog::addLogger(
            array(
                'text_file' => 'com_virtuemart.log.php', // Name of the log file
                'text_entry_format' => '{DATETIME} {PRIORITY} {CATEGORY} {MESSAGE}' // Log entry format
            ),
            JLog::ALL, // Log all levels of messages
            array('com_virtuemart') // Categories to log
        );
   
        JLog::add(
            sprintf(
                'User %s (ID: %d) is attempting to update stock for product ID %d with amount %f, signInStock %s, signOrderedStock %s',
                $username, $userId, $product->virtuemart_product_id, $amount, $signInStock, $signOrderedStock
            ),
            Log::INFO,
            'com_virtuemart'
        );

        vmdebug('updateStockInDB start ', $signInStock, $signOrderedStock);
        $validFields = array('=', '+', '-');
        if (!in_array ($signInStock, $validFields)) {
            return FALSE;
        }
        if (!in_array ($signOrderedStock, $validFields)) {
            return FALSE;
        }

        $lproduct = $this->getProductSingle($product->virtuemart_product_id);
        if($lproduct->shared_stock){
            $productId = $lproduct->product_parent_id;
        } else {
            $productId = $product->virtuemart_product_id;
        }

        $amount = (float)$amount;
        $update = array();

        if ($signInStock != '=' or $signOrderedStock != '=') {

            if ($signInStock != '=') {
                $update[] = '`product_in_stock` = `product_in_stock` ' . $signInStock . $amount;

                if (strpos ($signInStock, '+') !== FALSE) {
                    $signInStock = '-';
                }
                else {
                    $signInStock = '+';
                }
                $update[] = '`product_sales` = `product_sales` ' . $signInStock . $amount;

            }
            if ($signOrderedStock != '=') {
                $update[] = '`product_ordered` = `product_ordered` ' . $signOrderedStock . $amount;
            }
            $q = 'UPDATE `#__virtuemart_products` SET ' . implode (", ", $update) . ' WHERE `virtuemart_product_id` = ' . (int)$productId;

            $db = JFactory::getDbo();
            $db->setQuery ($q);
            $db->execute ();
            //vmdebug('updateStockInDB executed query ', $q);
            //The low on stock notification comes now, when the people ordered.
            //You need to know that the stock is going low before you actually sent the wares, because then you ususally know it already yourself
            //note by Max Milbers
            if ($signInStock == '+' or $signOrderedStock == '+') {

                $q = 'SELECT (IFNULL(`product_in_stock`,"0")-IFNULL(`product_ordered`,"0")) < IFNULL(`low_stock_notification`,"0") '
                . 'FROM `#__virtuemart_products` '
                . 'WHERE `virtuemart_product_id` = ' . (int)$productId;
                $db->setQuery ( $q );
                //vmdebug('Check for low stock ',$q);
                if ($db->loadResult () == 1) {
                    vmdebug('Check for low stock said therre is a low stock ');
                    $this->lowStockWarningEmail( $productId) ;
                }
            }
        }

    }


And then I also added the logging to the store when a user changes the stock manually:

public function store (&$data) {

        vRequest::vmCheckToken();

        if(!vmAccess::manager('product.edit')){
            vmError('You are not a vendor or administrator, storing of product cancelled');
            return FALSE;
        }

        if ($data and is_object($data)) {
            $data = get_object_vars($data);
        }

        $isChild = FALSE;
        if(!empty($data['isChild'])) $isChild = $data['isChild'];

        if (isset($data['intnotes'])) {
            $data['intnotes'] = trim ($data['intnotes']);
        }

        // Setup some place holders
        $product_data = $this->getTable ('products');

        $data['new'] = '1';
        if(!empty($data['virtuemart_product_id'])){
            $product_data -> load($data['virtuemart_product_id']);
            $data['new'] = '0';
        }
        if( (empty($data['virtuemart_product_id']) or empty($product_data->virtuemart_product_id)) and !vmAccess::manager('product.create')){
            vmWarn('Insufficient permission to create product');
            return false;
        }

        $vendorId = vmAccess::isSuperVendor();
        $vM = VmModel::getModel('vendor');
        $ven = $vM->getVendor($vendorId);

        if(VmConfig::get('multix','none')!='none' and !vmAccess::manager('core')){

            if($ven->max_products!=-1){
                $this->setGetCount (true);
                //$this->setDebugSql(true);
                parent::exeSortSearchListQuery(2,'virtuemart_product_id',' FROM #__virtuemart_products',' WHERE ( `virtuemart_vendor_id` = "'.$vendorId.'" AND `published`="1") ');
                $this->setGetCount (false);
                if($ven->max_products<($this->_total+1)){
                    vmWarn('You are not allowed to create more than '.$ven->max_products.' products');
                    return false;
                }
            }
        }

        if(!vmAccess::manager('product.edit.state')){
            if( (empty($data['virtuemart_product_id']) or empty($product_data->virtuemart_product_id))){
                $data['published'] = 0;
            } else {
                $data['published'] = $product_data->published;
            }
        }

        //Set the decimals like product packaging
        foreach(self::$decimals as $decimal){
            if (array_key_exists ($decimal, $data)) {
                if(!empty($data[$decimal])){
                    $data[$decimal] = str_replace(',','.',$data[$decimal]);
                    //vmdebug('Store product '.$data['virtuemart_product_id'].', set $decimal '.$decimal.' = '.$data[$decimal]);
                } else {
                    $data[$decimal] = null;
                    $product_data->{$decimal} = null;
                    //vmdebug('Store product '.$data['virtuemart_product_id'].', set $decimal '.$decimal.' = null');
                }
            }
        }

        if($ven->force_product_pattern>0 and empty($data['product_parent_id']) and $ven->force_product_pattern!=$data['virtuemart_product_id']){
            $data['product_parent_id'] = $ven->force_product_pattern;
        }

        //We prevent with this line, that someone is storing a product as its own parent
        if(!empty($data['product_parent_id']) and !empty($data['virtuemart_product_id']) and $data['product_parent_id'] == $data['virtuemart_product_id']){
            $data['product_parent_id'] = 0;
        }

        $product_data->has_prices = (isset($data['mprices']['product_price']) and count($data['mprices']['product_price']) > 0)? 1:0;

        if (!$isChild) {
            $product_data->has_shoppergroups = empty($data['virtuemart_shoppergroup_id'])? 0:1;
            $product_data->has_manufacturers = empty($data['virtuemart_manufacturer_id'])? 0:1;
            //$product_data->has_medias = !empty($data['virtuemart_media_id']) or !empty($data['media']['virtuemart_media_id'])? 1:0;
            $product_data->has_categories = empty($data['categories'])? 0:1;
            if(!empty($data['virtuemart_media_id']) or !empty($data['media']['virtuemart_media_id']) or !empty($data['media']['media_action'])){
                $product_data->has_medias = 1;
            } else {
                $product_data->has_medias = 0;
            }
        }


        vDispatcher::importVMPlugins('vmcustom');
        vDispatcher::trigger('plgVmBeforeStoreProduct',array(&$data, &$product_data));

        $stored = $product_data->bindChecknStore($data, false);

       

        if(!$stored ){
            vmError('You are not an administrator or the correct vendor, storing of product cancelled');
            vmdebug('You are not an administrator or the correct vendor, storing of product cancelled', $data, $product_data->loadFieldValues());
            return FALSE;
        }

        $this->_id = $data['virtuemart_product_id'] = (int)$product_data->virtuemart_product_id;

        if (empty($this->_id)) {
            vmError('Product not stored, no id');
            return FALSE;
        }

        //We may need to change this, the reason it is not in the other list of commands for parents
        if (!$isChild) {
            $modelCustomfields = VmModel::getModel ('Customfields');
            $modelCustomfields->storeProductCustomfields ('product', $data, $product_data->virtuemart_product_id);
        }

        // Get old IDS
        $old_price_ids = $this->loadProductPrices($this->_id,array(0),false);

        if (isset($data['mprices']['product_price']) and count($data['mprices']['product_price']) > 0){

            foreach($data['mprices']['product_price'] as $k => $product_price){

                $pricesToStore = array();
                $pricesToStore['virtuemart_product_id'] = $this->_id;
                $pricesToStore['virtuemart_product_price_id'] = (int)$data['mprices']['virtuemart_product_price_id'][$k];

                if (!$isChild){
                    //$pricesToStore['basePrice'] = $data['mprices']['basePrice'][$k];
                    $pricesToStore['product_override_price'] = $data['mprices']['product_override_price'][$k];
                    $pricesToStore['override'] = isset($data['mprices']['override'][$k])?(int)$data['mprices']['override'][$k]:0;
                    $pricesToStore['virtuemart_shoppergroup_id'] = (int)$data['mprices']['virtuemart_shoppergroup_id'][$k];
                    $pricesToStore['product_tax_id'] = !empty($data['mprices']['product_tax_id'][$k])? (int)$data['mprices']['product_tax_id'][$k]:0;
                    $pricesToStore['product_discount_id'] = !empty($data['mprices']['product_discount_id'][$k])? (int)$data['mprices']['product_discount_id'][$k]:0;
                    $pricesToStore['product_currency'] = !empty($data['mprices']['product_currency'][$k])? (int)$data['mprices']['product_currency'][$k] : $ven->vendor_currency;
                    $pricesToStore['product_price_publish_up'] = !empty($data['mprices']['product_price_publish_up'][$k])? $data['mprices']['product_price_publish_up'][$k]:0;
                    $pricesToStore['product_price_publish_down'] = !empty($data['mprices']['product_price_publish_down'][$k])? $data['mprices']['product_price_publish_down'][$k]:0;
                    $pricesToStore['price_quantity_start'] = !empty($data['mprices']['price_quantity_start'][$k])? (int)$data['mprices']['price_quantity_start'][$k]:0;
                    $pricesToStore['price_quantity_end'] = !empty($data['mprices']['price_quantity_end'][$k])? (int)$data['mprices']['price_quantity_end'][$k]:0;
                }

                if (!$isChild and isset($data['mprices']['use_desired_price'][$k]) and $data['mprices']['use_desired_price'][$k] == "1") {

                    $calculator = calculationHelper::getInstance ();
                    if(isset($data['mprices']['salesPrice'][$k])){
                        $data['mprices']['salesPrice'][$k] = str_replace(array(',',' '),array('.',''),$data['mprices']['salesPrice'][$k]);
                    }
                    $pricesToStore['salesPrice'] = $data['mprices']['salesPrice'][$k];
                    $pricesToStore['product_price'] = $data['mprices']['product_price'][$k] = $calculator->calculateCostprice ($this->_id, $pricesToStore);
                    unset($data['mprices']['use_desired_price'][$k]);
                } else {
                    if(isset($data['mprices']['product_price'][$k]) ){
                        $pricesToStore['product_price'] = $data['mprices']['product_price'][$k];
                    }

                }

                if ($isChild) $childPrices = $this->loadProductPrices($this->_id,array(0),false);

                if ((isset($pricesToStore['product_price']) and $pricesToStore['product_price']!='' and $pricesToStore['product_price']!=='0') || (isset($childPrices) and is_array($childPrices) and count($childPrices)>1)) {

                    if ($isChild) {

                        if(is_array($old_price_ids) and count($old_price_ids)>1){

                            //We do not touch multiple child prices. Because in the parent list, we see no price, the gui is
                            //missing to reflect the information properly.
                            $pricesToStore = false;
                            $old_price_ids = array();
                        } else {
                            unset($data['mprices']['product_override_price'][$k]);
                            unset($pricesToStore['product_override_price']);
                            unset($data['mprices']['override'][$k]);
                            unset($pricesToStore['override']);
                        }

                    }

                    if($pricesToStore){
                        $toUnset = array();
                        if (!empty($old_price_ids) and count($old_price_ids) ) {
                            foreach($old_price_ids as $key => $oldprice){
                                if($pricesToStore['virtuemart_product_price_id'] == $oldprice['virtuemart_product_price_id'] ){
                                    $pricesToStore = array_merge($oldprice,$pricesToStore);
                                    $toUnset[] = $key;
                                }
                            }
                        }
                        $this->updateXrefAndChildTables ($pricesToStore, 'product_prices',$isChild);

                        foreach($toUnset as $key){
                            unset( $old_price_ids[ $key ] );
                        }
                    }
                }
            }
        }

        if (!empty($old_price_ids) and count($old_price_ids) ) {
            $oldPriceIdsSql = array();
            foreach($old_price_ids as $oldPride){
                $oldPriceIdsSql[] = $oldPride['virtuemart_product_price_id'];
            }
            $db = JFactory::getDbo();
            // delete old unused Prices
            $db->setQuery( 'DELETE FROM `#__virtuemart_product_prices` WHERE `virtuemart_product_price_id` in ("'.implode('","', $oldPriceIdsSql ).'") ');
            $err = '';
            try {
                $db->execute();
            } catch(Exception $e) {
                $err = $e->getMessage();
            }
           
            if(!empty($err)){
                vmWarn('In store prodcut, deleting old price error',$err);
            }
        }

        if (!empty($data['childs'])) {
            foreach ($data['childs'] as $productId => $child) {
                if(empty($productId)) continue;
                if($productId!=$data['virtuemart_product_id']){

                    if(empty($child['product_parent_id'])) $child['product_parent_id'] = $data['virtuemart_product_id'];
                    $child['virtuemart_product_id'] = $productId;

                    if(!empty($child['product_parent_id']) and $child['product_parent_id'] == $child['virtuemart_product_id']){
                        $child['product_parent_id'] = 0;
                    }

                    $child['isChild'] = $this->_id;
                    $this->store ($child);
                }
            }
        }

        if (!$isChild) {

            $data = $this->updateXrefAndChildTables ($data, 'product_shoppergroups');

            $data = $this->updateXrefAndChildTables ($data, 'product_manufacturers');

            $storeCats = false;
            if (empty($data['categories']) or (!empty($data['categories'][0]) and $data['categories'][0]!="-2")){
                $storeCats = true;
            }

            if($storeCats){
                if (!empty($data['categories']) && count ($data['categories']) > 0) {
                    if(VmConfig::get('multix','none')!='none' and !vmAccess::manager('managevendors')){

                        if($ven->max_cats_per_product>=0){
                            while($ven->max_cats_per_product<count($data['categories'])){
                                array_pop($data['categories']);
                            }
                        }

                    }
                    $data['virtuemart_category_id'] = $data['categories'];
                } else {
                    $data['virtuemart_category_id'] = array();
                }
                $data = $this->updateXrefAndChildTables ($data, 'product_categories');
            }

            // Update waiting list
            if (!empty($data['notify_users'])) {
                if ($data['product_in_stock'] > 0 && $data['notify_users'] == '1') {
                    $waitinglist = VmModel::getModel ('Waitinglist');
                    $waitinglist->notifyList ($data['virtuemart_product_id']);
                }
            }

            // Process the images
            $mediaModel = VmModel::getModel ('Media');
            $mediaModel->storeMedia ($data, 'product');

        }

        $oldStock = $product_data->product_in_stock;

        // Attempt to store product data
       

        // Get the currently logged-in user
        $user = Factory::getUser();
        $userId = $user->id;
        $username = $user->username;

        // Initialize the logger
        Log::addLogger(
            array(
                'text_file' => 'com_virtuemart.log.php', // Name of the log file
                'text_entry_format' => '{DATETIME} {PRIORITY} {CATEGORY} {MESSAGE}' // Log entry format
            ),
            Log::ALL, // Log all levels of messages
            array('com_virtuemart') // Categories to log
        );

        // Check if the stock has changed and log the changes
        if ($oldStock != $data['product_in_stock']) {
            Log::add(
                sprintf(
                    'User %s (ID: %d) changed stock for product ID %d from %d to %d',
                    $username, $userId, $product_data->virtuemart_product_id, $oldStock, $data['product_in_stock']
                ),
                Log::INFO,
                'com_virtuemart'
            );
        }

        $cache = VmConfig::getCache('com_virtuemart_orderby_manus','callback');
        $cache->clean();

        vDispatcher::trigger('plgVmAfterStoreProduct',array(&$data, &$product_data));

        return $product_data->virtuemart_product_id;
    }
#3
Product pricing / for those who wants to remove ...
Last post by rdcustom - Today at 12:23:58 PM
did a trick today, I post here for future reference in case of updates:

currencydisplay.php

on line 301

add:

$res = preg_replace('/\,00/', '', $res);
$res = rtrim($res, ",");

this will show prices without useless ",00"

125,00 will result in "125"
#4
Product pricing / Re: If there's an override, cu...
Last post by rdcustom - Today at 11:46:22 AM
I found myself the bug:

in calculationh.php - line 416

need to remove:

"if($variant){
            $this->productPrices['discountedPriceWithoutTax'] += $variant;
         }

"
#5
Hi,
Any idea how to fix this?
#6
General Questions / Issues with Coupon Functionali...
Last post by k2mdedia - Yesterday at 21:59:09 PM

I'm experiencing two issues with the coupon functionality in Virtuemart 4.2.4 on Joomla 4:

- When I set a coupon to be used only with a specific product, apply it to the cart, and then remove the item, the cart does not automatically reload. After manually refreshing the page, the product does get removed, but the coupon remains applied.

- Even though the coupon is configured to be valid only for specific products, it incorrectly reappears and gets applied to any new products added to the cart after the original product is removed.
#7
Product pricing / If there's an override, custom...
Last post by rdcustom - Yesterday at 13:52:16 PM
Since last updates I have a strange issue.

Let's assume I have a 100€ product with a 20€ option:

when the option is added the price is calculated correctly: 120€

BUT

when the product has a price override (ex. 90€)
the option is added twice (40€ instead of 20€)

instead of 110€ total I have 130€

this happens only when there's a price override

How to solve this issue?
#8
Your layoutname for the product is Nessunasovrascrittura. We set 16 chars there, many years ago. I set it now to 48 varchars.
Do NOT modify the Db yourself. Next update it is reverted, because it is a field controlled by vm. You can add your own columns and they should be touched, but do not change existing ones.

But you can fix it just entering the same number as me in the svn for your live installation and use the table updater in the tools.

The file to change is in /administrator/components/com_virtuemart/install/install.sql. Change line 840 to `layout` varchar(48).

But do you really want to use the layout override here? The common way is to leave it blank. But a version before was something broken, there was no blank. So the fix is, that we have a gain the blank version.

and you could not store this layout before, because it has clearly more than 16 chars.
#9
Hazel we send always the canonical url and if the current url is differnt but valid, we add the other url.
But any url added in the header should not reflect the fantasy categories.

mysite.com/canonCat/myproduct-detail =>

<link href="http://mysite.com/canonCat/myproduct-detail" rel="canonical">

if you used mysite.com/validCat/myproduct-detail or if the product is available in other languages, you get an extra line in the header like

<link href="/validCat/myproduct-detail" rel="alternate" hreflang="de-DE">

So your whole text does not fit here, because your almost last sentence says it

QuoteUsing the canonical meta tag: The canonical meta tag can be added to the HTML code of pages to point Google to the preferred version of the page.

So if you enter mysite.com/fantasyCat/myproduct-detail

your header will show <link href="http://mysite.com/canonCat/myproduct-detail" rel="canonical">
#10
Because you should not choose the vat in the product edit. So you are using the old 90s system. VM2 and higher works with categories. So a simple job has one vat rule and thats it, no more config needed.

https://docs.virtuemart.net/manual/general-concepts/tax-and-calculation-rules-overview