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 // Check cache
177 if (!empty($this->combination_price_levels) && $useCache) {
178 if ((!empty($fk_price_level) && isset($this->combination_price_levels[$fk_price_level])) || empty($fk_price_level)) {
179 return 1;
180 }
181 }
182
183 if (!is_array($this->combination_price_levels)
184 || empty($fk_price_level) // if fetch an unique level don't erase all already fetched
185 ) {
186 $this->combination_price_levels = array();
187 }
188
189 $staticProductCombinationLevel = new ProductCombinationLevel($this->db);
190 $combination_price_levels = $staticProductCombinationLevel->fetchAll($this->id, $fk_price_level);
191
192 if (!is_array($combination_price_levels)) {
193 return -1;
194 }
195
196 if (empty($combination_price_levels)) {
200 if ($fk_price_level > 0) {
201 $combination_price_levels[$fk_price_level] = ProductCombinationLevel::createFromParent($this->db, $this, $fk_price_level);
202 } else {
203 $produit_multiprices_limit = getDolGlobalString('PRODUIT_MULTIPRICES_LIMIT');
204 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
205 $combination_price_levels[$i] = ProductCombinationLevel::createFromParent($this->db, $this, $i);
206 }
207 }
208 }
209
210 $this->combination_price_levels = $combination_price_levels;
211
212 return 1;
213 }
214
221 public function saveCombinationPriceLevels($clean = 1)
222 {
223 global $conf;
224
225 $error = 0;
226
227 $staticProductCombinationLevel = new ProductCombinationLevel($this->db);
228
229 // Delete all
230 if (empty($this->combination_price_levels)) {
231 return $staticProductCombinationLevel->deleteAllForCombination($this->id);
232 }
233
234 // Clean not needed price levels (level higher than number max defined into setup)
235 if ($clean) {
236 $res = $staticProductCombinationLevel->clean($this->id);
237 if ($res < 0) {
238 $this->errors[] = 'Fail to clean not needed price levels';
239 return -1;
240 }
241 }
242
243 foreach ($this->combination_price_levels as $fk_price_level => $combination_price_level) {
244 $res = $combination_price_level->save();
245 if ($res < 1) {
246 $this->error = 'Error saving combination price level '.$fk_price_level.' : '.$combination_price_level->error;
247 $this->errors[] = $this->error;
248 $error++;
249 break;
250 }
251 }
252
253 if ($error) {
254 return $error * -1;
255 } else {
256 return 1;
257 }
258 }
259
267 public function fetchByFkProductChild($productid, $donotloadpricelevel = 0)
268 {
269 global $conf;
270
271 $sql = "SELECT rowid, fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight";
272 $sql .= " FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE fk_product_child = ".((int) $productid)." AND entity IN (".getEntity('product').")";
273
274 $query = $this->db->query($sql);
275
276 if (!$query) {
277 return -1;
278 }
279
280 if (!$this->db->num_rows($query)) {
281 return 0;
282 }
283
284 $result = $this->db->fetch_object($query);
285
286 $this->id = $result->rowid;
287 $this->fk_product_parent = $result->fk_product_parent;
288 $this->fk_product_child = $result->fk_product_child;
289 $this->variation_price = $result->variation_price;
290 $this->variation_price_percentage = $result->variation_price_percentage;
291 $this->variation_weight = $result->variation_weight;
292
293 if (empty($donotloadpricelevel) && getDolGlobalString('PRODUIT_MULTIPRICES')) {
295 }
296
297 return (int) $this->fk_product_parent;
298 }
299
307 public function fetchAllByFkProductParent($fk_product_parent, $sort_by_ref = false)
308 {
309 global $conf;
310
311 $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";
312 $sql .= " FROM ".MAIN_DB_PREFIX."product_attribute_combination AS pac";
313 if ($sort_by_ref) {
314 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product AS p ON p.rowid = pac.fk_product_child";
315 }
316 $sql .= " WHERE pac.fk_product_parent = ".((int) $fk_product_parent)." AND pac.entity IN (".getEntity('product').")";
317 if ($sort_by_ref) {
318 $sql .= $this->db->order('p.ref', 'ASC');
319 }
320
321 $query = $this->db->query($sql);
322
323 if (!$query) {
324 return -1;
325 }
326
327 $return = array();
328
329 while ($result = $this->db->fetch_object($query)) {
330 $tmp = new ProductCombination($this->db);
331 $tmp->id = $result->rowid;
332 $tmp->fk_product_parent = $result->fk_product_parent;
333 $tmp->fk_product_child = $result->fk_product_child;
334 $tmp->variation_price = $result->variation_price;
335 $tmp->variation_price_percentage = $result->variation_price_percentage;
336 $tmp->variation_weight = $result->variation_weight;
337 $tmp->variation_ref_ext = $result->variation_ref_ext;
338
339 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
340 $tmp->fetchCombinationPriceLevels();
341 }
342
343 $return[] = $tmp;
344 }
345
346 return $return;
347 }
348
355 public function countNbOfCombinationForFkProductParent($fk_product_parent)
356 {
357 $nb = 0;
358 $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').")";
359
360 $resql = $this->db->query($sql);
361 if ($resql) {
362 $obj = $this->db->fetch_object($resql);
363 if ($obj) {
364 $nb = $obj->nb;
365 }
366 }
367
368 return $nb;
369 }
370
377 public function create($user)
378 {
379 global $conf;
380
381 /* $this->fk_product_child may be empty and will be filled later after subproduct has been created */
382
383 $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_attribute_combination";
384 $sql .= " (fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight, variation_ref_ext, entity)";
385 $sql .= " VALUES (".((int) $this->fk_product_parent).", ".((int) $this->fk_product_child).",";
386 $sql .= (float) $this->variation_price.", ".(int) $this->variation_price_percentage.",";
387 $sql .= (float) $this->variation_weight.", '".$this->db->escape($this->variation_ref_ext)."', ".(int) $this->entity.")";
388
389 $resql = $this->db->query($sql);
390 if ($resql) {
391 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'product_attribute_combination');
392 } else {
393 $this->error = $this->db->lasterror();
394 return -1;
395 }
396
397 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
398 $res = $this->saveCombinationPriceLevels();
399 if ($res < 0) {
400 return -2;
401 }
402 }
403
404 return 1;
405 }
406
413 public function update(User $user)
414 {
415 global $conf;
416
417 $sql = "UPDATE ".MAIN_DB_PREFIX."product_attribute_combination";
418 $sql .= " SET fk_product_parent = ".(int) $this->fk_product_parent.", fk_product_child = ".(int) $this->fk_product_child.",";
419 $sql .= " variation_price = ".(float) $this->variation_price.", variation_price_percentage = ".(int) $this->variation_price_percentage.",";
420 $sql .= " variation_ref_ext = '".$this->db->escape($this->variation_ref_ext)."',";
421 $sql .= " variation_weight = ".(float) $this->variation_weight." WHERE rowid = ".((int) $this->id);
422
423 $resql = $this->db->query($sql);
424 if (!$resql) {
425 return -1;
426 }
427
428 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
429 $res = $this->saveCombinationPriceLevels();
430 if ($res < 0) {
431 return -2;
432 }
433 }
434
435 $parent = new Product($this->db);
436 $parent->fetch($this->fk_product_parent);
437
438 $this->updateProperties($parent, $user);
439
440 return 1;
441 }
442
449 public function delete(User $user)
450 {
451 $this->db->begin();
452
453 $comb2val = new ProductCombination2ValuePair($this->db);
454 $comb2val->deleteByFkCombination($this->id);
455
456 // remove combination price levels
457 if (!$this->db->query("DELETE FROM ".MAIN_DB_PREFIX."product_attribute_combination_price_level WHERE fk_product_attribute_combination = ".(int) $this->id)) {
458 $this->db->rollback();
459 return -1;
460 }
461
462 $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE rowid = ".(int) $this->id;
463
464 if ($this->db->query($sql)) {
465 $this->db->commit();
466 return 1;
467 }
468
469 $this->db->rollback();
470 return -1;
471 }
472
480 public function deleteByFkProductParent($user, $fk_product_parent)
481 {
482 $this->db->begin();
483
484 foreach ($this->fetchAllByFkProductParent($fk_product_parent) as $prodcomb) {
485 $prodstatic = new Product($this->db);
486
487 $res = $prodstatic->fetch($prodcomb->fk_product_child);
488
489 if ($res > 0) {
490 $res = $prodcomb->delete($user);
491 }
492
493 if ($res > 0 && !$prodstatic->isObjectUsed($prodstatic->id)) {
494 $res = $prodstatic->delete($user);
495 }
496
497 if ($res < 0) {
498 $this->db->rollback();
499 return -1;
500 }
501 }
502
503 $this->db->commit();
504 return 1;
505 }
506
515 public function updateProperties(Product $parent, User $user)
516 {
517 global $conf;
518
519 $this->db->begin();
520
521 $child = new Product($this->db);
522 $child->fetch($this->fk_product_child);
523
524 $child->price_autogen = $parent->price_autogen;
525 $child->weight = $parent->weight;
526 // Only when Parent Status are updated
527 if (is_object($parent->oldcopy) && !$parent->oldcopy->isEmpty() && ($parent->status != $parent->oldcopy->status)) {
528 $child->status = $parent->status;
529 }
530 if (is_object($parent->oldcopy) && !$parent->oldcopy->isEmpty() && ($parent->status_buy != $parent->oldcopy->status_buy)) {
531 $child->status_buy = $parent->status_buy;
532 }
533
534 if ($this->variation_weight) { // If we must add a delta on weight
535 $child->weight = ($child->weight ? $child->weight : 0) + $this->variation_weight;
536 }
537 $child->weight_units = $parent->weight_units;
538
539 // Don't update the child label if the user has already modified it.
540 if ($child->label == $parent->label) {
541 // This will trigger only at variant creation time
542 $varlabel = $this->getCombinationLabel($this->fk_product_child);
543 $child->label = $parent->label.$varlabel;
544 }
545
546
547 if ($child->update($child->id, $user) > 0) {
548 $new_vat = $parent->tva_tx;
549 $new_npr = $parent->tva_npr;
550
551 // MultiPrix
552 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
553 $produit_multiprices_limit = getDolGlobalString('PRODUIT_MULTIPRICES_LIMIT');
554 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
555 if ($parent->multiprices[$i] != '' || isset($this->combination_price_levels[$i]->variation_price)) {
556 $new_type = empty($parent->multiprices_base_type[$i]) ? 'HT' : $parent->multiprices_base_type[$i];
557 $new_min_price = $parent->multiprices_min[$i];
558 $variation_price = (float) (!isset($this->combination_price_levels[$i]->variation_price) ? $this->variation_price : $this->combination_price_levels[$i]->variation_price);
559 $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);
560
561 if ($parent->prices_by_qty_list[$i]) {
562 $new_psq = 1;
563 } else {
564 $new_psq = 0;
565 }
566
567 if ($new_type == 'TTC') {
568 $new_price = $parent->multiprices_ttc[$i];
569 } else {
570 $new_price = $parent->multiprices[$i];
571 }
572
573 if ($variation_price_percentage) {
574 if ($new_price != 0) {
575 $new_price *= 1 + ($variation_price / 100);
576 }
577 } else {
578 $new_price += $variation_price;
579 }
580
581 $ret = $child->updatePrice($new_price, $new_type, $user, $new_vat, $new_min_price, $i, $new_npr, $new_psq, 0, array(), $parent->default_vat_code);
582
583 if ($ret < 0) {
584 $this->db->rollback();
585 $this->error = $child->error;
586 $this->errors = $child->errors;
587 return $ret;
588 }
589 }
590 }
591 } else {
592 $new_type = $parent->price_base_type;
593 $new_min_price = $parent->price_min;
594 $new_psq = $parent->price_by_qty;
595
596 if ($new_type == 'TTC') {
597 $new_price = $parent->price_ttc;
598 } else {
599 $new_price = $parent->price;
600 }
601
602 if ($this->variation_price_percentage) {
603 if ($new_price != 0) {
604 $new_price *= 1 + ($this->variation_price / 100);
605 }
606 } else {
607 $new_price += $this->variation_price;
608 }
609
610 $ret = $child->updatePrice($new_price, $new_type, $user, $new_vat, $new_min_price, 1, $new_npr, $new_psq);
611
612 if ($ret < 0) {
613 $this->db->rollback();
614 $this->error = $child->error;
615 $this->errors = $child->errors;
616 return $ret;
617 }
618 }
619
620 $this->db->commit();
621
622 return 1;
623 }
624
625 $this->db->rollback();
626 $this->error = $child->error;
627 $this->errors = $child->errors;
628 return -1;
629 }
630
638 public function fetchByProductCombination2ValuePairs($prodid, array $features)
639 {
640 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
641
642 $actual_comp = array();
643
644 $prodcomb2val = new ProductCombination2ValuePair($this->db);
645 $prodcomb = new ProductCombination($this->db);
646
647 $features = array_filter(
648 $features,
653 static function ($v) {
654 return !empty($v);
655 }
656 );
657
658 foreach ($features as $attr => $attr_val) {
659 $actual_comp[$attr] = $attr_val;
660 }
661
662 foreach ($prodcomb->fetchAllByFkProductParent($prodid) as $prc) {
663 $values = array();
664
665 foreach ($prodcomb2val->fetchByFkCombination($prc->id) as $value) {
666 $values[$value->fk_prod_attr] = $value->fk_prod_attr_val;
667 }
668
669 $check1 = count(array_diff_assoc($values, $actual_comp));
670 $check2 = count(array_diff_assoc($actual_comp, $values));
671
672 if (!$check1 && !$check2) {
673 return $prc;
674 }
675 }
676
677 return false;
678 }
679
688 {
689 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
690 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
691
692 // Attributes
693 // Select all unique attributes of the variants (which are to sell) of a given parent product.
694 $sql = "SELECT DISTINCT c2v.fk_prod_attr, a.position";
695 $sql .= " FROM ".MAIN_DB_PREFIX."product_attribute_combination2val c2v";
696 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_attribute_combination c";
697 $sql .= " ON c2v.fk_prod_combination = c.rowid";
698 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product p";
699 $sql .= " ON p.rowid = c.fk_product_child";
700 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_attribute a";
701 $sql .= " ON a.rowid = fk_prod_attr";
702 $sql .= " WHERE c.fk_product_parent = ".((int) $productid);
703 $sql .= " AND p.tosell = 1";
704 $sql .= $this->db->order('a.position', 'asc');
705
706 $resql = $this->db->query($sql);
707
708 // Values
709 $variants = array();
710 while ($obj = $this->db->fetch_object($resql)) {
711 $attr = new ProductAttribute($this->db);
712 $attr->fetch($obj->fk_prod_attr);
713
714 $tmp = new stdClass();
715 $tmp->id = $attr->id;
716 $tmp->ref = $attr->ref;
717 $tmp->label = $attr->label;
718 $tmp->values = array();
719
720 $attrval = new ProductAttributeValue($this->db);
721 // fetch only the used values of this attribute
722 foreach ($attrval->fetchAllByProductAttribute($attr->id, true) as $val) {
723 '@phan-var-force ProductAttributeValue $val';
724 $tmp->values[] = $val;
725 }
726
727 $variants[] = $tmp;
728 }
729
730 return $variants;
731 }
732
756 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 = '')
757 {
758 global $conf;
759
760 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
761 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
762
763 $this->db->begin();
764
765 $price_impact = array(1 => 0); // init level price impact
766
767 $forced_refvar = trim((string) $forced_refvar);
768
769 if (!empty($forced_refvar) && $forced_refvar != $product->ref) {
770 $existingProduct = new Product($this->db);
771 $result = $existingProduct->fetch(0, $forced_refvar);
772 if ($result > 0) {
773 $newproduct = $existingProduct;
774 } else {
775 $existingProduct = false;
776 $newproduct = clone $product;
777 $newproduct->ref = $forced_refvar;
778 }
779 } else {
780 $forced_refvar = false;
781 $existingProduct = false;
782 $newproduct = clone $product;
783 }
784
785 //Final weight impact
786 $weight_impact = (float) $forced_weightvar; // If false, return 0
787
788 //Final price impact
789 if (!is_array($forced_pricevar)) {
790 $price_impact[1] = (float) $forced_pricevar; // If false, return 0
791 } else {
792 $price_impact = $forced_pricevar;
793 }
794
795 if (!array($price_var_percent)) {
796 $price_var_percent[1] = (float) $price_var_percent;
797 }
798
799 $newcomb = new ProductCombination($this->db);
800 $existingCombination = $newcomb->fetchByProductCombination2ValuePairs($product->id, $combinations);
801
802 if ($existingCombination) {
803 $newcomb = $existingCombination;
804 } else {
805 $newcomb->fk_product_parent = $product->id;
806
807 // Create 1 entry into product_attribute_combination (1 entry for each combinations). This init also $newcomb->id
808 $result = $newcomb->create($user);
809 if ($result < 0) {
810 $this->error = $newcomb->error;
811 $this->errors = $newcomb->errors;
812 $this->db->rollback();
813 return -1;
814 }
815 }
816
817 $prodattr = new ProductAttribute($this->db);
818 $prodattrval = new ProductAttributeValue($this->db);
819
820 // $combination contains list of attributes pairs key->value. Example: array('id Color'=>id Blue, 'id Size'=>id Small, 'id Option'=>id val a, ...)
821 foreach ($combinations as $currcombattr => $currcombval) {
822 //This was checked earlier, so no need to double check
823 $prodattr->fetch($currcombattr);
824 $prodattrval->fetch($currcombval);
825
826 //If there is an existing combination, there is no need to duplicate the valuepair
827 if (!$existingCombination) {
828 $tmp = new ProductCombination2ValuePair($this->db);
829 $tmp->fk_prod_attr = $currcombattr;
830 $tmp->fk_prod_attr_val = $currcombval;
831 $tmp->fk_prod_combination = $newcomb->id;
832
833 if ($tmp->create($user) < 0) { // Create 1 entry into product_attribute_combination2val
834 $this->error = $tmp->error;
835 $this->errors = $tmp->errors;
836 $this->db->rollback();
837 return -1;
838 }
839 }
840 if ($forced_weightvar === false) {
841 $weight_impact += (float) price2num($variations[$currcombattr][$currcombval]['weight']);
842 }
843 if ($forced_pricevar === false) {
844 $price_impact[1] += (float) price2num($variations[$currcombattr][$currcombval]['price']);
845
846 // Manage Price levels
847 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
848 $produit_multiprices_limit = getDolGlobalString('PRODUIT_MULTIPRICES_LIMIT');
849 for ($i = 2; $i <= $produit_multiprices_limit; $i++) {
850 $price_impact[$i] += (float) price2num($variations[$currcombattr][$currcombval]['price']);
851 }
852 }
853 }
854
855 if ($forced_refvar === false) {
856 if (isset($conf->global->PRODUIT_ATTRIBUTES_SEPARATOR)) {
857 $newproduct->ref .= getDolGlobalString('PRODUIT_ATTRIBUTES_SEPARATOR') . $prodattrval->ref;
858 } else {
859 $newproduct->ref .= '_'.$prodattrval->ref;
860 }
861 }
862
863 //The first one should not contain a linebreak
864 if ($newproduct->description) {
865 $newproduct->description .= '<br>';
866 }
867 $newproduct->description .= '<strong>'.$prodattr->label.':</strong> '.$prodattrval->value;
868 }
869
870 $newcomb->variation_price_percentage = $price_var_percent[1];
871 $newcomb->variation_price = $price_impact[1];
872 $newcomb->variation_weight = $weight_impact;
873 $newcomb->variation_ref_ext = $this->db->escape($ref_ext);
874
875 // Init price level
876 if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
877 $produit_multiprices_limit = getDolGlobalString('PRODUIT_MULTIPRICES_LIMIT');
878 for ($i = 1; $i <= $produit_multiprices_limit; $i++) {
879 $productCombinationLevel = new ProductCombinationLevel($this->db);
880 $productCombinationLevel->fk_product_attribute_combination = $newcomb->id;
881 $productCombinationLevel->fk_price_level = $i;
882 $productCombinationLevel->variation_price = $price_impact[$i];
883
884 if (is_array($price_var_percent)) {
885 $productCombinationLevel->variation_price_percentage = (empty($price_var_percent[$i]) ? false : $price_var_percent[$i]);
886 } else {
887 $productCombinationLevel->variation_price_percentage = $price_var_percent;
888 }
889
890 $newcomb->combination_price_levels[$i] = $productCombinationLevel;
891 }
892 }
893 //var_dump($newcomb->combination_price_levels);
894
895 $newproduct->weight += $weight_impact;
896
897 // Now create the product
898 //print 'Create prod '.$newproduct->ref.'<br>'."\n";
899 if ($existingProduct === false) {
900 //To avoid wrong information in price history log
901 $newproduct->price = 0;
902 $newproduct->price_ttc = 0;
903 $newproduct->price_min = 0;
904 $newproduct->price_min_ttc = 0;
905
906 // A new variant must use a new barcode (not same product)
907 $newproduct->barcode = -1;
908 $result = $newproduct->create($user);
909
910 if ($result < 0) {
911 //In case the error is not related with an already existing product
912 if ($newproduct->error != 'ErrorProductAlreadyExists') {
913 $this->error = $newproduct->error;
914 $this->errors = $newproduct->errors;
915 $this->db->rollback();
916 return -1;
917 }
918
924 if ($newcomb->fk_product_child) {
925 $res = $newproduct->fetch($existingCombination->fk_product_child);
926 } else {
927 $orig_prod_ref = $newproduct->ref;
928 $i = 1;
929
930 do {
931 $newproduct->ref = $orig_prod_ref.$i;
932 $res = $newproduct->create($user);
933
934 if ($newproduct->error != 'ErrorProductAlreadyExists') {
935 $this->errors[] = $newproduct->error;
936 break;
937 }
938
939 $i++;
940 } while ($res < 0);
941 }
942
943 if ($res < 0) {
944 $this->db->rollback();
945 return -1;
946 }
947 }
948 } else {
949 $result = $newproduct->update($newproduct->id, $user);
950 if ($result < 0) {
951 $this->db->rollback();
952 return -1;
953 }
954 }
955
956 $newcomb->fk_product_child = $newproduct->id;
957
958 if ($newcomb->update($user) < 0) {
959 $this->error = $newcomb->error;
960 $this->errors = $newcomb->errors;
961 $this->db->rollback();
962 return -1;
963 }
964
965 $this->db->commit();
966 return $newproduct->id;
967 }
968
977 public function copyAll(User $user, $origProductId, Product $destProduct)
978 {
979 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
980
981 //To prevent a loop
982 if ($origProductId == $destProduct->id) {
983 return -1;
984 }
985
986 $prodcomb2val = new ProductCombination2ValuePair($this->db);
987
988 //Retrieve all product combinations
989 $combinations = $this->fetchAllByFkProductParent($origProductId);
990
991 foreach ($combinations as $combination) {
992 $variations = array();
993
994 foreach ($prodcomb2val->fetchByFkCombination($combination->id) as $tmp_pc2v) {
995 $variations[$tmp_pc2v->fk_prod_attr] = $tmp_pc2v->fk_prod_attr_val;
996 }
997
998 if ($this->createProductCombination(
999 $user,
1000 $destProduct,
1001 $variations,
1002 array(),
1003 $combination->variation_price_percentage,
1004 $combination->variation_price,
1005 $combination->variation_weight
1006 ) < 0) {
1007 return -1;
1008 }
1009 }
1010
1011 return 1;
1012 }
1013
1019 public function getCombinationLabel($prod_child)
1020 {
1021 $label = '';
1022 $sql = 'SELECT pav.value AS label';
1023 $sql .= ' FROM '.MAIN_DB_PREFIX.'product_attribute_combination pac';
1024 $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_attribute_combination2val pac2v ON pac2v.fk_prod_combination=pac.rowid';
1025 $sql .= ' INNER JOIN '.MAIN_DB_PREFIX.'product_attribute_value pav ON pav.rowid=pac2v.fk_prod_attr_val';
1026 $sql .= ' WHERE pac.fk_product_child='.((int) $prod_child);
1027
1028 $resql = $this->db->query($sql);
1029 if ($resql) {
1030 $num = $this->db->num_rows($resql);
1031
1032 $i = 0;
1033
1034 while ($i < $num) {
1035 $obj = $this->db->fetch_object($resql);
1036
1037 if ($obj->label) {
1038 $label .= ' '.$obj->label;
1039 }
1040 $i++;
1041 }
1042 }
1043 return $label;
1044 }
1045}
1046
1047
1048
1054{
1059 public $db;
1060
1064 public $table_element = 'product_attribute_combination_price_level';
1065
1070 public $id;
1071
1076 public $fk_product_attribute_combination;
1077
1082 public $fk_price_level;
1083
1088 public $variation_price;
1089
1094 public $variation_price_percentage = false;
1095
1099 public $error;
1100
1104 public $errors = array();
1105
1111 public function __construct(DoliDB $db)
1112 {
1113 $this->db = $db;
1114 }
1115
1122 public function fetch($rowid)
1123 {
1124 $sql = "SELECT rowid, fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
1125 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
1126 $sql .= " WHERE rowid = ".(int) $rowid;
1127
1128 $resql = $this->db->query($sql);
1129 if ($resql) {
1130 $obj = $this->db->fetch_object($resql);
1131 if ($obj) {
1132 return $this->fetchFormObj($obj);
1133 }
1134 }
1135
1136 return -1;
1137 }
1138
1139
1147 public function fetchAll($fk_product_attribute_combination, $fk_price_level = 0)
1148 {
1149 $result = array();
1150
1151 $sql = "SELECT rowid, fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
1152 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
1153 $sql .= " WHERE fk_product_attribute_combination = ".intval($fk_product_attribute_combination);
1154 if (!empty($fk_price_level)) {
1155 $sql .= ' AND fk_price_level = '.intval($fk_price_level);
1156 }
1157
1158 $res = $this->db->query($sql);
1159 if ($res) {
1160 if ($this->db->num_rows($res) > 0) {
1161 while ($obj = $this->db->fetch_object($res)) {
1162 $productCombinationLevel = new ProductCombinationLevel($this->db);
1163 $productCombinationLevel->fetchFormObj($obj);
1164 $result[$obj->fk_price_level] = $productCombinationLevel;
1165 }
1166 }
1167 } else {
1168 return -1;
1169 }
1170
1171 return $result;
1172 }
1173
1180 public function fetchFormObj($obj)
1181 {
1182 if (!$obj) {
1183 return -1;
1184 }
1185
1186 $this->id = $obj->rowid;
1187 $this->fk_product_attribute_combination = (int) $obj->fk_product_attribute_combination;
1188 $this->fk_price_level = intval($obj->fk_price_level);
1189 $this->variation_price = (float) $obj->variation_price;
1190 $this->variation_price_percentage = (bool) $obj->variation_price_percentage;
1191
1192 return 1;
1193 }
1194
1195
1201 public function save()
1202 {
1203 if (($this->id > 0 && empty($this->fk_product_attribute_combination)) || empty($this->fk_price_level)) {
1204 return -1;
1205 }
1206
1207 // Check if level exist in DB before add
1208 if ($this->fk_product_attribute_combination > 0 && empty($this->id)) {
1209 $sql = "SELECT rowid id";
1210 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
1211 $sql .= " WHERE fk_product_attribute_combination = ".(int) $this->fk_product_attribute_combination;
1212 $sql .= ' AND fk_price_level = '.((int) $this->fk_price_level);
1213
1214 $resql = $this->db->query($sql);
1215 if ($resql) {
1216 $obj = $this->db->fetch_object($resql);
1217 if ($obj) {
1218 $this->id = $obj->id;
1219 }
1220 }
1221 }
1222
1223 // Update
1224 if (!empty($this->id)) {
1225 $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
1226 $sql .= ' SET variation_price = '.(float) $this->variation_price.' , variation_price_percentage = '.intval($this->variation_price_percentage);
1227 $sql .= ' WHERE rowid = '.((int) $this->id);
1228
1229 $res = $this->db->query($sql);
1230 if ($res > 0) {
1231 return $this->id;
1232 } else {
1233 $this->error = $this->db->error();
1234 $this->errors[] = $this->error;
1235 return -1;
1236 }
1237 } else {
1238 // Add
1239 $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
1240 $sql .= "fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
1241 $sql .= ") VALUES (";
1242 $sql .= (int) $this->fk_product_attribute_combination;
1243 $sql .= ", ".intval($this->fk_price_level);
1244 $sql .= ", ".(float) $this->variation_price;
1245 $sql .= ", ".intval($this->variation_price_percentage);
1246 $sql .= ")";
1247
1248 $res = $this->db->query($sql);
1249 if ($res) {
1250 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
1251 } else {
1252 $this->error = $this->db->error();
1253 $this->errors[] = $this->error;
1254 return -1;
1255 }
1256 }
1257
1258 return $this->id;
1259 }
1260
1261
1267 public function delete()
1268 {
1269 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".(int) $this->id;
1270 $res = $this->db->query($sql);
1271
1272 return $res ? 1 : -1;
1273 }
1274
1275
1282 public function deleteAllForCombination($fk_product_attribute_combination)
1283 {
1284 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE fk_product_attribute_combination = ".(int) $fk_product_attribute_combination;
1285 $res = $this->db->query($sql);
1286
1287 return $res ? 1 : -1;
1288 }
1289
1290
1297 public function clean($fk_product_attribute_combination)
1298 {
1299 global $conf;
1300
1301 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element;
1302 $sql .= " WHERE fk_product_attribute_combination = ".(int) $fk_product_attribute_combination;
1303 $sql .= " AND fk_price_level > ".(int) getDolGlobalString('PRODUIT_MULTIPRICES_LIMIT');
1304 $res = $this->db->query($sql);
1305
1306 return $res ? 1 : -1;
1307 }
1308
1309
1318 public static function createFromParent(DoliDB $db, ProductCombination $productCombination, $fkPriceLevel)
1319 {
1320 $productCombinationLevel = new self($db);
1321 $productCombinationLevel->fk_price_level = $fkPriceLevel;
1322 $productCombinationLevel->fk_product_attribute_combination = $productCombination->id;
1323 $productCombinationLevel->variation_price = $productCombination->variation_price;
1324 $productCombinationLevel->variation_price_percentage = (bool) $productCombination->variation_price_percentage;
1325
1326 return $productCombinationLevel;
1327 }
1328}
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.