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