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