?i»?
Current Path : /home/scgforma/www/soctest/htdocs/variants/class/ |
Current File : /home/scgforma/www/soctest/htdocs/variants/class/ProductCombination.class.php |
<?php /* Copyright (C) 2016 Marcos GarcĂa <marcosgdf@gmail.com> * Copyright (C) 2018 Juanjo Menent <jmenent@2byte.es> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ /** * Class ProductCombination * Used to represent a product combination */ class ProductCombination { /** * Database handler * @var DoliDB */ private $db; /** * Rowid of combination * @var int */ public $id; /** * Rowid of parent product * @var int */ public $fk_product_parent; /** * Rowid of child product * @var int */ public $fk_product_child; /** * Price variation * @var float */ public $variation_price; /** * Is the price variation a relative variation? * @var bool */ public $variation_price_percentage = false; /** * Weight variation * @var float */ public $variation_weight; /** * Combination entity * @var int */ public $entity; /** * Constructor * * @param DoliDB $db Database handler */ public function __construct(DoliDB $db) { global $conf; $this->db = $db; $this->entity = $conf->entity; } /** * Retrieves a combination by its rowid * * @param int $rowid Row id * @return int <0 KO, >0 OK */ public function fetch($rowid) { $sql = "SELECT rowid, fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE rowid = ".(int) $rowid." AND entity IN (".getEntity('product').")"; $query = $this->db->query($sql); if (!$query) { return -1; } if (!$this->db->num_rows($query)) { return -1; } $result = $this->db->fetch_object($query); $this->id = $result->rowid; $this->fk_product_parent = $result->fk_product_parent; $this->fk_product_child = $result->fk_product_child; $this->variation_price = $result->variation_price; $this->variation_price_percentage = $result->variation_price_percentage; $this->variation_weight = $result->variation_weight; return 1; } /** * Retrieves a product combination by a child product row id * * @param int $fk_child Product row id * @return int <0 KO, >0 OK */ public function fetchByFkProductChild($fk_child) { $sql = "SELECT rowid, fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE fk_product_child = ".(int) $fk_child." AND entity IN (".getEntity('product').")"; $query = $this->db->query($sql); if (!$query) { return -1; } if (!$this->db->num_rows($query)) { return -1; } $result = $this->db->fetch_object($query); $this->id = $result->rowid; $this->fk_product_parent = $result->fk_product_parent; $this->fk_product_child = $result->fk_product_child; $this->variation_price = $result->variation_price; $this->variation_price_percentage = $result->variation_price_percentage; $this->variation_weight = $result->variation_weight; return 1; } /** * Retrieves all product combinations by the product parent row id * * @param int $fk_product_parent Rowid of parent product * @return int|ProductCombination[] <0 KO */ public function fetchAllByFkProductParent($fk_product_parent) { $sql = "SELECT rowid, fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE fk_product_parent = ".(int) $fk_product_parent." AND entity IN (".getEntity('product').")"; $query = $this->db->query($sql); if (!$query) { return -1; } $return = array(); while ($result = $this->db->fetch_object($query)) { $tmp = new ProductCombination($this->db); $tmp->id = $result->rowid; $tmp->fk_product_parent = $result->fk_product_parent; $tmp->fk_product_child = $result->fk_product_child; $tmp->variation_price = $result->variation_price; $tmp->variation_price_percentage = $result->variation_price_percentage; $tmp->variation_weight = $result->variation_weight; $return[] = $tmp; } return $return; } /** * Retrieves all product combinations by the product parent row id * * @param int $fk_product_parent Id of parent product * @return int Nb of record */ public function countNbOfCombinationForFkProductParent($fk_product_parent) { $nb = 0; $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').")"; $resql = $this->db->query($sql); if ($resql) { $obj = $this->db->fetch_object($resql); if ($obj) $nb = $obj->nb; } return $nb; } /** * Creates a product attribute combination * * @param User $user Object user * @return int <0 if KO, >0 if OK */ public function create($user) { $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_attribute_combination (fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight, entity) VALUES (".(int) $this->fk_product_parent.", ".(int) $this->fk_product_child.", ".(float) $this->variation_price.", ".(int) $this->variation_price_percentage.", ".(float) $this->variation_weight.", ".(int) $this->entity.")"; $query = $this->db->query($sql); if (!$query) { return -1; } $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'product_attribute_combination'); return 1; } /** * Updates a product combination * * @param User $user Object user * @return int <0 KO, >0 OK */ public function update(User $user) { $sql = "UPDATE ".MAIN_DB_PREFIX."product_attribute_combination SET fk_product_parent = ".(int) $this->fk_product_parent.", fk_product_child = ".(int) $this->fk_product_child.", variation_price = ".(float) $this->variation_price.", variation_price_percentage = ".(int) $this->variation_price_percentage.", variation_weight = ".(float) $this->variation_weight." WHERE rowid = ".(int) $this->id; $query = $this->db->query($sql); if (!$query) { return -1; } $parent = new Product($this->db); $parent->fetch($this->fk_product_parent); $this->updateProperties($parent); return 1; } /** * Deletes a product combination * * @param User $user Object user * @return int <0 if KO, >0 if OK */ public function delete(User $user) { $this->db->begin(); $comb2val = new ProductCombination2ValuePair($this->db); $comb2val->deleteByFkCombination($this->id); $sql = "DELETE FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE rowid = ".(int) $this->id; if ($this->db->query($sql)) { $this->db->commit(); return 1; } $this->db->rollback(); return -1; } /** * Deletes all product combinations of a parent product * * @param User $user Object user * @param int $fk_product_parent Rowid of parent product * @return int <0 KO >0 OK */ public function deleteByFkProductParent($user, $fk_product_parent) { $this->db->begin(); foreach ($this->fetchAllByFkProductParent($fk_product_parent) as $prodcomb) { $prodstatic = new Product($this->db); $res = $prodstatic->fetch($prodcomb->fk_product_child); if ($res > 0) { $res = $prodcomb->delete($user); } if ($res > 0 && !$prodstatic->isObjectUsed($prodstatic->id)) { $res = $prodstatic->delete($user); } if ($res < 0) { $this->db->rollback(); return -1; } } $this->db->commit(); return 1; } /** * Updates the weight of the child product. The price must be updated using Product::updatePrices * * @param Product $parent Parent product * @return int >0 OK <0 KO */ public function updateProperties(Product $parent) { global $user, $conf; $this->db->begin(); $child = new Product($this->db); $child->fetch($this->fk_product_child); $child->price_autogen = $parent->price_autogen; $child->weight = $parent->weight + $this->variation_weight; $child->weight_units = $parent->weight_units; $varlabel = $this->getCombinationLabel($this->fk_product_child); $child->label = $parent->label.$varlabel; if ($child->update($child->id, $user) > 0) { $new_vat = $parent->tva_tx; $new_npr = $parent->tva_npr; // MultiPrix if (! empty($conf->global->PRODUIT_MULTIPRICES)) { for ($i=1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) { if ($parent->multiprices[$i] != '') { $new_type = $parent->multiprices_base_type[$i]; $new_min_price = $parent->multiprices_min[$i]; if ($parent->prices_by_qty_list[$i]) { $new_psq = 1; } else { $new_psq = 0; } if ($new_type == 'TTC') { $new_price = $parent->multiprices_ttc[$i]; } else { $new_price = $parent->multiprices[$i]; } if ($this->variation_price_percentage) { if ($new_price != 0) { $new_price *= 1 + ($this->variation_price / 100); } } else { $new_price += $this->variation_price; } $child->updatePrice($new_price, $new_type, $user, $new_vat, $new_min_price, $i, $new_npr, $new_psq); } } } else { $new_type = $parent->price_base_type; $new_min_price = $parent->price_min; $new_psq = $parent->price_by_qty; if ($new_type == 'TTC') { $new_price = $parent->price_ttc; } else { $new_price = $parent->price; } if ($this->variation_price_percentage) { if ($new_price != 0) { $new_price *= 1 + ($this->variation_price / 100); } } else { $new_price += $this->variation_price; } $child->updatePrice($new_price, $new_type, $user, $new_vat, $new_min_price, 1, $new_npr, $new_psq); } $this->db->commit(); return 1; } $this->db->rollback(); return -1; } /** * Retrieves the combination that matches the given features. * * @param int $prodid Id of parent product * @param array $features Format: [$attr] => $attr_val * @return false|ProductCombination False if not found */ public function fetchByProductCombination2ValuePairs($prodid, array $features) { require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php'; $actual_comp = array(); $prodcomb2val = new ProductCombination2ValuePair($this->db); $prodcomb = new ProductCombination($this->db); foreach ($features as $attr => $attr_val) { $actual_comp[$attr] = $attr_val; } foreach ($prodcomb->fetchAllByFkProductParent($prodid) as $prc) { $values = array(); foreach ($prodcomb2val->fetchByFkCombination($prc->id) as $value) { $values[$value->fk_prod_attr] = $value->fk_prod_attr_val; } $check1 = count(array_diff_assoc($values, $actual_comp)); $check2 = count(array_diff_assoc($actual_comp, $values)); if (!$check1 && !$check2) { return $prc; } } return false; } /** * Retrieves all unique attributres for a parent product * * @param int $productid Product rowid * @return ProductAttribute[] */ public function getUniqueAttributesAndValuesByFkProductParent($productid) { require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php'; require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php'; $variants = array(); //Attributes $sql = "SELECT DISTINCT fk_prod_attr, a.rang FROM ".MAIN_DB_PREFIX."product_attribute_combination2val c2v LEFT JOIN ".MAIN_DB_PREFIX."product_attribute_combination c ON c2v.fk_prod_combination = c.rowid LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = c.fk_product_child LEFT JOIN ".MAIN_DB_PREFIX."product_attribute a ON a.rowid = fk_prod_attr WHERE c.fk_product_parent = ".(int) $productid." AND p.tosell = 1"; $sql .= $this->db->order('a.rang', 'asc'); $query = $this->db->query($sql); //Values while ($result = $this->db->fetch_object($query)) { $attr = new ProductAttribute($this->db); $attr->fetch($result->fk_prod_attr); $tmp = new stdClass(); $tmp->id = $attr->id; $tmp->ref = $attr->ref; $tmp->label = $attr->label; $tmp->values = array(); $attrval = new ProductAttributeValue($this->db); foreach ($res = $attrval->fetchAllByProductAttribute($attr->id, true) as $val) { $tmp->values[] = $val; } $variants[] = $tmp; } return $variants; } /** * Creates a product combination. Check usages to find more about its use * * Format of $combinations array: * array( * 0 => array( * attr => value, * attr2 => value * [...] * ), * [...] * ) * * @param Product $product Parent product * @param array $combinations Attribute and value combinations. * @param array $variations Price and weight variations * @param bool $price_var_percent Is the price variation a relative variation? * @param bool|float $forced_pricevar If the price variation is forced * @param bool|float $forced_weightvar If the weight variation is forced * @return int <0 KO, >0 OK */ public function createProductCombination(Product $product, array $combinations, array $variations, $price_var_percent = false, $forced_pricevar = false, $forced_weightvar = false) { global $db, $user, $conf; require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php'; require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php'; $db->begin(); $newproduct = clone $product; //Final weight impact $weight_impact = $forced_weightvar; if ($forced_weightvar === false) { $weight_impact = 0; } //Final price impact $price_impact = $forced_pricevar; if ($forced_pricevar === false) { $price_impact = 0; } $newcomb = new ProductCombination($db); $existingCombination = $newcomb->fetchByProductCombination2ValuePairs($product->id, $combinations); if ($existingCombination) { $newcomb = $existingCombination; } else { $newcomb->fk_product_parent = $product->id; if ($newcomb->create($user) < 0) { // Create 1 entry into product_attribute_combination (1 entry for all combinations) $db->rollback(); return -1; } } $prodattr = new ProductAttribute($db); $prodattrval = new ProductAttributeValue($db); // $combination contains list of attributes pairs key->value. Example: array('id Color'=>id Blue, 'id Size'=>id Small, 'id Option'=>id val a, ...) //var_dump($combinations); foreach ($combinations as $currcombattr => $currcombval) { //This was checked earlier, so no need to double check $prodattr->fetch($currcombattr); $prodattrval->fetch($currcombval); //If there is an existing combination, there is no need to duplicate the valuepair if (!$existingCombination) { $tmp = new ProductCombination2ValuePair($db); $tmp->fk_prod_attr = $currcombattr; $tmp->fk_prod_attr_val = $currcombval; $tmp->fk_prod_combination = $newcomb->id; if ($tmp->create($user) < 0) { // Create 1 entry into product_attribute_combination2val $db->rollback(); return -1; } } if ($forced_weightvar === false) { $weight_impact += (float) price2num($variations[$currcombattr][$currcombval]['weight']); } if ($forced_pricevar === false) { $price_impact += (float) price2num($variations[$currcombattr][$currcombval]['price']); } if (isset($conf->global->PRODUIT_ATTRIBUTES_SEPARATOR)) { $newproduct->ref .= $conf->global->PRODUIT_ATTRIBUTES_SEPARATOR . $prodattrval->ref; } else { $newproduct->ref .= '_'.$prodattrval->ref; } //The first one should not contain a linebreak if ($newproduct->description) { $newproduct->description .= '<br>'; } $newproduct->description .= '<strong>'.$prodattr->label.':</strong> '.$prodattrval->value; } $newcomb->variation_price_percentage = $price_var_percent; $newcomb->variation_price = $price_impact; $newcomb->variation_weight = $weight_impact; $newproduct->weight += $weight_impact; //To avoid wrong information in price history log $newproduct->price = 0; $newproduct->price_ttc = 0; $newproduct->price_min = 0; $newproduct->price_min_ttc = 0; // A new variant must use a new barcode (not same product) $newproduct->barcode = -1; // Now create the product //print 'Create prod '.$newproduct->ref.'<br>'."\n"; $newprodid = $newproduct->create($user); if ($newprodid < 0) { //In case the error is not related with an already existing product if ($newproduct->error != 'ErrorProductAlreadyExists') { $this->error[] = $newproduct->error; $this->errors = $newproduct->errors; $db->rollback(); return -1; } /** * If there is an existing combination, then we update the prices and weight * Otherwise, we try adding a random number to the ref */ if ($newcomb->fk_product_child) { $res = $newproduct->fetch($existingCombination->fk_product_child); } else { $orig_prod_ref = $newproduct->ref; $i = 1; do { $newproduct->ref = $orig_prod_ref.$i; $res = $newproduct->create($user); if ($newproduct->error != 'ErrorProductAlreadyExists') { $this->errors[] = $newproduct->error; break; } $i++; } while ($res < 0); } if ($res < 0) { $db->rollback(); return -1; } $newproduct->weight += $weight_impact; } $newcomb->fk_product_child = $newproduct->id; if ($newcomb->update($user) < 0) { $db->rollback(); return -1; } $db->commit(); return $newproduct->id; } /** * Copies all product combinations from the origin product to the destination product * * @param int $origProductId Origin product id * @param Product $destProduct Destination product * @return int >0 OK <0 KO */ public function copyAll($origProductId, Product $destProduct) { require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php'; //To prevent a loop if ($origProductId == $destProduct->id) { return -1; } $prodcomb2val = new ProductCombination2ValuePair($this->db); //Retrieve all product combinations $combinations = $this->fetchAllByFkProductParent($origProductId); foreach ($combinations as $combination) { $variations = array(); foreach ($prodcomb2val->fetchByFkCombination($combination->id) as $tmp_pc2v) { $variations[$tmp_pc2v->fk_prod_attr] = $tmp_pc2v->fk_prod_attr_val; } if ($this->createProductCombination( $destProduct, $variations, array(), $combination->variation_price_percentage, $combination->variation_price, $combination->variation_weight ) < 0) { return -1; } } return 1; } /** * Return label for combinations * @param int $prod_child id of child * @return string combination label */ public function getCombinationLabel($prod_child) { $label = ''; $sql = 'SELECT pav.value AS label'; $sql.= ' FROM '.MAIN_DB_PREFIX.'product_attribute_combination pac'; $sql.= ' INNER JOIN '.MAIN_DB_PREFIX.'product_attribute_combination2val pac2v ON pac2v.fk_prod_combination=pac.rowid'; $sql.= ' INNER JOIN '.MAIN_DB_PREFIX.'product_attribute_value pav ON pav.rowid=pac2v.fk_prod_attr_val'; $sql.= ' WHERE pac.fk_product_child='.$prod_child; $resql = $this->db->query($sql); if ($resql) { $num = $this->db->num_rows($resql); $i = 0; while ($i < $num) { $obj = $this->db->fetch_object($resql); if ($obj->label) { $label.=' '.$obj->label; } $i++; } } return $label; } }