diff --git a/htdocs/install/mysql/migration/4.0.0-5.0.0.sql b/htdocs/install/mysql/migration/4.0.0-5.0.0.sql index 9bd984bba1b09e9e54ce8cb98828d05da834dca2..e81027fd22f8d588668ae7750fd9b8103bcfc901 100644 --- a/htdocs/install/mysql/migration/4.0.0-5.0.0.sql +++ b/htdocs/install/mysql/migration/4.0.0-5.0.0.sql @@ -149,4 +149,7 @@ ALTER TABLE llx_commande_fournisseurdet ADD COLUMN vat_src_code varchar(10) DEFA ALTER TABLE llx_propaldet ADD COLUMN vat_src_code varchar(10) DEFAULT '' AFTER tva_tx; ALTER TABLE llx_supplier_proposaldet ADD COLUMN vat_src_code varchar(10) DEFAULT '' AFTER tva_tx; - +ALTER TABLE llx_c_payment_term change fdm type_cdr tinyint; + +ALTER TABLE llx_entrepot ADD COLUMN fk_parent integer DEFAULT 0; + diff --git a/htdocs/install/mysql/tables/llx_entrepot.sql b/htdocs/install/mysql/tables/llx_entrepot.sql index a8898a7ec92b414cb23f28fa4ecad6ec8688de22..d93eb6e157532d9551233f075dd4577127aec2e4 100644 --- a/htdocs/install/mysql/tables/llx_entrepot.sql +++ b/htdocs/install/mysql/tables/llx_entrepot.sql @@ -34,5 +34,6 @@ create table llx_entrepot fk_pays integer DEFAULT 0, statut tinyint DEFAULT 1, -- 1 open, 0 close fk_user_author integer, - import_key varchar(14) + import_key varchar(14), + fk_parent integer DEFAULT 0 )ENGINE=innodb; diff --git a/htdocs/langs/en_US/main.lang b/htdocs/langs/en_US/main.lang index 2c8d19a50c3e6a2566c8fffbb3da2fffe01afdc4..a3851b893393eaefb44a1fd7f732cabf928b6b51 100644 --- a/htdocs/langs/en_US/main.lang +++ b/htdocs/langs/en_US/main.lang @@ -62,6 +62,7 @@ ErrorCantLoadUserFromDolibarrDatabase=Failed to find user <b>%s</b> in Dolibarr ErrorNoVATRateDefinedForSellerCountry=Error, no vat rates defined for country '%s'. ErrorNoSocialContributionForSellerCountry=Error, no social/fiscal taxes type defined for country '%s'. ErrorFailedToSaveFile=Error, failed to save file. +ErrorCannotAddThisParentWarehouse=You are trying to add a parent warehouse which is already a child of current one NotAuthorized=You are not authorized to do that. SetDate=Set date SelectDate=Select a date diff --git a/htdocs/langs/en_US/stocks.lang b/htdocs/langs/en_US/stocks.lang index 413e6c8008871a50726f77d12eb6fa547de5b3da..d5fa618b1e0ec05d75f8932f61d85bf3bc73992e 100644 --- a/htdocs/langs/en_US/stocks.lang +++ b/htdocs/langs/en_US/stocks.lang @@ -2,6 +2,7 @@ WarehouseCard=Warehouse card Warehouse=Warehouse Warehouses=Warehouses +ParentWarehouse=Parent warehouse NewWarehouse=New warehouse / Stock area WarehouseEdit=Modify warehouse MenuNewWarehouse=New warehouse diff --git a/htdocs/product/class/html.formproduct.class.php b/htdocs/product/class/html.formproduct.class.php index cedf40678d24582116adbb1290d8c69e38094b0c..d1fe225ba0e4b1eaac0a6ec4264f9188ac8494d7 100644 --- a/htdocs/product/class/html.formproduct.class.php +++ b/htdocs/product/class/html.formproduct.class.php @@ -56,15 +56,18 @@ class FormProduct * @param string $batch Add quantity of batch stock in label for product with batch name batch, batch name precedes batch_id. Nothing if ''. * @param int $status additional filter on status other then 1 * @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 */ - function loadWarehouses($fk_product=0, $batch = '', $status=null, $sumStock = true) + function loadWarehouses($fk_product=0, $batch = '', $status=null, $sumStock = true, $exclude='') { global $conf, $langs; if (empty($fk_product) && count($this->cache_warehouses)) return 0; // Cache already loaded and we do not want a list with information specific to a product - - $sql = "SELECT e.rowid, e.label, e.description"; + + if (is_array($exclude)) $excludeGroups = implode("','",$exclude); + + $sql = "SELECT e.rowid, e.label, e.description, e.fk_parent"; if (!empty($fk_product)) { if (!empty($batch)) @@ -100,6 +103,8 @@ class FormProduct $sql.= " AND e.statut = 1"; } + if(!empty($exclude)) $sql.= ' AND e.rowid NOT IN('.implode(',', $exclude).')'; + if ($sumStock && empty($fk_product)) $sql.= " GROUP BY e.rowid, e.label, e.description"; $sql.= " ORDER BY e.label"; @@ -115,10 +120,17 @@ class FormProduct if ($sumStock) $obj->stock = price2num($obj->stock,5); $this->cache_warehouses[$obj->rowid]['id'] =$obj->rowid; $this->cache_warehouses[$obj->rowid]['label']=$obj->label; + $this->cache_warehouses[$obj->rowid]['parent_id']=$obj->fk_parent; $this->cache_warehouses[$obj->rowid]['description'] = $obj->description; $this->cache_warehouses[$obj->rowid]['stock'] = $obj->stock; $i++; } + + // Full label init + foreach($this->cache_warehouses as $obj_rowid=>$tab) { + $this->cache_warehouses[$obj_rowid]['full_label'] = $this->get_parent_path($tab); + } + return $num; } else @@ -127,6 +139,29 @@ class FormProduct return -1; } } + + /** + * Return full path to current warehouse in $tab (recursive function) + * + * @param array $tab warehouse data in $this->cache_warehouses line + * @param String $final_label full label with all parents, separated by ' >> ' (completed on each call) + * @return String full label with all parents, separated by ' >> ' + */ + private function get_parent_path($tab, $final_label='') { + + if(empty($final_label)) $final_label = $tab['label']; + + if(empty($tab['parent_id'])) return $final_label; + else { + if(!empty($this->cache_warehouses[$tab['parent_id']])) { + $final_label = $this->cache_warehouses[$tab['parent_id']]['label'].' >> '.$final_label; + return $this->get_parent_path($this->cache_warehouses[$tab['parent_id']], $final_label); + } + } + + return $final_label; + + } /** * Return list of warehouses @@ -142,9 +177,10 @@ class FormProduct * @param int $forcecombo force combo iso ajax select2 * @param array $events events to add to select2 * @param string $morecss Add more css classes + * @param array $exclude warehouses ids to exclude * @return string HTML select */ - function selectWarehouses($selected='',$htmlname='idwarehouse',$filtertype='',$empty=0,$disabled=0,$fk_product=0,$empty_label='', $showstock=0, $forcecombo=0, $events=array(), $morecss='minwidth200') + function selectWarehouses($selected='',$htmlname='idwarehouse',$filtertype='',$empty=0,$disabled=0,$fk_product=0,$empty_label='', $showstock=0, $forcecombo=0, $events=array(), $morecss='minwidth200', $exclude='') { global $conf,$langs,$user; @@ -152,7 +188,7 @@ class FormProduct $out=''; - $this->loadWarehouses($fk_product, '', + $filtertype); // filter on numeric status + $this->loadWarehouses($fk_product, '', + $filtertype, true, $exclude); // filter on numeric status $nbofwarehouses=count($this->cache_warehouses); if ($conf->use_javascript_ajax && ! $forcecombo) @@ -170,7 +206,7 @@ class FormProduct $out.='<option value="'.$id.'"'; if ($selected == $id || ($selected == 'ifone' && $nbofwarehouses == 1)) $out.=' selected'; $out.='>'; - $out.=$arraytypes['label']; + $out.=$arraytypes['full_label']; if (($fk_product || ($showstock > 0)) && ($arraytypes['stock'] != 0 || ($showstock > 0))) $out.=' ('.$langs->trans("Stock").':'.$arraytypes['stock'].')'; $out.='</option>'; } diff --git a/htdocs/product/stock/card.php b/htdocs/product/stock/card.php index 60f7938de06e0fd7d0a0f7302c76ca967d4a0786..5adff357db1c7c85e53fe692992dc63cbb22acec 100644 --- a/htdocs/product/stock/card.php +++ b/htdocs/product/stock/card.php @@ -31,10 +31,12 @@ require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/stock.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php'; require_once DOL_DOCUMENT_ROOT.'/core/class/html.formcompany.class.php'; +require_once DOL_DOCUMENT_ROOT.'/product/class/html.formproduct.class.php'; $langs->load("products"); $langs->load("stocks"); $langs->load("companies"); +$langs->load("categories"); $action=GETPOST('action'); $cancel=GETPOST('cancel'); @@ -64,6 +66,7 @@ $object = new Entrepot($db); if ($action == 'add' && $user->rights->stock->creer) { $object->ref = GETPOST("ref"); + $object->fk_parent = GETPOST("fk_parent"); $object->libelle = GETPOST("libelle"); $object->description = GETPOST("desc"); $object->statut = GETPOST("statut"); @@ -128,6 +131,7 @@ if ($action == 'update' && $cancel <> $langs->trans("Cancel")) if ($object->fetch($id)) { $object->libelle = GETPOST("libelle"); + $object->fk_parent = GETPOST("fk_parent"); $object->description = GETPOST("desc"); $object->statut = GETPOST("statut"); $object->lieu = GETPOST("lieu"); @@ -166,6 +170,7 @@ if ($cancel == $langs->trans("Cancel")) $productstatic=new Product($db); $form=new Form($db); +$formproduct=new FormProduct($db); $formcompany=new FormCompany($db); $help_url='EN:Module_Stocks_En|FR:Module_Stock|ES:Módulo_Stocks'; @@ -189,6 +194,11 @@ if ($action == 'create') print '<tr><td class="titlefieldcreate fieldrequired">'.$langs->trans("Ref").'</td><td colspan="3"><input name="libelle" size="20" value=""></td></tr>'; print '<tr><td >'.$langs->trans("LocationSummary").'</td><td colspan="3"><input name="lieu" size="40" value="'.(!empty($object->lieu)?$object->lieu:'').'"></td></tr>'; + + // Parent entrepot + print '<tr><td>'.$langs->trans("AddIn").'</td><td>'; + print $formproduct->selectWarehouses('', 'fk_parent', '', 1); + print '</td></tr>'; // Description print '<tr><td class="tdtop">'.$langs->trans("Description").'</td><td colspan="3">'; @@ -346,6 +356,16 @@ else //print '<tr><td>'.$langs->trans("LocationSummary").'</td><td colspan="3">'.$object->lieu.'</td></tr>'; + // Parent entrepot + $e = new Entrepot($db); + if(!empty($object->fk_parent) && $e->fetch($object->fk_parent) > 0) { + + print '<tr><td>'.$langs->trans("ParentWarehouse").'</td><td>'; + print $e->getNomUrl(3); + print '</td></tr>'; + + } + // Description print '<tr><td class="tdtop">'.$langs->trans("Description").'</td><td>'.nl2br($object->description).'</td></tr>'; @@ -623,6 +643,11 @@ else print '<tr><td width="20%" class="fieldrequired">'.$langs->trans("Ref").'</td><td colspan="3"><input name="libelle" size="20" value="'.$object->libelle.'"></td></tr>'; print '<tr><td>'.$langs->trans("LocationSummary").'</td><td colspan="3"><input name="lieu" size="40" value="'.$object->lieu.'"></td></tr>'; + + // Parent entrepot + print '<tr><td>'.$langs->trans("AddIn").'</td><td>'; + print $formproduct->selectWarehouses($object->fk_parent, 'fk_parent', '', 1); + print '</td></tr>'; // Description print '<tr><td class="tdtop">'.$langs->trans("Description").'</td><td colspan="3">'; diff --git a/htdocs/product/stock/class/entrepot.class.php b/htdocs/product/stock/class/entrepot.class.php index aee38a67ccf13995657620514e6cda584a8e9e83..032a0f3a56d5467ef6a7ca54f52052fe6be50f38 100644 --- a/htdocs/product/stock/class/entrepot.class.php +++ b/htdocs/product/stock/class/entrepot.class.php @@ -123,8 +123,8 @@ class Entrepot extends CommonObject $this->db->begin(); - $sql = "INSERT INTO ".MAIN_DB_PREFIX."entrepot (entity, datec, fk_user_author, label)"; - $sql .= " VALUES (".$conf->entity.",'".$this->db->idate($now)."',".$user->id.",'".$this->db->escape($this->libelle)."')"; + $sql = "INSERT INTO ".MAIN_DB_PREFIX."entrepot (entity, datec, fk_user_author, label, fk_parent)"; + $sql .= " VALUES (".$conf->entity.",'".$this->db->idate($now)."',".$user->id.",'".$this->db->escape($this->libelle)."', ".($this->fk_parent > 0 ? $this->fk_parent : 'NULL').")"; dol_syslog(get_class($this)."::create", LOG_DEBUG); $result=$this->db->query($sql); @@ -172,6 +172,16 @@ class Entrepot extends CommonObject */ function update($id, $user) { + // Check if new parent is already a child of current warehouse + if(!empty($this->fk_parent)) { + $TChildWarehouses = array($id); + $TChildWarehouses = $this->get_children_warehouses($this->id, $TChildWarehouses); + if(in_array($this->fk_parent, $TChildWarehouses)) { + $this->error = 'ErrorCannotAddThisParentWarehouse'; + return -2; + } + } + $this->libelle=trim($this->libelle); $this->description=trim($this->description); @@ -184,6 +194,7 @@ class Entrepot extends CommonObject $sql = "UPDATE ".MAIN_DB_PREFIX."entrepot "; $sql .= " SET label = '" . $this->db->escape($this->libelle) ."'"; + $sql .= ", fk_parent = '" . (($this->fk_parent > 0) ? $this->fk_parent : 'NULL') ."'"; $sql .= ", description = '" . $this->db->escape($this->description) ."'"; $sql .= ", statut = " . $this->statut; $sql .= ", lieu = '" . $this->db->escape($this->lieu) ."'"; @@ -294,7 +305,7 @@ class Entrepot extends CommonObject { global $conf; - $sql = "SELECT rowid, label, description, statut, lieu, address, zip, town, fk_pays as country_id"; + $sql = "SELECT rowid, fk_parent, label, description, statut, lieu, address, zip, town, fk_pays as country_id"; $sql .= " FROM ".MAIN_DB_PREFIX."entrepot"; if ($id) @@ -317,6 +328,7 @@ class Entrepot extends CommonObject $obj=$this->db->fetch_object($result); $this->id = $obj->rowid; + $this->fk_parent = $obj->fk_parent; $this->ref = $obj->rowid; $this->libelle = $obj->label; $this->description = $obj->description; @@ -573,7 +585,7 @@ class Entrepot extends CommonObject $linkend='</a>'; if ($withpicto) $result.=($link.img_object($label, 'stock', 'class="classfortooltip"').$linkend.' '); - $result.=$link.(empty($this->label)?$this->libelle:$this->label).$linkend; + $result.=$link.$this->get_full_arbo().$linkend; return $result; } @@ -604,4 +616,69 @@ class Entrepot extends CommonObject $this->country_id=1; $this->country_code='FR'; } + + /** + * Return full path to current warehouse + * + * @param int $protection Deep counter to avoid infinite loop + * @return string String full path to current warehouse separated by " >> " + */ + function get_full_arbo($protection=1000) { + + global $user,$langs,$conf; + + $TArbo = array($this->libelle); + + $id = $this->id; + + $i=0; + + while((empty($protection) || $i < $protection)) { + $sql = 'SELECT fk_parent + FROM '.MAIN_DB_PREFIX.'entrepot + WHERE rowid = '.$id; + + $resql = $this->db->query($sql); + if($resql) { + $res = $this->db->fetch_object($resql); + if(empty($res->fk_parent)) break; + $id = $res->fk_parent; + $o = new Entrepot($this->db); + $o->fetch($id); + $TArbo[] = $o->libelle; + } else break; + + $i++; + + } + + return implode(' >> ', array_reverse($TArbo)); + + } + + /** + * Return array of children warehouses ids from $id warehouse (recursive function) + * + * @param int $id id parent warehouse + * @param array() $TChildWarehouses array which will contain all children (param by reference) + * @return array() $TChildWarehouses array which will contain all children + */ + function get_children_warehouses($id, &$TChildWarehouses) { + + $sql = 'SELECT rowid + FROM '.MAIN_DB_PREFIX.'entrepot + WHERE fk_parent = '.$id; + + $resql = $this->db->query($sql); + if($resql) { + while($res = $this->db->fetch_object($resql)) { + $TChildWarehouses[] = $res->rowid; + $this->get_children_warehouses($res->rowid, $TChildWarehouses); + } + } + + return $TChildWarehouses; + + } + }