dolibarr  16.0.5
ProductCombination.class.php
1 <?php
2 
3 /* Copyright (C) 2016 Marcos GarcĂ­a <marcosgdf@gmail.com>
4  * Copyright (C) 2018 Juanjo Menent <jmenent@2byte.es>
5  * Copyright (C) 2022 Open-Dsi <support@open-dsi.fr>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program. If not, see <https://www.gnu.org/licenses/>.
19  */
20 
26 {
31  public $db;
32 
37  public $id;
38 
43  public $fk_product_parent;
44 
49  public $fk_product_child;
50 
55  public $variation_price;
56 
61  public $variation_price_percentage = false;
62 
67  public $variation_weight;
68 
73  public $entity;
74 
79  public $combination_price_levels;
80 
85  public $variation_ref_ext = '';
86 
92  public function __construct(DoliDB $db)
93  {
94  global $conf;
95 
96  $this->db = $db;
97  $this->entity = $conf->entity;
98  }
99 
106  public function fetch($rowid)
107  {
108  global $conf;
109 
110  $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').")";
111 
112  $query = $this->db->query($sql);
113 
114  if (!$query) {
115  return -1;
116  }
117 
118  if (!$this->db->num_rows($query)) {
119  return -1;
120  }
121 
122  $obj = $this->db->fetch_object($query);
123 
124  $this->id = $obj->rowid;
125  $this->fk_product_parent = $obj->fk_product_parent;
126  $this->fk_product_child = $obj->fk_product_child;
127  $this->variation_price = $obj->variation_price;
128  $this->variation_price_percentage = $obj->variation_price_percentage;
129  $this->variation_weight = $obj->variation_weight;
130  $this->variation_ref_ext = $obj->variation_ref_ext;
131 
132  if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
134  }
135 
136  return 1;
137  }
138 
139 
147  public function fetchCombinationPriceLevels($fk_price_level = 0, $useCache = true)
148  {
149  global $conf;
150 
151  // Check cache
152  if (!empty($this->combination_price_levels) && $useCache) {
153  if ((!empty($fk_price_level) && isset($this->combination_price_levels[$fk_price_level])) || empty($fk_price_level)) {
154  return 1;
155  }
156  }
157 
158  if (!is_array($this->combination_price_levels)
159  || empty($fk_price_level) // if fetch an unique level dont erase all already fetched
160  ) {
161  $this->combination_price_levels = array();
162  }
163 
164  $staticProductCombinationLevel = new ProductCombinationLevel($this->db);
165  $combination_price_levels = $staticProductCombinationLevel->fetchAll($this->id, $fk_price_level);
166 
167  if (!is_array($combination_price_levels)) {
168  return -1;
169  }
170 
171  if (empty($combination_price_levels)) {
175  if ($fk_price_level > 0) {
176  $combination_price_levels[$fk_price_level] = ProductCombinationLevel::createFromParent($this->db, $this, $fk_price_level);
177  } else {
178  for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
179  $combination_price_levels[$i] = ProductCombinationLevel::createFromParent($this->db, $this, $i);
180  }
181  }
182  }
183 
184  $this->combination_price_levels = $combination_price_levels;
185 
186  return 1;
187  }
188 
195  public function saveCombinationPriceLevels($clean = 1)
196  {
197  global $conf;
198 
199  $error = 0;
200 
201  $staticProductCombinationLevel = new ProductCombinationLevel($this->db);
202 
203  // Delete all
204  if (empty($this->combination_price_levels)) {
205  return $staticProductCombinationLevel->deleteAllForCombination($this->id);
206  }
207 
208  // Clean not needed price levels (level higher than number max defined into setup)
209  if ($clean) {
210  $res = $staticProductCombinationLevel->clean($this->id);
211  if ($res < 0) {
212  $this->errors[] = 'Fail to clean not needed price levels';
213  return -1;
214  }
215  }
216 
217  foreach ($this->combination_price_levels as $fk_price_level => $combination_price_level) {
218  $res = $combination_price_level->save();
219  if ($res < 1) {
220  $this->error = 'Error saving combination price level '.$fk_price_level.' : '.$combination_price_level->error;
221  $this->errors[] = $this->error;
222  $error++;
223  break;
224  }
225  }
226 
227  if ($error) {
228  return $error * -1;
229  } else {
230  return 1;
231  }
232  }
233 
241  public function fetchByFkProductChild($productid, $donotloadpricelevel = 0)
242  {
243  global $conf;
244 
245  $sql = "SELECT rowid, fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight";
246  $sql .= " FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE fk_product_child = ".((int) $productid)." AND entity IN (".getEntity('product').")";
247 
248  $query = $this->db->query($sql);
249 
250  if (!$query) {
251  return -1;
252  }
253 
254  if (!$this->db->num_rows($query)) {
255  return 0;
256  }
257 
258  $result = $this->db->fetch_object($query);
259 
260  $this->id = $result->rowid;
261  $this->fk_product_parent = $result->fk_product_parent;
262  $this->fk_product_child = $result->fk_product_child;
263  $this->variation_price = $result->variation_price;
264  $this->variation_price_percentage = $result->variation_price_percentage;
265  $this->variation_weight = $result->variation_weight;
266 
267  if (empty($donotloadpricelevel) && !empty($conf->global->PRODUIT_MULTIPRICES)) {
269  }
270 
271  return (int) $this->fk_product_parent;
272  }
273 
280  public function fetchAllByFkProductParent($fk_product_parent)
281  {
282  global $conf;
283 
284  $sql = "SELECT rowid, fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE fk_product_parent = ".((int) $fk_product_parent)." AND entity IN (".getEntity('product').")";
285 
286  $query = $this->db->query($sql);
287 
288  if (!$query) {
289  return -1;
290  }
291 
292  $return = array();
293 
294  while ($result = $this->db->fetch_object($query)) {
295  $tmp = new ProductCombination($this->db);
296  $tmp->id = $result->rowid;
297  $tmp->fk_product_parent = $result->fk_product_parent;
298  $tmp->fk_product_child = $result->fk_product_child;
299  $tmp->variation_price = $result->variation_price;
300  $tmp->variation_price_percentage = $result->variation_price_percentage;
301  $tmp->variation_weight = $result->variation_weight;
302  $tmp->variation_ref_ext = $result->variation_ref_ext;
303 
304  if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
305  $tmp->fetchCombinationPriceLevels();
306  }
307 
308  $return[] = $tmp;
309  }
310 
311  return $return;
312  }
313 
320  public function countNbOfCombinationForFkProductParent($fk_product_parent)
321  {
322  $nb = 0;
323  $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').")";
324 
325  $resql = $this->db->query($sql);
326  if ($resql) {
327  $obj = $this->db->fetch_object($resql);
328  if ($obj) {
329  $nb = $obj->nb;
330  }
331  }
332 
333  return $nb;
334  }
335 
342  public function create($user)
343  {
344  global $conf;
345 
346  /* $this->fk_product_child may be empty and will be filled later after subproduct has been created */
347 
348  $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_attribute_combination";
349  $sql .= " (fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight, variation_ref_ext, entity)";
350  $sql .= " VALUES (".((int) $this->fk_product_parent).", ".((int) $this->fk_product_child).",";
351  $sql .= (float) $this->variation_price.", ".(int) $this->variation_price_percentage.",";
352  $sql .= (float) $this->variation_weight.", '".$this->db->escape($this->variation_ref_ext)."', ".(int) $this->entity.")";
353 
354  $resql = $this->db->query($sql);
355  if ($resql) {
356  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'product_attribute_combination');
357  } else {
358  $this->error = $this->db->lasterror();
359  return -1;
360  }
361 
362  if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
363  $res = $this->saveCombinationPriceLevels();
364  if ($res < 0) {
365  return -2;
366  }
367  }
368 
369  return 1;
370  }
371 
378  public function update(User $user)
379  {
380  global $conf;
381 
382  $sql = "UPDATE ".MAIN_DB_PREFIX."product_attribute_combination";
383  $sql .= " SET fk_product_parent = ".(int) $this->fk_product_parent.", fk_product_child = ".(int) $this->fk_product_child.",";
384  $sql .= " variation_price = ".(float) $this->variation_price.", variation_price_percentage = ".(int) $this->variation_price_percentage.",";
385  $sql .= " variation_ref_ext = '".$this->db->escape($this->variation_ref_ext)."',";
386  $sql .= " variation_weight = ".(float) $this->variation_weight." WHERE rowid = ".((int) $this->id);
387 
388  $resql = $this->db->query($sql);
389  if (!$resql) {
390  return -1;
391  }
392 
393  if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
394  $res = $this->saveCombinationPriceLevels();
395  if ($res < 0) {
396  return -2;
397  }
398  }
399 
400  $parent = new Product($this->db);
401  $parent->fetch($this->fk_product_parent);
402 
403  $this->updateProperties($parent, $user);
404 
405  return 1;
406  }
407 
414  public function delete(User $user)
415  {
416  $this->db->begin();
417 
418  $comb2val = new ProductCombination2ValuePair($this->db);
419  $comb2val->deleteByFkCombination($this->id);
420 
421  // remove combination price levels
422  if (!$this->db->query("DELETE FROM ".MAIN_DB_PREFIX."product_attribute_combination_price_level WHERE fk_product_attribute_combination = ".(int) $this->id)) {
423  $this->db->rollback();
424  return -1;
425  }
426 
427  $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE rowid = ".(int) $this->id;
428 
429  if ($this->db->query($sql)) {
430  $this->db->commit();
431  return 1;
432  }
433 
434  $this->db->rollback();
435  return -1;
436  }
437 
445  public function deleteByFkProductParent($user, $fk_product_parent)
446  {
447  $this->db->begin();
448 
449  foreach ($this->fetchAllByFkProductParent($fk_product_parent) as $prodcomb) {
450  $prodstatic = new Product($this->db);
451 
452  $res = $prodstatic->fetch($prodcomb->fk_product_child);
453 
454  if ($res > 0) {
455  $res = $prodcomb->delete($user);
456  }
457 
458  if ($res > 0 && !$prodstatic->isObjectUsed($prodstatic->id)) {
459  $res = $prodstatic->delete($user);
460  }
461 
462  if ($res < 0) {
463  $this->db->rollback();
464  return -1;
465  }
466  }
467 
468  $this->db->commit();
469  return 1;
470  }
471 
480  public function updateProperties(Product $parent, User $user)
481  {
482  global $conf;
483 
484  $this->db->begin();
485 
486  $child = new Product($this->db);
487  $child->fetch($this->fk_product_child);
488 
489  $child->price_autogen = $parent->price_autogen;
490  $child->weight = $parent->weight;
491  // Only when Parent Status are updated
492  if ($parent->oldcopy && ($parent->status != $parent->oldcopy->status)) {
493  $child->status = $parent->status;
494  }
495  if ($parent->oldcopy && ($parent->status_buy != $parent->oldcopy->status_buy)) {
496  $child->status_buy = $parent->status_buy;
497  }
498 
499  if ($this->variation_weight) { // If we must add a delta on weight
500  $child->weight = ($child->weight ? $child->weight : 0) + $this->variation_weight;
501  }
502  $child->weight_units = $parent->weight_units;
503 
504  // Don't update the child label if the user has already modified it.
505  if ($child->label == $parent->label) {
506  // This will trigger only at variant creation time
507  $varlabel = $this->getCombinationLabel($this->fk_product_child);
508  $child->label = $parent->label.$varlabel; ;
509  }
510 
511 
512  if ($child->update($child->id, $user) > 0) {
513  $new_vat = $parent->tva_tx;
514  $new_npr = $parent->tva_npr;
515 
516  // MultiPrix
517  if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
518  for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
519  if ($parent->multiprices[$i] != '' || isset($this->combination_price_levels[$i]->variation_price)) {
520  $new_type = empty($parent->multiprices_base_type[$i]) ? 'HT' : $parent->multiprices_base_type[$i];
521  $new_min_price = $parent->multiprices_min[$i];
522  $variation_price = floatval(!isset($this->combination_price_levels[$i]->variation_price) ? $this->variation_price : $this->combination_price_levels[$i]->variation_price);
523  $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);
524 
525  if ($parent->prices_by_qty_list[$i]) {
526  $new_psq = 1;
527  } else {
528  $new_psq = 0;
529  }
530 
531  if ($new_type == 'TTC') {
532  $new_price = $parent->multiprices_ttc[$i];
533  } else {
534  $new_price = $parent->multiprices[$i];
535  }
536 
537  if ($variation_price_percentage) {
538  if ($new_price != 0) {
539  $new_price *= 1 + ($variation_price / 100);
540  }
541  } else {
542  $new_price += $variation_price;
543  }
544 
545  $ret = $child->updatePrice($new_price, $new_type, $user, $new_vat, $new_min_price, $i, $new_npr, $new_psq, 0, array(), $parent->default_vat_code);
546 
547  if ($ret < 0) {
548  $this->db->rollback();
549  $this->error = $child->error;
550  $this->errors = $child->errors;
551  return $ret;
552  }
553  }
554  }
555  } else {
556  $new_type = $parent->price_base_type;
557  $new_min_price = $parent->price_min;
558  $new_psq = $parent->price_by_qty;
559 
560  if ($new_type == 'TTC') {
561  $new_price = $parent->price_ttc;
562  } else {
563  $new_price = $parent->price;
564  }
565 
566  if ($this->variation_price_percentage) {
567  if ($new_price != 0) {
568  $new_price *= 1 + ($this->variation_price / 100);
569  }
570  } else {
571  $new_price += $this->variation_price;
572  }
573 
574  $ret = $child->updatePrice($new_price, $new_type, $user, $new_vat, $new_min_price, 1, $new_npr, $new_psq);
575 
576  if ($ret < 0) {
577  $this->db->rollback();
578  $this->error = $child->error;
579  $this->errors = $child->errors;
580  return $ret;
581  }
582  }
583 
584  $this->db->commit();
585 
586  return 1;
587  }
588 
589  $this->db->rollback();
590  $this->error = $child->error;
591  $this->errors = $child->errors;
592  return -1;
593  }
594 
602  public function fetchByProductCombination2ValuePairs($prodid, array $features)
603  {
604  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
605 
606  $actual_comp = array();
607 
608  $prodcomb2val = new ProductCombination2ValuePair($this->db);
609  $prodcomb = new ProductCombination($this->db);
610 
611  $features = array_filter($features, function ($v) {
612  return !empty($v);
613  });
614 
615  foreach ($features as $attr => $attr_val) {
616  $actual_comp[$attr] = $attr_val;
617  }
618 
619  foreach ($prodcomb->fetchAllByFkProductParent($prodid) as $prc) {
620  $values = array();
621 
622  foreach ($prodcomb2val->fetchByFkCombination($prc->id) as $value) {
623  $values[$value->fk_prod_attr] = $value->fk_prod_attr_val;
624  }
625 
626  $check1 = count(array_diff_assoc($values, $actual_comp));
627  $check2 = count(array_diff_assoc($actual_comp, $values));
628 
629  if (!$check1 && !$check2) {
630  return $prc;
631  }
632  }
633 
634  return false;
635  }
636 
644  {
645  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
646  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
647 
648  $variants = array();
649 
650  //Attributes
651  $sql = "SELECT DISTINCT fk_prod_attr, a.position";
652  $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";
653  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = c.fk_product_child";
654  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_attribute a ON a.rowid = fk_prod_attr";
655  $sql .= " WHERE c.fk_product_parent = ".((int) $productid)." AND p.tosell = 1";
656  $sql .= $this->db->order('a.position', 'asc');
657 
658  $query = $this->db->query($sql);
659 
660  //Values
661  while ($result = $this->db->fetch_object($query)) {
662  $attr = new ProductAttribute($this->db);
663  $attr->fetch($result->fk_prod_attr);
664 
665  $tmp = new stdClass();
666  $tmp->id = $attr->id;
667  $tmp->ref = $attr->ref;
668  $tmp->label = $attr->label;
669  $tmp->values = array();
670 
671  $attrval = new ProductAttributeValue($this->db);
672  foreach ($res = $attrval->fetchAllByProductAttribute($attr->id, true) as $val) {
673  $tmp->values[] = $val;
674  }
675 
676  $variants[] = $tmp;
677  }
678 
679  return $variants;
680  }
681 
705  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 = '')
706  {
707  global $conf;
708 
709  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
710  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
711 
712  $this->db->begin();
713 
714  $price_impact = array(1=>0); // init level price impact
715 
716  $forced_refvar = trim($forced_refvar);
717 
718  if (!empty($forced_refvar) && $forced_refvar != $product->ref) {
719  $existingProduct = new Product($this->db);
720  $result = $existingProduct->fetch('', $forced_refvar);
721  if ($result > 0) {
722  $newproduct = $existingProduct;
723  } else {
724  $existingProduct = false;
725  $newproduct = clone $product;
726  $newproduct->ref = $forced_refvar;
727  }
728  } else {
729  $forced_refvar = false;
730  $existingProduct = false;
731  $newproduct = clone $product;
732  }
733 
734  //Final weight impact
735  $weight_impact = (float) $forced_weightvar; // If false, return 0
736 
737  //Final price impact
738  if (!is_array($forced_pricevar)) {
739  $price_impact[1] = (float) $forced_pricevar; // If false, return 0
740  } else {
741  $price_impact = $forced_pricevar;
742  }
743 
744  if (!array($price_var_percent)) {
745  $price_var_percent[1] = (float) $price_var_percent;
746  }
747 
748  $newcomb = new ProductCombination($this->db);
749  $existingCombination = $newcomb->fetchByProductCombination2ValuePairs($product->id, $combinations);
750 
751  if ($existingCombination) {
752  $newcomb = $existingCombination;
753  } else {
754  $newcomb->fk_product_parent = $product->id;
755 
756  // Create 1 entry into product_attribute_combination (1 entry for each combinations). This init also $newcomb->id
757  $result = $newcomb->create($user);
758  if ($result < 0) {
759  $this->error = $newcomb->error;
760  $this->errors = $newcomb->errors;
761  $this->db->rollback();
762  return -1;
763  }
764  }
765 
766  $prodattr = new ProductAttribute($this->db);
767  $prodattrval = new ProductAttributeValue($this->db);
768 
769  // $combination contains list of attributes pairs key->value. Example: array('id Color'=>id Blue, 'id Size'=>id Small, 'id Option'=>id val a, ...)
770  //var_dump($combinations);
771  foreach ($combinations as $currcombattr => $currcombval) {
772  //This was checked earlier, so no need to double check
773  $prodattr->fetch($currcombattr);
774  $prodattrval->fetch($currcombval);
775 
776  //If there is an existing combination, there is no need to duplicate the valuepair
777  if (!$existingCombination) {
778  $tmp = new ProductCombination2ValuePair($this->db);
779  $tmp->fk_prod_attr = $currcombattr;
780  $tmp->fk_prod_attr_val = $currcombval;
781  $tmp->fk_prod_combination = $newcomb->id;
782 
783  if ($tmp->create($user) < 0) { // Create 1 entry into product_attribute_combination2val
784  $this->error = $tmp->error;
785  $this->errors = $tmp->errors;
786  $this->db->rollback();
787  return -1;
788  }
789  }
790 
791  if ($forced_weightvar === false) {
792  $weight_impact += (float) price2num($variations[$currcombattr][$currcombval]['weight']);
793  }
794  if ($forced_pricevar === false) {
795  $price_impact[1] += (float) price2num($variations[$currcombattr][$currcombval]['price']);
796 
797  // Manage Price levels
798  if ($conf->global->PRODUIT_MULTIPRICES) {
799  for ($i = 2; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
800  $price_impact[$i] += (float) price2num($variations[$currcombattr][$currcombval]['price']);
801  }
802  }
803  }
804 
805  if ($forced_refvar === false) {
806  if (isset($conf->global->PRODUIT_ATTRIBUTES_SEPARATOR)) {
807  $newproduct->ref .= $conf->global->PRODUIT_ATTRIBUTES_SEPARATOR.$prodattrval->ref;
808  } else {
809  $newproduct->ref .= '_'.$prodattrval->ref;
810  }
811  }
812 
813  //The first one should not contain a linebreak
814  if ($newproduct->description) {
815  $newproduct->description .= '<br>';
816  }
817  $newproduct->description .= '<strong>'.$prodattr->label.':</strong> '.$prodattrval->value;
818  }
819 
820  $newcomb->variation_price_percentage = $price_var_percent[1];
821  $newcomb->variation_price = $price_impact[1];
822  $newcomb->variation_weight = $weight_impact;
823  $newcomb->variation_ref_ext = $this->db->escape($ref_ext);
824 
825  // Init price level
826  if ($conf->global->PRODUIT_MULTIPRICES) {
827  for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
828  $productCombinationLevel = new ProductCombinationLevel($this->db);
829  $productCombinationLevel->fk_product_attribute_combination = $newcomb->id;
830  $productCombinationLevel->fk_price_level = $i;
831  $productCombinationLevel->variation_price = $price_impact[$i];
832 
833  if (is_array($price_var_percent)) {
834  $productCombinationLevel->variation_price_percentage = (empty($price_var_percent[$i]) ? false : $price_var_percent[$i]);
835  } else {
836  $productCombinationLevel->variation_price_percentage = $price_var_percent;
837  }
838 
839  $newcomb->combination_price_levels[$i] = $productCombinationLevel;
840  }
841  }
842  //var_dump($newcomb->combination_price_levels);
843 
844  $newproduct->weight += $weight_impact;
845 
846  // Now create the product
847  //print 'Create prod '.$newproduct->ref.'<br>'."\n";
848  if ($existingProduct === false) {
849  //To avoid wrong information in price history log
850  $newproduct->price = 0;
851  $newproduct->price_ttc = 0;
852  $newproduct->price_min = 0;
853  $newproduct->price_min_ttc = 0;
854 
855  // A new variant must use a new barcode (not same product)
856  $newproduct->barcode = -1;
857  $result = $newproduct->create($user);
858 
859  if ($result < 0) {
860  //In case the error is not related with an already existing product
861  if ($newproduct->error != 'ErrorProductAlreadyExists') {
862  $this->error[] = $newproduct->error;
863  $this->errors = $newproduct->errors;
864  $this->db->rollback();
865  return -1;
866  }
867 
873  if ($newcomb->fk_product_child) {
874  $res = $newproduct->fetch($existingCombination->fk_product_child);
875  } else {
876  $orig_prod_ref = $newproduct->ref;
877  $i = 1;
878 
879  do {
880  $newproduct->ref = $orig_prod_ref.$i;
881  $res = $newproduct->create($user);
882 
883  if ($newproduct->error != 'ErrorProductAlreadyExists') {
884  $this->errors[] = $newproduct->error;
885  break;
886  }
887 
888  $i++;
889  } while ($res < 0);
890  }
891 
892  if ($res < 0) {
893  $this->db->rollback();
894  return -1;
895  }
896  }
897  } else {
898  $result = $newproduct->update($newproduct->id, $user);
899  if ($result < 0) {
900  $this->db->rollback();
901  return -1;
902  }
903  }
904 
905  $newcomb->fk_product_child = $newproduct->id;
906 
907  if ($newcomb->update($user) < 0) {
908  $this->error = $newcomb->error;
909  $this->errors = $newcomb->errors;
910  $this->db->rollback();
911  return -1;
912  }
913 
914  $this->db->commit();
915  return $newproduct->id;
916  }
917 
926  public function copyAll(User $user, $origProductId, Product $destProduct)
927  {
928  require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
929 
930  //To prevent a loop
931  if ($origProductId == $destProduct->id) {
932  return -1;
933  }
934 
935  $prodcomb2val = new ProductCombination2ValuePair($this->db);
936 
937  //Retrieve all product combinations
938  $combinations = $this->fetchAllByFkProductParent($origProductId);
939 
940  foreach ($combinations as $combination) {
941  $variations = array();
942 
943  foreach ($prodcomb2val->fetchByFkCombination($combination->id) as $tmp_pc2v) {
944  $variations[$tmp_pc2v->fk_prod_attr] = $tmp_pc2v->fk_prod_attr_val;
945  }
946 
947  if ($this->createProductCombination(
948  $user,
949  $destProduct,
950  $variations,
951  array(),
952  $combination->variation_price_percentage,
953  $combination->variation_price,
954  $combination->variation_weight
955  ) < 0) {
956  return -1;
957  }
958  }
959 
960  return 1;
961  }
962 
968  public function getCombinationLabel($prod_child)
969  {
970  $label = '';
971  $sql = 'SELECT pav.value AS label';
972  $sql .= ' FROM '.MAIN_DB_PREFIX.'product_attribute_combination pac';
973  $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_attribute_combination2val pac2v ON pac2v.fk_prod_combination=pac.rowid';
974  $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_attribute_value pav ON pav.rowid=pac2v.fk_prod_attr_val';
975  $sql .= ' WHERE pac.fk_product_child='.((int) $prod_child);
976 
977  $resql = $this->db->query($sql);
978  if ($resql) {
979  $num = $this->db->num_rows($resql);
980 
981  $i = 0;
982 
983  while ($i < $num) {
984  $obj = $this->db->fetch_object($resql);
985 
986  if ($obj->label) {
987  $label .= ' '.$obj->label;
988  }
989  $i++;
990  }
991  }
992  return $label;
993  }
994 }
995 
996 
997 
1003 {
1008  public $db;
1009 
1013  public $table_element = 'product_attribute_combination_price_level';
1014 
1019  public $id;
1020 
1025  public $fk_product_attribute_combination;
1026 
1031  public $fk_price_level;
1032 
1037  public $variation_price;
1038 
1043  public $variation_price_percentage = false;
1044 
1050  public function __construct(DoliDB $db)
1051  {
1052  $this->db = $db;
1053  }
1054 
1061  public function fetch($rowid)
1062  {
1063  $sql = "SELECT rowid, fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
1064  $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
1065  $sql .= " WHERE rowid = ".(int) $rowid;
1066 
1067  $resql = $this->db->query($sql);
1068  if ($resql) {
1069  $obj = $this->db->fetch_object($resql);
1070  if ($obj) {
1071  return $this->fetchFormObj($obj);
1072  }
1073  }
1074 
1075  return -1;
1076  }
1077 
1078 
1086  public function fetchAll($fk_product_attribute_combination, $fk_price_level = 0)
1087  {
1088  $result = array();
1089 
1090  $sql = "SELECT rowid, fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
1091  $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
1092  $sql .= " WHERE fk_product_attribute_combination = ".intval($fk_product_attribute_combination);
1093  if (!empty($fk_price_level)) {
1094  $sql .= ' AND fk_price_level = '.intval($fk_price_level);
1095  }
1096 
1097  $res = $this->db->query($sql);
1098  if ($res) {
1099  if ($this->db->num_rows($res) > 0) {
1100  while ($obj = $this->db->fetch_object($res)) {
1101  $productCombinationLevel = new ProductCombinationLevel($this->db);
1102  $productCombinationLevel->fetchFormObj($obj);
1103  $result[$obj->fk_price_level] = $productCombinationLevel;
1104  }
1105  }
1106  } else {
1107  return -1;
1108  }
1109 
1110  return $result;
1111  }
1112 
1119  public function fetchFormObj($obj)
1120  {
1121  if (!$obj) {
1122  return -1;
1123  }
1124 
1125  $this->id = $obj->rowid;
1126  $this->fk_product_attribute_combination = floatval($obj->fk_product_attribute_combination);
1127  $this->fk_price_level = intval($obj->fk_price_level);
1128  $this->variation_price = floatval($obj->variation_price);
1129  $this->variation_price_percentage = (bool) $obj->variation_price_percentage;
1130 
1131  return 1;
1132  }
1133 
1134 
1140  public function save()
1141  {
1142  if (($this->id > 0 && empty($this->fk_product_attribute_combination)) || empty($this->fk_price_level)) {
1143  return -1;
1144  }
1145 
1146  // Check if level exist in DB before add
1147  if ($this->fk_product_attribute_combination > 0 && empty($this->id)) {
1148  $sql = "SELECT rowid id";
1149  $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
1150  $sql .= " WHERE fk_product_attribute_combination = ".(int) $this->fk_product_attribute_combination;
1151  $sql .= ' AND fk_price_level = '.((int) $this->fk_price_level);
1152 
1153  $resql = $this->db->query($sql);
1154  if ($resql) {
1155  $obj = $this->db->fetch_object($resql);
1156  if ($obj) {
1157  $this->id = $obj->id;
1158  }
1159  }
1160  }
1161 
1162  // Update
1163  if (!empty($this->id)) {
1164  $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1165  $sql .= ' SET variation_price = '.floatval($this->variation_price).' , variation_price_percentage = '.intval($this->variation_price_percentage);
1166  $sql .= ' WHERE rowid = '.((int) $this->id);
1167 
1168  $res = $this->db->query($sql);
1169  if ($res > 0) {
1170  return $this->id;
1171  } else {
1172  $this->error = $this->db->error();
1173  $this->errors[] = $this->error;
1174  return -1;
1175  }
1176  } else {
1177  // Add
1178  $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
1179  $sql .= "fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
1180  $sql .= ") VALUES (";
1181  $sql .= (int) $this->fk_product_attribute_combination;
1182  $sql .= ", ".intval($this->fk_price_level);
1183  $sql .= ", ".floatval($this->variation_price);
1184  $sql .= ", ".intval($this->variation_price_percentage);
1185  $sql .= ")";
1186 
1187  $res = $this->db->query($sql);
1188  if ($res) {
1189  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
1190  } else {
1191  $this->error = $this->db->error();
1192  $this->errors[] = $this->error;
1193  return -1;
1194  }
1195  }
1196 
1197  return $this->id;
1198  }
1199 
1200 
1206  public function delete()
1207  {
1208  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".(int) $this->id;
1209  $res = $this->db->query($sql);
1210 
1211  return $res ? 1 : -1;
1212  }
1213 
1214 
1221  public function deleteAllForCombination($fk_product_attribute_combination)
1222  {
1223  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE fk_product_attribute_combination = ".(int) $fk_product_attribute_combination;
1224  $res = $this->db->query($sql);
1225 
1226  return $res ? 1 : -1;
1227  }
1228 
1229 
1236  public function clean($fk_product_attribute_combination)
1237  {
1238  global $conf;
1239 
1240  $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element;
1241  $sql .= " WHERE fk_product_attribute_combination = ".(int) $fk_product_attribute_combination;
1242  $sql .= " AND fk_price_level > ".intval($conf->global->PRODUIT_MULTIPRICES_LIMIT);
1243  $res = $this->db->query($sql);
1244 
1245  return $res ? 1 : -1;
1246  }
1247 
1248 
1257  public static function createFromParent(DoliDB $db, ProductCombination $productCombination, $fkPriceLevel)
1258  {
1259  $productCombinationLevel = new self($db);
1260  $productCombinationLevel->fk_price_level = $fkPriceLevel;
1261  $productCombinationLevel->fk_product_attribute_combination = $productCombination->id;
1262  $productCombinationLevel->variation_price = $productCombination->variation_price;
1263  $productCombinationLevel->variation_price_percentage = (bool) $productCombination->variation_price_percentage;
1264 
1265  return $productCombinationLevel;
1266  }
1267 }
ProductCombinationLevel\fetchAll
fetchAll($fk_product_attribute_combination, $fk_price_level=0)
Retrieves combination price levels.
Definition: ProductCombination.class.php:1086
ProductCombination\updateProperties
updateProperties(Product $parent, User $user)
Updates the weight of the child product.
Definition: ProductCombination.class.php:480
ProductCombinationLevel\fetchFormObj
fetchFormObj($obj)
Assign vars form an stdclass like sql obj.
Definition: ProductCombination.class.php:1119
db
$conf db
API class for accounts.
Definition: inc.php:41
ProductCombination\getUniqueAttributesAndValuesByFkProductParent
getUniqueAttributesAndValuesByFkProductParent($productid)
Retrieves all unique attributes for a parent product.
Definition: ProductCombination.class.php:643
ProductCombinationLevel\fetch
fetch($rowid)
Retrieves a combination level by its rowid.
Definition: ProductCombination.class.php:1061
ProductCombinationLevel\deleteAllForCombination
deleteAllForCombination($fk_product_attribute_combination)
delete all for a combination
Definition: ProductCombination.class.php:1221
ProductCombination\create
create($user)
Creates a product attribute combination.
Definition: ProductCombination.class.php:342
ProductCombination
Class ProductCombination Used to represent a product combination.
Definition: ProductCombination.class.php:25
DoliDB
Class to manage Dolibarr database access.
Definition: DoliDB.class.php:30
ProductCombinationLevel\createFromParent
static createFromParent(DoliDB $db, ProductCombination $productCombination, $fkPriceLevel)
Create new Product Combination Price level from Parent.
Definition: ProductCombination.class.php:1257
ProductCombination\__construct
__construct(DoliDB $db)
Constructor.
Definition: ProductCombination.class.php:92
ProductCombination\fetch
fetch($rowid)
Retrieves a combination by its rowid.
Definition: ProductCombination.class.php:106
ProductCombination\createProductCombination
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.
Definition: ProductCombination.class.php:705
ProductAttributeValue
Class ProductAttributeValue Used to represent a product attribute value.
Definition: ProductAttributeValue.class.php:25
ProductCombinationLevel\clean
clean($fk_product_attribute_combination)
Clean not needed price levels for a combination.
Definition: ProductCombination.class.php:1236
price2num
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
Definition: functions.lib.php:5661
ProductCombination\countNbOfCombinationForFkProductParent
countNbOfCombinationForFkProductParent($fk_product_parent)
Retrieves all product combinations by the product parent row id.
Definition: ProductCombination.class.php:320
ProductAttribute
Class ProductAttribute Used to represent a product attribute.
Definition: ProductAttribute.class.php:25
getEntity
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
Definition: functions.lib.php:148
ProductCombination\deleteByFkProductParent
deleteByFkProductParent($user, $fk_product_parent)
Deletes all product combinations of a parent product.
Definition: ProductCombination.class.php:445
ProductCombinationLevel
Class ProductCombinationLevel Used to represent a product combination Level.
Definition: ProductCombination.class.php:1002
ProductCombination\saveCombinationPriceLevels
saveCombinationPriceLevels($clean=1)
Retrieves combination price levels.
Definition: ProductCombination.class.php:195
ProductCombination\fetchByFkProductChild
fetchByFkProductChild($productid, $donotloadpricelevel=0)
Retrieves information of a variant product and ID of its parent product.
Definition: ProductCombination.class.php:241
ProductCombination\fetchByProductCombination2ValuePairs
fetchByProductCombination2ValuePairs($prodid, array $features)
Retrieves the combination that matches the given features.
Definition: ProductCombination.class.php:602
ProductCombination\fetchAllByFkProductParent
fetchAllByFkProductParent($fk_product_parent)
Retrieves all product combinations by the product parent row id.
Definition: ProductCombination.class.php:280
ProductCombinationLevel\save
save()
Save a price impact of a product combination for a price level.
Definition: ProductCombination.class.php:1140
User
Class to manage Dolibarr users.
Definition: user.class.php:44
ProductCombination\getCombinationLabel
getCombinationLabel($prod_child)
Return label for combinations.
Definition: ProductCombination.class.php:968
Product
Class to manage products or services.
Definition: product.class.php:46
ProductCombination\copyAll
copyAll(User $user, $origProductId, Product $destProduct)
Copies all product combinations from the origin product to the destination product.
Definition: ProductCombination.class.php:926
ProductCombination\update
update(User $user)
Updates a product combination.
Definition: ProductCombination.class.php:378
$resql
if(isModEnabled('facture') &&!empty($user->rights->facture->lire)) if((isModEnabled('fournisseur') &&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) && $user->rights->fournisseur->facture->lire)||(isModEnabled('supplier_invoice') && $user->rights->supplier_invoice->lire)) if(isModEnabled('don') &&!empty($user->rights->don->lire)) if(isModEnabled('tax') &&!empty($user->rights->tax->charges->lire)) if(isModEnabled('facture') &&isModEnabled('commande') && $user->rights->commande->lire &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) $resql
Social contributions to pay.
Definition: index.php:742
ProductCombination\fetchCombinationPriceLevels
fetchCombinationPriceLevels($fk_price_level=0, $useCache=true)
Retrieves combination price levels.
Definition: ProductCombination.class.php:147
ProductCombination2ValuePair
Class ProductCombination2ValuePair Used to represent the relation between a product combination,...
Definition: ProductCombination2ValuePair.class.php:24
float
div float
Buy price without taxes.
Definition: style.css.php:809
ProductCombinationLevel\__construct
__construct(DoliDB $db)
Constructor.
Definition: ProductCombination.class.php:1050