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 img_warning().' '.$langs->trans("WarningConvertFromBatchToSerial").'';
+
+ print '';
+ print img_warning().' '.$langs->trans("WarningTransferBatchStockMouvToGlobal").'';
+
+ 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