dolibarr 19.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
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 (getDolGlobalString('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) && getDolGlobalString('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 (getDolGlobalString('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 (getDolGlobalString('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 (getDolGlobalString('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 (getDolGlobalString('PRODUIT_MULTIPRICES')) {
529 $produit_multiprices_limit = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
530 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
531 if ($parent->multiprices[$i] != '' || isset($this->combination_price_levels[$i]->variation_price)) {
532 $new_type = empty($parent->multiprices_base_type[$i]) ? 'HT' : $parent->multiprices_base_type[$i];
533 $new_min_price = $parent->multiprices_min[$i];
534 $variation_price = (float) (!isset($this->combination_price_levels[$i]->variation_price) ? $this->variation_price : $this->combination_price_levels[$i]->variation_price);
535 $variation_price_percentage = (bool) (!isset($this->combination_price_levels[$i]->variation_price_percentage) ? $this->variation_price_percentage : $this->combination_price_levels[$i]->variation_price_percentage);
536
537 if ($parent->prices_by_qty_list[$i]) {
538 $new_psq = 1;
539 } else {
540 $new_psq = 0;
541 }
542
543 if ($new_type == 'TTC') {
544 $new_price = $parent->multiprices_ttc[$i];
545 } else {
546 $new_price = $parent->multiprices[$i];
547 }
548
549 if ($variation_price_percentage) {
550 if ($new_price != 0) {
551 $new_price *= 1 + ($variation_price / 100);
552 }
553 } else {
554 $new_price += $variation_price;
555 }
556
557 $ret = $child->updatePrice($new_price, $new_type, $user, $new_vat, $new_min_price, $i, $new_npr, $new_psq, 0, array(), $parent->default_vat_code);
558
559 if ($ret < 0) {
560 $this->db->rollback();
561 $this->error = $child->error;
562 $this->errors = $child->errors;
563 return $ret;
564 }
565 }
566 }
567 } else {
568 $new_type = $parent->price_base_type;
569 $new_min_price = $parent->price_min;
570 $new_psq = $parent->price_by_qty;
571
572 if ($new_type == 'TTC') {
573 $new_price = $parent->price_ttc;
574 } else {
575 $new_price = $parent->price;
576 }
577
578 if ($this->variation_price_percentage) {
579 if ($new_price != 0) {
580 $new_price *= 1 + ($this->variation_price / 100);
581 }
582 } else {
583 $new_price += $this->variation_price;
584 }
585
586 $ret = $child->updatePrice($new_price, $new_type, $user, $new_vat, $new_min_price, 1, $new_npr, $new_psq);
587
588 if ($ret < 0) {
589 $this->db->rollback();
590 $this->error = $child->error;
591 $this->errors = $child->errors;
592 return $ret;
593 }
594 }
595
596 $this->db->commit();
597
598 return 1;
599 }
600
601 $this->db->rollback();
602 $this->error = $child->error;
603 $this->errors = $child->errors;
604 return -1;
605 }
606
614 public function fetchByProductCombination2ValuePairs($prodid, array $features)
615 {
616 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
617
618 $actual_comp = array();
619
620 $prodcomb2val = new ProductCombination2ValuePair($this->db);
621 $prodcomb = new ProductCombination($this->db);
622
623 $features = array_filter($features, function ($v) {
624 return !empty($v);
625 });
626
627 foreach ($features as $attr => $attr_val) {
628 $actual_comp[$attr] = $attr_val;
629 }
630
631 foreach ($prodcomb->fetchAllByFkProductParent($prodid) as $prc) {
632 $values = array();
633
634 foreach ($prodcomb2val->fetchByFkCombination($prc->id) as $value) {
635 $values[$value->fk_prod_attr] = $value->fk_prod_attr_val;
636 }
637
638 $check1 = count(array_diff_assoc($values, $actual_comp));
639 $check2 = count(array_diff_assoc($actual_comp, $values));
640
641 if (!$check1 && !$check2) {
642 return $prc;
643 }
644 }
645
646 return false;
647 }
648
656 {
657 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
658 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
659
660 $variants = array();
661
662 //Attributes
663 $sql = "SELECT DISTINCT fk_prod_attr, a.position";
664 $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";
665 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = c.fk_product_child";
666 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_attribute a ON a.rowid = fk_prod_attr";
667 $sql .= " WHERE c.fk_product_parent = ".((int) $productid)." AND p.tosell = 1";
668 $sql .= $this->db->order('a.position', 'asc');
669
670 $query = $this->db->query($sql);
671
672 //Values
673 while ($result = $this->db->fetch_object($query)) {
674 $attr = new ProductAttribute($this->db);
675 $attr->fetch($result->fk_prod_attr);
676
677 $tmp = new stdClass();
678 $tmp->id = $attr->id;
679 $tmp->ref = $attr->ref;
680 $tmp->label = $attr->label;
681 $tmp->values = array();
682
683 $attrval = new ProductAttributeValue($this->db);
684 foreach ($res = $attrval->fetchAllByProductAttribute($attr->id, true) as $val) {
685 $tmp->values[] = $val;
686 }
687
688 $variants[] = $tmp;
689 }
690
691 return $variants;
692 }
693
717 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 = '')
718 {
719 global $conf;
720
721 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
722 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
723
724 $this->db->begin();
725
726 $price_impact = array(1=>0); // init level price impact
727
728 $forced_refvar = trim($forced_refvar);
729
730 if (!empty($forced_refvar) && $forced_refvar != $product->ref) {
731 $existingProduct = new Product($this->db);
732 $result = $existingProduct->fetch('', $forced_refvar);
733 if ($result > 0) {
734 $newproduct = $existingProduct;
735 } else {
736 $existingProduct = false;
737 $newproduct = clone $product;
738 $newproduct->ref = $forced_refvar;
739 }
740 } else {
741 $forced_refvar = false;
742 $existingProduct = false;
743 $newproduct = clone $product;
744 }
745
746 //Final weight impact
747 $weight_impact = (float) $forced_weightvar; // If false, return 0
748
749 //Final price impact
750 if (!is_array($forced_pricevar)) {
751 $price_impact[1] = (float) $forced_pricevar; // If false, return 0
752 } else {
753 $price_impact = $forced_pricevar;
754 }
755
756 if (!is_array($price_var_percent)) {
757 $price_var_percent = array(1 => (bool) $price_var_percent);
758 }
759
760 $newcomb = new ProductCombination($this->db);
761 $existingCombination = $newcomb->fetchByProductCombination2ValuePairs($product->id, $combinations);
762
763 if ($existingCombination) {
764 $newcomb = $existingCombination;
765 } else {
766 $newcomb->fk_product_parent = $product->id;
767
768 // Create 1 entry into product_attribute_combination (1 entry for each combinations). This init also $newcomb->id
769 $result = $newcomb->create($user);
770 if ($result < 0) {
771 $this->error = $newcomb->error;
772 $this->errors = $newcomb->errors;
773 $this->db->rollback();
774 return -1;
775 }
776 }
777
778 $prodattr = new ProductAttribute($this->db);
779 $prodattrval = new ProductAttributeValue($this->db);
780
781 // $combination contains list of attributes pairs key->value. Example: array('id Color'=>id Blue, 'id Size'=>id Small, 'id Option'=>id val a, ...)
782 //var_dump($combinations);
783 foreach ($combinations as $currcombattr => $currcombval) {
784 //This was checked earlier, so no need to double check
785 $prodattr->fetch($currcombattr);
786 $prodattrval->fetch($currcombval);
787
788 //If there is an existing combination, there is no need to duplicate the valuepair
789 if (!$existingCombination) {
790 $tmp = new ProductCombination2ValuePair($this->db);
791 $tmp->fk_prod_attr = $currcombattr;
792 $tmp->fk_prod_attr_val = $currcombval;
793 $tmp->fk_prod_combination = $newcomb->id;
794
795 if ($tmp->create($user) < 0) { // Create 1 entry into product_attribute_combination2val
796 $this->error = $tmp->error;
797 $this->errors = $tmp->errors;
798 $this->db->rollback();
799 return -1;
800 }
801 }
802
803 if ($forced_weightvar === false) {
804 $weight_impact += (float) price2num($variations[$currcombattr][$currcombval]['weight']);
805 }
806 if ($forced_pricevar === false) {
807 $price_impact[1] += (float) price2num($variations[$currcombattr][$currcombval]['price']);
808
809 // Manage Price levels
810 if ($conf->global->PRODUIT_MULTIPRICES) {
811 for ($i = 2; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
812 $price_impact[$i] += (float) price2num($variations[$currcombattr][$currcombval]['price']);
813 }
814 }
815 }
816
817 if ($forced_refvar === false) {
818 if (isset($conf->global->PRODUIT_ATTRIBUTES_SEPARATOR)) {
819 $newproduct->ref .= getDolGlobalString('PRODUIT_ATTRIBUTES_SEPARATOR') . $prodattrval->ref;
820 } else {
821 $newproduct->ref .= '_'.$prodattrval->ref;
822 }
823 }
824
825 //The first one should not contain a linebreak
826 if ($newproduct->description) {
827 $newproduct->description .= '<br>';
828 }
829 $newproduct->description .= '<strong>'.$prodattr->label.':</strong> '.$prodattrval->value;
830 }
831
832 $newcomb->variation_price_percentage = (bool) $price_var_percent[1];
833 $newcomb->variation_price = $price_impact[1];
834 $newcomb->variation_weight = $weight_impact;
835 $newcomb->variation_ref_ext = $this->db->escape($ref_ext);
836
837 // Init price level
838 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
839 $produit_multiprices_limit = getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT');
840 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
841 $productCombinationLevel = new ProductCombinationLevel($this->db);
842 $productCombinationLevel->fk_product_attribute_combination = $newcomb->id;
843 $productCombinationLevel->fk_price_level = $i;
844 $productCombinationLevel->variation_price = $price_impact[$i];
845
846 if (is_array($price_var_percent)) {
847 $productCombinationLevel->variation_price_percentage = (bool) $price_var_percent[$i] ;
848 } else {
849 $productCombinationLevel->variation_price_percentage = $price_var_percent;
850 }
851
852 $newcomb->combination_price_levels[$i] = $productCombinationLevel;
853 }
854 }
855 //var_dump($newcomb->combination_price_levels);
856
857 $newproduct->weight += $weight_impact;
858
859 // Now create the product
860 //print 'Create prod '.$newproduct->ref.'<br>'."\n";
861 if ($existingProduct === false) {
862 //To avoid wrong information in price history log
863 $newproduct->price = 0;
864 $newproduct->price_ttc = 0;
865 $newproduct->price_min = 0;
866 $newproduct->price_min_ttc = 0;
867
868 // A new variant must use a new barcode (not same product)
869 $newproduct->barcode = -1;
870 $result = $newproduct->create($user);
871
872 if ($result < 0) {
873 //In case the error is not related with an already existing product
874 if ($newproduct->error != 'ErrorProductAlreadyExists') {
875 $this->error[] = $newproduct->error;
876 $this->errors = $newproduct->errors;
877 $this->db->rollback();
878 return -1;
879 }
880
886 if ($newcomb->fk_product_child) {
887 $res = $newproduct->fetch($existingCombination->fk_product_child);
888 } else {
889 $orig_prod_ref = $newproduct->ref;
890 $i = 1;
891
892 do {
893 $newproduct->ref = $orig_prod_ref.$i;
894 $res = $newproduct->create($user);
895
896 if ($newproduct->error != 'ErrorProductAlreadyExists') {
897 $this->errors[] = $newproduct->error;
898 break;
899 }
900
901 $i++;
902 } while ($res < 0);
903 }
904
905 if ($res < 0) {
906 $this->db->rollback();
907 return -1;
908 }
909 }
910 } else {
911 $result = $newproduct->update($newproduct->id, $user);
912 if ($result < 0) {
913 $this->db->rollback();
914 return -1;
915 }
916 }
917
918 $newcomb->fk_product_child = $newproduct->id;
919
920 if ($newcomb->update($user) < 0) {
921 $this->error = $newcomb->error;
922 $this->errors = $newcomb->errors;
923 $this->db->rollback();
924 return -1;
925 }
926
927 $this->db->commit();
928 return $newproduct->id;
929 }
930
939 public function copyAll(User $user, $origProductId, Product $destProduct)
940 {
941 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
942
943 //To prevent a loop
944 if ($origProductId == $destProduct->id) {
945 return -1;
946 }
947
948 $prodcomb2val = new ProductCombination2ValuePair($this->db);
949
950 //Retrieve all product combinations
951 $combinations = $this->fetchAllByFkProductParent($origProductId);
952
953 foreach ($combinations as $combination) {
954 $variations = array();
955
956 foreach ($prodcomb2val->fetchByFkCombination($combination->id) as $tmp_pc2v) {
957 $variations[$tmp_pc2v->fk_prod_attr] = $tmp_pc2v->fk_prod_attr_val;
958 }
959
960 $variation_price_percentage = $combination->variation_price_percentage;
961 $variation_price = $combination->variation_price;
962
963 if (getDolGlobalInt('PRODUIT_MULTIPRICES') && getDolGlobalInt('PRODUIT_MULTIPRICES_LIMIT') > 1) {
964 $variation_price_percentage = [ ];
965 $variation_price = [ ];
966
967 foreach ($combination->combination_price_levels as $productCombinationLevel) {
968 $variation_price_percentage[$productCombinationLevel->fk_price_level] = $productCombinationLevel->variation_price_percentage;
969 $variation_price[$productCombinationLevel->fk_price_level] = $productCombinationLevel->variation_price;
970 }
971 }
972
973 if ($this->createProductCombination(
974 $user,
975 $destProduct,
976 $variations,
977 array(),
978 $variation_price_percentage,
979 $variation_price,
980 $combination->variation_weight
981 ) < 0) {
982 return -1;
983 }
984 }
985
986 return 1;
987 }
988
994 public function getCombinationLabel($prod_child)
995 {
996 $label = '';
997 $sql = 'SELECT pav.value AS label';
998 $sql .= ' FROM '.MAIN_DB_PREFIX.'product_attribute_combination pac';
999 $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_attribute_combination2val pac2v ON pac2v.fk_prod_combination=pac.rowid';
1000 $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_attribute_value pav ON pav.rowid=pac2v.fk_prod_attr_val';
1001 $sql .= ' WHERE pac.fk_product_child='.((int) $prod_child);
1002
1003 $resql = $this->db->query($sql);
1004 if ($resql) {
1005 $num = $this->db->num_rows($resql);
1006
1007 $i = 0;
1008
1009 while ($i < $num) {
1010 $obj = $this->db->fetch_object($resql);
1011
1012 if ($obj->label) {
1013 $label .= ' '.$obj->label;
1014 }
1015 $i++;
1016 }
1017 }
1018 return $label;
1019 }
1020}
1021
1022
1023
1029{
1034 public $db;
1035
1039 public $table_element = 'product_attribute_combination_price_level';
1040
1045 public $id;
1046
1051 public $fk_product_attribute_combination;
1052
1057 public $fk_price_level;
1058
1063 public $variation_price;
1064
1069 public $variation_price_percentage = false;
1070
1074 public $error;
1075
1079 public $errors = array();
1080
1086 public function __construct(DoliDB $db)
1087 {
1088 $this->db = $db;
1089 }
1090
1097 public function fetch($rowid)
1098 {
1099 $sql = "SELECT rowid, fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
1100 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
1101 $sql .= " WHERE rowid = ".(int) $rowid;
1102
1103 $resql = $this->db->query($sql);
1104 if ($resql) {
1105 $obj = $this->db->fetch_object($resql);
1106 if ($obj) {
1107 return $this->fetchFormObj($obj);
1108 }
1109 }
1110
1111 return -1;
1112 }
1113
1114
1122 public function fetchAll($fk_product_attribute_combination, $fk_price_level = 0)
1123 {
1124 $result = array();
1125
1126 $sql = "SELECT rowid, fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
1127 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
1128 $sql .= " WHERE fk_product_attribute_combination = ".intval($fk_product_attribute_combination);
1129 if (!empty($fk_price_level)) {
1130 $sql .= ' AND fk_price_level = '.intval($fk_price_level);
1131 }
1132
1133 $res = $this->db->query($sql);
1134 if ($res) {
1135 if ($this->db->num_rows($res) > 0) {
1136 while ($obj = $this->db->fetch_object($res)) {
1137 $productCombinationLevel = new ProductCombinationLevel($this->db);
1138 $productCombinationLevel->fetchFormObj($obj);
1139 $result[$obj->fk_price_level] = $productCombinationLevel;
1140 }
1141 }
1142 } else {
1143 return -1;
1144 }
1145
1146 return $result;
1147 }
1148
1155 public function fetchFormObj($obj)
1156 {
1157 if (!$obj) {
1158 return -1;
1159 }
1160
1161 $this->id = $obj->rowid;
1162 $this->fk_product_attribute_combination = (float) $obj->fk_product_attribute_combination;
1163 $this->fk_price_level = intval($obj->fk_price_level);
1164 $this->variation_price = (float) $obj->variation_price;
1165 $this->variation_price_percentage = (bool) $obj->variation_price_percentage;
1166
1167 return 1;
1168 }
1169
1170
1176 public function save()
1177 {
1178 if (($this->id > 0 && empty($this->fk_product_attribute_combination)) || empty($this->fk_price_level)) {
1179 return -1;
1180 }
1181
1182 // Check if level exist in DB before add
1183 if ($this->fk_product_attribute_combination > 0 && empty($this->id)) {
1184 $sql = "SELECT rowid id";
1185 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
1186 $sql .= " WHERE fk_product_attribute_combination = ".(int) $this->fk_product_attribute_combination;
1187 $sql .= ' AND fk_price_level = '.((int) $this->fk_price_level);
1188
1189 $resql = $this->db->query($sql);
1190 if ($resql) {
1191 $obj = $this->db->fetch_object($resql);
1192 if ($obj) {
1193 $this->id = $obj->id;
1194 }
1195 }
1196 }
1197
1198 // Update
1199 if (!empty($this->id)) {
1200 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1201 $sql .= ' SET variation_price = '.(float) $this->variation_price.' , variation_price_percentage = '.intval($this->variation_price_percentage);
1202 $sql .= ' WHERE rowid = '.((int) $this->id);
1203
1204 $res = $this->db->query($sql);
1205 if ($res > 0) {
1206 return $this->id;
1207 } else {
1208 $this->error = $this->db->error();
1209 $this->errors[] = $this->error;
1210 return -1;
1211 }
1212 } else {
1213 // Add
1214 $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
1215 $sql .= "fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
1216 $sql .= ") VALUES (";
1217 $sql .= (int) $this->fk_product_attribute_combination;
1218 $sql .= ", ".intval($this->fk_price_level);
1219 $sql .= ", ".(float) $this->variation_price;
1220 $sql .= ", ".intval($this->variation_price_percentage);
1221 $sql .= ")";
1222
1223 $res = $this->db->query($sql);
1224 if ($res) {
1225 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
1226 } else {
1227 $this->error = $this->db->error();
1228 $this->errors[] = $this->error;
1229 return -1;
1230 }
1231 }
1232
1233 return $this->id;
1234 }
1235
1236
1242 public function delete()
1243 {
1244 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".(int) $this->id;
1245 $res = $this->db->query($sql);
1246
1247 return $res ? 1 : -1;
1248 }
1249
1250
1257 public function deleteAllForCombination($fk_product_attribute_combination)
1258 {
1259 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE fk_product_attribute_combination = ".(int) $fk_product_attribute_combination;
1260 $res = $this->db->query($sql);
1261
1262 return $res ? 1 : -1;
1263 }
1264
1265
1272 public function clean($fk_product_attribute_combination)
1273 {
1274 global $conf;
1275
1276 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element;
1277 $sql .= " WHERE fk_product_attribute_combination = ".(int) $fk_product_attribute_combination;
1278 $sql .= " AND fk_price_level > ".intval($conf->global->PRODUIT_MULTIPRICES_LIMIT);
1279 $res = $this->db->query($sql);
1280
1281 return $res ? 1 : -1;
1282 }
1283
1284
1293 public static function createFromParent(DoliDB $db, ProductCombination $productCombination, $fkPriceLevel)
1294 {
1295 $productCombinationLevel = new self($db);
1296 $productCombinationLevel->fk_price_level = $fkPriceLevel;
1297 $productCombinationLevel->fk_product_attribute_combination = $productCombination->id;
1298 $productCombinationLevel->variation_price = $productCombination->variation_price;
1299 $productCombinationLevel->variation_price_percentage = (bool) $productCombination->variation_price_percentage;
1300
1301 return $productCombinationLevel;
1302 }
1303}
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 a Dolibarr global constant int value.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.