dolibarr 21.0.0-alpha
ProductCombination.class.php
Go to the documentation of this file.
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 * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
6 * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
42{
47 public $db;
48
53 public $id;
54
59 public $fk_product_parent;
60
65 public $fk_product_child;
66
71 public $variation_price;
72
78 public $variation_price_percentage = false;
79
84 public $variation_weight;
85
90 public $entity;
91
96 public $combination_price_levels;
97
102 public $variation_ref_ext = '';
103
108 public $error;
109
114 public $errors = array();
115
121 public function __construct(DoliDB $db)
122 {
123 global $conf;
124
125 $this->db = $db;
126 $this->entity = $conf->entity;
127 }
128
135 public function fetch($rowid)
136 {
137 $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').")";
138
139 $query = $this->db->query($sql);
140
141 if (!$query) {
142 return -1;
143 }
144
145 if (!$this->db->num_rows($query)) {
146 return -1;
147 }
148
149 $obj = $this->db->fetch_object($query);
150
151 $this->id = $obj->rowid;
152 $this->fk_product_parent = $obj->fk_product_parent;
153 $this->fk_product_child = $obj->fk_product_child;
154 $this->variation_price = $obj->variation_price;
155 $this->variation_price_percentage = $obj->variation_price_percentage;
156 $this->variation_weight = $obj->variation_weight;
157 $this->variation_ref_ext = $obj->variation_ref_ext;
158
159 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
161 }
162
163 return 1;
164 }
165
166
174 public function fetchCombinationPriceLevels($fk_price_level = 0, $useCache = true)
175 {
176 global $conf;
177
178 // Check cache
179 if (!empty($this->combination_price_levels) && $useCache) {
180 if ((!empty($fk_price_level) && isset($this->combination_price_levels[$fk_price_level])) || empty($fk_price_level)) {
181 return 1;
182 }
183 }
184
185 if (!is_array($this->combination_price_levels)
186 || empty($fk_price_level) // if fetch an unique level don't erase all already fetched
187 ) {
188 $this->combination_price_levels = array();
189 }
190
191 $staticProductCombinationLevel = new ProductCombinationLevel($this->db);
192 $combination_price_levels = $staticProductCombinationLevel->fetchAll($this->id, $fk_price_level);
193
194 if (!is_array($combination_price_levels)) {
195 return -1;
196 }
197
198 if (empty($combination_price_levels)) {
202 if ($fk_price_level > 0) {
203 $combination_price_levels[$fk_price_level] = ProductCombinationLevel::createFromParent($this->db, $this, $fk_price_level);
204 } else {
205 $produit_multiprices_limit = getDolGlobalString('PRODUIT_MULTIPRICES_LIMIT');
206 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
207 $combination_price_levels[$i] = ProductCombinationLevel::createFromParent($this->db, $this, $i);
208 }
209 }
210 }
211
212 $this->combination_price_levels = $combination_price_levels;
213
214 return 1;
215 }
216
223 public function saveCombinationPriceLevels($clean = 1)
224 {
225 global $conf;
226
227 $error = 0;
228
229 $staticProductCombinationLevel = new ProductCombinationLevel($this->db);
230
231 // Delete all
232 if (empty($this->combination_price_levels)) {
233 return $staticProductCombinationLevel->deleteAllForCombination($this->id);
234 }
235
236 // Clean not needed price levels (level higher than number max defined into setup)
237 if ($clean) {
238 $res = $staticProductCombinationLevel->clean($this->id);
239 if ($res < 0) {
240 $this->errors[] = 'Fail to clean not needed price levels';
241 return -1;
242 }
243 }
244
245 foreach ($this->combination_price_levels as $fk_price_level => $combination_price_level) {
246 $res = $combination_price_level->save();
247 if ($res < 1) {
248 $this->error = 'Error saving combination price level '.$fk_price_level.' : '.$combination_price_level->error;
249 $this->errors[] = $this->error;
250 $error++;
251 break;
252 }
253 }
254
255 if ($error) {
256 return $error * -1;
257 } else {
258 return 1;
259 }
260 }
261
269 public function fetchByFkProductChild($productid, $donotloadpricelevel = 0)
270 {
271 global $conf;
272
273 $sql = "SELECT rowid, fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight";
274 $sql .= " FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE fk_product_child = ".((int) $productid)." AND entity IN (".getEntity('product').")";
275
276 $query = $this->db->query($sql);
277
278 if (!$query) {
279 return -1;
280 }
281
282 if (!$this->db->num_rows($query)) {
283 return 0;
284 }
285
286 $result = $this->db->fetch_object($query);
287
288 $this->id = $result->rowid;
289 $this->fk_product_parent = $result->fk_product_parent;
290 $this->fk_product_child = $result->fk_product_child;
291 $this->variation_price = $result->variation_price;
292 $this->variation_price_percentage = $result->variation_price_percentage;
293 $this->variation_weight = $result->variation_weight;
294
295 if (empty($donotloadpricelevel) && getDolGlobalString('PRODUIT_MULTIPRICES')) {
297 }
298
299 return (int) $this->fk_product_parent;
300 }
301
309 public function fetchAllByFkProductParent($fk_product_parent, $sort_by_ref = false)
310 {
311 global $conf;
312
313 $sql = "SELECT pac.rowid, pac.fk_product_parent, pac.fk_product_child, pac.variation_price, pac.variation_price_percentage, pac.variation_ref_ext, pac.variation_weight";
314 $sql .= " FROM ".MAIN_DB_PREFIX."product_attribute_combination AS pac";
315 if ($sort_by_ref) {
316 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product AS p ON p.rowid = pac.fk_product_child";
317 }
318 $sql .= " WHERE pac.fk_product_parent = ".((int) $fk_product_parent)." AND pac.entity IN (".getEntity('product').")";
319 if ($sort_by_ref) {
320 $sql .= $this->db->order('p.ref', 'ASC');
321 }
322
323 $query = $this->db->query($sql);
324
325 if (!$query) {
326 return -1;
327 }
328
329 $return = array();
330
331 while ($result = $this->db->fetch_object($query)) {
332 $tmp = new ProductCombination($this->db);
333 $tmp->id = $result->rowid;
334 $tmp->fk_product_parent = $result->fk_product_parent;
335 $tmp->fk_product_child = $result->fk_product_child;
336 $tmp->variation_price = $result->variation_price;
337 $tmp->variation_price_percentage = $result->variation_price_percentage;
338 $tmp->variation_weight = $result->variation_weight;
339 $tmp->variation_ref_ext = $result->variation_ref_ext;
340
341 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
342 $tmp->fetchCombinationPriceLevels();
343 }
344
345 $return[] = $tmp;
346 }
347
348 return $return;
349 }
350
357 public function countNbOfCombinationForFkProductParent($fk_product_parent)
358 {
359 $nb = 0;
360 $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').")";
361
362 $resql = $this->db->query($sql);
363 if ($resql) {
364 $obj = $this->db->fetch_object($resql);
365 if ($obj) {
366 $nb = $obj->nb;
367 }
368 }
369
370 return $nb;
371 }
372
379 public function create($user)
380 {
381 global $conf;
382
383 /* $this->fk_product_child may be empty and will be filled later after subproduct has been created */
384
385 $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_attribute_combination";
386 $sql .= " (fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight, variation_ref_ext, entity)";
387 $sql .= " VALUES (".((int) $this->fk_product_parent).", ".((int) $this->fk_product_child).",";
388 $sql .= (float) $this->variation_price.", ".(int) $this->variation_price_percentage.",";
389 $sql .= (float) $this->variation_weight.", '".$this->db->escape($this->variation_ref_ext)."', ".(int) $this->entity.")";
390
391 $resql = $this->db->query($sql);
392 if ($resql) {
393 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'product_attribute_combination');
394 } else {
395 $this->error = $this->db->lasterror();
396 return -1;
397 }
398
399 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
400 $res = $this->saveCombinationPriceLevels();
401 if ($res < 0) {
402 return -2;
403 }
404 }
405
406 return 1;
407 }
408
415 public function update(User $user)
416 {
417 global $conf;
418
419 $sql = "UPDATE ".MAIN_DB_PREFIX."product_attribute_combination";
420 $sql .= " SET fk_product_parent = ".(int) $this->fk_product_parent.", fk_product_child = ".(int) $this->fk_product_child.",";
421 $sql .= " variation_price = ".(float) $this->variation_price.", variation_price_percentage = ".(int) $this->variation_price_percentage.",";
422 $sql .= " variation_ref_ext = '".$this->db->escape($this->variation_ref_ext)."',";
423 $sql .= " variation_weight = ".(float) $this->variation_weight." WHERE rowid = ".((int) $this->id);
424
425 $resql = $this->db->query($sql);
426 if (!$resql) {
427 return -1;
428 }
429
430 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
431 $res = $this->saveCombinationPriceLevels();
432 if ($res < 0) {
433 return -2;
434 }
435 }
436
437 $parent = new Product($this->db);
438 $parent->fetch($this->fk_product_parent);
439
440 $this->updateProperties($parent, $user);
441
442 return 1;
443 }
444
451 public function delete(User $user)
452 {
453 $this->db->begin();
454
455 $comb2val = new ProductCombination2ValuePair($this->db);
456 $comb2val->deleteByFkCombination($this->id);
457
458 // remove combination price levels
459 if (!$this->db->query("DELETE FROM ".MAIN_DB_PREFIX."product_attribute_combination_price_level WHERE fk_product_attribute_combination = ".(int) $this->id)) {
460 $this->db->rollback();
461 return -1;
462 }
463
464 $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE rowid = ".(int) $this->id;
465
466 if ($this->db->query($sql)) {
467 $this->db->commit();
468 return 1;
469 }
470
471 $this->db->rollback();
472 return -1;
473 }
474
482 public function deleteByFkProductParent($user, $fk_product_parent)
483 {
484 $this->db->begin();
485
486 foreach ($this->fetchAllByFkProductParent($fk_product_parent) as $prodcomb) {
487 $prodstatic = new Product($this->db);
488
489 $res = $prodstatic->fetch($prodcomb->fk_product_child);
490
491 if ($res > 0) {
492 $res = $prodcomb->delete($user);
493 }
494
495 if ($res > 0 && !$prodstatic->isObjectUsed($prodstatic->id)) {
496 $res = $prodstatic->delete($user);
497 }
498
499 if ($res < 0) {
500 $this->db->rollback();
501 return -1;
502 }
503 }
504
505 $this->db->commit();
506 return 1;
507 }
508
517 public function updateProperties(Product $parent, User $user)
518 {
519 global $conf;
520
521 $this->db->begin();
522
523 $child = new Product($this->db);
524 $child->fetch($this->fk_product_child);
525
526 $child->price_autogen = $parent->price_autogen;
527 $child->weight = $parent->weight;
528 // Only when Parent Status are updated
529 if (is_object($parent->oldcopy) && !$parent->oldcopy->isEmpty() && ($parent->status != $parent->oldcopy->status)) {
530 $child->status = $parent->status;
531 }
532 if (is_object($parent->oldcopy) && !$parent->oldcopy->isEmpty() && ($parent->status_buy != $parent->oldcopy->status_buy)) {
533 $child->status_buy = $parent->status_buy;
534 }
535
536 if ($this->variation_weight) { // If we must add a delta on weight
537 $child->weight = ($child->weight ? $child->weight : 0) + $this->variation_weight;
538 }
539 $child->weight_units = $parent->weight_units;
540
541 // Don't update the child label if the user has already modified it.
542 if ($child->label == $parent->label) {
543 // This will trigger only at variant creation time
544 $varlabel = $this->getCombinationLabel($this->fk_product_child);
545 $child->label = $parent->label.$varlabel;
546 }
547
548
549 if ($child->update($child->id, $user) > 0) {
550 $new_vat = $parent->tva_tx;
551 $new_npr = $parent->tva_npr;
552
553 // MultiPrix
554 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
555 $produit_multiprices_limit = getDolGlobalString('PRODUIT_MULTIPRICES_LIMIT');
556 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
557 if ($parent->multiprices[$i] != '' || isset($this->combination_price_levels[$i]->variation_price)) {
558 $new_type = empty($parent->multiprices_base_type[$i]) ? 'HT' : $parent->multiprices_base_type[$i];
559 $new_min_price = $parent->multiprices_min[$i];
560 $variation_price = (float) (!isset($this->combination_price_levels[$i]->variation_price) ? $this->variation_price : $this->combination_price_levels[$i]->variation_price);
561 $variation_price_percentage = (float) (!isset($this->combination_price_levels[$i]->variation_price_percentage) ? $this->variation_price_percentage : $this->combination_price_levels[$i]->variation_price_percentage);
562
563 if ($parent->prices_by_qty_list[$i]) {
564 $new_psq = 1;
565 } else {
566 $new_psq = 0;
567 }
568
569 if ($new_type == 'TTC') {
570 $new_price = $parent->multiprices_ttc[$i];
571 } else {
572 $new_price = $parent->multiprices[$i];
573 }
574
575 if ($variation_price_percentage) {
576 if ($new_price != 0) {
577 $new_price *= 1 + ($variation_price / 100);
578 }
579 } else {
580 $new_price += $variation_price;
581 }
582
583 $ret = $child->updatePrice($new_price, $new_type, $user, $new_vat, $new_min_price, $i, $new_npr, $new_psq, 0, array(), $parent->default_vat_code);
584
585 if ($ret < 0) {
586 $this->db->rollback();
587 $this->error = $child->error;
588 $this->errors = $child->errors;
589 return $ret;
590 }
591 }
592 }
593 } else {
594 $new_type = $parent->price_base_type;
595 $new_min_price = $parent->price_min;
596 $new_psq = $parent->price_by_qty;
597
598 if ($new_type == 'TTC') {
599 $new_price = $parent->price_ttc;
600 } else {
601 $new_price = $parent->price;
602 }
603
604 if ($this->variation_price_percentage) {
605 if ($new_price != 0) {
606 $new_price *= 1 + ($this->variation_price / 100);
607 }
608 } else {
609 $new_price += $this->variation_price;
610 }
611
612 $ret = $child->updatePrice($new_price, $new_type, $user, $new_vat, $new_min_price, 1, $new_npr, $new_psq);
613
614 if ($ret < 0) {
615 $this->db->rollback();
616 $this->error = $child->error;
617 $this->errors = $child->errors;
618 return $ret;
619 }
620 }
621
622 $this->db->commit();
623
624 return 1;
625 }
626
627 $this->db->rollback();
628 $this->error = $child->error;
629 $this->errors = $child->errors;
630 return -1;
631 }
632
640 public function fetchByProductCombination2ValuePairs($prodid, array $features)
641 {
642 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
643
644 $actual_comp = array();
645
646 $prodcomb2val = new ProductCombination2ValuePair($this->db);
647 $prodcomb = new ProductCombination($this->db);
648
649 $features = array_filter(
650 $features,
655 static function ($v) {
656 return !empty($v);
657 }
658 );
659
660 foreach ($features as $attr => $attr_val) {
661 $actual_comp[$attr] = $attr_val;
662 }
663
664 foreach ($prodcomb->fetchAllByFkProductParent($prodid) as $prc) {
665 $values = array();
666
667 foreach ($prodcomb2val->fetchByFkCombination($prc->id) as $value) {
668 $values[$value->fk_prod_attr] = $value->fk_prod_attr_val;
669 }
670
671 $check1 = count(array_diff_assoc($values, $actual_comp));
672 $check2 = count(array_diff_assoc($actual_comp, $values));
673
674 if (!$check1 && !$check2) {
675 return $prc;
676 }
677 }
678
679 return false;
680 }
681
690 {
691 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
692 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
693
694 // Attributes
695 // Select all unique attributes of the variants (which are to sell) of a given parent product.
696 $sql = "SELECT DISTINCT c2v.fk_prod_attr, a.position";
697 $sql .= " FROM ".MAIN_DB_PREFIX."product_attribute_combination2val c2v";
698 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_attribute_combination c";
699 $sql .= " ON c2v.fk_prod_combination = c.rowid";
700 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product p";
701 $sql .= " ON p.rowid = c.fk_product_child";
702 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_attribute a";
703 $sql .= " ON a.rowid = fk_prod_attr";
704 $sql .= " WHERE c.fk_product_parent = ".((int) $productid);
705 $sql .= " AND p.tosell = 1";
706 $sql .= $this->db->order('a.position', 'asc');
707
708 $resql = $this->db->query($sql);
709
710 // Values
711 $variants = array();
712 while ($obj = $this->db->fetch_object($resql)) {
713 $attr = new ProductAttribute($this->db);
714 $attr->fetch($obj->fk_prod_attr);
715
716 $tmp = new stdClass();
717 $tmp->id = $attr->id;
718 $tmp->ref = $attr->ref;
719 $tmp->label = $attr->label;
720 $tmp->values = array();
721
722 $attrval = new ProductAttributeValue($this->db);
723 // fetch only the used values of this attribute
724 foreach ($attrval->fetchAllByProductAttribute($attr->id, true) as $val) {
725 '@phan-var-force ProductAttributeValue $val';
726 $tmp->values[] = $val;
727 }
728
729 $variants[] = $tmp;
730 }
731
732 return $variants;
733 }
734
758 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 = '')
759 {
760 global $conf;
761
762 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
763 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
764
765 $this->db->begin();
766
767 $price_impact = array(1 => 0); // init level price impact
768
769 $forced_refvar = trim((string) $forced_refvar);
770
771 if (!empty($forced_refvar) && $forced_refvar != $product->ref) {
772 $existingProduct = new Product($this->db);
773 $result = $existingProduct->fetch(0, $forced_refvar);
774 if ($result > 0) {
775 $newproduct = $existingProduct;
776 } else {
777 $existingProduct = false;
778 $newproduct = clone $product;
779 $newproduct->ref = $forced_refvar;
780 }
781 } else {
782 $forced_refvar = false;
783 $existingProduct = false;
784 $newproduct = clone $product;
785 }
786
787 //Final weight impact
788 $weight_impact = (float) $forced_weightvar; // If false, return 0
789
790 //Final price impact
791 if (!is_array($forced_pricevar)) {
792 $price_impact[1] = (float) $forced_pricevar; // If false, return 0
793 } else {
794 $price_impact = $forced_pricevar;
795 }
796
797 if (!array($price_var_percent)) {
798 $price_var_percent[1] = (float) $price_var_percent;
799 }
800
801 $newcomb = new ProductCombination($this->db);
802 $existingCombination = $newcomb->fetchByProductCombination2ValuePairs($product->id, $combinations);
803
804 if ($existingCombination) {
805 $newcomb = $existingCombination;
806 } else {
807 $newcomb->fk_product_parent = $product->id;
808
809 // Create 1 entry into product_attribute_combination (1 entry for each combinations). This init also $newcomb->id
810 $result = $newcomb->create($user);
811 if ($result < 0) {
812 $this->error = $newcomb->error;
813 $this->errors = $newcomb->errors;
814 $this->db->rollback();
815 return -1;
816 }
817 }
818
819 $prodattr = new ProductAttribute($this->db);
820 $prodattrval = new ProductAttributeValue($this->db);
821
822 // $combination contains list of attributes pairs key->value. Example: array('id Color'=>id Blue, 'id Size'=>id Small, 'id Option'=>id val a, ...)
823 foreach ($combinations as $currcombattr => $currcombval) {
824 //This was checked earlier, so no need to double check
825 $prodattr->fetch($currcombattr);
826 $prodattrval->fetch($currcombval);
827
828 //If there is an existing combination, there is no need to duplicate the valuepair
829 if (!$existingCombination) {
830 $tmp = new ProductCombination2ValuePair($this->db);
831 $tmp->fk_prod_attr = $currcombattr;
832 $tmp->fk_prod_attr_val = $currcombval;
833 $tmp->fk_prod_combination = $newcomb->id;
834
835 if ($tmp->create($user) < 0) { // Create 1 entry into product_attribute_combination2val
836 $this->error = $tmp->error;
837 $this->errors = $tmp->errors;
838 $this->db->rollback();
839 return -1;
840 }
841 }
842 if ($forced_weightvar === false) {
843 $weight_impact += (float) price2num($variations[$currcombattr][$currcombval]['weight']);
844 }
845 if ($forced_pricevar === false) {
846 $price_impact[1] += (float) price2num($variations[$currcombattr][$currcombval]['price']);
847
848 // Manage Price levels
849 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
850 $produit_multiprices_limit = getDolGlobalString('PRODUIT_MULTIPRICES_LIMIT');
851 for ($i = 2; $i <= $produit_multiprices_limit; $i++) {
852 $price_impact[$i] += (float) price2num($variations[$currcombattr][$currcombval]['price']);
853 }
854 }
855 }
856
857 if ($forced_refvar === false) {
858 if (isset($conf->global->PRODUIT_ATTRIBUTES_SEPARATOR)) {
859 $newproduct->ref .= getDolGlobalString('PRODUIT_ATTRIBUTES_SEPARATOR') . $prodattrval->ref;
860 } else {
861 $newproduct->ref .= '_'.$prodattrval->ref;
862 }
863 }
864
865 //The first one should not contain a linebreak
866 if ($newproduct->description) {
867 $newproduct->description .= '<br>';
868 }
869 $newproduct->description .= '<strong>'.$prodattr->label.':</strong> '.$prodattrval->value;
870 }
871
872 $newcomb->variation_price_percentage = $price_var_percent[1];
873 $newcomb->variation_price = $price_impact[1];
874 $newcomb->variation_weight = $weight_impact;
875 $newcomb->variation_ref_ext = $this->db->escape($ref_ext);
876
877 // Init price level
878 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
879 $produit_multiprices_limit = getDolGlobalString('PRODUIT_MULTIPRICES_LIMIT');
880 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
881 $productCombinationLevel = new ProductCombinationLevel($this->db);
882 $productCombinationLevel->fk_product_attribute_combination = $newcomb->id;
883 $productCombinationLevel->fk_price_level = $i;
884 $productCombinationLevel->variation_price = $price_impact[$i];
885
886 if (is_array($price_var_percent)) {
887 $productCombinationLevel->variation_price_percentage = (empty($price_var_percent[$i]) ? false : $price_var_percent[$i]);
888 } else {
889 $productCombinationLevel->variation_price_percentage = $price_var_percent;
890 }
891
892 $newcomb->combination_price_levels[$i] = $productCombinationLevel;
893 }
894 }
895 //var_dump($newcomb->combination_price_levels);
896
897 $newproduct->weight += $weight_impact;
898
899 // Now create the product
900 //print 'Create prod '.$newproduct->ref.'<br>'."\n";
901 if ($existingProduct === false) {
902 //To avoid wrong information in price history log
903 $newproduct->price = 0;
904 $newproduct->price_ttc = 0;
905 $newproduct->price_min = 0;
906 $newproduct->price_min_ttc = 0;
907
908 // A new variant must use a new barcode (not same product)
909 $newproduct->barcode = -1;
910 $result = $newproduct->create($user);
911
912 if ($result < 0) {
913 //In case the error is not related with an already existing product
914 if ($newproduct->error != 'ErrorProductAlreadyExists') {
915 $this->error = $newproduct->error;
916 $this->errors = $newproduct->errors;
917 $this->db->rollback();
918 return -1;
919 }
920
926 if ($newcomb->fk_product_child) {
927 $res = $newproduct->fetch($existingCombination->fk_product_child);
928 } else {
929 $orig_prod_ref = $newproduct->ref;
930 $i = 1;
931
932 do {
933 $newproduct->ref = $orig_prod_ref.$i;
934 $res = $newproduct->create($user);
935
936 if ($newproduct->error != 'ErrorProductAlreadyExists') {
937 $this->errors[] = $newproduct->error;
938 break;
939 }
940
941 $i++;
942 } while ($res < 0);
943 }
944
945 if ($res < 0) {
946 $this->db->rollback();
947 return -1;
948 }
949 }
950 } else {
951 $result = $newproduct->update($newproduct->id, $user);
952 if ($result < 0) {
953 $this->db->rollback();
954 return -1;
955 }
956 }
957
958 $newcomb->fk_product_child = $newproduct->id;
959
960 if ($newcomb->update($user) < 0) {
961 $this->error = $newcomb->error;
962 $this->errors = $newcomb->errors;
963 $this->db->rollback();
964 return -1;
965 }
966
967 $this->db->commit();
968 return $newproduct->id;
969 }
970
979 public function copyAll(User $user, $origProductId, Product $destProduct)
980 {
981 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
982
983 //To prevent a loop
984 if ($origProductId == $destProduct->id) {
985 return -1;
986 }
987
988 $prodcomb2val = new ProductCombination2ValuePair($this->db);
989
990 //Retrieve all product combinations
991 $combinations = $this->fetchAllByFkProductParent($origProductId);
992
993 foreach ($combinations as $combination) {
994 $variations = array();
995
996 foreach ($prodcomb2val->fetchByFkCombination($combination->id) as $tmp_pc2v) {
997 $variations[$tmp_pc2v->fk_prod_attr] = $tmp_pc2v->fk_prod_attr_val;
998 }
999
1000 if ($this->createProductCombination(
1001 $user,
1002 $destProduct,
1003 $variations,
1004 array(),
1005 $combination->variation_price_percentage,
1006 $combination->variation_price,
1007 $combination->variation_weight
1008 ) < 0) {
1009 return -1;
1010 }
1011 }
1012
1013 return 1;
1014 }
1015
1021 public function getCombinationLabel($prod_child)
1022 {
1023 $label = '';
1024 $sql = 'SELECT pav.value AS label';
1025 $sql .= ' FROM '.MAIN_DB_PREFIX.'product_attribute_combination pac';
1026 $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_attribute_combination2val pac2v ON pac2v.fk_prod_combination=pac.rowid';
1027 $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_attribute_value pav ON pav.rowid=pac2v.fk_prod_attr_val';
1028 $sql .= ' WHERE pac.fk_product_child='.((int) $prod_child);
1029
1030 $resql = $this->db->query($sql);
1031 if ($resql) {
1032 $num = $this->db->num_rows($resql);
1033
1034 $i = 0;
1035
1036 while ($i < $num) {
1037 $obj = $this->db->fetch_object($resql);
1038
1039 if ($obj->label) {
1040 $label .= ' '.$obj->label;
1041 }
1042 $i++;
1043 }
1044 }
1045 return $label;
1046 }
1047}
1048
1049
1050
1056{
1061 public $db;
1062
1066 public $table_element = 'product_attribute_combination_price_level';
1067
1072 public $id;
1073
1078 public $fk_product_attribute_combination;
1079
1084 public $fk_price_level;
1085
1090 public $variation_price;
1091
1096 public $variation_price_percentage = false;
1097
1101 public $error;
1102
1106 public $errors = array();
1107
1113 public function __construct(DoliDB $db)
1114 {
1115 $this->db = $db;
1116 }
1117
1124 public function fetch($rowid)
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 rowid = ".(int) $rowid;
1129
1130 $resql = $this->db->query($sql);
1131 if ($resql) {
1132 $obj = $this->db->fetch_object($resql);
1133 if ($obj) {
1134 return $this->fetchFormObj($obj);
1135 }
1136 }
1137
1138 return -1;
1139 }
1140
1141
1149 public function fetchAll($fk_product_attribute_combination, $fk_price_level = 0)
1150 {
1151 $result = array();
1152
1153 $sql = "SELECT rowid, fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
1154 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
1155 $sql .= " WHERE fk_product_attribute_combination = ".intval($fk_product_attribute_combination);
1156 if (!empty($fk_price_level)) {
1157 $sql .= ' AND fk_price_level = '.intval($fk_price_level);
1158 }
1159
1160 $res = $this->db->query($sql);
1161 if ($res) {
1162 if ($this->db->num_rows($res) > 0) {
1163 while ($obj = $this->db->fetch_object($res)) {
1164 $productCombinationLevel = new ProductCombinationLevel($this->db);
1165 $productCombinationLevel->fetchFormObj($obj);
1166 $result[$obj->fk_price_level] = $productCombinationLevel;
1167 }
1168 }
1169 } else {
1170 return -1;
1171 }
1172
1173 return $result;
1174 }
1175
1182 public function fetchFormObj($obj)
1183 {
1184 if (!$obj) {
1185 return -1;
1186 }
1187
1188 $this->id = $obj->rowid;
1189 $this->fk_product_attribute_combination = (int) $obj->fk_product_attribute_combination;
1190 $this->fk_price_level = intval($obj->fk_price_level);
1191 $this->variation_price = (float) $obj->variation_price;
1192 $this->variation_price_percentage = (bool) $obj->variation_price_percentage;
1193
1194 return 1;
1195 }
1196
1197
1203 public function save()
1204 {
1205 if (($this->id > 0 && empty($this->fk_product_attribute_combination)) || empty($this->fk_price_level)) {
1206 return -1;
1207 }
1208
1209 // Check if level exist in DB before add
1210 if ($this->fk_product_attribute_combination > 0 && empty($this->id)) {
1211 $sql = "SELECT rowid id";
1212 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
1213 $sql .= " WHERE fk_product_attribute_combination = ".(int) $this->fk_product_attribute_combination;
1214 $sql .= ' AND fk_price_level = '.((int) $this->fk_price_level);
1215
1216 $resql = $this->db->query($sql);
1217 if ($resql) {
1218 $obj = $this->db->fetch_object($resql);
1219 if ($obj) {
1220 $this->id = $obj->id;
1221 }
1222 }
1223 }
1224
1225 // Update
1226 if (!empty($this->id)) {
1227 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1228 $sql .= ' SET variation_price = '.(float) $this->variation_price.' , variation_price_percentage = '.intval($this->variation_price_percentage);
1229 $sql .= ' WHERE rowid = '.((int) $this->id);
1230
1231 $res = $this->db->query($sql);
1232 if ($res > 0) {
1233 return $this->id;
1234 } else {
1235 $this->error = $this->db->error();
1236 $this->errors[] = $this->error;
1237 return -1;
1238 }
1239 } else {
1240 // Add
1241 $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
1242 $sql .= "fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
1243 $sql .= ") VALUES (";
1244 $sql .= (int) $this->fk_product_attribute_combination;
1245 $sql .= ", ".intval($this->fk_price_level);
1246 $sql .= ", ".(float) $this->variation_price;
1247 $sql .= ", ".intval($this->variation_price_percentage);
1248 $sql .= ")";
1249
1250 $res = $this->db->query($sql);
1251 if ($res) {
1252 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
1253 } else {
1254 $this->error = $this->db->error();
1255 $this->errors[] = $this->error;
1256 return -1;
1257 }
1258 }
1259
1260 return $this->id;
1261 }
1262
1263
1269 public function delete()
1270 {
1271 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".(int) $this->id;
1272 $res = $this->db->query($sql);
1273
1274 return $res ? 1 : -1;
1275 }
1276
1277
1284 public function deleteAllForCombination($fk_product_attribute_combination)
1285 {
1286 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE fk_product_attribute_combination = ".(int) $fk_product_attribute_combination;
1287 $res = $this->db->query($sql);
1288
1289 return $res ? 1 : -1;
1290 }
1291
1292
1299 public function clean($fk_product_attribute_combination)
1300 {
1301 global $conf;
1302
1303 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element;
1304 $sql .= " WHERE fk_product_attribute_combination = ".(int) $fk_product_attribute_combination;
1305 $sql .= " AND fk_price_level > ".(int) getDolGlobalString('PRODUIT_MULTIPRICES_LIMIT');
1306 $res = $this->db->query($sql);
1307
1308 return $res ? 1 : -1;
1309 }
1310
1311
1320 public static function createFromParent(DoliDB $db, ProductCombination $productCombination, $fkPriceLevel)
1321 {
1322 $productCombinationLevel = new self($db);
1323 $productCombinationLevel->fk_price_level = $fkPriceLevel;
1324 $productCombinationLevel->fk_product_attribute_combination = $productCombination->id;
1325 $productCombinationLevel->variation_price = $productCombination->variation_price;
1326 $productCombinationLevel->variation_price_percentage = (bool) $productCombination->variation_price_percentage;
1327
1328 return $productCombinationLevel;
1329 }
1330}
Class to manage Dolibarr database access.
Class ProductAttribute Used to represent a Product attribute Examples:
Class ProductAttributeValue Used to represent a product attribute value.
Class ProductCombination2ValuePair Used to represent the relation between a variant and its attribute...
Class ProductCombination Used to represent the relation between a product and one of its variants.
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 (filtered on its 'to sell' variants)
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.
fetchAllByFkProductParent($fk_product_parent, $sort_by_ref=false)
Retrieves all product combinations by the product parent row id.
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 ProductCombination 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 '.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.