api_invoices.class.php 56 KB

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