diff --git a/htdocs/core/modules/modProduct.class.php b/htdocs/core/modules/modProduct.class.php index f972c235802..2bd42d64e11 100644 --- a/htdocs/core/modules/modProduct.class.php +++ b/htdocs/core/modules/modProduct.class.php @@ -587,7 +587,7 @@ class modProduct extends DolibarrModules )); $this->import_regex_array[$r] = array_merge($this->import_regex_array[$r], array( - 'p.tobatch' => '^[0|1]$' + 'p.tobatch' => '^[0|1|2]$' )); $this->import_convertvalue_array[$r] = array_merge($this->import_convertvalue_array[$r], array( @@ -679,7 +679,7 @@ class modProduct extends DolibarrModules //clauses copied from import_fields_array if (!empty($conf->stock->enabled)) { $import_sample = array_merge($import_sample, array( - 'p.tobatch'=>"0 (don't use) / 1 (use batch/serial number)", + 'p.tobatch'=>"0 (don't use) / 1 (use batch) / 2 (use serial number)", 'p.seuil_stock_alerte' => '', 'p.pmp' => '0', 'p.desiredstock' => '' diff --git a/htdocs/fourn/commande/dispatch.php b/htdocs/fourn/commande/dispatch.php index 0ee9e87db4d..78e3884e9c4 100644 --- a/htdocs/fourn/commande/dispatch.php +++ b/htdocs/fourn/commande/dispatch.php @@ -855,7 +855,7 @@ if ($id > 0 || !empty($ref)) { // Already dispatched print ''.$products_dispatched[$objp->rowid].''; - if (!empty($conf->productbatch->enabled) && $objp->tobatch == 1) { + if (!empty($conf->productbatch->enabled) && $objp->tobatch > 0) { $type = 'batch'; print ''; print ''; // Qty to dispatch @@ -967,7 +967,7 @@ if ($id > 0 || !empty($ref)) { print ''; print ''; - if (!empty($conf->productbatch->enabled) && $objp->tobatch == 1) { + if (!empty($conf->productbatch->enabled) && $objp->tobatch > 0) { $type = 'batch'; print img_picto($langs->trans('AddStockLocationLine'), 'split.png', 'class="splitbutton" onClick="addDispatchLine('.$i.', \''.$type.'\')"'); } else { diff --git a/htdocs/install/repair.php b/htdocs/install/repair.php index 2a9c2bb11f2..80678abe24f 100644 --- a/htdocs/install/repair.php +++ b/htdocs/install/repair.php @@ -897,7 +897,7 @@ if ($ok && GETPOST('clean_product_stock_batch', 'alpha')) { $sql = "SELECT p.rowid, p.ref, p.tobatch, ps.rowid as psrowid, ps.fk_entrepot, ps.reel, SUM(pb.qty) as reelbatch"; $sql .= " FROM ".MAIN_DB_PREFIX."product as p, ".MAIN_DB_PREFIX."product_stock as ps LEFT JOIN ".MAIN_DB_PREFIX."product_batch as pb ON ps.rowid = pb.fk_product_stock"; $sql .= " WHERE p.rowid = ps.fk_product"; - $sql .= " AND p.tobatch = 1"; + $sql .= " AND p.tobatch > 0"; $sql .= " GROUP BY p.rowid, p.ref, p.tobatch, ps.rowid, ps.fk_entrepot, ps.reel"; $sql .= " HAVING reel != SUM(pb.qty) or SUM(pb.qty) IS NULL"; print $sql; @@ -981,7 +981,7 @@ if ($ok && GETPOST('clean_product_stock_negative_if_batch', 'alpha')) { $sql = "SELECT p.rowid, p.ref, p.tobatch, ps.rowid as psrowid, ps.fk_entrepot, ps.reel, SUM(pb.qty) as reelbatch"; $sql .= " FROM ".MAIN_DB_PREFIX."product as p, ".MAIN_DB_PREFIX."product_stock as ps, ".MAIN_DB_PREFIX."product_batch as pb"; $sql .= " WHERE p.rowid = ps.fk_product AND ps.rowid = pb.fk_product_stock"; - $sql .= " AND p.tobatch = 1"; + $sql .= " AND p.tobatch > 0"; $sql .= " GROUP BY p.rowid, p.ref, p.tobatch, ps.rowid, ps.fk_entrepot, ps.reel"; $sql .= " HAVING reel != SUM(pb.qty)"; $resql = $db->query($sql); diff --git a/htdocs/langs/en_US/productbatch.lang b/htdocs/langs/en_US/productbatch.lang index 3ab83908ff5..36adfd571fb 100644 --- a/htdocs/langs/en_US/productbatch.lang +++ b/htdocs/langs/en_US/productbatch.lang @@ -24,3 +24,5 @@ ProductLotSetup=Setup of module lot/serial ShowCurrentStockOfLot=Show current stock for couple product/lot ShowLogOfMovementIfLot=Show log of movements for couple product/lot StockDetailPerBatch=Stock detail per lot +SerialNumberAlreadyInUse=Serial number %s is already used for product %s +TooManyQtyForSerialNumber=You can only have one product %s for serial number %S diff --git a/htdocs/langs/fr_FR/productbatch.lang b/htdocs/langs/fr_FR/productbatch.lang index 76cba909e2c..94ceb434bfd 100644 --- a/htdocs/langs/fr_FR/productbatch.lang +++ b/htdocs/langs/fr_FR/productbatch.lang @@ -24,3 +24,5 @@ ProductLotSetup=Configuration du module lot/série ShowCurrentStockOfLot=Afficher le stock actuel pour le couple produit / lot ShowLogOfMovementIfLot=Afficher l'historique des mouvements de couple produit / lot StockDetailPerBatch=Stock détaillé par lot +SerialNumberAlreadyInUse=Le numéro de série %s est déjà utilisé pour le produit %s +TooManyQtyForSerialNumber=Vous ne pouvez avoir qu'un produit %s avec le numéro de série %s diff --git a/htdocs/product/card.php b/htdocs/product/card.php index 3eccae1e114..d7080e39417 100644 --- a/htdocs/product/card.php +++ b/htdocs/product/card.php @@ -1084,11 +1084,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { // Batch number management if (!empty($conf->productbatch->enabled)) { print ''.$langs->trans("ManageLotSerial").''; - if (empty($conf->global ->MAIN_ADVANCE_NUMLOT)) { - $statutarray = array('0' => $langs->trans("ProductStatusNotOnBatch"), '1' => $langs->trans("ProductStatusOnBatch")); - } else { - $statutarray = array('0' => $langs->trans("ProductStatusNotOnBatch"), '1' => $langs->trans("ProductStatusOnBatch"), '2' => $langs->trans("ProductStatusOnSerial")); - } + $statutarray = array('0' => $langs->trans("ProductStatusNotOnBatch"), '1' => $langs->trans("ProductStatusOnBatch"), '2' => $langs->trans("ProductStatusOnSerial")); print $form->selectarray('status_batch', $statutarray, GETPOST('status_batch')); print ''; } @@ -1548,11 +1544,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { if ($conf->productbatch->enabled) { if ($object->isProduct() || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) { print ''.$langs->trans("ManageLotSerial").''; - if (empty($conf->global ->MAIN_ADVANCE_NUMLOT)) { - $statutarray = array('0' => $langs->trans("ProductStatusNotOnBatch"), '1' => $langs->trans("ProductStatusOnBatch")); - } else { - $statutarray = array('0' => $langs->trans("ProductStatusNotOnBatch"), '1' => $langs->trans("ProductStatusOnBatch"), '2' => $langs->trans("ProductStatusOnSerial")); - } + $statutarray = array('0' => $langs->trans("ProductStatusNotOnBatch"), '1' => $langs->trans("ProductStatusOnBatch"), '2' => $langs->trans("ProductStatusOnSerial")); print $form->selectarray('status_batch', $statutarray, $object->status_batch); print ''; } @@ -2040,11 +2032,7 @@ if (is_object($objcanvas) && $objcanvas->displayCanvasExists($action)) { if (!empty($conf->productbatch->enabled)) { if ($object->isProduct() || !empty($conf->global->STOCK_SUPPORTS_SERVICES)) { print ''.$langs->trans("ManageLotSerial").''; - if (!empty($conf->use_javascript_ajax) && $usercancreate && !empty($conf->global->MAIN_DIRECT_STATUS_UPDATE) && empty($conf->global->MAIN_ADVANCE_NUMLOT)) { - print ajax_object_onoff($object, 'status_batch', 'tobatch', 'ProductStatusOnBatch', 'ProductStatusNotOnBatch'); - } else { - print $object->getLibStatut(0, 2); - } + print $object->getLibStatut(0, 2); print ''; } } diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index 99e571fb8b7..072b113df7e 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -4747,10 +4747,10 @@ class Product extends CommonObject if ($type == 2) { switch ($mode) { case 0: - $label = ($status == 0 ? $langs->trans('ProductStatusNotOnBatch') : ($status == 1 || empty($conf->global->MAIN_ADVANCE_NUMLOT) ? $langs->trans('ProductStatusOnBatch') : $langs->trans('ProductStatusOnSerial'))); + $label = ($status == 0 ? $langs->trans('ProductStatusNotOnBatch') : ($status == 1 ? $langs->trans('ProductStatusOnBatch') : $langs->trans('ProductStatusOnSerial'))); return dolGetStatus($label); case 1: - $label = ($status == 0 ? $langs->trans('ProductStatusNotOnBatchShort') : ($status == 1 || empty($conf->global->MAIN_ADVANCE_NUMLOT) ? $langs->trans('ProductStatusOnBatchShort') : $langs->trans('ProductStatusOnSerialShort'))); + $label = ($status == 0 ? $langs->trans('ProductStatusNotOnBatchShort') : ($status == 1 ? $langs->trans('ProductStatusOnBatchShort') : $langs->trans('ProductStatusOnSerialShort'))); return dolGetStatus($label); case 2: return $this->LibStatut($status, 3, 2).' '.$this->LibStatut($status, 1, 2); @@ -4788,10 +4788,10 @@ class Product extends CommonObject $labelStatus = $langs->trans('ProductStatusOnBuyShort'); $labelStatusShort = $langs->trans('ProductStatusOnBuy'); } elseif ($type == 2) { - $labelStatus = ($status == 1 || empty($conf->global->MAIN_ADVANCE_NUMLOT) ? $langs->trans('ProductStatusOnBatch') : $langs->trans('ProductStatusOnSerial')); - $labelStatusShort = ($status == 1 || empty($conf->global->MAIN_ADVANCE_NUMLOT) ? $langs->trans('ProductStatusOnBatchShort') : $langs->trans('ProductStatusOnSerialShort')); + $labelStatus = ($status == 1 ? $langs->trans('ProductStatusOnBatch') : $langs->trans('ProductStatusOnSerial')); + $labelStatusShort = ($status == 1 ? $langs->trans('ProductStatusOnBatchShort') : $langs->trans('ProductStatusOnSerialShort')); } - } elseif (! empty($conf->global->MAIN_ADVANCE_NUMLOT) && $type == 2 && $status == 2) { + } elseif ( $type == 2 && $status == 2 ) { $labelStatus = $langs->trans('ProductStatusOnSerial'); $labelStatusShort = $langs->trans('ProductStatusOnSerialShort'); } diff --git a/htdocs/product/list.php b/htdocs/product/list.php index 1e96e7d97f8..5bc973e56b7 100644 --- a/htdocs/product/list.php +++ b/htdocs/product/list.php @@ -971,18 +971,13 @@ if ($resql) { // To batch if (!empty($arrayfields['p.tobatch']['checked'])) { print ''; - - if (empty($conf->global ->MAIN_ADVANCE_NUMLOT)) { - print $form->selectyesno('search_tobatch', $search_tobatch, 1, false, 1); - } else { - $statutarray = array( - '-1' => '', - '0' => $langs->trans("ProductStatusNotOnBatchShort"), - '1' => $langs->trans("ProductStatusOnBatchShort"), - '2' => $langs->trans("ProductStatusOnSerialShort") - ); - print $form->selectarray('search_tobatch', $statutarray, $search_tobatch); - } + $statutarray = array( + '-1' => '', + '0' => $langs->trans("ProductStatusNotOnBatchShort"), + '1' => $langs->trans("ProductStatusOnBatchShort"), + '2' => $langs->trans("ProductStatusOnSerialShort") + ); + print $form->selectarray('search_tobatch', $statutarray, $search_tobatch); print ''; } // Country @@ -1672,11 +1667,7 @@ if ($resql) { // Lot/Serial if (!empty($arrayfields['p.tobatch']['checked'])) { print ''; - if (empty($conf->global->MAIN_ADVANCE_NUMLOT)) { - print yn($obj->tobatch); - } else { - print $product_static->getLibStatut(1, 2); - } + print $product_static->getLibStatut(1, 2); print ''; if (!$i) { $totalarray['nbfield']++; diff --git a/htdocs/product/stock/class/mouvementstock.class.php b/htdocs/product/stock/class/mouvementstock.class.php index 17fe7193658..59bc60f342b 100644 --- a/htdocs/product/stock/class/mouvementstock.class.php +++ b/htdocs/product/stock/class/mouvementstock.class.php @@ -192,7 +192,7 @@ class MouvementStock extends CommonObject } } // end hook at beginning - + // Clean parameters $price = price2num($price, 'MU'); // Clean value for the casse we receive a float zero value, to have it a real zero value. if (empty($price)) $price = 0; @@ -568,14 +568,32 @@ class MouvementStock extends CommonObject // Update detail stock for batch product if (!$error && !empty($conf->productbatch->enabled) && $product->hasbatch() && !$skip_batch) { - if ($id_product_batch > 0) + // check unicity for serial numbered equipments ( different for lots managed products) + if ( $product->status_batch == 2 && $qty > 0 ) { - $result = $this->createBatch($id_product_batch, $qty); - } else { - $param_batch = array('fk_product_stock' =>$fk_product_stock, 'batchnumber'=>$batch); - $result = $this->createBatch($param_batch, $qty); + if ( $this->getBatchCount($fk_product, $batch) > 0 ) + { + $error++; + $this->errors[] = $langs->trans("SerialNumberAlreadyInUse", $batch, $product->ref); + } + elseif ( $qty > 1 ) + { + $error++; + $this->errors[] = $langs->trans("TooManyQtyForSerialNumber", $product->ref, $batch); + } + } + + if ( ! $error ) + { + if ($id_product_batch > 0) + { + $result = $this->createBatch($id_product_batch, $qty); + } else { + $param_batch = array('fk_product_stock' =>$fk_product_stock, 'batchnumber'=>$batch); + $result = $this->createBatch($param_batch, $qty); + } + if ($result < 0) $error++; } - if ($result < 0) $error++; } // Update PMP and denormalized value of stock qty at product level @@ -1208,4 +1226,39 @@ class MouvementStock extends CommonObject return $this->deleteCommon($user, $notrigger); //return $this->deleteCommon($user, $notrigger, 1); } + + /** + * Retrieve number of equipments for a product batch + * + * @param int $fk_product Product id + * @param varchar $batch batch number + * @return int <0 if KO, number of equipments if OK + */ + private function getBatchCount($fk_product, $batch) + { + global $conf; + + $cpt = 0; + + $sql = "SELECT sum(pb.qty) as cpt"; + $sql .= " FROM ".MAIN_DB_PREFIX."product_batch as pb"; + $sql .= " INNER JOIN ".MAIN_DB_PREFIX."product_stock as ps ON ps.rowid = pb.fk_product_stock"; + $sql .= " WHERE ps.fk_product = " . $fk_product; + $sql .= " AND pb.batch = '" . $this->db->escape($batch) . "'"; + + $result = $this->db->query($sql); + if ($result) { + if ($this->db->num_rows($result)) { + $obj = $this->db->fetch_object($result); + $cpt = $obj->cpt; + } + + $this->db->free($result); + } else { + dol_print_error($this->db); + return -1; + } + + return $cpt; + } }