From 44b84b21772b694f6293c3f26f956a55226aee58 Mon Sep 17 00:00:00 2001
From: Maxime Kohlhaas <mko@atm-consulting.fr>
Date: Thu, 9 Aug 2012 01:41:55 +0200
Subject: [PATCH] task # 326 : Add a numbering module to suggest automatically
 a product ref

---
 ChangeLog                                     |   1 +
 .../product/mod_codeproduct_elephant.php      | 302 ++++++++++++++++++
 .../product/mod_codeproduct_leopard.php       | 123 +++++++
 .../modules/product/modules_product.class.php | 213 ++++++++++++
 htdocs/install/mysql/data/llx_const.sql       |   4 +
 htdocs/langs/fr_FR/admin.lang                 |   3 +
 htdocs/langs/fr_FR/companies.lang             |   2 +-
 htdocs/langs/fr_FR/products.lang              |   4 +-
 htdocs/product/admin/product.php              | 137 ++++++++
 htdocs/product/fiche.php                      |  14 +-
 10 files changed, 800 insertions(+), 3 deletions(-)
 create mode 100644 htdocs/core/modules/product/mod_codeproduct_elephant.php
 create mode 100644 htdocs/core/modules/product/mod_codeproduct_leopard.php
 create mode 100644 htdocs/core/modules/product/modules_product.class.php

diff --git a/ChangeLog b/ChangeLog
index bddd25d5dd1..7c180afdb68 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -38,6 +38,7 @@ For users:
 - New: Update libs/tools/logo for DoliWamp.
 - Fix: No images into product description lines as PDF generation does
   not work with this.
