commoninvoice.class.php 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059
  1. <?php
  2. /* Copyright (C) 2012 Regis Houssin <regis.houssin@inodbox.com>
  3. * Copyright (C) 2012 Cédric Salvador <csalvador@gpcsolutions.fr>
  4. * Copyright (C) 2012-2014 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
  5. *
  6. * This program is free software; you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation; either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  18. */
  19. /**
  20. * \file htdocs/core/class/commoninvoice.class.php
  21. * \ingroup core
  22. * \brief File of the superclass of invoices classes (customer and supplier)
  23. */
  24. require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
  25. require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
  26. /**
  27. * Superclass for invoices classes
  28. */
  29. abstract class CommonInvoice extends CommonObject
  30. {
  31. use CommonIncoterm;
  32. /**
  33. * Standard invoice
  34. */
  35. const TYPE_STANDARD = 0;
  36. /**
  37. * Replacement invoice
  38. */
  39. const TYPE_REPLACEMENT = 1;
  40. /**
  41. * Credit note invoice
  42. */
  43. const TYPE_CREDIT_NOTE = 2;
  44. /**
  45. * Deposit invoice
  46. */
  47. const TYPE_DEPOSIT = 3;
  48. /**
  49. * Proforma invoice.
  50. * @deprectad Remove this. A "proforma invoice" is an order with a look of invoice, not an invoice !
  51. */
  52. const TYPE_PROFORMA = 4;
  53. /**
  54. * Situation invoice
  55. */
  56. const TYPE_SITUATION = 5;
  57. /**
  58. * Draft status
  59. */
  60. const STATUS_DRAFT = 0;
  61. /**
  62. * Validated (need to be paid)
  63. */
  64. const STATUS_VALIDATED = 1;
  65. /**
  66. * Classified paid.
  67. * If paid partially, $this->close_code can be:
  68. * - CLOSECODE_DISCOUNTVAT
  69. * - CLOSECODE_BADDEBT
  70. * If paid completelly, this->close_code will be null
  71. */
  72. const STATUS_CLOSED = 2;
  73. /**
  74. * Classified abandoned and no payment done.
  75. * $this->close_code can be:
  76. * - CLOSECODE_BADDEBT
  77. * - CLOSECODE_ABANDONED
  78. * - CLOSECODE_REPLACED
  79. */
  80. const STATUS_ABANDONED = 3;
  81. /**
  82. * Return remain amount to pay. Property ->id and ->total_ttc must be set.
  83. * This does not include open direct debit requests.
  84. *
  85. * @param int $multicurrency Return multicurrency_amount instead of amount
  86. * @return float Remain of amount to pay
  87. */
  88. public function getRemainToPay($multicurrency = 0)
  89. {
  90. $alreadypaid = 0.0;
  91. $alreadypaid += $this->getSommePaiement($multicurrency);
  92. $alreadypaid += $this->getSumDepositsUsed($multicurrency);
  93. $alreadypaid += $this->getSumCreditNotesUsed($multicurrency);
  94. $remaintopay = price2num($this->total_ttc - $alreadypaid, 'MT');
  95. if ($this->statut == self::STATUS_CLOSED && $this->close_code == 'discount_vat') { // If invoice closed with discount for anticipated payment
  96. $remaintopay = 0.0;
  97. }
  98. return $remaintopay;
  99. }
  100. /**
  101. * Return amount of payments already done. This must include ONLY the record into the payment table.
  102. * Payments dones using discounts, credit notes, etc are not included.
  103. *
  104. * @param int $multicurrency Return multicurrency_amount instead of amount
  105. * @return float Amount of payment already done, <0 if KO
  106. */
  107. public function getSommePaiement($multicurrency = 0)
  108. {
  109. $table = 'paiement_facture';
  110. $field = 'fk_facture';
  111. if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
  112. $table = 'paiementfourn_facturefourn';
  113. $field = 'fk_facturefourn';
  114. }
  115. $sql = 'SELECT sum(amount) as amount, sum(multicurrency_amount) as multicurrency_amount';
  116. $sql .= ' FROM '.MAIN_DB_PREFIX.$table;
  117. $sql .= " WHERE ".$field." = ".((int) $this->id);
  118. dol_syslog(get_class($this)."::getSommePaiement", LOG_DEBUG);
  119. $resql = $this->db->query($sql);
  120. if ($resql) {
  121. $obj = $this->db->fetch_object($resql);
  122. $this->db->free($resql);
  123. if ($multicurrency) {
  124. return $obj->multicurrency_amount;
  125. } else {
  126. return $obj->amount;
  127. }
  128. } else {
  129. $this->error = $this->db->lasterror();
  130. return -1;
  131. }
  132. }
  133. /**
  134. * Return amount (with tax) of all deposits invoices used by invoice.
  135. * Should always be empty, except if option FACTURE_DEPOSITS_ARE_JUST_PAYMENTS is on (not recommended).
  136. *
  137. * @param int $multicurrency Return multicurrency_amount instead of amount
  138. * @return float <0 if KO, Sum of deposits amount otherwise
  139. */
  140. public function getSumDepositsUsed($multicurrency = 0)
  141. {
  142. if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
  143. // TODO
  144. return 0.0;
  145. }
  146. require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
  147. $discountstatic = new DiscountAbsolute($this->db);
  148. $result = $discountstatic->getSumDepositsUsed($this, $multicurrency);
  149. if ($result >= 0) {
  150. return $result;
  151. } else {
  152. $this->error = $discountstatic->error;
  153. return -1;
  154. }
  155. }
  156. /**
  157. * Return amount (with tax) of all credit notes invoices + excess received used by invoice
  158. *
  159. * @param int $multicurrency Return multicurrency_amount instead of amount
  160. * @return float <0 if KO, Sum of credit notes and deposits amount otherwise
  161. */
  162. public function getSumCreditNotesUsed($multicurrency = 0)
  163. {
  164. require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
  165. $discountstatic = new DiscountAbsolute($this->db);
  166. $result = $discountstatic->getSumCreditNotesUsed($this, $multicurrency);
  167. if ($result >= 0) {
  168. return $result;
  169. } else {
  170. $this->error = $discountstatic->error;
  171. return -1;
  172. }
  173. }
  174. /**
  175. * Return amount (with tax) of all converted amount for this credit note
  176. *
  177. * @param int $multicurrency Return multicurrency_amount instead of amount
  178. * @return float <0 if KO, Sum of credit notes and deposits amount otherwise
  179. */
  180. public function getSumFromThisCreditNotesNotUsed($multicurrency = 0)
  181. {
  182. require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
  183. $discountstatic = new DiscountAbsolute($this->db);
  184. $result = $discountstatic->getSumFromThisCreditNotesNotUsed($this, $multicurrency);
  185. if ($result >= 0) {
  186. return $result;
  187. } else {
  188. $this->error = $discountstatic->error;
  189. return -1;
  190. }
  191. }
  192. /**
  193. * Returns array of credit note ids from the invoice
  194. *
  195. * @return array Array of credit note ids
  196. */
  197. public function getListIdAvoirFromInvoice()
  198. {
  199. $idarray = array();
  200. $sql = 'SELECT rowid';
  201. $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
  202. $sql .= " WHERE fk_facture_source = ".((int) $this->id);
  203. $sql .= ' AND type = 2';
  204. $resql = $this->db->query($sql);
  205. if ($resql) {
  206. $num = $this->db->num_rows($resql);
  207. $i = 0;
  208. while ($i < $num) {
  209. $row = $this->db->fetch_row($resql);
  210. $idarray[] = $row[0];
  211. $i++;
  212. }
  213. } else {
  214. dol_print_error($this->db);
  215. }
  216. return $idarray;
  217. }
  218. /**
  219. * Returns the id of the invoice that replaces it
  220. *
  221. * @param string $option status filter ('', 'validated', ...)
  222. * @return int <0 si KO, 0 if no invoice replaces it, id of invoice otherwise
  223. */
  224. public function getIdReplacingInvoice($option = '')
  225. {
  226. $sql = 'SELECT rowid';
  227. $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element;
  228. $sql .= " WHERE fk_facture_source = ".((int) $this->id);
  229. $sql .= ' AND type < 2';
  230. if ($option == 'validated') {
  231. $sql .= ' AND fk_statut = 1';
  232. }
  233. // PROTECTION BAD DATA
  234. // In case the database is corrupted and there is a valid replectement invoice
  235. // and another no, priority is given to the valid one.
  236. // Should not happen (unless concurrent access and 2 people have created a
  237. // replacement invoice for the same invoice at the same time)
  238. $sql .= ' ORDER BY fk_statut DESC';
  239. $resql = $this->db->query($sql);
  240. if ($resql) {
  241. $obj = $this->db->fetch_object($resql);
  242. if ($obj) {
  243. // If there is any
  244. return $obj->rowid;
  245. } else {
  246. // If no invoice replaces it
  247. return 0;
  248. }
  249. } else {
  250. return -1;
  251. }
  252. }
  253. /**
  254. * Return list of payments
  255. *
  256. * @param string $filtertype 1 to filter on type of payment == 'PRE'
  257. * @return array Array with list of payments
  258. */
  259. public function getListOfPayments($filtertype = '')
  260. {
  261. $retarray = array();
  262. $table = 'paiement_facture';
  263. $table2 = 'paiement';
  264. $field = 'fk_facture';
  265. $field2 = 'fk_paiement';
  266. $field3 = ', p.ref_ext';
  267. $sharedentity = 'facture';
  268. if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
  269. $table = 'paiementfourn_facturefourn';
  270. $table2 = 'paiementfourn';
  271. $field = 'fk_facturefourn';
  272. $field2 = 'fk_paiementfourn';
  273. $field3 = '';
  274. $sharedentity = 'facture_fourn';
  275. }
  276. $sql = "SELECT p.ref, pf.amount, pf.multicurrency_amount, p.fk_paiement, p.datep, p.num_paiement as num, t.code".$field3;
  277. $sql .= " FROM ".MAIN_DB_PREFIX.$table." as pf, ".MAIN_DB_PREFIX.$table2." as p, ".MAIN_DB_PREFIX."c_paiement as t";
  278. $sql .= " WHERE pf.".$field." = ".((int) $this->id);
  279. $sql .= " AND pf.".$field2." = p.rowid";
  280. $sql .= ' AND p.fk_paiement = t.id';
  281. $sql .= ' AND p.entity IN ('.getEntity($sharedentity).')';
  282. if ($filtertype) {
  283. $sql .= " AND t.code='PRE'";
  284. }
  285. dol_syslog(get_class($this)."::getListOfPayments", LOG_DEBUG);
  286. $resql = $this->db->query($sql);
  287. if ($resql) {
  288. $num = $this->db->num_rows($resql);
  289. $i = 0;
  290. while ($i < $num) {
  291. $obj = $this->db->fetch_object($resql);
  292. $tmp = array('amount'=>$obj->amount, 'type'=>$obj->code, 'date'=>$obj->datep, 'num'=>$obj->num, 'ref'=>$obj->ref);
  293. if (!empty($field3)) {
  294. $tmp['ref_ext'] = $obj->ref_ext;
  295. }
  296. $retarray[] = $tmp;
  297. $i++;
  298. }
  299. $this->db->free($resql);
  300. //look for credit notes and discounts and deposits
  301. $sql = '';
  302. if ($this->element == 'facture' || $this->element == 'invoice') {
  303. $sql = "SELECT rc.amount_ttc as amount, rc.multicurrency_amount_ttc as multicurrency_amount, rc.datec as date, f.ref as ref, rc.description as type";
  304. $sql .= ' FROM '.MAIN_DB_PREFIX.'societe_remise_except as rc, '.MAIN_DB_PREFIX.'facture as f';
  305. $sql .= ' WHERE rc.fk_facture_source=f.rowid AND rc.fk_facture = '.((int) $this->id);
  306. $sql .= ' AND (f.type = 2 OR f.type = 0 OR f.type = 3)'; // Find discount coming from credit note or excess received or deposits (payments from deposits are always null except if FACTURE_DEPOSITS_ARE_JUST_PAYMENTS is set)
  307. } elseif ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
  308. $sql = "SELECT rc.amount_ttc as amount, rc.multicurrency_amount_ttc as multicurrency_amount, rc.datec as date, f.ref as ref, rc.description as type";
  309. $sql .= ' FROM '.MAIN_DB_PREFIX.'societe_remise_except as rc, '.MAIN_DB_PREFIX.'facture_fourn as f';
  310. $sql .= ' WHERE rc.fk_invoice_supplier_source=f.rowid AND rc.fk_invoice_supplier = '.((int) $this->id);
  311. $sql .= ' AND (f.type = 2 OR f.type = 0 OR f.type = 3)'; // Find discount coming from credit note or excess received or deposits (payments from deposits are always null except if FACTURE_DEPOSITS_ARE_JUST_PAYMENTS is set)
  312. }
  313. if ($sql) {
  314. $resql = $this->db->query($sql);
  315. if ($resql) {
  316. $num = $this->db->num_rows($resql);
  317. $i = 0;
  318. while ($i < $num) {
  319. $obj = $this->db->fetch_object($resql);
  320. if ($multicurrency) {
  321. $retarray[] = array('amount'=>$obj->multicurrency_amount, 'type'=>$obj->type, 'date'=>$obj->date, 'num'=>'0', 'ref'=>$obj->ref);
  322. } else {
  323. $retarray[] = array('amount'=>$obj->amount, 'type'=>$obj->type, 'date'=>$obj->date, 'num'=>'', 'ref'=>$obj->ref);
  324. }
  325. $i++;
  326. }
  327. } else {
  328. $this->error = $this->db->lasterror();
  329. dol_print_error($this->db);
  330. return array();
  331. }
  332. $this->db->free($resql);
  333. }
  334. return $retarray;
  335. } else {
  336. $this->error = $this->db->lasterror();
  337. dol_print_error($this->db);
  338. return array();
  339. }
  340. }
  341. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  342. /**
  343. * Return if an invoice can be deleted
  344. * Rule is:
  345. * If invoice is draft and has a temporary ref -> yes (1)
  346. * If hidden option INVOICE_CAN_NEVER_BE_REMOVED is on -> no (0)
  347. * If invoice is dispatched in bookkeeping -> no (-1)
  348. * If invoice has a definitive ref, is not last and INVOICE_CAN_ALWAYS_BE_REMOVED off -> no (-2)
  349. * If invoice not last in a cycle -> no (-3)
  350. * If there is payment -> no (-4)
  351. * Otherwise -> yes (2)
  352. *
  353. * @return int <=0 if no, >0 if yes
  354. */
  355. public function is_erasable()
  356. {
  357. // phpcs:enable
  358. global $conf;
  359. // We check if invoice is a temporary number (PROVxxxx)
  360. $tmppart = substr($this->ref, 1, 4);
  361. if ($this->statut == self::STATUS_DRAFT && $tmppart === 'PROV') { // If draft invoice and ref not yet defined
  362. return 1;
  363. }
  364. if (!empty($conf->global->INVOICE_CAN_NEVER_BE_REMOVED)) {
  365. return 0;
  366. }
  367. // If not a draft invoice and not temporary invoice
  368. if ($tmppart !== 'PROV') {
  369. $ventilExportCompta = $this->getVentilExportCompta();
  370. if ($ventilExportCompta != 0) {
  371. return -1;
  372. }
  373. // Get last number of validated invoice
  374. if ($this->element != 'invoice_supplier') {
  375. if (empty($this->thirdparty)) {
  376. $this->fetch_thirdparty(); // We need to have this->thirdparty defined, in case of numbering rule use tags that depend on thirdparty (like {t} tag).
  377. }
  378. $maxref = $this->getNextNumRef($this->thirdparty, 'last');
  379. // If there is no invoice into the reset range and not already dispatched, we can delete
  380. // If invoice to delete is last one and not already dispatched, we can delete
  381. if (empty($conf->global->INVOICE_CAN_ALWAYS_BE_REMOVED) && $maxref != '' && $maxref != $this->ref) {
  382. return -2;
  383. }
  384. // TODO If there is payment in bookkeeping, check payment is not dispatched in accounting
  385. // ...
  386. if ($this->situation_cycle_ref && method_exists($this, 'is_last_in_cycle')) {
  387. $last = $this->is_last_in_cycle();
  388. if (!$last) {
  389. return -3;
  390. }
  391. }
  392. }
  393. }
  394. // Test if there is at least one payment. If yes, refuse to delete.
  395. if (empty($conf->global->INVOICE_CAN_ALWAYS_BE_REMOVED) && $this->getSommePaiement() > 0) {
  396. return -4;
  397. }
  398. return 2;
  399. }
  400. /**
  401. * Return if an invoice was dispatched into bookkeeping
  402. *
  403. * @return int <0 if KO, 0=no, 1=yes
  404. */
  405. public function getVentilExportCompta()
  406. {
  407. $alreadydispatched = 0;
  408. $type = 'customer_invoice';
  409. if ($this->element == 'invoice_supplier') {
  410. $type = 'supplier_invoice';
  411. }
  412. $sql = " SELECT COUNT(ab.rowid) as nb FROM ".MAIN_DB_PREFIX."accounting_bookkeeping as ab WHERE ab.doc_type='".$this->db->escape($type)."' AND ab.fk_doc = ".((int) $this->id);
  413. $resql = $this->db->query($sql);
  414. if ($resql) {
  415. $obj = $this->db->fetch_object($resql);
  416. if ($obj) {
  417. $alreadydispatched = $obj->nb;
  418. }
  419. } else {
  420. $this->error = $this->db->lasterror();
  421. return -1;
  422. }
  423. if ($alreadydispatched) {
  424. return 1;
  425. }
  426. return 0;
  427. }
  428. /**
  429. * Return label of type of invoice
  430. *
  431. * @return string Label of type of invoice
  432. */
  433. public function getLibType()
  434. {
  435. global $langs;
  436. if ($this->type == CommonInvoice::TYPE_STANDARD) {
  437. return $langs->trans("InvoiceStandard");
  438. } elseif ($this->type == CommonInvoice::TYPE_REPLACEMENT) {
  439. return $langs->trans("InvoiceReplacement");
  440. } elseif ($this->type == CommonInvoice::TYPE_CREDIT_NOTE) {
  441. return $langs->trans("InvoiceAvoir");
  442. } elseif ($this->type == CommonInvoice::TYPE_DEPOSIT) {
  443. return $langs->trans("InvoiceDeposit");
  444. } elseif ($this->type == CommonInvoice::TYPE_PROFORMA) {
  445. return $langs->trans("InvoiceProForma"); // Not used.
  446. } elseif ($this->type == CommonInvoice::TYPE_SITUATION) {
  447. return $langs->trans("InvoiceSituation");
  448. }
  449. return $langs->trans("Unknown");
  450. }
  451. /**
  452. * Return label of object status
  453. *
  454. * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=short label + picto, 6=Long label + picto
  455. * @param integer $alreadypaid 0=No payment already done, >0=Some payments were already done (we recommand to put here amount payed if you have it, 1 otherwise)
  456. * @return string Label of status
  457. */
  458. public function getLibStatut($mode = 0, $alreadypaid = -1)
  459. {
  460. return $this->LibStatut($this->paye, $this->statut, $mode, $alreadypaid, $this->type);
  461. }
  462. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  463. /**
  464. * Return label of a status
  465. *
  466. * @param int $paye Status field paye
  467. * @param int $status Id status
  468. * @param int $mode 0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=short label + picto, 6=long label + picto
  469. * @param integer $alreadypaid 0=No payment already done, >0=Some payments were already done (we recommand to put here amount payed if you have it, -1 otherwise)
  470. * @param int $type Type invoice. If -1, we use $this->type
  471. * @return string Label of status
  472. */
  473. public function LibStatut($paye, $status, $mode = 0, $alreadypaid = -1, $type = -1)
  474. {
  475. // phpcs:enable
  476. global $langs;
  477. $langs->load('bills');
  478. if ($type == -1) {
  479. $type = $this->type;
  480. }
  481. $statusType = 'status0';
  482. $prefix = 'Short';
  483. if (!$paye) {
  484. if ($status == 0) {
  485. $labelStatus = $langs->transnoentitiesnoconv('BillStatusDraft');
  486. $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusDraft');
  487. } elseif (($status == 3 || $status == 2) && $alreadypaid <= 0) {
  488. $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedUnpaid');
  489. $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedUnpaid');
  490. $statusType = 'status5';
  491. } elseif (($status == 3 || $status == 2) && $alreadypaid > 0) {
  492. $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedPaidPartially');
  493. $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedPaidPartially');
  494. $statusType = 'status9';
  495. } elseif ($alreadypaid == 0) {
  496. $labelStatus = $langs->transnoentitiesnoconv('BillStatusNotPaid');
  497. $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusNotPaid');
  498. $statusType = 'status1';
  499. } else {
  500. $labelStatus = $langs->transnoentitiesnoconv('BillStatusStarted');
  501. $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusStarted');
  502. $statusType = 'status3';
  503. }
  504. } else {
  505. $statusType = 'status6';
  506. if ($type == self::TYPE_CREDIT_NOTE) {
  507. $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaidBackOrConverted'); // credit note
  508. $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaidBackOrConverted'); // credit note
  509. } elseif ($type == self::TYPE_DEPOSIT) {
  510. $labelStatus = $langs->transnoentitiesnoconv('BillStatusConverted'); // deposit invoice
  511. $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusConverted'); // deposit invoice
  512. } else {
  513. $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaid');
  514. $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaid');
  515. }
  516. }
  517. return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
  518. }
  519. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  520. /**
  521. * Returns an invoice payment deadline based on the invoice settlement
  522. * conditions and billing date.
  523. *
  524. * @param integer $cond_reglement Condition of payment (code or id) to use. If 0, we use current condition.
  525. * @return integer Date limite de reglement si ok, <0 si ko
  526. */
  527. public function calculate_date_lim_reglement($cond_reglement = 0)
  528. {
  529. // phpcs:enable
  530. if (!$cond_reglement) {
  531. $cond_reglement = $this->cond_reglement_code;
  532. }
  533. if (!$cond_reglement) {
  534. $cond_reglement = $this->cond_reglement_id;
  535. }
  536. $cdr_nbjour = 0;
  537. $cdr_type = 0;
  538. $cdr_decalage = 0;
  539. $sqltemp = 'SELECT c.type_cdr, c.nbjour, c.decalage';
  540. $sqltemp .= ' FROM '.MAIN_DB_PREFIX.'c_payment_term as c';
  541. if (is_numeric($cond_reglement)) {
  542. $sqltemp .= " WHERE c.rowid=".((int) $cond_reglement);
  543. } else {
  544. $sqltemp .= " WHERE c.entity IN (".getEntity('c_payment_term').")";
  545. $sqltemp .= " AND c.code = '".$this->db->escape($cond_reglement)."'";
  546. }
  547. dol_syslog(get_class($this).'::calculate_date_lim_reglement', LOG_DEBUG);
  548. $resqltemp = $this->db->query($sqltemp);
  549. if ($resqltemp) {
  550. if ($this->db->num_rows($resqltemp)) {
  551. $obj = $this->db->fetch_object($resqltemp);
  552. $cdr_nbjour = $obj->nbjour;
  553. $cdr_type = $obj->type_cdr;
  554. $cdr_decalage = $obj->decalage;
  555. }
  556. } else {
  557. $this->error = $this->db->error();
  558. return -1;
  559. }
  560. $this->db->free($resqltemp);
  561. /* Definition de la date limite */
  562. // 0 : adding the number of days
  563. if ($cdr_type == 0) {
  564. $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
  565. $datelim += ($cdr_decalage * 3600 * 24);
  566. } elseif ($cdr_type == 1) {
  567. // 1 : application of the "end of the month" rule
  568. $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
  569. $mois = date('m', $datelim);
  570. $annee = date('Y', $datelim);
  571. if ($mois == 12) {
  572. $mois = 1;
  573. $annee += 1;
  574. } else {
  575. $mois += 1;
  576. }
  577. // We move at the beginning of the next month, and we take a day off
  578. $datelim = dol_mktime(12, 0, 0, $mois, 1, $annee);
  579. $datelim -= (3600 * 24);
  580. $datelim += ($cdr_decalage * 3600 * 24);
  581. } elseif ($cdr_type == 2 && !empty($cdr_decalage)) {
  582. // 2 : application of the rule, the N of the current or next month
  583. include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
  584. $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
  585. $date_piece = dol_mktime(0, 0, 0, date('m', $datelim), date('d', $datelim), date('Y', $datelim)); // Sans les heures minutes et secondes
  586. $date_lim_current = dol_mktime(0, 0, 0, date('m', $datelim), $cdr_decalage, date('Y', $datelim)); // Sans les heures minutes et secondes
  587. $date_lim_next = dol_time_plus_duree($date_lim_current, 1, 'm'); // Add 1 month
  588. $diff = $date_piece - $date_lim_current;
  589. if ($diff < 0) {
  590. $datelim = $date_lim_current;
  591. } else {
  592. $datelim = $date_lim_next;
  593. }
  594. } else {
  595. return 'Bad value for type_cdr in database for record cond_reglement = '.$cond_reglement;
  596. }
  597. return $datelim;
  598. }
  599. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  600. /**
  601. * Create a withdrawal request for a direct debit order or a credit transfer order.
  602. * Use the remain to pay excluding all existing open direct debit requests.
  603. *
  604. * @param User $fuser User asking the direct debit transfer
  605. * @param float $amount Amount we request direct debit for
  606. * @param string $type 'direct-debit' or 'bank-transfer'
  607. * @param string $sourcetype Source ('facture' or 'supplier_invoice')
  608. * @return int <0 if KO, >0 if OK
  609. */
  610. public function demande_prelevement($fuser, $amount = 0, $type = 'direct-debit', $sourcetype = 'facture')
  611. {
  612. // phpcs:enable
  613. global $conf;
  614. $error = 0;
  615. dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
  616. if ($this->statut > self::STATUS_DRAFT && $this->paye == 0) {
  617. require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
  618. $bac = new CompanyBankAccount($this->db);
  619. $bac->fetch(0, $this->socid);
  620. $sql = 'SELECT count(*)';
  621. $sql .= ' FROM '.MAIN_DB_PREFIX.'prelevement_facture_demande';
  622. if ($type == 'bank-transfer') {
  623. $sql .= ' WHERE fk_facture_fourn = '.((int) $this->id);
  624. } else {
  625. $sql .= ' WHERE fk_facture = '.((int) $this->id);
  626. }
  627. $sql .= ' AND ext_payment_id IS NULL'; // To exclude record done for some online payments
  628. $sql .= ' AND traite = 0';
  629. dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
  630. $resql = $this->db->query($sql);
  631. if ($resql) {
  632. $row = $this->db->fetch_row($resql);
  633. if ($row[0] == 0) {
  634. $now = dol_now();
  635. $totalpaye = $this->getSommePaiement();
  636. $totalcreditnotes = $this->getSumCreditNotesUsed();
  637. $totaldeposits = $this->getSumDepositsUsed();
  638. //print "totalpaye=".$totalpaye." totalcreditnotes=".$totalcreditnotes." totaldeposts=".$totaldeposits;
  639. // We can also use bcadd to avoid pb with floating points
  640. // For example print 239.2 - 229.3 - 9.9; does not return 0.
  641. //$resteapayer=bcadd($this->total_ttc,$totalpaye,$conf->global->MAIN_MAX_DECIMALS_TOT);
  642. //$resteapayer=bcadd($resteapayer,$totalavoir,$conf->global->MAIN_MAX_DECIMALS_TOT);
  643. if (empty($amount)) {
  644. $amount = price2num($this->total_ttc - $totalpaye - $totalcreditnotes - $totaldeposits, 'MT');
  645. }
  646. if (is_numeric($amount) && $amount != 0) {
  647. $sql = 'INSERT INTO '.MAIN_DB_PREFIX.'prelevement_facture_demande(';
  648. if ($type == 'bank-transfer') {
  649. $sql .= 'fk_facture_fourn, ';
  650. } else {
  651. $sql .= 'fk_facture, ';
  652. }
  653. $sql .= ' amount, date_demande, fk_user_demande, code_banque, code_guichet, number, cle_rib, sourcetype, entity)';
  654. $sql .= " VALUES (".((int) $this->id);
  655. $sql .= ", ".((float) price2num($amount));
  656. $sql .= ", '".$this->db->idate($now)."'";
  657. $sql .= ", ".((int) $fuser->id);
  658. $sql .= ", '".$this->db->escape($bac->code_banque)."'";
  659. $sql .= ", '".$this->db->escape($bac->code_guichet)."'";
  660. $sql .= ", '".$this->db->escape($bac->number)."'";
  661. $sql .= ", '".$this->db->escape($bac->cle_rib)."'";
  662. $sql .= ", '".$this->db->escape($sourcetype)."'";
  663. $sql .= ", ".((int) $conf->entity);
  664. $sql .= ")";
  665. dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
  666. $resql = $this->db->query($sql);
  667. if (!$resql) {
  668. $this->error = $this->db->lasterror();
  669. dol_syslog(get_class($this).'::demandeprelevement Erreur');
  670. $error++;
  671. }
  672. } else {
  673. $this->error = 'WithdrawRequestErrorNilAmount';
  674. dol_syslog(get_class($this).'::demandeprelevement WithdrawRequestErrorNilAmount');
  675. $error++;
  676. }
  677. if (!$error) {
  678. // Force payment mode of invoice to withdraw
  679. $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
  680. if ($payment_mode_id > 0) {
  681. $result = $this->setPaymentMethods($payment_mode_id);
  682. }
  683. }
  684. if ($error) {
  685. return -1;
  686. }
  687. return 1;
  688. } else {
  689. $this->error = "A request already exists";
  690. dol_syslog(get_class($this).'::demandeprelevement Impossible de creer une demande, demande deja en cours');
  691. return 0;
  692. }
  693. } else {
  694. $this->error = $this->db->error();
  695. dol_syslog(get_class($this).'::demandeprelevement Erreur -2');
  696. return -2;
  697. }
  698. } else {
  699. $this->error = "Status of invoice does not allow this";
  700. dol_syslog(get_class($this)."::demandeprelevement ".$this->error." $this->statut, $this->paye, $this->mode_reglement_id");
  701. return -3;
  702. }
  703. }
  704. // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
  705. /**
  706. * Remove a direct debit request or a credit transfer request
  707. *
  708. * @param User $fuser User making delete
  709. * @param int $did ID of request to delete
  710. * @return int <0 if OK, >0 if KO
  711. */
  712. public function demande_prelevement_delete($fuser, $did)
  713. {
  714. // phpcs:enable
  715. $sql = 'DELETE FROM '.MAIN_DB_PREFIX.'prelevement_facture_demande';
  716. $sql .= ' WHERE rowid = '.((int) $did);
  717. $sql .= ' AND traite = 0';
  718. if ($this->db->query($sql)) {
  719. return 0;
  720. } else {
  721. $this->error = $this->db->lasterror();
  722. dol_syslog(get_class($this).'::demande_prelevement_delete Error '.$this->error);
  723. return -1;
  724. }
  725. }
  726. /**
  727. * Build string for ZATCA QR Code (Arabi Saudia)
  728. *
  729. * @return string String for ZATCA QR Code
  730. */
  731. public function buildZATCAQRString()
  732. {
  733. global $conf, $mysoc;
  734. $tmplang = new Translate('', $conf);
  735. $tmplang->setDefaultLang('en_US');
  736. $tmplang->load("main");
  737. $datestring = dol_print_date($this->date, 'dayhourrfc');
  738. //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
  739. //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
  740. $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
  741. $pricetaxstring = price2num($this->total_tva, 2, 1);
  742. /*
  743. $name = implode(unpack("H*", $this->thirdparty->name));
  744. $vatnumber = implode(unpack("H*", $this->thirdparty->tva_intra));
  745. $date = implode(unpack("H*", $datestring));
  746. $pricewithtax = implode(unpack("H*", price2num($pricewithtaxstring, 2)));
  747. $pricetax = implode(unpack("H*", $pricetaxstring));
  748. var_dump(strlen($this->thirdparty->name));
  749. var_dump(str_pad(dechex('9'), 2, '0', STR_PAD_LEFT));
  750. var_dump($this->thirdparty->name);
  751. var_dump(implode(unpack("H*", $this->thirdparty->name)));
  752. var_dump(price($this->total_tva, 0, $tmplang, 0, -1, 2));
  753. $s = '01'.str_pad(dechex(strlen($this->thirdparty->name)), 2, '0', STR_PAD_LEFT).$name;
  754. $s .= '02'.str_pad(dechex(strlen($this->thirdparty->tva_intra)), 2, '0', STR_PAD_LEFT).$vatnumber;
  755. $s .= '03'.str_pad(dechex(strlen($datestring)), 2, '0', STR_PAD_LEFT).$date;
  756. $s .= '04'.str_pad(dechex(strlen($pricewithtaxstring)), 2, '0', STR_PAD_LEFT).$pricewithtax;
  757. $s .= '05'.str_pad(dechex(strlen($pricetaxstring)), 2, '0', STR_PAD_LEFT).$pricetax;
  758. $s .= ''; // Hash of xml invoice
  759. $s .= ''; // ecda signature
  760. $s .= ''; // ecda public key
  761. $s .= ''; // ecda signature of public key stamp
  762. */
  763. // Using TLV format
  764. $s = pack('C1', 1).pack('C1', strlen($this->thirdparty->name)).$mysoc->name;
  765. $s .= pack('C1', 2).pack('C1', strlen($this->thirdparty->tva_intra)).$mysoc->tva_intra;
  766. $s .= pack('C1', 3).pack('C1', strlen($datestring)).$datestring;
  767. $s .= pack('C1', 4).pack('C1', strlen($pricewithtaxstring)).$pricewithtaxstring;
  768. $s .= pack('C1', 5).pack('C1', strlen($pricetaxstring)).$pricetaxstring;
  769. $s .= ''; // Hash of xml invoice
  770. $s .= ''; // ecda signature
  771. $s .= ''; // ecda public key
  772. $s .= ''; // ecda signature of public key stamp
  773. $s = base64_encode($s);
  774. return $s;
  775. }
  776. }
  777. require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
  778. /**
  779. * Parent class of all other business classes for details of elements (invoices, contracts, proposals, orders, ...)
  780. */
  781. abstract class CommonInvoiceLine extends CommonObjectLine
  782. {
  783. /**
  784. * Custom label of line. Not used by default.
  785. * @deprecated
  786. */
  787. public $label;
  788. /**
  789. * @deprecated
  790. * @see $product_ref
  791. */
  792. public $ref; // Product ref (deprecated)
  793. /**
  794. * @deprecated
  795. * @see $product_label
  796. */
  797. public $libelle; // Product label (deprecated)
  798. /**
  799. * Type of the product. 0 for product 1 for service
  800. * @var int
  801. */
  802. public $product_type = 0;
  803. /**
  804. * Product ref
  805. * @var string
  806. */
  807. public $product_ref;
  808. /**
  809. * Product label
  810. * @var string
  811. */
  812. public $product_label;
  813. /**
  814. * Product description
  815. * @var string
  816. */
  817. public $product_desc;
  818. /**
  819. * Quantity
  820. * @var double
  821. */
  822. public $qty;
  823. /**
  824. * Unit price before taxes
  825. * @var float
  826. */
  827. public $subprice;
  828. /**
  829. * Unit price before taxes
  830. * @var float
  831. * @deprecated
  832. */
  833. public $price;
  834. /**
  835. * Id of corresponding product
  836. * @var int
  837. */
  838. public $fk_product;
  839. /**
  840. * VAT code
  841. * @var string
  842. */
  843. public $vat_src_code;
  844. /**
  845. * VAT %
  846. * @var float
  847. */
  848. public $tva_tx;
  849. /**
  850. * Local tax 1 %
  851. * @var float
  852. */
  853. public $localtax1_tx;
  854. /**
  855. * Local tax 2 %
  856. * @var float
  857. */
  858. public $localtax2_tx;
  859. /**
  860. * Local tax 1 type
  861. * @var string
  862. */
  863. public $localtax1_type;
  864. /**
  865. * Local tax 2 type
  866. * @var string
  867. */
  868. public $localtax2_type;
  869. /**
  870. * Percent of discount
  871. * @var float
  872. */
  873. public $remise_percent;
  874. /**
  875. * Fixed discount
  876. * @var float
  877. * @deprecated
  878. */
  879. public $remise;
  880. /**
  881. * Total amount before taxes
  882. * @var float
  883. */
  884. public $total_ht;
  885. /**
  886. * Total VAT amount
  887. * @var float
  888. */
  889. public $total_tva;
  890. /**
  891. * Total local tax 1 amount
  892. * @var float
  893. */
  894. public $total_localtax1;
  895. /**
  896. * Total local tax 2 amount
  897. * @var float
  898. */
  899. public $total_localtax2;
  900. /**
  901. * Total amount with taxes
  902. * @var float
  903. */
  904. public $total_ttc;
  905. public $date_start_fill; // If set to 1, when invoice is created from a template invoice, it will also auto set the field date_start at creation
  906. public $date_end_fill; // If set to 1, when invoice is created from a template invoice, it will also auto set the field date_end at creation
  907. public $buy_price_ht;
  908. public $buyprice; // For backward compatibility
  909. public $pa_ht; // For backward compatibility
  910. public $marge_tx;
  911. public $marque_tx;
  912. /**
  913. * List of cumulative options:
  914. * Bit 0: 0 for common VAT - 1 if VAT french NPR
  915. * Bit 1: 0 si ligne normal - 1 si bit discount (link to line into llx_remise_except)
  916. * @var int
  917. */
  918. public $info_bits = 0;
  919. public $special_code = 0;
  920. public $fk_multicurrency;
  921. public $multicurrency_code;
  922. public $multicurrency_subprice;
  923. public $multicurrency_total_ht;
  924. public $multicurrency_total_tva;
  925. public $multicurrency_total_ttc;
  926. public $fk_user_author;
  927. public $fk_user_modif;
  928. }