dolibarr 20.0.0
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
19use Luracast\Restler\RestException;
20
21require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
22require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
23require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
24require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
25require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
26require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
27require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
28
35class 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(0, $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(0, 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.
static _checkAccessToResource($resource, $resource_id=0, $dbtablename='', $feature2='', $dbt_keyfield='fk_soc', $dbt_select='rowid')
Check access by user to a given resource.
_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.
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.