';
+
print '';
print info_admin($langs->trans("LDAPDescValues"));
diff --git a/htdocs/core/class/ldap.class.php b/htdocs/core/class/ldap.class.php
index 1d1a6122288..267949e2149 100644
--- a/htdocs/core/class/ldap.class.php
+++ b/htdocs/core/class/ldap.class.php
@@ -609,7 +609,7 @@ class Ldap
}
if ($result <= 0)
{
- $this->error = ldap_errno($this->connection)." ".ldap_error($this->connection)." ".$this->error;
+ $this->error = ldap_error($this->connection).' (Code '.ldap_errno($this->connection).") ".$this->error;
dol_syslog(get_class($this)."::update ".$this->error, LOG_ERR);
//print_r($info);
return -1;
diff --git a/htdocs/core/triggers/interface_50_modIFTTT_IFTTT.class.php b/htdocs/core/triggers/interface_50_modIFTTT_IFTTT.class.php
deleted file mode 100644
index c7ce8f121fc..00000000000
--- a/htdocs/core/triggers/interface_50_modIFTTT_IFTTT.class.php
+++ /dev/null
@@ -1,147 +0,0 @@
-
- * 2016 Christophe Battarel
- *
- * 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 3 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 .
- */
-
-/**
- * \file htdocs/core/triggers/interface_50_modIFTTT_IFTTT.class.php
- * \ingroup core
- * \brief File of trigger for IFTTT module
- */
-require_once DOL_DOCUMENT_ROOT.'/core/triggers/dolibarrtriggers.class.php';
-
-
-/**
- * Class of triggers for IFTTT module
- */
-class InterfaceIFTTT extends DolibarrTriggers
-{
- /**
- * @var DoliDB Database handler.
- */
- public $db;
-
- /**
- * Constructor
- *
- * @param DoliDB $db Database handler
- */
- public function __construct($db)
- {
- $this->db = $db;
-
- $this->name = preg_replace('/^Interface/i', '', get_class($this));
- $this->family = "ifttt";
- $this->description = "Triggers of the module IFTTT";
- $this->version = 'dolibarr'; // 'development', 'experimental', 'dolibarr' or version
- $this->picto = 'ifttt';
- }
-
- /**
- * Return name of trigger file
- *
- * @return string Name of trigger file
- */
- public function getName()
- {
- return $this->name;
- }
-
- /**
- * Return description of trigger file
- *
- * @return string Description of trigger file
- */
- public function getDesc()
- {
- return $this->description;
- }
-
- /**
- * Return version of trigger file
- *
- * @return string Version of trigger file
- */
- public function getVersion()
- {
- global $langs;
- $langs->load("admin");
-
- if ($this->version == 'development') {
- return $langs->trans("Development");
- } elseif ($this->version == 'experimental') {
- return $langs->trans("Experimental");
- } elseif ($this->version == 'dolibarr') {
- return DOL_VERSION;
- } elseif ($this->version) {
- return $this->version;
- } else {
- return $langs->trans("Unknown");
- }
- }
-
- /**
- * Function called when a Dolibarrr business event is done.
- * All functions "runTrigger" are triggered if file is inside directory htdocs/core/triggers
- *
- * @param string $action Event action code
- * @param Object $object Object
- * @param User $user Object user
- * @param Translate $langs Object langs
- * @param conf $conf Object conf
- * @return int <0 if KO, 0 if no triggered ran, >0 if OK
- */
- public function runTrigger($action, $object, User $user, Translate $langs, Conf $conf)
- {
- $ok = 0;
-
- if (empty($conf->ifttt->enabled)) return 0; // Module not active, we do nothing
-
- switch ($action) {
- case 'THIRDPARTY_CREATED':
- dol_syslog("Trigger '" . $this->name . "' for action '$action' launched by " . __FILE__ . ". id=" . $object->id);
-
- include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
-
- // See https://platform.ifttt.com/docs/api_reference#realtime-api
-
- $arrayofdata=array();
- $arrayofdata['user_id']=$conf->global->IFTTT_USER_ID;
- $arrayofdata['trigger_identity']=$conf->global->IFTTT_TRIGGER_IDENTITY;
- $arrayofdata['name']='testabcdef';
- $arrayofdata['email']='testemailabcdef';
-
- $url = 'https://realtime.ifttt.com/v1/notifications';
-
- $addheaders=array(
- 'IFTTT-Service-Key'=>'123',
- 'Accept'=>'application/json',
- 'Accept-Charset'=>'utf-8',
- 'Accept-Encoding'=>'gzip, deflate',
- 'Content-Type'=>'application/json',
- 'X-Request-ID'=>getRandomPassword(true, null)
- );
-
- $result = getURLContent($url, 'POSTALREADYFORMATED', '', 1, $addheaders);
-
- $ok = 1;
- break;
- }
-
- return $ok;
- }
-}
diff --git a/htdocs/expedition/card.php b/htdocs/expedition/card.php
index cc05bf686c3..4202607cbd2 100644
--- a/htdocs/expedition/card.php
+++ b/htdocs/expedition/card.php
@@ -1262,7 +1262,13 @@ if ($action == 'create')
if ($line->fk_product > 0)
{
print '';
- print $formproduct->selectWarehouses($tmpentrepot_id, 'entl'.$indiceAsked, '', 1, 0, $line->fk_product, '', 1);
+
+ $stockMin = false;
+ if (empty($conf->global->STOCK_ALLOW_NEGATIVE_TRANSFER)) {
+ $stockMin = 0;
+ }
+ print $formproduct->selectWarehouses($tmpentrepot_id, 'entl'.$indiceAsked, '', 1, 0, $line->fk_product, '', 1, 0, array(), 'minwidth200', '', 1, $stockMin, 'stock DESC, e.ref');
+
if ($tmpentrepot_id > 0 && $tmpentrepot_id == $warehouse_id)
{
//print $stock.' '.$quantityToBeDelivered;
diff --git a/htdocs/langs/en_US/admin.lang b/htdocs/langs/en_US/admin.lang
index a8f7f9643ce..3eb9f24112d 100644
--- a/htdocs/langs/en_US/admin.lang
+++ b/htdocs/langs/en_US/admin.lang
@@ -1458,6 +1458,13 @@ LDAPFieldSidExample=Example: objectsid
LDAPFieldEndLastSubscription=Date of subscription end
LDAPFieldTitle=Job position
LDAPFieldTitleExample=Example: title
+LDAPFieldGroupid=Group id
+LDAPFieldGroupidExample=Exemple : gidnumber
+LDAPFieldUserid=User id
+LDAPFieldUseridExample=Exemple : uidnumber
+LDAPFieldHomedirectory=Home directory prefix
+LDAPFieldHomedirectoryExample=Exemple : homedirectory
+LDAPFieldHomedirectoryprefix=Home directory prefix
LDAPSetupNotComplete=LDAP setup not complete (go on others tabs)
LDAPNoUserOrPasswordProvidedAccessIsReadOnly=No administrator or password provided. LDAP access will be anonymous and in read only mode.
LDAPDescContact=This page allows you to define LDAP attributes name in LDAP tree for each data found on Dolibarr contacts.
diff --git a/htdocs/langs/fr_FR/admin.lang b/htdocs/langs/fr_FR/admin.lang
index 20cdc9175d6..ff684f01cb9 100644
--- a/htdocs/langs/fr_FR/admin.lang
+++ b/htdocs/langs/fr_FR/admin.lang
@@ -1458,6 +1458,13 @@ LDAPFieldSidExample=Exemple : objectsid
LDAPFieldEndLastSubscription=Date de fin de validité adhésion
LDAPFieldTitle=Poste/fonction
LDAPFieldTitleExample=Exemple: title
+LDAPFieldGroupid=Groupe id
+LDAPFieldGroupidExample=Exemple : gidnumber
+LDAPFieldUserid=User id
+LDAPFieldUseridExample=Exemple : uidnumber
+LDAPFieldHomedirectory=Répertoire d'accueil
+LDAPFieldHomedirectoryExample=Exemple : homedirectory
+LDAPFieldHomedirectoryprefix=Préfixe du répertoire d'accueil
LDAPSetupNotComplete=Configuration LDAP incomplète (à compléter sur les autres onglets)
LDAPNoUserOrPasswordProvidedAccessIsReadOnly=Administrateur ou mot de passe non renseigné. Les accès LDAP seront donc anonymes et en lecture seule.
LDAPDescContact=Cette page permet de définir le nom des attributs de l'arbre LDAP pour chaque information des contacts Dolibarr.
diff --git a/htdocs/product/class/html.formproduct.class.php b/htdocs/product/class/html.formproduct.class.php
index a0ebda88cd9..e356226dd2f 100644
--- a/htdocs/product/class/html.formproduct.class.php
+++ b/htdocs/product/class/html.formproduct.class.php
@@ -55,21 +55,24 @@ class FormProduct
}
- /**
- * Load in cache array list of warehouses
- * If fk_product is not 0, we do not use cache
- *
- * @param int $fk_product Add quantity of stock in label for product with id fk_product. Nothing if 0.
- * @param string $batch Add quantity of batch stock in label for product with batch name batch, batch name precedes batch_id. Nothing if ''.
- * @param string $status warehouse status filter, following comma separated filter options can be used
- * 'warehouseopen' = select products from open warehouses,
- * 'warehouseclosed' = select products from closed warehouses,
- * 'warehouseinternal' = select products from warehouses for internal correct/transfer only
- * @param boolean $sumStock sum total stock of a warehouse, default true
- * @param array $exclude warehouses ids to exclude
- * @return int Nb of loaded lines, 0 if already loaded, <0 if KO
- */
- public function loadWarehouses($fk_product = 0, $batch = '', $status = '', $sumStock = true, $exclude = '')
+ /**
+ * Load in cache array list of warehouses
+ * If fk_product is not 0, we do not use cache
+ *
+ * @param int $fk_product Add quantity of stock in label for product with id fk_product. Nothing if 0.
+ * @param string $batch Add quantity of batch stock in label for product with batch name batch, batch name precedes batch_id. Nothing if ''.
+ * @param string $status warehouse status filter, following comma separated filter options can be used
+ * 'warehouseopen' = select products from open warehouses,
+ * 'warehouseclosed' = select products from closed warehouses,
+ * 'warehouseinternal' = select products from warehouses for internal correct/transfer only
+ * @param boolean $sumStock sum total stock of a warehouse, default true
+ * @param string $exclude warehouses ids to exclude
+ * @param bool|int $stockMin [=false] Value of minimum stock to filter or false not not filter by minimum stock
+ * @param string $orderBy [='e.ref'] Order by
+ * @return int Nb of loaded lines, 0 if already loaded, <0 if KO
+ * @throws Exception
+ */
+ public function loadWarehouses($fk_product = 0, $batch = '', $status = '', $sumStock = true, $exclude = '', $stockMin = false, $orderBy = 'e.ref')
{
global $conf, $langs;
@@ -130,8 +133,26 @@ class FormProduct
if(!empty($exclude)) $sql.= ' AND e.rowid NOT IN('.$this->db->escape(implode(',', $exclude)).')';
- if ($sumStock && empty($fk_product)) $sql.= " GROUP BY e.rowid, e.ref, e.description, e.fk_parent";
- $sql.= " ORDER BY e.ref";
+ // minimum stock
+ if ($stockMin !== false) {
+ if (!empty($fk_product)) {
+ if (!empty($batch)) {
+ $sql .= " AND pb.qty > " . $this->db->escape($stockMin);
+ } else {
+ $sql .= " AND ps.reel > " . $this->db->escape($stockMin);
+ }
+ }
+ }
+
+ if ($sumStock && empty($fk_product)) {
+ $sql.= " GROUP BY e.rowid, e.ref, e.description, e.fk_parent";
+
+ // minimum stock
+ if ($stockMin !== false) {
+ $sql .= " HAVING sum(ps.reel) > " . $this->db->escape($stockMin);
+ }
+ }
+ $sql.= " ORDER BY " . $orderBy;
dol_syslog(get_class($this).'::loadWarehouses', LOG_DEBUG);
$resql = $this->db->query($sql);
@@ -192,25 +213,29 @@ class FormProduct
/**
* Return list of warehouses
*
- * @param int $selected Id of preselected warehouse ('' for no value, 'ifone'=select value if one value otherwise no value)
- * @param string $htmlname Name of html select html
- * @param string $filterstatus warehouse status filter, following comma separated filter options can be used
- * 'warehouseopen' = select products from open warehouses,
- * 'warehouseclosed' = select products from closed warehouses,
- * 'warehouseinternal' = select products from warehouses for internal correct/transfer only
- * @param int $empty 1=Can be empty, 0 if not
- * @param int $disabled 1=Select is disabled
- * @param int $fk_product Add quantity of stock in label for product with id fk_product. Nothing if 0.
- * @param string $empty_label Empty label if needed (only if $empty=1)
- * @param int $showstock 1=Show stock count
- * @param int $forcecombo 1=Force combo iso ajax select2
- * @param array $events Events to add to select2
- * @param string $morecss Add more css classes to HTML select
- * @param array $exclude Warehouses ids to exclude
- * @param int $showfullpath 1=Show full path of name (parent ref into label), 0=Show only ref of current warehouse
- * @return string HTML select
+ * @param string|int $selected Id of preselected warehouse ('' for no value, 'ifone'=select value if one value otherwise no value)
+ * @param string $htmlname Name of html select html
+ * @param string $filterstatus warehouse status filter, following comma separated filter options can be used
+ * 'warehouseopen' = select products from open warehouses,
+ * 'warehouseclosed' = select products from closed warehouses,
+ * 'warehouseinternal' = select products from warehouses for internal correct/transfer only
+ * @param int $empty 1=Can be empty, 0 if not
+ * @param int $disabled 1=Select is disabled
+ * @param int $fk_product Add quantity of stock in label for product with id fk_product. Nothing if 0.
+ * @param string $empty_label Empty label if needed (only if $empty=1)
+ * @param int $showstock 1=Show stock count
+ * @param int $forcecombo 1=Force combo iso ajax select2
+ * @param array $events Events to add to select2
+ * @param string $morecss Add more css classes to HTML select
+ * @param string $exclude Warehouses ids to exclude
+ * @param int $showfullpath 1=Show full path of name (parent ref into label), 0=Show only ref of current warehouse
+ * @param bool|int $stockMin [=false] Value of minimum stock to filter or false not not filter by minimum stock
+ * @param string $orderBy [='e.ref'] Order by
+ * @return string HTML select
+ *
+ * @throws Exception
*/
- public function selectWarehouses($selected = '', $htmlname = 'idwarehouse', $filterstatus = '', $empty = 0, $disabled = 0, $fk_product = 0, $empty_label = '', $showstock = 0, $forcecombo = 0, $events = array(), $morecss = 'minwidth200', $exclude = '', $showfullpath = 1)
+ public function selectWarehouses($selected = '', $htmlname = 'idwarehouse', $filterstatus = '', $empty = 0, $disabled = 0, $fk_product = 0, $empty_label = '', $showstock = 0, $forcecombo = 0, $events = array(), $morecss = 'minwidth200', $exclude = '', $showfullpath = 1, $stockMin = false, $orderBy = 'e.ref')
{
global $conf,$langs,$user;
@@ -218,7 +243,7 @@ class FormProduct
$out='';
if (empty($conf->global->ENTREPOT_EXTRA_STATUS)) $filterstatus = '';
- $this->loadWarehouses($fk_product, '', $filterstatus, true, $exclude);
+ $this->loadWarehouses($fk_product, '', $filterstatus, true, $exclude, $stockMin, $orderBy);
$nbofwarehouses=count($this->cache_warehouses);
if ($conf->use_javascript_ajax && ! $forcecombo)
diff --git a/htdocs/product/list.php b/htdocs/product/list.php
index e7e8f1e4999..d8d9686ae4a 100644
--- a/htdocs/product/list.php
+++ b/htdocs/product/list.php
@@ -57,7 +57,8 @@ $search_label=GETPOST("search_label", 'alpha');
$search_type = GETPOST("search_type", 'int');
$search_sale = GETPOST("search_sale", 'int');
$search_vatrate=GETPOST("search_vatrate", 'alpha');
-$search_categ = GETPOST("search_categ", 'int');
+$searchCategoryProductOperator = (GETPOST('search_category_product_operator', 'int') ? GETPOST('search_category_product_operator', 'int') : 0);
+$searchCategoryProductList = GETPOST('search_category_product_list', 'array');
$search_tosell = GETPOST("search_tosell", 'int');
$search_tobuy = GETPOST("search_tobuy", 'int');
$fourn_id = GETPOST("fourn_id", 'int');
@@ -231,7 +232,8 @@ if (empty($reshook))
$search_ref="";
$search_label="";
$search_barcode="";
- $search_categ=0;
+ $searchCategoryProductOperator = 0;
+ $searchCategoryProductList = array();
$search_tosell="";
$search_tobuy="";
$search_vatrate="";
@@ -301,7 +303,7 @@ $reshook=$hookmanager->executeHooks('printFieldListSelect', $parameters); //
$sql.=$hookmanager->resPrint;
$sql.= ' FROM '.MAIN_DB_PREFIX.'product as p';
if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label'])) $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_extrafields as ef on (p.rowid = ef.fk_object)";
-if (! empty($search_categ) || ! empty($catid)) $sql.= ' LEFT JOIN '.MAIN_DB_PREFIX."categorie_product as cp ON p.rowid = cp.fk_product"; // We'll need this table joined to the select in order to filter by categ
+if (!empty($searchCategoryProductList) || !empty($catid)) $sql.= ' LEFT JOIN '.MAIN_DB_PREFIX."categorie_product as cp ON p.rowid = cp.fk_product"; // We'll need this table joined to the select in order to filter by categ
$sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_fournisseur_price as pfp ON p.rowid = pfp.fk_product";
// multilang
if (! empty($conf->global->MAIN_MULTILANGS)) $sql.= " LEFT JOIN ".MAIN_DB_PREFIX."product_lang as pl ON pl.fk_product = p.rowid AND pl.lang = '".$langs->getDefaultLang() ."'";
@@ -333,8 +335,30 @@ if ($search_vatrate) $sql .= natural_search('p.tva_tx', $search_vatrate);
if (dol_strlen($canvas) > 0) $sql.= " AND p.canvas = '".$db->escape($canvas)."'";
if ($catid > 0) $sql.= " AND cp.fk_categorie = ".$catid;
if ($catid == -2) $sql.= " AND cp.fk_categorie IS NULL";
-if ($search_categ > 0) $sql.= " AND cp.fk_categorie = ".$db->escape($search_categ);
-if ($search_categ == -2) $sql.= " AND cp.fk_categorie IS NULL";
+$searchCategoryProductSqlList = array();
+if ($searchCategoryProductOperator == 1) {
+ foreach ($searchCategoryProductList as $searchCategoryProduct) {
+ if (intval($searchCategoryProduct) == -2) {
+ $searchCategoryProductSqlList[] = "cp.fk_categorie IS NULL";
+ } elseif (intval($searchCategoryProduct) > 0) {
+ $searchCategoryProductSqlList[] = "cp.fk_categorie = " . $db->escape($searchCategoryProduct);
+ }
+ }
+ if (!empty($searchCategoryProductSqlList)) {
+ $sql .= " AND (" . implode(' OR ', $searchCategoryProductSqlList) . ")";
+ }
+} else {
+ foreach ($searchCategoryProductList as $searchCategoryProduct) {
+ if (intval($searchCategoryProduct) == -2) {
+ $searchCategoryProductSqlList[] = "cp.fk_categorie IS NULL";
+ } elseif (intval($searchCategoryProduct) > 0) {
+ $searchCategoryProductSqlList[] = "p.rowid IN (SELECT fk_product FROM " . MAIN_DB_PREFIX . "categorie_product WHERE fk_categorie = " . $searchCategoryProduct . ")";
+ }
+ }
+ if (!empty($searchCategoryProductSqlList)) {
+ $sql .= " AND (" . implode(' AND ', $searchCategoryProductSqlList) . ")";
+ }
+}
if ($fourn_id > 0) $sql.= " AND pfp.fk_soc = ".$fourn_id;
if ($search_tobatch != '' && $search_tobatch >= 0) $sql.= " AND p.tobatch = ".$db->escape($search_tobatch);
if ($search_accountancy_code_sell) $sql.= natural_search('p.accountancy_code_sell', $search_accountancy_code_sell);
@@ -421,7 +445,10 @@ if ($resql)
if (! empty($contextpage) && $contextpage != $_SERVER["PHP_SELF"]) $param.='&contextpage='.urlencode($contextpage);
if ($limit > 0 && $limit != $conf->liste_limit) $param.='&limit='.urlencode($limit);
if ($sall) $param.="&sall=".urlencode($sall);
- if ($search_categ > 0) $param.="&search_categ=".urlencode($search_categ);
+ if ($searchCategoryProductOperator == 1) $param .= "&search_category_product_operator=" . urlencode($searchCategoryProductOperator);
+ foreach ($searchCategoryProductList as $searchCategoryProduct) {
+ $param .= "&search_category_product_list[]=" . urlencode($searchCategoryProduct);
+ }
if ($search_ref) $param="&search_ref=".urlencode($search_ref);
if ($search_ref_supplier) $param="&search_ref_supplier=".urlencode($search_ref_supplier);
if ($search_barcode) $param.=($search_barcode?"&search_barcode=".urlencode($search_barcode):"");
@@ -430,7 +457,7 @@ if ($resql)
if ($search_tobuy != '') $param.="&search_tobuy=".urlencode($search_tobuy);
if ($search_vatrate) $sql .= natural_search('p.tva_tx', $search_vatrate);
if ($fourn_id > 0) $param.=($fourn_id?"&fourn_id=".$fourn_id:"");
- if ($seach_categ) $param.=($search_categ?"&search_categ=".urlencode($search_categ):"");
+ //if ($seach_categ) $param.=($search_categ?"&search_categ=".urlencode($search_categ):"");
if ($show_childproducts) $param.=($show_childproducts?"&search_show_childproducts=".urlencode($show_childproducts):"");
if ($type != '') $param.='&type='.urlencode($type);
if ($search_type != '') $param.='&search_type='.urlencode($search_type);
@@ -465,7 +492,7 @@ if ($resql)
$label='NewProduct';
if($type == Product::TYPE_SERVICE) $label='NewService';
$newcardbutton.= dolGetButtonTitle($langs->trans($label), '', 'fa fa-plus-circle', DOL_URL_ROOT.'/product/card.php?action=create&type='.$type);
- }
+ }
print '