diff --git a/htdocs/core/ajax/row.php b/htdocs/core/ajax/row.php index 6047d8d289c..52cc23faf8f 100644 --- a/htdocs/core/ajax/row.php +++ b/htdocs/core/ajax/row.php @@ -99,6 +99,8 @@ if (GETPOST('roworder', 'alpha', 3) && GETPOST('table_element_line', 'aZ09', 3) $perm = 1; } elseif ($table_element_line == 'ecm_files' && $fk_element == 'fk_ticket' && !empty($user->rights->ticket->write)) { $perm = 1; + } elseif ($table_element_line == 'product_association' && $fk_element == 'fk_product' && !empty($user->rights->produit->creer)) { + $perm = 1; } elseif ($table_element_line == 'projet_task' && $fk_element == 'fk_projet' && $user->rights->projet->creer) { $perm = 1; } else { diff --git a/htdocs/core/class/commonobject.class.php b/htdocs/core/class/commonobject.class.php index 7adf7a89079..3c95d9c26a0 100644 --- a/htdocs/core/class/commonobject.class.php +++ b/htdocs/core/class/commonobject.class.php @@ -3038,7 +3038,7 @@ abstract class CommonObject * * @param int $rowid Id of line * @param int $rang Position - * @return void + * @return int <0 if KO, >0 if OK */ public function updateRangOfLine($rowid, $rang) { @@ -3054,10 +3054,13 @@ abstract class CommonObject dol_syslog(get_class($this)."::updateRangOfLine", LOG_DEBUG); if (!$this->db->query($sql)) { dol_print_error($this->db); + return -1; + } else { + $parameters=array('rowid'=>$rowid, 'rang'=>$rang, 'fieldposition' => $fieldposition); + $action=''; + $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action); + return 1; } - $parameters=array('rowid'=>$rowid, 'rang'=>$rang, 'fieldposition' => $fieldposition); - $action=''; - $reshook = $hookmanager->executeHooks('afterRankOfLineUpdate', $parameters, $this, $action); } // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps diff --git a/htdocs/install/mysql/migration/14.0.0-15.0.0.sql b/htdocs/install/mysql/migration/14.0.0-15.0.0.sql index 03bb7631dda..912eb7fe0eb 100644 --- a/htdocs/install/mysql/migration/14.0.0-15.0.0.sql +++ b/htdocs/install/mysql/migration/14.0.0-15.0.0.sql @@ -103,6 +103,7 @@ ALTER TABLE llx_categorie_ticket ADD CONSTRAINT fk_categorie_ticket_categorie_ro ALTER TABLE llx_categorie_ticket ADD CONSTRAINT fk_categorie_ticket_ticket_rowid FOREIGN KEY (fk_ticket) REFERENCES llx_ticket (rowid); ALTER TABLE llx_product_fournisseur_price MODIFY COLUMN ref_fourn varchar(128); ALTER TABLE llx_product_customer_price MODIFY COLUMN ref_customer varchar(128); +ALTER TABLE llx_product_association ADD COLUMN rang integer DEFAULT 0; -- -- add action trigger INSERT INTO llx_c_action_trigger (code,label,description,elementtype,rang) VALUES ('ORDER_SUPPLIER_CANCEL','Supplier order request canceled','Executed when a supplier order is canceled','order_supplier',13); diff --git a/htdocs/install/mysql/tables/llx_product_association.sql b/htdocs/install/mysql/tables/llx_product_association.sql index 91cc14882f1..f97e2aa6a0b 100644 --- a/htdocs/install/mysql/tables/llx_product_association.sql +++ b/htdocs/install/mysql/tables/llx_product_association.sql @@ -23,6 +23,7 @@ create table llx_product_association fk_product_pere integer NOT NULL DEFAULT 0, -- id du produit maitre fk_product_fils integer NOT NULL DEFAULT 0, -- id du sous-produit qty double NULL, - incdec integer DEFAULT 1 -- when set to 1 changing stock of product will change stock of linked product too + incdec integer DEFAULT 1, -- when set to 1 changing stock of product will change stock of linked product too + rang integer DEFAULT 0 )ENGINE=innodb; diff --git a/htdocs/langs/en_US/products.lang b/htdocs/langs/en_US/products.lang index 128eb120e0b..19349fb35a8 100644 --- a/htdocs/langs/en_US/products.lang +++ b/htdocs/langs/en_US/products.lang @@ -401,11 +401,10 @@ DeleteLinkedProduct=Delete the child product linked to the combination AmountUsedToUpdateWAP=Amount to use to update the Weighted Average Price PMPValue=Weighted average price PMPValueShort=WAP - mandatoryperiod=Mandatory periods mandatoryPeriodNeedTobeSet=Attention periods not entered and mandatory mandatoryPeriodNeedTobeSetMsgValidate=A service requires a start and end period mandatoryHelper=Message to the user on the need to enter a start date and an end date on a service when creating / validating an invoice, commercial proposal, sales order.
This action is not blocking in the process of confirmation DefaultBOM=Default BOM -DefaultBOMDesc=The default BOM recommended to use to manufacture this product. This field can be set only if nature of product is '%s'. - +DefaultBOMDesc=The default BOM recommended to use to manufacture this product. This field can be set only if nature of product is '%s'. +Rank=Rank diff --git a/htdocs/langs/fr_FR/products.lang b/htdocs/langs/fr_FR/products.lang index 83514bd95ff..24e5c062cc7 100644 --- a/htdocs/langs/fr_FR/products.lang +++ b/htdocs/langs/fr_FR/products.lang @@ -399,6 +399,7 @@ ProductSupplierExtraFields=Attributs supplémentaires (Prix fournisseur) DeleteLinkedProduct=Supprimer le produit enfant lié à la combinaison PMPValue=Prix moyen pondéré (PMP) PMPValueShort=PMP +Rank=Rang mandatoryperiod=Périodes obligatoires mandatoryPeriodNeedTobeSet=Attention périodes non saisies et obligatoires mandatoryPeriodNeedTobeSetMsgValidate=Un service nécessite une période de début et de fin diff --git a/htdocs/product/class/product.class.php b/htdocs/product/class/product.class.php index 2702f7989b3..67521ef149b 100644 --- a/htdocs/product/class/product.class.php +++ b/htdocs/product/class/product.class.php @@ -4021,28 +4021,31 @@ class Product extends CommonObject } // Check not already father of id_pere (to avoid father -> child -> father links) - $sql = 'SELECT fk_product_pere from '.MAIN_DB_PREFIX.'product_association'; - $sql .= ' WHERE fk_product_pere = '.((int) $id_fils).' AND fk_product_fils = '.((int) $id_pere); + $sql = "SELECT fk_product_pere from ".MAIN_DB_PREFIX."product_association"; + $sql .= " WHERE fk_product_pere = ".((int) $id_fils)." AND fk_product_fils = ".((int) $id_pere); if (!$this->db->query($sql)) { dol_print_error($this->db); return -1; } else { - $result = $this->db->query($sql); - if ($result) { - $num = $this->db->num_rows($result); - if ($num > 0) { - $this->error = "isFatherOfThis"; + //Selection of the highest row + $sql = "SELECT MAX(rang) as max_rank FROM ".MAIN_DB_PREFIX."product_association"; + $sql .= " WHERE fk_product_pere = ".((int) $id_pere); + $resql = $this->db->query($sql); + if ($resql > 0) { + $obj = $this->db->fetch_object($resql); + $rank = $obj->max_rank + 1; + //Addition of a product with the highest rank +1 + $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_association(fk_product_pere,fk_product_fils,qty,incdec,rang)"; + $sql .= " VALUES (".((int) $id_pere).", ".((int) $id_fils).", ".((float) $qty).", ".((float) $incdec).", ".$this->db->escape($rank).")"; + if (! $this->db->query($sql)) { + dol_print_error($this->db); return -1; } else { - $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'product_association(fk_product_pere,fk_product_fils,qty,incdec)'; - $sql .= ' VALUES ('.((int) $id_pere).', '.((int) $id_fils).', '.((float) $qty).', '.((int) $incdec).')'; - if (!$this->db->query($sql)) { - dol_print_error($this->db); - return -1; - } else { - return 1; - } + return 1; } + } else { + dol_print_error($this->db); + return -1; } } } @@ -4115,6 +4118,24 @@ class Product extends CommonObject return -1; } + //Updated ranks so that none are missing + $sqlrank = 'SELECT rowid, rang FROM '.MAIN_DB_PREFIX.'product_association'; + $sqlrank.= ' WHERE fk_product_pere = '.$this->db->escape($fk_parent); + $sqlrank.= ' ORDER BY rang'; + $resqlrank = $this->db->query($sqlrank); + if ($resqlrank) { + $cpt = 0; + while ($objrank = $this->db->fetch_object($resqlrank)) { + $cpt++; + $sql = 'UPDATE '.MAIN_DB_PREFIX.'product_association'; + $sql.= ' SET rang ='.$cpt; + $sql.= ' WHERE rowid ='.$this->db->escape($objrank->rowid); + if (! $this->db->query($sql)) { + dol_print_error($this->db); + return -1; + } + } + } return 1; } @@ -4679,12 +4700,14 @@ class Product extends CommonObject } $sql = "SELECT p.rowid, p.ref, p.label as label, p.fk_product_type,"; - $sql .= " pa.qty as qty, pa.fk_product_fils as id, pa.incdec"; + $sql .= " pa.qty as qty, pa.fk_product_fils as id, pa.incdec,"; + $sql .= " pa.rowid as fk_association, pa.rang"; $sql .= " FROM ".MAIN_DB_PREFIX."product as p,"; $sql .= " ".MAIN_DB_PREFIX."product_association as pa"; $sql .= " WHERE p.rowid = pa.fk_product_fils"; $sql .= " AND pa.fk_product_pere = ".((int) $id); $sql .= " AND pa.fk_product_fils <> ".((int) $id); // This should not happens, it is to avoid infinite loop if it happens + $sql.= " ORDER BY pa.rang"; dol_syslog(get_class($this).'::getChildsArbo id='.$id.' level='.$level, LOG_DEBUG); @@ -4711,7 +4734,9 @@ class Product extends CommonObject 2=>$rec['fk_product_type'], 3=>$this->db->escape($rec['label']), 4=>$rec['incdec'], - 5=>$rec['ref'] + 5=>$rec['ref'], + 6=>$rec['fk_association'], + 7=>$rec['rang'] ); //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty'],2=>$rec['fk_product_type']); //$prods[$this->db->escape($rec['label'])]= array(0=>$rec['id'],1=>$rec['qty']); diff --git a/htdocs/product/composition/card.php b/htdocs/product/composition/card.php index 000e5da6cbc..5f783d190e9 100644 --- a/htdocs/product/composition/card.php +++ b/htdocs/product/composition/card.php @@ -132,6 +132,8 @@ if ($action == 'add_prod' && ($user->rights->produit->creer || $user->rights->se setEventMessages('RecordSaved', null); } $action = ''; + header("Location: ".$_SERVER["PHP_SELF"].'?id='.$object->id); + exit; } @@ -268,8 +270,18 @@ if ($id > 0 || !empty($ref)) { $prodsfather = $object->getFather(); // Parent Products $object->get_sousproduits_arbo(); // Load $object->sousprods + $parent_label = $object->label; $prods_arbo = $object->get_arbo_each_prod(); + $tmpid = $id; + if (! empty($conf->use_javascript_ajax)) { + $nboflines = $prods_arbo; + $table_element_line='product_association'; + + include DOL_DOCUMENT_ROOT . '/core/tpl/ajaxrow.tpl.php'; + } + $id = $tmpid; + $nbofsubsubproducts = count($prods_arbo); // This include sub sub product into nb $prodschild = $object->getChildsArbo($id, 1); $nbofsubproducts = count($prodschild); // This include only first level of childs @@ -323,9 +335,10 @@ if ($id > 0 || !empty($ref)) { print ''; print ''; - print ''; + print '
'; - print ''; + print ''; + print ''; print ''; print ''; print ''; @@ -335,6 +348,7 @@ if ($id > 0 || !empty($ref)) { } print ''; print ''; + print ''; print ''."\n"; $totalsell = 0; @@ -343,7 +357,9 @@ if ($id > 0 || !empty($ref)) { $productstatic->fetch($value['id']); if ($value['level'] <= 1) { - print ''; + print ''; + + print ''; $notdefined = 0; $nb_of_subproduct = $value['nb']; @@ -407,6 +423,8 @@ if ($id > 0 || !empty($ref)) { print ''; } + print ''; print ''."\n"; } else { $hide = ''; @@ -414,10 +432,11 @@ if ($id > 0 || !empty($ref)) { $hide = ' hideobject'; // By default, we do not show this. It makes screen very difficult to understand } - print ''; + print ''; //$productstatic->ref=$value['label']; $productstatic->ref = $value['ref']; + print ''; print ''; print ''; + print ''; print ''."\n"; }
'.$langs->trans('Rank').''.$langs->trans('ComposedProduct').''.$langs->trans('Label').''.$langs->trans('MinSupplierPrice').''.$langs->trans('Qty').''.$langs->trans('ComposedProductIncDecStock').'
'.$object->sousprods[$parent_label][$value['id']][7].''.($value['incdec'] == 1 ? 'x' : '').''; + print '
'; for ($i = 0; $i < $value['level']; $i++) { print '     '; // Add indentation @@ -437,6 +456,7 @@ if ($id > 0 || !empty($ref)) { } print ''.$value['nb'].'