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 (!is_array($price_var_percent)) {
756 $price_var_percent = array(1 => (bool) $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 $productCombinationLevel->variation_price_percentage = $price_var_percent[$i];
844
845 $newcomb->combination_price_levels[$i] = $productCombinationLevel;
846 }
847 }
848 //var_dump($newcomb->combination_price_levels);
849
850 $newproduct->weight += $weight_impact;
851
852 // Now create the product
853 //print 'Create prod '.$newproduct->ref.'<br>'."\n";
854 if ($existingProduct === false) {
855 //To avoid wrong information in price history log
856 $newproduct->price = 0;
857 $newproduct->price_ttc = 0;
858 $newproduct->price_min = 0;
859 $newproduct->price_min_ttc = 0;
860
861 // A new variant must use a new barcode (not same product)
862 $newproduct->barcode = -1;
863 $result = $newproduct->create($user);
864
865 if ($result < 0) {
866 //In case the error is not related with an already existing product
867 if ($newproduct->error != 'ErrorProductAlreadyExists') {
868 $this->error[] = $newproduct->error;
869 $this->errors = $newproduct->errors;
870 $this->db->rollback();
871 return -1;
872 }
873
879 if ($newcomb->fk_product_child) {
880 $res = $newproduct->fetch($existingCombination->fk_product_child);
881 } else {
882 $orig_prod_ref = $newproduct->ref;
883 $i = 1;
884
885 do {
886 $newproduct->ref = $orig_prod_ref.$i;
887 $res = $newproduct->create($user);
888
889 if ($newproduct->error != 'ErrorProductAlreadyExists') {
890 $this->errors[] = $newproduct->error;
891 break;
892 }
893
894 $i++;
895 } while ($res < 0);
896 }
897
898 if ($res < 0) {
899 $this->db->rollback();
900 return -1;
901 }
902 }
903 } else {
904 $result = $newproduct->update($newproduct->id, $user);
905 if ($result < 0) {
906 $this->db->rollback();
907 return -1;
908 }
909 }
910
911 $newcomb->fk_product_child = $newproduct->id;
912
913 if ($newcomb->update($user) < 0) {
914 $this->error = $newcomb->error;
915 $this->errors = $newcomb->errors;
916 $this->db->rollback();
917 return -1;
918 }
919
920 $this->db->commit();
921 return $newproduct->id;
922 }
923
932 public function copyAll(User $user, $origProductId, Product $destProduct)
933 {
934 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
935
936 //To prevent a loop
937 if ($origProductId == $destProduct->id) {
938 return -1;
939 }
940
941 $prodcomb2val = new ProductCombination2ValuePair($this->db);
942
943 //Retrieve all product combinations
944 $combinations = $this->fetchAllByFkProductParent($origProductId);
945
946 foreach ($combinations as $combination) {
947 $variations = array();
948
949 foreach ($prodcomb2val->fetchByFkCombination($combination->id) as $tmp_pc2v) {
950 $variations[$tmp_pc2v->fk_prod_attr] = $tmp_pc2v->fk_prod_attr_val;
951 }
952
953 $variation_price_percentage = $combination->variation_price_percentage;
954 $variation_price = $combination->variation_price;
955
956 if (getDolGlobalInt('PRODUIT_MULTIPRICES') && getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT') > 1) {
957 $variation_price_percentage = [ ];
958 $variation_price = [ ];
959
960 foreach ($combination->combination_price_levels as $productCombinationLevel) {
961 $variation_price_percentage[$productCombinationLevel->fk_price_level] = $productCombinationLevel->variation_price_percentage;
962 $variation_price[$productCombinationLevel->fk_price_level] = $productCombinationLevel->variation_price;
963 }
964 }
965
966 if ($this->createProductCombination(
967 $user,
968 $destProduct,
969 $variations,
970 array(),
971 $variation_price_percentage,
972 $variation_price,
973 $combination->variation_weight
974 ) < 0) {
975 return -1;
976 }
977 }
978
979 return 1;
980 }
981
987 public function getCombinationLabel($prod_child)
988 {
989 $label = '';
990 $sql = 'SELECT pav.value AS label';
991 $sql .= ' FROM '.MAIN_DB_PREFIX.'product_attribute_combination pac';
992 $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_attribute_combination2val pac2v ON pac2v.fk_prod_combination=pac.rowid';
993 $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_attribute_value pav ON pav.rowid=pac2v.fk_prod_attr_val';
994 $sql .= ' WHERE pac.fk_product_child='.((int) $prod_child);
995
996 $resql = $this->db->query($sql);
997 if ($resql) {
998 $num = $this->db->num_rows($resql);
999
1000 $i = 0;
1001
1002 while ($i < $num) {
1003 $obj = $this->db->fetch_object($resql);
1004
1005 if ($obj->label) {
1006 $label .= ' '.$obj->label;
1007 }
1008 $i++;
1009 }
1010 }
1011 return $label;
1012 }
1013}
1014
1015
1016
1022{
1027 public $db;
1028
1032 public $table_element = 'product_attribute_combination_price_level';
1033
1038 public $id;
1039
1044 public $fk_product_attribute_combination;
1045
1050 public $fk_price_level;
1051
1056 public $variation_price;
1057
1062 public $variation_price_percentage = false;
1063
1067 public $error;
1068
1072 public $errors = array();
1073
1079 public function __construct(DoliDB $db)
1080 {
1081 $this->db = $db;
1082 }
1083
1090 public function fetch($rowid)
1091 {
1092 $sql = "SELECT rowid, fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
1093 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
1094 $sql .= " WHERE rowid = ".(int) $rowid;
1095
1096 $resql = $this->db->query($sql);
1097 if ($resql) {
1098 $obj = $this->db->fetch_object($resql);
1099 if ($obj) {
1100 return $this->fetchFormObj($obj);
1101 }
1102 }
1103
1104 return -1;
1105 }
1106
1107
1115 public function fetchAll($fk_product_attribute_combination, $fk_price_level = 0)
1116 {
1117 $result = array();
1118
1119 $sql = "SELECT rowid, fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
1120 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
1121 $sql .= " WHERE fk_product_attribute_combination = ".intval($fk_product_attribute_combination);
1122 if (!empty($fk_price_level)) {
1123 $sql .= ' AND fk_price_level = '.intval($fk_price_level);
1124 }
1125
1126 $res = $this->db->query($sql);
1127 if ($res) {
1128 if ($this->db->num_rows($res) > 0) {
1129 while ($obj = $this->db->fetch_object($res)) {
1130 $productCombinationLevel = new ProductCombinationLevel($this->db);
1131 $productCombinationLevel->fetchFormObj($obj);
1132 $result[$obj->fk_price_level] = $productCombinationLevel;
1133 }
1134 }
1135 } else {
1136 return -1;
1137 }
1138
1139 return $result;
1140 }
1141
1148 public function fetchFormObj($obj)
1149 {
1150 if (!$obj) {
1151 return -1;
1152 }
1153
1154 $this->id = $obj->rowid;
1155 $this->fk_product_attribute_combination = floatval($obj->fk_product_attribute_combination);
1156 $this->fk_price_level = intval($obj->fk_price_level);
1157 $this->variation_price = floatval($obj->variation_price);
1158 $this->variation_price_percentage = (bool) $obj->variation_price_percentage;
1159
1160 return 1;
1161 }
1162
1163
1169 public function save()
1170 {
1171 if (($this->id > 0 && empty($this->fk_product_attribute_combination)) || empty($this->fk_price_level)) {
1172 return -1;
1173 }
1174
1175 // Check if level exist in DB before add
1176 if ($this->fk_product_attribute_combination > 0 && empty($this->id)) {
1177 $sql = "SELECT rowid id";
1178 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
1179 $sql .= " WHERE fk_product_attribute_combination = ".(int) $this->fk_product_attribute_combination;
1180 $sql .= ' AND fk_price_level = '.((int) $this->fk_price_level);
1181
1182 $resql = $this->db->query($sql);
1183 if ($resql) {
1184 $obj = $this->db->fetch_object($resql);
1185 if ($obj) {
1186 $this->id = $obj->id;
1187 }
1188 }
1189 }
1190
1191 // Update
1192 if (!empty($this->id)) {
1193 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1194 $sql .= ' SET variation_price = '.floatval($this->variation_price).' , variation_price_percentage = '.intval($this->variation_price_percentage);
1195 $sql .= ' WHERE rowid = '.((int) $this->id);
1196
1197 $res = $this->db->query($sql);
1198 if ($res > 0) {
1199 return $this->id;
1200 } else {
1201 $this->error = $this->db->error();
1202 $this->errors[] = $this->error;
1203 return -1;
1204 }
1205 } else {
1206 // Add
1207 $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
1208 $sql .= "fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
1209 $sql .= ") VALUES (";
1210 $sql .= (int) $this->fk_product_attribute_combination;
1211 $sql .= ", ".intval($this->fk_price_level);
1212 $sql .= ", ".floatval($this->variation_price);
1213 $sql .= ", ".intval($this->variation_price_percentage);
1214 $sql .= ")";
1215
1216 $res = $this->db->query($sql);
1217 if ($res) {
1218 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
1219 } else {
1220 $this->error = $this->db->error();
1221 $this->errors[] = $this->error;
1222 return -1;
1223 }
1224 }
1225
1226 return $this->id;
1227 }
1228
1229
1235 public function delete()
1236 {
1237 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".(int) $this->id;
1238 $res = $this->db->query($sql);
1239
1240 return $res ? 1 : -1;
1241 }
1242
1243
1250 public function deleteAllForCombination($fk_product_attribute_combination)
1251 {
1252 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE fk_product_attribute_combination = ".(int) $fk_product_attribute_combination;
1253 $res = $this->db->query($sql);
1254
1255 return $res ? 1 : -1;
1256 }
1257
1258
1265 public function clean($fk_product_attribute_combination)
1266 {
1267 global $conf;
1268
1269 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element;
1270 $sql .= " WHERE fk_product_attribute_combination = ".(int) $fk_product_attribute_combination;
1271 $sql .= " AND fk_price_level > ".intval($conf->global->PRODUIT_MULTIPRICES_LIMIT);
1272 $res = $this->db->query($sql);
1273
1274 return $res ? 1 : -1;
1275 }
1276
1277
1286 public static function createFromParent(DoliDB $db, ProductCombination $productCombination, $fkPriceLevel)
1287 {
1288 $productCombinationLevel = new self($db);
1289 $productCombinationLevel->fk_price_level = $fkPriceLevel;
1290 $productCombinationLevel->fk_product_attribute_combination = $productCombination->id;
1291 $productCombinationLevel->variation_price = $productCombination->variation_price;
1292 $productCombinationLevel->variation_price_percentage = (bool) $productCombination->variation_price_percentage;
1293
1294 return $productCombinationLevel;
1295 }
1296}
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.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
getDolGlobalInt($key, $default=0)
Return dolibarr global constant int value.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.