| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268 |
- <?php
- /* Copyright (C) 2016 Marcos García <marcosgdf@gmail.com>
- * Copyright (C) 2018 Juanjo Menent <jmenent@2byte.es>
- * Copyright (C) 2022 Open-Dsi <support@open-dsi.fr>
- *
- * 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 <https://www.gnu.org/licenses/>.
- */
- /**
- * Class ProductCombination
- * Used to represent a product combination
- */
- class ProductCombination
- {
- /**
- * Database handler
- * @var DoliDB
- */
- public $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? Can be an array if multiprice feature per level is enabled.
- * @var bool|array
- */
- public $variation_price_percentage = false;
- /**
- * Weight variation
- * @var float
- */
- public $variation_weight;
- /**
- * Combination entity
- * @var int
- */
- public $entity;
- /**
- * Combination price level
- * @var ProductCombinationLevel[]
- */
- public $combination_price_levels;
- /**
- * External ref
- * @var string
- */
- public $variation_ref_ext = '';
- /**
- * 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)
- {
- global $conf;
- $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').")";
- $query = $this->db->query($sql);
- if (!$query) {
- return -1;
- }
- if (!$this->db->num_rows($query)) {
- return -1;
- }
- $obj = $this->db->fetch_object($query);
- $this->id = $obj->rowid;
- $this->fk_product_parent = $obj->fk_product_parent;
- $this->fk_product_child = $obj->fk_product_child;
- $this->variation_price = $obj->variation_price;
- $this->variation_price_percentage = $obj->variation_price_percentage;
- $this->variation_weight = $obj->variation_weight;
- $this->variation_ref_ext = $obj->variation_ref_ext;
- if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
- $this->fetchCombinationPriceLevels();
- }
- return 1;
- }
- /**
- * Retrieves combination price levels
- *
- * @param int $fk_price_level The price level to fetch, use 0 for all
- * @param bool $useCache To use cache or not
- * @return int <0 KO, >0 OK
- */
- public function fetchCombinationPriceLevels($fk_price_level = 0, $useCache = true)
- {
- global $conf;
- // Check cache
- if (!empty($this->combination_price_levels) && $useCache) {
- if ((!empty($fk_price_level) && isset($this->combination_price_levels[$fk_price_level])) || empty($fk_price_level)) {
- return 1;
- }
- }
- if (!is_array($this->combination_price_levels)
- || empty($fk_price_level) // if fetch an unique level dont erase all already fetched
- ) {
- $this->combination_price_levels = array();
- }
- $staticProductCombinationLevel = new ProductCombinationLevel($this->db);
- $combination_price_levels = $staticProductCombinationLevel->fetchAll($this->id, $fk_price_level);
- if (!is_array($combination_price_levels)) {
- return -1;
- }
- if (empty($combination_price_levels)) {
- /**
- * for auto retrocompatibility with last behavior
- */
- if ($fk_price_level > 0) {
- $combination_price_levels[$fk_price_level] = ProductCombinationLevel::createFromParent($this->db, $this, $fk_price_level);
- } else {
- for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
- $combination_price_levels[$i] = ProductCombinationLevel::createFromParent($this->db, $this, $i);
- }
- }
- }
- $this->combination_price_levels = $combination_price_levels;
- return 1;
- }
- /**
- * Retrieves combination price levels
- *
- * @param int $clean Levels of PRODUIT_MULTIPRICES_LIMIT
- * @return int <0 KO, >0 OK
- */
- public function saveCombinationPriceLevels($clean = 1)
- {
- global $conf;
- $error = 0;
- $staticProductCombinationLevel = new ProductCombinationLevel($this->db);
- // Delete all
- if (empty($this->combination_price_levels)) {
- return $staticProductCombinationLevel->deleteAllForCombination($this->id);
- }
- // Clean not needed price levels (level higher than number max defined into setup)
- if ($clean) {
- $res = $staticProductCombinationLevel->clean($this->id);
- if ($res < 0) {
- $this->errors[] = 'Fail to clean not needed price levels';
- return -1;
- }
- }
- foreach ($this->combination_price_levels as $fk_price_level => $combination_price_level) {
- $res = $combination_price_level->save();
- if ($res < 1) {
- $this->error = 'Error saving combination price level '.$fk_price_level.' : '.$combination_price_level->error;
- $this->errors[] = $this->error;
- $error++;
- break;
- }
- }
- if ($error) {
- return $error * -1;
- } else {
- return 1;
- }
- }
- /**
- * Retrieves information of a variant product and ID of its parent product.
- *
- * @param int $productid Product ID of variant
- * @param int $donotloadpricelevel Avoid loading price impact for each level. If PRODUIT_MULTIPRICES is not set, this has no effect.
- * @return int <0 if KO, 0 if product ID is not ID of a variant product (so parent not found), >0 if OK (ID of parent)
- */
- public function fetchByFkProductChild($productid, $donotloadpricelevel = 0)
- {
- global $conf;
- $sql = "SELECT rowid, fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight";
- $sql .= " FROM ".MAIN_DB_PREFIX."product_attribute_combination WHERE fk_product_child = ".((int) $productid)." AND entity IN (".getEntity('product').")";
- $query = $this->db->query($sql);
- if (!$query) {
- return -1;
- }
- if (!$this->db->num_rows($query)) {
- return 0;
- }
- $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;
- if (empty($donotloadpricelevel) && !empty($conf->global->PRODUIT_MULTIPRICES)) {
- $this->fetchCombinationPriceLevels();
- }
- return (int) $this->fk_product_parent;
- }
- /**
- * 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)
- {
- global $conf;
- $sql = "SELECT rowid, fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_ref_ext, variation_weight";
- $sql.= " FROM ".MAIN_DB_PREFIX."product_attribute_combination";
- $sql.= " 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;
- $tmp->variation_ref_ext = $result->variation_ref_ext;
- if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
- $tmp->fetchCombinationPriceLevels();
- }
- $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)
- {
- global $conf;
- /* $this->fk_product_child may be empty and will be filled later after subproduct has been created */
- $sql = "INSERT INTO ".MAIN_DB_PREFIX."product_attribute_combination";
- $sql .= " (fk_product_parent, fk_product_child, variation_price, variation_price_percentage, variation_weight, variation_ref_ext, entity)";
- $sql .= " VALUES (".((int) $this->fk_product_parent).", ".((int) $this->fk_product_child).",";
- $sql .= (float) $this->variation_price.", ".(int) $this->variation_price_percentage.",";
- $sql .= (float) $this->variation_weight.", '".$this->db->escape($this->variation_ref_ext)."', ".(int) $this->entity.")";
- $resql = $this->db->query($sql);
- if ($resql) {
- $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.'product_attribute_combination');
- } else {
- $this->error = $this->db->lasterror();
- return -1;
- }
- if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
- $res = $this->saveCombinationPriceLevels();
- if ($res < 0) {
- return -2;
- }
- }
- return 1;
- }
- /**
- * Updates a product combination
- *
- * @param User $user Object user
- * @return int <0 KO, >0 OK
- */
- public function update(User $user)
- {
- global $conf;
- $sql = "UPDATE ".MAIN_DB_PREFIX."product_attribute_combination";
- $sql .= " SET fk_product_parent = ".(int) $this->fk_product_parent.", fk_product_child = ".(int) $this->fk_product_child.",";
- $sql .= " variation_price = ".(float) $this->variation_price.", variation_price_percentage = ".(int) $this->variation_price_percentage.",";
- $sql .= " variation_ref_ext = '".$this->db->escape($this->variation_ref_ext)."',";
- $sql .= " variation_weight = ".(float) $this->variation_weight." WHERE rowid = ".((int) $this->id);
- $resql = $this->db->query($sql);
- if (!$resql) {
- return -1;
- }
- if (!empty($conf->global->PRODUIT_MULTIPRICES)) {
- $res = $this->saveCombinationPriceLevels();
- if ($res < 0) {
- return -2;
- }
- }
- $parent = new Product($this->db);
- $parent->fetch($this->fk_product_parent);
- $this->updateProperties($parent, $user);
- 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);
- // remove combination price levels
- if (!$this->db->query("DELETE FROM ".MAIN_DB_PREFIX."product_attribute_combination_price_level WHERE fk_product_attribute_combination = ".(int) $this->id)) {
- $this->db->rollback();
- return -1;
- }
- $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.
- * This method is called by the update() of a product.
- *
- * @param Product $parent Parent product
- * @param User $user Object user
- * @return int >0 if OK, <0 if KO
- */
- public function updateProperties(Product $parent, User $user)
- {
- global $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;
- // Only when Parent Status are updated
- if (!empty($parent->oldcopy) && ($parent->status != $parent->oldcopy->status)) {
- $child->status = $parent->status;
- }
- if (!empty($parent->oldcopy) && ($parent->status_buy != $parent->oldcopy->status_buy)) {
- $child->status_buy = $parent->status_buy;
- }
- if ($this->variation_weight) { // If we must add a delta on weight
- $child->weight = ($child->weight ? $child->weight : 0) + $this->variation_weight;
- }
- $child->weight_units = $parent->weight_units;
- // Don't update the child label if the user has already modified it.
- if ($child->label == $parent->label) {
- // This will trigger only at variant creation time
- $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] != '' || isset($this->combination_price_levels[$i]->variation_price)) {
- $new_type = empty($parent->multiprices_base_type[$i]) ? 'HT' : $parent->multiprices_base_type[$i];
- $new_min_price = $parent->multiprices_min[$i];
- $variation_price = floatval(!isset($this->combination_price_levels[$i]->variation_price) ? $this->variation_price : $this->combination_price_levels[$i]->variation_price);
- $variation_price_percentage = floatval(!isset($this->combination_price_levels[$i]->variation_price_percentage) ? $this->variation_price_percentage : $this->combination_price_levels[$i]->variation_price_percentage);
- 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 ($variation_price_percentage) {
- if ($new_price != 0) {
- $new_price *= 1 + ($variation_price / 100);
- }
- } else {
- $new_price += $variation_price;
- }
- $ret = $child->updatePrice($new_price, $new_type, $user, $new_vat, $new_min_price, $i, $new_npr, $new_psq, 0, array(), $parent->default_vat_code);
- if ($ret < 0) {
- $this->db->rollback();
- $this->error = $child->error;
- $this->errors = $child->errors;
- return $ret;
- }
- }
- }
- } 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;
- }
- $ret = $child->updatePrice($new_price, $new_type, $user, $new_vat, $new_min_price, 1, $new_npr, $new_psq);
- if ($ret < 0) {
- $this->db->rollback();
- $this->error = $child->error;
- $this->errors = $child->errors;
- return $ret;
- }
- }
- $this->db->commit();
- return 1;
- }
- $this->db->rollback();
- $this->error = $child->error;
- $this->errors = $child->errors;
- 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);
- $features = array_filter($features, function ($v) {
- return !empty($v);
- });
- 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 attributes for a parent product
- *
- * @param int $productid Product rowid
- * @return ProductAttribute[] Array of attributes
- */
- 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.position";
- $sql .= " FROM ".MAIN_DB_PREFIX."product_attribute_combination2val c2v LEFT JOIN ".MAIN_DB_PREFIX."product_attribute_combination c ON c2v.fk_prod_combination = c.rowid";
- $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product p ON p.rowid = c.fk_product_child";
- $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_attribute a ON a.rowid = fk_prod_attr";
- $sql .= " WHERE c.fk_product_parent = ".((int) $productid)." AND p.tosell = 1";
- $sql .= $this->db->order('a.position', '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 User $user Object user
- * @param Product $product Parent product
- * @param array $combinations Attribute and value combinations.
- * @param array $variations Price and weight variations
- * @param bool|array $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
- * @param bool|string $forced_refvar If the reference is forced
- * @param string $ref_ext External reference
- * @return int <0 KO, >0 OK
- */
- 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 = '')
- {
- global $conf;
- require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
- require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
- $this->db->begin();
- $price_impact = array(1=>0); // init level price impact
- $forced_refvar = trim($forced_refvar);
- if (!empty($forced_refvar) && $forced_refvar != $product->ref) {
- $existingProduct = new Product($this->db);
- $result = $existingProduct->fetch('', $forced_refvar);
- if ($result > 0) {
- $newproduct = $existingProduct;
- } else {
- $existingProduct = false;
- $newproduct = clone $product;
- $newproduct->ref = $forced_refvar;
- }
- } else {
- $forced_refvar = false;
- $existingProduct = false;
- $newproduct = clone $product;
- }
- //Final weight impact
- $weight_impact = (float) $forced_weightvar; // If false, return 0
- //Final price impact
- if (!is_array($forced_pricevar)) {
- $price_impact[1] = (float) $forced_pricevar; // If false, return 0
- } else {
- $price_impact = $forced_pricevar;
- }
- if (!array($price_var_percent)) {
- $price_var_percent[1] = (float) $price_var_percent;
- }
- $newcomb = new ProductCombination($this->db);
- $existingCombination = $newcomb->fetchByProductCombination2ValuePairs($product->id, $combinations);
- if ($existingCombination) {
- $newcomb = $existingCombination;
- } else {
- $newcomb->fk_product_parent = $product->id;
- // Create 1 entry into product_attribute_combination (1 entry for each combinations). This init also $newcomb->id
- $result = $newcomb->create($user);
- if ($result < 0) {
- $this->error = $newcomb->error;
- $this->errors = $newcomb->errors;
- $this->db->rollback();
- return -1;
- }
- }
- $prodattr = new ProductAttribute($this->db);
- $prodattrval = new ProductAttributeValue($this->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($this->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
- $this->error = $tmp->error;
- $this->errors = $tmp->errors;
- $this->db->rollback();
- return -1;
- }
- }
- if ($forced_weightvar === false) {
- $weight_impact += (float) price2num($variations[$currcombattr][$currcombval]['weight']);
- }
- if ($forced_pricevar === false) {
- $price_impact[1] += (float) price2num($variations[$currcombattr][$currcombval]['price']);
- // Manage Price levels
- if ($conf->global->PRODUIT_MULTIPRICES) {
- for ($i = 2; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
- $price_impact[$i] += (float) price2num($variations[$currcombattr][$currcombval]['price']);
- }
- }
- }
- if ($forced_refvar === false) {
- 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[1];
- $newcomb->variation_price = $price_impact[1];
- $newcomb->variation_weight = $weight_impact;
- $newcomb->variation_ref_ext = $this->db->escape($ref_ext);
- // Init price level
- if ($conf->global->PRODUIT_MULTIPRICES) {
- for ($i = 1; $i <= $conf->global->PRODUIT_MULTIPRICES_LIMIT; $i++) {
- $productCombinationLevel = new ProductCombinationLevel($this->db);
- $productCombinationLevel->fk_product_attribute_combination = $newcomb->id;
- $productCombinationLevel->fk_price_level = $i;
- $productCombinationLevel->variation_price = $price_impact[$i];
- if (is_array($price_var_percent)) {
- $productCombinationLevel->variation_price_percentage = (empty($price_var_percent[$i]) ? false : $price_var_percent[$i]);
- } else {
- $productCombinationLevel->variation_price_percentage = $price_var_percent;
- }
- $newcomb->combination_price_levels[$i] = $productCombinationLevel;
- }
- }
- //var_dump($newcomb->combination_price_levels);
- $newproduct->weight += $weight_impact;
- // Now create the product
- //print 'Create prod '.$newproduct->ref.'<br>'."\n";
- if ($existingProduct === false) {
- //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;
- $result = $newproduct->create($user);
- if ($result < 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;
- $this->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) {
- $this->db->rollback();
- return -1;
- }
- }
- } else {
- $result = $newproduct->update($newproduct->id, $user);
- if ($result < 0) {
- $this->db->rollback();
- return -1;
- }
- }
- $newcomb->fk_product_child = $newproduct->id;
- if ($newcomb->update($user) < 0) {
- $this->error = $newcomb->error;
- $this->errors = $newcomb->errors;
- $this->db->rollback();
- return -1;
- }
- $this->db->commit();
- return $newproduct->id;
- }
- /**
- * Copies all product combinations from the origin product to the destination product
- *
- * @param User $user Object user
- * @param int $origProductId Origin product id
- * @param Product $destProduct Destination product
- * @return int >0 OK <0 KO
- */
- public function copyAll(User $user, $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(
- $user,
- $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='.((int) $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;
- }
- }
- /**
- * Class ProductCombinationLevel
- * Used to represent a product combination Level
- */
- class ProductCombinationLevel
- {
- /**
- * Database handler
- * @var DoliDB
- */
- public $db;
- /**
- * @var string Name of table without prefix where object is stored
- */
- public $table_element = 'product_attribute_combination_price_level';
- /**
- * Rowid of combination
- * @var int
- */
- public $id;
- /**
- * Rowid of parent product combination
- * @var int
- */
- public $fk_product_attribute_combination;
- /**
- * Combination price level
- * @var int
- */
- public $fk_price_level;
- /**
- * Price variation
- * @var float
- */
- public $variation_price;
- /**
- * Is the price variation a relative variation?
- * @var bool
- */
- public $variation_price_percentage = false;
- /**
- * Constructor
- *
- * @param DoliDB $db Database handler
- */
- public function __construct(DoliDB $db)
- {
- $this->db = $db;
- }
- /**
- * Retrieves a combination level by its rowid
- *
- * @param int $rowid Row id
- * @return int <0 KO, >0 OK
- */
- public function fetch($rowid)
- {
- $sql = "SELECT rowid, fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
- $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
- $sql .= " WHERE rowid = ".(int) $rowid;
- $resql = $this->db->query($sql);
- if ($resql) {
- $obj = $this->db->fetch_object($resql);
- if ($obj) {
- return $this->fetchFormObj($obj);
- }
- }
- return -1;
- }
- /**
- * Retrieves combination price levels
- *
- * @param int $fk_product_attribute_combination Id of product combination
- * @param int $fk_price_level The price level to fetch, use 0 for all
- * @return mixed self[] | -1 on KO
- */
- public function fetchAll($fk_product_attribute_combination, $fk_price_level = 0)
- {
- $result = array();
- $sql = "SELECT rowid, fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
- $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
- $sql .= " WHERE fk_product_attribute_combination = ".intval($fk_product_attribute_combination);
- if (!empty($fk_price_level)) {
- $sql .= ' AND fk_price_level = '.intval($fk_price_level);
- }
- $res = $this->db->query($sql);
- if ($res) {
- if ($this->db->num_rows($res) > 0) {
- while ($obj = $this->db->fetch_object($res)) {
- $productCombinationLevel = new ProductCombinationLevel($this->db);
- $productCombinationLevel->fetchFormObj($obj);
- $result[$obj->fk_price_level] = $productCombinationLevel;
- }
- }
- } else {
- return -1;
- }
- return $result;
- }
- /**
- * Assign vars form an stdclass like sql obj
- *
- * @param int $obj Object resultset
- * @return int <0 KO, >0 OK
- */
- public function fetchFormObj($obj)
- {
- if (!$obj) {
- return -1;
- }
- $this->id = $obj->rowid;
- $this->fk_product_attribute_combination = floatval($obj->fk_product_attribute_combination);
- $this->fk_price_level = intval($obj->fk_price_level);
- $this->variation_price = floatval($obj->variation_price);
- $this->variation_price_percentage = (bool) $obj->variation_price_percentage;
- return 1;
- }
- /**
- * Save a price impact of a product combination for a price level
- *
- * @return int <0 KO, >0 OK
- */
- public function save()
- {
- if (($this->id > 0 && empty($this->fk_product_attribute_combination)) || empty($this->fk_price_level)) {
- return -1;
- }
- // Check if level exist in DB before add
- if ($this->fk_product_attribute_combination > 0 && empty($this->id)) {
- $sql = "SELECT rowid id";
- $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
- $sql .= " WHERE fk_product_attribute_combination = ".(int) $this->fk_product_attribute_combination;
- $sql .= ' AND fk_price_level = '.((int) $this->fk_price_level);
- $resql = $this->db->query($sql);
- if ($resql) {
- $obj = $this->db->fetch_object($resql);
- if ($obj) {
- $this->id = $obj->id;
- }
- }
- }
- // Update
- if (!empty($this->id)) {
- $sql = 'UPDATE '.MAIN_DB_PREFIX.$this->table_element;
- $sql .= ' SET variation_price = '.floatval($this->variation_price).' , variation_price_percentage = '.intval($this->variation_price_percentage);
- $sql .= ' WHERE rowid = '.((int) $this->id);
- $res = $this->db->query($sql);
- if ($res > 0) {
- return $this->id;
- } else {
- $this->error = $this->db->error();
- $this->errors[] = $this->error;
- return -1;
- }
- } else {
- // Add
- $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element." (";
- $sql .= "fk_product_attribute_combination, fk_price_level, variation_price, variation_price_percentage";
- $sql .= ") VALUES (";
- $sql .= (int) $this->fk_product_attribute_combination;
- $sql .= ", ".intval($this->fk_price_level);
- $sql .= ", ".floatval($this->variation_price);
- $sql .= ", ".intval($this->variation_price_percentage);
- $sql .= ")";
- $res = $this->db->query($sql);
- if ($res) {
- $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
- } else {
- $this->error = $this->db->error();
- $this->errors[] = $this->error;
- return -1;
- }
- }
- return $this->id;
- }
- /**
- * delete
- *
- * @return int <0 KO, >0 OK
- */
- public function delete()
- {
- $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE rowid = ".(int) $this->id;
- $res = $this->db->query($sql);
- return $res ? 1 : -1;
- }
- /**
- * delete all for a combination
- *
- * @param int $fk_product_attribute_combination Id of combination
- * @return int <0 KO, >0 OK
- */
- public function deleteAllForCombination($fk_product_attribute_combination)
- {
- $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element." WHERE fk_product_attribute_combination = ".(int) $fk_product_attribute_combination;
- $res = $this->db->query($sql);
- return $res ? 1 : -1;
- }
- /**
- * Clean not needed price levels for a combination
- *
- * @param int $fk_product_attribute_combination Id of combination
- * @return int <0 KO, >0 OK
- */
- public function clean($fk_product_attribute_combination)
- {
- global $conf;
- $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element;
- $sql .= " WHERE fk_product_attribute_combination = ".(int) $fk_product_attribute_combination;
- $sql .= " AND fk_price_level > ".intval($conf->global->PRODUIT_MULTIPRICES_LIMIT);
- $res = $this->db->query($sql);
- return $res ? 1 : -1;
- }
- /**
- * Create new Product Combination Price level from Parent
- *
- * @param DoliDB $db Database handler
- * @param ProductCombination $productCombination Product combination
- * @param int $fkPriceLevel Price level
- * @return ProductCombinationLevel
- */
- public static function createFromParent(DoliDB $db, ProductCombination $productCombination, $fkPriceLevel)
- {
- $productCombinationLevel = new self($db);
- $productCombinationLevel->fk_price_level = $fkPriceLevel;
- $productCombinationLevel->fk_product_attribute_combination = $productCombination->id;
- $productCombinationLevel->variation_price = $productCombination->variation_price;
- $productCombinationLevel->variation_price_percentage = (bool) $productCombination->variation_price_percentage;
- return $productCombinationLevel;
- }
- }
|