api_invoices.class.php 52 KB

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