api_products.class.php 64 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083
  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. use Luracast\Restler\RestException;
  19. require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
  20. require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
  21. require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
  22. require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttribute.class.php';
  23. require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductAttributeValue.class.php';
  24. require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination.class.php';
  25. require_once DOL_DOCUMENT_ROOT.'/variants/class/ProductCombination2ValuePair.class.php';
  26. /**
  27. * API class for products
  28. *
  29. * @access protected
  30. * @class DolibarrApiAccess {@requires user,external}
  31. */
  32. class Products extends DolibarrApi
  33. {
  34. /**
  35. * @var array $FIELDS Mandatory fields, checked when create and update object
  36. */
  37. public static $FIELDS = array(
  38. 'ref',
  39. 'label'
  40. );
  41. /**
  42. * @var Product $product {@type Product}
  43. */
  44. public $product;
  45. /**
  46. * @var ProductFournisseur $productsupplier {@type ProductFournisseur}
  47. */
  48. public $productsupplier;
  49. /**
  50. * Constructor
  51. */
  52. public function __construct()
  53. {
  54. global $db, $conf;
  55. $this->db = $db;
  56. $this->product = new Product($this->db);
  57. $this->productsupplier = new ProductFournisseur($this->db);
  58. }
  59. /**
  60. * Get properties of a product object by id
  61. *
  62. * Return an array with product information.
  63. *
  64. * @param int $id ID of product
  65. * @param int $includestockdata Load also information about stock (slower)
  66. * @param bool $includesubproducts Load information about subproducts
  67. * @param bool $includeparentid Load also ID of parent product (if product is a variant of a parent product)
  68. * @param bool $includetrans Load also the translations of product label and description
  69. * @return array|mixed Data without useless information
  70. *
  71. * @throws RestException 401
  72. * @throws RestException 403
  73. * @throws RestException 404
  74. */
  75. public function get($id, $includestockdata = 0, $includesubproducts = false, $includeparentid = false, $includetrans = false)
  76. {
  77. return $this->_fetch($id, '', '', '', $includestockdata, $includesubproducts, $includeparentid, false, $includetrans);
  78. }
  79. /**
  80. * Get properties of a product object by ref
  81. *
  82. * Return an array with product information.
  83. *
  84. * @param string $ref Ref of element
  85. * @param int $includestockdata Load also information about stock (slower)
  86. * @param bool $includesubproducts Load information about subproducts
  87. * @param bool $includeparentid Load also ID of parent product (if product is a variant of a parent product)
  88. * @param bool $includetrans Load also the translations of product label and description
  89. *
  90. * @return array|mixed Data without useless information
  91. *
  92. * @url GET ref/{ref}
  93. *
  94. * @throws RestException 401
  95. * @throws RestException 403
  96. * @throws RestException 404
  97. */
  98. public function getByRef($ref, $includestockdata = 0, $includesubproducts = false, $includeparentid = false, $includetrans = false)
  99. {
  100. return $this->_fetch('', $ref, '', '', $includestockdata, $includesubproducts, $includeparentid, false, $includetrans);
  101. }
  102. /**
  103. * Get properties of a product object by ref_ext
  104. *
  105. * Return an array with product information.
  106. *
  107. * @param string $ref_ext Ref_ext of element
  108. * @param int $includestockdata Load also information about stock (slower)
  109. * @param bool $includesubproducts Load information about subproducts
  110. * @param bool $includeparentid Load also ID of parent product (if product is a variant of a parent product)
  111. * @param bool $includetrans Load also the translations of product label and description
  112. *
  113. * @return array|mixed Data without useless information
  114. *
  115. * @url GET ref_ext/{ref_ext}
  116. *
  117. * @throws RestException 401
  118. * @throws RestException 403
  119. * @throws RestException 404
  120. */
  121. public function getByRefExt($ref_ext, $includestockdata = 0, $includesubproducts = false, $includeparentid = false, $includetrans = false)
  122. {
  123. return $this->_fetch('', '', $ref_ext, '', $includestockdata, $includesubproducts, $includeparentid, false, $includetrans);
  124. }
  125. /**
  126. * Get properties of a product object by barcode
  127. *
  128. * Return an array with product information.
  129. *
  130. * @param string $barcode Barcode of element
  131. * @param int $includestockdata Load also information about stock (slower)
  132. * @param bool $includesubproducts Load information about subproducts
  133. * @param bool $includeparentid Load also ID of parent product (if product is a variant of a parent product)
  134. * @param bool $includetrans Load also the translations of product label and description
  135. *
  136. * @return array|mixed Data without useless information
  137. *
  138. * @url GET barcode/{barcode}
  139. *
  140. * @throws RestException 401
  141. * @throws RestException 403
  142. * @throws RestException 404
  143. */
  144. public function getByBarcode($barcode, $includestockdata = 0, $includesubproducts = false, $includeparentid = false, $includetrans = false)
  145. {
  146. return $this->_fetch('', '', '', $barcode, $includestockdata, $includesubproducts, $includeparentid, false, $includetrans);
  147. }
  148. /**
  149. * List products
  150. *
  151. * Get a list of products
  152. *
  153. * @param string $sortfield Sort field
  154. * @param string $sortorder Sort order
  155. * @param int $limit Limit for list
  156. * @param int $page Page number
  157. * @param int $mode Use this param to filter list (0 for all, 1 for only product, 2 for only service)
  158. * @param int $category Use this param to filter list by category
  159. * @param string $sqlfilters Other criteria to filter answers separated by a comma. Syntax example "(t.tobuy:=:0) and (t.tosell:=:1)"
  160. * @param bool $ids_only Return only IDs of product instead of all properties (faster, above all if list is long)
  161. * @param int $variant_filter Use this param to filter list (0 = all, 1=products without variants, 2=parent of variants, 3=variants only)
  162. * @param bool $pagination_data If this parameter is set to true the response will include pagination data. Default value is false. Page starts from 0
  163. * @param int $includestockdata Load also information about stock (slower)
  164. * @return array Array of product objects
  165. */
  166. 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)
  167. {
  168. global $db, $conf;
  169. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  170. throw new RestException(403);
  171. }
  172. $obj_ret = array();
  173. $socid = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : '';
  174. $sql = "SELECT t.rowid, t.ref, t.ref_ext";
  175. $sql .= " FROM ".$this->db->prefix()."product as t";
  176. $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
  177. if ($category > 0) {
  178. $sql .= ", ".$this->db->prefix()."categorie_product as c";
  179. }
  180. $sql .= ' WHERE t.entity IN ('.getEntity('product').')';
  181. if ($variant_filter == 1) {
  182. $sql .= ' AND t.rowid not in (select distinct fk_product_parent from '.$this->db->prefix().'product_attribute_combination)';
  183. $sql .= ' AND t.rowid not in (select distinct fk_product_child from '.$this->db->prefix().'product_attribute_combination)';
  184. }
  185. if ($variant_filter == 2) {
  186. $sql .= ' AND t.rowid in (select distinct fk_product_parent from '.$this->db->prefix().'product_attribute_combination)';
  187. }
  188. if ($variant_filter == 3) {
  189. $sql .= ' AND t.rowid in (select distinct fk_product_child from '.$this->db->prefix().'product_attribute_combination)';
  190. }
  191. // Select products of given category
  192. if ($category > 0) {
  193. $sql .= " AND c.fk_categorie = ".((int) $category);
  194. $sql .= " AND c.fk_product = t.rowid";
  195. }
  196. if ($mode == 1) {
  197. // Show only products
  198. $sql .= " AND t.fk_product_type = 0";
  199. } elseif ($mode == 2) {
  200. // Show only services
  201. $sql .= " AND t.fk_product_type = 1";
  202. }
  203. // Add sql filters
  204. if ($sqlfilters) {
  205. $errormessage = '';
  206. if (!DolibarrApi::_checkFilters($sqlfilters, $errormessage)) {
  207. throw new RestException(503, 'Error when validating parameter sqlfilters -> '.$errormessage);
  208. }
  209. //var_dump($sqlfilters);exit;
  210. $regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^\(\)]+)\)'; // We must accept datc:<:2020-01-01 10:10:10
  211. $sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'DolibarrApi::_forge_criteria_callback', $sqlfilters).")";
  212. }
  213. //this query will return total products with the filters given
  214. $sqlTotals = str_replace('SELECT t.rowid, t.ref, t.ref_ext', 'SELECT count(t.rowid) as total', $sql);
  215. $sql .= $this->db->order($sortfield, $sortorder);
  216. if ($limit) {
  217. if ($page < 0) {
  218. $page = 0;
  219. }
  220. $offset = $limit * $page;
  221. $sql .= $this->db->plimit($limit + 1, $offset);
  222. }
  223. $result = $this->db->query($sql);
  224. if ($result) {
  225. $num = $this->db->num_rows($result);
  226. $min = min($num, ($limit <= 0 ? $num : $limit));
  227. $i = 0;
  228. while ($i < $min) {
  229. $obj = $this->db->fetch_object($result);
  230. if (!$ids_only) {
  231. $product_static = new Product($this->db);
  232. if ($product_static->fetch($obj->rowid)) {
  233. if ($includestockdata && DolibarrApiAccess::$user->rights->stock->lire) {
  234. $product_static->load_stock();
  235. if (is_array($product_static->stock_warehouse)) {
  236. foreach ($product_static->stock_warehouse as $keytmp => $valtmp) {
  237. if (is_array($product_static->stock_warehouse[$keytmp]->detail_batch)) {
  238. foreach ($product_static->stock_warehouse[$keytmp]->detail_batch as $keytmp2 => $valtmp2) {
  239. unset($product_static->stock_warehouse[$keytmp]->detail_batch[$keytmp2]->db);
  240. }
  241. }
  242. }
  243. }
  244. }
  245. $obj_ret[] = $this->_cleanObjectDatas($product_static);
  246. }
  247. } else {
  248. $obj_ret[] = $obj->rowid;
  249. }
  250. $i++;
  251. }
  252. } else {
  253. throw new RestException(503, 'Error when retrieve product list : '.$this->db->lasterror());
  254. }
  255. if (!count($obj_ret)) {
  256. throw new RestException(404, 'No product found');
  257. }
  258. //if $pagination_data is true the response will contain element data with all values and element pagination with pagination data(total,page,limit)
  259. if ($pagination_data) {
  260. $totalsResult = $this->db->query($sqlTotals);
  261. $total = $this->db->fetch_object($totalsResult)->total;
  262. $tmp = $obj_ret;
  263. $obj_ret = array();
  264. $obj_ret['data'] = $tmp;
  265. $obj_ret['pagination'] = array(
  266. 'total' => (int) $total,
  267. 'page' => $page, //count starts from 0
  268. 'page_count' => ceil((int) $total/$limit),
  269. 'limit' => $limit
  270. );
  271. }
  272. return $obj_ret;
  273. }
  274. /**
  275. * Create product object
  276. *
  277. * @param array $request_data Request data
  278. * @return int ID of product
  279. */
  280. public function post($request_data = null)
  281. {
  282. if (!DolibarrApiAccess::$user->rights->produit->creer) {
  283. throw new RestException(401);
  284. }
  285. // Check mandatory fields
  286. $result = $this->_validate($request_data);
  287. foreach ($request_data as $field => $value) {
  288. $this->product->$field = $value;
  289. }
  290. if ($this->product->create(DolibarrApiAccess::$user) < 0) {
  291. throw new RestException(500, "Error creating product", array_merge(array($this->product->error), $this->product->errors));
  292. }
  293. return $this->product->id;
  294. }
  295. /**
  296. * Update product.
  297. * Price will be updated by this API only if option is set on "One price per product". See other APIs for other price modes.
  298. *
  299. * @param int $id Id of product to update
  300. * @param array $request_data Datas
  301. * @return int
  302. *
  303. * @throws RestException 401
  304. * @throws RestException 404
  305. */
  306. public function put($id, $request_data = null)
  307. {
  308. global $conf;
  309. if (!DolibarrApiAccess::$user->rights->produit->creer) {
  310. throw new RestException(401);
  311. }
  312. $result = $this->product->fetch($id);
  313. if (!$result) {
  314. throw new RestException(404, 'Product not found');
  315. }
  316. if (!DolibarrApi::_checkAccessToResource('product', $this->product->id)) {
  317. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  318. }
  319. $oldproduct = dol_clone($this->product, 0);
  320. foreach ($request_data as $field => $value) {
  321. if ($field == 'id') {
  322. continue;
  323. }
  324. if ($field == 'stock_reel') {
  325. throw new RestException(400, 'Stock reel cannot be updated here. Use the /stockmovements endpoint instead');
  326. }
  327. $this->product->$field = $value;
  328. }
  329. $updatetype = false;
  330. if ($this->product->type != $oldproduct->type && ($this->product->isProduct() || $this->product->isService())) {
  331. $updatetype = true;
  332. }
  333. $result = $this->product->update($id, DolibarrApiAccess::$user, 1, 'update', $updatetype);
  334. // If price mode is 1 price per product
  335. if ($result > 0 && !empty($conf->global->PRODUCT_PRICE_UNIQ)) {
  336. // We update price only if it was changed
  337. $pricemodified = false;
  338. if ($this->product->price_base_type != $oldproduct->price_base_type) {
  339. $pricemodified = true;
  340. } else {
  341. if ($this->product->tva_tx != $oldproduct->tva_tx) {
  342. $pricemodified = true;
  343. }
  344. if ($this->product->tva_npr != $oldproduct->tva_npr) {
  345. $pricemodified = true;
  346. }
  347. if ($this->product->default_vat_code != $oldproduct->default_vat_code) {
  348. $pricemodified = true;
  349. }
  350. if ($this->product->price_base_type == 'TTC') {
  351. if ($this->product->price_ttc != $oldproduct->price_ttc) {
  352. $pricemodified = true;
  353. }
  354. if ($this->product->price_min_ttc != $oldproduct->price_min_ttc) {
  355. $pricemodified = true;
  356. }
  357. } else {
  358. if ($this->product->price != $oldproduct->price) {
  359. $pricemodified = true;
  360. }
  361. if ($this->product->price_min != $oldproduct->price_min) {
  362. $pricemodified = true;
  363. }
  364. }
  365. }
  366. if ($pricemodified) {
  367. $newvat = $this->product->tva_tx;
  368. $newnpr = $this->product->tva_npr;
  369. $newvatsrccode = $this->product->default_vat_code;
  370. $newprice = $this->product->price;
  371. $newpricemin = $this->product->price_min;
  372. if ($this->product->price_base_type == 'TTC') {
  373. $newprice = $this->product->price_ttc;
  374. $newpricemin = $this->product->price_min_ttc;
  375. }
  376. $result = $this->product->updatePrice($newprice, $this->product->price_base_type, DolibarrApiAccess::$user, $newvat, $newpricemin, 0, $newnpr, 0, 0, array(), $newvatsrccode);
  377. }
  378. }
  379. if ($result <= 0) {
  380. throw new RestException(500, "Error updating product", array_merge(array($this->product->error), $this->product->errors));
  381. }
  382. return $this->get($id);
  383. }
  384. /**
  385. * Delete product
  386. *
  387. * @param int $id Product ID
  388. * @return array
  389. */
  390. public function delete($id)
  391. {
  392. if (!DolibarrApiAccess::$user->rights->produit->supprimer) {
  393. throw new RestException(401);
  394. }
  395. $result = $this->product->fetch($id);
  396. if (!$result) {
  397. throw new RestException(404, 'Product not found');
  398. }
  399. if (!DolibarrApi::_checkAccessToResource('product', $this->product->id)) {
  400. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  401. }
  402. // The Product::delete() method uses the global variable $user.
  403. global $user;
  404. $user = DolibarrApiAccess::$user;
  405. return $this->product->delete(DolibarrApiAccess::$user);
  406. }
  407. /**
  408. * Get the list of subproducts of the product.
  409. *
  410. * @param int $id Id of parent product/service
  411. * @return array
  412. *
  413. * @throws RestException
  414. * @throws RestException 401
  415. * @throws RestException 404
  416. *
  417. * @url GET {id}/subproducts
  418. */
  419. public function getSubproducts($id)
  420. {
  421. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  422. throw new RestException(401);
  423. }
  424. if (!DolibarrApi::_checkAccessToResource('product', $id)) {
  425. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  426. }
  427. $childsArbo = $this->product->getChildsArbo($id, 1);
  428. $keys = array('rowid', 'qty', 'fk_product_type', 'label', 'incdec', 'ref', 'fk_association', 'rang');
  429. $childs = array();
  430. foreach ($childsArbo as $values) {
  431. $childs[] = array_combine($keys, $values);
  432. }
  433. return $childs;
  434. }
  435. /**
  436. * Add subproduct.
  437. *
  438. * Link a product/service to a parent product/service
  439. *
  440. * @param int $id Id of parent product/service
  441. * @param int $subproduct_id Id of child product/service
  442. * @param int $qty Quantity
  443. * @param int $incdec 1=Increase/decrease stock of child when parent stock increase/decrease
  444. * @return int
  445. *
  446. * @throws RestException
  447. * @throws RestException 401
  448. * @throws RestException 404
  449. *
  450. * @url POST {id}/subproducts/add
  451. */
  452. public function addSubproducts($id, $subproduct_id, $qty, $incdec = 1)
  453. {
  454. if (!DolibarrApiAccess::$user->rights->produit->creer) {
  455. throw new RestException(401);
  456. }
  457. if (!DolibarrApi::_checkAccessToResource('product', $id)) {
  458. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  459. }
  460. $result = $this->product->add_sousproduit($id, $subproduct_id, $qty, $incdec);
  461. if ($result <= 0) {
  462. throw new RestException(500, "Error adding product child");
  463. }
  464. return $result;
  465. }
  466. /**
  467. * Remove subproduct.
  468. * Unlink a product/service from a parent product/service
  469. *
  470. * @param int $id Id of parent product/service
  471. * @param int $subproduct_id Id of child product/service
  472. * @return int
  473. *
  474. * @throws RestException 401
  475. * @throws RestException 404
  476. *
  477. * @url DELETE {id}/subproducts/remove/{subproduct_id}
  478. */
  479. public function delSubproducts($id, $subproduct_id)
  480. {
  481. if (!DolibarrApiAccess::$user->rights->produit->creer) {
  482. throw new RestException(401);
  483. }
  484. if (!DolibarrApi::_checkAccessToResource('product', $id)) {
  485. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  486. }
  487. $result = $this->product->del_sousproduit($id, $subproduct_id);
  488. if ($result <= 0) {
  489. throw new RestException(500, "Error while removing product child");
  490. }
  491. return $result;
  492. }
  493. /**
  494. * Get categories for a product
  495. *
  496. * @param int $id ID of product
  497. * @param string $sortfield Sort field
  498. * @param string $sortorder Sort order
  499. * @param int $limit Limit for list
  500. * @param int $page Page number
  501. *
  502. * @return mixed
  503. *
  504. * @url GET {id}/categories
  505. */
  506. public function getCategories($id, $sortfield = "s.rowid", $sortorder = 'ASC', $limit = 0, $page = 0)
  507. {
  508. if (!DolibarrApiAccess::$user->rights->categorie->lire) {
  509. throw new RestException(401);
  510. }
  511. $categories = new Categorie($this->db);
  512. $result = $categories->getListForItem($id, 'product', $sortfield, $sortorder, $limit, $page);
  513. if (empty($result)) {
  514. throw new RestException(404, 'No category found');
  515. }
  516. if ($result < 0) {
  517. throw new RestException(503, 'Error when retrieve category list : '.array_merge(array($categories->error), $categories->errors));
  518. }
  519. return $result;
  520. }
  521. /**
  522. * Get prices per segment for a product
  523. *
  524. * @param int $id ID of product
  525. *
  526. * @return mixed
  527. *
  528. * @url GET {id}/selling_multiprices/per_segment
  529. */
  530. public function getCustomerPricesPerSegment($id)
  531. {
  532. global $conf;
  533. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  534. throw new RestException(401);
  535. }
  536. if (empty($conf->global->PRODUIT_MULTIPRICES)) {
  537. throw new RestException(400, 'API not available: this mode of pricing is not enabled by setup');
  538. }
  539. $result = $this->product->fetch($id);
  540. if (!$result) {
  541. throw new RestException(404, 'Product not found');
  542. }
  543. if ($result < 0) {
  544. throw new RestException(503, 'Error when retrieve prices list : '.array_merge(array($this->product->error), $this->product->errors));
  545. }
  546. return array(
  547. 'multiprices'=>$this->product->multiprices,
  548. 'multiprices_inc_tax'=>$this->product->multiprices_ttc,
  549. 'multiprices_min'=>$this->product->multiprices_min,
  550. 'multiprices_min_inc_tax'=>$this->product->multiprices_min_ttc,
  551. 'multiprices_vat'=>$this->product->multiprices_tva_tx,
  552. 'multiprices_base_type'=>$this->product->multiprices_base_type,
  553. //'multiprices_default_vat_code'=>$this->product->multiprices_default_vat_code
  554. );
  555. }
  556. /**
  557. * Get prices per customer for a product
  558. *
  559. * @param int $id ID of product
  560. * @param string $thirdparty_id Thirdparty id to filter orders of (example '1') {@pattern /^[0-9,]*$/i}
  561. *
  562. * @return mixed
  563. *
  564. * @url GET {id}/selling_multiprices/per_customer
  565. */
  566. public function getCustomerPricesPerCustomer($id, $thirdparty_id = '')
  567. {
  568. global $conf;
  569. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  570. throw new RestException(401);
  571. }
  572. if (empty($conf->global->PRODUIT_CUSTOMER_PRICES)) {
  573. throw new RestException(400, 'API not available: this mode of pricing is not enabled by setup');
  574. }
  575. $socid = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : '';
  576. if ($socid > 0 && $socid != $thirdparty_id) {
  577. throw new RestException(401, 'Getting prices for all customers or for the customer ID '.$thirdparty_id.' is not allowed for login '.DolibarrApiAccess::$user->login);
  578. }
  579. $result = $this->product->fetch($id);
  580. if (!$result) {
  581. throw new RestException(404, 'Product not found');
  582. }
  583. if ($result > 0) {
  584. require_once DOL_DOCUMENT_ROOT.'/product/class/productcustomerprice.class.php';
  585. $prodcustprice = new Productcustomerprice($this->db);
  586. $filter = array();
  587. $filter['t.fk_product'] .= $id;
  588. if ($thirdparty_id) {
  589. $filter['t.fk_soc'] .= $thirdparty_id;
  590. }
  591. $result = $prodcustprice->fetch_all('', '', 0, 0, $filter);
  592. }
  593. if (empty($prodcustprice->lines)) {
  594. throw new RestException(404, 'Prices not found');
  595. }
  596. return $prodcustprice->lines;
  597. }
  598. /**
  599. * Get prices per quantity for a product
  600. *
  601. * @param int $id ID of product
  602. *
  603. * @return mixed
  604. *
  605. * @url GET {id}/selling_multiprices/per_quantity
  606. */
  607. public function getCustomerPricesPerQuantity($id)
  608. {
  609. global $conf;
  610. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  611. throw new RestException(401);
  612. }
  613. if (empty($conf->global->PRODUIT_CUSTOMER_PRICES_BY_QTY)) {
  614. throw new RestException(400, 'API not available: this mode of pricing is not enabled by setup');
  615. }
  616. $result = $this->product->fetch($id);
  617. if (!$result) {
  618. throw new RestException(404, 'Product not found');
  619. }
  620. if ($result < 0) {
  621. throw new RestException(503, 'Error when retrieve prices list : '.array_merge(array($this->product->error), $this->product->errors));
  622. }
  623. return array(
  624. 'prices_by_qty'=>$this->product->prices_by_qty[0], // 1 if price by quantity was activated for the product
  625. 'prices_by_qty_list'=>$this->product->prices_by_qty_list[0]
  626. );
  627. }
  628. /**
  629. * Add/Update purchase prices for a product.
  630. *
  631. * @param int $id ID of Product
  632. * @param float $qty Min quantity for which price is valid
  633. * @param float $buyprice Purchase price for the quantity min
  634. * @param string $price_base_type HT or TTC
  635. * @param int $fourn_id Supplier ID
  636. * @param int $availability Product availability
  637. * @param string $ref_fourn Supplier ref
  638. * @param float $tva_tx New VAT Rate (For example 8.5. Should not be a string)
  639. * @param string $charges costs affering to product
  640. * @param float $remise_percent Discount regarding qty (percent)
  641. * @param float $remise Discount regarding qty (amount)
  642. * @param int $newnpr Set NPR or not
  643. * @param int $delivery_time_days Delay in days for delivery (max). May be '' if not defined.
  644. * @param string $supplier_reputation Reputation with this product to the defined supplier (empty, FAVORITE, DONOTORDER)
  645. * @param array $localtaxes_array Array with localtaxes info array('0'=>type1,'1'=>rate1,'2'=>type2,'3'=>rate2) (loaded by getLocalTaxesFromRate(vatrate, 0, ...) function).
  646. * @param string $newdefaultvatcode Default vat code
  647. * @param float $multicurrency_buyprice Purchase price for the quantity min in currency
  648. * @param string $multicurrency_price_base_type HT or TTC in currency
  649. * @param float $multicurrency_tx Rate currency
  650. * @param string $multicurrency_code Currency code
  651. * @param string $desc_fourn Custom description for product_fourn_price
  652. * @param string $barcode Barcode
  653. * @param int $fk_barcode_type Barcode type
  654. * @return int
  655. *
  656. * @throws RestException 500 System error
  657. * @throws RestException 401
  658. *
  659. * @url POST {id}/purchase_prices
  660. */
  661. 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)
  662. {
  663. if (!DolibarrApiAccess::$user->rights->produit->creer) {
  664. throw new RestException(401);
  665. }
  666. $result = $this->productsupplier->fetch($id);
  667. if (!$result) {
  668. throw new RestException(404, 'Product not found');
  669. }
  670. if (!DolibarrApi::_checkAccessToResource('product', $this->productsupplier->id)) {
  671. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  672. }
  673. $socid = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : '';
  674. if ($socid > 0 && $socid != $fourn_id) {
  675. throw new RestException(401, 'Adding purchase price for the supplier ID '.$fourn_id.' is not allowed for login '.DolibarrApiAccess::$user->login);
  676. }
  677. $result = $this->productsupplier->add_fournisseur(DolibarrApiAccess::$user, $fourn_id, $ref_fourn, $qty);
  678. if ($result < 0) {
  679. throw new RestException(500, "Error adding supplier to product : ".$this->db->lasterror());
  680. }
  681. $fourn = new Fournisseur($this->db);
  682. $result = $fourn->fetch($fourn_id);
  683. if ($result <= 0) {
  684. throw new RestException(404, 'Supplier not found');
  685. }
  686. // Clean data
  687. $ref_fourn = checkVal($ref_fourn, 'alphanohtml');
  688. $desc_fourn = checkVal($desc_fourn, 'restricthtml');
  689. $barcode = checkVal($barcode, 'alphanohtml');
  690. $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);
  691. if ($result <= 0) {
  692. throw new RestException(500, "Error updating buy price : ".$this->db->lasterror());
  693. }
  694. return (int) $this->productsupplier->product_fourn_price_id;
  695. }
  696. /**
  697. * Delete purchase price for a product
  698. *
  699. * @param int $id Product ID
  700. * @param int $priceid purchase price ID
  701. *
  702. * @url DELETE {id}/purchase_prices/{priceid}
  703. *
  704. * @return int
  705. *
  706. * @throws RestException 401
  707. * @throws RestException 404
  708. *
  709. */
  710. public function deletePurchasePrice($id, $priceid)
  711. {
  712. if (!DolibarrApiAccess::$user->rights->produit->supprimer) {
  713. throw new RestException(401);
  714. }
  715. $result = $this->productsupplier->fetch($id);
  716. if (!$result) {
  717. throw new RestException(404, 'Product not found');
  718. }
  719. if (!DolibarrApi::_checkAccessToResource('product', $this->productsupplier->id)) {
  720. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  721. }
  722. $resultsupplier = 0;
  723. if ($result > 0) {
  724. $resultsupplier = $this->productsupplier->remove_product_fournisseur_price($priceid);
  725. }
  726. return $resultsupplier;
  727. }
  728. /**
  729. * Get a list of all purchase prices of products
  730. *
  731. * @param string $sortfield Sort field
  732. * @param string $sortorder Sort order
  733. * @param int $limit Limit for list
  734. * @param int $page Page number
  735. * @param int $mode Use this param to filter list (0 for all, 1 for only product, 2 for only service)
  736. * @param int $category Use this param to filter list by category of product
  737. * @param int $supplier Use this param to filter list by supplier
  738. * @param string $sqlfilters Other criteria to filter answers separated by a comma. Syntax example "(t.tobuy:=:0) and (t.tosell:=:1)"
  739. * @return array Array of product objects
  740. *
  741. * @url GET purchase_prices
  742. */
  743. public function getSupplierProducts($sortfield = "t.ref", $sortorder = 'ASC', $limit = 100, $page = 0, $mode = 0, $category = 0, $supplier = 0, $sqlfilters = '')
  744. {
  745. global $db, $conf;
  746. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  747. throw new RestException(401);
  748. }
  749. $obj_ret = array();
  750. // Force id of company for external users
  751. $socid = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : '';
  752. if ($socid > 0) {
  753. if ($supplier != $socid || empty($supplier)) {
  754. throw new RestException(401, 'As an external user, you can request only for your supplier id = '.$socid);
  755. }
  756. }
  757. $sql = "SELECT t.rowid, t.ref, t.ref_ext";
  758. $sql .= " FROM ".$this->db->prefix()."product as t";
  759. if ($category > 0) {
  760. $sql .= ", ".$this->db->prefix()."categorie_product as c";
  761. }
  762. $sql .= ", ".$this->db->prefix()."product_fournisseur_price as s";
  763. $sql .= ' WHERE t.entity IN ('.getEntity('product').')';
  764. if ($supplier > 0) {
  765. $sql .= " AND s.fk_soc = ".((int) $supplier);
  766. }
  767. if ($socid > 0) { // if external user
  768. $sql .= " AND s.fk_soc = ".((int) $socid);
  769. }
  770. $sql .= " AND s.fk_product = t.rowid";
  771. // Select products of given category
  772. if ($category > 0) {
  773. $sql .= " AND c.fk_categorie = ".((int) $category);
  774. $sql .= " AND c.fk_product = t.rowid";
  775. }
  776. if ($mode == 1) {
  777. // Show only products
  778. $sql .= " AND t.fk_product_type = 0";
  779. } elseif ($mode == 2) {
  780. // Show only services
  781. $sql .= " AND t.fk_product_type = 1";
  782. }
  783. // Add sql filters
  784. if ($sqlfilters) {
  785. $errormessage = '';
  786. if (!DolibarrApi::_checkFilters($sqlfilters, $errormessage)) {
  787. throw new RestException(503, 'Error when validating parameter sqlfilters -> '.$errormessage);
  788. }
  789. $regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^\(\)]+)\)';
  790. $sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'DolibarrApi::_forge_criteria_callback', $sqlfilters).")";
  791. }
  792. $sql .= $this->db->order($sortfield, $sortorder);
  793. if ($limit) {
  794. if ($page < 0) {
  795. $page = 0;
  796. }
  797. $offset = $limit * $page;
  798. $sql .= $this->db->plimit($limit + 1, $offset);
  799. }
  800. $result = $this->db->query($sql);
  801. if ($result) {
  802. $num = $this->db->num_rows($result);
  803. $min = min($num, ($limit <= 0 ? $num : $limit));
  804. $i = 0;
  805. while ($i < $min) {
  806. $obj = $this->db->fetch_object($result);
  807. $product_fourn = new ProductFournisseur($this->db);
  808. $product_fourn_list = $product_fourn->list_product_fournisseur_price($obj->rowid, '', '', 0, 0);
  809. foreach ($product_fourn_list as $tmpobj) {
  810. $this->_cleanObjectDatas($tmpobj);
  811. }
  812. //var_dump($product_fourn_list->db);exit;
  813. $obj_ret[$obj->rowid] = $product_fourn_list;
  814. $i++;
  815. }
  816. } else {
  817. throw new RestException(503, 'Error when retrieve product list : '.$this->db->lasterror());
  818. }
  819. if (!count($obj_ret)) {
  820. throw new RestException(404, 'No product found');
  821. }
  822. return $obj_ret;
  823. }
  824. /**
  825. * Get purchase prices for a product
  826. *
  827. * Return an array with product information.
  828. * TODO implement getting a product by ref or by $ref_ext
  829. *
  830. * @param int $id ID of product
  831. * @param string $ref Ref of element
  832. * @param string $ref_ext Ref ext of element
  833. * @param string $barcode Barcode of element
  834. * @return array|mixed Data without useless information
  835. *
  836. * @url GET {id}/purchase_prices
  837. *
  838. * @throws RestException 401
  839. * @throws RestException 403
  840. * @throws RestException 404
  841. *
  842. */
  843. public function getPurchasePrices($id, $ref = '', $ref_ext = '', $barcode = '')
  844. {
  845. if (empty($id) && empty($ref) && empty($ref_ext) && empty($barcode)) {
  846. throw new RestException(400, 'bad value for parameter id, ref, ref_ext or barcode');
  847. }
  848. $id = (empty($id) ? 0 : $id);
  849. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  850. throw new RestException(403);
  851. }
  852. $socid = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : '';
  853. $result = $this->product->fetch($id, $ref, $ref_ext, $barcode);
  854. if (!$result) {
  855. throw new RestException(404, 'Product not found');
  856. }
  857. if (!DolibarrApi::_checkAccessToResource('product', $this->product->id)) {
  858. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  859. }
  860. $product_fourn_list = array();
  861. if ($result) {
  862. $product_fourn = new ProductFournisseur($this->db);
  863. $product_fourn_list = $product_fourn->list_product_fournisseur_price($this->product->id, '', '', 0, 0, ($socid > 0 ? $socid : 0));
  864. }
  865. foreach ($product_fourn_list as $tmpobj) {
  866. $this->_cleanObjectDatas($tmpobj);
  867. }
  868. return $this->_cleanObjectDatas($product_fourn_list);
  869. }
  870. /**
  871. * Get attributes.
  872. *
  873. * @param string $sortfield Sort field
  874. * @param string $sortorder Sort order
  875. * @param int $limit Limit for list
  876. * @param int $page Page number
  877. * @param string $sqlfilters Other criteria to filter answers separated by a comma. Syntax example "(t.ref:like:color)"
  878. * @return array
  879. *
  880. * @throws RestException 401
  881. * @throws RestException 404
  882. * @throws RestException 503
  883. *
  884. * @url GET attributes
  885. */
  886. public function getAttributes($sortfield = "t.ref", $sortorder = 'ASC', $limit = 100, $page = 0, $sqlfilters = '')
  887. {
  888. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  889. throw new RestException(401);
  890. }
  891. $sql = "SELECT t.rowid, t.ref, t.ref_ext, t.label, t.rang, t.entity";
  892. $sql .= " FROM ".$this->db->prefix()."product_attribute as t";
  893. $sql .= ' WHERE t.entity IN ('.getEntity('product').')';
  894. // Add sql filters
  895. if ($sqlfilters) {
  896. $errormessage = '';
  897. if (!DolibarrApi::_checkFilters($sqlfilters, $errormessage)) {
  898. throw new RestException(503, 'Error when validating parameter sqlfilters -> '.$errormessage);
  899. }
  900. $regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^\(\)]+)\)';
  901. $sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'DolibarrApi::_forge_criteria_callback', $sqlfilters).")";
  902. }
  903. $sql .= $this->db->order($sortfield, $sortorder);
  904. if ($limit) {
  905. if ($page < 0) {
  906. $page = 0;
  907. }
  908. $offset = $limit * $page;
  909. $sql .= $this->db->plimit($limit, $offset);
  910. }
  911. $result = $this->db->query($sql);
  912. if (!$result) {
  913. throw new RestException(503, 'Error when retrieve product attribute list : '.$this->db->lasterror());
  914. }
  915. $return = array();
  916. while ($result = $this->db->fetch_object($query)) {
  917. $tmp = new ProductAttribute($this->db);
  918. $tmp->id = $result->rowid;
  919. $tmp->ref = $result->ref;
  920. $tmp->ref_ext = $result->ref_ext;
  921. $tmp->label = $result->label;
  922. $tmp->rang = $result->rang;
  923. $tmp->entity = $result->entity;
  924. $return[] = $this->_cleanObjectDatas($tmp);
  925. }
  926. if (!count($return)) {
  927. throw new RestException(404, 'No product attribute found');
  928. }
  929. return $return;
  930. }
  931. /**
  932. * Get attribute by ID.
  933. *
  934. * @param int $id ID of Attribute
  935. * @return array
  936. *
  937. * @throws RestException 401
  938. * @throws RestException 404
  939. *
  940. * @url GET attributes/{id}
  941. */
  942. public function getAttributeById($id)
  943. {
  944. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  945. throw new RestException(401);
  946. }
  947. $prodattr = new ProductAttribute($this->db);
  948. $result = $prodattr->fetch((int) $id);
  949. if ($result < 0) {
  950. throw new RestException(404, "Product attribute not found");
  951. }
  952. $fields = ["id", "ref", "ref_ext", "label", "rang", "entity"];
  953. foreach ($prodattr as $field => $value) {
  954. if (!in_array($field, $fields)) {
  955. unset($prodattr->{$field});
  956. }
  957. }
  958. $sql = "SELECT COUNT(*) as nb FROM ".$this->db->prefix()."product_attribute_combination2val as pac2v";
  959. $sql .= " JOIN ".$this->db->prefix()."product_attribute_combination as pac ON pac2v.fk_prod_combination = pac.rowid";
  960. $sql .= " WHERE pac2v.fk_prod_attr = ".((int) $prodattr->id)." AND pac.entity IN (".getEntity('product').")";
  961. $resql = $this->db->query($sql);
  962. $obj = $this->db->fetch_object($resql);
  963. $prodattr->is_used_by_products = (int) $obj->nb;
  964. return $prodattr;
  965. }
  966. /**
  967. * Get attributes by ref.
  968. *
  969. * @param string $ref Reference of Attribute
  970. * @return array
  971. *
  972. * @throws RestException 401
  973. * @throws RestException 404
  974. *
  975. * @url GET attributes/ref/{ref}
  976. */
  977. public function getAttributesByRef($ref)
  978. {
  979. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  980. throw new RestException(401);
  981. }
  982. $ref = trim($ref);
  983. $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').")";
  984. $query = $this->db->query($sql);
  985. if (!$this->db->num_rows($query)) {
  986. throw new RestException(404);
  987. }
  988. $result = $this->db->fetch_object($query);
  989. $attr = array();
  990. $attr['id'] = $result->rowid;
  991. $attr['ref'] = $result->ref;
  992. $attr['ref_ext'] = $result->ref_ext;
  993. $attr['label'] = $result->label;
  994. $attr['rang'] = $result->position;
  995. $attr['position'] = $result->position;
  996. $attr['entity'] = $result->entity;
  997. $sql = "SELECT COUNT(*) as nb FROM ".$this->db->prefix()."product_attribute_combination2val as pac2v";
  998. $sql .= " JOIN ".$this->db->prefix()."product_attribute_combination as pac ON pac2v.fk_prod_combination = pac.rowid";
  999. $sql .= " WHERE pac2v.fk_prod_attr = ".((int) $result->rowid)." AND pac.entity IN (".getEntity('product').")";
  1000. $resql = $this->db->query($sql);
  1001. $obj = $this->db->fetch_object($resql);
  1002. $attr["is_used_by_products"] = (int) $obj->nb;
  1003. return $attr;
  1004. }
  1005. /**
  1006. * Get attributes by ref_ext.
  1007. *
  1008. * @param string $ref_ext External reference of Attribute
  1009. * @return array
  1010. *
  1011. * @throws RestException 500 System error
  1012. * @throws RestException 401
  1013. *
  1014. * @url GET attributes/ref_ext/{ref_ext}
  1015. */
  1016. public function getAttributesByRefExt($ref_ext)
  1017. {
  1018. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  1019. throw new RestException(401);
  1020. }
  1021. $ref_ext = trim($ref_ext);
  1022. $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').")";
  1023. $query = $this->db->query($sql);
  1024. if (!$this->db->num_rows($query)) {
  1025. throw new RestException(404);
  1026. }
  1027. $result = $this->db->fetch_object($query);
  1028. $attr = array();
  1029. $attr['id'] = $result->rowid;
  1030. $attr['ref'] = $result->ref;
  1031. $attr['ref_ext'] = $result->ref_ext;
  1032. $attr['label'] = $result->label;
  1033. $attr['rang'] = $result->position;
  1034. $attr['position'] = $result->position;
  1035. $attr['entity'] = $result->entity;
  1036. $sql = "SELECT COUNT(*) as nb FROM ".$this->db->prefix()."product_attribute_combination2val as pac2v";
  1037. $sql .= " JOIN ".$this->db->prefix()."product_attribute_combination as pac ON pac2v.fk_prod_combination = pac.rowid";
  1038. $sql .= " WHERE pac2v.fk_prod_attr = ".((int) $result->rowid)." AND pac.entity IN (".getEntity('product').")";
  1039. $resql = $this->db->query($sql);
  1040. $obj = $this->db->fetch_object($resql);
  1041. $attr["is_used_by_products"] = (int) $obj->nb;
  1042. return $attr;
  1043. }
  1044. /**
  1045. * Add attributes.
  1046. *
  1047. * @param string $ref Reference of Attribute
  1048. * @param string $label Label of Attribute
  1049. * @param string $ref_ext Reference of Attribute
  1050. * @return int
  1051. *
  1052. * @throws RestException 500 System error
  1053. * @throws RestException 401
  1054. *
  1055. * @url POST attributes
  1056. */
  1057. public function addAttributes($ref, $label, $ref_ext = '')
  1058. {
  1059. if (!DolibarrApiAccess::$user->rights->produit->creer) {
  1060. throw new RestException(401);
  1061. }
  1062. $prodattr = new ProductAttribute($this->db);
  1063. $prodattr->label = $label;
  1064. $prodattr->ref = $ref;
  1065. $prodattr->ref_ext = $ref_ext;
  1066. $resid = $prodattr->create(DolibarrApiAccess::$user);
  1067. if ($resid <= 0) {
  1068. throw new RestException(500, "Error creating new attribute");
  1069. }
  1070. return $resid;
  1071. }
  1072. /**
  1073. * Update attributes by id.
  1074. *
  1075. * @param int $id ID of Attribute
  1076. * @param array $request_data Datas
  1077. * @return array
  1078. *
  1079. * @throws RestException
  1080. * @throws RestException 401
  1081. * @throws RestException 404
  1082. *
  1083. * @url PUT attributes/{id}
  1084. */
  1085. public function putAttributes($id, $request_data = null)
  1086. {
  1087. if (!DolibarrApiAccess::$user->rights->produit->creer) {
  1088. throw new RestException(401);
  1089. }
  1090. $prodattr = new ProductAttribute($this->db);
  1091. $result = $prodattr->fetch((int) $id);
  1092. if ($result == 0) {
  1093. throw new RestException(404, 'Attribute not found');
  1094. } elseif ($result < 0) {
  1095. throw new RestException(500, "Error fetching attribute");
  1096. }
  1097. foreach ($request_data as $field => $value) {
  1098. if ($field == 'rowid') {
  1099. continue;
  1100. }
  1101. $prodattr->$field = $value;
  1102. }
  1103. if ($prodattr->update(DolibarrApiAccess::$user) > 0) {
  1104. $result = $prodattr->fetch((int) $id);
  1105. if ($result == 0) {
  1106. throw new RestException(404, 'Attribute not found');
  1107. } elseif ($result < 0) {
  1108. throw new RestException(500, "Error fetching attribute");
  1109. } else {
  1110. return $prodattr;
  1111. }
  1112. }
  1113. throw new RestException(500, "Error updating attribute");
  1114. }
  1115. /**
  1116. * Delete attributes by id.
  1117. *
  1118. * @param int $id ID of Attribute
  1119. * @return int Result of deletion
  1120. *
  1121. * @throws RestException 500 System error
  1122. * @throws RestException 401
  1123. *
  1124. * @url DELETE attributes/{id}
  1125. */
  1126. public function deleteAttributes($id)
  1127. {
  1128. if (!DolibarrApiAccess::$user->rights->produit->supprimer) {
  1129. throw new RestException(401);
  1130. }
  1131. $prodattr = new ProductAttribute($this->db);
  1132. $prodattr->id = (int) $id;
  1133. $result = $prodattr->delete(DolibarrApiAccess::$user);
  1134. if ($result <= 0) {
  1135. throw new RestException(500, "Error deleting attribute");
  1136. }
  1137. return $result;
  1138. }
  1139. /**
  1140. * Get attribute value by id.
  1141. *
  1142. * @param int $id ID of Attribute value
  1143. * @return array
  1144. *
  1145. * @throws RestException 500 System error
  1146. * @throws RestException 401
  1147. *
  1148. * @url GET attributes/values/{id}
  1149. */
  1150. public function getAttributeValueById($id)
  1151. {
  1152. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  1153. throw new RestException(401);
  1154. }
  1155. $sql = "SELECT rowid, fk_product_attribute, ref, value FROM ".$this->db->prefix()."product_attribute_value WHERE rowid = ".(int) $id." AND entity IN (".getEntity('product').")";
  1156. $query = $this->db->query($sql);
  1157. if (!$query) {
  1158. throw new RestException(401);
  1159. }
  1160. if (!$this->db->num_rows($query)) {
  1161. throw new RestException(404, 'Attribute value not found');
  1162. }
  1163. $result = $this->db->fetch_object($query);
  1164. $attrval = array();
  1165. $attrval['id'] = $result->rowid;
  1166. $attrval['fk_product_attribute'] = $result->fk_product_attribute;
  1167. $attrval['ref'] = $result->ref;
  1168. $attrval['value'] = $result->value;
  1169. return $attrval;
  1170. }
  1171. /**
  1172. * Get attribute value by ref.
  1173. *
  1174. * @param int $id ID of Attribute value
  1175. * @param string $ref Ref of Attribute value
  1176. * @return array
  1177. *
  1178. * @throws RestException 500 System error
  1179. * @throws RestException 401
  1180. *
  1181. * @url GET attributes/{id}/values/ref/{ref}
  1182. */
  1183. public function getAttributeValueByRef($id, $ref)
  1184. {
  1185. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  1186. throw new RestException(401);
  1187. }
  1188. $ref = trim($ref);
  1189. $sql = "SELECT rowid, fk_product_attribute, ref, value FROM ".$this->db->prefix()."product_attribute_value";
  1190. $sql .= " WHERE ref LIKE '".$this->db->escape($ref)."' AND fk_product_attribute = ".((int) $id)." AND entity IN (".getEntity('product').")";
  1191. $query = $this->db->query($sql);
  1192. if (!$query) {
  1193. throw new RestException(401);
  1194. }
  1195. if (!$this->db->num_rows($query)) {
  1196. throw new RestException(404, 'Attribute value not found');
  1197. }
  1198. $result = $this->db->fetch_object($query);
  1199. $attrval = array();
  1200. $attrval['id'] = $result->rowid;
  1201. $attrval['fk_product_attribute'] = $result->fk_product_attribute;
  1202. $attrval['ref'] = $result->ref;
  1203. $attrval['value'] = $result->value;
  1204. return $attrval;
  1205. }
  1206. /**
  1207. * Delete attribute value by ref.
  1208. *
  1209. * @param int $id ID of Attribute
  1210. * @param string $ref Ref of Attribute value
  1211. * @return int
  1212. *
  1213. * @throws RestException 401
  1214. *
  1215. * @url DELETE attributes/{id}/values/ref/{ref}
  1216. */
  1217. public function deleteAttributeValueByRef($id, $ref)
  1218. {
  1219. if (!DolibarrApiAccess::$user->rights->produit->supprimer) {
  1220. throw new RestException(401);
  1221. }
  1222. $ref = trim($ref);
  1223. $sql = "SELECT rowid FROM ".$this->db->prefix()."product_attribute_value";
  1224. $sql .= " WHERE ref LIKE '".$this->db->escape($ref)."' AND fk_product_attribute = ".((int) $id)." AND entity IN (".getEntity('product').")";
  1225. $query = $this->db->query($sql);
  1226. if (!$query) {
  1227. throw new RestException(401);
  1228. }
  1229. if (!$this->db->num_rows($query)) {
  1230. throw new RestException(404, 'Attribute value not found');
  1231. }
  1232. $result = $this->db->fetch_object($query);
  1233. $attrval = new ProductAttributeValue($this->db);
  1234. $attrval->id = $result->rowid;
  1235. $result = $attrval->delete(DolibarrApiAccess::$user);
  1236. if ($result > 0) {
  1237. return 1;
  1238. }
  1239. throw new RestException(500, "Error deleting attribute value");
  1240. }
  1241. /**
  1242. * Get all values for an attribute id.
  1243. *
  1244. * @param int $id ID of an Attribute
  1245. * @return array
  1246. *
  1247. * @throws RestException 401
  1248. * @throws RestException 500 System error
  1249. *
  1250. * @url GET attributes/{id}/values
  1251. */
  1252. public function getAttributeValues($id)
  1253. {
  1254. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  1255. throw new RestException(401);
  1256. }
  1257. $objectval = new ProductAttributeValue($this->db);
  1258. $return = $objectval->fetchAllByProductAttribute((int) $id);
  1259. if (count($return) == 0) {
  1260. throw new RestException(404, 'Attribute values not found');
  1261. }
  1262. foreach ($return as $key => $val) {
  1263. $return[$key] = $this->_cleanObjectDatas($return[$key]);
  1264. }
  1265. return $return;
  1266. }
  1267. /**
  1268. * Get all values for an attribute ref.
  1269. *
  1270. * @param string $ref Ref of an Attribute
  1271. * @return array
  1272. *
  1273. * @throws RestException 401
  1274. *
  1275. * @url GET attributes/ref/{ref}/values
  1276. */
  1277. public function getAttributeValuesByRef($ref)
  1278. {
  1279. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  1280. throw new RestException(401);
  1281. }
  1282. $ref = trim($ref);
  1283. $return = array();
  1284. $sql = "SELECT ";
  1285. $sql .= "v.fk_product_attribute, v.rowid, v.ref, v.value FROM ".$this->db->prefix()."product_attribute_value as v";
  1286. $sql .= " WHERE v.fk_product_attribute IN (SELECT rowid FROM ".$this->db->prefix()."product_attribute WHERE ref LIKE '".$this->db->escape($ref)."')";
  1287. $resql = $this->db->query($sql);
  1288. while ($result = $this->db->fetch_object($resql)) {
  1289. $tmp = new ProductAttributeValue($this->db);
  1290. $tmp->fk_product_attribute = $result->fk_product_attribute;
  1291. $tmp->id = $result->rowid;
  1292. $tmp->ref = $result->ref;
  1293. $tmp->value = $result->value;
  1294. $return[] = $this->_cleanObjectDatas($tmp);
  1295. }
  1296. return $return;
  1297. }
  1298. /**
  1299. * Add attribute value.
  1300. *
  1301. * @param int $id ID of Attribute
  1302. * @param string $ref Reference of Attribute value
  1303. * @param string $value Value of Attribute value
  1304. * @return int
  1305. *
  1306. * @throws RestException 500 System error
  1307. * @throws RestException 401
  1308. *
  1309. * @url POST attributes/{id}/values
  1310. */
  1311. public function addAttributeValue($id, $ref, $value)
  1312. {
  1313. if (!DolibarrApiAccess::$user->rights->produit->creer) {
  1314. throw new RestException(401);
  1315. }
  1316. if (empty($ref) || empty($value)) {
  1317. throw new RestException(401);
  1318. }
  1319. $objectval = new ProductAttributeValue($this->db);
  1320. $objectval->fk_product_attribute = ((int) $id);
  1321. $objectval->ref = $ref;
  1322. $objectval->value = $value;
  1323. if ($objectval->create(DolibarrApiAccess::$user) > 0) {
  1324. return $objectval->id;
  1325. }
  1326. throw new RestException(500, "Error creating new attribute value");
  1327. }
  1328. /**
  1329. * Update attribute value.
  1330. *
  1331. * @param int $id ID of Attribute
  1332. * @param array $request_data Datas
  1333. * @return array
  1334. *
  1335. * @throws RestException 401
  1336. * @throws RestException 500 System error
  1337. *
  1338. * @url PUT attributes/values/{id}
  1339. */
  1340. public function putAttributeValue($id, $request_data)
  1341. {
  1342. if (!DolibarrApiAccess::$user->rights->produit->creer) {
  1343. throw new RestException(401);
  1344. }
  1345. $objectval = new ProductAttributeValue($this->db);
  1346. $result = $objectval->fetch((int) $id);
  1347. if ($result == 0) {
  1348. throw new RestException(404, 'Attribute value not found');
  1349. } elseif ($result < 0) {
  1350. throw new RestException(500, "Error fetching attribute value");
  1351. }
  1352. foreach ($request_data as $field => $value) {
  1353. if ($field == 'rowid') {
  1354. continue;
  1355. }
  1356. $objectval->$field = $value;
  1357. }
  1358. if ($objectval->update(DolibarrApiAccess::$user) > 0) {
  1359. $result = $objectval->fetch((int) $id);
  1360. if ($result == 0) {
  1361. throw new RestException(404, 'Attribute not found');
  1362. } elseif ($result < 0) {
  1363. throw new RestException(500, "Error fetching attribute");
  1364. } else {
  1365. return $objectval;
  1366. }
  1367. }
  1368. throw new RestException(500, "Error updating attribute");
  1369. }
  1370. /**
  1371. * Delete attribute value by id.
  1372. *
  1373. * @param int $id ID of Attribute value
  1374. * @return int
  1375. *
  1376. * @throws RestException 500 System error
  1377. * @throws RestException 401
  1378. *
  1379. * @url DELETE attributes/values/{id}
  1380. */
  1381. public function deleteAttributeValueById($id)
  1382. {
  1383. if (!DolibarrApiAccess::$user->rights->produit->supprimer) {
  1384. throw new RestException(401);
  1385. }
  1386. $objectval = new ProductAttributeValue($this->db);
  1387. $objectval->id = (int) $id;
  1388. if ($objectval->delete(DolibarrApiAccess::$user) > 0) {
  1389. return 1;
  1390. }
  1391. throw new RestException(500, "Error deleting attribute value");
  1392. }
  1393. /**
  1394. * Get product variants.
  1395. *
  1396. * @param int $id ID of Product
  1397. * @param int $includestock Default value 0. If parameter is set to 1 the response will contain stock data of each variant
  1398. * @return array
  1399. *
  1400. * @throws RestException 500 System error
  1401. * @throws RestException 401
  1402. *
  1403. * @url GET {id}/variants
  1404. */
  1405. public function getVariants($id, $includestock = 0)
  1406. {
  1407. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  1408. throw new RestException(401);
  1409. }
  1410. $prodcomb = new ProductCombination($this->db);
  1411. $combinations = $prodcomb->fetchAllByFkProductParent((int) $id);
  1412. foreach ($combinations as $key => $combination) {
  1413. $prodc2vp = new ProductCombination2ValuePair($this->db);
  1414. $combinations[$key]->attributes = $prodc2vp->fetchByFkCombination((int) $combination->id);
  1415. $combinations[$key] = $this->_cleanObjectDatas($combinations[$key]);
  1416. if ($includestock==1 && DolibarrApiAccess::$user->rights->stock->lire) {
  1417. $productModel = new Product($this->db);
  1418. $productModel->fetch((int) $combination->fk_product_child);
  1419. $productModel->load_stock();
  1420. $combinations[$key]->stock_warehouse = $this->_cleanObjectDatas($productModel)->stock_warehouse;
  1421. }
  1422. }
  1423. return $combinations;
  1424. }
  1425. /**
  1426. * Get product variants by Product ref.
  1427. *
  1428. * @param string $ref Ref of Product
  1429. * @return array
  1430. *
  1431. * @throws RestException 500 System error
  1432. * @throws RestException 401
  1433. *
  1434. * @url GET ref/{ref}/variants
  1435. */
  1436. public function getVariantsByProdRef($ref)
  1437. {
  1438. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  1439. throw new RestException(401);
  1440. }
  1441. $result = $this->product->fetch('', $ref);
  1442. if (!$result) {
  1443. throw new RestException(404, 'Product not found');
  1444. }
  1445. $prodcomb = new ProductCombination($this->db);
  1446. $combinations = $prodcomb->fetchAllByFkProductParent((int) $this->product->id);
  1447. foreach ($combinations as $key => $combination) {
  1448. $prodc2vp = new ProductCombination2ValuePair($this->db);
  1449. $combinations[$key]->attributes = $prodc2vp->fetchByFkCombination((int) $combination->id);
  1450. $combinations[$key] = $this->_cleanObjectDatas($combinations[$key]);
  1451. }
  1452. return $combinations;
  1453. }
  1454. /**
  1455. * Add variant.
  1456. *
  1457. * "features" is a list of attributes pairs id_attribute=>id_value. Example: array(id_color=>id_Blue, id_size=>id_small, id_option=>id_val_a, ...)
  1458. *
  1459. * @param int $id ID of Product
  1460. * @param float $weight_impact Weight impact of variant
  1461. * @param float $price_impact Price impact of variant
  1462. * @param bool $price_impact_is_percent Price impact in percent (true or false)
  1463. * @param array $features List of attributes pairs id_attribute->id_value. Example: array(id_color=>id_Blue, id_size=>id_small, id_option=>id_val_a, ...)
  1464. * @param string $reference Customized reference of variant
  1465. * @param string $ref_ext External reference of variant
  1466. * @return int
  1467. *
  1468. * @throws RestException 500 System error
  1469. * @throws RestException 401
  1470. * @throws RestException 404
  1471. *
  1472. * @url POST {id}/variants
  1473. */
  1474. public function addVariant($id, $weight_impact, $price_impact, $price_impact_is_percent, $features, $reference = '', $ref_ext = '')
  1475. {
  1476. if (!DolibarrApiAccess::$user->rights->produit->creer) {
  1477. throw new RestException(401);
  1478. }
  1479. if (empty($id) || empty($features) || !is_array($features)) {
  1480. throw new RestException(401);
  1481. }
  1482. $weight_impact = price2num($weight_impact);
  1483. $price_impact = price2num($price_impact);
  1484. $prodattr = new ProductAttribute($this->db);
  1485. $prodattr_val = new ProductAttributeValue($this->db);
  1486. foreach ($features as $id_attr => $id_value) {
  1487. if ($prodattr->fetch((int) $id_attr) < 0) {
  1488. throw new RestException(401);
  1489. }
  1490. if ($prodattr_val->fetch((int) $id_value) < 0) {
  1491. throw new RestException(401);
  1492. }
  1493. }
  1494. $result = $this->product->fetch((int) $id);
  1495. if (!$result) {
  1496. throw new RestException(404, 'Product not found');
  1497. }
  1498. $prodcomb = new ProductCombination($this->db);
  1499. $result = $prodcomb->createProductCombination(DolibarrApiAccess::$user, $this->product, $features, array(), $price_impact_is_percent, $price_impact, $weight_impact, $reference, $ref_ext);
  1500. if ($result > 0) {
  1501. return $result;
  1502. } else {
  1503. throw new RestException(500, "Error creating new product variant");
  1504. }
  1505. }
  1506. /**
  1507. * Add variant by product ref.
  1508. *
  1509. * "features" is a list of attributes pairs id_attribute=>id_value. Example: array(id_color=>id_Blue, id_size=>id_small, id_option=>id_val_a, ...)
  1510. *
  1511. * @param string $ref Ref of Product
  1512. * @param float $weight_impact Weight impact of variant
  1513. * @param float $price_impact Price impact of variant
  1514. * @param bool $price_impact_is_percent Price impact in percent (true or false)
  1515. * @param array $features List of attributes pairs id_attribute->id_value. Example: array(id_color=>id_Blue, id_size=>id_small, id_option=>id_val_a, ...)
  1516. * @return int
  1517. *
  1518. * @throws RestException 500 System error
  1519. * @throws RestException 401
  1520. * @throws RestException 404
  1521. *
  1522. * @url POST ref/{ref}/variants
  1523. */
  1524. public function addVariantByProductRef($ref, $weight_impact, $price_impact, $price_impact_is_percent, $features)
  1525. {
  1526. if (!DolibarrApiAccess::$user->rights->produit->creer) {
  1527. throw new RestException(401);
  1528. }
  1529. if (empty($ref) || empty($features) || !is_array($features)) {
  1530. throw new RestException(401);
  1531. }
  1532. $weight_impact = price2num($weight_impact);
  1533. $price_impact = price2num($price_impact);
  1534. $prodattr = new ProductAttribute($this->db);
  1535. $prodattr_val = new ProductAttributeValue($this->db);
  1536. foreach ($features as $id_attr => $id_value) {
  1537. if ($prodattr->fetch((int) $id_attr) < 0) {
  1538. throw new RestException(404);
  1539. }
  1540. if ($prodattr_val->fetch((int) $id_value) < 0) {
  1541. throw new RestException(404);
  1542. }
  1543. }
  1544. $result = $this->product->fetch('', trim($ref));
  1545. if (!$result) {
  1546. throw new RestException(404, 'Product not found');
  1547. }
  1548. $prodcomb = new ProductCombination($this->db);
  1549. if (!$prodcomb->fetchByProductCombination2ValuePairs($this->product->id, $features)) {
  1550. $result = $prodcomb->createProductCombination(DolibarrApiAccess::$user, $this->product, $features, array(), $price_impact_is_percent, $price_impact, $weight_impact);
  1551. if ($result > 0) {
  1552. return $result;
  1553. } else {
  1554. throw new RestException(500, "Error creating new product variant");
  1555. }
  1556. } else {
  1557. return $prodcomb->id;
  1558. }
  1559. }
  1560. /**
  1561. * Put product variants.
  1562. *
  1563. * @param int $id ID of Variant
  1564. * @param array $request_data Datas
  1565. * @return int
  1566. *
  1567. * @throws RestException 500 System error
  1568. * @throws RestException 401
  1569. *
  1570. * @url PUT variants/{id}
  1571. */
  1572. public function putVariant($id, $request_data = null)
  1573. {
  1574. if (!DolibarrApiAccess::$user->rights->produit->creer) {
  1575. throw new RestException(401);
  1576. }
  1577. $prodcomb = new ProductCombination($this->db);
  1578. $prodcomb->fetch((int) $id);
  1579. foreach ($request_data as $field => $value) {
  1580. if ($field == 'rowid') {
  1581. continue;
  1582. }
  1583. $prodcomb->$field = $value;
  1584. }
  1585. $result = $prodcomb->update(DolibarrApiAccess::$user);
  1586. if ($result > 0) {
  1587. return 1;
  1588. }
  1589. throw new RestException(500, "Error editing variant");
  1590. }
  1591. /**
  1592. * Delete product variants.
  1593. *
  1594. * @param int $id ID of Variant
  1595. * @return int Result of deletion
  1596. *
  1597. * @throws RestException 500 System error
  1598. * @throws RestException 401
  1599. *
  1600. * @url DELETE variants/{id}
  1601. */
  1602. public function deleteVariant($id)
  1603. {
  1604. if (!DolibarrApiAccess::$user->rights->produit->supprimer) {
  1605. throw new RestException(401);
  1606. }
  1607. $prodcomb = new ProductCombination($this->db);
  1608. $prodcomb->id = (int) $id;
  1609. $result = $prodcomb->delete(DolibarrApiAccess::$user);
  1610. if ($result <= 0) {
  1611. throw new RestException(500, "Error deleting variant");
  1612. }
  1613. return $result;
  1614. }
  1615. /**
  1616. * Get stock data for the product id given.
  1617. * Optionaly with $selected_warehouse_id parameter user can get stock of specific warehouse
  1618. *
  1619. * @param int $id ID of Product
  1620. * @param int $selected_warehouse_id ID of warehouse
  1621. * @return int
  1622. *
  1623. * @throws RestException 500 System error
  1624. * @throws RestException 401
  1625. * @throws RestException 404
  1626. *
  1627. * @url GET {id}/stock
  1628. */
  1629. public function getStock($id, $selected_warehouse_id = null)
  1630. {
  1631. if (!DolibarrApiAccess::$user->rights->produit->lire || !DolibarrApiAccess::$user->rights->stock->lire) {
  1632. throw new RestException(401);
  1633. }
  1634. if (!DolibarrApi::_checkAccessToResource('product', $id)) {
  1635. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  1636. }
  1637. $product_model = new Product($this->db);
  1638. $product_model->fetch($id);
  1639. $product_model->load_stock();
  1640. $stockData = $this->_cleanObjectDatas($product_model)->stock_warehouse;
  1641. if ($selected_warehouse_id) {
  1642. foreach ($stockData as $warehouse_id => $warehouse) {
  1643. if ($warehouse_id != $selected_warehouse_id) {
  1644. unset($stockData[$warehouse_id]);
  1645. }
  1646. }
  1647. }
  1648. if (empty($stockData)) {
  1649. throw new RestException(404, 'No stock found');
  1650. }
  1651. return ['stock_warehouses'=>$stockData];
  1652. }
  1653. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
  1654. /**
  1655. * Clean sensible object datas
  1656. *
  1657. * @param Object $object Object to clean
  1658. * @return Object Object with cleaned properties
  1659. */
  1660. protected function _cleanObjectDatas($object)
  1661. {
  1662. // phpcs:enable
  1663. $object = parent::_cleanObjectDatas($object);
  1664. unset($object->statut);
  1665. unset($object->regeximgext);
  1666. unset($object->price_by_qty);
  1667. unset($object->prices_by_qty_id);
  1668. unset($object->libelle);
  1669. unset($object->product_id_already_linked);
  1670. unset($object->reputations);
  1671. unset($object->db);
  1672. unset($object->name);
  1673. unset($object->firstname);
  1674. unset($object->lastname);
  1675. unset($object->civility_id);
  1676. unset($object->contact);
  1677. unset($object->contact_id);
  1678. unset($object->thirdparty);
  1679. unset($object->user);
  1680. unset($object->origin);
  1681. unset($object->origin_id);
  1682. unset($object->fourn_pu);
  1683. unset($object->fourn_price_base_type);
  1684. unset($object->fourn_socid);
  1685. unset($object->ref_fourn);
  1686. unset($object->ref_supplier);
  1687. unset($object->product_fourn_id);
  1688. unset($object->fk_project);
  1689. unset($object->mode_reglement_id);
  1690. unset($object->cond_reglement_id);
  1691. unset($object->demand_reason_id);
  1692. unset($object->transport_mode_id);
  1693. unset($object->cond_reglement);
  1694. unset($object->shipping_method_id);
  1695. unset($object->model_pdf);
  1696. unset($object->note);
  1697. unset($object->nbphoto);
  1698. unset($object->recuperableonly);
  1699. unset($object->multiprices_recuperableonly);
  1700. unset($object->tva_npr);
  1701. unset($object->lines);
  1702. unset($object->fk_bank);
  1703. unset($object->fk_account);
  1704. unset($object->supplierprices); // Mut use another API to get them
  1705. if (empty(DolibarrApiAccess::$user->rights->stock->lire)) {
  1706. unset($object->stock_reel);
  1707. unset($object->stock_theorique);
  1708. unset($object->stock_warehouse);
  1709. }
  1710. return $object;
  1711. }
  1712. /**
  1713. * Validate fields before create or update object
  1714. *
  1715. * @param array $data Datas to validate
  1716. * @return array
  1717. * @throws RestException
  1718. */
  1719. private function _validate($data)
  1720. {
  1721. $product = array();
  1722. foreach (Products::$FIELDS as $field) {
  1723. if (!isset($data[$field])) {
  1724. throw new RestException(400, "$field field missing");
  1725. }
  1726. $product[$field] = $data[$field];
  1727. }
  1728. return $product;
  1729. }
  1730. /**
  1731. * Get properties of 1 product object.
  1732. * Return an array with product information.
  1733. *
  1734. * @param int $id ID of product
  1735. * @param string $ref Ref of element
  1736. * @param string $ref_ext Ref ext of element
  1737. * @param string $barcode Barcode of element
  1738. * @param int $includestockdata Load also information about stock (slower)
  1739. * @param bool $includesubproducts Load information about subproducts (if product is a virtual product)
  1740. * @param bool $includeparentid Load also ID of parent product (if product is a variant of a parent product)
  1741. * @param bool $includeifobjectisused Check if product object is used and set property 'is_object_used' with result.
  1742. * @param bool $includetrans Load also the translations of product label and description
  1743. * @return array|mixed Data without useless information
  1744. *
  1745. * @throws RestException 401
  1746. * @throws RestException 403
  1747. * @throws RestException 404
  1748. */
  1749. private function _fetch($id, $ref = '', $ref_ext = '', $barcode = '', $includestockdata = 0, $includesubproducts = false, $includeparentid = false, $includeifobjectisused = false, $includetrans = false)
  1750. {
  1751. if (empty($id) && empty($ref) && empty($ref_ext) && empty($barcode)) {
  1752. throw new RestException(400, 'bad value for parameter id, ref, ref_ext or barcode');
  1753. }
  1754. $id = (empty($id) ? 0 : $id);
  1755. if (!DolibarrApiAccess::$user->rights->produit->lire) {
  1756. throw new RestException(403);
  1757. }
  1758. $result = $this->product->fetch($id, $ref, $ref_ext, $barcode, 0, 0, ($includetrans ? 0 : 1));
  1759. if (!$result) {
  1760. throw new RestException(404, 'Product not found');
  1761. }
  1762. if (!DolibarrApi::_checkAccessToResource('product', $this->product->id)) {
  1763. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  1764. }
  1765. if ($includestockdata && DolibarrApiAccess::$user->rights->stock->lire) {
  1766. $this->product->load_stock();
  1767. if (is_array($this->product->stock_warehouse)) {
  1768. foreach ($this->product->stock_warehouse as $keytmp => $valtmp) {
  1769. if (is_array($this->product->stock_warehouse[$keytmp]->detail_batch)) {
  1770. foreach ($this->product->stock_warehouse[$keytmp]->detail_batch as $keytmp2 => $valtmp2) {
  1771. unset($this->product->stock_warehouse[$keytmp]->detail_batch[$keytmp2]->db);
  1772. }
  1773. }
  1774. }
  1775. }
  1776. }
  1777. if ($includesubproducts) {
  1778. $childsArbo = $this->product->getChildsArbo($id, 1);
  1779. $keys = array('rowid', 'qty', 'fk_product_type', 'label', 'incdec', 'ref', 'fk_association', 'rang');
  1780. $childs = array();
  1781. foreach ($childsArbo as $values) {
  1782. $childs[] = array_combine($keys, $values);
  1783. }
  1784. $this->product->sousprods = $childs;
  1785. }
  1786. if ($includeparentid) {
  1787. $prodcomb = new ProductCombination($this->db);
  1788. $this->product->fk_product_parent = null;
  1789. if (($fk_product_parent = $prodcomb->fetchByFkProductChild($this->product->id)) > 0) {
  1790. $this->product->fk_product_parent = $fk_product_parent;
  1791. }
  1792. }
  1793. if ($includeifobjectisused) {
  1794. $this->product->is_object_used = ($this->product->isObjectUsed() > 0);
  1795. }
  1796. return $this->_cleanObjectDatas($this->product);
  1797. }
  1798. }