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 '
| '.$langs->trans('Rank').' | '; print ''.$langs->trans('ComposedProduct').' | '; print ''.$langs->trans('Label').' | '; print ''.$langs->trans('MinSupplierPrice').' | '; @@ -335,6 +348,7 @@ if ($id > 0 || !empty($ref)) { } print ''.$langs->trans('Qty').' | '; print ''.$langs->trans('ComposedProductIncDecStock').' | '; + print ''; print ' | |
| '.$object->sousprods[$parent_label][$value['id']][7].' | '; $notdefined = 0; $nb_of_subproduct = $value['nb']; @@ -407,6 +423,8 @@ if ($id > 0 || !empty($ref)) { print ''.($value['incdec'] == 1 ? 'x' : '').' | '; } + print ''; + print ' | '; print '|||||
| '; print ' | '; for ($i = 0; $i < $value['level']; $i++) { print ' '; // Add indentation @@ -437,6 +456,7 @@ if ($id > 0 || !empty($ref)) { } print ' | '.$value['nb'].' | '; print ''; + print ' | '; print ' | |||