diff --git a/htdocs/commande/list.php b/htdocs/commande/list.php index 26c8a5beef9..0e4cd66e323 100644 --- a/htdocs/commande/list.php +++ b/htdocs/commande/list.php @@ -715,7 +715,7 @@ if ($resql) // Get local and virtual stock and store it into cache if (empty($productstat_cache[$generic_commande->lines[$lig]->fk_product])) { - $generic_product->load_stock(); + $generic_product->load_stock('nobatch'); //$generic_product->load_virtual_stock(); Already included into load_stock $productstat_cache[$generic_commande->lines[$lig]->fk_product]['stock_reel'] = $generic_product->stock_reel; $productstat_cachevirtual[$generic_commande->lines[$lig]->fk_product]['stock_reel'] = $generic_product->stock_theorique; @@ -723,7 +723,7 @@ if ($resql) $generic_product->stock_reel = $productstat_cache[$generic_commande->lines[$lig]->fk_product]['stock_reel']; $generic_product->stock_theorique = $productstat_cachevirtual[$generic_commande->lines[$lig]->fk_product]['stock_reel'] = $generic_product->stock_theorique; } - + if (empty($conf->global->SHIPPABLE_ORDER_ICON_IN_LIST)) // Default code. Default is when this option is not set, setting it create strange result { $text_info .= $generic_commande->lines[$lig]->qty.' X '.$generic_commande->lines[$lig]->ref.' '.dol_trunc($generic_commande->lines[$lig]->product_label, 25); diff --git a/htdocs/install/mysql/migration/3.9.0-4.0.0.sql b/htdocs/install/mysql/migration/3.9.0-4.0.0.sql index 832107e7f6f..fffca45c77e 100644 --- a/htdocs/install/mysql/migration/3.9.0-4.0.0.sql +++ b/htdocs/install/mysql/migration/3.9.0-4.0.0.sql @@ -66,6 +66,8 @@ ALTER TABLE llx_product ADD COLUMN height_units tinyint DEFAULT NULL; ALTER TABLE llx_product ADD COLUMN default_vat_code varchar(10) after cost_price; +ALTER TABLE llx_product MODIFY COLUMN stock real; + CREATE TABLE llx_categorie_user ( fk_categorie integer NOT NULL, diff --git a/htdocs/install/mysql/tables/llx_product.sql b/htdocs/install/mysql/tables/llx_product.sql index 9d3446d696d..8b43844498e 100755 --- a/htdocs/install/mysql/tables/llx_product.sql +++ b/htdocs/install/mysql/tables/llx_product.sql @@ -79,7 +79,7 @@ create table llx_product surface_units tinyint DEFAULT NULL, volume float DEFAULT NULL, volume_units tinyint DEFAULT NULL, - stock integer, -- Current physical stock (dernormalized field) + stock real, -- Current physical stock (dernormalized field) pmp double(24,8) DEFAULT 0 NOT NULL, -- To store valuation of stock calculated using average price method, for this product fifo double(24,8), -- To store valuation of stock calculated using fifo method, for this product lifo double(24,8), -- To store valuation of stock calculated using lifo method, for this product diff --git a/htdocs/install/mysql/tables/llx_stock_mouvement.sql b/htdocs/install/mysql/tables/llx_stock_mouvement.sql index 3db9a4475cd..3ac42c0c852 100644 --- a/htdocs/install/mysql/tables/llx_stock_mouvement.sql +++ b/htdocs/install/mysql/tables/llx_stock_mouvement.sql @@ -24,8 +24,8 @@ create table llx_stock_mouvement datem datetime, -- Date and hour of movement fk_product integer NOT NULL, -- Id of product batch varchar(30) DEFAULT NULL, -- Lot or serial number - eatby date DEFAULT NULL, -- Eatby date - sellby date DEFAULT NULL, -- Sellby date + eatby date DEFAULT NULL, -- Eatby date (deprecated, we should get value from batch number in table llx_product_lot) + sellby date DEFAULT NULL, -- Sellby date (deprecated, we should get value from batch number in table llx_product_lot) fk_entrepot integer NOT NULL, -- Id warehouse value real, -- Qty of movement price double(24,8) DEFAULT 0, -- Entry price (used to calculate PMP, FIFO or LIFO value) diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index 685a29cb8e1..f93e5254ce0 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -1850,7 +1850,7 @@ class Product extends CommonObject } } - // We should not load stock at each fetch. If someone need stock, he must call load_stock after fetch. + // We should not load stock during the fetch. If someone need stock of product, he must call load_stock after fetching product. //$res=$this->load_stock(); // instead we just init the stock_warehouse array $this->stock_warehouse = array(); @@ -2847,7 +2847,7 @@ class Product extends CommonObject //print "XXX We add id=".$id." - label=".$label." - nb=".$nb." - multiply=".$multiply." fullpath=".$compl_path.$label."\n"; $this->fetch($id); // Load product - $this->load_stock(); // Load stock + $this->load_stock('nobatch,novirtual'); // Load stock to get true this->stock_reel $this->res[]= array( 'id'=>$id, // Id product 'id_parent'=>$id_parent, diff --git a/htdocs/product/list.php b/htdocs/product/list.php index 1df8e667569..3ed4673a95d 100644 --- a/htdocs/product/list.php +++ b/htdocs/product/list.php @@ -142,8 +142,8 @@ $arrayfields=array( 'p.minbuyprice'=>array('label'=>$langs->trans("BuyingPriceMinShort"), 'checked'=>1, 'enabled'=>(! empty($user->rights->fournisseur->lire))), 'p.seuil_stock_alerte'=>array('label'=>$langs->trans("StockLimit"), 'checked'=>0, 'enabled'=>(! empty($conf->stock->enabled) && $user->rights->stock->lire && $contextpage != 'service')), 'p.desiredstock'=>array('label'=>$langs->trans("DesiredStock"), 'checked'=>1, 'enabled'=>(! empty($conf->stock->enabled) && $user->rights->stock->lire && $contextpage != 'service')), - 'p.tobatch'=>array('label'=>$langs->trans("ManageLotSerial"), 'checked'=>0, 'enabled'=>(! empty($conf->productbatch->enabled))), 'p.stock'=>array('label'=>$langs->trans("PhysicalStock"), 'checked'=>1, 'enabled'=>(! empty($conf->stock->enabled) && $user->rights->stock->lire && $contextpage != 'service')), + 'p.tobatch'=>array('label'=>$langs->trans("ManageLotSerial"), 'checked'=>0, 'enabled'=>(! empty($conf->productbatch->enabled))), 'p.accountancy_code_sell'=>array('label'=>$langs->trans("ProductAccountancySellCode"), 'checked'=>0), 'p.accountancy_code_buy'=>array('label'=>$langs->trans("ProductAccountancyBuyCode"), 'checked'=>0), 'p.datec'=>array('label'=>$langs->trans("DateCreation"), 'checked'=>0, 'position'=>500), @@ -428,8 +428,8 @@ else if (! empty($arrayfields['p.minbuyprice']['checked'])) print_liste_field_titre($arrayfields['p.minbuyprice']['label'], $_SERVER["PHP_SELF"],"","",$param,'align="right"',$sortfield,$sortorder); if (! empty($arrayfields['p.seuil_stock_alerte']['checked'])) print_liste_field_titre($arrayfields['p.seuil_stock_alerte']['label'], $_SERVER["PHP_SELF"],"p.seuil_stock_alerte","",$param,'align="right"',$sortfield,$sortorder); if (! empty($arrayfields['p.desiredstock']['checked'])) print_liste_field_titre($arrayfields['p.desiredstock']['label'], $_SERVER["PHP_SELF"],"p.desiredstock","",$param,'align="right"',$sortfield,$sortorder); - if (! empty($arrayfields['p.tobatch']['checked'])) print_liste_field_titre($arrayfields['p.tobatch']['label'], $_SERVER["PHP_SELF"],"p.tobatch","",$param,'align="center"',$sortfield,$sortorder); if (! empty($arrayfields['p.stock']['checked'])) print_liste_field_titre($arrayfields['p.stock']['label'], $_SERVER["PHP_SELF"],"p.stock","",$param,'align="right"',$sortfield,$sortorder); + if (! empty($arrayfields['p.tobatch']['checked'])) print_liste_field_titre($arrayfields['p.tobatch']['label'], $_SERVER["PHP_SELF"],"p.tobatch","",$param,'align="center"',$sortfield,$sortorder); if (! empty($arrayfields['p.accountancy_code_sell']['checked'])) print_liste_field_titre($arrayfields['p.accountancy_code_sell']['label'], $_SERVER["PHP_SELF"],"p.accountancy_code_sell","",$param,'',$sortfield,$sortorder); if (! empty($arrayfields['p.accountancy_code_buy']['checked'])) print_liste_field_titre($arrayfields['p.accountancy_code_buy']['label'], $_SERVER["PHP_SELF"],"p.accountancy_code_buy","",$param,'',$sortfield,$sortorder); if (is_array($extrafields->attribute_label) && count($extrafields->attribute_label)) @@ -515,11 +515,11 @@ else print ' '; print ''; } - // To batch - if (! empty($arrayfields['p.tobatch']['checked'])) print ''.$form->selectyesno($search_tobatch, '', '', '', 1).''; // Stock if (! empty($arrayfields['p.stock']['checked'])) print ' '; - // Accountancy code sell + // To batch + if (! empty($arrayfields['p.tobatch']['checked'])) print ''.$form->selectyesno($search_tobatch, '', '', '', 1).''; + // Accountancy code sell if (! empty($arrayfields['p.accountancy_code_sell']['checked'])) print ''; // Accountancy code sell if (! empty($arrayfields['p.accountancy_code_buy']['checked'])) print ''; @@ -600,7 +600,16 @@ else $product_static->status_buy = $objp->tobuy; $product_static->status = $objp->tosell; $product_static->entity = $objp->entity; - + + if (! empty($conf->stock->enabled) && $user->rights->stock->lire && $type != 1) // To optimize call of load_stock + { + if ($objp->fk_product_type != 1) // Not a service + { + $product_static->load_stock('nobatch'); // Load stock_reel + stock_warehouse. This also call load_virtual_stock() + } + } + + $var=!$var; print ''; @@ -681,15 +690,6 @@ else print ''; } - if (! empty($conf->stock->enabled) && $user->rights->stock->lire && $type != 1) // To optimize call of load_stock - { - if ($objp->fk_product_type != 1) // Not a service - { - $product_static->id = $objp->rowid; - $product_static->load_stock('nobatch'); // Load stock_reel + stock_warehouse + batch detail. This also call load_virtual_stock() - } - } - // Limit alert if (! empty($arrayfields['p.seuil_stock_alerte']['checked'])) { @@ -710,13 +710,6 @@ else } print ''; } - // Lot/Serial - if (! empty($arrayfields['p.tobatch']['checked'])) - { - print ''; - print yn($objp->tobatch); - print ''; - } // Stock if (! empty($arrayfields['p.stock']['checked'])) { @@ -728,6 +721,13 @@ else } print ''; } + // Lot/Serial + if (! empty($arrayfields['p.tobatch']['checked'])) + { + print ''; + print yn($objp->tobatch); + print ''; + } // Accountancy code sell if (! empty($arrayfields['p.accountancy_code_sell']['checked'])) print ''.$objp->accountancy_code_sell.''; // Accountancy code sell diff --git a/htdocs/product/stock/class/mouvementstock.class.php b/htdocs/product/stock/class/mouvementstock.class.php index 652c6d9dc36..d34668e2519 100644 --- a/htdocs/product/stock/class/mouvementstock.class.php +++ b/htdocs/product/stock/class/mouvementstock.class.php @@ -252,25 +252,12 @@ class MouvementStock extends CommonObject // Calculate new PMP. $newpmp=0; - //$newpmpwarehouse=0; if (! $error) { // Note: PMP is calculated on stock input only (type of movement = 0 or 3). If type == 0 or 3, qty should be > 0. // Note: Price should always be >0 or 0. PMP should be always >0 (calculated on input) if (($type == 0 || $type == 3) && $price > 0) { - // If we will change PMP for the warehouse we edit and the product, we must first check/clean that PMP is defined - // on every stock entry with old value (so global updated value will match recalculated value from product_stock) - /* $sql = "UPDATE ".MAIN_DB_PREFIX."product_stock SET pmp = ".($oldpmp?$oldpmp:'0'); - $sql.= " WHERE pmp = 0 AND fk_product = ".$fk_product; - dol_syslog(get_class($this)."::_create", LOG_DEBUG); - $resql=$this->db->query($sql); - if (! $resql) - { - $this->errors[]=$this->db->lasterror(); - $error = -4; - } - */ $oldqtytouse=($oldqty >= 0?$oldqty:0); // We make a test on oldpmp>0 to avoid to use normal rule on old data with no pmp field defined if ($oldpmp > 0) $newpmp=price2num((($oldqtytouse * $oldpmp) + ($qty * $price)) / ($oldqtytouse + $qty), 'MU'); @@ -278,13 +265,8 @@ class MouvementStock extends CommonObject { $newpmp=$price; // For this product, PMP was not yet set. We set it to input price. } - /* - $oldqtywarehousetouse=$oldqtywarehouse; - if ($oldpmpwarehouse > 0) $newpmpwarehouse=price2num((($oldqtywarehousetouse * $oldpmpwarehouse) + ($qty * $price)) / ($oldqtywarehousetouse + $qty), 'MU'); - else $newpmpwarehouse=$price; - */ - //print "oldqtytouse=".$oldqtytouse." oldpmp=".$oldpmp." oldqtywarehousetouse=".$oldqtywarehousetouse." oldpmpwarehouse=".$oldpmpwarehouse." "; - //print "qty=".$qty." newpmp=".$newpmp." newpmpwarehouse=".$newpmpwarehouse; + //print "oldqtytouse=".$oldqtytouse." oldpmp=".$oldpmp." oldqtywarehousetouse=".$oldqtywarehousetouse." "; + //print "qty=".$qty." newpmp=".$newpmp; //exit; } else if ($type == 1 || $type == 2) @@ -295,7 +277,6 @@ class MouvementStock extends CommonObject else { $newpmp = $oldpmp; - //$newpmpwarehouse = $oldpmpwarehouse; } } @@ -339,11 +320,13 @@ class MouvementStock extends CommonObject // Update PMP and denormalized value of stock qty at product level if (! $error) { - $sql = "UPDATE ".MAIN_DB_PREFIX."product SET pmp = ".$newpmp.", stock = ".$this->db->ifsql("stock IS NULL", 0, "stock") . " + ".$qty; + //$sql = "UPDATE ".MAIN_DB_PREFIX."product SET pmp = ".$newpmp.", stock = ".$this->db->ifsql("stock IS NULL", 0, "stock") . " + ".$qty; + //$sql.= " WHERE rowid = ".$fk_product; + // Update pmp + denormalized fields because we change content of produt_stock. Warning: Do not use "SET p.stock", does not works with pgsql + $sql = "UPDATE ".MAIN_DB_PREFIX."product as p SET p.pmp = ".$newpmp.", "; + $sql.= " stock=(SELECT SUM(ps.reel) FROM llx_product_stock ps WHERE ps.fk_product = p.rowid)"; $sql.= " WHERE rowid = ".$fk_product; - // May be this request is better: - // UPDATE llx_product p SET p.stock= (SELECT SUM(ps.reel) FROM llx_product_stock ps WHERE ps.fk_product = p.rowid); - + print $sql; dol_syslog(get_class($this)."::_create", LOG_DEBUG); $resql=$this->db->query($sql); if (! $resql) diff --git a/htdocs/product/stock/massstockmove.php b/htdocs/product/stock/massstockmove.php index 7d62699b425..c7b7ed41034 100644 --- a/htdocs/product/stock/massstockmove.php +++ b/htdocs/product/stock/massstockmove.php @@ -185,7 +185,7 @@ if ($action == 'createmovements') { $result=$product->fetch($id_product); - $product->load_stock(); // Load array product->stock_warehouse + $product->load_stock('novirtual'); // Load array product->stock_warehouse // Define value of products moved $pricesrc=0; diff --git a/htdocs/product/stock/mouvement.php b/htdocs/product/stock/mouvement.php index 190bf676c8a..29ee33c56eb 100644 --- a/htdocs/product/stock/mouvement.php +++ b/htdocs/product/stock/mouvement.php @@ -207,11 +207,10 @@ if ($action == "transfert_stock" && ! $cancel) $db->begin(); - $product->load_stock(); // Load array product->stock_warehouse + $product->load_stock('novirtual'); // Load array product->stock_warehouse // Define value of products moved $pricesrc=0; - //if (isset($product->stock_warehouse[GETPOST("id_entrepot_source")]->pmp)) $pricesrc=$product->stock_warehouse[GETPOST("id_entrepot_source")]->pmp; if (isset($product->pmp)) $pricesrc=$product->pmp; $pricedest=$pricesrc; diff --git a/htdocs/product/stock/product.php b/htdocs/product/stock/product.php index de30bc3d0b6..05b0aecf5b6 100644 --- a/htdocs/product/stock/product.php +++ b/htdocs/product/stock/product.php @@ -185,7 +185,7 @@ if ($action == "correct_stock" && ! $cancel) // Transfer stock from a warehouse to another warehouse if ($action == "transfert_stock" && ! $cancel) { - if (! (GETPOST("id_entrepot_source",'int') > 0) || ! (GETPOST("id_entrepot_destination",'int') > 0)) + if (! (GETPOST("id_entrepot",'int') > 0) || ! (GETPOST("id_entrepot_destination",'int') > 0)) { setEventMessages($langs->trans("ErrorFieldRequired", $langs->transnoentitiesnoconv("Warehouse")), null, 'errors'); $error++; @@ -197,7 +197,7 @@ if ($action == "transfert_stock" && ! $cancel) $error++; $action='transfert'; } - if (GETPOST("id_entrepot_source",'int') == GETPOST("id_entrepot_destination",'int')) + if (GETPOST("id_entrepot",'int') == GETPOST("id_entrepot_destination",'int')) { setEventMessages($langs->trans("ErrorSrcAndTargetWarehouseMustDiffers"), null, 'errors'); $error++; @@ -225,7 +225,7 @@ if ($action == "transfert_stock" && ! $cancel) $db->begin(); - $object->load_stock(); // Load array product->stock_warehouse + $object->load_stock('novirtual'); // Load array product->stock_warehouse // Define value of products moved $pricesrc=0; @@ -254,7 +254,7 @@ if ($action == "transfert_stock" && ! $cancel) } else { - $srcwarehouseid=GETPOST('id_entrepot_source','int'); + $srcwarehouseid=GETPOST('id_entrepot','int'); $batch=GETPOST('batch_number'); $eatby=$d_eatby; $sellby=$d_sellby; @@ -291,7 +291,7 @@ if ($action == "transfert_stock" && ! $cancel) // Remove stock $result1=$object->correct_stock( $user, - GETPOST("id_entrepot_source"), + GETPOST("id_entrepot"), GETPOST("nbpiece"), 1, GETPOST("label"), @@ -390,6 +390,7 @@ if ($id > 0 || $ref) { $object = new Product($db); $result = $object->fetch($id,$ref); + $object->load_stock(); $help_url='EN:Module_Stocks_En|FR:Module_Stock|ES:Módulo_Stocks'; @@ -480,7 +481,6 @@ if ($id > 0 || $ref) print ''; // Real stock - $object->load_stock(); $text_stock_options = ''; $text_stock_options.= (! empty($conf->global->STOCK_CALCULATE_ON_SHIPMENT)?$langs->trans("DeStockOnShipment").'
':''); $text_stock_options.= (! empty($conf->global->STOCK_CALCULATE_ON_VALIDATE_ORDER)?$langs->trans("DeStockOnValidateOrder").'
':''); @@ -702,9 +702,10 @@ if ($resql) $entrepotstatic->id=$obj->rowid; $entrepotstatic->libelle=$obj->label; $entrepotstatic->lieu=$obj->lieu; + $stock_real = round($obj->reel, 10); print ''; print ''.$entrepotstatic->getNomUrl(1).''; - print ''.$obj->reel.($obj->reel<0?' '.img_warning():'').''; + print ''.$stock_real.($stock_real < 0 ?' '.img_warning():'').''; // PMP print ''.(price2num($object->pmp)?price2num($object->pmp,'MU'):'').''; // Value purchase diff --git a/htdocs/webservices/server_productorservice.php b/htdocs/webservices/server_productorservice.php index 43facbb24fa..aa43dbb185c 100644 --- a/htdocs/webservices/server_productorservice.php +++ b/htdocs/webservices/server_productorservice.php @@ -411,7 +411,8 @@ function getProductOrService($authentication,$id='',$ref='',$ref_ext='',$lang='' 'localtax1_tx' => $product->localtax1_tx, 'localtax2_tx' => $product->localtax2_tx, - 'stock_real' => $product->stock_reel, + 'stock_real' => $product->stock_reel, + 'stock_virtual' => $product->stock_theorique, 'stock_alert' => $product->seuil_stock_alerte, 'pmp' => $product->pmp, 'import_key' => $product->import_key, @@ -572,7 +573,7 @@ function createProductOrService($authentication,$product) require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php'; $savstockreal=$newobject->stock_reel; - $newobject->load_stock(); // This overwrite ->stock_reel + $newobject->load_stock('novirtual,nobatch'); // This overwrite ->stock_reel, surely 0 because we have just created product $getstockreal = $newobject->stock_reel; if ($savstockreal != $getstockreal) @@ -741,7 +742,7 @@ function updateProductOrService($authentication,$product) require_once DOL_DOCUMENT_ROOT.'/product/stock/class/entrepot.class.php'; $savstockreal=$newobject->stock_reel; - $newobject->load_stock(); // This overwrite ->stock_reel + $newobject->load_stock('novirtual,nobatch'); // This overwrite ->stock_reel $getstockreal = $newobject->stock_reel; if ($savstockreal != $getstockreal)