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;
}
If you store a parent, which has the extra tab for children, than it takes this values for the children.
that works most time correctly, but if someone buys something meanwhile, it wont work 100%.
But that is a common problem for our stock feature.
Quote from: Milbo on May 23, 2024, 21:35:45 PMIf you store a parent, which has the extra tab for children, than it takes this values for the children.
that works most time correctly, but if someone buys something meanwhile, it wont work 100%.
But that is a common problem for our stock feature.
Thanks Max, for now I added the logging. But there is one thing that bothers me in this logging that is that when you log it it will show that the child products get set to 0 and then after it somewhere else sets the child products back to it's current stock values again. But I'd like to log that as well, do you know where I can find it?