api_contracts.class.php 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691
  1. <?php
  2. /* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
  3. * Copyright (C) 2016 Laurent Destailleur <eldy@users.sourceforge.net>
  4. * Copyright (C) 2018-2020 Frédéric France <frederic.france@netlogic.fr>
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. use Luracast\Restler\RestException;
  20. require_once DOL_DOCUMENT_ROOT.'/contrat/class/contrat.class.php';
  21. /**
  22. * API class for contracts
  23. *
  24. * @access protected
  25. * @class DolibarrApiAccess {@requires user,external}
  26. */
  27. class Contracts extends DolibarrApi
  28. {
  29. /**
  30. * @var array $FIELDS Mandatory fields, checked when create and update object
  31. */
  32. static $FIELDS = array(
  33. 'socid',
  34. 'date_contrat',
  35. 'commercial_signature_id',
  36. 'commercial_suivi_id'
  37. );
  38. /**
  39. * @var Contrat $contract {@type Contrat}
  40. */
  41. public $contract;
  42. /**
  43. * Constructor
  44. */
  45. public function __construct()
  46. {
  47. global $db, $conf;
  48. $this->db = $db;
  49. $this->contract = new Contrat($this->db);
  50. }
  51. /**
  52. * Get properties of a contract object
  53. *
  54. * Return an array with contract informations
  55. *
  56. * @param int $id ID of contract
  57. * @return array|mixed data without useless information
  58. *
  59. * @throws RestException
  60. */
  61. public function get($id)
  62. {
  63. if (!DolibarrApiAccess::$user->rights->contrat->lire) {
  64. throw new RestException(401);
  65. }
  66. $result = $this->contract->fetch($id);
  67. if (!$result) {
  68. throw new RestException(404, 'Contract not found');
  69. }
  70. if (!DolibarrApi::_checkAccessToResource('contrat', $this->contract->id)) {
  71. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  72. }
  73. $this->contract->fetchObjectLinked();
  74. return $this->_cleanObjectDatas($this->contract);
  75. }
  76. /**
  77. * List contracts
  78. *
  79. * Get a list of contracts
  80. *
  81. * @param string $sortfield Sort field
  82. * @param string $sortorder Sort order
  83. * @param int $limit Limit for list
  84. * @param int $page Page number
  85. * @param string $thirdparty_ids Thirdparty ids to filter contracts of (example '1' or '1,2,3') {@pattern /^[0-9,]*$/i}
  86. * @param string $sqlfilters Other criteria to filter answers separated by a comma. Syntax example "(t.ref:like:'SO-%') and (t.date_creation:<:'20160101')"
  87. * @return array Array of contract objects
  88. *
  89. * @throws RestException 404 Not found
  90. * @throws RestException 503 Error
  91. */
  92. public function index($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $thirdparty_ids = '', $sqlfilters = '')
  93. {
  94. global $db, $conf;
  95. if (!DolibarrApiAccess::$user->rights->contrat->lire) {
  96. throw new RestException(401);
  97. }
  98. $obj_ret = array();
  99. // case of external user, $thirdparty_ids param is ignored and replaced by user's socid
  100. $socids = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : $thirdparty_ids;
  101. // If the internal user must only see his customers, force searching by him
  102. $search_sale = 0;
  103. if (!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) {
  104. $search_sale = DolibarrApiAccess::$user->id;
  105. }
  106. $sql = "SELECT t.rowid";
  107. if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) {
  108. $sql .= ", sc.fk_soc, sc.fk_user"; // We need these fields in order to filter by sale (including the case where the user can only see his prospects)
  109. }
  110. $sql .= " FROM ".MAIN_DB_PREFIX."contrat as t";
  111. if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) {
  112. $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; // We need this table joined to the select in order to filter by sale
  113. }
  114. $sql .= ' WHERE t.entity IN ('.getEntity('contrat').')';
  115. if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) {
  116. $sql .= " AND t.fk_soc = sc.fk_soc";
  117. }
  118. if ($socids) {
  119. $sql .= " AND t.fk_soc IN (".$this->db->sanitize($socids).")";
  120. }
  121. if ($search_sale > 0) {
  122. $sql .= " AND t.rowid = sc.fk_soc"; // Join for the needed table to filter by sale
  123. }
  124. // Insert sale filter
  125. if ($search_sale > 0) {
  126. $sql .= " AND sc.fk_user = ".((int) $search_sale);
  127. }
  128. // Add sql filters
  129. if ($sqlfilters) {
  130. $errormessage = '';
  131. $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
  132. if ($errormessage) {
  133. throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
  134. }
  135. }
  136. $sql .= $this->db->order($sortfield, $sortorder);
  137. if ($limit) {
  138. if ($page < 0) {
  139. $page = 0;
  140. }
  141. $offset = $limit * $page;
  142. $sql .= $this->db->plimit($limit + 1, $offset);
  143. }
  144. dol_syslog("API Rest request");
  145. $result = $this->db->query($sql);
  146. if ($result) {
  147. $num = $this->db->num_rows($result);
  148. $min = min($num, ($limit <= 0 ? $num : $limit));
  149. $i = 0;
  150. while ($i < $min) {
  151. $obj = $this->db->fetch_object($result);
  152. $contrat_static = new Contrat($this->db);
  153. if ($contrat_static->fetch($obj->rowid)) {
  154. $obj_ret[] = $this->_cleanObjectDatas($contrat_static);
  155. }
  156. $i++;
  157. }
  158. } else {
  159. throw new RestException(503, 'Error when retrieve contrat list : '.$this->db->lasterror());
  160. }
  161. if (!count($obj_ret)) {
  162. throw new RestException(404, 'No contract found');
  163. }
  164. return $obj_ret;
  165. }
  166. /**
  167. * Create contract object
  168. *
  169. * @param array $request_data Request data
  170. * @return int ID of contrat
  171. */
  172. public function post($request_data = null)
  173. {
  174. if (!DolibarrApiAccess::$user->rights->contrat->creer) {
  175. throw new RestException(401, "Insufficient rights");
  176. }
  177. // Check mandatory fields
  178. $result = $this->_validate($request_data);
  179. foreach ($request_data as $field => $value) {
  180. $this->contract->$field = $value;
  181. }
  182. /*if (isset($request_data["lines"])) {
  183. $lines = array();
  184. foreach ($request_data["lines"] as $line) {
  185. array_push($lines, (object) $line);
  186. }
  187. $this->contract->lines = $lines;
  188. }*/
  189. if ($this->contract->create(DolibarrApiAccess::$user) < 0) {
  190. throw new RestException(500, "Error creating contract", array_merge(array($this->contract->error), $this->contract->errors));
  191. }
  192. return $this->contract->id;
  193. }
  194. /**
  195. * Get lines of a contract
  196. *
  197. * @param int $id Id of contract
  198. *
  199. * @url GET {id}/lines
  200. *
  201. * @return array
  202. */
  203. public function getLines($id)
  204. {
  205. if (!DolibarrApiAccess::$user->rights->contrat->lire) {
  206. throw new RestException(401);
  207. }
  208. $result = $this->contract->fetch($id);
  209. if (!$result) {
  210. throw new RestException(404, 'Contract not found');
  211. }
  212. if (!DolibarrApi::_checkAccessToResource('contrat', $this->contract->id)) {
  213. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  214. }
  215. $this->contract->getLinesArray();
  216. $result = array();
  217. foreach ($this->contract->lines as $line) {
  218. array_push($result, $this->_cleanObjectDatas($line));
  219. }
  220. return $result;
  221. }
  222. /**
  223. * Add a line to given contract
  224. *
  225. * @param int $id Id of contrat to update
  226. * @param array $request_data Contractline data
  227. *
  228. * @url POST {id}/lines
  229. *
  230. * @return int|bool
  231. */
  232. public function postLine($id, $request_data = null)
  233. {
  234. if (!DolibarrApiAccess::$user->rights->contrat->creer) {
  235. throw new RestException(401);
  236. }
  237. $result = $this->contract->fetch($id);
  238. if (!$result) {
  239. throw new RestException(404, 'Contract not found');
  240. }
  241. if (!DolibarrApi::_checkAccessToResource('contrat', $this->contract->id)) {
  242. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  243. }
  244. $request_data = (object) $request_data;
  245. $request_data->desc = sanitizeVal($request_data->desc, 'restricthtml');
  246. $request_data->price_base_type = sanitizeVal($request_data->price_base_type);
  247. $updateRes = $this->contract->addline(
  248. $request_data->desc,
  249. $request_data->subprice,
  250. $request_data->qty,
  251. $request_data->tva_tx,
  252. $request_data->localtax1_tx,
  253. $request_data->localtax2_tx,
  254. $request_data->fk_product,
  255. $request_data->remise_percent,
  256. $request_data->date_start,
  257. $request_data->date_end,
  258. $request_data->price_base_type ? $request_data->price_base_type : 'HT',
  259. $request_data->subprice_excl_tax,
  260. $request_data->info_bits,
  261. $request_data->fk_fournprice,
  262. $request_data->pa_ht,
  263. $request_data->array_options,
  264. $request_data->fk_unit,
  265. $request_data->rang
  266. );
  267. if ($updateRes > 0) {
  268. return $updateRes;
  269. }
  270. return false;
  271. }
  272. /**
  273. * Update a line to given contract
  274. *
  275. * @param int $id Id of contrat to update
  276. * @param int $lineid Id of line to update
  277. * @param array $request_data Contractline data
  278. *
  279. * @url PUT {id}/lines/{lineid}
  280. *
  281. * @return Object|bool
  282. */
  283. public function putLine($id, $lineid, $request_data = null)
  284. {
  285. if (!DolibarrApiAccess::$user->rights->contrat->creer) {
  286. throw new RestException(401);
  287. }
  288. $result = $this->contract->fetch($id);
  289. if (!$result) {
  290. throw new RestException(404, 'Contrat not found');
  291. }
  292. if (!DolibarrApi::_checkAccessToResource('contrat', $this->contract->id)) {
  293. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  294. }
  295. $request_data = (object) $request_data;
  296. $request_data->desc = sanitizeVal($request_data->desc, 'restricthtml');
  297. $request_data->price_base_type = sanitizeVal($request_data->price_base_type);
  298. $updateRes = $this->contract->updateline(
  299. $lineid,
  300. $request_data->desc,
  301. $request_data->subprice,
  302. $request_data->qty,
  303. $request_data->remise_percent,
  304. $request_data->date_start,
  305. $request_data->date_end,
  306. $request_data->tva_tx,
  307. $request_data->localtax1_tx,
  308. $request_data->localtax2_tx,
  309. $request_data->date_start_real,
  310. $request_data->date_end_real,
  311. $request_data->price_base_type ? $request_data->price_base_type : 'HT',
  312. $request_data->info_bits,
  313. $request_data->fk_fourn_price,
  314. $request_data->pa_ht,
  315. $request_data->array_options,
  316. $request_data->fk_unit
  317. );
  318. if ($updateRes > 0) {
  319. $result = $this->get($id);
  320. unset($result->line);
  321. return $this->_cleanObjectDatas($result);
  322. }
  323. return false;
  324. }
  325. /**
  326. * Activate a service line of a given contract
  327. *
  328. * @param int $id Id of contract to activate
  329. * @param int $lineid Id of line to activate
  330. * @param string $datestart {@from body} Date start {@type timestamp}
  331. * @param string $dateend {@from body} Date end {@type timestamp}
  332. * @param string $comment {@from body} Comment
  333. *
  334. * @url PUT {id}/lines/{lineid}/activate
  335. *
  336. * @return Object|bool
  337. */
  338. public function activateLine($id, $lineid, $datestart, $dateend = null, $comment = null)
  339. {
  340. if (!DolibarrApiAccess::$user->rights->contrat->creer) {
  341. throw new RestException(401);
  342. }
  343. $result = $this->contract->fetch($id);
  344. if (!$result) {
  345. throw new RestException(404, 'Contrat not found');
  346. }
  347. if (!DolibarrApi::_checkAccessToResource('contrat', $this->contract->id)) {
  348. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  349. }
  350. $updateRes = $this->contract->active_line(DolibarrApiAccess::$user, $lineid, $datestart, $dateend, $comment);
  351. if ($updateRes > 0) {
  352. $result = $this->get($id);
  353. unset($result->line);
  354. return $this->_cleanObjectDatas($result);
  355. }
  356. return false;
  357. }
  358. /**
  359. * Unactivate a service line of a given contract
  360. *
  361. * @param int $id Id of contract to activate
  362. * @param int $lineid Id of line to activate
  363. * @param string $datestart {@from body} Date start {@type timestamp}
  364. * @param string $comment {@from body} Comment
  365. *
  366. * @url PUT {id}/lines/{lineid}/unactivate
  367. *
  368. * @return Object|bool
  369. */
  370. public function unactivateLine($id, $lineid, $datestart, $comment = null)
  371. {
  372. if (!DolibarrApiAccess::$user->rights->contrat->creer) {
  373. throw new RestException(401);
  374. }
  375. $result = $this->contract->fetch($id);
  376. if (!$result) {
  377. throw new RestException(404, 'Contrat not found');
  378. }
  379. if (!DolibarrApi::_checkAccessToResource('contrat', $this->contract->id)) {
  380. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  381. }
  382. $updateRes = $this->contract->close_line(DolibarrApiAccess::$user, $lineid, $datestart, $comment);
  383. if ($updateRes > 0) {
  384. $result = $this->get($id);
  385. unset($result->line);
  386. return $this->_cleanObjectDatas($result);
  387. }
  388. return false;
  389. }
  390. /**
  391. * Delete a line to given contract
  392. *
  393. *
  394. * @param int $id Id of contract to update
  395. * @param int $lineid Id of line to delete
  396. *
  397. * @url DELETE {id}/lines/{lineid}
  398. *
  399. * @return array|mixed
  400. *
  401. * @throws RestException 401
  402. * @throws RestException 404
  403. */
  404. public function deleteLine($id, $lineid)
  405. {
  406. if (!DolibarrApiAccess::$user->rights->contrat->creer) {
  407. throw new RestException(401);
  408. }
  409. $result = $this->contract->fetch($id);
  410. if (!$result) {
  411. throw new RestException(404, 'Contrat not found');
  412. }
  413. if (!DolibarrApi::_checkAccessToResource('contrat', $this->contract->id)) {
  414. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  415. }
  416. // TODO Check the lineid $lineid is a line of object
  417. $updateRes = $this->contract->deleteline($lineid, DolibarrApiAccess::$user);
  418. if ($updateRes > 0) {
  419. return $this->get($id);
  420. } else {
  421. throw new RestException(405, $this->contract->error);
  422. }
  423. }
  424. /**
  425. * Update contract general fields (won't touch lines of contract)
  426. *
  427. * @param int $id Id of contract to update
  428. * @param array $request_data Datas
  429. *
  430. * @return array|mixed
  431. */
  432. public function put($id, $request_data = null)
  433. {
  434. if (!DolibarrApiAccess::$user->rights->contrat->creer) {
  435. throw new RestException(401);
  436. }
  437. $result = $this->contract->fetch($id);
  438. if (!$result) {
  439. throw new RestException(404, 'Contrat not found');
  440. }
  441. if (!DolibarrApi::_checkAccessToResource('contrat', $this->contract->id)) {
  442. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  443. }
  444. foreach ($request_data as $field => $value) {
  445. if ($field == 'id') {
  446. continue;
  447. }
  448. $this->contract->$field = $value;
  449. }
  450. if ($this->contract->update(DolibarrApiAccess::$user) > 0) {
  451. return $this->get($id);
  452. } else {
  453. throw new RestException(500, $this->contract->error);
  454. }
  455. }
  456. /**
  457. * Delete contract
  458. *
  459. * @param int $id Contract ID
  460. *
  461. * @return array
  462. */
  463. public function delete($id)
  464. {
  465. if (!DolibarrApiAccess::$user->rights->contrat->supprimer) {
  466. throw new RestException(401);
  467. }
  468. $result = $this->contract->fetch($id);
  469. if (!$result) {
  470. throw new RestException(404, 'Contract not found');
  471. }
  472. if (!DolibarrApi::_checkAccessToResource('contrat', $this->contract->id)) {
  473. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  474. }
  475. if (!$this->contract->delete(DolibarrApiAccess::$user)) {
  476. throw new RestException(500, 'Error when delete contract : '.$this->contract->error);
  477. }
  478. return array(
  479. 'success' => array(
  480. 'code' => 200,
  481. 'message' => 'Contract deleted'
  482. )
  483. );
  484. }
  485. /**
  486. * Validate a contract
  487. *
  488. * @param int $id Contract ID
  489. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  490. *
  491. * @url POST {id}/validate
  492. *
  493. * @return array
  494. * FIXME An error 403 is returned if the request has an empty body.
  495. * Error message: "Forbidden: Content type `text/plain` is not supported."
  496. * Workaround: send this in the body
  497. * {
  498. * "notrigger": 0
  499. * }
  500. */
  501. public function validate($id, $notrigger = 0)
  502. {
  503. if (!DolibarrApiAccess::$user->rights->contrat->creer) {
  504. throw new RestException(401);
  505. }
  506. $result = $this->contract->fetch($id);
  507. if (!$result) {
  508. throw new RestException(404, 'Contract not found');
  509. }
  510. if (!DolibarrApi::_checkAccessToResource('contrat', $this->contract->id)) {
  511. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  512. }
  513. $result = $this->contract->validate(DolibarrApiAccess::$user, '', $notrigger);
  514. if ($result == 0) {
  515. throw new RestException(304, 'Error nothing done. May be object is already validated');
  516. }
  517. if ($result < 0) {
  518. throw new RestException(500, 'Error when validating Contract: '.$this->contract->error);
  519. }
  520. return array(
  521. 'success' => array(
  522. 'code' => 200,
  523. 'message' => 'Contract validated (Ref='.$this->contract->ref.')'
  524. )
  525. );
  526. }
  527. /**
  528. * Close all services of a contract
  529. *
  530. * @param int $id Contract ID
  531. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  532. *
  533. * @url POST {id}/close
  534. *
  535. * @return array
  536. * FIXME An error 403 is returned if the request has an empty body.
  537. * Error message: "Forbidden: Content type `text/plain` is not supported."
  538. * Workaround: send this in the body
  539. * {
  540. * "notrigger": 0
  541. * }
  542. */
  543. public function close($id, $notrigger = 0)
  544. {
  545. if (!DolibarrApiAccess::$user->rights->contrat->creer) {
  546. throw new RestException(401);
  547. }
  548. $result = $this->contract->fetch($id);
  549. if (!$result) {
  550. throw new RestException(404, 'Contract not found');
  551. }
  552. if (!DolibarrApi::_checkAccessToResource('contrat', $this->contract->id)) {
  553. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  554. }
  555. $result = $this->contract->closeAll(DolibarrApiAccess::$user, $notrigger);
  556. if ($result == 0) {
  557. throw new RestException(304, 'Error nothing done. May be object is already close');
  558. }
  559. if ($result < 0) {
  560. throw new RestException(500, 'Error when closing Contract: '.$this->contract->error);
  561. }
  562. return array(
  563. 'success' => array(
  564. 'code' => 200,
  565. 'message' => 'Contract closed (Ref='.$this->contract->ref.'). All services were closed.'
  566. )
  567. );
  568. }
  569. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
  570. /**
  571. * Clean sensible object datas
  572. *
  573. * @param Object $object Object to clean
  574. * @return Object Object with cleaned properties
  575. */
  576. protected function _cleanObjectDatas($object)
  577. {
  578. // phpcs:enable
  579. $object = parent::_cleanObjectDatas($object);
  580. unset($object->address);
  581. unset($object->date_start);
  582. unset($object->date_start_real);
  583. unset($object->date_end);
  584. unset($object->date_end_real);
  585. unset($object->civility_id);
  586. return $object;
  587. }
  588. /**
  589. * Validate fields before create or update object
  590. *
  591. * @param array $data Array with data to verify
  592. * @return array
  593. * @throws RestException
  594. */
  595. private function _validate($data)
  596. {
  597. $contrat = array();
  598. foreach (Contracts::$FIELDS as $field) {
  599. if (!isset($data[$field])) {
  600. throw new RestException(400, "$field field missing");
  601. }
  602. $contrat[$field] = $data[$field];
  603. }
  604. return $contrat;
  605. }
  606. }