123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263 |
- <?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_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;
- $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 ($parent->oldcopy && ($parent->status != $parent->oldcopy->status)) {
- $child->status = $parent->status;
- }
- if ($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);
- 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;
- }
- }
|