VirtueMart Forum

VirtueMart 2 + 3 + 4 => Product creation => Topic started by: tommo27 on June 16, 2016, 01:36:39 AM

Title: Associate multiple products / Bulk products / Composite Products
Post by: tommo27 on June 16, 2016, 01:36:39 AM
Hello everybody, wanted to share something with you all in the hope somebody else will find this useful.

I've developed a few eCommerce solutions with Virtuemart for clients now and a common request or requirement for the inventory is the linking of multiple products in terms of quantity.  For example, if you sell one product, you may need to adjust the quantity on 'related' products and a parent child structure is not appropriate.

One of the sites I am working on sells a large number of hand made items covering a lot of different categories.  Each of the items are made using one or more smaller items that are not for sale.  This essentially makes all products sold composite as they are built of one or more smaller products.  When a product is sold on the site, the quantity needs to be adjusted for the products that make this one, not just the quantity here.  Product variations and child products came sort of close but no default configuration was able to provide this type of setup.

I've spent some time looking around and can see there are many paid for solutions to this problem but isn't really anything free and simple.  As such, I've put together a little modification that looks for product SKU's in the internal notes field on the back-end and makes the same quantity adjustment to those products.  Imaging somebody purchases product 0001, you can specify in the internal notes that this product made up of 0002, 0003 and 0004 for example.  When the quantity is adjusted on the sold item (0001) then the same adjustment is then made on the others listed.  You specify this as sku-code[SKU1,ABCD,0001,AB-01,GT=EC-00] where each of the SKU's in the square bracket will be quantity adjusted also.

It is a little simple but gets the job done nicely.  I'd ideally like to improve on this and integrate it much more - hopefully with a simple extension installer but that will have to wait.  I've put full instructions together at https://www.tbdev.co.uk/library/11-php-programming/33-associating-products-in-virtuemart (https://www.tbdev.co.uk/library/11-php-programming/33-associating-products-in-virtuemart) but the code is below too.

All tested and working on two different setups; in both cases, the smaller products that make the sellable products are left unpublished in an unpublished custom category which allows full inventory management of all stock, regardless of what the customer sees.  Let me know what you think and if it helps out at all.


/administrator/components/com_virtuemart/models/product.php

Change the updateStockInDB function to the following (line 2527)

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


Change the sanitize field comment and the line below to:


//sanitize fields
        if ($productID == NULL){
            $id = (int)$product->virtuemart_product_id;
        } else {
            $id = (int)$productID;
        }





/administrator/components/com_virtuemart/models/order.php

Add the following code to the end of the handleStockAfterStatusChangedPerProduct function on line 1207 (add it after the if / else block):


$db = JFactory::getDBO();
        $q = ('SELECT `intnotes` FROM `#__virtuemart_products` ');
        $q .= ' WHERE `virtuemart_product_id` = "'.$virtuemart_product_id.'"';
        $db->setQuery($q);
        if($vmOId = $db->loadResult()){

            // Have the internal notes for this product.  We now need to search through and see if there is a pattern.

            // see if pattern appears in notes
            if (strpos($vmOId, "sku-code") !== FALSE){


                $matches = array();
                preg_match('(\[.*\])', $vmOId, $matches); // Looks for everything between [ and ] and puts it into an array.
                $match = implode("", $matches); // Creates a single string of the sku's with commas if any between

                // $matches now contains all allowed characters between [ and ].  Strip these off each end.
                $match = trim($match, '[');  // Removes the starting [
                $match = trim($match, ']');  // Removes the closing ]


                // Convert new list into an array of SKU's
                $list = explode(",", $match);  // Creates a new array based on the pattern match.  specified comma defines new array element.

                // Can't see a convenient way to find ID's by SKU so sql here
                foreach ($list as $v){  // For each product SKU in the list

                    $db = JFactory::getDBO();
                    $q = ('SELECT `virtuemart_product_id` FROM `#__virtuemart_products` ');
                    $q .= ' WHERE `product_sku` = "'.$v.'"';
                    $db->setQuery($q);  //  Get it's product ID

                    if ($vmOId = $db->loadResult()){  // Now have the ID for this product
                        $productModel->updateStockInDB ($vmOId, $quantity,$product_in_stock,$product_ordered, $vmOId);  // Performs the database update
                    }
                }
            }
        }





/administrator/components/com_virtuemart/views/product/tmpl/product_edit_information

Add the following code just under the "COM_VIRTUEMART_PRODUCT_PRINT_INTNOTES" declaration at line 288:


            <p>You can link this products quantity with any other product in the database.  Include each linked products SKU inside the brackets: <em>sku-code[ ]</em>.  You can also include more than one by seperating them with a comma (,)</p>
            <p>For example, sku-code[0001,0002,0003] - assuming this products sku is not one of these.  When you sell this product, the quantity will also be reduced for these products.</p>





**** UPDATE ****
It would appear that this causes the removal / deletion of orders to fail as a negative number is being passed in the SQL when updateStockInDB is called at the end.  I will pop the solution to this below once I have something elegant but I have personally removed the initial call on my project as I don't want a change of stock at all when an order is deleted.  I would suggest modifying the calling function to include a isOrderDelete Boolean allowing us to check and skip the database update for those that want to keep things as they are.