dolibarr  18.0.6
ProductCombination.class.php
1 <?php
2 /* Copyright (C) 2016 Marcos GarcĂ­a <marcosgdf@gmail.com>
3  * Copyright (C) 2018 Juanjo Menent <jmenent@2byte.es>
4  * Copyright (C) 2022 Open-Dsi <support@open-dsi.fr>
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see <https://www.gnu.org/licenses/>.
18  */
19 
25 {
30  public $db;
31 
36  public $id;
37 
42  public $fk_product_parent;
43 
48  public $fk_product_child;
49 
54  public $variation_price;
55 
60  public $variation_price_percentage = false;
61 
66  public $variation_weight;
67 
72  public $entity;
73 
78  public $combination_price_levels;
79 
84  public $variation_ref_ext = '';
85 
89  public $error;
90 
94  public $errors = array();
95 
101  public function __construct(DoliDB $db)
102  {
103  global $conf;
104 
105  $this->db = $db;
106  $this->entity = $conf->entity;
107  }
108 
115  public function fetch($rowid)
116  {
117  global $conf;
118 
119  $sql = "SELECT rowid, fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight, variation_ref_ext FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE rowid = ".((int) $rowid)." AND entity IN (".getEntity('product').")";
120 
121  $query = $this->db->query($sql);
122 
123  if (!$query) {
124  return -1;
125  }
126 
127  if (!$this->db->num_rows($query)) {
128  return -1;
129  }
130 
131  $obj = $this->db->fetch_object($query);
132 
133  $this->id = $obj->rowid;
134  $this->fk_product_parent = $obj->fk_product_parent;
135  $this->fk_product_child = $obj->fk_product_child;
136  $this->variation_price = $obj->variation_price;
137  $this->variation_price_percentage = $obj->variation_price_percentage;
138  $this->variation_weight = $obj->variation_weight;
139  $this->variation_ref_ext = $obj->variation_ref_ext;
140 
141  if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
143  }
144 
145  return 1;
146  }
147 
148 
156  public function fetchCombinationPriceLevels($fk_price_level = 0, $useCache = true)
157  {
158  global $conf;
159 
160  // Check cache
161  if (!empty($this->combination_price_levels) && $useCache) {
162  if ((!empty($fk_price_level) && isset($this->combination_price_levels[$fk_price_level])) || empty($fk_price_level)) {
163  return 1;
164  }
165  }
166 
167  if (!is_array($this->combination_price_levels)
168  || empty($fk_price_level) // if fetch an unique level dont erase all already fetched
169  ) {
170  $this->combination_price_levels = array();
171  }
172 
173  $staticProductCombinationLevel = new ProductCombinationLevel($this->db);
174  $combination_price_levels = $staticProductCombinationLevel->fetchAll($this->id, $fk_price_level);
175 
176  if (!is_array($combination_price_levels)) {
177  return -1;
178  }
179 
180  if (empty($combination_price_levels)) {
184  if ($fk_price_level > 0) {
185  $combination_price_levels[$fk_price_level] = ProductCombinationLevel::createFromParent($this->db, $this, $fk_price_level);
186  } else {
187  for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
188  $combination_price_levels[$i] = ProductCombinationLevel::createFromParent($this->db, $this, $i);
189  }
190  }
191  }
192 
193  $this->combination_price_levels = $combination_price_levels;
194 
195  return 1;
196  }
197 
204  public function saveCombinationPriceLevels($clean = 1)
205  {
206  global $conf;
207 
208  $error = 0;
209 
210  $staticProductCombinationLevel = new ProductCombinationLevel($this->db);
211 
212  // Delete all
213  if (empty($this->combination_price_levels)) {
214  return $staticProductCombinationLevel->deleteAllForCombination($this->id);
215  }
216 
217  // Clean not needed price levels (level higher than number max defined into setup)
218  if ($clean) {
219  $res = $staticProductCombinationLevel->clean($this->id);
220  if ($res < 0) {
221  $this->errors[] = 'Fail to clean not needed price levels';
222  return -1;
223  }
224  }
225 
226  foreach ($this->combination_price_levels as $fk_price_level => $combination_price_level) {
227  $res = $combination_price_level->save();
228  if ($res < 1) {
229  $this->error = 'Error saving combination price level '.$fk_price_level.' : '.$combination_price_level->error;
230  $this->errors[] = $this->error;
231  $error++;
232  break;
233  }
234  }
235 
236  if ($error) {
237  return $error * -1;
238  } else {
239  return 1;
240  }
241  }
242 
250  public function fetchByFkProductChild($productid, $donotloadpricelevel = 0)
251  {
252  global $conf;
253 
254  $sql = "SELECT rowid, fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight";
255  $sql .= " FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE fk_product_child = ".((int) $productid)." AND entity IN (".getEntity('product').")";
256 
257  $query = $this->db->query($sql);
258 
259  if (!$query) {
260  return -1;
261  }
262 
263  if (!$this->db->num_rows($query)) {
264  return 0;
265  }
266 
267  $result = $this->db->fetch_object($query);
268 
269  $this->id = $result->rowid;
270  $this->fk_product_parent = $result->fk_product_parent;
271  $this->fk_product_child = $result->fk_product_child;
272  $this->variation_price = $result->variation_price;
273  $this->variation_price_percentage = $result->variation_price_percentage;
274  $this->variation_weight = $result->variation_weight;
275 
276  if (empty($donotloadpricelevel) && !empty($conf->global->PRODUIT_MULTIPRICES)) {
278  }
279 
280  return (int) $this->fk_product_parent;
281  }
282 
289  public function fetchAllByFkProductParent($fk_product_parent)
290  {
291  global $conf;
292 
293  $sql = "SELECT rowid, fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_ref_ext, variation_weight";
294  $sql.= " FROM ".MAIN_DB_PREFIX."product_attribute_combination";
295  $sql.= " WHERE fk_product_parent = ".((int) $fk_product_parent)." AND entity IN (".getEntity('product').")";
296 
297  $query = $this->db->query($sql);
298 
299  if (!$query) {
300  return -1;
301  }
302 
303  $return = array();
304 
305  while ($result = $this->db->fetch_object($query)) {
306  $tmp = new ProductCombination($this->db);
307  $tmp->id = $result->rowid;
308  $tmp->fk_product_parent = $result->fk_product_parent;
309  $tmp->fk_product_child = $result->fk_product_child;
310  $tmp->variation_price = $result->variation_price;
311  $tmp->variation_price_percentage = $result->variation_price_percentage;
312  $tmp->variation_weight = $result->variation_weight;
313  $tmp->variation_ref_ext = $result->variation_ref_ext;
314 
315  if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
316  $tmp->fetchCombinationPriceLevels();
317  }
318 
319  $return[] = $tmp;
320  }
321 
322  return $return;
323  }
324 
331  public function countNbOfCombinationForFkProductParent($fk_product_parent)
332  {
333  $nb = 0;
334  $sql = "SELECT count(rowid) as nb FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE fk_product_parent = ".((int) $fk_product_parent)." AND entity IN (".getEntity('product').")";
335 
336  $resql = $this->db->query($sql);
337  if ($resql) {
338  $obj = $this->db->fetch_object($resql);
339  if ($obj) {
340  $nb = $obj->nb;
341  }
342  }
343 
344  return $nb;
345  }
346 
353  public function create($user)
354  {
355  global $conf;
356 
357  /* $this->fk_product_child may be empty and will be filled later after subproduct has been created */
358 
359  $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_attribute_combination";
360  $sql .= " (fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight, variation_ref_ext, entity)";
361  $sql .= " VALUES (".((int) $this->fk_product_parent).", ".((int) $this->fk_product_child).",";
362  $sql .= (float) $this->variation_price.", ".(int) $this->variation_price_percentage.",";
363  $sql .= (float) $this->variation_weight.", '".$this->db->escape($this->variation_ref_ext)."', ".(int) $this->entity.")";
364 
365  $resql = $this->db->query($sql);
366  if ($resql) {
367  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'product_attribute_combination');
368  } else {
369  $this->error = $this->db->lasterror();
370  return -1;
371  }
372 
373  if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
374  $res = $this->saveCombinationPriceLevels();
375  if ($res < 0) {
376  return -2;
377  }
378  }
379 
380  return 1;
381  }
382 
389  public function update(User $user)
390  {
391  global $conf;
392 
393  $sql = "UPDATE ".MAIN_DB_PREFIX."product_attribute_combination";
394  $sql .= " SET fk_product_parent = ".(int) $this->fk_product_parent.", fk_product_child = ".(int) $this->fk_product_child.",";
395  $sql .= " variation_price = ".(float) $this->variation_price.", variation_price_percentage = ".(int) $this->variation_price_percentage.",";
396  $sql .= " variation_ref_ext = '".$this->db->escape($this->variation_ref_ext)."',";
397  $sql .= " variation_weight = ".(float) $this->variation_weight." WHERE rowid = ".((int) $this->id);
398 
399  $resql = $this->db->query($sql);
400  if (!$resql) {
401  return -1;
402  }
403 
404  if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
405  $res = $this->saveCombinationPriceLevels();
406  if ($res < 0) {
407  return -2;
408  }
409  }
410 
411  $parent = new Product($this->db);
412  $parent->fetch($this->fk_product_parent);
413 
414  $this->updateProperties($parent, $user);
415 
416  return 1;
417  }
418 
425  public function delete(User $user)
426  {
427  $this->db->begin();
428 
429  $comb2val = new ProductCombination2ValuePair($this->db);
430  $comb2val->deleteByFkCombination($this->id);
431 
432  // remove combination price levels
433  if (!$this->db->query("DELETE FROM ".MAIN_DB_PREFIX."product_attribute_combination_price_level WHERE fk_product_attribute_combination = ".(int) $this->id)) {
434  $this->db->rollback();
435  return -1;
436  }
437 
438  $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE rowid = ".(int) $this->id;
439 
440  if ($this->db->query($sql)) {
441  $this->db->commit();
442  return 1;
443  }
444 
445  $this->db->rollback();
446  return -1;
447  }
448 
456  public function deleteByFkProductParent($user, $fk_product_parent)
457  {
458  $this->db->begin();
459 
460  foreach ($this->fetchAllByFkProductParent($fk_product_parent) as $prodcomb) {
461  $prodstatic = new Product($this->db);
462 
463  $res = $prodstatic->fetch($prodcomb->fk_product_child);
464 
465  if ($res > 0) {
466  $res = $prodcomb->delete($user);
467  }
468 
469  if ($res > 0 && !$prodstatic->isObjectUsed($prodstatic->id)) {
470  $res = $prodstatic->delete($user);
471  }
472 
473  if ($res < 0) {
474  $this->db->rollback();
475  return -1;
476  }
477  }
478 
479  $this->db->commit();
480  return 1;
481  }
482 
491  public function updateProperties(Product $parent, User $user)
492  {
493  global $conf;
494 
495  $this->db->begin();
496 
497  $child = new Product($this->db);
498  $child->fetch($this->fk_product_child);
499 
500  $child->price_autogen = $parent->price_autogen;
501  $child->weight = $parent->weight;
502  // Only when Parent Status are updated
503  if (!empty($parent->oldcopy) && ($parent->status != $parent->oldcopy->status)) {
504  $child->status = $parent->status;
505  }
506  if (!empty($parent->oldcopy) && ($parent->status_buy != $parent->oldcopy->status_buy)) {
507  $child->status_buy = $parent->status_buy;
508  }
509 
510  if ($this->variation_weight) { // If we must add a delta on weight
511  $child->weight = ($child->weight ? $child->weight : 0) + $this->variation_weight;
512  }
513  $child->weight_units = $parent->weight_units;
514 
515  // Don't update the child label if the user has already modified it.
516  if ($child->label == $parent->label) {
517  // This will trigger only at variant creation time
518  $varlabel = $this->getCombinationLabel($this->fk_product_child);
519  $child->label = $parent->label.$varlabel;
520  }
521 
522 
523  if ($child->update($child->id, $user) > 0) {
524  $new_vat = $parent->tva_tx;
525  $new_npr = $parent->tva_npr;
526 
527  // MultiPrix
528  if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
529  for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
530  if ($parent->multiprices[$i] != '' || isset($this->combination_price_levels[$i]->variation_price)) {
531  $new_type = empty($parent->multiprices_base_type[$i]) ? 'HT' : $parent->multiprices_base_type[$i];
532  $new_min_price = $parent->multiprices_min[$i];
533  $variation_price = floatval(!isset($this->combination_price_levels[$i]->variation_price) ? $this->variation_price : $this->combination_price_levels[$i]->variation_price);
534  $variation_price_percentage = floatval(!isset($this->combination_price_levels[$i]->variation_price_percentage) ? $this->variation_price_percentage : $this->combination_price_levels[$i]->variation_price_percentage);
535 
536  if ($parent->prices_by_qty_list[$i]) {
537  $new_psq = 1;
538  } else {
539  $new_psq = 0;
540  }
541 
542  if ($new_type == 'TTC') {
543  $new_price = $parent->multiprices_ttc[$i];
544  } else {
545  $new_price = $parent->multiprices[$i];
546  }
547 
548  if ($variation_price_percentage) {
549  if ($new_price != 0) {
550  $new_price *= 1 + ($variation_price / 100);
551  }
552  } else {
553  $new_price += $variation_price;
554  }
555 
556  $ret = $child->updatePrice($new_price, $new_type, $user, $new_vat, $new_min_price, $i, $new_npr, $new_psq, 0, array(), $parent->default_vat_code);
557 
558  if ($ret < 0) {
559  $this->db->rollback();
560  $this->error = $child->error;
561  $this->errors = $child->errors;
562  return $ret;
563  }
564  }
565  }
566  } else {
567  $new_type = $parent->price_base_type;
568  $new_min_price = $parent->price_min;
569  $new_psq = $parent->price_by_qty;
570 
571  if ($new_type == 'TTC') {
572  $new_price = $parent->price_ttc;
573  } else {
574  $new_price = $parent->price;
575  }
576 
577  if ($this->variation_price_percentage) {
578  if ($new_price != 0) {
579  $new_price *= 1 + ($this->variation_price / 100);
580  }
581  } else {
582  $new_price += $this->variation_price;
583  }
584 
585  $ret = $child->updatePrice($new_price, $new_type, $user, $new_vat, $new_min_price, 1, $new_npr, $new_psq);
586 
587  if ($ret < 0) {
588  $this->db->rollback();
589  $this->error = $child->error;
590  $this->errors = $child->errors;
591  return $ret;
592  }
593  }
594 
595  $this->db->commit();
596 
597  return 1;
598  }
599 
600  $this->db->rollback();
601  $this->error = $child->error;
602  $this->errors = $child->errors;
603  return -1;
604  }
605 
613  public function fetchByProductCombination2ValuePairs($prodid, array $features)
614  {
615  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
616 
617  $actual_comp = array();
618 
619  $prodcomb2val = new ProductCombination2ValuePair($this->db);
620  $prodcomb = new ProductCombination($this->db);
621 
622  $features = array_filter($features, function ($v) {
623  return !empty($v);
624  });
625 
626  foreach ($features as $attr => $attr_val) {
627  $actual_comp[$attr] = $attr_val;
628  }
629 
630  foreach ($prodcomb->fetchAllByFkProductParent($prodid) as $prc) {
631  $values = array();
632 
633  foreach ($prodcomb2val->fetchByFkCombination($prc->id) as $value) {
634  $values[$value->fk_prod_attr] = $value->fk_prod_attr_val;
635  }
636 
637  $check1 = count(array_diff_assoc($values, $actual_comp));
638  $check2 = count(array_diff_assoc($actual_comp, $values));
639 
640  if (!$check1 && !$check2) {
641  return $prc;
642  }
643  }
644 
645  return false;
646  }
647 
655  {
656  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
657  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
658 
659  $variants = array();
660 
661  //Attributes
662  $sql = "SELECT DISTINCT fk_prod_attr, a.position";
663  $sql .= " FROM ".MAIN_DB_PREFIX."product_attribute_combination2val c2v LEFT JOIN ".MAIN_DB_PREFIX."product_attribute_combination c ON c2v.fk_prod_combination = c.rowid";
664  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = c.fk_product_child";
665  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_attribute a ON a.rowid = fk_prod_attr";
666  $sql .= " WHERE c.fk_product_parent = ".((int) $productid)." AND p.tosell = 1";
667  $sql .= $this->db->order('a.position', 'asc');
668 
669  $query = $this->db->query($sql);
670 
671  //Values
672  while ($result = $this->db->fetch_object($query)) {
673  $attr = new ProductAttribute($this->db);
674  $attr->fetch($result->fk_prod_attr);
675 
676  $tmp = new stdClass();
677  $tmp->id = $attr->id;
678  $tmp->ref = $attr->ref;
679  $tmp->label = $attr->label;
680  $tmp->values = array();
681 
682  $attrval = new ProductAttributeValue($this->db);
683  foreach ($res = $attrval->fetchAllByProductAttribute($attr->id, true) as $val) {
684  $tmp->values[] = $val;
685  }
686 
687  $variants[] = $tmp;
688  }
689 
690  return $variants;
691  }
692 
716  public function createProductCombination(User $user, Product $product, array $combinations, array $variations, $price_var_percent = false, $forced_pricevar = false, $forced_weightvar = false, $forced_refvar = false, $ref_ext = '')
717  {
718  global $conf;
719 
720  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
721  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
722 
723  $this->db->begin();
724 
725  $price_impact = array(1=>0); // init level price impact
726 
727  $forced_refvar = trim($forced_refvar);
728 
729  if (!empty($forced_refvar) && $forced_refvar != $product->ref) {
730  $existingProduct = new Product($this->db);
731  $result = $existingProduct->fetch('', $forced_refvar);
732  if ($result > 0) {
733  $newproduct = $existingProduct;
734  } else {
735  $existingProduct = false;
736  $newproduct = clone $product;
737  $newproduct->ref = $forced_refvar;
738  }
739  } else {
740  $forced_refvar = false;
741  $existingProduct = false;
742  $newproduct = clone $product;
743  }
744 
745  //Final weight impact
746  $weight_impact = (float) $forced_weightvar; // If false, return 0
747 
748  //Final price impact
749  if (!is_array($forced_pricevar)) {
750  $price_impact[1] = (float) $forced_pricevar; // If false, return 0
751  } else {
752  $price_impact = $forced_pricevar;
753  }
754 
755  if (!array($price_var_percent)) {
756  $price_var_percent[1] = (float) $price_var_percent;
757  }
758 
759  $newcomb = new ProductCombination($this->db);
760  $existingCombination = $newcomb->fetchByProductCombination2ValuePairs($product->id, $combinations);
761 
762  if ($existingCombination) {
763  $newcomb = $existingCombination;
764  } else {
765  $newcomb->fk_product_parent = $product->id;
766 
767  // Create 1 entry into product_attribute_combination (1 entry for each combinations). This init also $newcomb->id
768  $result = $newcomb->create($user);
769  if ($result < 0) {
770  $this->error = $newcomb->error;
771  $this->errors = $newcomb->errors;
772  $this->db->rollback();
773  return -1;
774  }
775  }
776 
777  $prodattr = new ProductAttribute($this->db);
778  $prodattrval = new ProductAttributeValue($this->db);
779 
780  // $combination contains list of attributes pairs key->value. Example: array('id Color'=>id Blue, 'id Size'=>id Small, 'id Option'=>id val a, ...)
781  //var_dump($combinations);
782  foreach ($combinations as $currcombattr => $currcombval) {
783  //This was checked earlier, so no need to double check
784  $prodattr->fetch($currcombattr);
785  $prodattrval->fetch($currcombval);
786 
787  //If there is an existing combination, there is no need to duplicate the valuepair
788  if (!$existingCombination) {
789  $tmp = new ProductCombination2ValuePair($this->db);
790  $tmp->fk_prod_attr = $currcombattr;
791  $tmp->fk_prod_attr_val = $currcombval;
792  $tmp->fk_prod_combination = $newcomb->id;
793 
794  if ($tmp->create($user) < 0) { // Create 1 entry into product_attribute_combination2val
795  $this->error = $tmp->error;
796  $this->errors = $tmp->errors;
797  $this->db->rollback();
798  return -1;
799  }
800  }
801 
802  if ($forced_weightvar === false) {
803  $weight_impact += (float) price2num($variations[$currcombattr][$currcombval]['weight']);
804  }
805  if ($forced_pricevar === false) {
806  $price_impact[1] += (float) price2num($variations[$currcombattr][$currcombval]['price']);
807 
808  // Manage Price levels
809  if ($conf->global->PRODUIT_MULTIPRICES) {
810  for ($i = 2; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
811  $price_impact[$i] += (float) price2num($variations[$currcombattr][$currcombval]['price']);
812  }
813  }
814  }
815 
816  if ($forced_refvar === false) {
817  if (isset($conf->global->PRODUIT_ATTRIBUTES_SEPARATOR)) {
818  $newproduct->ref .= $conf->global->PRODUIT_ATTRIBUTES_SEPARATOR.$prodattrval->ref;
819  } else {
820  $newproduct->ref .= '_'.$prodattrval->ref;
821  }
822  }
823 
824  //The first one should not contain a linebreak
825  if ($newproduct->description) {
826  $newproduct->description .= '<br>';
827  }
828  $newproduct->description .= '<strong>'.$prodattr->label.':</strong> '.$prodattrval->value;
829  }
830 
831  $newcomb->variation_price_percentage = $price_var_percent[1];
832  $newcomb->variation_price = $price_impact[1];
833  $newcomb->variation_weight = $weight_impact;
834  $newcomb->variation_ref_ext = $this->db->escape($ref_ext);
835 
836  // Init price level
837  if ($conf->global->PRODUIT_MULTIPRICES) {
838  for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
839  $productCombinationLevel = new ProductCombinationLevel($this->db);
840  $productCombinationLevel->fk_product_attribute_combination = $newcomb->id;
841  $productCombinationLevel->fk_price_level = $i;
842  $productCombinationLevel->variation_price = $price_impact[$i];
843 
844  if (is_array($price_var_percent)) {
845  $productCombinationLevel->variation_price_percentage = (empty($price_var_percent[$i]) ? false : $price_var_percent[$i]);
846  } else {
847  $productCombinationLevel->variation_price_percentage = $price_var_percent;
848  }
849 
850  $newcomb->combination_price_levels[$i] = $productCombinationLevel;
851  }
852  }
853  //var_dump($newcomb->combination_price_levels);
854 
855  $newproduct->weight += $weight_impact;
856 
857  // Now create the product
858  //print 'Create prod '.$newproduct->ref.'<br>'."\n";
859  if ($existingProduct === false) {
860  //To avoid wrong information in price history log
861  $newproduct->price = 0;
862  $newproduct->price_ttc = 0;
863  $newproduct->price_min = 0;
864  $newproduct->price_min_ttc = 0;
865 
866  // A new variant must use a new barcode (not same product)
867  $newproduct->barcode = -1;
868  $result = $newproduct->create($user);
869 
870  if ($result < 0) {
871  //In case the error is not related with an already existing product
872  if ($newproduct->error != 'ErrorProductAlreadyExists') {
873  $this->error[] = $newproduct->error;
874  $this->errors = $newproduct->errors;
875  $this->db->rollback();
876  return -1;
877  }
878 
884  if ($newcomb->fk_product_child) {
885  $res = $newproduct->fetch($existingCombination->fk_product_child);
886  } else {
887  $orig_prod_ref = $newproduct->ref;
888  $i = 1;
889 
890  do {
891  $newproduct->ref = $orig_prod_ref.$i;
892  $res = $newproduct->create($user);
893 
894  if ($newproduct->error != 'ErrorProductAlreadyExists') {
895  $this->errors[] = $newproduct->error;
896  break;
897  }
898 
899  $i++;
900  } while ($res < 0);
901  }
902 
903  if ($res < 0) {
904  $this->db->rollback();
905  return -1;
906  }
907  }
908  } else {
909  $result = $newproduct->update($newproduct->id, $user);
910  if ($result < 0) {
911  $this->db->rollback();
912  return -1;
913  }
914  }
915 
916  $newcomb->fk_product_child = $newproduct->id;
917 
918  if ($newcomb->update($user) < 0) {
919  $this->error = $newcomb->error;
920  $this->errors = $newcomb->errors;
921  $this->db->rollback();
922  return -1;
923  }
924 
925  $this->db->commit();
926  return $newproduct->id;
927  }
928 
937  public function copyAll(User $user, $origProductId, Product $destProduct)
938  {
939  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
940 
941  //To prevent a loop
942  if ($origProductId == $destProduct->id) {
943  return -1;
944  }
945 
946  $prodcomb2val = new ProductCombination2ValuePair($this->db);
947 
948  //Retrieve all product combinations
949  $combinations = $this->fetchAllByFkProductParent($origProductId);
950 
951  foreach ($combinations as $combination) {
952  $variations = array();
953 
954  foreach ($prodcomb2val->fetchByFkCombination($combination->id) as $tmp_pc2v) {
955  $variations[$tmp_pc2v->fk_prod_attr] = $tmp_pc2v->fk_prod_attr_val;
956  }
957 
958  if ($this->createProductCombination(
959  $user,
960  $destProduct,
961  $variations,
962  array(),
963  $combination->variation_price_percentage,
964  $combination->variation_price,
965  $combination->variation_weight
966  ) < 0) {
967  return -1;
968  }
969  }
970 
971  return 1;
972  }
973 
979  public function getCombinationLabel($prod_child)
980  {
981  $label = '';
982  $sql = 'SELECT pav.value AS label';
983  $sql .= ' FROM '.MAIN_DB_PREFIX.'product_attribute_combination pac';
984  $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_attribute_combination2val pac2v ON pac2v.fk_prod_combination=pac.rowid';
985  $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_attribute_value pav ON pav.rowid=pac2v.fk_prod_attr_val';
986  $sql .= ' WHERE pac.fk_product_child='.((int) $prod_child);
987 
988  $resql = $this->db->query($sql);
989  if ($resql) {
990  $num = $this->db->num_rows($resql);
991 
992  $i = 0;
993 
994  while ($i < $num) {
995  $obj = $this->db->fetch_object($resql);
996 
997  if ($obj->label) {
998  $label .= ' '.$obj->label;
999  }
1000  $i++;
1001  }
1002  }
1003  return $label;
1004  }
1005 }
1006 
1007 
1008 
1014 {
1019  public $db;
1020 
1024  public $table_element = 'product_attribute_combination_price_level';
1025 
1030  public $id;
1031 
1036  public $fk_product_attribute_combination;
1037 
1042  public $fk_price_level;
1043 
1048  public $variation_price;
1049 
1054  public $variation_price_percentage = false;
1055 
1059  public $error;
1060 
1064  public $errors = array();
1065 
1071  public function __construct(DoliDB $db)
1072  {
1073  $this->db = $db;
1074  }
1075 
1082  public function fetch($rowid)
1083  {
1084  $sql = "SELECT rowid, fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
1085  $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
1086  $sql .= " WHERE rowid = ".(int) $rowid;
1087 
1088  $resql = $this->db->query($sql);
1089  if ($resql) {
1090  $obj = $this->db->fetch_object($resql);
1091  if ($obj) {
1092  return $this->fetchFormObj($obj);
1093  }
1094  }
1095 
1096  return -1;
1097  }
1098 
1099 
1107  public function fetchAll($fk_product_attribute_combination, $fk_price_level = 0)
1108  {
1109  $result = array();
1110 
1111  $sql = "SELECT rowid, fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
1112  $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
1113  $sql .= " WHERE fk_product_attribute_combination = ".intval($fk_product_attribute_combination);
1114  if (!empty($fk_price_level)) {
1115  $sql .= ' AND fk_price_level = '.intval($fk_price_level);
1116  }
1117 
1118  $res = $this->db->query($sql);
1119  if ($res) {
1120  if ($this->db->num_rows($res) > 0) {
1121  while ($obj = $this->db->fetch_object($res)) {
1122  $productCombinationLevel = new ProductCombinationLevel($this->db);
1123  $productCombinationLevel->fetchFormObj($obj);
1124  $result[$obj->fk_price_level] = $productCombinationLevel;
1125  }
1126  }
1127  } else {
1128  return -1;
1129  }
1130 
1131  return $result;
1132  }
1133 
1140  public function fetchFormObj($obj)
1141  {
1142  if (!$obj) {
1143  return -1;
1144  }
1145 
1146  $this->id = $obj->rowid;
1147  $this->fk_product_attribute_combination = floatval($obj->fk_product_attribute_combination);
1148  $this->fk_price_level = intval($obj->fk_price_level);
1149  $this->variation_price = floatval($obj->variation_price);
1150  $this->variation_price_percentage = (bool) $obj->variation_price_percentage;
1151 
1152  return 1;
1153  }
1154 
1155 
1161  public function save()
1162  {
1163  if (($this->id > 0 && empty($this->fk_product_attribute_combination)) || empty($this->fk_price_level)) {
1164  return -1;
1165  }
1166 
1167  // Check if level exist in DB before add
1168  if ($this->fk_product_attribute_combination > 0 && empty($this->id)) {
1169  $sql = "SELECT rowid id";
1170  $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
1171  $sql .= " WHERE fk_product_attribute_combination = ".(int) $this->fk_product_attribute_combination;
1172  $sql .= ' AND fk_price_level = '.((int) $this->fk_price_level);
1173 
1174  $resql = $this->db->query($sql);
1175  if ($resql) {
1176  $obj = $this->db->fetch_object($resql);
1177  if ($obj) {
1178  $this->id = $obj->id;
1179  }
1180  }
1181  }
1182 
1183  // Update
1184  if (!empty($this->id)) {
1185  $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1186  $sql .= ' SET variation_price = '.floatval($this->variation_price).' , variation_price_percentage = '.intval($this->variation_price_percentage);
1187  $sql .= ' WHERE rowid = '.((int) $this->id);
1188 
1189  $res = $this->db->query($sql);
1190  if ($res > 0) {
1191  return $this->id;
1192  } else {
1193  $this->error = $this->db->error();
1194  $this->errors[] = $this->error;
1195  return -1;
1196  }
1197  } else {
1198  // Add
1199  $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
1200  $sql .= "fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
1201  $sql .= ") VALUES (";
1202  $sql .= (int) $this->fk_product_attribute_combination;
1203  $sql .= ", ".intval($this->fk_price_level);
1204  $sql .= ", ".floatval($this->variation_price);
1205  $sql .= ", ".intval($this->variation_price_percentage);
1206  $sql .= ")";
1207 
1208  $res = $this->db->query($sql);
1209  if ($res) {
1210  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
1211  } else {
1212  $this->error = $this->db->error();
1213  $this->errors[] = $this->error;
1214  return -1;
1215  }
1216  }
1217 
1218  return $this->id;
1219  }
1220 
1221 
1227  public function delete()
1228  {
1229  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".(int) $this->id;
1230  $res = $this->db->query($sql);
1231 
1232  return $res ? 1 : -1;
1233  }
1234 
1235 
1242  public function deleteAllForCombination($fk_product_attribute_combination)
1243  {
1244  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE fk_product_attribute_combination = ".(int) $fk_product_attribute_combination;
1245  $res = $this->db->query($sql);
1246 
1247  return $res ? 1 : -1;
1248  }
1249 
1250 
1257  public function clean($fk_product_attribute_combination)
1258  {
1259  global $conf;
1260 
1261  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element;
1262  $sql .= " WHERE fk_product_attribute_combination = ".(int) $fk_product_attribute_combination;
1263  $sql .= " AND fk_price_level > ".intval($conf->global->PRODUIT_MULTIPRICES_LIMIT);
1264  $res = $this->db->query($sql);
1265 
1266  return $res ? 1 : -1;
1267  }
1268 
1269 
1278  public static function createFromParent(DoliDB $db, ProductCombination $productCombination, $fkPriceLevel)
1279  {
1280  $productCombinationLevel = new self($db);
1281  $productCombinationLevel->fk_price_level = $fkPriceLevel;
1282  $productCombinationLevel->fk_product_attribute_combination = $productCombination->id;
1283  $productCombinationLevel->variation_price = $productCombination->variation_price;
1284  $productCombinationLevel->variation_price_percentage = (bool) $productCombination->variation_price_percentage;
1285 
1286  return $productCombinationLevel;
1287  }
1288 }
Class to manage Dolibarr database access.
Class ProductAttribute Used to represent a product attribute.
Class ProductAttributeValue Used to represent a product attribute value.
Class ProductCombination2ValuePair Used to represent the relation between a product combination,...
Class ProductCombination Used to represent a product combination.
fetchAllByFkProductParent($fk_product_parent)
Retrieves all product combinations by the product parent row id.
update(User $user)
Updates a product combination.
countNbOfCombinationForFkProductParent($fk_product_parent)
Retrieves all product combinations by the product parent row id.
getUniqueAttributesAndValuesByFkProductParent($productid)
Retrieves all unique attributes for a parent product.
fetchCombinationPriceLevels($fk_price_level=0, $useCache=true)
Retrieves combination price levels.
deleteByFkProductParent($user, $fk_product_parent)
Deletes all product combinations of a parent product.
updateProperties(Product $parent, User $user)
Updates the weight of the child product.
fetchByFkProductChild($productid, $donotloadpricelevel=0)
Retrieves information of a variant product and ID of its parent product.
copyAll(User $user, $origProductId, Product $destProduct)
Copies all product combinations from the origin product to the destination product.
__construct(DoliDB $db)
Constructor.
createProductCombination(User $user, Product $product, array $combinations, array $variations, $price_var_percent=false, $forced_pricevar=false, $forced_weightvar=false, $forced_refvar=false, $ref_ext='')
Creates a product combination.
create($user)
Creates a product attribute combination.
saveCombinationPriceLevels($clean=1)
Retrieves combination price levels.
getCombinationLabel($prod_child)
Return label for combinations.
fetchByProductCombination2ValuePairs($prodid, array $features)
Retrieves the combination that matches the given features.
fetch($rowid)
Retrieves a combination by its rowid.
Class ProductCombinationLevel Used to represent a product combination Level.
static createFromParent(DoliDB $db, ProductCombination $productCombination, $fkPriceLevel)
Create new Product Combination Price level from Parent.
save()
Save a price impact of a product combination for a price level.
clean($fk_product_attribute_combination)
Clean not needed price levels for a combination.
fetchAll($fk_product_attribute_combination, $fk_price_level=0)
Retrieves combination price levels.
deleteAllForCombination($fk_product_attribute_combination)
delete all for a combination
fetchFormObj($obj)
Assign vars form an stdclass like sql obj.
__construct(DoliDB $db)
Constructor.
fetch($rowid)
Retrieves a combination level by its rowid.
Class to manage products or services.
Class to manage Dolibarr users.
Definition: user.class.php:48
if(isModEnabled('facture') && $user->hasRight('facture', 'lire')) if((isModEnabled('fournisseur') &&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') && $user->hasRight('don', 'lire')) if(isModEnabled('tax') &&!empty($user->rights->tax->charges->lire)) if(isModEnabled('facture') &&isModEnabled('commande') && $user->hasRight("commande", "lire") &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) $sql
Social contributions to pay.
Definition: index.php:746
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
div float
Buy price without taxes.
Definition: style.css.php:926