dolibarr  20.0.0-alpha
commoninvoice.class.php
Go to the documentation of this file.
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  * Copyright (C) 2023 Nick Fragoulis
6  * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
7  * Copyright (C) 2024 MDW <mdeweerd@users.noreply.github.com>
8  *
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 3 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program. If not, see <https://www.gnu.org/licenses/>.
21  */
22 
29 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
30 require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
31 
35 abstract class CommonInvoice extends CommonObject
36 {
37  use CommonIncoterm;
38 
42  public $title;
43 
47  public $type = self::TYPE_STANDARD;
48 
52  public $subtype;
53 
59  public $fk_soc;
63  public $socid;
64 
65  public $paye;
66 
72  public $date;
73 
77  public $date_lim_reglement;
78 
79  public $cond_reglement_id; // Id in llx_c_paiement
80  public $cond_reglement_code; // Code in llx_c_paiement
81  public $cond_reglement_label;
82  public $cond_reglement_doc; // Code in llx_c_paiement
83 
84  public $mode_reglement_id;
85  public $mode_reglement_code; // Code in llx_c_paiement
86 
90  public $mode_reglement;
91 
95  public $revenuestamp;
96 
97  public $totalpaid; // duplicate with sumpayed
98  public $totaldeposits; // duplicate with sumdeposit
99  public $totalcreditnotes; // duplicate with sumcreditnote
100 
101  public $sumpayed;
102  public $sumpayed_multicurrency;
103  public $sumdeposit;
104  public $sumdeposit_multicurrency;
105  public $sumcreditnote;
106  public $sumcreditnote_multicurrency;
107  public $remaintopay;
108 
112  public $stripechargedone;
113 
117  public $stripechargeerror;
118 
123  public $description;
124 
130  public $ref_client;
131 
135  public $situation_cycle_ref;
136 
142  public $close_code;
143 
148  public $close_note;
149 
150 
155  public $postactionmessages;
156 
157 
161  const TYPE_STANDARD = 0;
162 
166  const TYPE_REPLACEMENT = 1;
167 
171  const TYPE_CREDIT_NOTE = 2;
172 
176  const TYPE_DEPOSIT = 3;
177 
182  const TYPE_PROFORMA = 4;
183 
187  const TYPE_SITUATION = 5;
188 
192  const STATUS_DRAFT = 0;
193 
197  const STATUS_VALIDATED = 1;
198 
206  const STATUS_CLOSED = 2;
207 
215  const STATUS_ABANDONED = 3;
216 
217 
218 
226  public function getRemainToPay($multicurrency = 0)
227  {
228  $alreadypaid = 0.0;
229  $alreadypaid += $this->getSommePaiement($multicurrency);
230  $alreadypaid += $this->getSumDepositsUsed($multicurrency);
231  $alreadypaid += $this->getSumCreditNotesUsed($multicurrency);
232 
233  $remaintopay = price2num($this->total_ttc - $alreadypaid, 'MT');
234  if ($this->statut == self::STATUS_CLOSED && $this->close_code == 'discount_vat') { // If invoice closed with discount for anticipated payment
235  $remaintopay = 0.0;
236  }
237  return $remaintopay;
238  }
239 
248  public function getSommePaiement($multicurrency = 0)
249  {
250  $table = 'paiement_facture';
251  $field = 'fk_facture';
252  if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
253  $table = 'paiementfourn_facturefourn';
254  $field = 'fk_facturefourn';
255  }
256 
257  $sql = "SELECT sum(amount) as amount, sum(multicurrency_amount) as multicurrency_amount";
258  $sql .= " FROM ".$this->db->prefix().$table;
259  $sql .= " WHERE ".$field." = ".((int) $this->id);
260 
261  dol_syslog(get_class($this)."::getSommePaiement", LOG_DEBUG);
262 
263  $resql = $this->db->query($sql);
264  if ($resql) {
265  $obj = $this->db->fetch_object($resql);
266 
267  $this->db->free($resql);
268 
269  if ($obj) {
270  if ($multicurrency < 0) {
271  $this->sumpayed = $obj->amount;
272  $this->sumpayed_multicurrency = $obj->multicurrency_amount;
273  return array('alreadypaid' => (float) $obj->amount, 'alreadypaid_multicurrency' => (float) $obj->multicurrency_amount);
274  } elseif ($multicurrency) {
275  $this->sumpayed_multicurrency = $obj->multicurrency_amount;
276  return (float) $obj->multicurrency_amount;
277  } else {
278  $this->sumpayed = $obj->amount;
279  return (float) $obj->amount;
280  }
281  } else {
282  return 0;
283  }
284  } else {
285  $this->error = $this->db->lasterror();
286  return -1;
287  }
288  }
289 
299  public function getSumDepositsUsed($multicurrency = 0)
300  {
301  /*if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
302  // FACTURE_SUPPLIER_DEPOSITS_ARE_JUST_PAYMENTS was never supported for purchase invoice, so we can return 0 with no need of SQL for this case.
303  return 0.0;
304  }*/
305 
306  require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
307 
308  $discountstatic = new DiscountAbsolute($this->db);
309  $result = $discountstatic->getSumDepositsUsed($this, $multicurrency);
310 
311  if ($result >= 0) {
312  if ($multicurrency) {
313  $this->sumdeposit_multicurrency = $result;
314  } else {
315  $this->sumdeposit = $result;
316  }
317 
318  return $result;
319  } else {
320  $this->error = $discountstatic->error;
321  return -1;
322  }
323  }
324 
332  public function getSumCreditNotesUsed($multicurrency = 0)
333  {
334  require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
335 
336  $discountstatic = new DiscountAbsolute($this->db);
337  $result = $discountstatic->getSumCreditNotesUsed($this, $multicurrency);
338  if ($result >= 0) {
339  if ($multicurrency) {
340  $this->sumcreditnote_multicurrency = $result;
341  } else {
342  $this->sumcreditnote = $result;
343  }
344 
345  return $result;
346  } else {
347  $this->error = $discountstatic->error;
348  return -1;
349  }
350  }
351 
358  public function getSumFromThisCreditNotesNotUsed($multicurrency = 0)
359  {
360  require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
361 
362  $discountstatic = new DiscountAbsolute($this->db);
363  $result = $discountstatic->getSumFromThisCreditNotesNotUsed($this, $multicurrency);
364  if ($result >= 0) {
365  return $result;
366  } else {
367  $this->error = $discountstatic->error;
368  return -1;
369  }
370  }
371 
377  public function getListIdAvoirFromInvoice()
378  {
379  $idarray = array();
380 
381  $sql = "SELECT rowid";
382  $sql .= " FROM ".$this->db->prefix().$this->table_element;
383  $sql .= " WHERE fk_facture_source = ".((int) $this->id);
384  $sql .= " AND type = 2";
385  $resql = $this->db->query($sql);
386  if ($resql) {
387  $num = $this->db->num_rows($resql);
388  $i = 0;
389  while ($i < $num) {
390  $row = $this->db->fetch_row($resql);
391  $idarray[] = $row[0];
392  $i++;
393  }
394  } else {
395  dol_print_error($this->db);
396  }
397  return $idarray;
398  }
399 
406  public function getIdReplacingInvoice($option = '')
407  {
408  $sql = "SELECT rowid";
409  $sql .= " FROM ".$this->db->prefix().$this->table_element;
410  $sql .= " WHERE fk_facture_source = ".((int) $this->id);
411  $sql .= " AND type < 2";
412  if ($option == 'validated') {
413  $sql .= ' AND fk_statut = 1';
414  }
415  // PROTECTION BAD DATA
416  // In case the database is corrupted and there is a valid replectement invoice
417  // and another no, priority is given to the valid one.
418  // Should not happen (unless concurrent access and 2 people have created a
419  // replacement invoice for the same invoice at the same time)
420  $sql .= " ORDER BY fk_statut DESC";
421 
422  $resql = $this->db->query($sql);
423  if ($resql) {
424  $obj = $this->db->fetch_object($resql);
425  if ($obj) {
426  // If there is any
427  return $obj->rowid;
428  } else {
429  // If no invoice replaces it
430  return 0;
431  }
432  } else {
433  return -1;
434  }
435  }
436 
446  public function getListOfPayments($filtertype = '', $multicurrency = 0)
447  {
448  $retarray = array();
449  // By default no error, list can be empty.
450  $this->error = '';
451 
452  $table = 'paiement_facture';
453  $table2 = 'paiement';
454  $field = 'fk_facture';
455  $field2 = 'fk_paiement';
456  $field3 = ', p.ref_ext';
457  $field4 = ', p.fk_bank'; // Bank line id
458  $sharedentity = 'facture';
459  if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
460  $table = 'paiementfourn_facturefourn';
461  $table2 = 'paiementfourn';
462  $field = 'fk_facturefourn';
463  $field2 = 'fk_paiementfourn';
464  $field3 = '';
465  $sharedentity = 'facture_fourn';
466  }
467 
468  $sql = "SELECT p.ref, pf.amount, pf.multicurrency_amount, p.fk_paiement, p.datep, p.num_paiement as num, t.code".$field3 . $field4;
469  $sql .= " FROM ".$this->db->prefix().$table." as pf, ".$this->db->prefix().$table2." as p, ".$this->db->prefix()."c_paiement as t";
470  $sql .= " WHERE pf.".$field." = ".((int) $this->id);
471  $sql .= " AND pf.".$field2." = p.rowid";
472  $sql .= ' AND p.fk_paiement = t.id';
473  $sql .= ' AND p.entity IN ('.getEntity($sharedentity).')';
474  if ($filtertype) {
475  $sql .= " AND t.code='PRE'";
476  }
477 
478  dol_syslog(get_class($this)."::getListOfPayments", LOG_DEBUG);
479  $resql = $this->db->query($sql);
480  if ($resql) {
481  $num = $this->db->num_rows($resql);
482  $i = 0;
483  while ($i < $num) {
484  $obj = $this->db->fetch_object($resql);
485  $tmp = array('amount' => $obj->amount, 'type' => $obj->code, 'date' => $obj->datep, 'num' => $obj->num, 'ref' => $obj->ref);
486  if (!empty($field3)) {
487  $tmp['ref_ext'] = $obj->ref_ext;
488  }
489  if (!empty($field4)) {
490  $tmp['fk_bank_line'] = $obj->fk_bank;
491  }
492  $retarray[] = $tmp;
493  $i++;
494  }
495  $this->db->free($resql);
496 
497  //look for credit notes and discounts and deposits
498  $sql = '';
499  if ($this->element == 'facture' || $this->element == 'invoice') {
500  $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";
501  $sql .= ' FROM '.$this->db->prefix().'societe_remise_except as rc, '.$this->db->prefix().'facture as f';
502  $sql .= ' WHERE rc.fk_facture_source=f.rowid AND rc.fk_facture = '.((int) $this->id);
503  $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)
504  } elseif ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
505  $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";
506  $sql .= ' FROM '.$this->db->prefix().'societe_remise_except as rc, '.$this->db->prefix().'facture_fourn as f';
507  $sql .= ' WHERE rc.fk_invoice_supplier_source=f.rowid AND rc.fk_invoice_supplier = '.((int) $this->id);
508  $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_SUPPLIER_DEPOSITS_ARE_JUST_PAYMENTS is set)
509  }
510 
511  if ($sql) {
512  $resql = $this->db->query($sql);
513  if ($resql) {
514  $num = $this->db->num_rows($resql);
515  $i = 0;
516  while ($i < $num) {
517  $obj = $this->db->fetch_object($resql);
518  if ($multicurrency) {
519  $retarray[] = array('amount' => $obj->multicurrency_amount, 'type' => $obj->type, 'date' => $obj->date, 'num' => '0', 'ref' => $obj->ref);
520  } else {
521  $retarray[] = array('amount' => $obj->amount, 'type' => $obj->type, 'date' => $obj->date, 'num' => '', 'ref' => $obj->ref);
522  }
523  $i++;
524  }
525  } else {
526  $this->error = $this->db->lasterror();
527  dol_print_error($this->db);
528  return array();
529  }
530  $this->db->free($resql);
531  }
532 
533  return $retarray;
534  } else {
535  $this->error = $this->db->lasterror();
536  dol_print_error($this->db);
537  return array();
538  }
539  }
540 
541 
542  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
556  public function is_erasable()
557  {
558  // phpcs:enable
559 
560  // We check if invoice is a temporary number (PROVxxxx)
561  $tmppart = substr($this->ref, 1, 4);
562 
563  if ($this->statut == self::STATUS_DRAFT && $tmppart === 'PROV') { // If draft invoice and ref not yet defined
564  return 1;
565  }
566 
567  if (getDolGlobalString('INVOICE_CAN_NEVER_BE_REMOVED')) {
568  return 0;
569  }
570 
571  // If not a draft invoice and not temporary invoice
572  if ($tmppart !== 'PROV') {
573  $ventilExportCompta = $this->getVentilExportCompta();
574  if ($ventilExportCompta != 0) {
575  return -1;
576  }
577 
578  // Get last number of validated invoice
579  if ($this->element != 'invoice_supplier') {
580  if (empty($this->thirdparty)) {
581  $this->fetch_thirdparty(); // We need to have this->thirdparty defined, in case of numbering rule use tags that depend on thirdparty (like {t} tag).
582  }
583  $maxref = $this->getNextNumRef($this->thirdparty, 'last');
584 
585  // If there is no invoice into the reset range and not already dispatched, we can delete
586  // If invoice to delete is last one and not already dispatched, we can delete
587  if (!getDolGlobalString('INVOICE_CAN_ALWAYS_BE_REMOVED') && $maxref != '' && $maxref != $this->ref) {
588  return -2;
589  }
590 
591  // TODO If there is payment in bookkeeping, check payment is not dispatched in accounting
592  // ...
593 
594  if ($this->situation_cycle_ref && method_exists($this, 'is_last_in_cycle')) {
595  $last = $this->is_last_in_cycle();
596  if (!$last) {
597  return -3;
598  }
599  }
600  }
601  }
602 
603  // Test if there is at least one payment. If yes, refuse to delete.
604  if (!getDolGlobalString('INVOICE_CAN_ALWAYS_BE_REMOVED') && $this->getSommePaiement() > 0) {
605  return -4;
606  }
607 
608  return 2;
609  }
610 
616  public function getVentilExportCompta()
617  {
618  $alreadydispatched = 0;
619 
620  $type = 'customer_invoice';
621  if ($this->element == 'invoice_supplier') {
622  $type = 'supplier_invoice';
623  }
624 
625  $sql = " SELECT COUNT(ab.rowid) as nb FROM ".$this->db->prefix()."accounting_bookkeeping as ab WHERE ab.doc_type='".$this->db->escape($type)."' AND ab.fk_doc = ".((int) $this->id);
626  $resql = $this->db->query($sql);
627  if ($resql) {
628  $obj = $this->db->fetch_object($resql);
629  if ($obj) {
630  $alreadydispatched = $obj->nb;
631  }
632  } else {
633  $this->error = $this->db->lasterror();
634  return -1;
635  }
636 
637  if ($alreadydispatched) {
638  return 1;
639  }
640  return 0;
641  }
642 
650  public function getNextNumRef($soc, $mode = 'next')
651  {
652  // TODO Must be implemented into main class
653  return '';
654  }
655 
662  public function getLibType($withbadge = 0)
663  {
664  global $langs;
665 
666  $labellong = "Unknown";
667  if ($this->type == CommonInvoice::TYPE_STANDARD) {
668  $labellong = "InvoiceStandard";
669  $labelshort = "InvoiceStandardShort";
670  } elseif ($this->type == CommonInvoice::TYPE_REPLACEMENT) {
671  $labellong = "InvoiceReplacement";
672  $labelshort = "InvoiceReplacementShort";
673  } elseif ($this->type == CommonInvoice::TYPE_CREDIT_NOTE) {
674  $labellong = "InvoiceAvoir";
675  $labelshort = "CreditNote";
676  } elseif ($this->type == CommonInvoice::TYPE_DEPOSIT) {
677  $labellong = "InvoiceDeposit";
678  $labelshort = "Deposit";
679  } elseif ($this->type == CommonInvoice::TYPE_PROFORMA) {
680  $labellong = "InvoiceProForma"; // Not used.
681  $labelshort = "ProForma";
682  } elseif ($this->type == CommonInvoice::TYPE_SITUATION) {
683  $labellong = "InvoiceSituation";
684  $labelshort = "Situation";
685  }
686 
687  $out = '';
688  if ($withbadge) {
689  $out .= '<span class="badgeneutral" title="'.dol_escape_htmltag($langs->trans($labellong)).'">';
690  }
691  $out .= $langs->trans($withbadge == 2 ? $labelshort : $labellong);
692  if ($withbadge) {
693  $out .= '</span>';
694  }
695  return $out;
696  }
697 
704  public function getSubtypeLabel($table = '')
705  {
706  $subtypeLabel = '';
707  if ($table === 'facture' || $table === 'facture_fourn') {
708  $sql = "SELECT s.label FROM " . $this->db->prefix() . $table . " AS f";
709  $sql .= " INNER JOIN " . $this->db->prefix() . "c_invoice_subtype AS s ON f.subtype = s.rowid";
710  $sql .= " WHERE f.ref = '".$this->db->escape($this->ref)."'";
711  } elseif ($table === 'facture_rec' || $table === 'facture_fourn_rec') {
712  $sql = "SELECT s.label FROM " . $this->db->prefix() . $table . " AS f";
713  $sql .= " INNER JOIN " . $this->db->prefix() . "c_invoice_subtype AS s ON f.subtype = s.rowid";
714  $sql .= " WHERE f.titre = '".$this->db->escape($this->title)."'";
715  } else {
716  return -1;
717  }
718 
719  $resql = $this->db->query($sql);
720  if ($resql) {
721  while ($obj = $this->db->fetch_object($resql)) {
722  $subtypeLabel = $obj->label;
723  }
724  } else {
725  dol_print_error($this->db);
726  return -1;
727  }
728 
729  return $subtypeLabel;
730  }
731 
738  public function getArrayOfInvoiceSubtypes($mode = 0)
739  {
740  global $mysoc;
741 
742  $effs = array();
743 
744  $sql = "SELECT rowid, code, label as label";
745  $sql .= " FROM " . MAIN_DB_PREFIX . 'c_invoice_subtype';
746  $sql .= " WHERE active = 1 AND fk_country = ".((int) $mysoc->country_id)." AND entity IN(".getEntity('c_invoice_subtype').")";
747  $sql .= " ORDER by rowid, code";
748 
749  dol_syslog(get_class($this) . '::getArrayOfInvoiceSubtypes', LOG_DEBUG);
750  $resql = $this->db->query($sql);
751  if ($resql) {
752  $num = $this->db->num_rows($resql);
753  $i = 0;
754 
755  while ($i < $num) {
756  $objp = $this->db->fetch_object($resql);
757  if (!$mode) {
758  $key = $objp->rowid;
759  $effs[$key] = $objp->label;
760  } else {
761  $key = $objp->code;
762  $effs[$key] = $objp->rowid;
763  }
764 
765  $i++;
766  }
767  $this->db->free($resql);
768  }
769 
770  return $effs;
771  }
772 
780  public function getLibStatut($mode = 0, $alreadypaid = -1)
781  {
782  return $this->LibStatut($this->paye, $this->statut, $mode, $alreadypaid, $this->type);
783  }
784 
785  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
796  public function LibStatut($paye, $status, $mode = 0, $alreadypaid = -1, $type = -1)
797  {
798  // phpcs:enable
799  global $langs, $hookmanager;
800  $langs->load('bills');
801 
802  if ($type == -1) {
803  $type = $this->type;
804  }
805 
806  $statusType = 'status0';
807  $prefix = 'Short';
808  if (!$paye) {
809  if ($status == 0) {
810  $labelStatus = $langs->transnoentitiesnoconv('BillStatusDraft');
811  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusDraft');
812  } elseif (($status == 3 || $status == 2) && $alreadypaid <= 0) {
813  if ($status == 3) {
814  $labelStatus = $langs->transnoentitiesnoconv('BillStatusCanceled');
815  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusCanceled');
816  } else {
817  $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedUnpaid');
818  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedUnpaid');
819  }
820  $statusType = 'status5';
821  } elseif (($status == 3 || $status == 2) && $alreadypaid > 0) {
822  $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedPaidPartially');
823  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedPaidPartially');
824  $statusType = 'status9';
825  } elseif ($alreadypaid == 0) {
826  $labelStatus = $langs->transnoentitiesnoconv('BillStatusNotPaid');
827  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusNotPaid');
828  $statusType = 'status1';
829  } else {
830  $labelStatus = $langs->transnoentitiesnoconv('BillStatusStarted');
831  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusStarted');
832  $statusType = 'status3';
833  }
834  } else {
835  $statusType = 'status6';
836 
837  if ($type == self::TYPE_CREDIT_NOTE) {
838  $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaidBackOrConverted'); // credit note
839  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaidBackOrConverted'); // credit note
840  } elseif ($type == self::TYPE_DEPOSIT) {
841  $labelStatus = $langs->transnoentitiesnoconv('BillStatusConverted'); // deposit invoice
842  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusConverted'); // deposit invoice
843  } else {
844  $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaid');
845  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaid');
846  }
847  }
848 
849  $parameters = array(
850  'status' => $status,
851  'mode' => $mode,
852  'paye' => $paye,
853  'alreadypaid' => $alreadypaid,
854  'type' => $type
855  );
856 
857  $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
858 
859  if ($reshook > 0) {
860  return $hookmanager->resPrint;
861  }
862 
863 
864 
865  return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
866  }
867 
868  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
876  public function calculate_date_lim_reglement($cond_reglement = 0)
877  {
878  // phpcs:enable
879  if (!$cond_reglement) {
880  $cond_reglement = $this->cond_reglement_code;
881  }
882  if (!$cond_reglement) {
883  $cond_reglement = $this->cond_reglement_id;
884  }
885  if (!$cond_reglement) {
886  return $this->date;
887  }
888 
889  $cdr_nbjour = 0;
890  $cdr_type = 0;
891  $cdr_decalage = 0;
892 
893  $sqltemp = "SELECT c.type_cdr, c.nbjour, c.decalage";
894  $sqltemp .= " FROM ".$this->db->prefix()."c_payment_term as c";
895  if (is_numeric($cond_reglement)) {
896  $sqltemp .= " WHERE c.rowid=".((int) $cond_reglement);
897  } else {
898  $sqltemp .= " WHERE c.entity IN (".getEntity('c_payment_term').")";
899  $sqltemp .= " AND c.code = '".$this->db->escape($cond_reglement)."'";
900  }
901 
902  dol_syslog(get_class($this).'::calculate_date_lim_reglement', LOG_DEBUG);
903  $resqltemp = $this->db->query($sqltemp);
904  if ($resqltemp) {
905  if ($this->db->num_rows($resqltemp)) {
906  $obj = $this->db->fetch_object($resqltemp);
907  $cdr_nbjour = $obj->nbjour;
908  $cdr_type = $obj->type_cdr;
909  $cdr_decalage = $obj->decalage;
910  }
911  } else {
912  $this->error = $this->db->error();
913  return -1;
914  }
915  $this->db->free($resqltemp);
916 
917  /* Definition de la date limit */
918 
919  // 0 : adding the number of days
920  if ($cdr_type == 0) {
921  $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
922 
923  $datelim += ($cdr_decalage * 3600 * 24);
924  } elseif ($cdr_type == 1) {
925  // 1 : application of the "end of the month" rule
926  $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
927 
928  $mois = date('m', $datelim);
929  $annee = date('Y', $datelim);
930  if ($mois == 12) {
931  $mois = 1;
932  $annee += 1;
933  } else {
934  $mois += 1;
935  }
936  // We move at the beginning of the next month, and we take a day off
937  $datelim = dol_mktime(12, 0, 0, $mois, 1, $annee);
938  $datelim -= (3600 * 24);
939 
940  $datelim += ($cdr_decalage * 3600 * 24);
941  } elseif ($cdr_type == 2 && !empty($cdr_decalage)) {
942  // 2 : application of the rule, the N of the current or next month
943  include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
944  $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
945 
946  $date_piece = dol_mktime(0, 0, 0, date('m', $datelim), date('d', $datelim), date('Y', $datelim)); // Sans les heures minutes et secondes
947  $date_lim_current = dol_mktime(0, 0, 0, date('m', $datelim), $cdr_decalage, date('Y', $datelim)); // Sans les heures minutes et secondes
948  $date_lim_next = dol_time_plus_duree($date_lim_current, 1, 'm'); // Add 1 month
949 
950  $diff = $date_piece - $date_lim_current;
951 
952  if ($diff < 0) {
953  $datelim = $date_lim_current;
954  } else {
955  $datelim = $date_lim_next;
956  }
957  } else {
958  return 'Bad value for type_cdr in database for record cond_reglement = '.$cond_reglement;
959  }
960 
961  return $datelim;
962  }
963 
964  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
976  public function demande_prelevement($fuser, $amount = 0, $type = 'direct-debit', $sourcetype = 'facture', $checkduplicateamongall = 0)
977  {
978  // phpcs:enable
979  global $conf;
980 
981  $error = 0;
982 
983  dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
984 
985  if ($this->status > self::STATUS_DRAFT && $this->paye == 0) {
986  require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
987  $bac = new CompanyBankAccount($this->db);
988  $bac->fetch(0, '', $this->socid);
989 
990  $sql = "SELECT count(rowid) as nb";
991  $sql .= " FROM ".$this->db->prefix()."prelevement_demande";
992  if ($type == 'bank-transfer') {
993  $sql .= " WHERE fk_facture_fourn = ".((int) $this->id);
994  } else {
995  $sql .= " WHERE fk_facture = ".((int) $this->id);
996  }
997  $sql .= " AND type = 'ban'"; // To exclude record done for some online payments
998  if (empty($checkduplicateamongall)) {
999  $sql .= " AND traite = 0";
1000  }
1001 
1002  dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
1003 
1004  $resql = $this->db->query($sql);
1005  if ($resql) {
1006  $obj = $this->db->fetch_object($resql);
1007  if ($obj && $obj->nb == 0) { // If no request found yet
1008  $now = dol_now();
1009 
1010  $totalpaid = $this->getSommePaiement();
1011  $totalcreditnotes = $this->getSumCreditNotesUsed();
1012  $totaldeposits = $this->getSumDepositsUsed();
1013  //print "totalpaid=".$totalpaid." totalcreditnotes=".$totalcreditnotes." totaldeposts=".$totaldeposits;
1014 
1015  // We can also use bcadd to avoid pb with floating points
1016  // For example print 239.2 - 229.3 - 9.9; does not return 0.
1017  //$resteapayer=bcadd($this->total_ttc,$totalpaid,$conf->global->MAIN_MAX_DECIMALS_TOT);
1018  //$resteapayer=bcadd($resteapayer,$totalavoir,$conf->global->MAIN_MAX_DECIMALS_TOT);
1019  if (empty($amount)) {
1020  $amount = price2num($this->total_ttc - $totalpaid - $totalcreditnotes - $totaldeposits, 'MT');
1021  }
1022 
1023  if (is_numeric($amount) && $amount != 0) {
1024  $sql = 'INSERT INTO '.$this->db->prefix().'prelevement_demande(';
1025  if ($type == 'bank-transfer') {
1026  $sql .= 'fk_facture_fourn, ';
1027  } else {
1028  $sql .= 'fk_facture, ';
1029  }
1030  $sql .= ' amount, date_demande, fk_user_demande, code_banque, code_guichet, number, cle_rib, sourcetype, type, entity)';
1031  $sql .= " VALUES (".((int) $this->id);
1032  $sql .= ", ".((float) price2num($amount));
1033  $sql .= ", '".$this->db->idate($now)."'";
1034  $sql .= ", ".((int) $fuser->id);
1035  $sql .= ", '".$this->db->escape($bac->code_banque)."'";
1036  $sql .= ", '".$this->db->escape($bac->code_guichet)."'";
1037  $sql .= ", '".$this->db->escape($bac->number)."'";
1038  $sql .= ", '".$this->db->escape($bac->cle_rib)."'";
1039  $sql .= ", '".$this->db->escape($sourcetype)."'";
1040  $sql .= ", 'ban'";
1041  $sql .= ", ".((int) $conf->entity);
1042  $sql .= ")";
1043 
1044  dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
1045  $resql = $this->db->query($sql);
1046  if (!$resql) {
1047  $this->error = $this->db->lasterror();
1048  dol_syslog(get_class($this).'::demandeprelevement Erreur');
1049  $error++;
1050  }
1051  } else {
1052  $this->error = 'WithdrawRequestErrorNilAmount';
1053  dol_syslog(get_class($this).'::demandeprelevement WithdrawRequestErrorNilAmount');
1054  $error++;
1055  }
1056 
1057  if (!$error) {
1058  // Force payment mode of invoice to withdraw
1059  $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
1060  if ($payment_mode_id > 0) {
1061  $result = $this->setPaymentMethods($payment_mode_id);
1062  }
1063  }
1064 
1065  if ($error) {
1066  return -1;
1067  }
1068  return 1;
1069  } else {
1070  $this->error = "A request already exists";
1071  dol_syslog(get_class($this).'::demandeprelevement Can t create a request to generate a direct debit, a request already exists.');
1072  return 0;
1073  }
1074  } else {
1075  $this->error = $this->db->error();
1076  dol_syslog(get_class($this).'::demandeprelevement Error -2');
1077  return -2;
1078  }
1079  } else {
1080  $this->error = "Status of invoice does not allow this";
1081  dol_syslog(get_class($this)."::demandeprelevement ".$this->error." $this->status, $this->paye, $this->mode_reglement_id");
1082  return -3;
1083  }
1084  }
1085 
1086 
1098  public function makeStripeCardRequest($fuser, $id, $sourcetype = 'facture')
1099  {
1100  // TODO See in sellyoursaas
1101  return 0;
1102  }
1103 
1116  public function makeStripeSepaRequest($fuser, $did, $type = 'direct-debit', $sourcetype = 'facture', $service = '', $forcestripe = '')
1117  {
1118  global $conf, $user, $langs;
1119 
1120  if ($type != 'bank-transfer' && $type != 'credit-transfer' && !getDolGlobalString('STRIPE_SEPA_DIRECT_DEBIT')) {
1121  return 0;
1122  }
1123  if ($type != 'direct-debit' && !getDolGlobalString('STRIPE_SEPA_CREDIT_TRANSFER')) {
1124  return 0;
1125  }
1126  // Set a default value for service if not provided
1127  if (empty($service)) {
1128  $service = 'StripeTest';
1129  if (getDolGlobalString('STRIPE_LIVE') && !GETPOST('forcesandbox', 'alpha')) {
1130  $service = 'StripeLive';
1131  }
1132  }
1133 
1134  $error = 0;
1135 
1136  dol_syslog(get_class($this)."::makeStripeSepaRequest start did=".$did." type=".$type." service=".$service." sourcetype=".$sourcetype." forcestripe=".$forcestripe, LOG_DEBUG);
1137 
1138  if ($this->status > self::STATUS_DRAFT && $this->paye == 0) {
1139  // Get the default payment mode for BAN payment of the third party
1140  require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
1141  $bac = new CompanyBankAccount($this->db); // Table societe_rib
1142  $result = $bac->fetch(0, '', $this->socid, 1, 'ban');
1143  if ($result <= 0 || empty($bac->id)) {
1144  $this->error = $langs->trans("ThirdpartyHasNoDefaultBanAccount");
1145  $this->errors[] = $this->error;
1146  dol_syslog(get_class($this)."::makeStripeSepaRequest ".$this->error);
1147  return -1;
1148  }
1149 
1150  // Load the pending payment request to process (with rowid=$did)
1151  $sql = "SELECT rowid, date_demande, amount, fk_facture, fk_facture_fourn, fk_salary, fk_prelevement_bons";
1152  $sql .= " FROM ".$this->db->prefix()."prelevement_demande";
1153  $sql .= " WHERE rowid = ".((int) $did);
1154  if ($type != 'bank-transfer' && $type != 'credit-transfer') {
1155  $sql .= " AND fk_facture = ".((int) $this->id); // Add a protection to not pay another invoice than current one
1156  }
1157  if ($type != 'direct-debit') {
1158  if ($sourcetype == 'salary') {
1159  $sql .= " AND fk_salary = ".((int) $this->id); // Add a protection to not pay another salary than current one
1160  } else {
1161  $sql .= " AND fk_facture_fourn = ".((int) $this->id); // Add a protection to not pay another invoice than current one
1162  }
1163  }
1164  $sql .= " AND traite = 0"; // To not process payment request that were already converted into a direct debit or credit transfer order (Note: fk_prelevement_bons is also empty when traite = 0)
1165 
1166  dol_syslog(get_class($this)."::makeStripeSepaRequest load requests to process", LOG_DEBUG);
1167  $resql = $this->db->query($sql);
1168  if ($resql) {
1169  $obj = $this->db->fetch_object($resql);
1170  if (!$obj) {
1171  dol_print_error($this->db, 'CantFindRequestWithId');
1172  return -2;
1173  }
1174 
1175  // amount to pay
1176  $amount = $obj->amount;
1177 
1178  if (is_numeric($amount) && $amount != 0) {
1179  require_once DOL_DOCUMENT_ROOT.'/societe/class/companypaymentmode.class.php';
1180  $companypaymentmode = new CompanyPaymentMode($this->db); // table societe_rib
1181  $companypaymentmode->fetch($bac->id);
1182 
1183  $this->stripechargedone = 0;
1184  $this->stripechargeerror = 0;
1185 
1186  $now = dol_now();
1187 
1188  $currency = $conf->currency;
1189 
1190  $errorforinvoice = 0; // We reset the $errorforinvoice at each invoice loop
1191 
1192  $this->fetch_thirdparty();
1193 
1194  dol_syslog("makeStripeSepaRequest Process payment request amount=".$amount." thirdparty_id=" . $this->thirdparty->id . ", thirdparty_name=" . $this->thirdparty->name . " ban id=" . $bac->id, LOG_DEBUG);
1195 
1196  //$alreadypayed = $this->getSommePaiement();
1197  //$amount_credit_notes_included = $this->getSumCreditNotesUsed();
1198  //$amounttopay = $this->total_ttc - $alreadypayed - $amount_credit_notes_included;
1199  $amounttopay = $amount;
1200 
1201  // Correct the amount according to unit of currency
1202  // See https://support.stripe.com/questions/which-zero-decimal-currencies-does-stripe-support
1203  $arrayzerounitcurrency = ['BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'];
1204  $amountstripe = $amounttopay;
1205  if (!in_array($currency, $arrayzerounitcurrency)) {
1206  $amountstripe = $amountstripe * 100;
1207  }
1208 
1209  $fk_bank_account = getDolGlobalInt('STRIPE_BANK_ACCOUNT_FOR_PAYMENTS'); // Bank account used for SEPA direct debit or credit transfer. Must be the Stripe account in Dolibarr.
1210  if (!($fk_bank_account > 0)) {
1211  $error++;
1212  $errorforinvoice++;
1213  dol_syslog("makeStripeSepaRequest Error no bank account defined for Stripe payments", LOG_ERR);
1214  $this->errors[] = "Error bank account for Stripe payments not defined into Stripe module";
1215  }
1216 
1217  $this->db->begin();
1218 
1219  // Create a prelevement_bon
1220  require_once DOL_DOCUMENT_ROOT.'/compta/prelevement/class/bonprelevement.class.php';
1221  $bon = new BonPrelevement($this->db);
1222  if (!$error) {
1223  if (empty($obj->fk_prelevement_bons)) {
1224  // This creates a record into llx_prelevement_bons and updates link with llx_prelevement_demande
1225  $nbinvoices = $bon->create(0, 0, 'real', 'ALL', '', 0, $type, $did, $fk_bank_account);
1226  if ($nbinvoices <= 0) {
1227  $error++;
1228  $errorforinvoice++;
1229  dol_syslog("makeStripeSepaRequest Error on BonPrelevement creation", LOG_ERR);
1230  $this->errors[] = "Error on BonPrelevement creation";
1231  }
1232  /*
1233  if (!$error) {
1234  // Update the direct debit payment request of the processed request to save the id of the prelevement_bon
1235  $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
1236  $sql .= " fk_prelevement_bons = ".((int) $bon->id);
1237  $sql .= " WHERE rowid = ".((int) $did);
1238 
1239  $result = $this->db->query($sql);
1240  if ($result < 0) {
1241  $error++;
1242  $this->errors[] = "Error on updating fk_prelevement_bons to ".$bon->id;
1243  }
1244  }
1245  */
1246  } else {
1247  $error++;
1248  $errorforinvoice++;
1249  dol_syslog("makeStripeSepaRequest Error Line already part of a bank payment order", LOG_ERR);
1250  $this->errors[] = "The line is already included into a bank payment order. Delete the bank payment order first.";
1251  }
1252  }
1253 
1254  if (!$error) {
1255  if ($amountstripe > 0) {
1256  try {
1257  global $savstripearrayofkeysbyenv;
1258  global $stripearrayofkeysbyenv;
1259  $servicestatus = 0;
1260  if ($service == 'StripeLive') {
1261  $servicestatus = 1;
1262  }
1263 
1264  //var_dump($companypaymentmode);
1265  dol_syslog("makeStripeSepaRequest We will try to pay with companypaymentmodeid=" . $companypaymentmode->id . " stripe_card_ref=" . $companypaymentmode->stripe_card_ref . " mode=" . $companypaymentmode->status, LOG_DEBUG);
1266 
1267  $thirdparty = new Societe($this->db);
1268  $resultthirdparty = $thirdparty->fetch($this->socid);
1269 
1270  include_once DOL_DOCUMENT_ROOT . '/stripe/class/stripe.class.php'; // This include the include of htdocs/stripe/config.php
1271  // So it inits or erases the $stripearrayofkeysbyenv
1272  $stripe = new Stripe($this->db);
1273 
1274  if (empty($savstripearrayofkeysbyenv)) {
1275  $savstripearrayofkeysbyenv = $stripearrayofkeysbyenv;
1276  }
1277  dol_syslog("makeStripeSepaRequest Current Stripe environment is " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key']);
1278  dol_syslog("makeStripeSepaRequest Current Saved Stripe environment is ".$savstripearrayofkeysbyenv[$servicestatus]['publishable_key']);
1279 
1280  $foundalternativestripeaccount = '';
1281 
1282  // Force stripe to another value (by default this value is empty)
1283  if (! empty($forcestripe)) {
1284  dol_syslog("makeStripeSepaRequest A dedicated stripe account was forced, so we switch to it.");
1285 
1286  $tmparray = explode('@', $forcestripe);
1287  if (! empty($tmparray[1])) {
1288  $tmparray2 = explode(':', $tmparray[1]);
1289  if (! empty($tmparray2[1])) {
1290  $stripearrayofkeysbyenv[$servicestatus]["publishable_key"] = $tmparray2[0];
1291  $stripearrayofkeysbyenv[$servicestatus]["secret_key"] = $tmparray2[1];
1292 
1293  $stripearrayofkeys = $stripearrayofkeysbyenv[$servicestatus];
1294  \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1295 
1296  $foundalternativestripeaccount = $tmparray[0]; // Store the customer id
1297 
1298  dol_syslog("makeStripeSepaRequest We use now customer=".$foundalternativestripeaccount." publishable_key=".$stripearrayofkeys['publishable_key'], LOG_DEBUG);
1299  }
1300  }
1301 
1302  if (! $foundalternativestripeaccount) {
1303  $stripearrayofkeysbyenv = $savstripearrayofkeysbyenv;
1304 
1305  $stripearrayofkeys = $savstripearrayofkeysbyenv[$servicestatus];
1306  \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1307  dol_syslog("makeStripeSepaRequest We found a bad value for Stripe Account for thirdparty id=".$thirdparty->id.", so we ignore it and keep using the global one, so ".$stripearrayofkeys['publishable_key'], LOG_WARNING);
1308  }
1309  } else {
1310  $stripearrayofkeysbyenv = $savstripearrayofkeysbyenv;
1311 
1312  $stripearrayofkeys = $savstripearrayofkeysbyenv[$servicestatus];
1313  \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1314  dol_syslog("makeStripeSepaRequest No dedicated Stripe Account requested, so we use global one, so ".$stripearrayofkeys['publishable_key'], LOG_DEBUG);
1315  }
1316 
1317  $stripeacc = $stripe->getStripeAccount($service, $this->socid); // Get Stripe OAuth connect account if it exists (no network access here)
1318 
1319  if ($foundalternativestripeaccount) {
1320  if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
1321  $customer = \Stripe\Customer::retrieve(array('id' => "$foundalternativestripeaccount", 'expand[]' => 'sources'));
1322  } else {
1323  $customer = \Stripe\Customer::retrieve(array('id' => "$foundalternativestripeaccount", 'expand[]' => 'sources'), array("stripe_account" => $stripeacc));
1324  }
1325  } else {
1326  $customer = $stripe->customerStripe($thirdparty, $stripeacc, $servicestatus, 0);
1327  if (empty($customer) && ! empty($stripe->error)) {
1328  $this->errors[] = $stripe->error;
1329  }
1330  /*if (!empty($customer) && empty($customer->sources)) {
1331  $customer = null;
1332  $this->errors[] = '\Stripe\Customer::retrieve did not returned the sources';
1333  }*/
1334  }
1335 
1336  // $nbhoursbetweentries = (empty($conf->global->SELLYOURSAAS_NBHOURSBETWEENTRIES) ? 49 : $conf->global->SELLYOURSAAS_NBHOURSBETWEENTRIES); // Must have more that 48 hours + 1 between each try (so 1 try every 3 daily batch)
1337  // $nbdaysbeforeendoftries = (empty($conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES) ? 35 : $conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES);
1338  $postactionmessages = [];
1339 
1340  if ($resultthirdparty > 0 && !empty($customer)) {
1341  if (!$error) { // Payment was not canceled
1342  $stripecard = null;
1343  if ($companypaymentmode->type == 'ban') {
1344  // Check into societe_rib if a payment mode for Stripe and ban payment exists
1345  // To make a Stripe SEPA payment request, we must have the payment mode source already saved into societe_rib and retrieved with ->sepaStripe
1346  // The payment mode source is created when we create the bank account on Stripe with paymentmodes.php?action=create
1347  $stripecard = $stripe->sepaStripe($customer, $companypaymentmode, $stripeacc, $servicestatus, 0);
1348  } else {
1349  $error++;
1350  $this->error = 'The payment mode type is not "ban"';
1351  }
1352 
1353  if ($stripecard) { // Can be src_... (for sepa) or pm_... (new card mode). Note that card_... (old card mode) should not happen here.
1354  $FULLTAG = 'DID='.$did.'-INV=' . $this->id . '-CUS=' . $thirdparty->id;
1355  $description = 'Stripe payment from makeStripeSepaRequest: ' . $FULLTAG . ' did='.$did.' ref=' . $this->ref;
1356 
1357  $stripefailurecode = '';
1358  $stripefailuremessage = '';
1359  $stripefailuredeclinecode = '';
1360 
1361  // Using new SCA method
1362  dol_syslog("* Create payment on SEPA " . $stripecard->id . ", amounttopay=" . $amounttopay . ", amountstripe=" . $amountstripe . ", FULLTAG=" . $FULLTAG, LOG_DEBUG);
1363 
1364  // Create payment intent and charge payment (confirmnow = true)
1365  $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $this, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1, 1, $did);
1366 
1367  $charge = new stdClass();
1368 
1369  if ($paymentintent->status === 'succeeded' || $paymentintent->status === 'processing') {
1370  $charge->status = 'ok';
1371  $charge->id = $paymentintent->id;
1372  $charge->customer = $customer->id;
1373  } elseif ($paymentintent->status === 'requires_action') {
1374  //paymentintent->status may be => 'requires_action' (no error in such a case)
1375  dol_syslog(var_export($paymentintent, true), LOG_DEBUG);
1376 
1377  $charge->status = 'failed';
1378  $charge->customer = $customer->id;
1379  $charge->failure_code = $stripe->code;
1380  $charge->failure_message = $stripe->error;
1381  $charge->failure_declinecode = $stripe->declinecode;
1382  $stripefailurecode = $stripe->code;
1383  $stripefailuremessage = 'Action required. Contact the support at ';// . $conf->global->SELLYOURSAAS_MAIN_EMAIL;
1384  $stripefailuredeclinecode = $stripe->declinecode;
1385  } else {
1386  dol_syslog(var_export($paymentintent, true), LOG_DEBUG);
1387 
1388  $charge->status = 'failed';
1389  $charge->customer = $customer->id;
1390  $charge->failure_code = $stripe->code;
1391  $charge->failure_message = $stripe->error;
1392  $charge->failure_declinecode = $stripe->declinecode;
1393  $stripefailurecode = $stripe->code;
1394  $stripefailuremessage = $stripe->error;
1395  $stripefailuredeclinecode = $stripe->declinecode;
1396  }
1397 
1398  //var_dump("stripefailurecode=".$stripefailurecode." stripefailuremessage=".$stripefailuremessage." stripefailuredeclinecode=".$stripefailuredeclinecode);
1399  //exit;
1400 
1401 
1402  // Return $charge = array('id'=>'ch_XXXX', 'status'=>'succeeded|pending|failed', 'failure_code'=>, 'failure_message'=>...)
1403  if (empty($charge) || $charge->status == 'failed') {
1404  dol_syslog('Failed to charge payment mode ' . $stripecard->id . ' stripefailurecode=' . $stripefailurecode . ' stripefailuremessage=' . $stripefailuremessage . ' stripefailuredeclinecode=' . $stripefailuredeclinecode, LOG_WARNING);
1405 
1406  // Save a stripe payment was in error
1407  $this->stripechargeerror++;
1408 
1409  $error++;
1410  $errorforinvoice++;
1411  $errmsg = $langs->trans("FailedToChargeCard");
1412  if (!empty($charge)) {
1413  if ($stripefailuredeclinecode == 'authentication_required') {
1414  $errauthenticationmessage = $langs->trans("ErrSCAAuthentication");
1415  $errmsg = $errauthenticationmessage;
1416  } elseif (in_array($stripefailuredeclinecode, ['insufficient_funds', 'generic_decline'])) {
1417  $errmsg .= ': ' . $charge->failure_code;
1418  $errmsg .= ($charge->failure_message ? ' - ' : '') . ' ' . $charge->failure_message;
1419  if (empty($stripefailurecode)) {
1420  $stripefailurecode = $charge->failure_code;
1421  }
1422  if (empty($stripefailuremessage)) {
1423  $stripefailuremessage = $charge->failure_message;
1424  }
1425  } else {
1426  $errmsg .= ': failure_code=' . $charge->failure_code;
1427  $errmsg .= ($charge->failure_message ? ' - ' : '') . ' failure_message=' . $charge->failure_message;
1428  if (empty($stripefailurecode)) {
1429  $stripefailurecode = $charge->failure_code;
1430  }
1431  if (empty($stripefailuremessage)) {
1432  $stripefailuremessage = $charge->failure_message;
1433  }
1434  }
1435  } else {
1436  $errmsg .= ': ' . $stripefailurecode . ' - ' . $stripefailuremessage;
1437  $errmsg .= ($stripefailuredeclinecode ? ' - ' . $stripefailuredeclinecode : '');
1438  }
1439 
1440  $description = 'Stripe payment ERROR from makeStripeSepaRequest: ' . $FULLTAG;
1441  $postactionmessages[] = $errmsg . ' (' . $stripearrayofkeys['publishable_key'] . ')';
1442  $this->errors[] = $errmsg;
1443  } else {
1444  dol_syslog('Successfuly request '.$type.' '.$stripecard->id);
1445 
1446  $postactionmessages[] = 'Success to request '.$type.' (' . $charge->id . ' with ' . $stripearrayofkeys['publishable_key'] . ')';
1447 
1448  // Save a stripe payment was done in real life so later we will be able to force a commit on recorded payments
1449  // even if in batch mode (method doTakePaymentStripe), we will always make all action in one transaction with a forced commit.
1450  $this->stripechargedone++;
1451 
1452  // Default description used for label of event. Will be overwrite by another value later.
1453  $description = 'Stripe payment request OK (' . $charge->id . ') from makeStripeSepaRequest: ' . $FULLTAG;
1454  }
1455 
1456  $object = $this;
1457 
1458  // Track an event
1459  if (empty($charge) || $charge->status == 'failed') {
1460  $actioncode = 'PAYMENT_STRIPE_KO';
1461  $extraparams = $stripefailurecode;
1462  $extraparams .= (($extraparams && $stripefailuremessage) ? ' - ' : '') . $stripefailuremessage;
1463  $extraparams .= (($extraparams && $stripefailuredeclinecode) ? ' - ' : '') . $stripefailuredeclinecode;
1464  } else {
1465  $actioncode = 'PAYMENT_STRIPE_OK';
1466  $extraparams = '';
1467  }
1468  } else {
1469  $error++;
1470  $errorforinvoice++;
1471  dol_syslog("No ban payment method found for this stripe customer " . $customer->id, LOG_WARNING);
1472  $this->errors[] = 'Failed to get direct debit payment method for stripe customer = ' . $customer->id;
1473 
1474  $description = 'Failed to find or use the payment mode - no ban defined for the thirdparty account';
1475  $stripefailurecode = 'BADPAYMENTMODE';
1476  $stripefailuremessage = 'Failed to find or use the payment mode - no ban defined for the thirdparty account';
1477  $postactionmessages[] = $description . ' (' . $stripearrayofkeys['publishable_key'] . ')';
1478 
1479  $object = $this;
1480 
1481  $actioncode = 'PAYMENT_STRIPE_KO';
1482  $extraparams = '';
1483  }
1484  } else {
1485  // If error because payment was canceled for a logical reason, we do nothing (no event added)
1486  $description = '';
1487  $stripefailurecode = '';
1488  $stripefailuremessage = '';
1489 
1490  $object = $this;
1491 
1492  $actioncode = '';
1493  $extraparams = '';
1494  }
1495  } else { // Else of the if ($resultthirdparty > 0 && ! empty($customer)) {
1496  if ($resultthirdparty <= 0) {
1497  dol_syslog('SellYourSaasUtils Failed to load customer for thirdparty_id = ' . $thirdparty->id, LOG_WARNING);
1498  $this->errors[] = 'Failed to load Stripe account for thirdparty_id = ' . $thirdparty->id;
1499  } else { // $customer stripe not found
1500  dol_syslog('SellYourSaasUtils Failed to get Stripe customer id for thirdparty_id = ' . $thirdparty->id . " in mode " . $servicestatus . " in Stripe env " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key'], LOG_WARNING);
1501  $this->errors[] = 'Failed to get Stripe account id for thirdparty_id = ' . $thirdparty->id . " in mode " . $servicestatus . " in Stripe env " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key'];
1502  }
1503  $error++;
1504  $errorforinvoice++;
1505 
1506  $description = 'Failed to find or use your payment mode (no payment mode for this customer id)';
1507  $stripefailurecode = 'BADPAYMENTMODE';
1508  $stripefailuremessage = 'Failed to find or use your payment mode (no payment mode for this customer id)';
1509  $postactionmessages = []; // @phan-suppress-current-line PhanPluginRedundantAssignment
1510 
1511  $object = $this;
1512 
1513  $actioncode = 'PAYMENT_STRIPE_KO';
1514  $extraparams = '';
1515  }
1516 
1517  if ($description) {
1518  dol_syslog("* Record event for credit transfer or direct debit request result - " . $description);
1519  require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
1520 
1521  // Insert record of payment (success or error)
1522  $actioncomm = new ActionComm($this->db);
1523 
1524  $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
1525  $actioncomm->code = 'AC_' . $actioncode;
1526  $actioncomm->label = $description;
1527  $actioncomm->note_private = implode(",\n", $postactionmessages);
1528  $actioncomm->fk_project = $this->fk_project;
1529  $actioncomm->datep = $now;
1530  $actioncomm->datef = $now;
1531  $actioncomm->percentage = -1; // Not applicable
1532  $actioncomm->socid = $thirdparty->id;
1533  $actioncomm->contactid = 0;
1534  $actioncomm->authorid = $user->id; // User saving action
1535  $actioncomm->userownerid = $user->id; // Owner of action
1536  // Fields when action is a real email (content is already into note)
1537  /*$actioncomm->email_msgid = $object->email_msgid;
1538  $actioncomm->email_from = $object->email_from;
1539  $actioncomm->email_sender= $object->email_sender;
1540  $actioncomm->email_to = $object->email_to;
1541  $actioncomm->email_tocc = $object->email_tocc;
1542  $actioncomm->email_tobcc = $object->email_tobcc;
1543  $actioncomm->email_subject = $object->email_subject;
1544  $actioncomm->errors_to = $object->errors_to;*/
1545  $actioncomm->fk_element = $this->id;
1546  $actioncomm->elementtype = $this->element;
1547  $actioncomm->extraparams = dol_trunc($extraparams, 250);
1548 
1549  $actioncomm->create($user);
1550  }
1551 
1552  $this->description = $description;
1553  $this->postactionmessages = $postactionmessages;
1554  } catch (Exception $e) {
1555  $error++;
1556  $errorforinvoice++;
1557  dol_syslog('Error ' . $e->getMessage(), LOG_ERR);
1558  $this->errors[] = 'Error ' . $e->getMessage();
1559  }
1560  } else { // If remain to pay is null
1561  $error++;
1562  $errorforinvoice++;
1563  dol_syslog("Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?", LOG_WARNING);
1564  $this->errors[] = "Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?";
1565  }
1566  }
1567 
1568  // Set status of the order to "Transferred" with method 'api'
1569  if (!$error && !$errorforinvoice) {
1570  $result = $bon->set_infotrans($user, $now, 3);
1571  if ($result < 0) {
1572  $error++;
1573  $errorforinvoice++;
1574  dol_syslog("Error on BonPrelevement creation", LOG_ERR);
1575  $this->errors[] = "Error on BonPrelevement creation";
1576  }
1577  }
1578 
1579  if (!$error && !$errorforinvoice) {
1580  // Update the direct debit payment request of the processed invoice to save the id of the prelevement_bon
1581  $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
1582  $sql .= " ext_payment_id = '".$this->db->escape($paymentintent->id)."',";
1583  $sql .= " ext_payment_site = '".$this->db->escape($service)."'";
1584  $sql .= " WHERE rowid = ".((int) $did);
1585 
1586  dol_syslog(get_class($this)."::makeStripeSepaRequest update to save stripe paymentintent ids", LOG_DEBUG);
1587  $resql = $this->db->query($sql);
1588  if (!$resql) {
1589  $this->error = $this->db->lasterror();
1590  dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur');
1591  $error++;
1592  }
1593  }
1594 
1595  if (!$error && !$errorforinvoice) {
1596  $this->db->commit();
1597  } else {
1598  $this->db->rollback();
1599  }
1600  } else {
1601  $this->error = 'WithdrawRequestErrorNilAmount';
1602  dol_syslog(get_class($this).'::makeStripeSepaRequest WithdrawRequestErrorNilAmount');
1603  $error++;
1604  }
1605 
1606  /*
1607  if (!$error) {
1608  // Force payment mode of the invoice to withdraw
1609  $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
1610  if ($payment_mode_id > 0) {
1611  $result = $this->setPaymentMethods($payment_mode_id);
1612  }
1613  }*/
1614 
1615  if ($error) {
1616  return -1;
1617  }
1618  return 1;
1619  } else {
1620  $this->error = $this->db->error();
1621  dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur -2');
1622  return -2;
1623  }
1624  } else {
1625  $this->error = "Status of invoice does not allow this";
1626  dol_syslog(get_class($this)."::makeStripeSepaRequest ".$this->error." ".$this->status." ,".$this->paye.", ".$this->mode_reglement_id, LOG_WARNING);
1627  return -3;
1628  }
1629  }
1630 
1631  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1639  public function demande_prelevement_delete($fuser, $did)
1640  {
1641  // phpcs:enable
1642  $sql = 'DELETE FROM '.$this->db->prefix().'prelevement_demande';
1643  $sql .= ' WHERE rowid = '.((int) $did);
1644  $sql .= ' AND traite = 0';
1645  if ($this->db->query($sql)) {
1646  return 0;
1647  } else {
1648  $this->error = $this->db->lasterror();
1649  dol_syslog(get_class($this).'::demande_prelevement_delete Error '.$this->error);
1650  return -1;
1651  }
1652  }
1653 
1659  public function buildEPCQrCodeString()
1660  {
1661  global $mysoc;
1662 
1663  // Convert total_ttc to a string with 2 decimal places
1664  $totalTTCString = number_format($this->total_ttc, 2, '.', '');
1665 
1666  // Initialize an array to hold the lines of the QR code
1667  $lines = array();
1668 
1669  // Add the standard elements to the QR code
1670  $lines = [
1671  'BCD', // Service Tag (optional)
1672  '002', // Version (optional)
1673  '1', // Character set (optional)
1674  'SCT', // Identification (optional)
1675  ];
1676 
1677  // Add the bank account information
1678  include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
1679  $bankAccount = new Account($this->db);
1680  if ($this->fk_account > 0) {
1681  $bankAccount->fetch($this->fk_account);
1682  $lines[] = $bankAccount->bic; //BIC (required)
1683  $lines[] = $mysoc->name; //Name (required)
1684  $lines[] = $bankAccount->iban; //IBAN (required)
1685  } else {
1686  $lines[] = ""; //BIC (required)
1687  $lines[] = $mysoc->name; //Name (required)
1688  $lines[] = ""; //IBAN (required)
1689  }
1690 
1691  // Add the amount and reference
1692  $lines[] = 'EUR' . $totalTTCString; // Amount (optional)
1693  $lines[] = ''; // Payment reference (optional)
1694  $lines[] = $this->ref; // Remittance Information (optional)
1695 
1696  // Join the lines with newline characters and return the result
1697  return implode("\n", $lines);
1698  }
1704  public function buildZATCAQRString()
1705  {
1706  global $conf, $mysoc;
1707 
1708  $tmplang = new Translate('', $conf);
1709  $tmplang->setDefaultLang('en_US');
1710  $tmplang->load("main");
1711 
1712  $datestring = dol_print_date($this->date, 'dayhourrfc');
1713  //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
1714  //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
1715  $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
1716  $pricetaxstring = price2num($this->total_tva, 2, 1);
1717 
1718  /*
1719  $name = implode(unpack("H*", $this->thirdparty->name));
1720  $vatnumber = implode(unpack("H*", $this->thirdparty->tva_intra));
1721  $date = implode(unpack("H*", $datestring));
1722  $pricewithtax = implode(unpack("H*", price2num($pricewithtaxstring, 2)));
1723  $pricetax = implode(unpack("H*", $pricetaxstring));
1724 
1725  //var_dump(strlen($this->thirdparty->name));
1726  //var_dump(str_pad(dechex('9'), 2, '0', STR_PAD_LEFT));
1727  //var_dump($this->thirdparty->name);
1728  //var_dump(implode(unpack("H*", $this->thirdparty->name)));
1729  //var_dump(price($this->total_tva, 0, $tmplang, 0, -1, 2));
1730 
1731  $s = '01'.str_pad(dechex(strlen($this->thirdparty->name)), 2, '0', STR_PAD_LEFT).$name;
1732  $s .= '02'.str_pad(dechex(strlen($this->thirdparty->tva_intra)), 2, '0', STR_PAD_LEFT).$vatnumber;
1733  $s .= '03'.str_pad(dechex(strlen($datestring)), 2, '0', STR_PAD_LEFT).$date;
1734  $s .= '04'.str_pad(dechex(strlen($pricewithtaxstring)), 2, '0', STR_PAD_LEFT).$pricewithtax;
1735  $s .= '05'.str_pad(dechex(strlen($pricetaxstring)), 2, '0', STR_PAD_LEFT).$pricetax;
1736  $s .= ''; // Hash of xml invoice
1737  $s .= ''; // ecda signature
1738  $s .= ''; // ecda public key
1739  $s .= ''; // ecda signature of public key stamp
1740  */
1741 
1742  // Using TLV format
1743  $s = pack('C1', 1).pack('C1', strlen($mysoc->name)).$mysoc->name;
1744  $s .= pack('C1', 2).pack('C1', strlen($mysoc->tva_intra)).$mysoc->tva_intra;
1745  $s .= pack('C1', 3).pack('C1', strlen($datestring)).$datestring;
1746  $s .= pack('C1', 4).pack('C1', strlen($pricewithtaxstring)).$pricewithtaxstring;
1747  $s .= pack('C1', 5).pack('C1', strlen($pricetaxstring)).$pricetaxstring;
1748  $s .= ''; // Hash of xml invoice
1749  $s .= ''; // ecda signature
1750  $s .= ''; // ecda public key
1751  $s .= ''; // ecda signature of public key stamp
1752 
1753  $s = base64_encode($s);
1754 
1755  return $s;
1756  }
1757 
1758 
1764  public function buildSwitzerlandQRString()
1765  {
1766  global $conf, $mysoc;
1767 
1768  $tmplang = new Translate('', $conf);
1769  $tmplang->setDefaultLang('en_US');
1770  $tmplang->load("main");
1771 
1772  $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
1773  $pricetaxstring = price2num($this->total_tva, 2, 1);
1774 
1775  $complementaryinfo = '';
1776  /*
1777  Example: //S1/10/10201409/11/190512/20/1400.000-53/30/106017086/31/180508/32/7.7/40/2:10;0:30
1778  /10/ Numéro de facture – 10201409
1779  /11/ Date de facture – 12.05.2019
1780  /20/ Référence client – 1400.000-53
1781  /30/ Numéro IDE pour la TVA – CHE-106.017.086 TVA
1782  /31/ Date de la prestation pour la comptabilisation de la TVA – 08.05.2018
1783  /32/ Taux de TVA sur le montant total de la facture – 7.7%
1784  /40/ Conditions – 2% d’escompte à 10 jours, paiement net à 30 jours
1785  */
1786  $datestring = dol_print_date($this->date, '%y%m%d');
1787  //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
1788  //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
1789  $complementaryinfo = '//S1/10/'.str_replace('/', '', $this->ref).'/11/'.$datestring;
1790  if ($this->ref_client) {
1791  $complementaryinfo .= '/20/'.$this->ref_client;
1792  }
1793  if ($this->thirdparty->tva_intra) {
1794  $complementaryinfo .= '/30/'.$this->thirdparty->tva_intra;
1795  }
1796 
1797  include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
1798  $bankaccount = new Account($this->db);
1799 
1800  // Header
1801  $s = '';
1802  $s .= "SPC\n";
1803  $s .= "0200\n";
1804  $s .= "1\n";
1805  // Info Seller ("Compte / Payable à")
1806  if ($this->fk_account > 0) {
1807  // Bank BAN if country is LI or CH. TODO Add a test to check than IBAN start with CH or LI
1808  $bankaccount->fetch($this->fk_account);
1809  $s .= $bankaccount->iban."\n";
1810  } else {
1811  $s .= "\n";
1812  }
1813  if ($bankaccount->id > 0 && getDolGlobalString('PDF_SWISS_QRCODE_USE_OWNER_OF_ACCOUNT_AS_CREDITOR')) {
1814  // If a bank account is provided and we ask to use it as creditor, we use the bank address
1815  // TODO In a future, we may always use this address, and if name/address/zip/town/country differs from $mysoc, we can use the address of $mysoc into the final seller field ?
1816  $s .= "S\n";
1817  $s .= dol_trunc($bankaccount->proprio, 70, 'right', 'UTF-8', 1)."\n";
1818  $addresslinearray = explode("\n", $bankaccount->owner_address);
1819  $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
1820  $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
1821  /*$s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
1822  $s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
1823  $s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";*/
1824  } else {
1825  $s .= "S\n";
1826  $s .= dol_trunc($mysoc->name, 70, 'right', 'UTF-8', 1)."\n";
1827  $addresslinearray = explode("\n", $mysoc->address);
1828  $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
1829  $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
1830  $s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
1831  $s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
1832  $s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";
1833  }
1834  // Final seller (Ultimate seller) ("Créancier final" = "En faveur de")
1835  $s .= "\n";
1836  $s .= "\n";
1837  $s .= "\n";
1838  $s .= "\n";
1839  $s .= "\n";
1840  $s .= "\n";
1841  $s .= "\n";
1842  // Amount of payment (to do?)
1843  $s .= price($pricewithtaxstring, 0, 'none', 0, 0, 2)."\n";
1844  $s .= ($this->multicurrency_code ? $this->multicurrency_code : $conf->currency)."\n";
1845  // Buyer
1846  $s .= "S\n";
1847  $s .= dol_trunc($this->thirdparty->name, 70, 'right', 'UTF-8', 1)."\n";
1848  $addresslinearray = explode("\n", $this->thirdparty->address);
1849  $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
1850  $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
1851  $s .= dol_trunc($this->thirdparty->zip, 16, 'right', 'UTF-8', 1)."\n";
1852  $s .= dol_trunc($this->thirdparty->town, 35, 'right', 'UTF-8', 1)."\n";
1853  $s .= dol_trunc($this->thirdparty->country_code, 2, 'right', 'UTF-8', 1)."\n";
1854  // ID of payment
1855  $s .= "NON\n"; // NON or QRR
1856  $s .= "\n"; // QR Code reference if previous field is QRR
1857  // Free text
1858  if ($complementaryinfo) {
1859  $s .= $complementaryinfo."\n";
1860  } else {
1861  $s .= "\n";
1862  }
1863  $s .= "EPD\n";
1864  // More text, complementary info
1865  if ($complementaryinfo) {
1866  $s .= $complementaryinfo."\n";
1867  }
1868  $s .= "\n";
1869  //var_dump($s);exit;
1870  return $s;
1871  }
1872 }
1873 
1874 
1875 
1876 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
1877 
1881 abstract class CommonInvoiceLine extends CommonObjectLine
1882 {
1887  public $label;
1888 
1893  public $ref; // Product ref (deprecated)
1898  public $libelle; // Product label (deprecated)
1899 
1904  public $product_type = 0;
1905 
1910  public $product_ref;
1911 
1916  public $product_label;
1917 
1922  public $product_desc;
1923 
1928  public $qty;
1929 
1934  public $subprice;
1935 
1941  public $price;
1942 
1947  public $fk_product;
1948 
1953  public $vat_src_code;
1954 
1959  public $tva_tx;
1960 
1965  public $localtax1_tx;
1966 
1971  public $localtax2_tx;
1972 
1978  public $localtax1_type;
1979 
1985  public $localtax2_type;
1986 
1991  public $remise_percent;
1992 
1998  public $remise;
1999 
2004  public $total_ht;
2005 
2010  public $total_tva;
2011 
2016  public $total_localtax1;
2017 
2022  public $total_localtax2;
2023 
2028  public $total_ttc;
2029 
2030  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
2031  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
2032 
2033  public $buy_price_ht;
2034  public $buyprice; // For backward compatibility
2035  public $pa_ht; // For backward compatibility
2036 
2037  public $marge_tx;
2038  public $marque_tx;
2039 
2046  public $info_bits = 0;
2047 
2056  public $special_code = 0;
2057 
2062 
2067 
2068  public $fk_accounting_account;
2069 }
if($user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition: card.php:58
print $langs trans("AuditedSecurityEvents").'</strong >< span class="opacitymedium"></span >< br > status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition: security.php:604
$object ref
Definition: info.php:79
Class to manage bank accounts.
Class to manage agenda events (actions)
Class to manage withdrawal receipts.
Superclass for invoices classes.
const TYPE_CREDIT_NOTE
Credit note invoice.
const STATUS_CLOSED
Classified paid.
getSumCreditNotesUsed($multicurrency=0)
Return amount (with tax) of all credit notes invoices + excess received used by invoice.
getRemainToPay($multicurrency=0)
Return remain amount to pay.
buildZATCAQRString()
Build string for ZATCA QR Code (Arabi Saudia)
makeStripeCardRequest($fuser, $id, $sourcetype='facture')
Create a payment with Stripe card Must take amount using Stripe and record an event into llx_actionco...
const TYPE_STANDARD
Standard invoice.
buildEPCQrCodeString()
Build string for EPC QR Code.
makeStripeSepaRequest($fuser, $did, $type='direct-debit', $sourcetype='facture', $service='', $forcestripe='')
Create a direct debit order into prelevement_bons for a given prelevement_request,...
demande_prelevement($fuser, $amount=0, $type='direct-debit', $sourcetype='facture', $checkduplicateamongall=0)
Create a withdrawal request for a direct debit order or a credit transfer order.
getSubtypeLabel($table='')
Return label of invoice subtype.
demande_prelevement_delete($fuser, $did)
Remove a direct debit request or a credit transfer request.
getVentilExportCompta()
Return if an invoice was dispatched into bookkeeping.
const TYPE_PROFORMA
Proforma invoice.
buildSwitzerlandQRString()
Build string for QR-Bill (Switzerland)
const STATUS_VALIDATED
Validated (need to be paid)
const TYPE_SITUATION
Situation invoice.
getSumDepositsUsed($multicurrency=0)
Return amount (with tax) of all deposits invoices used by invoice.
getSommePaiement($multicurrency=0)
Return amount of payments already done.
const TYPE_DEPOSIT
Deposit invoice.
LibStatut($paye, $status, $mode=0, $alreadypaid=-1, $type=-1)
Return label of a status.
const STATUS_ABANDONED
Classified abandoned and no payment done.
calculate_date_lim_reglement($cond_reglement=0)
Returns an invoice payment deadline based on the invoice settlement conditions and billing date.
const TYPE_REPLACEMENT
Replacement invoice.
getSumFromThisCreditNotesNotUsed($multicurrency=0)
Return amount (with tax) of all converted amount for this credit note.
getListIdAvoirFromInvoice()
Returns array of credit note ids from the invoice.
const STATUS_DRAFT
Draft status.
getIdReplacingInvoice($option='')
Returns the id of the invoice that replaces it.
getLibType($withbadge=0)
Return label of type of invoice.
is_erasable()
Return if an invoice can be deleted Rule is: If invoice is draft and has a temporary ref -> yes (1) I...
getArrayOfInvoiceSubtypes($mode=0)
Retrieve a list of invoice subtype labels or codes.
getListOfPayments($filtertype='', $multicurrency=0)
Return list of payments.
getLibStatut($mode=0, $alreadypaid=-1)
Return label of object status.
getNextNumRef($soc, $mode='next')
Return next reference of invoice not already used (or last reference)
Parent class of all other business classes for details of elements (invoices, contracts,...
$label
Custom label of line.
Parent class of all other business classes (invoices, contracts, proposals, orders,...
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
setPaymentMethods($id)
Change the payments methods.
Parent class for class inheritance lines of business objects This class is useless for the moment so ...
Class to manage bank accounts description of third parties.
Class for CompanyPaymentMode.
Class to manage absolute discounts.
Class to manage third parties objects (customers, suppliers, prospects...)
Stripe class.
Class to manage translations.
trait CommonIncoterm
Superclass for incoterm classes.
if(isModEnabled('invoice') && $user->hasRight('facture', 'lire')) if((isModEnabled('fournisseur') &&!getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD') && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') && $user->hasRight('don', 'lire')) if(isModEnabled('tax') && $user->hasRight('tax', 'charges', 'lire')) if(isModEnabled('invoice') &&isModEnabled('order') && $user->hasRight("commande", "lire") &&!getDolGlobalString('WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER')) $sql
Social contributions to pay.
Definition: index.php:744
dol_time_plus_duree($time, $duration_value, $duration_unit, $ruleforendofmonth=0)
Add a delay to a date.
Definition: date.lib.php:123
print *****$script_file(".$version.") pid cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_getIdFromCode($db, $key, $tablename, $fieldkey='code', $fieldid='id', $entityfilter=0, $filters='')
Return an id or code from a code or id.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
dol_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
if(!defined( 'CSRFCHECK_WITH_TOKEN'))
if(preg_match('/crypted:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition: repair.php:122