diff --git a/htdocs/expedition/card.php b/htdocs/expedition/card.php index e873ef6e056..781a5c690cf 100644 --- a/htdocs/expedition/card.php +++ b/htdocs/expedition/card.php @@ -2100,7 +2100,7 @@ if ($action == 'create') { //if ($filter) $sql.= $filter; $sql .= " ORDER BY obj.fk_product"; - dol_syslog("get list of shipment lines", LOG_DEBUG); + dol_syslog("expedition/card.php get list of shipment lines", LOG_DEBUG); $resql = $db->query($sql); if ($resql) { $num = $db->num_rows($resql); diff --git a/htdocs/expedition/class/expedition.class.php b/htdocs/expedition/class/expedition.class.php index a3ce1738fac..77620585b98 100644 --- a/htdocs/expedition/class/expedition.class.php +++ b/htdocs/expedition/class/expedition.class.php @@ -759,7 +759,7 @@ class Expedition extends CommonObject // line without batch detail // We decrement stock of product (and sub-products) -> update table llx_product_stock (key of this table is fk_product+fk_entrepot) and add a movement record. - $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans("ShipmentValidatedInDolibarr", $numref)); + $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans("ShipmentValidatedInDolibarr", $numref), '', '', '', '', 0, '', 1); if ($result < 0) { $error++; @@ -772,7 +772,7 @@ class Expedition extends CommonObject // We decrement stock of product (and sub-products) -> update table llx_product_stock (key of this table is fk_product+fk_entrepot) and add a movement record. // Note: ->fk_origin_stock = id into table llx_product_batch (may be rename into llx_product_stock_batch in another version) - $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans("ShipmentValidatedInDolibarr", $numref), '', $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, $obj->fk_origin_stock); + $result = $mouvS->livraison($user, $obj->fk_product, $obj->fk_entrepot, $qty, $obj->subprice, $langs->trans("ShipmentValidatedInDolibarr", $numref), '', $this->db->jdate($obj->eatby), $this->db->jdate($obj->sellby), $obj->batch, $obj->fk_origin_stock, '', 1); if ($result < 0) { $error++; $this->error = $mouvS->error; @@ -781,6 +781,12 @@ class Expedition extends CommonObject } } } + + // If some stock lines are now 0, we can remove entry into llx_product_stock, but only if there is no child lines into llx_product_batch (detail of batch, because we can imagine + // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot. + $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_stock WHERE reel = 0 AND rowid NOT IN (SELECT fk_product_stock FROM ".MAIN_DB_PREFIX."product_batch as pb)"; + $resql = $this->db->query($sql); + // We do not test error, it can fails if there is child in batch details } else { $this->db->rollback(); $this->error = $this->db->error(); diff --git a/htdocs/product/stock/class/mouvementstock.class.php b/htdocs/product/stock/class/mouvementstock.class.php index b913fd0cf38..5653a564b9e 100644 --- a/htdocs/product/stock/class/mouvementstock.class.php +++ b/htdocs/product/stock/class/mouvementstock.class.php @@ -163,9 +163,10 @@ class MouvementStock extends CommonObject * @param boolean $skip_batch If set to true, stock movement is done without impacting batch record * @param int $id_product_batch Id product_batch (when skip_batch is false and we already know which record of product_batch to use) * @param int $disablestockchangeforsubproduct Disable stock change for sub-products of kit (usefull only if product is a subproduct) + * @param int $donotcleanemptylines Do not clean lines in stock table with qty=0 (because we want to have this done by the caller) * @return int <0 if KO, 0 if fk_product is null or product id does not exists, >0 if OK */ - public function _create($user, $fk_product, $entrepot_id, $qty, $type, $price = 0, $label = '', $inventorycode = '', $datem = '', $eatby = '', $sellby = '', $batch = '', $skip_batch = false, $id_product_batch = 0, $disablestockchangeforsubproduct = 0) + public function _create($user, $fk_product, $entrepot_id, $qty, $type, $price = 0, $label = '', $inventorycode = '', $datem = '', $eatby = '', $sellby = '', $batch = '', $skip_batch = false, $id_product_batch = 0, $disablestockchangeforsubproduct = 0, $donotcleanemptylines = 0) { // phpcs:enable global $conf, $langs; @@ -574,11 +575,13 @@ class MouvementStock extends CommonObject } } - // If stock is now 0, we can remove entry into llx_product_stock, but only if there is no child lines into llx_product_batch (detail of batch, because we can imagine - // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot. - $sql = "DELETE FROM ".$this->db->prefix()."product_stock WHERE reel = 0 AND rowid NOT IN (SELECT fk_product_stock FROM ".$this->db->prefix()."product_batch as pb)"; - $resql = $this->db->query($sql); - // We do not test error, it can fails if there is child in batch details + if (empty($donotcleanemptylines)) { + // If stock is now 0, we can remove entry into llx_product_stock, but only if there is no child lines into llx_product_batch (detail of batch, because we can imagine + // having a lot1/qty=X and lot2/qty=-X, so 0 but we must not loose repartition of different lot. + $sql = "DELETE FROM ".$this->db->prefix()."product_stock WHERE reel = 0 AND rowid NOT IN (SELECT fk_product_stock FROM ".$this->db->prefix()."product_batch as pb)"; + $resql = $this->db->query($sql); + // We do not test error, it can fails if there is child in batch details + } } // Add movement for sub products (recursive call) @@ -764,53 +767,55 @@ class MouvementStock extends CommonObject /** * Decrease stock for product and subproducts * - * @param User $user Object user - * @param int $fk_product Id product - * @param int $entrepot_id Warehouse id - * @param int $qty Quantity - * @param int $price Price - * @param string $label Label of stock movement - * @param integer|string $datem Force date of movement - * @param integer $eatby eat-by date - * @param integer $sellby sell-by date - * @param string $batch batch number - * @param int $id_product_batch Id product_batch - * @param string $inventorycode Inventory code - * @return int <0 if KO, >0 if OK + * @param User $user Object user + * @param int $fk_product Id product + * @param int $entrepot_id Warehouse id + * @param int $qty Quantity + * @param int $price Price + * @param string $label Label of stock movement + * @param integer|string $datem Force date of movement + * @param integer $eatby eat-by date + * @param integer $sellby sell-by date + * @param string $batch batch number + * @param int $id_product_batch Id product_batch + * @param string $inventorycode Inventory code + * @param int $donotcleanemptylines Do not clean lines that remains in stock table with qty=0 (because we want to have this done by the caller) + * @return int <0 if KO, >0 if OK */ - public function livraison($user, $fk_product, $entrepot_id, $qty, $price = 0, $label = '', $datem = '', $eatby = '', $sellby = '', $batch = '', $id_product_batch = 0, $inventorycode = '') + public function livraison($user, $fk_product, $entrepot_id, $qty, $price = 0, $label = '', $datem = '', $eatby = '', $sellby = '', $batch = '', $id_product_batch = 0, $inventorycode = '', $donotcleanemptylines = 0) { global $conf; $skip_batch = empty($conf->productbatch->enabled); - return $this->_create($user, $fk_product, $entrepot_id, (0 - $qty), 2, $price, $label, $inventorycode, $datem, $eatby, $sellby, $batch, $skip_batch, $id_product_batch); + return $this->_create($user, $fk_product, $entrepot_id, (0 - $qty), 2, $price, $label, $inventorycode, $datem, $eatby, $sellby, $batch, $skip_batch, $id_product_batch, 0, $donotcleanemptylines); } /** * Increase stock for product and subproducts * - * @param User $user Object user - * @param int $fk_product Id product - * @param int $entrepot_id Warehouse id - * @param int $qty Quantity - * @param int $price Price - * @param string $label Label of stock movement - * @param integer|string $eatby eat-by date - * @param integer|string $sellby sell-by date - * @param string $batch batch number - * @param integer|string $datem Force date of movement - * @param int $id_product_batch Id product_batch - * @param string $inventorycode Inventory code - * @return int <0 if KO, >0 if OK + * @param User $user Object user + * @param int $fk_product Id product + * @param int $entrepot_id Warehouse id + * @param int $qty Quantity + * @param int $price Price + * @param string $label Label of stock movement + * @param integer|string $eatby eat-by date + * @param integer|string $sellby sell-by date + * @param string $batch batch number + * @param integer|string $datem Force date of movement + * @param int $id_product_batch Id product_batch + * @param string $inventorycode Inventory code + * @param int $donotcleanemptylines Do not clean lines that remains in stock table with qty=0 (because we want to have this done by the caller) + * @return int <0 if KO, >0 if OK */ - public function reception($user, $fk_product, $entrepot_id, $qty, $price = 0, $label = '', $eatby = '', $sellby = '', $batch = '', $datem = '', $id_product_batch = 0, $inventorycode = '') + public function reception($user, $fk_product, $entrepot_id, $qty, $price = 0, $label = '', $eatby = '', $sellby = '', $batch = '', $datem = '', $id_product_batch = 0, $inventorycode = '', $donotcleanemptylines = 0) { global $conf; $skip_batch = empty($conf->productbatch->enabled); - return $this->_create($user, $fk_product, $entrepot_id, $qty, 3, $price, $label, $inventorycode, $datem, $eatby, $sellby, $batch, $skip_batch, $id_product_batch); + return $this->_create($user, $fk_product, $entrepot_id, $qty, 3, $price, $label, $inventorycode, $datem, $eatby, $sellby, $batch, $skip_batch, $id_product_batch, 0, $donotcleanemptylines); } /**