diff --git a/htdocs/langs/en_US/products.lang b/htdocs/langs/en_US/products.lang index 4d296f0c79da7..14144ada4af9a 100644 --- a/htdocs/langs/en_US/products.lang +++ b/htdocs/langs/en_US/products.lang @@ -432,3 +432,5 @@ ConfirmEditExtrafield = Select the extrafield you want modify ConfirmEditExtrafieldQuestion = Are you sure you want to modify this extrafield? ModifyValueExtrafields = Modify value of an extrafield OrProductsWithCategories=Or products with tags/categories +WarningTransferBatchStockMouvToGlobal = If you want to deserialize this product, all its serialized stock will be transformed into global stock +WarningConvertFromBatchToSerial=If you currently have a quantity higher or equal to 2 for the product, switching to this choice means you will still have a product with different objects of the same batch (while you want a unique serial number). The duplicate will remain until an inventory or a manual stock movement to fix this is done. diff --git a/htdocs/langs/en_US/stocks.lang b/htdocs/langs/en_US/stocks.lang index 1a9233e907708..1d13fe6e1bc4a 100644 --- a/htdocs/langs/en_US/stocks.lang +++ b/htdocs/langs/en_US/stocks.lang @@ -168,6 +168,7 @@ qtyToTranferLotIsNotEnough=You don't have enough stock, for this lot number, fro ShowWarehouse=Show warehouse MovementCorrectStock=Stock correction for product %s MovementTransferStock=Stock transfer of product %s into another warehouse +BatchStockMouvementAddInGlobal=Batch stock move into global stock (product doesn't use batch anymore) InventoryCodeShort=Inv./Mov. code NoPendingReceptionOnSupplierOrder=No pending reception due to open purchase order ThisSerialAlreadyExistWithDifferentDate=This lot/serial number (%s) already exists but with different eatby or sellby date (found %s but you enter %s). @@ -322,6 +323,8 @@ BatchNotFound=Lot / serial not found for this product StockEntryDate=Date of
entry in stock StockMovementWillBeRecorded=Stock movement will be recorded StockMovementNotYetRecorded=Stock movement will not be affected by this step + + WarningThisWIllAlsoDeleteStock=Warning, this will also destroy all quantities in stock in the warehouse ValidateInventory=Inventory validation IncludeSubWarehouse=Include sub-warehouse ? @@ -331,3 +334,4 @@ ConfirmDeleteBatch=Are you sure you want to delete lot/serial ? WarehouseUsage=Warehouse usage InternalWarehouse=Internal warehouse ExternalWarehouse=External warehouse + diff --git a/htdocs/langs/fr_FR/stocks.lang b/htdocs/langs/fr_FR/stocks.lang index 776777358b73e..6af8aca75e39e 100644 --- a/htdocs/langs/fr_FR/stocks.lang +++ b/htdocs/langs/fr_FR/stocks.lang @@ -168,6 +168,7 @@ qtyToTranferLotIsNotEnough=Vous n'avez pas assez de stock, pour ce numéro de lo ShowWarehouse=Afficher entrepôt MovementCorrectStock=Correction du stock pour le produit %s MovementTransferStock=Transfert de stock du produit %s dans un autre entrepôt +BatchStockMouvementAddInGlobal=Batch stock move into global stock (product doesn't use batch anymore) InventoryCodeShort=Code Inv./Mouv. NoPendingReceptionOnSupplierOrder=Pas de réception en attente consécutive à des commandes fournisseurs ThisSerialAlreadyExistWithDifferentDate=Ce lot/numéro de série (%s) existe déjà mais avec des dates de consommation ou péremption différente (trouvé %s mais vous avez entré %s). diff --git a/htdocs/product/card.php b/htdocs/product/card.php index a409f3ba42d77..6dba53bc686a9 100644 --- a/htdocs/product/card.php +++ b/htdocs/product/card.php @@ -1944,7 +1944,45 @@ if ($object->isProduct() || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) { print ''.$langs->trans("ManageLotSerial").''; $statutarray = array('0' => $langs->trans("ProductStatusNotOnBatch"), '1' => $langs->trans("ProductStatusOnBatch"), '2' => $langs->trans("ProductStatusOnSerial")); - print $form->selectarray('status_batch', $statutarray, (GETPOSTISSET('status_batch') ? GETPOST('status_batch') : $object->status_batch)); + + print $form->selectarray('status_batch', $statutarray, GETPOSTISSET('status_batch') ? GETPOST('status_batch') : $object->status_batch); + + print ''; + + print ''; + + if ($object->status_batch) { + // Display message to make user know that all batch will be move into global stock + print ''; + + // Display message to explain that if the product currently have a quantity higher or equal to 2, switching to this choice means we will still have a product with different objects of the same batch (while we want a unique serial number) + if ($object->status_batch == 1) { + print ''; + } + } + print ''; if (!empty($object->status_batch) || !empty($conf->use_javascript_ajax)) { $langs->load("admin"); diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index a548c8705d869..99c2d30459274 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -1296,6 +1296,42 @@ public function update($id, $user, $notrigger = false, $action = 'update', $upda } } + if (!$this->hasbatch() && $this->oldcopy->hasbatch()) { + // Selection of all product stock mouvements that contains batchs + $sql = 'SELECT pb.qty, pb.fk_entrepot, pb.batch FROM '.MAIN_DB_PREFIX.'product_batch as pb'; + $sql.= ' INNER JOIN '.MAIN_DB_PREFIX.'product_stock as ps ON (ps.rowid = batch.fk_product_stock)'; + $sql.= ' WHERE ps.fk_product = '.(int) $this->id; + + $resql = $this->db->query($sql); + if ($resql) { + $inventorycode = dol_print_date(dol_now(), '%Y%m%d%H%M%S'); + + while ($obj = $this->db->fetch_object($resql)) { + $value = $obj->qty; + $fk_entrepot = $obj->fk_entrepot; + $price = 0; + $dlc = ''; + $dluo = ''; + $batch = $obj->batch; + + // To know how to revert stockMouvement (add or remove) + $addOremove = $value > 0 ? 1 : 0; // 1 if remove, 0 if add + $label = $langs->trans('BatchStockMouvementAddInGlobal'); + $res = $this->correct_stock_batch($user, $fk_entrepot, abs($value), $addOremove, $label, $price, $dlc, $dluo, $batch, $inventorycode, '', null, 0, null, true); + + if ($res > 0) { + $label = $langs->trans('BatchStockMouvementAddInGlobal'); + $res = $this->correct_stock($user, $fk_entrepot, abs($value), (int) empty($addOremove), $label, $price, $inventorycode, '', null, 0); + if ($res < 0) { + $error++; + } + } else { + $error++; + } + } + } + } + // Actions on extra fields if (!$error) { $result = $this->insertExtraFields(); @@ -5535,9 +5571,10 @@ public function correct_stock($user, $id_entrepot, $nbpiece, $movement, $label = * @param int $origin_id Origin id of element * @param int $disablestockchangeforsubproduct Disable stock change for sub-products of kit (usefull only if product is a subproduct) * @param Extrafields $extrafields Array of extrafields + * @param boolean $force_update_batch Force update batch * @return int <0 if KO, >0 if OK */ - public function correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $dlc = '', $dluo = '', $lot = '', $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null) + public function correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $label = '', $price = 0, $dlc = '', $dluo = '', $lot = '', $inventorycode = '', $origin_element = '', $origin_id = null, $disablestockchangeforsubproduct = 0, $extrafields = null, $force_update_batch = false) { // phpcs:enable if ($id_entrepot) { @@ -5557,7 +5594,7 @@ public function correct_stock_batch($user, $id_entrepot, $nbpiece, $movement, $l $movementstock = new MouvementStock($this->db); $movementstock->setOrigin($origin_element, $origin_id); // Set ->origin_type and ->fk_origin - $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct); + $result = $movementstock->_create($user, $this->id, $id_entrepot, $op[$movement], $movement, $price, $label, $inventorycode, '', $dlc, $dluo, $lot, false, 0, $disablestockchangeforsubproduct, 0, $force_update_batch); if ($result >= 0) { if ($extrafields) { diff --git a/htdocs/product/stock/class/mouvementstock.class.php b/htdocs/product/stock/class/mouvementstock.class.php index 2291effe8c467..bada9c6194ecb 100644 --- a/htdocs/product/stock/class/mouvementstock.class.php +++ b/htdocs/product/stock/class/mouvementstock.class.php @@ -175,9 +175,10 @@ public function __construct($db) * @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) + * @param boolean $force_update_batch Allows to add batch stock movement even if $product doesn't use batch anymore * @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, $donotcleanemptylines = 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, $force_update_batch = false) { // phpcs:enable global $conf, $langs; @@ -559,7 +560,7 @@ public function _create($user, $fk_product, $entrepot_id, $qty, $type, $price = } // Update detail of stock for the lot. - if (!$error && isModEnabled('productbatch') && $product->hasbatch() && !$skip_batch) { + if (!$error && isModEnabled('productbatch') && (($product->hasbatch() && !$skip_batch) || $force_update_batch)) { if ($id_product_batch > 0) { $result = $this->createBatch($id_product_batch, $qty); if ($result == -2 && $fk_product_stock > 0) { // The entry for this product batch does not exists anymore, bu we already have a llx_product_stock, so we recreate the batch entry in product_batch