dolibarr  20.0.0-alpha
api_products.class.php
1 <?php
2 /* Copyright (C) 2015 Jean-Fran├žois Ferry <jfefe@aternatik.fr>
3  * Copyright (C) 2019 Cedric Ancelin <icedo.anc@gmail.com>
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <https://www.gnu.org/licenses/>.
17  */
18 
19 use Luracast\Restler\RestException;
20 
21 require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
22 require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
23 require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
24 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
25 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
26 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
27 require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
28 
35 class Products extends DolibarrApi
36 {
40  public static $FIELDS = array(
41  'ref',
42  'label'
43  );
44 
48  public $product;
49 
53  public $productsupplier;
54 
58  public function __construct()
59  {
60  global $db, $conf;
61 
62  $this->db = $db;
63  $this->product = new Product($this->db);
64  $this->productsupplier = new ProductFournisseur($this->db);
65  }
66 
83  public function get($id, $includestockdata = 0, $includesubproducts = false, $includeparentid = false, $includetrans = false)
84  {
85  return $this->_fetch($id, '', '', '', $includestockdata, $includesubproducts, $includeparentid, false, $includetrans);
86  }
87 
107  public function getByRef($ref, $includestockdata = 0, $includesubproducts = false, $includeparentid = false, $includetrans = false)
108  {
109  return $this->_fetch('', $ref, '', '', $includestockdata, $includesubproducts, $includeparentid, false, $includetrans);
110  }
111 
131  public function getByRefExt($ref_ext, $includestockdata = 0, $includesubproducts = false, $includeparentid = false, $includetrans = false)
132  {
133  return $this->_fetch('', '', $ref_ext, '', $includestockdata, $includesubproducts, $includeparentid, false, $includetrans);
134  }
135 
155  public function getByBarcode($barcode, $includestockdata = 0, $includesubproducts = false, $includeparentid = false, $includetrans = false)
156  {
157  return $this->_fetch('', '', '', $barcode, $includestockdata, $includesubproducts, $includeparentid, false, $includetrans);
158  }
159 
179  public function index($sortfield = "t.ref", $sortorder = 'ASC', $limit = 100, $page = 0, $mode = 0, $category = 0, $sqlfilters = '', $ids_only = false, $variant_filter = 0, $pagination_data = false, $includestockdata = 0, $properties = '')
180  {
181  global $db, $conf;
182 
183  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
184  throw new RestException(403);
185  }
186 
187  $obj_ret = array();
188 
189  $socid = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : '';
190 
191  $sql = "SELECT t.rowid, t.ref, t.ref_ext";
192  $sql .= " FROM ".$this->db->prefix()."product as t";
193  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."product_extrafields AS ef ON ef.fk_object = t.rowid"; // So we will be able to filter on extrafields
194  if ($category > 0) {
195  $sql .= ", ".$this->db->prefix()."categorie_product as c";
196  }
197  $sql .= ' WHERE t.entity IN ('.getEntity('product').')';
198 
199  if ($variant_filter == 1) {
200  $sql .= ' AND t.rowid not in (select distinct fk_product_parent from '.$this->db->prefix().'product_attribute_combination)';
201  $sql .= ' AND t.rowid not in (select distinct fk_product_child from '.$this->db->prefix().'product_attribute_combination)';
202  }
203  if ($variant_filter == 2) {
204  $sql .= ' AND t.rowid in (select distinct fk_product_parent from '.$this->db->prefix().'product_attribute_combination)';
205  }
206  if ($variant_filter == 3) {
207  $sql .= ' AND t.rowid in (select distinct fk_product_child from '.$this->db->prefix().'product_attribute_combination)';
208  }
209 
210  // Select products of given category
211  if ($category > 0) {
212  $sql .= " AND c.fk_categorie = ".((int) $category);
213  $sql .= " AND c.fk_product = t.rowid";
214  }
215  if ($mode == 1) {
216  // Show only products
217  $sql .= " AND t.fk_product_type = 0";
218  } elseif ($mode == 2) {
219  // Show only services
220  $sql .= " AND t.fk_product_type = 1";
221  }
222 
223  // Add sql filters
224  if ($sqlfilters) {
225  $errormessage = '';
226  $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
227  if ($errormessage) {
228  throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
229  }
230  }
231 
232  //this query will return total products with the filters given
233  $sqlTotals = str_replace('SELECT t.rowid, t.ref, t.ref_ext', 'SELECT count(t.rowid) as total', $sql);
234 
235  $sql .= $this->db->order($sortfield, $sortorder);
236  if ($limit) {
237  if ($page < 0) {
238  $page = 0;
239  }
240  $offset = $limit * $page;
241 
242  $sql .= $this->db->plimit($limit + 1, $offset);
243  }
244 
245  $result = $this->db->query($sql);
246  if ($result) {
247  $num = $this->db->num_rows($result);
248  $min = min($num, ($limit <= 0 ? $num : $limit));
249  $i = 0;
250  while ($i < $min) {
251  $obj = $this->db->fetch_object($result);
252  if (!$ids_only) {
253  $product_static = new Product($this->db);
254  if ($product_static->fetch($obj->rowid)) {
255  if (!empty($includestockdata) && DolibarrApiAccess::$user->hasRight('stock', 'lire')) {
256  $product_static->load_stock();
257 
258  if (is_array($product_static->stock_warehouse)) {
259  foreach ($product_static->stock_warehouse as $keytmp => $valtmp) {
260  if (isset($product_static->stock_warehouse[$keytmp]->detail_batch) && is_array($product_static->stock_warehouse[$keytmp]->detail_batch)) {
261  foreach ($product_static->stock_warehouse[$keytmp]->detail_batch as $keytmp2 => $valtmp2) {
262  unset($product_static->stock_warehouse[$keytmp]->detail_batch[$keytmp2]->db);
263  }
264  }
265  }
266  }
267  }
268 
269 
270  $obj_ret[] = $this->_filterObjectProperties($this->_cleanObjectDatas($product_static), $properties);
271  }
272  } else {
273  $obj_ret[] = $obj->rowid;
274  }
275  $i++;
276  }
277  } else {
278  throw new RestException(503, 'Error when retrieve product list : '.$this->db->lasterror());
279  }
280 
281  //if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit)
282  if ($pagination_data) {
283  $totalsResult = $this->db->query($sqlTotals);
284  $total = $this->db->fetch_object($totalsResult)->total;
285 
286  $tmp = $obj_ret;
287  $obj_ret = array();
288 
289  $obj_ret['data'] = $tmp;
290  $obj_ret['pagination'] = array(
291  'total' => (int) $total,
292  'page' => $page, //count starts from 0
293  'page_count' => ceil((int) $total/$limit),
294  'limit' => $limit
295  );
296  }
297 
298  return $obj_ret;
299  }
300 
307  public function post($request_data = null)
308  {
309  if (!DolibarrApiAccess::$user->hasRight('produit', 'creer')) {
310  throw new RestException(403);
311  }
312  // Check mandatory fields
313  $result = $this->_validate($request_data);
314 
315  foreach ($request_data as $field => $value) {
316  if ($field === 'caller') {
317  // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller
318  $this->product->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
319  continue;
320  }
321 
322  $this->product->$field = $this->_checkValForAPI($field, $value, $this->product);
323  }
324  if ($this->product->create(DolibarrApiAccess::$user) < 0) {
325  throw new RestException(500, "Error creating product", array_merge(array($this->product->error), $this->product->errors));
326  }
327 
328  if (getDolGlobalString('PRODUIT_MULTIPRICES')) {
329  $key_max = getDolGlobalString('PRODUIT_MULTIPRICES_LIMIT');
330  for ($key = 1; $key <= $key_max ; $key++) {
331  $newvat = $this->product->multiprices_tva_tx[$key];
332  $newnpr = 0;
333  $newvatsrccode = $this->product->default_vat_code;
334  $newprice = $this->product->multiprices[$key];
335  $newpricemin = $this->product->multiprices_min[$key];
336  $newbasetype = $this->product->multiprices_base_type[$key];
337  if (empty($newbasetype) || $newbasetype == '') {
338  $newbasetype = $this->product->price_base_type;
339  }
340  if ($newbasetype == 'TTC') {
341  $newprice = $this->product->multiprices_ttc[$key];
342  $newpricemin = $this->product->multiprices_min_ttc[$key];
343  }
344  if ($newprice > 0) {
345  $result = $this->product->updatePrice($newprice, $newbasetype, DolibarrApiAccess::$user, $newvat, $newpricemin, $key, $newnpr, 0, 0, array(), $newvatsrccode);
346  }
347  }
348  }
349 
350  return $this->product->id;
351  }
352 
366  public function put($id, $request_data = null)
367  {
368  if (!DolibarrApiAccess::$user->hasRight('produit', 'creer')) {
369  throw new RestException(403);
370  }
371 
372  $result = $this->product->fetch($id);
373  if (!$result) {
374  throw new RestException(404, 'Product not found');
375  }
376 
377  if (!DolibarrApi::_checkAccessToResource('product', $this->product->id)) {
378  throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
379  }
380 
381  $oldproduct = dol_clone($this->product, 2);
382 
383  foreach ($request_data as $field => $value) {
384  if ($field == 'id') {
385  continue;
386  }
387  if ($field == 'stock_reel') {
388  throw new RestException(400, 'Stock reel cannot be updated here. Use the /stockmovements endpoint instead');
389  }
390  if ($field === 'caller') {
391  // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller
392  $this->product->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
393  continue;
394  }
395  if ($field == 'array_options' && is_array($value)) {
396  foreach ($value as $index => $val) {
397  $this->product->array_options[$index] = $this->_checkValForAPI($field, $val, $this->product);
398  }
399  continue;
400  }
401  $this->product->$field = $this->_checkValForAPI($field, $value, $this->product);
402  }
403 
404  $updatetype = false;
405  if ($this->product->type != $oldproduct->type && ($this->product->isProduct() || $this->product->isService())) {
406  $updatetype = true;
407  }
408 
409  $result = $this->product->update($id, DolibarrApiAccess::$user, 1, 'update', $updatetype);
410 
411  // If price mode is 1 price per product
412  if ($result > 0 && getDolGlobalString('PRODUCT_PRICE_UNIQ')) {
413  // We update price only if it was changed
414  $pricemodified = false;
415  if ($this->product->price_base_type != $oldproduct->price_base_type) {
416  $pricemodified = true;
417  } else {
418  if ($this->product->tva_tx != $oldproduct->tva_tx) {
419  $pricemodified = true;
420  }
421  if ($this->product->tva_npr != $oldproduct->tva_npr) {
422  $pricemodified = true;
423  }
424  if ($this->product->default_vat_code != $oldproduct->default_vat_code) {
425  $pricemodified = true;
426  }
427 
428  if ($this->product->price_base_type == 'TTC') {
429  if ($this->product->price_ttc != $oldproduct->price_ttc) {
430  $pricemodified = true;
431  }
432  if ($this->product->price_min_ttc != $oldproduct->price_min_ttc) {
433  $pricemodified = true;
434  }
435  } else {
436  if ($this->product->price != $oldproduct->price) {
437  $pricemodified = true;
438  }
439  if ($this->product->price_min != $oldproduct->price_min) {
440  $pricemodified = true;
441  }
442  }
443  }
444 
445  if ($pricemodified) {
446  $newvat = $this->product->tva_tx;
447  $newnpr = $this->product->tva_npr;
448  $newvatsrccode = $this->product->default_vat_code;
449 
450  $newprice = $this->product->price;
451  $newpricemin = $this->product->price_min;
452  if ($this->product->price_base_type == 'TTC') {
453  $newprice = $this->product->price_ttc;
454  $newpricemin = $this->product->price_min_ttc;
455  }
456 
457  $result = $this->product->updatePrice($newprice, $this->product->price_base_type, DolibarrApiAccess::$user, $newvat, $newpricemin, 0, $newnpr, 0, 0, array(), $newvatsrccode);
458  }
459  }
460 
461  if ($result > 0 && getDolGlobalString('PRODUIT_MULTIPRICES')) {
462  $key_max = getDolGlobalString('PRODUIT_MULTIPRICES_LIMIT');
463  for ($key = 1; $key <= $key_max ; $key++) {
464  $pricemodified = false;
465  if ($this->product->multiprices_base_type[$key] != $oldproduct->multiprices_base_type[$key]) {
466  $pricemodified = true;
467  } else {
468  if ($this->product->multiprices_tva_tx[$key] != $oldproduct->multiprices_tva_tx[$key]) $pricemodified = true;
469  if ($this->product->multiprices_base_type[$key] == 'TTC') {
470  if ($this->product->multiprices_ttc[$key] != $oldproduct->multiprices_ttc[$key]) $pricemodified = true;
471  if ($this->product->multiprices_min_ttc[$key] != $oldproduct->multiprices_min_ttc[$key]) $pricemodified = true;
472  } else {
473  if ($this->product->multiprices[$key] != $oldproduct->multiprices[$key]) $pricemodified = true;
474  if ($this->product->multiprices_min[$key] != $oldproduct->multiprices[$key]) $pricemodified = true;
475  }
476  }
477  if ($pricemodified && $result > 0) {
478  $newvat = $this->product->multiprices_tva_tx[$key];
479  $newnpr = 0;
480  $newvatsrccode = $this->product->default_vat_code;
481  $newprice = $this->product->multiprices[$key];
482  $newpricemin = $this->product->multiprices_min[$key];
483  $newbasetype = $this->product->multiprices_base_type[$key];
484  if (empty($newbasetype) || $newbasetype == '') {
485  $newbasetype = $this->product->price_base_type;
486  }
487  if ($newbasetype == 'TTC') {
488  $newprice = $this->product->multiprices_ttc[$key];
489  $newpricemin = $this->product->multiprices_min_ttc[$key];
490  }
491 
492  $result = $this->product->updatePrice($newprice, $newbasetype, DolibarrApiAccess::$user, $newvat, $newpricemin, $key, $newnpr, 0, 0, array(), $newvatsrccode);
493  }
494  }
495  }
496 
497  if ($result <= 0) {
498  throw new RestException(500, "Error updating product", array_merge(array($this->product->error), $this->product->errors));
499  }
500 
501  return $this->get($id);
502  }
503 
510  public function delete($id)
511  {
512  if (!DolibarrApiAccess::$user->hasRight('produit', 'supprimer')) {
513  throw new RestException(403);
514  }
515  $result = $this->product->fetch($id);
516  if (!$result) {
517  throw new RestException(404, 'Product not found');
518  }
519 
520  if (!DolibarrApi::_checkAccessToResource('product', $this->product->id)) {
521  throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
522  }
523 
524  // The Product::delete() method uses the global variable $user.
525  global $user;
526  $user = DolibarrApiAccess::$user;
527 
528  $res = $this->product->delete(DolibarrApiAccess::$user);
529  if ($res < 0) {
530  throw new RestException(500, "Can't delete, error occurs");
531  } elseif ($res == 0) {
532  throw new RestException(409, "Can't delete, that product is probably used");
533  }
534 
535  return array(
536  'success' => array(
537  'code' => 200,
538  'message' => 'Object deleted'
539  )
540  );
541  }
542 
555  public function getSubproducts($id)
556  {
557  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
558  throw new RestException(403);
559  }
560 
561  if (!DolibarrApi::_checkAccessToResource('product', $id)) {
562  throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
563  }
564 
565  $childrenArbo = $this->product->getChildsArbo($id, 1);
566 
567  $keys = array('rowid', 'qty', 'fk_product_type', 'label', 'incdec', 'ref', 'fk_association', 'rang');
568  $children = array();
569  foreach ($childrenArbo as $values) {
570  $children[] = array_combine($keys, $values);
571  }
572 
573  return $children;
574  }
575 
593  public function addSubproducts($id, $subproduct_id, $qty, $incdec = 1)
594  {
595  if (!DolibarrApiAccess::$user->hasRight('produit', 'creer')) {
596  throw new RestException(403);
597  }
598 
599  if (!DolibarrApi::_checkAccessToResource('product', $id)) {
600  throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
601  }
602 
603  $result = $this->product->add_sousproduit($id, $subproduct_id, $qty, $incdec);
604  if ($result <= 0) {
605  throw new RestException(500, "Error adding product child");
606  }
607  return $result;
608  }
609 
623  public function delSubproducts($id, $subproduct_id)
624  {
625  if (!DolibarrApiAccess::$user->hasRight('produit', 'creer')) {
626  throw new RestException(403);
627  }
628 
629  if (!DolibarrApi::_checkAccessToResource('product', $id)) {
630  throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
631  }
632 
633  $result = $this->product->del_sousproduit($id, $subproduct_id);
634  if ($result <= 0) {
635  throw new RestException(500, "Error while removing product child");
636  }
637  return $result;
638  }
639 
640 
654  public function getCategories($id, $sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0)
655  {
656  if (!DolibarrApiAccess::$user->hasRight('categorie', 'lire')) {
657  throw new RestException(403);
658  }
659 
660  $categories = new Categorie($this->db);
661 
662  $result = $categories->getListForItem($id, 'product', $sortfield, $sortorder, $limit, $page);
663 
664  if ($result < 0) {
665  throw new RestException(503, 'Error when retrieve category list : '.implode(',', array_merge(array($categories->error), $categories->errors)));
666  }
667 
668  return $result;
669  }
670 
680  public function getCustomerPricesPerSegment($id)
681  {
682  global $conf;
683 
684  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
685  throw new RestException(403);
686  }
687 
688  if (!getDolGlobalString('PRODUIT_MULTIPRICES')) {
689  throw new RestException(400, 'API not available: this mode of pricing is not enabled by setup');
690  }
691 
692  $result = $this->product->fetch($id);
693  if (!$result) {
694  throw new RestException(404, 'Product not found');
695  }
696 
697  if ($result < 0) {
698  throw new RestException(503, 'Error when retrieve prices list : '.implode(',', array_merge(array($this->product->error), $this->product->errors)));
699  }
700 
701  return array(
702  'multiprices'=>$this->product->multiprices,
703  'multiprices_inc_tax'=>$this->product->multiprices_ttc,
704  'multiprices_min'=>$this->product->multiprices_min,
705  'multiprices_min_inc_tax'=>$this->product->multiprices_min_ttc,
706  'multiprices_vat'=>$this->product->multiprices_tva_tx,
707  'multiprices_base_type'=>$this->product->multiprices_base_type,
708  //'multiprices_default_vat_code'=>$this->product->multiprices_default_vat_code
709  );
710  }
711 
722  public function getCustomerPricesPerCustomer($id, $thirdparty_id = '')
723  {
724  global $conf;
725 
726  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
727  throw new RestException(403);
728  }
729 
730  if (!getDolGlobalString('PRODUIT_CUSTOMER_PRICES')) {
731  throw new RestException(400, 'API not available: this mode of pricing is not enabled by setup');
732  }
733 
734  $socid = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : '';
735  if ($socid > 0 && $socid != $thirdparty_id) {
736  throw new RestException(403, 'Getting prices for all customers or for the customer ID '.$thirdparty_id.' is not allowed for login '.DolibarrApiAccess::$user->login);
737  }
738 
739  $result = $this->product->fetch($id);
740  if (!$result) {
741  throw new RestException(404, 'Product not found');
742  }
743 
744  if ($result > 0) {
745  require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
746  $prodcustprice = new ProductCustomerPrice($this->db);
747  $filter = array();
748  $filter['t.fk_product'] = $id;
749  if ($thirdparty_id) {
750  $filter['t.fk_soc'] = $thirdparty_id;
751  }
752  $result = $prodcustprice->fetchAll('', '', 0, 0, $filter);
753  }
754 
755  if (empty($prodcustprice->lines)) {
756  throw new RestException(404, 'Prices not found');
757  }
758 
759  return $prodcustprice->lines;
760  }
761 
771  public function getCustomerPricesPerQuantity($id)
772  {
773  global $conf;
774 
775  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
776  throw new RestException(403);
777  }
778 
779  if (!getDolGlobalString('PRODUIT_CUSTOMER_PRICES_BY_QTY')) {
780  throw new RestException(400, 'API not available: this mode of pricing is not enabled by setup');
781  }
782 
783  $result = $this->product->fetch($id);
784  if (!$result) {
785  throw new RestException(404, 'Product not found');
786  }
787 
788  if ($result < 0) {
789  throw new RestException(503, 'Error when retrieve prices list : '.implode(',', array_merge(array($this->product->error), $this->product->errors)));
790  }
791 
792  return array(
793  'prices_by_qty'=>$this->product->prices_by_qty[0], // 1 if price by quantity was activated for the product
794  'prices_by_qty_list'=>$this->product->prices_by_qty_list[0]
795  );
796  }
797 
831  public function addPurchasePrice($id, $qty, $buyprice, $price_base_type, $fourn_id, $availability, $ref_fourn, $tva_tx, $charges = 0, $remise_percent = 0, $remise = 0, $newnpr = 0, $delivery_time_days = 0, $supplier_reputation = '', $localtaxes_array = array(), $newdefaultvatcode = '', $multicurrency_buyprice = 0, $multicurrency_price_base_type = 'HT', $multicurrency_tx = 1, $multicurrency_code = '', $desc_fourn = '', $barcode = '', $fk_barcode_type = null)
832  {
833  if (!DolibarrApiAccess::$user->hasRight('produit', 'creer')) {
834  throw new RestException(403);
835  }
836 
837  $result = $this->productsupplier->fetch($id);
838  if (!$result) {
839  throw new RestException(404, 'Product not found');
840  }
841 
842  if (!DolibarrApi::_checkAccessToResource('product', $this->productsupplier->id)) {
843  throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
844  }
845 
846  $socid = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : '';
847  if ($socid > 0 && $socid != $fourn_id) {
848  throw new RestException(403, 'Adding purchase price for the supplier ID '.$fourn_id.' is not allowed for login '.DolibarrApiAccess::$user->login);
849  }
850 
851  $result = $this->productsupplier->add_fournisseur(DolibarrApiAccess::$user, $fourn_id, $ref_fourn, $qty);
852  if ($result < 0) {
853  throw new RestException(500, "Error adding supplier to product : ".$this->db->lasterror());
854  }
855 
856  $fourn = new Fournisseur($this->db);
857  $result = $fourn->fetch($fourn_id);
858  if ($result <= 0) {
859  throw new RestException(404, 'Supplier not found');
860  }
861 
862  // Clean data
863  $ref_fourn = sanitizeVal($ref_fourn, 'alphanohtml');
864  $desc_fourn = sanitizeVal($desc_fourn, 'restricthtml');
865  $barcode = sanitizeVal($barcode, 'alphanohtml');
866 
867  $result = $this->productsupplier->update_buyprice($qty, $buyprice, DolibarrApiAccess::$user, $price_base_type, $fourn, $availability, $ref_fourn, $tva_tx, $charges, $remise_percent, $remise, $newnpr, $delivery_time_days, $supplier_reputation, $localtaxes_array, $newdefaultvatcode, $multicurrency_buyprice, $multicurrency_price_base_type, $multicurrency_tx, $multicurrency_code, $desc_fourn, $barcode, $fk_barcode_type);
868 
869  if ($result <= 0) {
870  throw new RestException(500, "Error updating buy price : ".$this->db->lasterror());
871  }
872  return (int) $this->productsupplier->product_fourn_price_id;
873  }
874 
889  public function deletePurchasePrice($id, $priceid)
890  {
891  if (!DolibarrApiAccess::$user->hasRight('produit', 'supprimer')) {
892  throw new RestException(403);
893  }
894  $result = $this->productsupplier->fetch($id);
895  if (!$result) {
896  throw new RestException(404, 'Product not found');
897  }
898 
899  if (!DolibarrApi::_checkAccessToResource('product', $this->productsupplier->id)) {
900  throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
901  }
902 
903  $resultsupplier = 0;
904  if ($result > 0) {
905  $resultsupplier = $this->productsupplier->remove_product_fournisseur_price($priceid);
906  }
907 
908  return $resultsupplier;
909  }
910 
926  public function getSupplierProducts($sortfield = "t.ref", $sortorder = 'ASC', $limit = 100, $page = 0, $mode = 0, $category = 0, $supplier = 0, $sqlfilters = '')
927  {
928  global $db, $conf;
929 
930  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
931  throw new RestException(403);
932  }
933 
934  $obj_ret = array();
935 
936  // Force id of company for external users
937  $socid = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : '';
938  if ($socid > 0) {
939  if ($supplier != $socid || empty($supplier)) {
940  throw new RestException(403, 'As an external user, you can request only for your supplier id = '.$socid);
941  }
942  }
943 
944  $sql = "SELECT t.rowid, t.ref, t.ref_ext";
945  $sql .= " FROM ".MAIN_DB_PREFIX."product AS t LEFT JOIN ".MAIN_DB_PREFIX."product_extrafields AS ef ON (ef.fk_object = t.rowid)"; // Modification VMR Global Solutions to include extrafields as search parameters in the API GET call, so we will be able to filter on extrafields
946 
947  if ($category > 0) {
948  $sql .= ", ".$this->db->prefix()."categorie_product as c";
949  }
950  $sql .= ", ".$this->db->prefix()."product_fournisseur_price as s";
951 
952  $sql .= ' WHERE t.entity IN ('.getEntity('product').')';
953 
954  if ($supplier > 0) {
955  $sql .= " AND s.fk_soc = ".((int) $supplier);
956  }
957  if ($socid > 0) { // if external user
958  $sql .= " AND s.fk_soc = ".((int) $socid);
959  }
960  $sql .= " AND s.fk_product = t.rowid";
961  // Select products of given category
962  if ($category > 0) {
963  $sql .= " AND c.fk_categorie = ".((int) $category);
964  $sql .= " AND c.fk_product = t.rowid";
965  }
966  if ($mode == 1) {
967  // Show only products
968  $sql .= " AND t.fk_product_type = 0";
969  } elseif ($mode == 2) {
970  // Show only services
971  $sql .= " AND t.fk_product_type = 1";
972  }
973  // Add sql filters
974  if ($sqlfilters) {
975  $errormessage = '';
976  $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
977  if ($errormessage) {
978  throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
979  }
980  }
981 
982  $sql .= $this->db->order($sortfield, $sortorder);
983  if ($limit) {
984  if ($page < 0) {
985  $page = 0;
986  }
987  $offset = $limit * $page;
988  $sql .= $this->db->plimit($limit + 1, $offset);
989  }
990  $result = $this->db->query($sql);
991  if ($result) {
992  $num = $this->db->num_rows($result);
993  $min = min($num, ($limit <= 0 ? $num : $limit));
994  $i = 0;
995  while ($i < $min) {
996  $obj = $this->db->fetch_object($result);
997 
998  $product_fourn = new ProductFournisseur($this->db);
999  $product_fourn_list = $product_fourn->list_product_fournisseur_price($obj->rowid, '', '', 0, 0);
1000  foreach ($product_fourn_list as $tmpobj) {
1001  $this->_cleanObjectDatas($tmpobj);
1002  }
1003 
1004  //var_dump($product_fourn_list->db);exit;
1005  $obj_ret[$obj->rowid] = $product_fourn_list;
1006 
1007  $i++;
1008  }
1009  } else {
1010  throw new RestException(503, 'Error when retrieve product list : '.$this->db->lasterror());
1011  }
1012 
1013  return $obj_ret;
1014  }
1015 
1035  public function getPurchasePrices($id, $ref = '', $ref_ext = '', $barcode = '')
1036  {
1037  if (empty($id) && empty($ref) && empty($ref_ext) && empty($barcode)) {
1038  throw new RestException(400, 'bad value for parameter id, ref, ref_ext or barcode');
1039  }
1040 
1041  $id = (empty($id) ? 0 : $id);
1042 
1043  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
1044  throw new RestException(403);
1045  }
1046 
1047  $socid = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : '';
1048 
1049  $result = $this->product->fetch($id, $ref, $ref_ext, $barcode);
1050  if (!$result) {
1051  throw new RestException(404, 'Product not found');
1052  }
1053 
1054  if (!DolibarrApi::_checkAccessToResource('product', $this->product->id)) {
1055  throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1056  }
1057 
1058  $product_fourn_list = array();
1059 
1060  if ($result) {
1061  $product_fourn = new ProductFournisseur($this->db);
1062  $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->product->id, '', '', 0, 0, ($socid > 0 ? $socid : 0));
1063  }
1064 
1065  foreach ($product_fourn_list as $tmpobj) {
1066  $this->_cleanObjectDatas($tmpobj);
1067  }
1068 
1069  return $this->_cleanObjectDatas($product_fourn_list);
1070  }
1071 
1089  public function getAttributes($sortfield = "t.ref", $sortorder = 'ASC', $limit = 100, $page = 0, $sqlfilters = '', $properties = '')
1090  {
1091  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
1092  throw new RestException(403);
1093  }
1094 
1095  $sql = "SELECT t.rowid, t.ref, t.ref_ext, t.label, t.position, t.entity";
1096  $sql .= " FROM ".$this->db->prefix()."product_attribute as t";
1097  $sql .= ' WHERE t.entity IN ('.getEntity('product').')';
1098 
1099  // Add sql filters
1100  if ($sqlfilters) {
1101  $errormessage = '';
1102  $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
1103  if ($errormessage) {
1104  throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
1105  }
1106  }
1107 
1108  $sql .= $this->db->order($sortfield, $sortorder);
1109  if ($limit) {
1110  if ($page < 0) {
1111  $page = 0;
1112  }
1113  $offset = $limit * $page;
1114 
1115  $sql .= $this->db->plimit($limit, $offset);
1116  }
1117 
1118  $resql = $this->db->query($sql);
1119 
1120  if (!$resql) {
1121  throw new RestException(503, 'Error when retrieving product attribute list : '.$this->db->lasterror());
1122  }
1123 
1124  $return = array();
1125  while ($obj = $this->db->fetch_object($resql)) {
1126  $tmp = new ProductAttribute($this->db);
1127  $tmp->id = $obj->rowid;
1128  $tmp->ref = $obj->ref;
1129  $tmp->ref_ext = $obj->ref_ext;
1130  $tmp->label = $obj->label;
1131  $tmp->position = $obj->position;
1132  $tmp->entity = $obj->entity;
1133 
1134  $return[] = $this->_filterObjectProperties($this->_cleanObjectDatas($tmp), $properties);
1135  }
1136 
1137  return $return;
1138  }
1139 
1151  public function getAttributeById($id)
1152  {
1153  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
1154  throw new RestException(403);
1155  }
1156 
1157  $prodattr = new ProductAttribute($this->db);
1158  $result = $prodattr->fetch((int) $id);
1159 
1160  if ($result < 0) {
1161  throw new RestException(404, "Product attribute not found");
1162  }
1163 
1164  $fields = ["id", "ref", "ref_ext", "label", "position", "entity"];
1165 
1166  foreach ($prodattr as $field => $value) {
1167  if (!in_array($field, $fields)) {
1168  unset($prodattr->{$field});
1169  }
1170  }
1171 
1172  $sql = "SELECT COUNT(*) as nb FROM ".$this->db->prefix()."product_attribute_combination2val as pac2v";
1173  $sql .= " JOIN ".$this->db->prefix()."product_attribute_combination as pac ON pac2v.fk_prod_combination = pac.rowid";
1174  $sql .= " WHERE pac2v.fk_prod_attr = ".((int) $prodattr->id)." AND pac.entity IN (".getEntity('product').")";
1175 
1176  $resql = $this->db->query($sql);
1177  $obj = $this->db->fetch_object($resql);
1178  $prodattr->is_used_by_products = (int) $obj->nb;
1179 
1180  return $this->_cleanObjectDatas($prodattr);
1181  }
1182 
1194  public function getAttributesByRef($ref)
1195  {
1196  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
1197  throw new RestException(403);
1198  }
1199 
1200  $ref = trim($ref);
1201 
1202  $sql = "SELECT rowid, ref, ref_ext, label, position, entity FROM ".$this->db->prefix()."product_attribute WHERE ref LIKE '".$this->db->escape($ref)."' AND entity IN (".getEntity('product').")";
1203 
1204  $query = $this->db->query($sql);
1205 
1206  if (!$this->db->num_rows($query)) {
1207  throw new RestException(404);
1208  }
1209 
1210  $result = $this->db->fetch_object($query);
1211 
1212  $attr = array();
1213  $attr['id'] = $result->rowid;
1214  $attr['ref'] = $result->ref;
1215  $attr['ref_ext'] = $result->ref_ext;
1216  $attr['label'] = $result->label;
1217  $attr['rang'] = $result->position;
1218  $attr['position'] = $result->position;
1219  $attr['entity'] = $result->entity;
1220 
1221  $sql = "SELECT COUNT(*) as nb FROM ".$this->db->prefix()."product_attribute_combination2val as pac2v";
1222  $sql .= " JOIN ".$this->db->prefix()."product_attribute_combination as pac ON pac2v.fk_prod_combination = pac.rowid";
1223  $sql .= " WHERE pac2v.fk_prod_attr = ".((int) $result->rowid)." AND pac.entity IN (".getEntity('product').")";
1224 
1225  $resql = $this->db->query($sql);
1226  $obj = $this->db->fetch_object($resql);
1227 
1228  $attr["is_used_by_products"] = (int) $obj->nb;
1229 
1230  return $attr;
1231  }
1232 
1244  public function getAttributesByRefExt($ref_ext)
1245  {
1246  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
1247  throw new RestException(403);
1248  }
1249 
1250  $ref_ext = trim($ref_ext);
1251 
1252  $sql = "SELECT rowid, ref, ref_ext, label, position, entity FROM ".$this->db->prefix()."product_attribute WHERE ref_ext LIKE '".$this->db->escape($ref_ext)."' AND entity IN (".getEntity('product').")";
1253 
1254  $query = $this->db->query($sql);
1255 
1256  if (!$this->db->num_rows($query)) {
1257  throw new RestException(404);
1258  }
1259 
1260  $result = $this->db->fetch_object($query);
1261 
1262  $attr = array();
1263  $attr['id'] = $result->rowid;
1264  $attr['ref'] = $result->ref;
1265  $attr['ref_ext'] = $result->ref_ext;
1266  $attr['label'] = $result->label;
1267  $attr['rang'] = $result->position;
1268  $attr['position'] = $result->position;
1269  $attr['entity'] = $result->entity;
1270 
1271  $sql = "SELECT COUNT(*) as nb FROM ".$this->db->prefix()."product_attribute_combination2val as pac2v";
1272  $sql .= " JOIN ".$this->db->prefix()."product_attribute_combination as pac ON pac2v.fk_prod_combination = pac.rowid";
1273  $sql .= " WHERE pac2v.fk_prod_attr = ".((int) $result->rowid)." AND pac.entity IN (".getEntity('product').")";
1274 
1275  $resql = $this->db->query($sql);
1276  $obj = $this->db->fetch_object($resql);
1277 
1278  $attr["is_used_by_products"] = (int) $obj->nb;
1279 
1280  return $attr;
1281  }
1282 
1296  public function addAttributes($ref, $label, $ref_ext = '')
1297  {
1298  if (!DolibarrApiAccess::$user->hasRight('produit', 'creer')) {
1299  throw new RestException(403);
1300  }
1301 
1302  $prodattr = new ProductAttribute($this->db);
1303  $prodattr->label = $label;
1304  $prodattr->ref = $ref;
1305  $prodattr->ref_ext = $ref_ext;
1306 
1307  $resid = $prodattr->create(DolibarrApiAccess::$user);
1308  if ($resid <= 0) {
1309  throw new RestException(500, "Error creating new attribute");
1310  }
1311 
1312  return $resid;
1313  }
1314 
1328  public function putAttributes($id, $request_data = null)
1329  {
1330  if (!DolibarrApiAccess::$user->hasRight('produit', 'creer')) {
1331  throw new RestException(403);
1332  }
1333 
1334  $prodattr = new ProductAttribute($this->db);
1335 
1336  $result = $prodattr->fetch((int) $id);
1337  if ($result == 0) {
1338  throw new RestException(404, 'Attribute not found');
1339  } elseif ($result < 0) {
1340  throw new RestException(500, "Error fetching attribute");
1341  }
1342 
1343  foreach ($request_data as $field => $value) {
1344  if ($field == 'rowid') {
1345  continue;
1346  }
1347  if ($field === 'caller') {
1348  // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller
1349  $prodattr->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
1350  continue;
1351  }
1352 
1353  $prodattr->$field = $this->_checkValForAPI($field, $value, $prodattr);
1354  }
1355 
1356  if ($prodattr->update(DolibarrApiAccess::$user) > 0) {
1357  $result = $prodattr->fetch((int) $id);
1358  if ($result == 0) {
1359  throw new RestException(404, 'Attribute not found');
1360  } elseif ($result < 0) {
1361  throw new RestException(500, "Error fetching attribute");
1362  } else {
1363  return $this->_cleanObjectDatas($prodattr);
1364  }
1365  }
1366  throw new RestException(500, "Error updating attribute");
1367  }
1368 
1380  public function deleteAttributes($id)
1381  {
1382  if (!DolibarrApiAccess::$user->hasRight('produit', 'supprimer')) {
1383  throw new RestException(403);
1384  }
1385 
1386  $prodattr = new ProductAttribute($this->db);
1387  $prodattr->id = (int) $id;
1388  $result = $prodattr->delete(DolibarrApiAccess::$user);
1389 
1390  if ($result <= 0) {
1391  throw new RestException(500, "Error deleting attribute");
1392  }
1393 
1394  return $result;
1395  }
1396 
1408  public function getAttributeValueById($id)
1409  {
1410  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
1411  throw new RestException(403);
1412  }
1413 
1414  $sql = "SELECT rowid, fk_product_attribute, ref, value FROM ".$this->db->prefix()."product_attribute_value WHERE rowid = ".(int) $id." AND entity IN (".getEntity('product').")";
1415 
1416  $query = $this->db->query($sql);
1417 
1418  if (!$query) {
1419  throw new RestException(403);
1420  }
1421 
1422  if (!$this->db->num_rows($query)) {
1423  throw new RestException(404, 'Attribute value not found');
1424  }
1425 
1426  $result = $this->db->fetch_object($query);
1427 
1428  $attrval = array();
1429  $attrval['id'] = $result->rowid;
1430  $attrval['fk_product_attribute'] = $result->fk_product_attribute;
1431  $attrval['ref'] = $result->ref;
1432  $attrval['value'] = $result->value;
1433 
1434  return $attrval;
1435  }
1436 
1449  public function getAttributeValueByRef($id, $ref)
1450  {
1451  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
1452  throw new RestException(403);
1453  }
1454 
1455  $ref = trim($ref);
1456 
1457  $sql = "SELECT rowid, fk_product_attribute, ref, value FROM ".$this->db->prefix()."product_attribute_value";
1458  $sql .= " WHERE ref LIKE '".$this->db->escape($ref)."' AND fk_product_attribute = ".((int) $id)." AND entity IN (".getEntity('product').")";
1459 
1460  $query = $this->db->query($sql);
1461 
1462  if (!$query) {
1463  throw new RestException(403);
1464  }
1465 
1466  if (!$this->db->num_rows($query)) {
1467  throw new RestException(404, 'Attribute value not found');
1468  }
1469 
1470  $result = $this->db->fetch_object($query);
1471 
1472  $attrval = array();
1473  $attrval['id'] = $result->rowid;
1474  $attrval['fk_product_attribute'] = $result->fk_product_attribute;
1475  $attrval['ref'] = $result->ref;
1476  $attrval['value'] = $result->value;
1477 
1478  return $attrval;
1479  }
1480 
1492  public function deleteAttributeValueByRef($id, $ref)
1493  {
1494  if (!DolibarrApiAccess::$user->hasRight('produit', 'supprimer')) {
1495  throw new RestException(403);
1496  }
1497 
1498  $ref = trim($ref);
1499 
1500  $sql = "SELECT rowid FROM ".$this->db->prefix()."product_attribute_value";
1501  $sql .= " WHERE ref LIKE '".$this->db->escape($ref)."' AND fk_product_attribute = ".((int) $id)." AND entity IN (".getEntity('product').")";
1502  $query = $this->db->query($sql);
1503 
1504  if (!$query) {
1505  throw new RestException(403);
1506  }
1507 
1508  if (!$this->db->num_rows($query)) {
1509  throw new RestException(404, 'Attribute value not found');
1510  }
1511 
1512  $result = $this->db->fetch_object($query);
1513 
1514  $attrval = new ProductAttributeValue($this->db);
1515  $attrval->id = $result->rowid;
1516  $result = $attrval->delete(DolibarrApiAccess::$user);
1517  if ($result > 0) {
1518  return 1;
1519  }
1520 
1521  throw new RestException(500, "Error deleting attribute value");
1522  }
1523 
1535  public function getAttributeValues($id)
1536  {
1537  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
1538  throw new RestException(403);
1539  }
1540 
1541  $objectval = new ProductAttributeValue($this->db);
1542 
1543  $return = $objectval->fetchAllByProductAttribute((int) $id);
1544 
1545  if (count($return) == 0) {
1546  throw new RestException(404, 'Attribute values not found');
1547  }
1548 
1549  foreach ($return as $key => $val) {
1550  $return[$key] = $this->_cleanObjectDatas($return[$key]);
1551  }
1552 
1553  return $return;
1554  }
1555 
1566  public function getAttributeValuesByRef($ref)
1567  {
1568  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
1569  throw new RestException(403);
1570  }
1571 
1572  $ref = trim($ref);
1573 
1574  $return = array();
1575 
1576  $sql = "SELECT ";
1577  $sql .= "v.fk_product_attribute, v.rowid, v.ref, v.value FROM ".$this->db->prefix()."product_attribute_value as v";
1578  $sql .= " WHERE v.fk_product_attribute IN (SELECT rowid FROM ".$this->db->prefix()."product_attribute WHERE ref LIKE '".$this->db->escape($ref)."')";
1579 
1580  $resql = $this->db->query($sql);
1581 
1582  while ($result = $this->db->fetch_object($resql)) {
1583  $tmp = new ProductAttributeValue($this->db);
1584  $tmp->fk_product_attribute = $result->fk_product_attribute;
1585  $tmp->id = $result->rowid;
1586  $tmp->ref = $result->ref;
1587  $tmp->value = $result->value;
1588 
1589  $return[] = $this->_cleanObjectDatas($tmp);
1590  }
1591 
1592  return $return;
1593  }
1594 
1608  public function addAttributeValue($id, $ref, $value)
1609  {
1610  if (!DolibarrApiAccess::$user->hasRight('produit', 'creer')) {
1611  throw new RestException(403);
1612  }
1613 
1614  if (empty($ref) || empty($value)) {
1615  throw new RestException(403);
1616  }
1617 
1618  $objectval = new ProductAttributeValue($this->db);
1619  $objectval->fk_product_attribute = ((int) $id);
1620  $objectval->ref = $ref;
1621  $objectval->value = $value;
1622 
1623  if ($objectval->create(DolibarrApiAccess::$user) > 0) {
1624  return $objectval->id;
1625  }
1626  throw new RestException(500, "Error creating new attribute value");
1627  }
1628 
1641  public function putAttributeValue($id, $request_data)
1642  {
1643  if (!DolibarrApiAccess::$user->hasRight('produit', 'creer')) {
1644  throw new RestException(403);
1645  }
1646 
1647  $objectval = new ProductAttributeValue($this->db);
1648  $result = $objectval->fetch((int) $id);
1649 
1650  if ($result == 0) {
1651  throw new RestException(404, 'Attribute value not found');
1652  } elseif ($result < 0) {
1653  throw new RestException(500, "Error fetching attribute value");
1654  }
1655 
1656  foreach ($request_data as $field => $value) {
1657  if ($field == 'rowid') {
1658  continue;
1659  }
1660  if ($field === 'caller') {
1661  // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller
1662  $objectval->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
1663  continue;
1664  }
1665 
1666  $objectval->$field = $this->_checkValForAPI($field, $value, $objectval);
1667  }
1668 
1669  if ($objectval->update(DolibarrApiAccess::$user) > 0) {
1670  $result = $objectval->fetch((int) $id);
1671  if ($result == 0) {
1672  throw new RestException(404, 'Attribute not found');
1673  } elseif ($result < 0) {
1674  throw new RestException(500, "Error fetching attribute");
1675  } else {
1676  return $this->_cleanObjectDatas($objectval);
1677  }
1678  }
1679  throw new RestException(500, "Error updating attribute");
1680  }
1681 
1693  public function deleteAttributeValueById($id)
1694  {
1695  if (!DolibarrApiAccess::$user->hasRight('produit', 'supprimer')) {
1696  throw new RestException(403);
1697  }
1698 
1699  $objectval = new ProductAttributeValue($this->db);
1700  $objectval->id = (int) $id;
1701 
1702  if ($objectval->delete(DolibarrApiAccess::$user) > 0) {
1703  return 1;
1704  }
1705  throw new RestException(500, "Error deleting attribute value");
1706  }
1707 
1720  public function getVariants($id, $includestock = 0)
1721  {
1722  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
1723  throw new RestException(403);
1724  }
1725 
1726  $prodcomb = new ProductCombination($this->db);
1727  $combinations = $prodcomb->fetchAllByFkProductParent((int) $id);
1728 
1729  foreach ($combinations as $key => $combination) {
1730  $prodc2vp = new ProductCombination2ValuePair($this->db);
1731  $combinations[$key]->attributes = $prodc2vp->fetchByFkCombination((int) $combination->id);
1732  $combinations[$key] = $this->_cleanObjectDatas($combinations[$key]);
1733 
1734  if (!empty($includestock) && DolibarrApiAccess::$user->hasRight('stock', 'lire')) {
1735  $productModel = new Product($this->db);
1736  $productModel->fetch((int) $combination->fk_product_child);
1737  $productModel->load_stock($includestock);
1738  $combinations[$key]->stock_warehouse = $this->_cleanObjectDatas($productModel)->stock_warehouse;
1739  }
1740  }
1741 
1742  return $combinations;
1743  }
1744 
1756  public function getVariantsByProdRef($ref)
1757  {
1758  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
1759  throw new RestException(403);
1760  }
1761 
1762  $result = $this->product->fetch('', $ref);
1763  if (!$result) {
1764  throw new RestException(404, 'Product not found');
1765  }
1766 
1767  $prodcomb = new ProductCombination($this->db);
1768  $combinations = $prodcomb->fetchAllByFkProductParent((int) $this->product->id);
1769 
1770  foreach ($combinations as $key => $combination) {
1771  $prodc2vp = new ProductCombination2ValuePair($this->db);
1772  $combinations[$key]->attributes = $prodc2vp->fetchByFkCombination((int) $combination->id);
1773  $combinations[$key] = $this->_cleanObjectDatas($combinations[$key]);
1774  }
1775 
1776  return $combinations;
1777  }
1778 
1799  public function addVariant($id, $weight_impact, $price_impact, $price_impact_is_percent, $features, $reference = '', $ref_ext = '')
1800  {
1801  if (!DolibarrApiAccess::$user->hasRight('produit', 'creer')) {
1802  throw new RestException(403);
1803  }
1804 
1805  if (empty($id)) {
1806  throw new RestException(400, 'Product ID is mandatory');
1807  }
1808 
1809  if (empty($features) || !is_array($features)) {
1810  throw new RestException(400, 'Features is mandatory and should be IDs of attribute values indexed by IDs of attributes');
1811  }
1812 
1813  $weight_impact = price2num($weight_impact);
1814  $price_impact = price2num($price_impact);
1815 
1816  $prodattr = new ProductAttribute($this->db);
1817  $prodattr_val = new ProductAttributeValue($this->db);
1818  foreach ($features as $id_attr => $id_value) {
1819  if ($prodattr->fetch((int) $id_attr) < 0) {
1820  throw new RestException(400, 'Invalid attribute ID: '.$id_attr);
1821  }
1822  if ($prodattr_val->fetch((int) $id_value) < 0) {
1823  throw new RestException(400, 'Invalid attribute value ID: '.$id_value);
1824  }
1825  }
1826 
1827  $result = $this->product->fetch((int) $id);
1828  if (!$result) {
1829  throw new RestException(404, 'Product not found');
1830  }
1831 
1832  $prodcomb = new ProductCombination($this->db);
1833 
1834  $result = $prodcomb->createProductCombination(DolibarrApiAccess::$user, $this->product, $features, array(), $price_impact_is_percent, $price_impact, $weight_impact, $reference, $ref_ext);
1835  if ($result > 0) {
1836  return $result;
1837  } else {
1838  throw new RestException(500, "Error creating new product variant");
1839  }
1840  }
1841 
1860  public function addVariantByProductRef($ref, $weight_impact, $price_impact, $price_impact_is_percent, $features)
1861  {
1862  if (!DolibarrApiAccess::$user->hasRight('produit', 'creer')) {
1863  throw new RestException(403);
1864  }
1865 
1866  if (empty($ref) || empty($features) || !is_array($features)) {
1867  throw new RestException(403);
1868  }
1869 
1870  $weight_impact = price2num($weight_impact);
1871  $price_impact = price2num($price_impact);
1872 
1873  $prodattr = new ProductAttribute($this->db);
1874  $prodattr_val = new ProductAttributeValue($this->db);
1875  foreach ($features as $id_attr => $id_value) {
1876  if ($prodattr->fetch((int) $id_attr) < 0) {
1877  throw new RestException(404);
1878  }
1879  if ($prodattr_val->fetch((int) $id_value) < 0) {
1880  throw new RestException(404);
1881  }
1882  }
1883 
1884  $result = $this->product->fetch('', trim($ref));
1885  if (!$result) {
1886  throw new RestException(404, 'Product not found');
1887  }
1888 
1889  $prodcomb = new ProductCombination($this->db);
1890  if (!$prodcomb->fetchByProductCombination2ValuePairs($this->product->id, $features)) {
1891  $result = $prodcomb->createProductCombination(DolibarrApiAccess::$user, $this->product, $features, array(), $price_impact_is_percent, $price_impact, $weight_impact);
1892  if ($result > 0) {
1893  return $result;
1894  } else {
1895  throw new RestException(500, "Error creating new product variant");
1896  }
1897  } else {
1898  return $prodcomb->id;
1899  }
1900  }
1901 
1914  public function putVariant($id, $request_data = null)
1915  {
1916  if (!DolibarrApiAccess::$user->hasRight('produit', 'creer')) {
1917  throw new RestException(403);
1918  }
1919 
1920  $prodcomb = new ProductCombination($this->db);
1921  $prodcomb->fetch((int) $id);
1922 
1923  foreach ($request_data as $field => $value) {
1924  if ($field == 'rowid') {
1925  continue;
1926  }
1927  if ($field === 'caller') {
1928  // Add a mention of caller so on trigger called after action, we can filter to avoid a loop if we try to sync back again with the caller
1929  $prodcomb->context['caller'] = sanitizeVal($request_data['caller'], 'aZ09');
1930  continue;
1931  }
1932 
1933  $prodcomb->$field = $this->_checkValForAPI($field, $value, $prodcomb);
1934  }
1935 
1936  $result = $prodcomb->update(DolibarrApiAccess::$user);
1937  if ($result > 0) {
1938  return 1;
1939  }
1940  throw new RestException(500, "Error editing variant");
1941  }
1942 
1954  public function deleteVariant($id)
1955  {
1956  if (!DolibarrApiAccess::$user->hasRight('produit', 'supprimer')) {
1957  throw new RestException(403);
1958  }
1959 
1960  $prodcomb = new ProductCombination($this->db);
1961  $prodcomb->id = (int) $id;
1962  $result = $prodcomb->delete(DolibarrApiAccess::$user);
1963  if ($result <= 0) {
1964  throw new RestException(500, "Error deleting variant");
1965  }
1966  return $result;
1967  }
1968 
1983  public function getStock($id, $selected_warehouse_id = null)
1984  {
1985  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire') || !DolibarrApiAccess::$user->hasRight('stock', 'lire')) {
1986  throw new RestException(403);
1987  }
1988 
1989  if (!DolibarrApi::_checkAccessToResource('product', $id)) {
1990  throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
1991  }
1992 
1993  $product_model = new Product($this->db);
1994  $product_model->fetch($id);
1995  $product_model->load_stock();
1996 
1997  $stockData = $this->_cleanObjectDatas($product_model)->stock_warehouse;
1998  if ($selected_warehouse_id) {
1999  foreach ($stockData as $warehouse_id => $warehouse) {
2000  if ($warehouse_id != $selected_warehouse_id) {
2001  unset($stockData[$warehouse_id]);
2002  }
2003  }
2004  }
2005 
2006  return array('stock_warehouses'=>$stockData);
2007  }
2008 
2009  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
2016  protected function _cleanObjectDatas($object)
2017  {
2018  // phpcs:enable
2019  $object = parent::_cleanObjectDatas($object);
2020 
2021  unset($object->statut);
2022 
2023  unset($object->regeximgext);
2024  unset($object->price_by_qty);
2025  unset($object->prices_by_qty_id);
2026  unset($object->libelle);
2027  unset($object->product_id_already_linked);
2028  unset($object->reputations);
2029  unset($object->db);
2030  unset($object->name);
2031  unset($object->firstname);
2032  unset($object->lastname);
2033  unset($object->civility_id);
2034  unset($object->contact);
2035  unset($object->contact_id);
2036  unset($object->thirdparty);
2037  unset($object->user);
2038  unset($object->origin);
2039  unset($object->origin_id);
2040  unset($object->fourn_pu);
2041  unset($object->fourn_price_base_type);
2042  unset($object->fourn_socid);
2043  unset($object->ref_fourn);
2044  unset($object->ref_supplier);
2045  unset($object->product_fourn_id);
2046  unset($object->fk_project);
2047 
2048  unset($object->mode_reglement_id);
2049  unset($object->cond_reglement_id);
2050  unset($object->demand_reason_id);
2051  unset($object->transport_mode_id);
2052  unset($object->cond_reglement);
2053  unset($object->shipping_method_id);
2054  unset($object->model_pdf);
2055  unset($object->note);
2056 
2057  unset($object->nbphoto);
2058  unset($object->recuperableonly);
2059  unset($object->multiprices_recuperableonly);
2060  unset($object->tva_npr);
2061  unset($object->lines);
2062  unset($object->fk_bank);
2063  unset($object->fk_account);
2064 
2065  unset($object->supplierprices); // Must use another API to get them
2066 
2067  if (!DolibarrApiAccess::$user->hasRight('stock', 'lire')) {
2068  unset($object->stock_reel);
2069  unset($object->stock_theorique);
2070  unset($object->stock_warehouse);
2071  }
2072 
2073  return $object;
2074  }
2075 
2083  private function _validate($data)
2084  {
2085  $product = array();
2086  foreach (Products::$FIELDS as $field) {
2087  if (!isset($data[$field])) {
2088  throw new RestException(400, "$field field missing");
2089  }
2090  $product[$field] = $data[$field];
2091  }
2092  return $product;
2093  }
2094 
2114  private function _fetch($id, $ref = '', $ref_ext = '', $barcode = '', $includestockdata = 0, $includesubproducts = false, $includeparentid = false, $includeifobjectisused = false, $includetrans = false)
2115  {
2116  if (empty($id) && empty($ref) && empty($ref_ext) && empty($barcode)) {
2117  throw new RestException(400, 'bad value for parameter id, ref, ref_ext or barcode');
2118  }
2119 
2120  $id = (empty($id) ? 0 : $id);
2121 
2122  if (!DolibarrApiAccess::$user->hasRight('produit', 'lire')) {
2123  throw new RestException(403);
2124  }
2125 
2126  $result = $this->product->fetch($id, $ref, $ref_ext, $barcode, 0, 0, ($includetrans ? 0 : 1));
2127  if (!$result) {
2128  throw new RestException(404, 'Product not found');
2129  }
2130 
2131  if (!DolibarrApi::_checkAccessToResource('product', $this->product->id)) {
2132  throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
2133  }
2134 
2135  if (!empty($includestockdata) && DolibarrApiAccess::$user->hasRight('stock', 'lire')) {
2136  $this->product->load_stock($includestockdata);
2137 
2138  if (is_array($this->product->stock_warehouse)) {
2139  foreach ($this->product->stock_warehouse as $keytmp => $valtmp) {
2140  if (isset($this->product->stock_warehouse[$keytmp]->detail_batch) && is_array($this->product->stock_warehouse[$keytmp]->detail_batch)) {
2141  foreach ($this->product->stock_warehouse[$keytmp]->detail_batch as $keytmp2 => $valtmp2) {
2142  unset($this->product->stock_warehouse[$keytmp]->detail_batch[$keytmp2]->db);
2143  }
2144  }
2145  }
2146  }
2147  }
2148 
2149  if ($includesubproducts) {
2150  $childrenArbo = $this->product->getChildsArbo($id, 1);
2151 
2152  $keys = array('rowid', 'qty', 'fk_product_type', 'label', 'incdec', 'ref', 'fk_association', 'rang');
2153  $children = array();
2154  foreach ($childrenArbo as $values) {
2155  $children[] = array_combine($keys, $values);
2156  }
2157 
2158  $this->product->sousprods = $children;
2159  }
2160 
2161  if ($includeparentid) {
2162  $prodcomb = new ProductCombination($this->db);
2163  $this->product->fk_product_parent = null;
2164  if (($fk_product_parent = $prodcomb->fetchByFkProductChild($this->product->id)) > 0) {
2165  $this->product->fk_product_parent = $fk_product_parent;
2166  }
2167  }
2168 
2169  if ($includeifobjectisused) {
2170  $this->product->is_object_used = ($this->product->isObjectUsed() > 0);
2171  }
2172 
2173  return $this->_cleanObjectDatas($this->product);
2174  }
2175 }
if($user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition: card.php:58
Class to manage categories.
Class for API REST v1.
Definition: api.class.php:30
_filterObjectProperties($object, $properties)
Filter properties that will be returned on object.
Definition: api.class.php:136
static _checkAccessToResource($resource, $resource_id=0, $dbtablename='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid')
Check access by user to a given resource.
Definition: api.class.php:369
_checkValForAPI($field, $value, $object)
Check and convert a string depending on its type/name.
Definition: api.class.php:82
Class to manage suppliers.
Class ProductAttribute Used to represent a Product attribute Examples:
Class ProductAttributeValue Used to represent a product attribute value.
Class ProductCombination2ValuePair Used to represent the relation between a variant and its attribute...
Class ProductCombination Used to represent the relation between a product and one of its variants.
File of class to manage predefined price products or services by customer.
Class to manage predefined suppliers products.
Class to manage products or services.
_cleanObjectDatas($object)
Clean sensible object datas.
getAttributes($sortfield="t.ref", $sortorder='ASC', $limit=100, $page=0, $sqlfilters='', $properties='')
Get attributes.
putAttributeValue($id, $request_data)
Update attribute value.
deleteAttributes($id)
Delete attributes by id.
deletePurchasePrice($id, $priceid)
Delete purchase price for a product.
getAttributeValuesByRef($ref)
Get all values for an attribute ref.
putVariant($id, $request_data=null)
Put product variants.
index($sortfield="t.ref", $sortorder='ASC', $limit=100, $page=0, $mode=0, $category=0, $sqlfilters='', $ids_only=false, $variant_filter=0, $pagination_data=false, $includestockdata=0, $properties='')
List products.
put($id, $request_data=null)
Update product.
addAttributeValue($id, $ref, $value)
Add attribute value.
addVariantByProductRef($ref, $weight_impact, $price_impact, $price_impact_is_percent, $features)
Add variant by product ref.
getAttributeValueById($id)
Get attribute value by id.
getVariantsByProdRef($ref)
Get product variants by Product ref.
getCustomerPricesPerQuantity($id)
Get prices per quantity for a product.
__construct()
Constructor.
getPurchasePrices($id, $ref='', $ref_ext='', $barcode='')
Get purchase prices for a product.
delSubproducts($id, $subproduct_id)
Remove subproduct.
getVariants($id, $includestock=0)
Get product variants.
putAttributes($id, $request_data=null)
Update attributes by id.
deleteAttributeValueById($id)
Delete attribute value by id.
getAttributeValues($id)
Get all values for an attribute id.
addVariant($id, $weight_impact, $price_impact, $price_impact_is_percent, $features, $reference='', $ref_ext='')
Add variant.
getSubproducts($id)
Get the list of subproducts of the product.
getAttributesByRefExt($ref_ext)
Get attributes by ref_ext.
getByRef($ref, $includestockdata=0, $includesubproducts=false, $includeparentid=false, $includetrans=false)
Get properties of a product object by ref.
deleteAttributeValueByRef($id, $ref)
Delete attribute value by ref.
post($request_data=null)
Create product object.
addPurchasePrice($id, $qty, $buyprice, $price_base_type, $fourn_id, $availability, $ref_fourn, $tva_tx, $charges=0, $remise_percent=0, $remise=0, $newnpr=0, $delivery_time_days=0, $supplier_reputation='', $localtaxes_array=array(), $newdefaultvatcode='', $multicurrency_buyprice=0, $multicurrency_price_base_type='HT', $multicurrency_tx=1, $multicurrency_code='', $desc_fourn='', $barcode='', $fk_barcode_type=null)
Add/Update purchase prices for a product.
getCategories($id, $sortfield="s.rowid", $sortorder='ASC', $limit=0, $page=0)
Get categories for a product.
getByBarcode($barcode, $includestockdata=0, $includesubproducts=false, $includeparentid=false, $includetrans=false)
Get properties of a product object by barcode.
getByRefExt($ref_ext, $includestockdata=0, $includesubproducts=false, $includeparentid=false, $includetrans=false)
Get properties of a product object by ref_ext.
getCustomerPricesPerCustomer($id, $thirdparty_id='')
Get prices per customer for a product.
deleteVariant($id)
Delete product variants.
_validate($data)
Validate fields before create or update object.
getCustomerPricesPerSegment($id)
Get prices per segment for a product.
_fetch($id, $ref='', $ref_ext='', $barcode='', $includestockdata=0, $includesubproducts=false, $includeparentid=false, $includeifobjectisused=false, $includetrans=false)
Get properties of 1 product object.
addAttributes($ref, $label, $ref_ext='')
Add attributes.
getAttributeValueByRef($id, $ref)
Get attribute value by ref.
getSupplierProducts($sortfield="t.ref", $sortorder='ASC', $limit=100, $page=0, $mode=0, $category=0, $supplier=0, $sqlfilters='')
Get a list of all purchase prices of products.
getAttributeById($id)
Get attribute by ID.
getStock($id, $selected_warehouse_id=null)
Get stock data for the product id given.
getAttributesByRef($ref)
Get attributes by ref.
addSubproducts($id, $subproduct_id, $qty, $incdec=1)
Add subproduct.
if(isModEnabled('invoice') && $user->hasRight('facture', 'lire')) if((isModEnabled('fournisseur') &&!getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD') && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') && $user->hasRight('don', 'lire')) if(isModEnabled('tax') && $user->hasRight('tax', 'charges', 'lire')) if(isModEnabled('invoice') &&isModEnabled('order') && $user->hasRight("commande", "lire") &&!getDolGlobalString('WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER')) $sql
Social contributions to pay.
Definition: index.php:744
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0)
forgeSQLFromUniversalSearchCriteria
dol_clone($object, $native=0)
Create a clone of instance of object (new instance with same value for each properties) With native =...
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
sanitizeVal($out='', $check='alphanohtml', $filter=null, $options=null)
Return a sanitized or empty value after checking value against a rule.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.