api_invoices.class.php 56 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809
  1. <?php
  2. /* Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
  3. * Copyright (C) 2020 Thibault FOUCART <support@ptibogxiv.net>
  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.'/compta/facture/class/facture.class.php';
  20. require_once DOL_DOCUMENT_ROOT.'/compta/facture/class/facture-rec.class.php';
  21. /**
  22. * API class for invoices
  23. *
  24. * @access protected
  25. * @class DolibarrApiAccess {@requires user,external}
  26. */
  27. class Invoices extends DolibarrApi
  28. {
  29. /**
  30. *
  31. * @var array $FIELDS Mandatory fields, checked when create and update object
  32. */
  33. static $FIELDS = array(
  34. 'socid',
  35. );
  36. /**
  37. * @var Facture $invoice {@type Facture}
  38. */
  39. private $invoice;
  40. /**
  41. * @var FactureRec $templte_invoice {@type FactureRec}
  42. */
  43. private $template_invoice;
  44. /**
  45. * Constructor
  46. */
  47. public function __construct()
  48. {
  49. global $db, $conf;
  50. $this->db = $db;
  51. $this->invoice = new Facture($this->db);
  52. $this->template_invoice = new FactureRec($this->db);
  53. }
  54. /**
  55. * Get properties of a invoice object
  56. *
  57. * Return an array with invoice informations
  58. *
  59. * @param int $id ID of invoice
  60. * @param int $contact_list 0:Return array contains all properties, 1:Return array contains just id, -1: Do not return contacts/adddesses
  61. * @return array|mixed data without useless information
  62. *
  63. * @throws RestException
  64. */
  65. public function get($id, $contact_list = 1)
  66. {
  67. return $this->_fetch($id, '', '', $contact_list);
  68. }
  69. /**
  70. * Get properties of an invoice object by ref
  71. *
  72. * Return an array with invoice informations
  73. *
  74. * @param string $ref Ref of object
  75. * @param int $contact_list 0: Returned array of contacts/addresses contains all properties, 1: Return array contains just id, -1: Do not return contacts/adddesses
  76. * @return array|mixed data without useless information
  77. *
  78. * @url GET ref/{ref}
  79. *
  80. * @throws RestException
  81. */
  82. public function getByRef($ref, $contact_list = 1)
  83. {
  84. return $this->_fetch('', $ref, '', $contact_list);
  85. }
  86. /**
  87. * Get properties of an invoice object by ref_ext
  88. *
  89. * Return an array with invoice informations
  90. *
  91. * @param string $ref_ext External reference of object
  92. * @param int $contact_list 0: Returned array of contacts/addresses contains all properties, 1: Return array contains just id, -1: Do not return contacts/adddesses
  93. * @return array|mixed data without useless information
  94. *
  95. * @url GET ref_ext/{ref_ext}
  96. *
  97. * @throws RestException
  98. */
  99. public function getByRefExt($ref_ext, $contact_list = 1)
  100. {
  101. return $this->_fetch('', '', $ref_ext, $contact_list);
  102. }
  103. /**
  104. * Get properties of an invoice object
  105. *
  106. * Return an array with invoice informations
  107. *
  108. * @param int $id ID of order
  109. * @param string $ref Ref of object
  110. * @param string $ref_ext External reference of object
  111. * @param int $contact_list 0: Returned array of contacts/addresses contains all properties, 1: Return array contains just id, -1: Do not return contacts/adddesses
  112. * @return array|mixed data without useless information
  113. *
  114. * @throws RestException
  115. */
  116. private function _fetch($id, $ref = '', $ref_ext = '', $contact_list = 1)
  117. {
  118. if (!DolibarrApiAccess::$user->rights->facture->lire) {
  119. throw new RestException(401);
  120. }
  121. $result = $this->invoice->fetch($id, $ref, $ref_ext);
  122. if (!$result) {
  123. throw new RestException(404, 'Invoice not found');
  124. }
  125. // Get payment details
  126. $this->invoice->totalpaid = $this->invoice->getSommePaiement();
  127. $this->invoice->totalcreditnotes = $this->invoice->getSumCreditNotesUsed();
  128. $this->invoice->totaldeposits = $this->invoice->getSumDepositsUsed();
  129. $this->invoice->remaintopay = price2num($this->invoice->total_ttc - $this->invoice->totalpaid - $this->invoice->totalcreditnotes - $this->invoice->totaldeposits, 'MT');
  130. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  131. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  132. }
  133. // Add external contacts ids
  134. if ($contact_list > -1) {
  135. $tmparray = $this->invoice->liste_contact(-1, 'external', $contact_list);
  136. if (is_array($tmparray)) {
  137. $this->invoice->contacts_ids = $tmparray;
  138. }
  139. }
  140. $this->invoice->fetchObjectLinked();
  141. return $this->_cleanObjectDatas($this->invoice);
  142. }
  143. /**
  144. * List invoices
  145. *
  146. * Get a list of invoices
  147. *
  148. * @param string $sortfield Sort field
  149. * @param string $sortorder Sort order
  150. * @param int $limit Limit for list
  151. * @param int $page Page number
  152. * @param string $thirdparty_ids Thirdparty ids to filter orders of (example '1' or '1,2,3') {@pattern /^[0-9,]*$/i}
  153. * @param string $status Filter by invoice status : draft | unpaid | paid | cancelled
  154. * @param string $sqlfilters Other criteria to filter answers separated by a comma. Syntax example "(t.ref:like:'SO-%') and (t.date_creation:<:'20160101')"
  155. * @return array Array of invoice objects
  156. *
  157. * @throws RestException 404 Not found
  158. * @throws RestException 503 Error
  159. */
  160. public function index($sortfield = "t.rowid", $sortorder = 'ASC', $limit = 100, $page = 0, $thirdparty_ids = '', $status = '', $sqlfilters = '')
  161. {
  162. global $db, $conf;
  163. if (!DolibarrApiAccess::$user->rights->facture->lire) {
  164. throw new RestException(401);
  165. }
  166. $obj_ret = array();
  167. // case of external user, $thirdparty_ids param is ignored and replaced by user's socid
  168. $socids = DolibarrApiAccess::$user->socid ? DolibarrApiAccess::$user->socid : $thirdparty_ids;
  169. // If the internal user must only see his customers, force searching by him
  170. $search_sale = 0;
  171. if (!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) {
  172. $search_sale = DolibarrApiAccess::$user->id;
  173. }
  174. $sql = "SELECT t.rowid";
  175. if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) {
  176. $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)
  177. }
  178. $sql .= " FROM ".MAIN_DB_PREFIX."facture as t";
  179. if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) {
  180. $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc"; // We need this table joined to the select in order to filter by sale
  181. }
  182. $sql .= ' WHERE t.entity IN ('.getEntity('invoice').')';
  183. if ((!DolibarrApiAccess::$user->rights->societe->client->voir && !$socids) || $search_sale > 0) {
  184. $sql .= " AND t.fk_soc = sc.fk_soc";
  185. }
  186. if ($socids) {
  187. $sql .= " AND t.fk_soc IN (".$this->db->sanitize($socids).")";
  188. }
  189. if ($search_sale > 0) {
  190. $sql .= " AND t.rowid = sc.fk_soc"; // Join for the needed table to filter by sale
  191. }
  192. // Filter by status
  193. if ($status == 'draft') {
  194. $sql .= " AND t.fk_statut IN (0)";
  195. }
  196. if ($status == 'unpaid') {
  197. $sql .= " AND t.fk_statut IN (1)";
  198. }
  199. if ($status == 'paid') {
  200. $sql .= " AND t.fk_statut IN (2)";
  201. }
  202. if ($status == 'cancelled') {
  203. $sql .= " AND t.fk_statut IN (3)";
  204. }
  205. // Insert sale filter
  206. if ($search_sale > 0) {
  207. $sql .= " AND sc.fk_user = ".((int) $search_sale);
  208. }
  209. // Add sql filters
  210. if ($sqlfilters) {
  211. $errormessage = '';
  212. $sql .= forgeSQLFromUniversalSearchCriteria($sqlfilters, $errormessage);
  213. if ($errormessage) {
  214. throw new RestException(400, 'Error when validating parameter sqlfilters -> '.$errormessage);
  215. }
  216. }
  217. $sql .= $this->db->order($sortfield, $sortorder);
  218. if ($limit) {
  219. if ($page < 0) {
  220. $page = 0;
  221. }
  222. $offset = $limit * $page;
  223. $sql .= $this->db->plimit($limit + 1, $offset);
  224. }
  225. $result = $this->db->query($sql);
  226. if ($result) {
  227. $i = 0;
  228. $num = $this->db->num_rows($result);
  229. $min = min($num, ($limit <= 0 ? $num : $limit));
  230. while ($i < $min) {
  231. $obj = $this->db->fetch_object($result);
  232. $invoice_static = new Facture($this->db);
  233. if ($invoice_static->fetch($obj->rowid)) {
  234. // Get payment details
  235. $invoice_static->totalpaid = $invoice_static->getSommePaiement();
  236. $invoice_static->totalcreditnotes = $invoice_static->getSumCreditNotesUsed();
  237. $invoice_static->totaldeposits = $invoice_static->getSumDepositsUsed();
  238. $invoice_static->remaintopay = price2num($invoice_static->total_ttc - $invoice_static->totalpaid - $invoice_static->totalcreditnotes - $invoice_static->totaldeposits, 'MT');
  239. // Add external contacts ids
  240. $tmparray = $invoice_static->liste_contact(-1, 'external', 1);
  241. if (is_array($tmparray)) {
  242. $invoice_static->contacts_ids = $tmparray;
  243. }
  244. $obj_ret[] = $this->_cleanObjectDatas($invoice_static);
  245. }
  246. $i++;
  247. }
  248. } else {
  249. throw new RestException(503, 'Error when retrieve invoice list : '.$this->db->lasterror());
  250. }
  251. if (!count($obj_ret)) {
  252. throw new RestException(404, 'No invoice found');
  253. }
  254. return $obj_ret;
  255. }
  256. /**
  257. * Create invoice object
  258. *
  259. * @param array $request_data Request datas
  260. * @return int ID of invoice
  261. */
  262. public function post($request_data = null)
  263. {
  264. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  265. throw new RestException(401, "Insuffisant rights");
  266. }
  267. // Check mandatory fields
  268. $result = $this->_validate($request_data);
  269. foreach ($request_data as $field => $value) {
  270. $this->invoice->$field = $value;
  271. }
  272. if (!array_key_exists('date', $request_data)) {
  273. $this->invoice->date = dol_now();
  274. }
  275. /* We keep lines as an array
  276. if (isset($request_data["lines"])) {
  277. $lines = array();
  278. foreach ($request_data["lines"] as $line) {
  279. array_push($lines, (object) $line);
  280. }
  281. $this->invoice->lines = $lines;
  282. }*/
  283. if ($this->invoice->create(DolibarrApiAccess::$user, 0, (empty($request_data["date_lim_reglement"]) ? 0 : $request_data["date_lim_reglement"])) < 0) {
  284. throw new RestException(500, "Error creating invoice", array_merge(array($this->invoice->error), $this->invoice->errors));
  285. }
  286. return $this->invoice->id;
  287. }
  288. /**
  289. * Create an invoice using an existing order.
  290. *
  291. *
  292. * @param int $orderid Id of the order
  293. *
  294. * @url POST /createfromorder/{orderid}
  295. *
  296. * @return int
  297. * @throws RestException 400
  298. * @throws RestException 401
  299. * @throws RestException 404
  300. * @throws RestException 405
  301. */
  302. public function createInvoiceFromOrder($orderid)
  303. {
  304. require_once DOL_DOCUMENT_ROOT.'/commande/class/commande.class.php';
  305. if (!DolibarrApiAccess::$user->rights->commande->lire) {
  306. throw new RestException(401);
  307. }
  308. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  309. throw new RestException(401);
  310. }
  311. if (empty($orderid)) {
  312. throw new RestException(400, 'Order ID is mandatory');
  313. }
  314. $order = new Commande($this->db);
  315. $result = $order->fetch($orderid);
  316. if (!$result) {
  317. throw new RestException(404, 'Order not found');
  318. }
  319. $result = $this->invoice->createFromOrder($order, DolibarrApiAccess::$user);
  320. if ($result < 0) {
  321. throw new RestException(405, $this->invoice->error);
  322. }
  323. $this->invoice->fetchObjectLinked();
  324. return $this->_cleanObjectDatas($this->invoice);
  325. }
  326. /**
  327. * Get lines of an invoice
  328. *
  329. * @param int $id Id of invoice
  330. *
  331. * @url GET {id}/lines
  332. *
  333. * @return int
  334. */
  335. public function getLines($id)
  336. {
  337. if (!DolibarrApiAccess::$user->rights->facture->lire) {
  338. throw new RestException(401);
  339. }
  340. $result = $this->invoice->fetch($id);
  341. if (!$result) {
  342. throw new RestException(404, 'Invoice not found');
  343. }
  344. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  345. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  346. }
  347. $this->invoice->getLinesArray();
  348. $result = array();
  349. foreach ($this->invoice->lines as $line) {
  350. array_push($result, $this->_cleanObjectDatas($line));
  351. }
  352. return $result;
  353. }
  354. /**
  355. * Update a line to a given invoice
  356. *
  357. * @param int $id Id of invoice to update
  358. * @param int $lineid Id of line to update
  359. * @param array $request_data InvoiceLine data
  360. *
  361. * @url PUT {id}/lines/{lineid}
  362. *
  363. * @return array
  364. *
  365. * @throws RestException 304
  366. * @throws RestException 401
  367. * @throws RestException 404 Invoice not found
  368. */
  369. public function putLine($id, $lineid, $request_data = null)
  370. {
  371. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  372. throw new RestException(401);
  373. }
  374. $result = $this->invoice->fetch($id);
  375. if (!$result) {
  376. throw new RestException(404, 'Invoice not found');
  377. }
  378. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  379. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  380. }
  381. $request_data = (object) $request_data;
  382. $request_data->desc = sanitizeVal($request_data->desc, 'restricthtml');
  383. $request_data->label = sanitizeVal($request_data->label);
  384. $updateRes = $this->invoice->updateline(
  385. $lineid,
  386. $request_data->desc,
  387. $request_data->subprice,
  388. $request_data->qty,
  389. $request_data->remise_percent,
  390. $request_data->date_start,
  391. $request_data->date_end,
  392. $request_data->tva_tx,
  393. $request_data->localtax1_tx,
  394. $request_data->localtax2_tx,
  395. $request_data->price_base_type ? $request_data->price_base_type : 'HT',
  396. $request_data->info_bits,
  397. $request_data->product_type,
  398. $request_data->fk_parent_line,
  399. 0,
  400. $request_data->fk_fournprice,
  401. $request_data->pa_ht,
  402. $request_data->label,
  403. $request_data->special_code,
  404. $request_data->array_options,
  405. $request_data->situation_percent,
  406. $request_data->fk_unit,
  407. $request_data->multicurrency_subprice,
  408. 0,
  409. $request_data->ref_ext,
  410. $request_data->rang
  411. );
  412. if ($updateRes > 0) {
  413. $result = $this->get($id);
  414. unset($result->line);
  415. return $this->_cleanObjectDatas($result);
  416. } else {
  417. throw new RestException(304, $this->invoice->error);
  418. }
  419. }
  420. /**
  421. * Add a contact type of given invoice
  422. *
  423. * @param int $id Id of invoice to update
  424. * @param int $contactid Id of contact to add
  425. * @param string $type Type of the contact (BILLING, SHIPPING, CUSTOMER)
  426. *
  427. * @url POST {id}/contact/{contactid}/{type}
  428. *
  429. * @return int
  430. *
  431. * @throws RestException 401
  432. * @throws RestException 404
  433. */
  434. public function postContact($id, $contactid, $type)
  435. {
  436. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  437. throw new RestException(401);
  438. }
  439. $result = $this->invoice->fetch($id);
  440. if (!$result) {
  441. throw new RestException(404, 'Invoice not found');
  442. }
  443. if (!in_array($type, array('BILLING', 'SHIPPING', 'CUSTOMER'), true)) {
  444. throw new RestException(500, 'Availables types: BILLING, SHIPPING OR CUSTOMER');
  445. }
  446. if (!DolibarrApi::_checkAccessToResource('invoice', $this->invoice->id)) {
  447. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  448. }
  449. $result = $this->invoice->add_contact($contactid, $type, 'external');
  450. if (!$result) {
  451. throw new RestException(500, 'Error when added the contact');
  452. }
  453. return $this->_cleanObjectDatas($this->invoice);
  454. }
  455. /**
  456. * Delete a contact type of given invoice
  457. *
  458. * @param int $id Id of invoice to update
  459. * @param int $contactid Row key of the contact in the array contact_ids.
  460. * @param string $type Type of the contact (BILLING, SHIPPING, CUSTOMER).
  461. *
  462. * @url DELETE {id}/contact/{contactid}/{type}
  463. *
  464. * @return array
  465. *
  466. * @throws RestException 401
  467. * @throws RestException 404
  468. * @throws RestException 500 System error
  469. */
  470. public function deleteContact($id, $contactid, $type)
  471. {
  472. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  473. throw new RestException(401);
  474. }
  475. $result = $this->invoice->fetch($id);
  476. if (!$result) {
  477. throw new RestException(404, 'Invoice not found');
  478. }
  479. if (!DolibarrApi::_checkAccessToResource('invoice', $this->invoice->id)) {
  480. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  481. }
  482. $contacts = $this->invoice->liste_contact();
  483. foreach ($contacts as $contact) {
  484. if ($contact['id'] == $contactid && $contact['code'] == $type) {
  485. $result = $this->invoice->delete_contact($contact['rowid']);
  486. if (!$result) {
  487. throw new RestException(500, 'Error when deleted the contact');
  488. }
  489. }
  490. }
  491. return $this->_cleanObjectDatas($this->invoice);
  492. }
  493. /**
  494. * Deletes a line of a given invoice
  495. *
  496. * @param int $id Id of invoice
  497. * @param int $lineid Id of the line to delete
  498. *
  499. * @url DELETE {id}/lines/{lineid}
  500. *
  501. * @return array
  502. *
  503. * @throws RestException 400
  504. * @throws RestException 401
  505. * @throws RestException 404
  506. * @throws RestException 405
  507. */
  508. public function deleteLine($id, $lineid)
  509. {
  510. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  511. throw new RestException(401);
  512. }
  513. if (empty($lineid)) {
  514. throw new RestException(400, 'Line ID is mandatory');
  515. }
  516. if (!DolibarrApi::_checkAccessToResource('facture', $id)) {
  517. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  518. }
  519. $result = $this->invoice->fetch($id);
  520. if (!$result) {
  521. throw new RestException(404, 'Invoice not found');
  522. }
  523. // TODO Check the lineid $lineid is a line of ojbect
  524. $updateRes = $this->invoice->deleteline($lineid);
  525. if ($updateRes > 0) {
  526. return $this->get($id);
  527. } else {
  528. throw new RestException(405, $this->invoice->error);
  529. }
  530. }
  531. /**
  532. * Update invoice
  533. *
  534. * @param int $id Id of invoice to update
  535. * @param array $request_data Datas
  536. * @return int
  537. */
  538. public function put($id, $request_data = null)
  539. {
  540. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  541. throw new RestException(401);
  542. }
  543. $result = $this->invoice->fetch($id);
  544. if (!$result) {
  545. throw new RestException(404, 'Invoice not found');
  546. }
  547. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  548. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  549. }
  550. foreach ($request_data as $field => $value) {
  551. if ($field == 'id') {
  552. continue;
  553. }
  554. $this->invoice->$field = $value;
  555. }
  556. // update bank account
  557. if (!empty($this->invoice->fk_account)) {
  558. if ($this->invoice->setBankAccount($this->invoice->fk_account) == 0) {
  559. throw new RestException(400, $this->invoice->error);
  560. }
  561. }
  562. if ($this->invoice->update(DolibarrApiAccess::$user)) {
  563. return $this->get($id);
  564. }
  565. return false;
  566. }
  567. /**
  568. * Delete invoice
  569. *
  570. * @param int $id Invoice ID
  571. * @return array
  572. */
  573. public function delete($id)
  574. {
  575. if (!DolibarrApiAccess::$user->rights->facture->supprimer) {
  576. throw new RestException(401);
  577. }
  578. $result = $this->invoice->fetch($id);
  579. if (!$result) {
  580. throw new RestException(404, 'Invoice not found');
  581. }
  582. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  583. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  584. }
  585. $result = $this->invoice->delete(DolibarrApiAccess::$user);
  586. if ($result < 0) {
  587. throw new RestException(500, 'Error when deleting invoice');
  588. } elseif ($result == 0) {
  589. throw new RestException(403, 'Invoice not erasable');
  590. }
  591. return array(
  592. 'success' => array(
  593. 'code' => 200,
  594. 'message' => 'Invoice deleted'
  595. )
  596. );
  597. }
  598. /**
  599. * Add a line to a given invoice
  600. *
  601. * Exemple of POST query :
  602. * {
  603. * "desc": "Desc", "subprice": "1.00000000", "qty": "1", "tva_tx": "20.000", "localtax1_tx": "0.000", "localtax2_tx": "0.000",
  604. * "fk_product": "1", "remise_percent": "0", "date_start": "", "date_end": "", "fk_code_ventilation": 0, "info_bits": "0",
  605. * "fk_remise_except": null, "product_type": "1", "rang": "-1", "special_code": "0", "fk_parent_line": null, "fk_fournprice": null,
  606. * "pa_ht": "0.00000000", "label": "", "array_options": [], "situation_percent": "100", "fk_prev_id": null, "fk_unit": null
  607. * }
  608. *
  609. * @param int $id Id of invoice
  610. * @param array $request_data InvoiceLine data
  611. *
  612. * @url POST {id}/lines
  613. *
  614. * @return int
  615. *
  616. * @throws RestException 304
  617. * @throws RestException 401
  618. * @throws RestException 404
  619. * @throws RestException 400
  620. */
  621. public function postLine($id, $request_data = null)
  622. {
  623. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  624. throw new RestException(401);
  625. }
  626. $result = $this->invoice->fetch($id);
  627. if (!$result) {
  628. throw new RestException(404, 'Invoice not found');
  629. }
  630. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  631. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  632. }
  633. $request_data = (object) $request_data;
  634. $request_data->desc = sanitizeVal($request_data->desc, 'restricthtml');
  635. $request_data->label = sanitizeVal($request_data->label);
  636. // Reset fk_parent_line for no child products and special product
  637. if (($request_data->product_type != 9 && empty($request_data->fk_parent_line)) || $request_data->product_type == 9) {
  638. $request_data->fk_parent_line = 0;
  639. }
  640. // calculate pa_ht
  641. $marginInfos = getMarginInfos($request_data->subprice, $request_data->remise_percent, $request_data->tva_tx, $request_data->localtax1_tx, $request_data->localtax2_tx, $request_data->fk_fournprice, $request_data->pa_ht);
  642. $pa_ht = $marginInfos[0];
  643. $updateRes = $this->invoice->addline(
  644. $request_data->desc,
  645. $request_data->subprice,
  646. $request_data->qty,
  647. $request_data->tva_tx,
  648. $request_data->localtax1_tx,
  649. $request_data->localtax2_tx,
  650. $request_data->fk_product,
  651. $request_data->remise_percent,
  652. $request_data->date_start,
  653. $request_data->date_end,
  654. $request_data->fk_code_ventilation,
  655. $request_data->info_bits,
  656. $request_data->fk_remise_except,
  657. $request_data->price_base_type ? $request_data->price_base_type : 'HT',
  658. $request_data->subprice,
  659. $request_data->product_type,
  660. $request_data->rang,
  661. $request_data->special_code,
  662. $request_data->origin,
  663. $request_data->origin_id,
  664. $request_data->fk_parent_line,
  665. empty($request_data->fk_fournprice) ?null:$request_data->fk_fournprice,
  666. $pa_ht,
  667. $request_data->label,
  668. $request_data->array_options,
  669. $request_data->situation_percent,
  670. $request_data->fk_prev_id,
  671. $request_data->fk_unit,
  672. 0,
  673. $request_data->ref_ext
  674. );
  675. if ($updateRes < 0) {
  676. throw new RestException(400, 'Unable to insert the new line. Check your inputs. '.$this->invoice->error);
  677. }
  678. return $updateRes;
  679. }
  680. /**
  681. * Adds a contact to an invoice
  682. *
  683. * @param int $id Order ID
  684. * @param int $fk_socpeople Id of thirdparty contact (if source = 'external') or id of user (if souce = 'internal') to link
  685. * @param string $type_contact Type of contact (code). Must a code found into table llx_c_type_contact. For example: BILLING
  686. * @param string $source external=Contact extern (llx_socpeople), internal=Contact intern (llx_user)
  687. * @param int $notrigger Disable all triggers
  688. *
  689. * @url POST {id}/contacts
  690. *
  691. * @return array
  692. *
  693. * @throws RestException 304
  694. * @throws RestException 401
  695. * @throws RestException 404
  696. * @throws RestException 500 System error
  697. *
  698. */
  699. public function addContact($id, $fk_socpeople, $type_contact, $source, $notrigger = 0)
  700. {
  701. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  702. throw new RestException(401);
  703. }
  704. $result = $this->invoice->fetch($id);
  705. if (!$result) {
  706. throw new RestException(404, 'Invoice not found');
  707. }
  708. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  709. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  710. }
  711. $result = $this->invoice->add_contact($fk_socpeople, $type_contact, $source, $notrigger);
  712. if ($result < 0) {
  713. throw new RestException(500, 'Error : '.$this->invoice->error);
  714. }
  715. $result = $this->invoice->fetch($id);
  716. if (!$result) {
  717. throw new RestException(404, 'Invoice not found');
  718. }
  719. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  720. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  721. }
  722. return $this->_cleanObjectDatas($this->invoice);
  723. }
  724. /**
  725. * Sets an invoice as draft
  726. *
  727. * @param int $id Order ID
  728. * @param int $idwarehouse Warehouse ID
  729. *
  730. * @url POST {id}/settodraft
  731. *
  732. * @return array
  733. *
  734. * @throws RestException 304
  735. * @throws RestException 401
  736. * @throws RestException 404
  737. * @throws RestException 500 System error
  738. *
  739. */
  740. public function settodraft($id, $idwarehouse = -1)
  741. {
  742. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  743. throw new RestException(401);
  744. }
  745. $result = $this->invoice->fetch($id);
  746. if (!$result) {
  747. throw new RestException(404, 'Invoice not found');
  748. }
  749. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  750. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  751. }
  752. $result = $this->invoice->setDraft(DolibarrApiAccess::$user, $idwarehouse);
  753. if ($result == 0) {
  754. throw new RestException(304, 'Nothing done.');
  755. }
  756. if ($result < 0) {
  757. throw new RestException(500, 'Error : '.$this->invoice->error);
  758. }
  759. $result = $this->invoice->fetch($id);
  760. if (!$result) {
  761. throw new RestException(404, 'Invoice not found');
  762. }
  763. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  764. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  765. }
  766. return $this->_cleanObjectDatas($this->invoice);
  767. }
  768. /**
  769. * Validate an invoice
  770. *
  771. * If you get a bad value for param notrigger check that ou provide this in body
  772. * {
  773. * "idwarehouse": 0,
  774. * "notrigger": 0
  775. * }
  776. *
  777. * @param int $id Invoice ID
  778. * @param int $idwarehouse Warehouse ID
  779. * @param int $notrigger 1=Does not execute triggers, 0= execute triggers
  780. *
  781. * @url POST {id}/validate
  782. *
  783. * @return array
  784. */
  785. public function validate($id, $idwarehouse = 0, $notrigger = 0)
  786. {
  787. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  788. throw new RestException(401);
  789. }
  790. $result = $this->invoice->fetch($id);
  791. if (!$result) {
  792. throw new RestException(404, 'Invoice not found');
  793. }
  794. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  795. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  796. }
  797. $result = $this->invoice->validate(DolibarrApiAccess::$user, '', $idwarehouse, $notrigger);
  798. if ($result == 0) {
  799. throw new RestException(304, 'Error nothing done. May be object is already validated');
  800. }
  801. if ($result < 0) {
  802. throw new RestException(500, 'Error when validating Invoice: '.$this->invoice->error);
  803. }
  804. $result = $this->invoice->fetch($id);
  805. if (!$result) {
  806. throw new RestException(404, 'Invoice not found');
  807. }
  808. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  809. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  810. }
  811. return $this->_cleanObjectDatas($this->invoice);
  812. }
  813. /**
  814. * Sets an invoice as paid
  815. *
  816. * @param int $id Order ID
  817. * @param string $close_code Code filled if we classify to 'Paid completely' when payment is not complete (for escompte for example)
  818. * @param string $close_note Comment defined if we classify to 'Paid' when payment is not complete (for escompte for example)
  819. *
  820. * @url POST {id}/settopaid
  821. *
  822. * @return array An invoice object
  823. *
  824. * @throws RestException 304
  825. * @throws RestException 401
  826. * @throws RestException 404
  827. * @throws RestException 500 System error
  828. */
  829. public function settopaid($id, $close_code = '', $close_note = '')
  830. {
  831. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  832. throw new RestException(401);
  833. }
  834. $result = $this->invoice->fetch($id);
  835. if (!$result) {
  836. throw new RestException(404, 'Invoice not found');
  837. }
  838. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  839. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  840. }
  841. $result = $this->invoice->setPaid(DolibarrApiAccess::$user, $close_code, $close_note);
  842. if ($result == 0) {
  843. throw new RestException(304, 'Error nothing done. May be object is already validated');
  844. }
  845. if ($result < 0) {
  846. throw new RestException(500, 'Error : '.$this->invoice->error);
  847. }
  848. $result = $this->invoice->fetch($id);
  849. if (!$result) {
  850. throw new RestException(404, 'Invoice not found');
  851. }
  852. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  853. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  854. }
  855. return $this->_cleanObjectDatas($this->invoice);
  856. }
  857. /**
  858. * Sets an invoice as unpaid
  859. *
  860. * @param int $id Order ID
  861. *
  862. * @url POST {id}/settounpaid
  863. *
  864. * @return array An invoice object
  865. *
  866. * @throws RestException 304
  867. * @throws RestException 401
  868. * @throws RestException 404
  869. * @throws RestException 500 System error
  870. */
  871. public function settounpaid($id)
  872. {
  873. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  874. throw new RestException(401);
  875. }
  876. $result = $this->invoice->fetch($id);
  877. if (!$result) {
  878. throw new RestException(404, 'Invoice not found');
  879. }
  880. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  881. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  882. }
  883. $result = $this->invoice->setUnpaid(DolibarrApiAccess::$user);
  884. if ($result == 0) {
  885. throw new RestException(304, 'Nothing done');
  886. }
  887. if ($result < 0) {
  888. throw new RestException(500, 'Error : '.$this->invoice->error);
  889. }
  890. $result = $this->invoice->fetch($id);
  891. if (!$result) {
  892. throw new RestException(404, 'Invoice not found');
  893. }
  894. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  895. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  896. }
  897. return $this->_cleanObjectDatas($this->invoice);
  898. }
  899. /**
  900. * Get discount from invoice
  901. *
  902. * @param int $id Id of invoice
  903. *
  904. * @url GET {id}/discount
  905. *
  906. * @return mixed
  907. */
  908. public function getDiscount($id)
  909. {
  910. require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
  911. if (!DolibarrApiAccess::$user->rights->facture->lire) {
  912. throw new RestException(401);
  913. }
  914. $result = $this->invoice->fetch($id);
  915. if (!$result) {
  916. throw new RestException(404, 'Invoice not found');
  917. }
  918. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  919. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  920. }
  921. $discountcheck = new DiscountAbsolute($this->db);
  922. $result = $discountcheck->fetch(0, $this->invoice->id);
  923. if ($result == 0) {
  924. throw new RestException(404, 'Discount not found');
  925. }
  926. if ($result < 0) {
  927. throw new RestException(500, $discountcheck->error);
  928. }
  929. return parent::_cleanObjectDatas($discountcheck);
  930. }
  931. /**
  932. * Create a discount (credit available) for a credit note or a deposit.
  933. *
  934. * @param int $id Invoice ID
  935. * @url POST {id}/markAsCreditAvailable
  936. *
  937. * @return array An invoice object
  938. *
  939. * @throws RestException 304
  940. * @throws RestException 401
  941. * @throws RestException 404
  942. * @throws RestException 500 System error
  943. */
  944. public function markAsCreditAvailable($id)
  945. {
  946. require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
  947. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  948. throw new RestException(401);
  949. }
  950. $result = $this->invoice->fetch($id);
  951. if (!$result) {
  952. throw new RestException(404, 'Invoice not found');
  953. }
  954. if (!DolibarrApi::_checkAccessToResource('facture', $this->invoice->id)) {
  955. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  956. }
  957. if ($this->invoice->paye) {
  958. throw new RestException(500, 'Alreay paid');
  959. }
  960. $this->invoice->fetch($id);
  961. $this->invoice->fetch_thirdparty();
  962. // Check if there is already a discount (protection to avoid duplicate creation when resubmit post)
  963. $discountcheck = new DiscountAbsolute($this->db);
  964. $result = $discountcheck->fetch(0, $this->invoice->id);
  965. $canconvert = 0;
  966. if ($this->invoice->type == Facture::TYPE_DEPOSIT && empty($discountcheck->id)) {
  967. $canconvert = 1; // we can convert deposit into discount if deposit is payed (completely, partially or not at all) and not already converted (see real condition into condition used to show button converttoreduc)
  968. }
  969. if (($this->invoice->type == Facture::TYPE_CREDIT_NOTE || $this->invoice->type == Facture::TYPE_STANDARD) && $this->invoice->paye == 0 && empty($discountcheck->id)) {
  970. $canconvert = 1; // we can convert credit note into discount if credit note is not payed back and not already converted and amount of payment is 0 (see real condition into condition used to show button converttoreduc)
  971. }
  972. if ($canconvert) {
  973. $this->db->begin();
  974. $amount_ht = $amount_tva = $amount_ttc = array();
  975. $multicurrency_amount_ht = $multicurrency_amount_tva = $multicurrency_amount_ttc = array();
  976. // Loop on each vat rate
  977. $i = 0;
  978. foreach ($this->invoice->lines as $line) {
  979. if ($line->product_type < 9 && $line->total_ht != 0) { // Remove lines with product_type greater than or equal to 9
  980. // no need to create discount if amount is null
  981. $amount_ht[$line->tva_tx] += $line->total_ht;
  982. $amount_tva[$line->tva_tx] += $line->total_tva;
  983. $amount_ttc[$line->tva_tx] += $line->total_ttc;
  984. $multicurrency_amount_ht[$line->tva_tx] += $line->multicurrency_total_ht;
  985. $multicurrency_amount_tva[$line->tva_tx] += $line->multicurrency_total_tva;
  986. $multicurrency_amount_ttc[$line->tva_tx] += $line->multicurrency_total_ttc;
  987. $i++;
  988. }
  989. }
  990. // Insert one discount by VAT rate category
  991. $discount = new DiscountAbsolute($this->db);
  992. if ($this->invoice->type == Facture::TYPE_CREDIT_NOTE) {
  993. $discount->description = '(CREDIT_NOTE)';
  994. } elseif ($this->invoice->type == Facture::TYPE_DEPOSIT) {
  995. $discount->description = '(DEPOSIT)';
  996. } elseif ($this->invoice->type == Facture::TYPE_STANDARD || $this->invoice->type == Facture::TYPE_REPLACEMENT || $this->invoice->type == Facture::TYPE_SITUATION) {
  997. $discount->description = '(EXCESS RECEIVED)';
  998. } else {
  999. throw new RestException(500, 'Cant convert to reduc an Invoice of this type');
  1000. }
  1001. $discount->fk_soc = $this->invoice->socid;
  1002. $discount->fk_facture_source = $this->invoice->id;
  1003. $error = 0;
  1004. if ($this->invoice->type == Facture::TYPE_STANDARD || $this->invoice->type == Facture::TYPE_REPLACEMENT || $this->invoice->type == Facture::TYPE_SITUATION) {
  1005. // If we're on a standard invoice, we have to get excess received to create a discount in TTC without VAT
  1006. // Total payments
  1007. $sql = 'SELECT SUM(pf.amount) as total_payments';
  1008. $sql .= ' FROM '.MAIN_DB_PREFIX.'paiement_facture as pf, '.MAIN_DB_PREFIX.'paiement as p';
  1009. $sql .= ' LEFT JOIN '.MAIN_DB_PREFIX.'c_paiement as c ON p.fk_paiement = c.id';
  1010. $sql .= ' WHERE pf.fk_facture = '.((int) $this->invoice->id);
  1011. $sql .= ' AND pf.fk_paiement = p.rowid';
  1012. $sql .= ' AND p.entity IN ('.getEntity('invoice').')';
  1013. $resql = $this->db->query($sql);
  1014. if (!$resql) {
  1015. dol_print_error($this->db);
  1016. }
  1017. $res = $this->db->fetch_object($resql);
  1018. $total_payments = $res->total_payments;
  1019. // Total credit note and deposit
  1020. $total_creditnote_and_deposit = 0;
  1021. $sql = "SELECT re.rowid, re.amount_ht, re.amount_tva, re.amount_ttc,";
  1022. $sql .= " re.description, re.fk_facture_source";
  1023. $sql .= " FROM ".MAIN_DB_PREFIX."societe_remise_except as re";
  1024. $sql .= " WHERE fk_facture = ".((int) $this->invoice->id);
  1025. $resql = $this->db->query($sql);
  1026. if (!empty($resql)) {
  1027. while ($obj = $this->db->fetch_object($resql)) {
  1028. $total_creditnote_and_deposit += $obj->amount_ttc;
  1029. }
  1030. } else {
  1031. dol_print_error($this->db);
  1032. }
  1033. $discount->amount_ht = $discount->amount_ttc = $total_payments + $total_creditnote_and_deposit - $this->invoice->total_ttc;
  1034. $discount->amount_tva = 0;
  1035. $discount->tva_tx = 0;
  1036. $result = $discount->create(DolibarrApiAccess::$user);
  1037. if ($result < 0) {
  1038. $error++;
  1039. }
  1040. }
  1041. if ($this->invoice->type == Facture::TYPE_CREDIT_NOTE || $this->invoice->type == Facture::TYPE_DEPOSIT) {
  1042. foreach ($amount_ht as $tva_tx => $xxx) {
  1043. $discount->amount_ht = abs($amount_ht[$tva_tx]);
  1044. $discount->amount_tva = abs($amount_tva[$tva_tx]);
  1045. $discount->amount_ttc = abs($amount_ttc[$tva_tx]);
  1046. $discount->multicurrency_amount_ht = abs($multicurrency_amount_ht[$tva_tx]);
  1047. $discount->multicurrency_amount_tva = abs($multicurrency_amount_tva[$tva_tx]);
  1048. $discount->multicurrency_amount_ttc = abs($multicurrency_amount_ttc[$tva_tx]);
  1049. $discount->tva_tx = abs($tva_tx);
  1050. $result = $discount->create(DolibarrApiAccess::$user);
  1051. if ($result < 0) {
  1052. $error++;
  1053. break;
  1054. }
  1055. }
  1056. }
  1057. if (empty($error)) {
  1058. if ($this->invoice->type != Facture::TYPE_DEPOSIT) {
  1059. // Classe facture
  1060. $result = $this->invoice->setPaid(DolibarrApiAccess::$user);
  1061. if ($result >= 0) {
  1062. $this->db->commit();
  1063. } else {
  1064. $this->db->rollback();
  1065. throw new RestException(500, 'Could not set paid');
  1066. }
  1067. } else {
  1068. $this->db->commit();
  1069. }
  1070. } else {
  1071. $this->db->rollback();
  1072. throw new RestException(500, 'Discount creation error');
  1073. }
  1074. }
  1075. return $this->_cleanObjectDatas($this->invoice);
  1076. }
  1077. /**
  1078. * Add a discount line into an invoice (as an invoice line) using an existing absolute discount
  1079. *
  1080. * Note that this consume the discount.
  1081. *
  1082. * @param int $id Id of invoice
  1083. * @param int $discountid Id of discount
  1084. *
  1085. * @url POST {id}/usediscount/{discountid}
  1086. *
  1087. * @return int
  1088. *
  1089. * @throws RestException 400
  1090. * @throws RestException 401
  1091. * @throws RestException 404
  1092. * @throws RestException 405
  1093. */
  1094. public function useDiscount($id, $discountid)
  1095. {
  1096. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  1097. throw new RestException(401);
  1098. }
  1099. if (empty($id)) {
  1100. throw new RestException(400, 'Invoice ID is mandatory');
  1101. }
  1102. if (empty($discountid)) {
  1103. throw new RestException(400, 'Discount ID is mandatory');
  1104. }
  1105. if (!DolibarrApi::_checkAccessToResource('facture', $id)) {
  1106. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  1107. }
  1108. $result = $this->invoice->fetch($id);
  1109. if (!$result) {
  1110. throw new RestException(404, 'Invoice not found');
  1111. }
  1112. $result = $this->invoice->insert_discount($discountid);
  1113. if ($result < 0) {
  1114. throw new RestException(405, $this->invoice->error);
  1115. }
  1116. return $result;
  1117. }
  1118. /**
  1119. * Add an available credit note discount to payments of an existing invoice.
  1120. *
  1121. * Note that this consume the credit note.
  1122. *
  1123. * @param int $id Id of invoice
  1124. * @param int $discountid Id of a discount coming from a credit note
  1125. *
  1126. * @url POST {id}/usecreditnote/{discountid}
  1127. *
  1128. * @return int
  1129. *
  1130. * @throws RestException 400
  1131. * @throws RestException 401
  1132. * @throws RestException 404
  1133. * @throws RestException 405
  1134. */
  1135. public function useCreditNote($id, $discountid)
  1136. {
  1137. require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
  1138. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  1139. throw new RestException(401);
  1140. }
  1141. if (empty($id)) {
  1142. throw new RestException(400, 'Invoice ID is mandatory');
  1143. }
  1144. if (empty($discountid)) {
  1145. throw new RestException(400, 'Credit ID is mandatory');
  1146. }
  1147. if (!DolibarrApi::_checkAccessToResource('facture', $id)) {
  1148. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  1149. }
  1150. $discount = new DiscountAbsolute($this->db);
  1151. $result = $discount->fetch($discountid);
  1152. if (!$result) {
  1153. throw new RestException(404, 'Credit not found');
  1154. }
  1155. $result = $discount->link_to_invoice(0, $id);
  1156. if ($result < 0) {
  1157. throw new RestException(405, $discount->error);
  1158. }
  1159. return $result;
  1160. }
  1161. /**
  1162. * Get list of payments of a given invoice
  1163. *
  1164. * @param int $id Id of invoice
  1165. *
  1166. * @url GET {id}/payments
  1167. *
  1168. * @return array
  1169. *
  1170. * @throws RestException 400
  1171. * @throws RestException 401
  1172. * @throws RestException 404
  1173. * @throws RestException 405
  1174. */
  1175. public function getPayments($id)
  1176. {
  1177. if (!DolibarrApiAccess::$user->rights->facture->lire) {
  1178. throw new RestException(401);
  1179. }
  1180. if (empty($id)) {
  1181. throw new RestException(400, 'Invoice ID is mandatory');
  1182. }
  1183. if (!DolibarrApi::_checkAccessToResource('facture', $id)) {
  1184. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  1185. }
  1186. $result = $this->invoice->fetch($id);
  1187. if (!$result) {
  1188. throw new RestException(404, 'Invoice not found');
  1189. }
  1190. $result = $this->invoice->getListOfPayments();
  1191. if ($result < 0) {
  1192. throw new RestException(405, $this->invoice->error);
  1193. }
  1194. return $result;
  1195. }
  1196. /**
  1197. * Add payment line to a specific invoice with the remain to pay as amount.
  1198. *
  1199. * @param int $id Id of invoice
  1200. * @param string $datepaye {@from body} Payment date {@type timestamp}
  1201. * @param int $paymentid {@from body} Payment mode Id {@min 1}
  1202. * @param string $closepaidinvoices {@from body} Close paid invoices {@choice yes,no}
  1203. * @param int $accountid {@from body} Account Id {@min 1}
  1204. * @param string $num_payment {@from body} Payment number (optional)
  1205. * @param string $comment {@from body} Note private (optional)
  1206. * @param string $chqemetteur {@from body} Payment issuer (mandatory if paymentcode = 'CHQ')
  1207. * @param string $chqbank {@from body} Issuer bank name (optional)
  1208. *
  1209. * @url POST {id}/payments
  1210. *
  1211. * @return int Payment ID
  1212. *
  1213. * @throws RestException 400
  1214. * @throws RestException 401
  1215. * @throws RestException 404
  1216. */
  1217. public function addPayment($id, $datepaye, $paymentid, $closepaidinvoices, $accountid, $num_payment = '', $comment = '', $chqemetteur = '', $chqbank = '')
  1218. {
  1219. global $conf;
  1220. require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';
  1221. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  1222. throw new RestException(403);
  1223. }
  1224. if (empty($id)) {
  1225. throw new RestException(400, 'Invoice ID is mandatory');
  1226. }
  1227. if (!DolibarrApi::_checkAccessToResource('facture', $id)) {
  1228. throw new RestException(403, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  1229. }
  1230. if (isModEnabled("banque")) {
  1231. if (empty($accountid)) {
  1232. throw new RestException(400, 'Account ID is mandatory');
  1233. }
  1234. }
  1235. if (empty($paymentid)) {
  1236. throw new RestException(400, 'Payment ID or Payment Code is mandatory');
  1237. }
  1238. $result = $this->invoice->fetch($id);
  1239. if (!$result) {
  1240. throw new RestException(404, 'Invoice not found');
  1241. }
  1242. // Calculate amount to pay
  1243. $totalpaid = $this->invoice->getSommePaiement();
  1244. $totalcreditnotes = $this->invoice->getSumCreditNotesUsed();
  1245. $totaldeposits = $this->invoice->getSumDepositsUsed();
  1246. $resteapayer = price2num($this->invoice->total_ttc - $totalpaid - $totalcreditnotes - $totaldeposits, 'MT');
  1247. $this->db->begin();
  1248. $amounts = array();
  1249. $multicurrency_amounts = array();
  1250. // Clean parameters amount if payment is for a credit note
  1251. if ($this->invoice->type == Facture::TYPE_CREDIT_NOTE) {
  1252. $resteapayer = price2num($resteapayer, 'MT');
  1253. $amounts[$id] = -$resteapayer;
  1254. // Multicurrency
  1255. $newvalue = price2num($this->invoice->multicurrency_total_ttc, 'MT');
  1256. $multicurrency_amounts[$id] = -$newvalue;
  1257. } else {
  1258. $resteapayer = price2num($resteapayer, 'MT');
  1259. $amounts[$id] = $resteapayer;
  1260. // Multicurrency
  1261. $newvalue = price2num($this->invoice->multicurrency_total_ttc, 'MT');
  1262. $multicurrency_amounts[$id] = $newvalue;
  1263. }
  1264. // Creation of payment line
  1265. $paymentobj = new Paiement($this->db);
  1266. $paymentobj->datepaye = $datepaye;
  1267. $paymentobj->amounts = $amounts; // Array with all payments dispatching with invoice id
  1268. $paymentobj->multicurrency_amounts = $multicurrency_amounts; // Array with all payments dispatching
  1269. $paymentobj->paiementid = $paymentid;
  1270. $paymentobj->paiementcode = dol_getIdFromCode($this->db, $paymentid, 'c_paiement', 'id', 'code', 1);
  1271. $paymentobj->num_payment = $num_payment;
  1272. $paymentobj->note_private = $comment;
  1273. $payment_id = $paymentobj->create(DolibarrApiAccess::$user, ($closepaidinvoices == 'yes' ? 1 : 0)); // This include closing invoices
  1274. if ($payment_id < 0) {
  1275. $this->db->rollback();
  1276. throw new RestException(400, 'Payment error : '.$paymentobj->error);
  1277. }
  1278. if (isModEnabled("banque")) {
  1279. $label = '(CustomerInvoicePayment)';
  1280. if ($paymentobj->paiementcode == 'CHQ' && empty($chqemetteur)) {
  1281. throw new RestException(400, 'Emetteur is mandatory when payment code is '.$paymentobj->paiementcode);
  1282. }
  1283. if ($this->invoice->type == Facture::TYPE_CREDIT_NOTE) {
  1284. $label = '(CustomerInvoicePaymentBack)'; // Refund of a credit note
  1285. }
  1286. $result = $paymentobj->addPaymentToBank(DolibarrApiAccess::$user, 'payment', $label, $accountid, $chqemetteur, $chqbank);
  1287. if ($result < 0) {
  1288. $this->db->rollback();
  1289. throw new RestException(400, 'Add payment to bank error : '.$paymentobj->error);
  1290. }
  1291. }
  1292. $this->db->commit();
  1293. return $payment_id;
  1294. }
  1295. /**
  1296. * Add a payment to pay partially or completely one or several invoices.
  1297. * Warning: Take care that all invoices are owned by the same customer.
  1298. * Example of value for parameter arrayofamounts: {"1": {"amount": "99.99", "multicurrency_amount": ""}, "2": {"amount": "", "multicurrency_amount": "10"}}
  1299. *
  1300. * @param array $arrayofamounts {@from body} Array with id of invoices with amount to pay for each invoice
  1301. * @param string $datepaye {@from body} Payment date {@type timestamp}
  1302. * @param int $paymentid {@from body} Payment mode Id {@min 1}
  1303. * @param string $closepaidinvoices {@from body} Close paid invoices {@choice yes,no}
  1304. * @param int $accountid {@from body} Account Id {@min 1}
  1305. * @param string $num_payment {@from body} Payment number (optional)
  1306. * @param string $comment {@from body} Note private (optional)
  1307. * @param string $chqemetteur {@from body} Payment issuer (mandatory if paiementcode = 'CHQ')
  1308. * @param string $chqbank {@from body} Issuer bank name (optional)
  1309. * @param string $ref_ext {@from body} External reference (optional)
  1310. * @param bool $accepthigherpayment {@from body} Accept higher payments that it remains to be paid (optional)
  1311. *
  1312. * @url POST /paymentsdistributed
  1313. *
  1314. * @return int Payment ID
  1315. * @throws RestException 400
  1316. * @throws RestException 401
  1317. * @throws RestException 403
  1318. * @throws RestException 404
  1319. */
  1320. public function addPaymentDistributed($arrayofamounts, $datepaye, $paymentid, $closepaidinvoices, $accountid, $num_payment = '', $comment = '', $chqemetteur = '', $chqbank = '', $ref_ext = '', $accepthigherpayment = false)
  1321. {
  1322. global $conf;
  1323. require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';
  1324. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  1325. throw new RestException(403);
  1326. }
  1327. foreach ($arrayofamounts as $id => $amount) {
  1328. if (empty($id)) {
  1329. throw new RestException(400, 'Invoice ID is mandatory. Fill the invoice id and amount into arrayofamounts parameter. For example: {"1": "99.99", "2": "10"}');
  1330. }
  1331. if (!DolibarrApi::_checkAccessToResource('facture', $id)) {
  1332. throw new RestException(403, 'Access not allowed on invoice ID '.$id.' for login '.DolibarrApiAccess::$user->login);
  1333. }
  1334. }
  1335. if (isModEnabled("banque")) {
  1336. if (empty($accountid)) {
  1337. throw new RestException(400, 'Account ID is mandatory');
  1338. }
  1339. }
  1340. if (empty($paymentid)) {
  1341. throw new RestException(400, 'Payment ID or Payment Code is mandatory');
  1342. }
  1343. $this->db->begin();
  1344. $amounts = array();
  1345. $multicurrency_amounts = array();
  1346. // Loop on each invoice to pay
  1347. foreach ($arrayofamounts as $id => $amountarray) {
  1348. $result = $this->invoice->fetch($id);
  1349. if (!$result) {
  1350. $this->db->rollback();
  1351. throw new RestException(404, 'Invoice ID '.$id.' not found');
  1352. }
  1353. if (($amountarray["amount"] == "remain" || $amountarray["amount"] > 0) && ($amountarray["multicurrency_amount"] == "remain" || $amountarray["multicurrency_amount"] > 0)) {
  1354. $this->db->rollback();
  1355. throw new RestException(400, 'Payment in both currency '.$id.' ( amount: '.$amountarray["amount"].', multicurrency_amount: '.$amountarray["multicurrency_amount"].')');
  1356. }
  1357. $is_multicurrency = 0;
  1358. $total_ttc = $this->invoice->total_ttc;
  1359. if ($amountarray["multicurrency_amount"] > 0 || $amountarray["multicurrency_amount"] == "remain") {
  1360. $is_multicurrency = 1;
  1361. $total_ttc = $this->invoice->multicurrency_total_ttc;
  1362. }
  1363. // Calculate amount to pay
  1364. $totalpaid = $this->invoice->getSommePaiement($is_multicurrency);
  1365. $totalcreditnotes = $this->invoice->getSumCreditNotesUsed($is_multicurrency);
  1366. $totaldeposits = $this->invoice->getSumDepositsUsed($is_multicurrency);
  1367. $remainstopay = $amount = price2num($total_ttc - $totalpaid - $totalcreditnotes - $totaldeposits, 'MT');
  1368. if (!$is_multicurrency && $amountarray["amount"] != 'remain') {
  1369. $amount = price2num($amountarray["amount"], 'MT');
  1370. }
  1371. if ($is_multicurrency && $amountarray["multicurrency_amount"] != 'remain') {
  1372. $amount = price2num($amountarray["multicurrency_amount"], 'MT');
  1373. }
  1374. if ($amount > $remainstopay && !$accepthigherpayment) {
  1375. $this->db->rollback();
  1376. throw new RestException(400, 'Payment amount on invoice ID '.$id.' ('.$amount.') is higher than remain to pay ('.$remainstopay.')');
  1377. }
  1378. if ($this->invoice->type == Facture::TYPE_CREDIT_NOTE) {
  1379. $amount = -$amount;
  1380. }
  1381. if ($is_multicurrency) {
  1382. $amounts[$id] = null;
  1383. // Multicurrency
  1384. $multicurrency_amounts[$id] = $amount;
  1385. } else {
  1386. $amounts[$id] = $amount;
  1387. // Multicurrency
  1388. $multicurrency_amounts[$id] = null;
  1389. }
  1390. }
  1391. // Creation of payment line
  1392. $paymentobj = new Paiement($this->db);
  1393. $paymentobj->datepaye = $datepaye;
  1394. $paymentobj->amounts = $amounts; // Array with all payments dispatching with invoice id
  1395. $paymentobj->multicurrency_amounts = $multicurrency_amounts; // Array with all payments dispatching
  1396. $paymentobj->paiementid = $paymentid;
  1397. $paymentobj->paiementcode = dol_getIdFromCode($this->db, $paymentid, 'c_paiement', 'id', 'code', 1);
  1398. $paymentobj->num_payment = $num_payment;
  1399. $paymentobj->note_private = $comment;
  1400. $paymentobj->ref_ext = $ref_ext;
  1401. $payment_id = $paymentobj->create(DolibarrApiAccess::$user, ($closepaidinvoices == 'yes' ? 1 : 0)); // This include closing invoices
  1402. if ($payment_id < 0) {
  1403. $this->db->rollback();
  1404. throw new RestException(400, 'Payment error : '.$paymentobj->error);
  1405. }
  1406. if (isModEnabled("banque")) {
  1407. $label = '(CustomerInvoicePayment)';
  1408. if ($paymentobj->paiementcode == 'CHQ' && empty($chqemetteur)) {
  1409. throw new RestException(400, 'Emetteur is mandatory when payment code is '.$paymentobj->paiementcode);
  1410. }
  1411. if ($this->invoice->type == Facture::TYPE_CREDIT_NOTE) {
  1412. $label = '(CustomerInvoicePaymentBack)'; // Refund of a credit note
  1413. }
  1414. $result = $paymentobj->addPaymentToBank(DolibarrApiAccess::$user, 'payment', $label, $accountid, $chqemetteur, $chqbank);
  1415. if ($result < 0) {
  1416. $this->db->rollback();
  1417. throw new RestException(400, 'Add payment to bank error : '.$paymentobj->error);
  1418. }
  1419. }
  1420. $this->db->commit();
  1421. return $payment_id;
  1422. }
  1423. /**
  1424. * Update a payment
  1425. *
  1426. * @param int $id Id of payment
  1427. * @param string $num_payment Payment number
  1428. *
  1429. * @url PUT payments/{id}
  1430. *
  1431. * @return array
  1432. * @throws RestException 400 Bad parameters
  1433. * @throws RestException 401 Not allowed
  1434. * @throws RestException 404 Not found
  1435. */
  1436. public function putPayment($id, $num_payment = '')
  1437. {
  1438. require_once DOL_DOCUMENT_ROOT.'/compta/paiement/class/paiement.class.php';
  1439. if (!DolibarrApiAccess::$user->rights->facture->creer) {
  1440. throw new RestException(401);
  1441. }
  1442. if (empty($id)) {
  1443. throw new RestException(400, 'Payment ID is mandatory');
  1444. }
  1445. $paymentobj = new Paiement($this->db);
  1446. $result = $paymentobj->fetch($id);
  1447. if (!$result) {
  1448. throw new RestException(404, 'Payment not found');
  1449. }
  1450. if (!empty($num_payment)) {
  1451. $result = $paymentobj->update_num($num_payment);
  1452. if ($result < 0) {
  1453. throw new RestException(500, 'Error when updating the payment num');
  1454. }
  1455. }
  1456. return [
  1457. 'success' => [
  1458. 'code' => 200,
  1459. 'message' => 'Payment updated'
  1460. ]
  1461. ];
  1462. }
  1463. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
  1464. /**
  1465. * Clean sensible object datas
  1466. *
  1467. * @param Object $object Object to clean
  1468. * @return Object Object with cleaned properties
  1469. */
  1470. protected function _cleanObjectDatas($object)
  1471. {
  1472. // phpcs:enable
  1473. $object = parent::_cleanObjectDatas($object);
  1474. unset($object->note);
  1475. unset($object->address);
  1476. unset($object->barcode_type);
  1477. unset($object->barcode_type_code);
  1478. unset($object->barcode_type_label);
  1479. unset($object->barcode_type_coder);
  1480. unset($object->canvas);
  1481. return $object;
  1482. }
  1483. /**
  1484. * Validate fields before create or update object
  1485. *
  1486. * @param array|null $data Datas to validate
  1487. * @return array
  1488. *
  1489. * @throws RestException
  1490. */
  1491. private function _validate($data)
  1492. {
  1493. $invoice = array();
  1494. foreach (Invoices::$FIELDS as $field) {
  1495. if (!isset($data[$field])) {
  1496. throw new RestException(400, "$field field missing");
  1497. }
  1498. $invoice[$field] = $data[$field];
  1499. }
  1500. return $invoice;
  1501. }
  1502. /**
  1503. * Get properties of a template invoice object
  1504. *
  1505. * Return an array with invoice informations
  1506. *
  1507. * @param int $id ID of template invoice
  1508. * @param int $contact_list 0:Return array contains all properties, 1:Return array contains just id, -1: Do not return contacts/adddesses
  1509. * @return array|mixed data without useless information
  1510. *
  1511. * @url GET templates/{id}
  1512. *
  1513. * @throws RestException
  1514. */
  1515. public function getTemplateInvoice($id, $contact_list = 1)
  1516. {
  1517. return $this->_fetchTemplateInvoice($id, '', '', $contact_list);
  1518. }
  1519. /**
  1520. * Get properties of an invoice object
  1521. *
  1522. * Return an array with invoice informations
  1523. *
  1524. * @param int $id ID of order
  1525. * @param string $ref Ref of object
  1526. * @param string $ref_ext External reference of object
  1527. * @param int $contact_list 0: Returned array of contacts/addresses contains all properties, 1: Return array contains just id, -1: Do not return contacts/adddesses
  1528. * @return array|mixed data without useless information
  1529. *
  1530. * @throws RestException
  1531. */
  1532. private function _fetchTemplateInvoice($id, $ref = '', $ref_ext = '', $contact_list = 1)
  1533. {
  1534. if (!DolibarrApiAccess::$user->rights->facture->lire) {
  1535. throw new RestException(401);
  1536. }
  1537. $result = $this->template_invoice->fetch($id, $ref, $ref_ext);
  1538. if (!$result) {
  1539. throw new RestException(404, 'Template invoice not found');
  1540. }
  1541. if (!DolibarrApi::_checkAccessToResource('facturerec', $this->template_invoice->id)) {
  1542. throw new RestException(401, 'Access not allowed for login '.DolibarrApiAccess::$user->login);
  1543. }
  1544. // Add external contacts ids
  1545. if ($contact_list > -1) {
  1546. $tmparray = $this->template_invoice->liste_contact(-1, 'external', $contact_list);
  1547. if (is_array($tmparray)) {
  1548. $this->template_invoice->contacts_ids = $tmparray;
  1549. }
  1550. }
  1551. $this->template_invoice->fetchObjectLinked();
  1552. return $this->_cleanTemplateObjectDatas($this->template_invoice);
  1553. }
  1554. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.PublicUnderscore
  1555. /**
  1556. * Clean sensible object datas
  1557. *
  1558. * @param Object $object Object to clean
  1559. * @return Object Object with cleaned properties
  1560. */
  1561. protected function _cleanTemplateObjectDatas($object)
  1562. {
  1563. // phpcs:enable
  1564. $object = parent::_cleanObjectDatas($object);
  1565. unset($object->note);
  1566. unset($object->address);
  1567. unset($object->barcode_type);
  1568. unset($object->barcode_type_code);
  1569. unset($object->barcode_type_label);
  1570. unset($object->barcode_type_coder);
  1571. unset($object->canvas);
  1572. return $object;
  1573. }
  1574. }