api_products.class.php 64 KB

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