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