+- New: [ task #326 ]: Add a numbering module to suggest automatically a product ref
 
 For developers:
 - New: Add webservice for thirdparty creation and list.
diff --git a/htdocs/core/modules/product/mod_codeproduct_elephant.php b/htdocs/core/modules/product/mod_codeproduct_elephant.php
new file mode 100644
index 00000000000..437e9e6f4e8
--- /dev/null
+++ b/htdocs/core/modules/product/mod_codeproduct_elephant.php
@@ -0,0 +1,302 @@
+<?php
+/* Copyright (C) 2004      Rodolphe Quiedeville <rodolphe@quiedeville.org>
+ * Copyright (C) 2006-2009 Laurent Destailleur  <eldy@users.sourceforge.net>
+ * Copyright (C) 2007-2012 Regis Houssin        <regis@dolibarr.fr>
+ * Copyright (C) 2011      Juanjo Menent	    <jmenent@2byte.es>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * or see http://www.gnu.org/
+ */
+
+/**
+ *       \file       htdocs/core/modules/product/mod_codeproduct_elephant.php
+ *       \ingroup    product
+ *       \brief      File of class to manage product code with elephant rule
+ */
+
+require_once(DOL_DOCUMENT_ROOT."/core/modules/product/modules_product.class.php");
+
+
+/**
+ *       \class 		mod_codeproduct_elephant
+ *       \brief 		Class to manage product code with elephant rule
+ */
+class mod_codeproduct_elephant extends ModeleProductCode
+{
+	var $nom='Elephant';				// Nom du modele
+	var $code_modifiable;				// Code modifiable
+	var $code_modifiable_invalide;		// Code modifiable si il est invalide
+	var $code_modifiable_null;			// Code modifiables si il est null
+	var $code_null;						// Code facultatif
+	var $version='dolibarr';    		// 'development', 'experimental', 'dolibarr'
+	var $code_auto;                     // Numerotation automatique
+
+	var $searchcode; // String de recherche
+	var $numbitcounter; // Nombre de chiffres du compteur
+	var $prefixIsRequired; // Le champ prefix du tiers doit etre renseigne quand on utilise {pre}
+
+
+	/**
+	 *	Constructor
+	 */
+	function __construct()
+	{
+		$this->code_null = 0;
+		$this->code_modifiable = 1;
+		$this->code_modifiable_invalide = 1;
+		$this->code_modifiable_null = 1;
+		$this->code_auto = 1;
+		$this->prefixIsRequired = 0;
+	}
+
+
+	/**		Return description of module
+	 *
+	 * 		@param	string 		$langs		Object langs
+	 * 		@return string      			Description of module
+	 */
+	function info($langs)
+	{
+		global $conf, $mc;
+
+		$langs->load("products");
+
+		$form = new Form($this->db);
+
+		$disabled = ((! empty($mc->sharings['referent']) && $mc->sharings['referent'] != $conf->entity) ? ' disabled="disabled"' : '');
+
+		$texte = $langs->trans('GenericNumRefModelDesc')."<br>\n";
+		$texte.= '<form action="'.$_SERVER["PHP_SELF"].'" method="POST">';
+		$texte.= '<input type="hidden" name="token" value="'.$_SESSION['newtoken'].'">';
+		$texte.= '<input type="hidden" name="action" value="setModuleOptions">';
+		$texte.= '<input type="hidden" name="param1" value="PRODUCT_ELEPHANT_MASK_PRODUCT">';
+		$texte.= '<input type="hidden" name="param2" value="PRODUCT_ELEPHANT_MASK_SERVICE">';
+		$texte.= '<table class="nobordernopadding" width="100%">';
+
+		$tooltip=$langs->trans("GenericMaskCodes",$langs->transnoentities("Product"),$langs->transnoentities("Product"));
+		$tooltip.=$langs->trans("GenericMaskCodes3");
+		$tooltip.=$langs->trans("GenericMaskCodes4c");
+		$tooltip.=$langs->trans("GenericMaskCodes5");
+
+		// Parametrage du prefix customers
+		$texte.= '<tr><td>'.$langs->trans("Mask").' ('.$langs->trans("ProductCodeModel").'):</td>';
+		$texte.= '<td align="right">'.$form->textwithpicto('<input type="text" class="flat" size="24" name="value1" value="'.$conf->global->PRODUCT_ELEPHANT_MASK_PRODUCT.'"'.$disabled.'>',$tooltip,1,1).'</td>';
+
+		$texte.= '<td align="left" rowspan="2">&nbsp; <input type="submit" class="button" value="'.$langs->trans("Modify").'" name="Button"'.$disabled.'></td>';
+
+		$texte.= '</tr>';
+
+		// Parametrage du prefix suppliers
+		$texte.= '<tr><td>'.$langs->trans("Mask").' ('.$langs->trans("ServiceCodeModel").'):</td>';
+		$texte.= '<td align="right">'.$form->textwithpicto('<input type="text" class="flat" size="24" name="value2" value="'.$conf->global->PRODUCT_ELEPHANT_MASK_SERVICE.'"'.$disabled.'>',$tooltip,1,1).'</td>';
+		$texte.= '</tr>';
+
+		$texte.= '</table>';
+		$texte.= '</form>';
+
+		return $texte;
+	}
+
+
+	/**
+	 * Return an example of result returned by getNextValue
+	 *
+	 * @param	Translate	$langs		Object langs
+	 * @param	product		$objproduct		Object product
+	 * @param	int			$type		Type of third party (1:customer, 2:supplier, -1:autodetect)
+	 * @return	string					Return string example
+	 */
+	function getExample($langs,$objproduct=0,$type=-1)
+	{
+		if ($type == 0 || $type == -1)
+		{
+			$exampleproduct = $this->getNextValue($objproduct,0);
+			if (! $exampleproduct)
+			{
+				$exampleproduct = $langs->trans('NotConfigured');
+			}
+			if($exampleproduct=="ErrorBadMask")
+			{
+				$langs->load("errors");
+				$exampleproduct=$langs->trans($exampleproduct);
+			}
+		}
+		if ($type == 1 || $type == -1)
+		{
+			$exampleservice = $this->getNextValue($objproduct,1);
+			if (! $exampleservice)
+			{
+				$exampleservice = $langs->trans('NotConfigured');
+			}
+			if($exampleservice=="ErrorBadMask")
+			{
+				$langs->load("errors");
+				$exampleservice=$langs->trans($exampleservice);
+			}
+		}
+
+		if ($type == 0) return $exampleproduct;
+		if ($type == 1) return $exampleservice;
+		return $exampleproduct.'<br>'.$exampleservice;
+	}
+
+	/**
+	 * Return next value
+	 *
+	 * @param	Product		$objproduct     Object product
+	 * @param  	int		    $type       Produit ou service (0:product, 1:service)
+	 * @return 	string      			Value if OK, '' if module not configured, <0 if KO
+	 */
+	function getNextValue($objproduct=0,$type=-1)
+	{
+		global $db,$conf;
+
+		require_once(DOL_DOCUMENT_ROOT ."/core/lib/functions2.lib.php");
+
+		// Get Mask value
+		$mask = '';
+		if ($type==0) $mask = $conf->global->PRODUCT_ELEPHANT_MASK_PRODUCT;
+		if ($type==1) $mask = $conf->global->PRODUCT_ELEPHANT_MASK_SERVICE;
+		if (! $mask)
+		{
+			$this->error='NotConfigured';
+			return '';
+		}
+
+		$field='';$where='';
+		if ($type == 0)
+		{
+			$field = 'ref';
+			//$where = ' AND client in (1,2)';
+		}
+		else if ($type == 1)
+		{
+			$field = 'ref';
+			//$where = ' AND fournisseur = 1';
+		}
+		else return -1;
+
+		$now=dol_now();
+
+		$numFinal=get_next_value($db,$mask,'product',$field,$where,'',$now);
+
+		return  $numFinal;
+	}
+
+
+	/**
+	 *   Check if mask/numbering use prefix
+	 *
+	 *   @return	int			0 or 1
+	 */
+	function verif_prefixIsUsed()
+	{
+		global $conf;
+
+		$mask = $conf->global->PRODUCT_ELEPHANT_MASK_PRODUCT;
+		if (preg_match('/\{pre\}/i',$mask)) return 1;
+
+		$mask = $conf->global->PRODUCT_ELEPHANT_MASK_SERVICE;
+		if (preg_match('/\{pre\}/i',$mask)) return 1;
+
+		return 0;
+	}
+
+
+	/**
+	 * 	Check validity of code according to its rules
+	 *
+	 *	@param	DoliDB		$db		Database handler
+	 *	@param	string		&$code	Code to check/correct
+	 *	@param	Product		$product	Object product
+	 *  @param  int		  	$type   0 = customer/prospect , 1 = supplier
+	 *  @return int					0 if OK
+	 * 								-1 ErrorBadCustomerCodeSyntax
+	 * 								-2 ErrorCustomerCodeRequired
+	 * 								-3 ErrorCustomerCodeAlreadyUsed
+	 * 								-4 ErrorPrefixRequired
+	 */
+	function verif($db, &$code, $product, $type)
+	{
+		global $conf;
+
+		require_once(DOL_DOCUMENT_ROOT ."/core/lib/functions2.lib.php");
+
+		$result=0;
+		$code = strtoupper(trim($code));
+
+		if (empty($code) && $this->code_null && empty($conf->global->MAIN_COMPANY_CODE_ALWAYS_REQUIRED))
+		{
+			$result=0;
+		}
+		else if (empty($code) && (! $this->code_null || ! empty($conf->global->MAIN_COMPANY_CODE_ALWAYS_REQUIRED)) )
+		{
+			$result=-2;
+		}
+		else
+		{
+			// Get Mask value
+			$mask = '';
+			if ($type==0) $mask = empty($conf->global->PRODUCT_ELEPHANT_MASK_PRODUCT)?'':$conf->global->PRODUCT_ELEPHANT_MASK_PRODUCT;
+			if ($type==1) $mask = empty($conf->global->PRODUCT_ELEPHANT_MASK_SSERVICE)?'':$conf->global->PRODUCT_ELEPHANT_MASK_SERVICE;
+			if (! $mask)
+			{
+				$this->error='NotConfigured';
+				return '';
+			}
+
+			$result=check_value($mask,$code);
+		}
+
+		dol_syslog("mod_codeclient_elephant::verif type=".$type." result=".$result);
+		return $result;
+	}
+
+
+	/**
+	 *		Renvoi si un code est pris ou non (par autre tiers)
+	 *
+	 *		@param	DoliDB		$db			Handler acces base
+	 *		@param	string		$code		Code a verifier
+	 *		@param	Product		$product		Objet product
+	 *		@return	int						0 if available, <0 if KO
+	 */
+	function verif_dispo($db, $code, $product)
+	{
+		$sql = "SELECT ref FROM ".MAIN_DB_PREFIX."product";
+		$sql.= " WHERE ref = '".$code."'";
+		if ($product->id > 0) $sql.= " AND rowid <> ".$product->id;
+
+		$resql=$db->query($sql);
+		if ($resql)
+		{
+			if ($db->num_rows($resql) == 0)
+			{
+				return 0;
+			}
+			else
+			{
+				return -1;
+			}
+		}
+		else
+		{
+			return -2;
+		}
+
+	}
+
+}
+
+?>
diff --git a/htdocs/core/modules/product/mod_codeproduct_leopard.php b/htdocs/core/modules/product/mod_codeproduct_leopard.php
new file mode 100644
index 00000000000..d1a6b529da9
--- /dev/null
+++ b/htdocs/core/modules/product/mod_codeproduct_leopard.php
@@ -0,0 +1,123 @@
+<?php
+/* Copyright (C) 2004      Rodolphe Quiedeville <rodolphe@quiedeville.org>
+ * Copyright (C) 2006-2009 Laurent Destailleur  <eldy@users.sourceforge.net>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * or see http://www.gnu.org/
+ */
+
+/**
+ *       \file       htdocs/core/modules/product/mod_codeproduct_leopard.php
+ *       \ingroup    product
+ *       \brief      Fichier de la classe des gestion leopard des codes produits
+ */
+
+require_once(DOL_DOCUMENT_ROOT."/core/modules/product/modules_product.class.php");
+
+
+/**
+ *	\class 		mod_codeproduct_leopard
+ *	\brief 		Classe permettant la gestion leopard des codes produits
+ */
+class mod_codeproduct_leopard extends ModeleProductCode
+{
+	/*
+	 * Attention ce module est utilise par defaut si aucun module n'a
+	 * ete definit dans la configuration
+	 *
+	 * Le fonctionnement de celui-ci doit donc rester le plus ouvert possible
+	 */
+
+	var $nom='Leopard';					// Nom du modele
+	var $code_modifiable;				// Code modifiable
+	var $code_modifiable_invalide;		// Code modifiable si il est invalide
+	var $code_modifiable_null;			// Code modifiables si il est null
+	var $code_null;						// Code facultatif
+	var $version='dolibarr';    		// 'development', 'experimental', 'dolibarr'
+	var $code_auto; 	                // Numerotation automatique
+
+
+	/**
+	 *	Constructor
+	 */
+	function __construct()
+	{
+		$this->code_null = 1;
+		$this->code_modifiable = 1;
+		$this->code_modifiable_invalide = 1;
+		$this->code_modifiable_null = 1;
+		$this->code_auto = 0;
+	}
+
+
+	/**		Return description of module
+	 *
+	 * 		@param	string	$langs		Object langs
+	 * 		@return string      		Description of module
+	 */
+	function info($langs)
+	{
+		return $langs->trans("LeopardNumRefModelDesc");
+	}
+
+
+	/**
+	 * Return an example of result returned by getNextValue
+	 *
+	 * @param	product		$objproduct		Object product
+	 * @param	int			$type		Type of third party (1:customer, 2:supplier, -1:autodetect)
+	 * @return	string					Return next value
+	 */
+	function getNextValue($objproduct=0,$type=-1)
+	{
+		global $langs;
+		return '';
+	}
+
+
+	/**
+	 * 	Check validity of code according to its rules
+	 *
+	 *	@param	DoliDB		$db		Database handler
+	 *	@param	string		&$code	Code to check/correct
+	 *	@param	Product		$product	Object product
+	 *  @param  int		  	$type   0 = product , 1 = service
+	 *  @return int					0 if OK
+	 * 								-1 ErrorBadProductCodeSyntax
+	 * 								-2 ErrorProductCodeRequired
+	 * 								-3 ErrorProductCodeAlreadyUsed
+	 * 								-4 ErrorPrefixRequired
+	 */
+	function verif($db, &$code, $product, $type)
+	{
+		global $conf;
+
+		$result=0;
+		$code = strtoupper(trim($code));
+
+		if (empty($code) && $this->code_null && empty($conf->global->MAIN_COMPANY_CODE_ALWAYS_REQUIRED))
+		{
+			$result=0;
+		}
+		else if (empty($code) && (! $this->code_null || ! empty($conf->global->MAIN_COMPANY_CODE_ALWAYS_REQUIRED)) )
+		{
+			$result=-2;
+		}
+
+		dol_syslog("mod_codeproduct_leopard::verif type=".$type." result=".$result);
+		return $result;
+	}
+}
+
+?>
diff --git a/htdocs/core/modules/product/modules_product.class.php b/htdocs/core/modules/product/modules_product.class.php
new file mode 100644
index 00000000000..26a5ad6aab4
--- /dev/null
+++ b/htdocs/core/modules/product/modules_product.class.php
@@ -0,0 +1,213 @@
+<?php
+/* Copyright (C) 2003-2005 Rodolphe Quiedeville <rodolphe@quiedeville.org>
+ * Copyright (C) 2004-2010 Laurent Destailleur  <eldy@users.sourceforge.net>
+ * Copyright (C) 2004      Eric Seigne          <eric.seigne@ryxeo.com>
+ * Copyright (C) 2005-2012 Regis Houssin        <regis@dolibarr.fr>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * or see http://www.gnu.org/
+ */
+
+
+/**
+ *	    \class      ModeleProductCode
+ *		\brief  	Parent class for product code generators
+ */
+abstract class ModeleProductCode
+{
+    var $error='';
+
+    /**     Renvoi la description par defaut du modele de numerotation
+     *
+     *		@param	Translate	$langs		Object langs
+     *      @return string      			Texte descripif
+     */
+    function info($langs)
+    {
+        $langs->load("bills");
+        return $langs->trans("NoDescription");
+    }
+
+    /**     Renvoi nom module
+     *
+     *		@param	Translate	$langs		Object langs
+     *      @return string      			Nom du module
+     */
+    function getNom($langs)
+    {
+        return $this->nom;
+    }
+
+
+    /**     Renvoi un exemple de numerotation
+     *
+     *		@param	Translate	$langs		Object langs
+     *      @return string      			Example
+     */
+    function getExample($langs)
+    {
+        $langs->load("bills");
+        return $langs->trans("NoExample");
+    }
+
+    /**     Test si les numeros deja en vigueur dans la base ne provoquent pas de
+     *      de conflits qui empechera cette numerotation de fonctionner.
+     *
+     *      @return     boolean     false si conflit, true si ok
+     */
+    function canBeActivated()
+    {
+        return true;
+    }
+
+    /**
+     *  Return next value available
+     *
+     *	@param	Product		$objproduct		Object product
+     *	@param	int			$type		Type
+     *  @return string      			Value
+     */
+    function getNextValue($objproduct=0,$type=-1)
+    {
+        global $langs;
+        return $langs->trans("Function_getNextValue_InModuleNotWorking");
+    }
+
+
+    /**     Return version of module
+     *
+     *      @return     string      Version
+     */
+    function getVersion()
+    {
+        global $langs;
+        $langs->load("admin");
+
+        if ($this->version == 'development') return $langs->trans("VersionDevelopment");
+        if ($this->version == 'experimental') return $langs->trans("VersionExperimental");
+        if ($this->version == 'dolibarr') return DOL_VERSION;
+        return $langs->trans("NotAvailable");
+    }
+
+    /**
+     *  Renvoi la liste des modeles de numérotation
+     *
+     *  @param	DoliDB	$db     			Database handler
+     *  @param  string	$maxfilenamelength  Max length of value to show
+     *  @return	array						List of numbers
+     */
+    static function liste_modeles($db,$maxfilenamelength=0)
+    {
+        $liste=array();
+        $sql ="";
+
+        $resql = $db->query($sql);
+        if ($resql)
+        {
+            $num = $db->num_rows($resql);
+            $i = 0;
+            while ($i < $num)
+            {
+                $row = $db->fetch_row($resql);
+                $liste[$row[0]]=$row[1];
+                $i++;
+            }
+        }
+        else
+        {
+            return -1;
+        }
+        return $liste;
+    }
+
+    /**
+     *      Return description of module parameters
+     *
+     *      @param	Translate	$langs      Output language
+     *		@param	Product		$product	Product object
+     *		@param	int			$type		-1=Nothing, 0=Customer, 1=Supplier
+     *		@return	string					HTML translated description
+     */
+    function getToolTip($langs,$product,$type)
+    {
+        global $conf;
+
+        $langs->load("admin");
+
+        $s='';
+        if ($type == -1) $s.=$langs->trans("Name").': <b>'.$this->nom.'</b><br>';
+        if ($type == -1) $s.=$langs->trans("Version").': <b>'.$this->getVersion().'</b><br>';
+        if ($type == 0)  $s.=$langs->trans("ProductCodeDesc").'<br>';
+        if ($type == 1)  $s.=$langs->trans("ServiceCodeDesc").'<br>';
+        if ($type != -1) $s.=$langs->trans("ValidityControledByModule").': <b>'.$this->getNom($langs).'</b><br>';
+        $s.='<br>';
+        $s.='<u>'.$langs->trans("ThisIsModuleRules").':</u><br>';
+        if ($type == 0)
+        {
+            $s.=$langs->trans("RequiredIfProduct").': ';
+            if (! empty($conf->global->MAIN_COMPANY_CODE_ALWAYS_REQUIRED) && ! empty($this->code_null)) $s.='<strike>';
+            $s.=yn(!$this->code_null,1,2);
+            if (! empty($conf->global->MAIN_COMPANY_CODE_ALWAYS_REQUIRED) && ! empty($this->code_null)) $s.='</strike> '.yn(1,1,2).' ('.$langs->trans("ForcedToByAModule",$langs->transnoentities("yes")).')';
+            $s.='<br>';
+        }
+        if ($type == 1)
+        {
+            $s.=$langs->trans("RequiredIfService").': ';
+            if (! empty($conf->global->MAIN_COMPANY_CODE_ALWAYS_REQUIRED) && ! empty($this->code_null)) $s.='<strike>';
+            $s.=yn(!$this->code_null,1,2);
+            if (! empty($conf->global->MAIN_COMPANY_CODE_ALWAYS_REQUIRED) && ! empty($this->code_null)) $s.='</strike> '.yn(1,1,2).' ('.$langs->trans("ForcedToByAModule",$langs->transnoentities("yes")).')';
+            $s.='<br>';
+        }
+        if ($type == -1)
+        {
+            $s.=$langs->trans("Required").': ';
+            if (! empty($conf->global->MAIN_COMPANY_CODE_ALWAYS_REQUIRED) && ! empty($this->code_null)) $s.='<strike>';
+            $s.=yn(!$this->code_null,1,2);
+            if (! empty($conf->global->MAIN_COMPANY_CODE_ALWAYS_REQUIRED) && ! empty($this->code_null)) $s.='</strike> '.yn(1,1,2).' ('.$langs->trans("ForcedToByAModule",$langs->transnoentities("yes")).')';
+            $s.='<br>';
+        }
+        $s.=$langs->trans("CanBeModifiedIfOk").': ';
+        $s.=yn($this->code_modifiable,1,2);
+        $s.='<br>';
+        $s.=$langs->trans("CanBeModifiedIfKo").': '.yn($this->code_modifiable_invalide,1,2).'<br>';
+        $s.=$langs->trans("AutomaticCode").': '.yn($this->code_auto,1,2).'<br>';
+        $s.='<br>';
+        if ($type == 0 || $type == -1)
+        {
+            $nextval=$this->getNextValue($product,0);
+            if (empty($nextval)) $nextval=$langs->trans("Undefined");
+            $s.=$langs->trans("NextValue").($type == -1?' ('.$langs->trans("Product").')':'').': <b>'.$nextval.'</b><br>';
+        }
+        if ($type == 1 || $type == -1)
+        {
+            $nextval=$this->getNextValue($product,1);
+            if (empty($nextval)) $nextval=$langs->trans("Undefined");
+            $s.=$langs->trans("NextValue").($type == -1?' ('.$langs->trans("Service").')':'').': <b>'.$nextval.'</b>';
+        }
+        return $s;
+    }
+
+	/**
+	 *   Check if mask/numbering use prefix
+	 *
+	 *   @return	int		0=no, 1=yes
+	 */
+    function verif_prefixIsUsed()
+    {
+        return 0;
+    }
+
+}
+
+?>
diff --git a/htdocs/install/mysql/data/llx_const.sql b/htdocs/install/mysql/data/llx_const.sql
index 18a81459535..8d7434e80a6 100644
--- a/htdocs/install/mysql/data/llx_const.sql
+++ b/htdocs/install/mysql/data/llx_const.sql
@@ -89,3 +89,7 @@ insert into llx_const (name, value, type, note, visible) values('SOCIETE_CODECOM
 --
 insert into llx_const (name, value, type, note, visible) values ('MAILING_EMAIL_FROM','dolibarr@domain.com','chaine','EMail emmetteur pour les envois d emailings',0);
 
+--
+-- Product
+--
+insert into llx_const (name, value, type, note, visible) values('PRODUCT_CODEPRODUCT_ADDON','mod_codeproduct_leopard','yesno','Module to control product codes',0);
\ No newline at end of file
diff --git a/htdocs/langs/fr_FR/admin.lang b/htdocs/langs/fr_FR/admin.lang
index a6b91499d6b..3f4b5b69751 100644
--- a/htdocs/langs/fr_FR/admin.lang
+++ b/htdocs/langs/fr_FR/admin.lang
@@ -278,6 +278,7 @@ GenericMaskCodes2= <b>{cccc}</b> le code client sur n lettres<br><b>{cccc000}</b
 GenericMaskCodes3= Tout autre caractère dans le masque sera laissé inchangé.<br>Les espaces ne sont pas permis.<br>
 GenericMaskCodes4a= <u>Exemple sur la 99eme %s du tiers LaCompanie faite le 31/03/2007:</u><br>
 GenericMaskCodes4b= <u>Exemple sur un tiers créé le 31/03/2007:</u><br>
+GenericMaskCodes4c= <u>Exemple sur un produit/service créé le 31/03/2007:</u><br>
 GenericMaskCodes5= <b>ABC{yy}{mm}-{000000}</b> donnera <b>ABC0703-000099</b><br><b>{0000+100}-XXX/{dd}/YYY</b> donnera <b>0199-XXX/31/YYY</b>
 GenericNumRefModelDesc= Renvoie un numéro personnalisable selon un masque à définir.
 ServerAvailableOnIPOrPort= Serveur disponible à l'adresse <b>%s</b> sur le port <b>%s</b>
@@ -1144,6 +1145,8 @@ UseSearchToSelectProduct= Utiliser un formulaire de recherche pour le choix d'un
 UseEcoTaxeAbility= Prise en charge des éco-taxes (DEEE)
 SetDefaultBarcodeTypeProducts= Type de code-barres utilisé par défaut pour les produits
 SetDefaultBarcodeTypeThirdParties= Type de code-barres utilisé par défaut pour les tiers
+ProductCodeChecker= Modèle de numérotation des produits / services
+ProductOtherConf= Paramètres des produits / services
 ##### Syslog #####
 SyslogSetup= Configuration du module Logs et traces
 SyslogOutput= Sortie des log
diff --git a/htdocs/langs/fr_FR/companies.lang b/htdocs/langs/fr_FR/companies.lang
index acd7e4eeacf..ae6b8c44369 100644
--- a/htdocs/langs/fr_FR/companies.lang
+++ b/htdocs/langs/fr_FR/companies.lang
@@ -394,4 +394,4 @@ ActivityStateFilter=Statut d'activité
 # Monkey
 MonkeyNumRefModelDesc=Renvoie le numéro sous la forme %syymm-nnnn pour les codes clients et %syymm-nnnn pour les codes fournisseurs où yy est l'année, mm le mois et nnnn un compteur séquentiel sans rupture et sans remise à 0.
 # Leopard
-LeopardNumRefModelDesc=Code client/fournisseur libre sans vérification. Peut être modifié à tout moment.
+LeopardNumRefModelDesc=Code libre sans vérification. Peut être modifié à tout moment.
diff --git a/htdocs/langs/fr_FR/products.lang b/htdocs/langs/fr_FR/products.lang
index 2d3dfd12b2d..4484e5686c8 100644
--- a/htdocs/langs/fr_FR/products.lang
+++ b/htdocs/langs/fr_FR/products.lang
@@ -169,4 +169,6 @@ SuppliersPrices=Prix fournisseurs
 CustomCode=Code douane
 CountryOrigin=Pays d'origine
 HiddenIntoCombo=Caché dans les listes
-Nature=Nature
\ No newline at end of file
+Nature=Nature
+ProductCodeModel=Modèle de code produit
+ServiceCodeModel=Modèle de code service
\ No newline at end of file
diff --git a/htdocs/product/admin/product.php b/htdocs/product/admin/product.php
index 9c12199496c..eddac8c041d 100644
--- a/htdocs/product/admin/product.php
+++ b/htdocs/product/admin/product.php
@@ -46,6 +46,47 @@ $value = GETPOST('value','alpha');
 /*
  * Actions
  */
+if ($action == 'setcodeproduct')
+{
+	if (dolibarr_set_const($db, "PRODUCT_CODEPRODUCT_ADDON",$value,'chaine',0,'',$conf->entity) > 0)
+	{
+		Header("Location: ".$_SERVER["PHP_SELF"]);
+		exit;
+	}
+	else
+	{
+		dol_print_error($db);
+	}
+}
+
+// Define constants for submodules that contains parameters (forms with param1, param2, ... and value1, value2, ...)
+if ($action == 'setModuleOptions')
+{
+	$post_size=count($_POST);
+
+	$db->begin();
+
+	for($i=0;$i < $post_size;$i++)
+    {
+    	if (array_key_exists('param'.$i,$_POST))
+    	{
+    		$param=GETPOST("param".$i,'alpha');
+    		$value=GETPOST("value".$i,'alpha');
+    		if ($param) $res = dolibarr_set_const($db,$param,$value,'chaine',0,'',$conf->entity);
+	    	if (! $res > 0) $error++;
+    	}
+    }
+	if (! $error)
+    {
+        $db->commit();
+        $mesg = "<font class=\"ok\">".$langs->trans("SetupSaved")."</font>";
+    }
+    else
+    {
+        $db->rollback();
+        $mesg = "<font class=\"error\">".$langs->trans("Error")."</font>";
+	}
+}
 
 if ($action == 'nbprod')
 {
@@ -136,6 +177,102 @@ $head = product_admin_prepare_head();
 dol_fiche_head($head, 'general', $tab, 0, 'product');
 
 $form=new Form($db);
+
+/*
+ * Module to manage product / services code
+ */
+$dirproduct=array('/core/modules/product/');
+
+print_titre($langs->trans("ProductCodeChecker"));
+
+print '<table class="noborder" width="100%">'."\n";
+print '<tr class="liste_titre">'."\n";
+print '  <td>'.$langs->trans("Name").'</td>';
+print '  <td>'.$langs->trans("Description").'</td>';
+print '  <td>'.$langs->trans("Example").'</td>';
+print '  <td align="center" width="80">'.$langs->trans("Status").'</td>';
+print '  <td align="center" width="60">'.$langs->trans("Infos").'</td>';
+print "</tr>\n";
+
+$var = true;
+foreach ($dirproduct as $dirroot)
+{
+	$dir = dol_buildpath($dirroot,0);
+
+    $handle = @opendir($dir);
+    if (is_resource($handle))
+    {
+    	// Loop on each module find in opened directory
+    	while (($file = readdir($handle))!==false)
+    	{
+    		if (substr($file, 0, 16) == 'mod_codeproduct_' && substr($file, -3) == 'php')
+    		{
+    			$file = substr($file, 0, dol_strlen($file)-4);
+
+    			try {
+        			dol_include_once($dirroot.$file.".php");
+    			}
+    			catch(Exception $e)
+    			{
+    			    dol_syslog($e->getMessage(), LOG_ERR);
+    			}
+
+    			$modCodeProduct = new $file;
+
+    			// Show modules according to features level
+    			if ($modCodeProduct->version == 'development'  && $conf->global->MAIN_FEATURES_LEVEL < 2) continue;
+    			if ($modCodeProduct->version == 'experimental' && $conf->global->MAIN_FEATURES_LEVEL < 1) continue;
+
+    			$var = !$var;
+    			print '<tr '.$bc[$var].'>'."\n";
+    			print '<td width="140">'.$modCodeProduct->nom.'</td>'."\n";
+    			print '<td>'.$modCodeProduct->info($langs).'</td>'."\n";
+    			print '<td nowrap="nowrap">'.$modCodeProduct->getExample($langs).'</td>'."\n";
+
+    			if ($conf->global->PRODUCT_CODEPRODUCT_ADDON == "$file")
+    			{
+    				print '<td align="center">'."\n";
+    				print img_picto($langs->trans("Activated"),'switch_on');
+    				print "</td>\n";
+    			}
+    			else
+    			{
+    				// @todo : What's that ?
+    				/*$disabled = false;
+    				if (! empty($conf->multicompany->enabled) && (is_object($mc) && ! empty($mc->sharings['referent']) && $mc->sharings['referent'] == $conf->entity) ? false : true);
+    				print '<td align="center">';
+    				if (! $disabled) print '<a href="'.$_SERVER['PHP_SELF'].'?action=setcodeclient&value='.$file.'">';
+    				print img_picto($langs->trans("Disabled"),'switch_off');
+    				if (! $disabled) print '</a>';
+    				print '</td>';*/
+					print '<td align="center">';
+    				print '<a href="'.$_SERVER['PHP_SELF'].'?action=setcodeproduct&value='.$file.'">';
+    				print img_picto($langs->trans("Disabled"),'switch_off');
+    				print '</a>';
+    				print '</td>';
+    			}
+
+    			print '<td align="center">';
+    			$s=$modCodeProduct->getToolTip($langs,null,-1);
+    			print $form->textwithpicto('',$s,1);
+    			print '</td>';
+
+    			print '</tr>';
+    		}
+    	}
+    	closedir($handle);
+    }
+}
+print '</table>';
+
+/*
+ * Other conf
+ */
+
+print "<br>";
+
+print_titre($langs->trans("ProductOtherConf"));
+
 $var=true;
 print '<table class="noborder" width="100%">';
 print '<tr class="liste_titre">';
diff --git a/htdocs/product/fiche.php b/htdocs/product/fiche.php
index ce4573e47a5..4aa49ee651a 100644
--- a/htdocs/product/fiche.php
+++ b/htdocs/product/fiche.php
@@ -696,11 +696,22 @@ else
     {
         //WYSIWYG Editor
         require_once(DOL_DOCUMENT_ROOT."/core/class/doleditor.class.php");
+		
+		// Load object modCodeProduct
+        $module=$conf->global->PRODUCT_CODEPRODUCT_ADDON;
+        if (! $module) dolibarr_error('',$langs->trans("ErrorModuleThirdPartyCodeInCompanyModuleNotDefined"));
+        if (substr($module, 0, 16) == 'mod_codeproduct_' && substr($module, -3) == 'php')
+        {
+            $module = substr($module, 0, dol_strlen($module)-4);
+        }
+        dol_include_once('/core/modules/product/'.$module.".php");
+        $modCodeProduct = new $module;
 
         print '<form action="fiche.php" method="post">';
         print '<input type="hidden" name="token" value="'.$_SESSION['newtoken'].'">';
         print '<input type="hidden" name="action" value="add">';
         print '<input type="hidden" name="type" value="'.$type.'">'."\n";
+		if ($modCodeProduct->code_auto) print '<input type="hidden" name="code_auto" value="1">';
 
         if ($type==1) $title=$langs->trans("NewService");
         else $title=$langs->trans("NewProduct");
@@ -710,7 +721,8 @@ else
 
         print '<table class="border" width="100%">';
         print '<tr>';
-        print '<td class="fieldrequired" width="20%">'.$langs->trans("Ref").'</td><td><input name="ref" size="40" maxlength="32" value="'.$ref.'">';
+		if ($modCodeProduct->code_auto) $tmpcode=$modCodeProduct->getNextValue($object,$type);
+        print '<td class="fieldrequired" width="20%">'.$langs->trans("Ref").'</td><td><input name="ref" size="40" maxlength="32" value="'.$tmpcode.'">';
         if ($_error)
         {
             print $langs->trans("RefAlreadyExists");
-- 
GitLab