dolibarr  x.y.z
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  *
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 
26 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
27 require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
28 
32 abstract class CommonInvoice extends CommonObject
33 {
34  use CommonIncoterm;
35 
39  const TYPE_STANDARD = 0;
40 
44  const TYPE_REPLACEMENT = 1;
45 
49  const TYPE_CREDIT_NOTE = 2;
50 
54  const TYPE_DEPOSIT = 3;
55 
60  const TYPE_PROFORMA = 4;
61 
65  const TYPE_SITUATION = 5;
66 
70  const STATUS_DRAFT = 0;
71 
75  const STATUS_VALIDATED = 1;
76 
84  const STATUS_CLOSED = 2;
85 
93  const STATUS_ABANDONED = 3;
94 
95 
96  public $totalpaid; // duplicate with sumpayed
97  public $totaldeposits; // duplicate with sumdeposit
98  public $totalcreditnotes; // duplicate with sumcreditnote
99 
100  public $sumpayed;
101  public $sumpayed_multicurrency;
102  public $sumdeposit;
103  public $sumdeposit_multicurrency;
104  public $sumcreditnote;
105  public $sumcreditnote_multicurrency;
106  public $remaintopay;
107 
108 
116  public function getRemainToPay($multicurrency = 0)
117  {
118  $alreadypaid = 0.0;
119  $alreadypaid += $this->getSommePaiement($multicurrency);
120  $alreadypaid += $this->getSumDepositsUsed($multicurrency);
121  $alreadypaid += $this->getSumCreditNotesUsed($multicurrency);
122 
123  $remaintopay = price2num($this->total_ttc - $alreadypaid, 'MT');
124  if ($this->statut == self::STATUS_CLOSED && $this->close_code == 'discount_vat') { // If invoice closed with discount for anticipated payment
125  $remaintopay = 0.0;
126  }
127  return $remaintopay;
128  }
129 
137  public function getSommePaiement($multicurrency = 0)
138  {
139  $table = 'paiement_facture';
140  $field = 'fk_facture';
141  if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
142  $table = 'paiementfourn_facturefourn';
143  $field = 'fk_facturefourn';
144  }
145 
146  $sql = "SELECT sum(amount) as amount, sum(multicurrency_amount) as multicurrency_amount";
147  $sql .= " FROM ".$this->db->prefix().$table;
148  $sql .= " WHERE ".$field." = ".((int) $this->id);
149 
150  dol_syslog(get_class($this)."::getSommePaiement", LOG_DEBUG);
151 
152  $resql = $this->db->query($sql);
153  if ($resql) {
154  $obj = $this->db->fetch_object($resql);
155 
156  $this->db->free($resql);
157 
158  if ($obj) {
159  if ($multicurrency) {
160  $this->sumpayed_multicurrency = $obj->multicurrency_amount;
161  return (float) $obj->multicurrency_amount;
162  } else {
163  $this->sumpayed = $obj->amount;
164  return (float) $obj->amount;
165  }
166  } else {
167  return 0;
168  }
169  } else {
170  $this->error = $this->db->lasterror();
171  return -1;
172  }
173  }
174 
183  public function getSumDepositsUsed($multicurrency = 0)
184  {
185  /*if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
186  // 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.
187  return 0.0;
188  }*/
189 
190  require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
191 
192  $discountstatic = new DiscountAbsolute($this->db);
193  $result = $discountstatic->getSumDepositsUsed($this, $multicurrency);
194 
195  if ($result >= 0) {
196  if ($multicurrency) {
197  $this->sumdeposit_multicurrency = $result;
198  } else {
199  $this->sumdeposit = $result;
200  }
201 
202  return $result;
203  } else {
204  $this->error = $discountstatic->error;
205  return -1;
206  }
207  }
208 
215  public function getSumCreditNotesUsed($multicurrency = 0)
216  {
217  require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
218 
219  $discountstatic = new DiscountAbsolute($this->db);
220  $result = $discountstatic->getSumCreditNotesUsed($this, $multicurrency);
221  if ($result >= 0) {
222  if ($multicurrency) {
223  $this->sumcreditnote_multicurrency = $result;
224  } else {
225  $this->sumcreditnote = $result;
226  }
227 
228  return $result;
229  } else {
230  $this->error = $discountstatic->error;
231  return -1;
232  }
233  }
234 
241  public function getSumFromThisCreditNotesNotUsed($multicurrency = 0)
242  {
243  require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
244 
245  $discountstatic = new DiscountAbsolute($this->db);
246  $result = $discountstatic->getSumFromThisCreditNotesNotUsed($this, $multicurrency);
247  if ($result >= 0) {
248  return $result;
249  } else {
250  $this->error = $discountstatic->error;
251  return -1;
252  }
253  }
254 
260  public function getListIdAvoirFromInvoice()
261  {
262  $idarray = array();
263 
264  $sql = "SELECT rowid";
265  $sql .= " FROM ".$this->db->prefix().$this->table_element;
266  $sql .= " WHERE fk_facture_source = ".((int) $this->id);
267  $sql .= " AND type = 2";
268  $resql = $this->db->query($sql);
269  if ($resql) {
270  $num = $this->db->num_rows($resql);
271  $i = 0;
272  while ($i < $num) {
273  $row = $this->db->fetch_row($resql);
274  $idarray[] = $row[0];
275  $i++;
276  }
277  } else {
278  dol_print_error($this->db);
279  }
280  return $idarray;
281  }
282 
289  public function getIdReplacingInvoice($option = '')
290  {
291  $sql = "SELECT rowid";
292  $sql .= " FROM ".$this->db->prefix().$this->table_element;
293  $sql .= " WHERE fk_facture_source = ".((int) $this->id);
294  $sql .= " AND type < 2";
295  if ($option == 'validated') {
296  $sql .= ' AND fk_statut = 1';
297  }
298  // PROTECTION BAD DATA
299  // In case the database is corrupted and there is a valid replectement invoice
300  // and another no, priority is given to the valid one.
301  // Should not happen (unless concurrent access and 2 people have created a
302  // replacement invoice for the same invoice at the same time)
303  $sql .= " ORDER BY fk_statut DESC";
304 
305  $resql = $this->db->query($sql);
306  if ($resql) {
307  $obj = $this->db->fetch_object($resql);
308  if ($obj) {
309  // If there is any
310  return $obj->rowid;
311  } else {
312  // If no invoice replaces it
313  return 0;
314  }
315  } else {
316  return -1;
317  }
318  }
319 
326  public function getListOfPayments($filtertype = '')
327  {
328  $retarray = array();
329 
330  $table = 'paiement_facture';
331  $table2 = 'paiement';
332  $field = 'fk_facture';
333  $field2 = 'fk_paiement';
334  $field3 = ', p.ref_ext';
335  $sharedentity = 'facture';
336  if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
337  $table = 'paiementfourn_facturefourn';
338  $table2 = 'paiementfourn';
339  $field = 'fk_facturefourn';
340  $field2 = 'fk_paiementfourn';
341  $field3 = '';
342  $sharedentity = 'facture_fourn';
343  }
344 
345  $sql = "SELECT p.ref, pf.amount, pf.multicurrency_amount, p.fk_paiement, p.datep, p.num_paiement as num, t.code".$field3;
346  $sql .= " FROM ".$this->db->prefix().$table." as pf, ".$this->db->prefix().$table2." as p, ".$this->db->prefix()."c_paiement as t";
347  $sql .= " WHERE pf.".$field." = ".((int) $this->id);
348  $sql .= " AND pf.".$field2." = p.rowid";
349  $sql .= ' AND p.fk_paiement = t.id';
350  $sql .= ' AND p.entity IN ('.getEntity($sharedentity).')';
351  if ($filtertype) {
352  $sql .= " AND t.code='PRE'";
353  }
354 
355  dol_syslog(get_class($this)."::getListOfPayments", LOG_DEBUG);
356  $resql = $this->db->query($sql);
357  if ($resql) {
358  $num = $this->db->num_rows($resql);
359  $i = 0;
360  while ($i < $num) {
361  $obj = $this->db->fetch_object($resql);
362  $tmp = array('amount'=>$obj->amount, 'type'=>$obj->code, 'date'=>$obj->datep, 'num'=>$obj->num, 'ref'=>$obj->ref);
363  if (!empty($field3)) {
364  $tmp['ref_ext'] = $obj->ref_ext;
365  }
366  $retarray[] = $tmp;
367  $i++;
368  }
369  $this->db->free($resql);
370 
371  //look for credit notes and discounts and deposits
372  $sql = '';
373  if ($this->element == 'facture' || $this->element == 'invoice') {
374  $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";
375  $sql .= ' FROM '.$this->db->prefix().'societe_remise_except as rc, '.$this->db->prefix().'facture as f';
376  $sql .= ' WHERE rc.fk_facture_source=f.rowid AND rc.fk_facture = '.((int) $this->id);
377  $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)
378  } elseif ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
379  $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";
380  $sql .= ' FROM '.$this->db->prefix().'societe_remise_except as rc, '.$this->db->prefix().'facture_fourn as f';
381  $sql .= ' WHERE rc.fk_invoice_supplier_source=f.rowid AND rc.fk_invoice_supplier = '.((int) $this->id);
382  $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)
383  }
384 
385  if ($sql) {
386  $resql = $this->db->query($sql);
387  if ($resql) {
388  $num = $this->db->num_rows($resql);
389  $i = 0;
390  while ($i < $num) {
391  $obj = $this->db->fetch_object($resql);
392  if ($multicurrency) {
393  $retarray[] = array('amount'=>$obj->multicurrency_amount, 'type'=>$obj->type, 'date'=>$obj->date, 'num'=>'0', 'ref'=>$obj->ref);
394  } else {
395  $retarray[] = array('amount'=>$obj->amount, 'type'=>$obj->type, 'date'=>$obj->date, 'num'=>'', 'ref'=>$obj->ref);
396  }
397  $i++;
398  }
399  } else {
400  $this->error = $this->db->lasterror();
401  dol_print_error($this->db);
402  return array();
403  }
404  $this->db->free($resql);
405  }
406 
407  return $retarray;
408  } else {
409  $this->error = $this->db->lasterror();
410  dol_print_error($this->db);
411  return array();
412  }
413  }
414 
415 
416  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
430  public function is_erasable()
431  {
432  // phpcs:enable
433  global $conf;
434 
435  // We check if invoice is a temporary number (PROVxxxx)
436  $tmppart = substr($this->ref, 1, 4);
437 
438  if ($this->statut == self::STATUS_DRAFT && $tmppart === 'PROV') { // If draft invoice and ref not yet defined
439  return 1;
440  }
441 
442  if (!empty($conf->global->INVOICE_CAN_NEVER_BE_REMOVED)) {
443  return 0;
444  }
445 
446  // If not a draft invoice and not temporary invoice
447  if ($tmppart !== 'PROV') {
448  $ventilExportCompta = $this->getVentilExportCompta();
449  if ($ventilExportCompta != 0) {
450  return -1;
451  }
452 
453  // Get last number of validated invoice
454  if ($this->element != 'invoice_supplier') {
455  if (empty($this->thirdparty)) {
456  $this->fetch_thirdparty(); // We need to have this->thirdparty defined, in case of numbering rule use tags that depend on thirdparty (like {t} tag).
457  }
458  $maxref = $this->getNextNumRef($this->thirdparty, 'last');
459 
460  // If there is no invoice into the reset range and not already dispatched, we can delete
461  // If invoice to delete is last one and not already dispatched, we can delete
462  if (empty($conf->global->INVOICE_CAN_ALWAYS_BE_REMOVED) && $maxref != '' && $maxref != $this->ref) {
463  return -2;
464  }
465 
466  // TODO If there is payment in bookkeeping, check payment is not dispatched in accounting
467  // ...
468 
469  if ($this->situation_cycle_ref && method_exists($this, 'is_last_in_cycle')) {
470  $last = $this->is_last_in_cycle();
471  if (!$last) {
472  return -3;
473  }
474  }
475  }
476  }
477 
478  // Test if there is at least one payment. If yes, refuse to delete.
479  if (empty($conf->global->INVOICE_CAN_ALWAYS_BE_REMOVED) && $this->getSommePaiement() > 0) {
480  return -4;
481  }
482 
483  return 2;
484  }
485 
491  public function getVentilExportCompta()
492  {
493  $alreadydispatched = 0;
494 
495  $type = 'customer_invoice';
496  if ($this->element == 'invoice_supplier') {
497  $type = 'supplier_invoice';
498  }
499 
500  $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);
501  $resql = $this->db->query($sql);
502  if ($resql) {
503  $obj = $this->db->fetch_object($resql);
504  if ($obj) {
505  $alreadydispatched = $obj->nb;
506  }
507  } else {
508  $this->error = $this->db->lasterror();
509  return -1;
510  }
511 
512  if ($alreadydispatched) {
513  return 1;
514  }
515  return 0;
516  }
517 
518 
525  public function getLibType($withbadge = 0)
526  {
527  global $langs;
528 
529  $labellong = "Unknown";
530  if ($this->type == CommonInvoice::TYPE_STANDARD) {
531  $labellong = "InvoiceStandard";
532  $labelshort = "InvoiceStandardShort";
533  } elseif ($this->type == CommonInvoice::TYPE_REPLACEMENT) {
534  $labellong = "InvoiceReplacement";
535  $labelshort = "InvoiceReplacementShort";
536  } elseif ($this->type == CommonInvoice::TYPE_CREDIT_NOTE) {
537  $labellong = "InvoiceAvoir";
538  $labelshort = "CreditNote";
539  } elseif ($this->type == CommonInvoice::TYPE_DEPOSIT) {
540  $labellong = "InvoiceDeposit";
541  $labelshort = "Deposit";
542  } elseif ($this->type == CommonInvoice::TYPE_PROFORMA) {
543  $labellong = "InvoiceProForma"; // Not used.
544  $labelshort = "ProForma";
545  } elseif ($this->type == CommonInvoice::TYPE_SITUATION) {
546  $labellong = "InvoiceSituation";
547  $labelshort = "Situation";
548  }
549 
550  $out = '';
551  if ($withbadge) {
552  $out .= '<span class="badgeneutral" title="'.dol_escape_htmltag($langs->trans($labellong)).'">';
553  }
554  $out .= $langs->trans($withbadge == 2 ? $labelshort : $labellong);
555  if ($withbadge) {
556  $out .= '</span>';
557  }
558  return $out;
559  }
560 
568  public function getLibStatut($mode = 0, $alreadypaid = -1)
569  {
570  return $this->LibStatut($this->paye, $this->statut, $mode, $alreadypaid, $this->type);
571  }
572 
573  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
584  public function LibStatut($paye, $status, $mode = 0, $alreadypaid = -1, $type = -1)
585  {
586  // phpcs:enable
587  global $langs, $hookmanager;
588  $langs->load('bills');
589 
590  if ($type == -1) {
591  $type = $this->type;
592  }
593 
594  $statusType = 'status0';
595  $prefix = 'Short';
596  if (!$paye) {
597  if ($status == 0) {
598  $labelStatus = $langs->transnoentitiesnoconv('BillStatusDraft');
599  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusDraft');
600  } elseif (($status == 3 || $status == 2) && $alreadypaid <= 0) {
601  if ($status == 3) {
602  $labelStatus = $langs->transnoentitiesnoconv('BillStatusCanceled');
603  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusCanceled');
604  } else {
605  $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedUnpaid');
606  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedUnpaid');
607  }
608  $statusType = 'status5';
609  } elseif (($status == 3 || $status == 2) && $alreadypaid > 0) {
610  $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedPaidPartially');
611  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedPaidPartially');
612  $statusType = 'status9';
613  } elseif ($alreadypaid == 0) {
614  $labelStatus = $langs->transnoentitiesnoconv('BillStatusNotPaid');
615  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusNotPaid');
616  $statusType = 'status1';
617  } else {
618  $labelStatus = $langs->transnoentitiesnoconv('BillStatusStarted');
619  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusStarted');
620  $statusType = 'status3';
621  }
622  } else {
623  $statusType = 'status6';
624 
625  if ($type == self::TYPE_CREDIT_NOTE) {
626  $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaidBackOrConverted'); // credit note
627  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaidBackOrConverted'); // credit note
628  } elseif ($type == self::TYPE_DEPOSIT) {
629  $labelStatus = $langs->transnoentitiesnoconv('BillStatusConverted'); // deposit invoice
630  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusConverted'); // deposit invoice
631  } else {
632  $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaid');
633  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaid');
634  }
635  }
636 
637  $parameters = array(
638  'status' => $status,
639  'mode' => $mode,
640  'paye' => $paye,
641  'alreadypaid' => $alreadypaid,
642  'type' => $type
643  );
644 
645  $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
646 
647  if ($reshook > 0) {
648  return $hookmanager->resPrint;
649  }
650 
651 
652 
653  return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
654  }
655 
656  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
664  public function calculate_date_lim_reglement($cond_reglement = 0)
665  {
666  // phpcs:enable
667  if (!$cond_reglement) {
668  $cond_reglement = $this->cond_reglement_code;
669  }
670  if (!$cond_reglement) {
671  $cond_reglement = $this->cond_reglement_id;
672  }
673 
674  $cdr_nbjour = 0;
675  $cdr_type = 0;
676  $cdr_decalage = 0;
677 
678  $sqltemp = "SELECT c.type_cdr, c.nbjour, c.decalage";
679  $sqltemp .= " FROM ".$this->db->prefix()."c_payment_term as c";
680  if (is_numeric($cond_reglement)) {
681  $sqltemp .= " WHERE c.rowid=".((int) $cond_reglement);
682  } else {
683  $sqltemp .= " WHERE c.entity IN (".getEntity('c_payment_term').")";
684  $sqltemp .= " AND c.code = '".$this->db->escape($cond_reglement)."'";
685  }
686 
687  dol_syslog(get_class($this).'::calculate_date_lim_reglement', LOG_DEBUG);
688  $resqltemp = $this->db->query($sqltemp);
689  if ($resqltemp) {
690  if ($this->db->num_rows($resqltemp)) {
691  $obj = $this->db->fetch_object($resqltemp);
692  $cdr_nbjour = $obj->nbjour;
693  $cdr_type = $obj->type_cdr;
694  $cdr_decalage = $obj->decalage;
695  }
696  } else {
697  $this->error = $this->db->error();
698  return -1;
699  }
700  $this->db->free($resqltemp);
701 
702  /* Definition de la date limite */
703 
704  // 0 : adding the number of days
705  if ($cdr_type == 0) {
706  $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
707 
708  $datelim += ($cdr_decalage * 3600 * 24);
709  } elseif ($cdr_type == 1) {
710  // 1 : application of the "end of the month" rule
711  $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
712 
713  $mois = date('m', $datelim);
714  $annee = date('Y', $datelim);
715  if ($mois == 12) {
716  $mois = 1;
717  $annee += 1;
718  } else {
719  $mois += 1;
720  }
721  // We move at the beginning of the next month, and we take a day off
722  $datelim = dol_mktime(12, 0, 0, $mois, 1, $annee);
723  $datelim -= (3600 * 24);
724 
725  $datelim += ($cdr_decalage * 3600 * 24);
726  } elseif ($cdr_type == 2 && !empty($cdr_decalage)) {
727  // 2 : application of the rule, the N of the current or next month
728  include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
729  $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
730 
731  $date_piece = dol_mktime(0, 0, 0, date('m', $datelim), date('d', $datelim), date('Y', $datelim)); // Sans les heures minutes et secondes
732  $date_lim_current = dol_mktime(0, 0, 0, date('m', $datelim), $cdr_decalage, date('Y', $datelim)); // Sans les heures minutes et secondes
733  $date_lim_next = dol_time_plus_duree($date_lim_current, 1, 'm'); // Add 1 month
734 
735  $diff = $date_piece - $date_lim_current;
736 
737  if ($diff < 0) {
738  $datelim = $date_lim_current;
739  } else {
740  $datelim = $date_lim_next;
741  }
742  } else {
743  return 'Bad value for type_cdr in database for record cond_reglement = '.$cond_reglement;
744  }
745 
746  return $datelim;
747  }
748 
749  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
760  public function demande_prelevement($fuser, $amount = 0, $type = 'direct-debit', $sourcetype = 'facture')
761  {
762  // phpcs:enable
763  global $conf;
764 
765  $error = 0;
766 
767  dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
768 
769  if ($this->statut > self::STATUS_DRAFT && $this->paye == 0) {
770  require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
771  $bac = new CompanyBankAccount($this->db);
772  $bac->fetch(0, $this->socid);
773 
774  $sql = "SELECT count(*)";
775  $sql .= " FROM ".$this->db->prefix()."prelevement_demande";
776  if ($type == 'bank-transfer') {
777  $sql .= " WHERE fk_facture_fourn = ".((int) $this->id);
778  } else {
779  $sql .= " WHERE fk_facture = ".((int) $this->id);
780  }
781  $sql .= " AND ext_payment_id IS NULL"; // To exclude record done for some online payments
782  $sql .= " AND traite = 0";
783 
784  dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
785  $resql = $this->db->query($sql);
786  if ($resql) {
787  $row = $this->db->fetch_row($resql);
788  if ($row[0] == 0) {
789  $now = dol_now();
790 
791  $totalpaid = $this->getSommePaiement();
792  $totalcreditnotes = $this->getSumCreditNotesUsed();
793  $totaldeposits = $this->getSumDepositsUsed();
794  //print "totalpaid=".$totalpaid." totalcreditnotes=".$totalcreditnotes." totaldeposts=".$totaldeposits;
795 
796  // We can also use bcadd to avoid pb with floating points
797  // For example print 239.2 - 229.3 - 9.9; does not return 0.
798  //$resteapayer=bcadd($this->total_ttc,$totalpaid,$conf->global->MAIN_MAX_DECIMALS_TOT);
799  //$resteapayer=bcadd($resteapayer,$totalavoir,$conf->global->MAIN_MAX_DECIMALS_TOT);
800  if (empty($amount)) {
801  $amount = price2num($this->total_ttc - $totalpaid - $totalcreditnotes - $totaldeposits, 'MT');
802  }
803 
804  if (is_numeric($amount) && $amount != 0) {
805  $sql = 'INSERT INTO '.$this->db->prefix().'prelevement_demande(';
806  if ($type == 'bank-transfer') {
807  $sql .= 'fk_facture_fourn, ';
808  } else {
809  $sql .= 'fk_facture, ';
810  }
811  $sql .= ' amount, date_demande, fk_user_demande, code_banque, code_guichet, number, cle_rib, sourcetype, entity)';
812  $sql .= " VALUES (".((int) $this->id);
813  $sql .= ", ".((float) price2num($amount));
814  $sql .= ", '".$this->db->idate($now)."'";
815  $sql .= ", ".((int) $fuser->id);
816  $sql .= ", '".$this->db->escape($bac->code_banque)."'";
817  $sql .= ", '".$this->db->escape($bac->code_guichet)."'";
818  $sql .= ", '".$this->db->escape($bac->number)."'";
819  $sql .= ", '".$this->db->escape($bac->cle_rib)."'";
820  $sql .= ", '".$this->db->escape($sourcetype)."'";
821  $sql .= ", ".((int) $conf->entity);
822  $sql .= ")";
823 
824  dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
825  $resql = $this->db->query($sql);
826  if (!$resql) {
827  $this->error = $this->db->lasterror();
828  dol_syslog(get_class($this).'::demandeprelevement Erreur');
829  $error++;
830  }
831  } else {
832  $this->error = 'WithdrawRequestErrorNilAmount';
833  dol_syslog(get_class($this).'::demandeprelevement WithdrawRequestErrorNilAmount');
834  $error++;
835  }
836 
837  if (!$error) {
838  // Force payment mode of invoice to withdraw
839  $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
840  if ($payment_mode_id > 0) {
841  $result = $this->setPaymentMethods($payment_mode_id);
842  }
843  }
844 
845  if ($error) {
846  return -1;
847  }
848  return 1;
849  } else {
850  $this->error = "A request already exists";
851  dol_syslog(get_class($this).'::demandeprelevement Impossible de creer une demande, demande deja en cours');
852  return 0;
853  }
854  } else {
855  $this->error = $this->db->error();
856  dol_syslog(get_class($this).'::demandeprelevement Erreur -2');
857  return -2;
858  }
859  } else {
860  $this->error = "Status of invoice does not allow this";
861  dol_syslog(get_class($this)."::demandeprelevement ".$this->error." $this->statut, $this->paye, $this->mode_reglement_id");
862  return -3;
863  }
864  }
865 
866 
877  public function makeStripeSepaRequest($fuser, $did = 0, $type = 'direct-debit', $sourcetype = 'facture')
878  {
879  global $conf, $mysoc, $user, $langs;
880 
881  if (empty($conf->global->STRIPE_SEPA_DIRECT_DEBIT)) {
882  //exit
883  return 0;
884  }
885 
886  $error = 0;
887 
888  dol_syslog(get_class($this)."::makeStripeSepaRequest 0", LOG_DEBUG);
889 
890  if ($this->statut > self::STATUS_DRAFT && $this->paye == 0) {
891  require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
892  $bac = new CompanyBankAccount($this->db);
893  $result = $bac->fetch(0, $this->socid, 1, 'ban');
894  if ($result <= 0 || empty($bac->id)) {
895  $this->error = $langs->trans("ThirdpartyHasNoDefaultBanAccount");
896  $this->errors[] = $this->error;
897  dol_syslog(get_class($this)."::makeStripeSepaRequest ".$this->error);
898  return -1;
899  }
900 
901  $sql = "SELECT rowid, date_demande, amount, fk_facture, fk_facture_fourn";
902  $sql .= " FROM ".$this->db->prefix()."prelevement_demande";
903  $sql .= " WHERE rowid = ".((int) $did);
904 
905  dol_syslog(get_class($this)."::makeStripeSepaRequest 1", LOG_DEBUG);
906  $resql = $this->db->query($sql);
907  if ($resql) {
908  $obj = $this->db->fetch_object($resql);
909  if (!$obj) {
910  dol_print_error($this->db, 'CantFindRequestWithId');
911  return -2;
912  }
913 
914  //
915  $amount = $obj->amount;
916 
917  $now = dol_now();
918 
919  $totalpaye = $this->getSommePaiement();
920  $totalcreditnotes = $this->getSumCreditNotesUsed();
921  $totaldeposits = $this->getSumDepositsUsed();
922  //print "totalpaye=".$totalpaye." totalcreditnotes=".$totalcreditnotes." totaldeposts=".$totaldeposits;
923 
924  // We can also use bcadd to avoid pb with floating points
925  // For example print 239.2 - 229.3 - 9.9; does not return 0.
926  //$resteapayer=bcadd($this->total_ttc,$totalpaye,$conf->global->MAIN_MAX_DECIMALS_TOT);
927  //$resteapayer=bcadd($resteapayer,$totalavoir,$conf->global->MAIN_MAX_DECIMALS_TOT);
928  $amounttocheck = price2num($this->total_ttc - $totalpaye - $totalcreditnotes - $totaldeposits, 'MT');
929 
930  // TODO We can compare $amount and $amounttocheck
931 
932  if (is_numeric($amount) && $amount != 0) {
933  require_once DOL_DOCUMENT_ROOT.'/societe/class/companypaymentmode.class.php';
934  $companypaymentmode = new CompanyPaymentMode($this->db);
935  $companypaymentmode->fetch($bac->id);
936 
937  // Start code for Stripe
938  $service = 'StripeTest';
939  $servicestatus = 0;
940  if (!empty($conf->global->STRIPE_LIVE) && !GETPOST('forcesandbox', 'alpha')) {
941  $service = 'StripeLive';
942  $servicestatus = 1;
943  }
944 
945  dol_syslog("makeStripeSepaRequest amount = ".$amount." service=" . $service . " servicestatus=" . $servicestatus . " thirdparty_id=" . $this->socid . " companypaymentmode=" . $companypaymentmode->id);
946 
947  $this->stripechargedone = 0;
948  $this->stripechargeerror = 0;
949  $now = dol_now();
950 
951  $currency = $conf->currency;
952 
953  global $stripearrayofkeysbyenv;
954  global $savstripearrayofkeysbyenv;
955 
956  $errorforinvoice = 0; // We reset the $errorforinvoice at each invoice loop
957 
958  $this->fetch_thirdparty();
959 
960  dol_syslog("--- Process invoice thirdparty_id=" . $this->id . ", thirdparty_name=" . $this->thirdparty->name . " id=" . $this->id . ", ref=" . $this->ref . ", datef=" . dol_print_date($this->date, 'dayhourlog'), LOG_DEBUG);
961 
962  $alreadypayed = $this->getSommePaiement();
963  $amount_credit_notes_included = $this->getSumCreditNotesUsed();
964  $amounttopay = $this->total_ttc - $alreadypayed - $amount_credit_notes_included;
965 
966  // Correct the amount according to unit of currency
967  // See https://support.stripe.com/questions/which-zero-decimal-currencies-does-stripe-support
968  $arrayzerounitcurrency = ['BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'];
969  $amountstripe = $amounttopay;
970  if (!in_array($currency, $arrayzerounitcurrency)) {
971  $amountstripe = $amountstripe * 100;
972  }
973 
974  if ($amountstripe > 0) {
975  try {
976  //var_dump($companypaymentmode);
977  dol_syslog("We will try to pay with companypaymentmodeid=" . $companypaymentmode->id . " stripe_card_ref=" . $companypaymentmode->stripe_card_ref . " mode=" . $companypaymentmode->status, LOG_DEBUG);
978 
979  $thirdparty = new Societe($this->db);
980  $resultthirdparty = $thirdparty->fetch($this->socid);
981 
982  include_once DOL_DOCUMENT_ROOT . '/stripe/class/stripe.class.php'; // This include the include of htdocs/stripe/config.php
983  // So it inits or erases the $stripearrayofkeysbyenv
984  $stripe = new Stripe($this->db);
985 
986  if (empty($savstripearrayofkeysbyenv)) {
987  $savstripearrayofkeysbyenv = $stripearrayofkeysbyenv;
988  }
989  dol_syslog("makeStripeSepaRequest Current Stripe environment is " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key']);
990  dol_syslog("makeStripeSepaRequest Current Saved Stripe environment is " . $savstripearrayofkeysbyenv[$servicestatus]['publishable_key']);
991 
992  $foundalternativestripeaccount = '';
993 
994  // Force stripe to another value (by default this value is empty)
995  if (!empty($thirdparty->array_options['options_stripeaccount'])) {
996  dol_syslog("makeStripeSepaRequest The thirdparty id=" . $thirdparty->id . " has a dedicated Stripe Account, so we switch to it.");
997 
998  $tmparray = explode('@', $thirdparty->array_options['options_stripeaccount']);
999  if (!empty($tmparray[1])) {
1000  $tmparray2 = explode(':', $tmparray[1]);
1001  if (!empty($tmparray2[3])) {
1002  $stripearrayofkeysbyenv = [
1003  0 => [
1004  "publishable_key" => $tmparray2[0],
1005  "secret_key" => $tmparray2[1]
1006  ],
1007  1 => [
1008  "publishable_key" => $tmparray2[2],
1009  "secret_key" => $tmparray2[3]
1010  ]
1011  ];
1012 
1013  $stripearrayofkeys = $stripearrayofkeysbyenv[$servicestatus];
1014  \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1015 
1016  $foundalternativestripeaccount = $tmparray[0]; // Store the customer id
1017 
1018  dol_syslog("We use now customer=" . $foundalternativestripeaccount . " publishable_key=" . $stripearrayofkeys['publishable_key'], LOG_DEBUG);
1019  }
1020  }
1021 
1022  if (!$foundalternativestripeaccount) {
1023  $stripearrayofkeysbyenv = $savstripearrayofkeysbyenv;
1024 
1025  $stripearrayofkeys = $savstripearrayofkeysbyenv[$servicestatus];
1026  \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1027  dol_syslog("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);
1028  }
1029  } else {
1030  $stripearrayofkeysbyenv = $savstripearrayofkeysbyenv;
1031 
1032  $stripearrayofkeys = $savstripearrayofkeysbyenv[$servicestatus];
1033  \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1034  dol_syslog("The thirdparty id=" . $thirdparty->id . " has no dedicated Stripe Account, so we use global one, so " . json_encode($stripearrayofkeys), LOG_DEBUG);
1035  }
1036 
1037 
1038  dol_syslog("makeStripeSepaRequest get stripe account", LOG_DEBUG);
1039  $stripeacc = $stripe->getStripeAccount($service, $this->socid); // Get Stripe OAuth connect account if it exists (no network access here)
1040  dol_syslog("makeStripeSepaRequest get stripe account return " . json_encode($stripeacc), LOG_DEBUG);
1041 
1042  if ($foundalternativestripeaccount) {
1043  if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
1044  $customer = \Stripe\Customer::retrieve(['id' => "$foundalternativestripeaccount", 'expand[]' => 'sources']);
1045  } else {
1046  $customer = \Stripe\Customer::retrieve(['id' => "$foundalternativestripeaccount", 'expand[]' => 'sources'], ["stripe_account" => $stripeacc]);
1047  }
1048  } else {
1049  $customer = $stripe->customerStripe($thirdparty, $stripeacc, $servicestatus, 0);
1050  if (empty($customer) && !empty($stripe->error)) {
1051  $this->errors[] = $stripe->error;
1052  }
1053  /*if (!empty($customer) && empty($customer->sources)) {
1054  $customer = null;
1055  $this->errors[] = '\Stripe\Customer::retrieve did not returned the sources';
1056  }*/
1057  }
1058 
1059  // $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)
1060  // $nbdaysbeforeendoftries = (empty($conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES) ? 35 : $conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES);
1061  $labeltouse = '';
1062  $postactionmessages = [];
1063 
1064  if ($resultthirdparty > 0 && !empty($customer)) {
1065  if (!$error && !empty($this->array_options['options_delayautopayment']) && $this->array_options['options_delayautopayment'] > $now && empty($calledinmyaccountcontext)) {
1066  $errmsg = 'Payment try was canceled (invoice qualified by the automatic payment was delayed after the ' . dol_print_date($this->array_options['options_delayautopayment'], 'day') . ')';
1067  dol_syslog($errmsg, LOG_DEBUG);
1068 
1069  $error++;
1070  $errorforinvoice++;
1071  $this->errors[] = $errmsg;
1072  }
1073 
1074  if (!$error) { // Payment was not canceled
1075  //erics card or sepa ?
1076  $sepaMode = false;
1077  if ($companypaymentmode->type == 'ban') {
1078  $sepaMode = true;
1079  $stripecard = $stripe->sepaStripe($customer, $companypaymentmode, $stripeacc, $servicestatus, 0);
1080  } else {
1081  $stripecard = $stripe->cardStripe($customer, $companypaymentmode, $stripeacc, $servicestatus, 0);
1082  }
1083 
1084  if ($stripecard) { // Can be card_... (old mode) or pm_... (new mode)
1085  $FULLTAG = 'INV=' . $this->id . '-CUS=' . $thirdparty->id;
1086  $description = 'Stripe payment from doTakePaymentStripeForThirdparty: ' . $FULLTAG . ' ref=' . $this->ref;
1087 
1088  $stripefailurecode = '';
1089  $stripefailuremessage = '';
1090  $stripefailuredeclinecode = '';
1091 
1092  if (preg_match('/^card_/', $stripecard->id)) { // Using old method
1093  dol_syslog("* Create charge on card " . $stripecard->id . ", amountstripe=" . $amountstripe . ", FULLTAG=" . $FULLTAG, LOG_DEBUG);
1094 
1095  $ipaddress = getUserRemoteIP();
1096 
1097  $charge = null; // Force reset of $charge, so, if already set from a previous fetch, it will be empty even if there is an exception at next step
1098  try {
1099  $charge = \Stripe\Charge::create([
1100  'amount' => price2num($amountstripe, 'MU'),
1101  'currency' => $currency,
1102  'capture' => true, // Charge immediatly
1103  'description' => $description,
1104  'metadata' => ["FULLTAG" => $FULLTAG, 'Recipient' => $mysoc->name, 'dol_version' => DOL_VERSION, 'dol_entity' => $conf->entity, 'ipaddress' => $ipaddress],
1105  'customer' => $customer->id,
1106  //'customer' => 'bidon_to_force_error', // To use to force a stripe error
1107  'source' => $stripecard,
1108  'statement_descriptor' => dol_trunc('INV=' . $this->id, 10, 'right', 'UTF-8', 1), // 22 chars that appears on bank receipt (company + description)
1109  ]);
1110  } catch (\Stripe\Error\Card $e) {
1111  // Since it's a decline, Stripe_CardError will be caught
1112  $body = $e->getJsonBody();
1113  $err = $body['error'];
1114 
1115  $stripefailurecode = $err['code'];
1116  $stripefailuremessage = $err['message'];
1117  $stripefailuredeclinecode = $err['decline_code'];
1118  } catch (Exception $e) {
1119  $stripefailurecode = 'UnknownChargeError';
1120  $stripefailuremessage = $e->getMessage();
1121  }
1122  } else { // Using new SCA method
1123  if ($sepaMode) {
1124  dol_syslog("* Create payment on SEPA " . $stripecard->id . ", amounttopay=" . $amounttopay . ", amountstripe=" . $amountstripe . ", FULLTAG=" . $FULLTAG, LOG_DEBUG);
1125  } else {
1126  dol_syslog("* Create payment on card " . $stripecard->id . ", amounttopay=" . $amounttopay . ", amountstripe=" . $amountstripe . ", FULLTAG=" . $FULLTAG, LOG_DEBUG);
1127  }
1128 
1129  // Create payment intent and charge payment (confirmnow = true)
1130  $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $invoice, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1);
1131 
1132  $charge = new stdClass();
1133  //erics add processing sepa is like success ?
1134  if ($paymentintent->status === 'succeeded' || $paymentintent->status === 'processing') {
1135  $charge->status = 'ok';
1136  $charge->id = $paymentintent->id;
1137  $charge->customer = $customer->id;
1138  } elseif ($paymentintent->status === 'requires_action') {
1139  //paymentintent->status may be => 'requires_action' (no error in such a case)
1140  dol_syslog(var_export($paymentintent, true), LOG_DEBUG);
1141 
1142  $charge->status = 'failed';
1143  $charge->customer = $customer->id;
1144  $charge->failure_code = $stripe->code;
1145  $charge->failure_message = $stripe->error;
1146  $charge->failure_declinecode = $stripe->declinecode;
1147  $stripefailurecode = $stripe->code;
1148  $stripefailuremessage = 'Action required. Contact the support at ';// . $conf->global->SELLYOURSAAS_MAIN_EMAIL;
1149  $stripefailuredeclinecode = $stripe->declinecode;
1150  } else {
1151  dol_syslog(var_export($paymentintent, true), LOG_DEBUG);
1152 
1153  $charge->status = 'failed';
1154  $charge->customer = $customer->id;
1155  $charge->failure_code = $stripe->code;
1156  $charge->failure_message = $stripe->error;
1157  $charge->failure_declinecode = $stripe->declinecode;
1158  $stripefailurecode = $stripe->code;
1159  $stripefailuremessage = $stripe->error;
1160  $stripefailuredeclinecode = $stripe->declinecode;
1161  }
1162 
1163  //var_dump("stripefailurecode=".$stripefailurecode." stripefailuremessage=".$stripefailuremessage." stripefailuredeclinecode=".$stripefailuredeclinecode);
1164  //exit;
1165  }
1166 
1167  // Return $charge = array('id'=>'ch_XXXX', 'status'=>'succeeded|pending|failed', 'failure_code'=>, 'failure_message'=>...)
1168  if (empty($charge) || $charge->status == 'failed') {
1169  dol_syslog('Failed to charge card or payment mode ' . $stripecard->id . ' stripefailurecode=' . $stripefailurecode . ' stripefailuremessage=' . $stripefailuremessage . ' stripefailuredeclinecode=' . $stripefailuredeclinecode, LOG_WARNING);
1170 
1171  // Save a stripe payment was in error
1172  $this->stripechargeerror++;
1173 
1174  $error++;
1175  $errorforinvoice++;
1176  $errmsg = $langs->trans("FailedToChargeCard");
1177  if (!empty($charge)) {
1178  if ($stripefailuredeclinecode == 'authentication_required') {
1179  $errauthenticationmessage = $langs->trans("ErrSCAAuthentication");
1180  $errmsg = $errauthenticationmessage;
1181  } elseif (in_array($stripefailuredeclinecode, ['insufficient_funds', 'generic_decline'])) {
1182  $errmsg .= ': ' . $charge->failure_code;
1183  $errmsg .= ($charge->failure_message ? ' - ' : '') . ' ' . $charge->failure_message;
1184  if (empty($stripefailurecode)) {
1185  $stripefailurecode = $charge->failure_code;
1186  }
1187  if (empty($stripefailuremessage)) {
1188  $stripefailuremessage = $charge->failure_message;
1189  }
1190  } else {
1191  $errmsg .= ': failure_code=' . $charge->failure_code;
1192  $errmsg .= ($charge->failure_message ? ' - ' : '') . ' failure_message=' . $charge->failure_message;
1193  if (empty($stripefailurecode)) {
1194  $stripefailurecode = $charge->failure_code;
1195  }
1196  if (empty($stripefailuremessage)) {
1197  $stripefailuremessage = $charge->failure_message;
1198  }
1199  }
1200  } else {
1201  $errmsg .= ': ' . $stripefailurecode . ' - ' . $stripefailuremessage;
1202  $errmsg .= ($stripefailuredeclinecode ? ' - ' . $stripefailuredeclinecode : '');
1203  }
1204 
1205  $description = 'Stripe payment ERROR from doTakePaymentStripeForThirdparty: ' . $FULLTAG;
1206  $postactionmessages[] = $errmsg . ' (' . $stripearrayofkeys['publishable_key'] . ')';
1207  $this->errors[] = $errmsg;
1208  } else {
1209  dol_syslog('Successfuly charge card ' . $stripecard->id);
1210 
1211  $postactionmessages[] = 'Success to charge card (' . $charge->id . ' with ' . $stripearrayofkeys['publishable_key'] . ')';
1212 
1213  // Save a stripe payment was done in realy life so later we will be able to force a commit on recorded payments
1214  // even if in batch mode (method doTakePaymentStripe), we will always make all action in one transaction with a forced commit.
1215  $this->stripechargedone++;
1216 
1217  // Default description used for label of event. Will be overwrite by another value later.
1218  $description = 'Stripe payment OK (' . $charge->id . ') from doTakePaymentStripeForThirdparty: ' . $FULLTAG;
1219 
1220  $db = $this->db;
1221 
1222  $ipaddress = getUserRemoteIP();
1223 
1224  $TRANSACTIONID = $charge->id;
1225  $currency = $conf->currency;
1226  $paymentmethod = 'stripe';
1227  $emetteur_name = $charge->customer;
1228 
1229  // Same code than into paymentok.php...
1230 
1231  $paymentTypeId = 0;
1232  if ($paymentmethod == 'paybox') {
1233  $paymentTypeId = $conf->global->PAYBOX_PAYMENT_MODE_FOR_PAYMENTS;
1234  }
1235  if ($paymentmethod == 'paypal') {
1236  $paymentTypeId = $conf->global->PAYPAL_PAYMENT_MODE_FOR_PAYMENTS;
1237  }
1238  if ($paymentmethod == 'stripe') {
1239  $paymentTypeId = $conf->global->STRIPE_PAYMENT_MODE_FOR_PAYMENTS;
1240  }
1241  if (empty($paymentTypeId)) {
1242  //erics
1243  if ($sepaMode) {
1244  $paymentType = 'PRE';
1245  } else {
1246  $paymentType = $_SESSION["paymentType"];
1247  if (empty($paymentType)) {
1248  $paymentType = 'CB';
1249  }
1250  }
1251  $paymentTypeId = dol_getIdFromCode($this->db, $paymentType, 'c_paiement', 'code', 'id', 1);
1252  }
1253 
1254  $currencyCodeType = $currency;
1255 
1256  $ispostactionok = 1;
1257 
1258  // Creation of payment line
1259  include_once DOL_DOCUMENT_ROOT . '/compta/paiement/class/paiement.class.php';
1260  $paiement = new Paiement($this->db);
1261  $paiement->datepaye = $now;
1262  $paiement->date = $now;
1263  if ($currencyCodeType == $conf->currency) {
1264  $paiement->amounts = [$this->id => $amounttopay]; // Array with all payments dispatching with invoice id
1265  } else {
1266  $paiement->multicurrency_amounts = [$this->id => $amounttopay]; // Array with all payments dispatching
1267 
1268  $postactionmessages[] = 'Payment was done in a different currency than currency expected of company';
1269  $ispostactionok = -1;
1270  // Not yet supported, so error
1271  $error++;
1272  $errorforinvoice++;
1273  }
1274  $paiement->paiementid = $paymentTypeId;
1275  $paiement->num_paiement = '';
1276  $paiement->num_payment = '';
1277  // Add a comment with keyword 'SellYourSaas' in text. Used by trigger.
1278  $paiement->note_public = 'StripeSepa payment ' . dol_print_date($now, 'standard') . ' using ' . $paymentmethod . ($ipaddress ? ' from ip ' . $ipaddress : '') . ' - Transaction ID = ' . $TRANSACTIONID;
1279  $paiement->note_private = 'StripeSepa payment ' . dol_print_date($now, 'standard') . ' using ' . $paymentmethod . ($ipaddress ? ' from ip ' . $ipaddress : '') . ' - Transaction ID = ' . $TRANSACTIONID;
1280  $paiement->ext_payment_id = $charge->id . ':' . $customer->id . '@' . $stripearrayofkeys['publishable_key'];
1281  $paiement->ext_payment_site = 'stripe';
1282 
1283  if (!$errorforinvoice) {
1284  dol_syslog('* Record payment for invoice id ' . $this->id . '. It includes closing of invoice and regenerating document');
1285 
1286  // This include closing invoices to 'paid' (and trigger including unsuspending) and regenerating document
1287  $paiement_id = $paiement->create($user, 1);
1288  if ($paiement_id < 0) {
1289  $postactionmessages[] = $paiement->error . ($paiement->error ? ' ' : '') . join("<br>\n", $paiement->errors);
1290  $ispostactionok = -1;
1291  $error++;
1292  $errorforinvoice++;
1293  } else {
1294  $postactionmessages[] = 'Payment created';
1295  }
1296 
1297  dol_syslog("The payment has been created for invoice id " . $this->id);
1298  }
1299 
1300  if (!$errorforinvoice && isModEnabled('banque')) {
1301  dol_syslog('* Add payment to bank');
1302 
1303  $bankaccountid = 0;
1304  if ($paymentmethod == 'paybox') {
1305  $bankaccountid = $conf->global->PAYBOX_BANK_ACCOUNT_FOR_PAYMENTS;
1306  }
1307  if ($paymentmethod == 'paypal') {
1308  $bankaccountid = $conf->global->PAYPAL_BANK_ACCOUNT_FOR_PAYMENTS;
1309  }
1310  if ($paymentmethod == 'stripe') {
1311  $bankaccountid = $conf->global->STRIPE_BANK_ACCOUNT_FOR_PAYMENTS;
1312  }
1313 
1314  if ($bankaccountid > 0) {
1315  $label = '(CustomerInvoicePayment)';
1316  if ($this->type == Facture::TYPE_CREDIT_NOTE) {
1317  $label = '(CustomerInvoicePaymentBack)';
1318  } // Refund of a credit note
1319  $result = $paiement->addPaymentToBank($user, 'payment', $label, $bankaccountid, $emetteur_name, '');
1320  if ($result < 0) {
1321  $postactionmessages[] = $paiement->error . ($paiement->error ? ' ' : '') . join("<br>\n", $paiement->errors);
1322  $ispostactionok = -1;
1323  $error++;
1324  $errorforinvoice++;
1325  } else {
1326  $postactionmessages[] = 'Bank transaction of payment created (by doTakePaymentStripeForThirdparty)';
1327  }
1328  } else {
1329  $postactionmessages[] = 'Setup of bank account to use in module ' . $paymentmethod . ' was not set. No way to record the payment.';
1330  $ispostactionok = -1;
1331  $error++;
1332  $errorforinvoice++;
1333  }
1334  }
1335 
1336  if ($ispostactionok < 1) {
1337  $description = 'Stripe payment OK (' . $charge->id . ' - ' . $amounttopay . ' ' . $conf->currency . ') but post action KO from doTakePaymentStripeForThirdparty: ' . $FULLTAG;
1338  } else {
1339  $description = 'Stripe payment+post action OK (' . $charge->id . ' - ' . $amounttopay . ' ' . $conf->currency . ') from doTakePaymentStripeForThirdparty: ' . $FULLTAG;
1340  }
1341  }
1342 
1343  $object = $invoice;
1344 
1345  // Send emails
1346  $labeltouse = 'InvoicePaymentSuccess';
1347  $sendemailtocustomer = 1;
1348 
1349  if (empty($charge) || $charge->status == 'failed') {
1350  $labeltouse = 'InvoicePaymentFailure';
1351  if ($noemailtocustomeriferror) {
1352  $sendemailtocustomer = 0;
1353  } // $noemailtocustomeriferror is set when error already reported on myaccount screen
1354  }
1355 
1356  // Track an event
1357  if (empty($charge) || $charge->status == 'failed') {
1358  $actioncode = 'PAYMENT_STRIPE_KO';
1359  $extraparams = $stripefailurecode;
1360  $extraparams .= (($extraparams && $stripefailuremessage) ? ' - ' : '') . $stripefailuremessage;
1361  $extraparams .= (($extraparams && $stripefailuredeclinecode) ? ' - ' : '') . $stripefailuredeclinecode;
1362  } else {
1363  $actioncode = 'PAYMENT_STRIPE_OK';
1364  $extraparams = '';
1365  }
1366  } else {
1367  $error++;
1368  $errorforinvoice++;
1369  dol_syslog("No card or payment method found for this stripe customer " . $customer->id, LOG_WARNING);
1370  $this->errors[] = 'Failed to get card | payment method for stripe customer = ' . $customer->id;
1371 
1372  $labeltouse = 'InvoicePaymentFailure';
1373  $sendemailtocustomer = 1;
1374  if ($noemailtocustomeriferror) {
1375  $sendemailtocustomer = 0;
1376  } // $noemailtocustomeriferror is set when error already reported on myaccount screen
1377 
1378  $description = 'Failed to find or use the payment mode - no credit card defined for the customer account';
1379  $stripefailurecode = 'BADPAYMENTMODE';
1380  $stripefailuremessage = 'Failed to find or use the payment mode - no credit card defined for the customer account';
1381  $postactionmessages[] = $description . ' (' . $stripearrayofkeys['publishable_key'] . ')';
1382 
1383  $object = $invoice;
1384 
1385  $actioncode = 'PAYMENT_STRIPE_KO';
1386  $extraparams = '';
1387  }
1388  } else {
1389  // If error because payment was canceled for a logical reason, we do nothing (no email and no event added)
1390  $labeltouse = '';
1391  $sendemailtocustomer = 0;
1392 
1393  $description = '';
1394  $stripefailurecode = '';
1395  $stripefailuremessage = '';
1396 
1397  $object = $invoice;
1398 
1399  $actioncode = '';
1400  $extraparams = '';
1401  }
1402  } else { // Else of the if ($resultthirdparty > 0 && ! empty($customer)) {
1403  if ($resultthirdparty <= 0) {
1404  dol_syslog('SellYourSaasUtils Failed to load customer for thirdparty_id = ' . $thirdparty->id, LOG_WARNING);
1405  $this->errors[] = 'Failed to load customer for thirdparty_id = ' . $thirdparty->id;
1406  } else { // $customer stripe not found
1407  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);
1408  $this->errors[] = 'Failed to get Stripe customer id for thirdparty_id = ' . $thirdparty->id . " in mode " . $servicestatus . " in Stripe env " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key'];
1409  }
1410  $error++;
1411  $errorforinvoice++;
1412 
1413  $labeltouse = 'InvoicePaymentFailure';
1414  $sendemailtocustomer = 1;
1415  if ($noemailtocustomeriferror) {
1416  $sendemailtocustomer = 0;
1417  } // $noemailtocustomeriferror is set when error already reported on myaccount screen
1418 
1419  $description = 'Failed to find or use your payment mode (no payment mode for this customer id)';
1420  $stripefailurecode = 'BADPAYMENTMODE';
1421  $stripefailuremessage = 'Failed to find or use your payment mode (no payment mode for this customer id)';
1422  $postactionmessages = [];
1423 
1424  $object = $invoice;
1425 
1426  $actioncode = 'PAYMENT_STRIPE_KO';
1427  $extraparams = '';
1428  }
1429 
1430  // Send email + create action after
1431  if ($sendemailtocustomer && $labeltouse) {
1432  dol_syslog("* Send email with result of payment - " . $labeltouse);
1433 
1434  // Set output language
1435  $outputlangs = new Translate('', $conf);
1436  $outputlangs->setDefaultLang(empty($object->thirdparty->default_lang) ? $mysoc->default_lang : $object->thirdparty->default_lang);
1437  $outputlangs->loadLangs(["main", "members", "bills"]);
1438 
1439  // Get email content from templae
1440  $arraydefaultmessage = null;
1441 
1442  include_once DOL_DOCUMENT_ROOT . '/core/class/html.formmail.class.php';
1443  $formmail = new FormMail($this->db);
1444 
1445  if (!empty($labeltouse)) {
1446  $arraydefaultmessage = $formmail->getEMailTemplate($this->db, 'facture_send', $user, $outputlangs, 0, 1, $labeltouse);
1447  }
1448 
1449  if (!empty($labeltouse) && is_object($arraydefaultmessage) && $arraydefaultmessage->id > 0) {
1450  $subject = $arraydefaultmessage->topic;
1451  $msg = $arraydefaultmessage->content;
1452  }
1453 
1454  $substitutionarray = getCommonSubstitutionArray($outputlangs, 0, null, $object);
1455 
1456  //$substitutionarray['__SELLYOURSAAS_PAYMENT_ERROR_DESC__'] = $stripefailurecode . ' ' . $stripefailuremessage;
1457 
1458  complete_substitutions_array($substitutionarray, $outputlangs, $object);
1459 
1460  // Set the property ->ref_customer with ref_customer of contract so __REF_CLIENT__ will be replaced in email content
1461  // Search contract linked to invoice
1462  $foundcontract = null;
1463  $this->fetchObjectLinked();
1464  if (is_array($this->linkedObjects['contrat']) && count($this->linkedObjects['contrat']) > 0) {
1465  //dol_sort_array($object->linkedObjects['facture'], 'date');
1466  foreach ($this->linkedObjects['contrat'] as $idcontract => $contract) {
1467  $substitutionarray['__CONTRACT_REF__'] = $contract->ref_customer;
1468  $substitutionarray['__REFCLIENT__'] = $contract->ref_customer; // For backward compatibility
1469  $substitutionarray['__REF_CLIENT__'] = $contract->ref_customer;
1470  $foundcontract = $contract;
1471  break;
1472  }
1473  }
1474 
1475  dol_syslog('__DIRECTDOWNLOAD_URL_INVOICE__=' . $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__']);
1476 
1477  //erics - erreur de réécriture de l'url de téléchargement direct de la facture ... le lien de base est le bon
1478  //on cherche donc d'ou vien le pb ...
1479  //$urlforsellyoursaasaccount = getRootUrlForAccount($foundcontract);
1480  // if ($urlforsellyoursaasaccount) {
1481  // $tmpforurl = preg_replace('/.*document.php/', '', $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__']);
1482  // if ($tmpforurl) {
1483  // dol_syslog('__DIRECTDOWNLOAD_URL_INVOICE__ cas 1, urlforsellyoursaasaccount=' . $urlforsellyoursaasaccount);
1484  // // $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = $urlforsellyoursaasaccount . '/source/document.php' . $tmpforurl;
1485  // } else {
1486  // dol_syslog('__DIRECTDOWNLOAD_URL_INVOICE__ cas 2, urlforsellyoursaasaccount=' . $urlforsellyoursaasaccount);
1487  // // $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = $urlforsellyoursaasaccount;
1488  // }
1489  // }
1490 
1491  $subjecttosend = make_substitutions($subject, $substitutionarray, $outputlangs);
1492  $texttosend = make_substitutions($msg, $substitutionarray, $outputlangs);
1493 
1494  // Attach a file ?
1495  $file = '';
1496  $listofpaths = [];
1497  $listofnames = [];
1498  $listofmimes = [];
1499  if (is_object($invoice)) {
1500  $invoicediroutput = $conf->facture->dir_output;
1501  //erics - choix du PDF a joindre aux mails
1502  $fileparams = dol_most_recent_file($invoicediroutput . '/' . $this->ref, preg_quote($this->ref, '/') . '[^\-]+*.pdf');
1503  $file = $fileparams['fullname'];
1504  //$file = $invoicediroutput . '/' . $this->ref . '/' . $this->ref . '.pdf';
1505  // $file = ''; // Disable attachment of invoice in emails
1506 
1507  if ($file) {
1508  $listofpaths = [$file];
1509  $listofnames = [basename($file)];
1510  $listofmimes = [dol_mimetype($file)];
1511  }
1512  }
1513  $from = "";//$conf->global->SELLYOURSAAS_NOREPLY_EMAIL;
1514 
1515  $trackid = 'inv' . $this->id;
1516  $moreinheader = 'X-Dolibarr-Info: makeStripeSepaRequest' . "\r\n";
1517 
1518  // Send email (substitutionarray must be done just before this)
1519  include_once DOL_DOCUMENT_ROOT . '/core/class/CMailFile.class.php';
1520  $mailfile = new CMailFile($subjecttosend, $this->thirdparty->email, $from, $texttosend, $listofpaths, $listofmimes, $listofnames, '', '', 0, -1, '', '', $trackid, $moreinheader);
1521  if ($mailfile->sendfile()) {
1522  $result = 1;
1523  } else {
1524  $this->error = $langs->trans("ErrorFailedToSendMail", $from, $this->thirdparty->email) . '. ' . $mailfile->error;
1525  $result = -1;
1526  }
1527 
1528  if ($result < 0) {
1529  $errmsg = $this->error;
1530  $postactionmessages[] = $errmsg;
1531  $ispostactionok = -1;
1532  } else {
1533  if ($file) {
1534  $postactionmessages[] = 'Email sent to thirdparty (to ' . $this->thirdparty->email . ' with invoice document attached: ' . $file . ', language = ' . $outputlangs->defaultlang . ')';
1535  } else {
1536  $postactionmessages[] = 'Email sent to thirdparty (to ' . $this->thirdparty->email . ' without any attached document, language = ' . $outputlangs->defaultlang . ')';
1537  }
1538  }
1539  }
1540 
1541  if ($description) {
1542  dol_syslog("* Record event for payment result - " . $description);
1543  require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
1544 
1545  // Insert record of payment (success or error)
1546  $actioncomm = new ActionComm($this->db);
1547 
1548  $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
1549  $actioncomm->code = 'AC_' . $actioncode;
1550  $actioncomm->label = $description;
1551  $actioncomm->note_private = join(",\n", $postactionmessages);
1552  $actioncomm->fk_project = $this->fk_project;
1553  $actioncomm->datep = $now;
1554  $actioncomm->datef = $now;
1555  $actioncomm->percentage = -1; // Not applicable
1556  $actioncomm->socid = $thirdparty->id;
1557  $actioncomm->contactid = 0;
1558  $actioncomm->authorid = $user->id; // User saving action
1559  $actioncomm->userownerid = $user->id; // Owner of action
1560  // Fields when action is a real email (content is already into note)
1561  /*$actioncomm->email_msgid = $object->email_msgid;
1562  $actioncomm->email_from = $object->email_from;
1563  $actioncomm->email_sender= $object->email_sender;
1564  $actioncomm->email_to = $object->email_to;
1565  $actioncomm->email_tocc = $object->email_tocc;
1566  $actioncomm->email_tobcc = $object->email_tobcc;
1567  $actioncomm->email_subject = $object->email_subject;
1568  $actioncomm->errors_to = $object->errors_to;*/
1569  $actioncomm->fk_element = $this->id;
1570  $actioncomm->elementtype = $this->element;
1571  $actioncomm->extraparams = dol_trunc($extraparams, 250);
1572 
1573  $actioncomm->create($user);
1574  }
1575 
1576  $this->description = $description;
1577  $this->postactionmessages = $postactionmessages;
1578  } catch (Exception $e) {
1579  $error++;
1580  $errorforinvoice++;
1581  dol_syslog('Error ' . $e->getMessage(), LOG_ERR);
1582  $this->errors[] = 'Error ' . $e->getMessage();
1583  }
1584  } else { // If remain to pay is null
1585  $error++;
1586  $errorforinvoice++;
1587  dol_syslog("Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?", LOG_WARNING);
1588  $this->errors[] = "Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?";
1589  }
1590 
1591  $sql = "INSERT INTO ".MAIN_DB_PREFIX."prelevement_demande(";
1592  $sql .= "fk_facture, ";
1593  $sql .= " amount, date_demande, fk_user_demande, ext_payment_id, ext_payment_site, sourcetype, entity)";
1594  $sql .= " VALUES (".$this->id;
1595  $sql .= ",".((float) price2num($amount));
1596  $sql .= ",'".$this->db->idate($now)."'";
1597  $sql .= ",".((int) $fuser->id);
1598  $sql .= ",'".$this->db->escape($stripe_id)."'";
1599  $sql .= ",'".$this->db->escape($stripe_uri)."'";
1600  $sql .= ",'".$this->db->escape($sourcetype)."'";
1601  $sql .= ",".$conf->entity;
1602  $sql .= ")";
1603 
1604  dol_syslog(get_class($this)."::makeStripeSepaRequest", LOG_DEBUG);
1605  $resql = $this->db->query($sql);
1606  if (!$resql) {
1607  $this->error = $this->db->lasterror();
1608  dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur');
1609  $error++;
1610  }
1611  } else {
1612  $this->error = 'WithdrawRequestErrorNilAmount';
1613  dol_syslog(get_class($this).'::makeStripeSepaRequest WithdrawRequestErrorNilAmount');
1614  $error++;
1615  }
1616 
1617  if (!$error) {
1618  // Force payment mode of invoice to withdraw
1619  $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
1620  if ($payment_mode_id > 0) {
1621  $result = $this->setPaymentMethods($payment_mode_id);
1622  }
1623  }
1624 
1625  if ($error) {
1626  return -1;
1627  }
1628  return 1;
1629  } else {
1630  $this->error = $this->db->error();
1631  dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur -2');
1632  return -2;
1633  }
1634  } else {
1635  $this->error = "Status of invoice does not allow this";
1636  dol_syslog(get_class($this)."::makeStripeSepaRequest ".$this->error." $this->statut, $this->paye, $this->mode_reglement_id");
1637  return -3;
1638  }
1639  }
1640 
1641  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1649  public function demande_prelevement_delete($fuser, $did)
1650  {
1651  // phpcs:enable
1652  $sql = 'DELETE FROM '.$this->db->prefix().'prelevement_demande';
1653  $sql .= ' WHERE rowid = '.((int) $did);
1654  $sql .= ' AND traite = 0';
1655  if ($this->db->query($sql)) {
1656  return 0;
1657  } else {
1658  $this->error = $this->db->lasterror();
1659  dol_syslog(get_class($this).'::demande_prelevement_delete Error '.$this->error);
1660  return -1;
1661  }
1662  }
1663 
1664 
1670  public function buildZATCAQRString()
1671  {
1672  global $conf, $mysoc;
1673 
1674  $tmplang = new Translate('', $conf);
1675  $tmplang->setDefaultLang('en_US');
1676  $tmplang->load("main");
1677 
1678  $datestring = dol_print_date($this->date, 'dayhourrfc');
1679  //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
1680  //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
1681  $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
1682  $pricetaxstring = price2num($this->total_tva, 2, 1);
1683 
1684  /*
1685  $name = implode(unpack("H*", $this->thirdparty->name));
1686  $vatnumber = implode(unpack("H*", $this->thirdparty->tva_intra));
1687  $date = implode(unpack("H*", $datestring));
1688  $pricewithtax = implode(unpack("H*", price2num($pricewithtaxstring, 2)));
1689  $pricetax = implode(unpack("H*", $pricetaxstring));
1690 
1691  //var_dump(strlen($this->thirdparty->name));
1692  //var_dump(str_pad(dechex('9'), 2, '0', STR_PAD_LEFT));
1693  //var_dump($this->thirdparty->name);
1694  //var_dump(implode(unpack("H*", $this->thirdparty->name)));
1695  //var_dump(price($this->total_tva, 0, $tmplang, 0, -1, 2));
1696 
1697  $s = '01'.str_pad(dechex(strlen($this->thirdparty->name)), 2, '0', STR_PAD_LEFT).$name;
1698  $s .= '02'.str_pad(dechex(strlen($this->thirdparty->tva_intra)), 2, '0', STR_PAD_LEFT).$vatnumber;
1699  $s .= '03'.str_pad(dechex(strlen($datestring)), 2, '0', STR_PAD_LEFT).$date;
1700  $s .= '04'.str_pad(dechex(strlen($pricewithtaxstring)), 2, '0', STR_PAD_LEFT).$pricewithtax;
1701  $s .= '05'.str_pad(dechex(strlen($pricetaxstring)), 2, '0', STR_PAD_LEFT).$pricetax;
1702  $s .= ''; // Hash of xml invoice
1703  $s .= ''; // ecda signature
1704  $s .= ''; // ecda public key
1705  $s .= ''; // ecda signature of public key stamp
1706  */
1707 
1708  // Using TLV format
1709  $s = pack('C1', 1).pack('C1', strlen($mysoc->name)).$mysoc->name;
1710  $s .= pack('C1', 2).pack('C1', strlen($mysoc->tva_intra)).$mysoc->tva_intra;
1711  $s .= pack('C1', 3).pack('C1', strlen($datestring)).$datestring;
1712  $s .= pack('C1', 4).pack('C1', strlen($pricewithtaxstring)).$pricewithtaxstring;
1713  $s .= pack('C1', 5).pack('C1', strlen($pricetaxstring)).$pricetaxstring;
1714  $s .= ''; // Hash of xml invoice
1715  $s .= ''; // ecda signature
1716  $s .= ''; // ecda public key
1717  $s .= ''; // ecda signature of public key stamp
1718 
1719  $s = base64_encode($s);
1720 
1721  return $s;
1722  }
1723 
1724 
1730  public function buildSwitzerlandQRString()
1731  {
1732  global $conf, $mysoc;
1733 
1734  $tmplang = new Translate('', $conf);
1735  $tmplang->setDefaultLang('en_US');
1736  $tmplang->load("main");
1737 
1738  $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
1739  $pricetaxstring = price2num($this->total_tva, 2, 1);
1740 
1741  $complementaryinfo = '';
1742  /*
1743  Example: //S1/10/10201409/11/190512/20/1400.000-53/30/106017086/31/180508/32/7.7/40/2:10;0:30
1744  /10/ Numéro de facture – 10201409
1745  /11/ Date de facture – 12.05.2019
1746  /20/ Référence client – 1400.000-53
1747  /30/ Numéro IDE pour la TVA – CHE-106.017.086 TVA
1748  /31/ Date de la prestation pour la comptabilisation de la TVA – 08.05.2018
1749  /32/ Taux de TVA sur le montant total de la facture – 7.7%
1750  /40/ Conditions – 2% d’escompte à 10 jours, paiement net à 30 jours
1751  */
1752  $datestring = dol_print_date($this->date, '%y%m%d');
1753  //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
1754  //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
1755  $complementaryinfo = '//S1/10/'.str_replace('/', '', $this->ref).'/11/'.$datestring;
1756  if ($this->ref_client) {
1757  $complementaryinfo .= '/20/'.$this->ref_client;
1758  }
1759  if ($this->thirdparty->tva_intra) {
1760  $complementaryinfo .= '/30/'.$this->thirdparty->tva_intra;
1761  }
1762 
1763  // Header
1764  $s = '';
1765  $s .= "SPC\n";
1766  $s .= "0200\n";
1767  $s .= "1\n";
1768  if ($this->fk_account > 0) {
1769  // Bank BAN if country is LI or CH
1770  // TODO Add
1771  $bankaccount = new Account($this->db);
1772  $bankaccount->fetch($this->fk_account);
1773  $s .= $bankaccount->iban."\n";
1774  } else {
1775  $s .= "\n";
1776  }
1777  // Seller
1778  $s .= "S\n";
1779  $s .= dol_trunc($mysoc->name, 70, 'right', 'UTF-8', 1)."\n";
1780  $addresslinearray = explode("\n", $mysoc->address);
1781  $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
1782  $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
1783  $s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
1784  $s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
1785  $s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";
1786  // Final seller
1787  $s .= "\n";
1788  $s .= "\n";
1789  $s .= "\n";
1790  $s .= "\n";
1791  $s .= "\n";
1792  $s .= "\n";
1793  $s .= "\n";
1794  // Amount of payment (to do?)
1795  $s .= price($pricewithtaxstring, 0, 'none', 0, 0, 2)."\n";
1796  $s .= ($this->multicurrency_code ? $this->multicurrency_code : $conf->currency)."\n";
1797  // Buyer
1798  $s .= "S\n";
1799  $s .= dol_trunc($this->thirdparty->name, 70, 'right', 'UTF-8', 1)."\n";
1800  $addresslinearray = explode("\n", $this->thirdparty->address);
1801  $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
1802  $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
1803  $s .= dol_trunc($this->thirdparty->zip, 16, 'right', 'UTF-8', 1)."\n";
1804  $s .= dol_trunc($this->thirdparty->town, 35, 'right', 'UTF-8', 1)."\n";
1805  $s .= dol_trunc($this->thirdparty->country_code, 2, 'right', 'UTF-8', 1)."\n";
1806  // ID of payment
1807  $s .= "NON\n"; // NON or QRR
1808  $s .= "\n"; // QR Code if previous field is QRR
1809  if ($complementaryinfo) {
1810  $s .= $complementaryinfo."\n";
1811  } else {
1812  $s .= "\n";
1813  }
1814  $s .= "EPD\n";
1815  $s .= "\n";
1816  //var_dump($s);exit;
1817  return $s;
1818  }
1819 }
1820 
1821 
1822 
1823 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
1824 
1828 abstract class CommonInvoiceLine extends CommonObjectLine
1829 {
1834  public $label;
1835 
1840  public $ref; // Product ref (deprecated)
1845  public $libelle; // Product label (deprecated)
1846 
1851  public $product_type = 0;
1852 
1857  public $product_ref;
1858 
1863  public $product_label;
1864 
1869  public $product_desc;
1870 
1875  public $qty;
1876 
1881  public $subprice;
1882 
1888  public $price;
1889 
1894  public $fk_product;
1895 
1900  public $vat_src_code;
1901 
1906  public $tva_tx;
1907 
1912  public $localtax1_tx;
1913 
1918  public $localtax2_tx;
1919 
1924  public $localtax1_type;
1925 
1930  public $localtax2_type;
1931 
1936  public $remise_percent;
1937 
1943  public $remise;
1944 
1949  public $total_ht;
1950 
1955  public $total_tva;
1956 
1961  public $total_localtax1;
1962 
1967  public $total_localtax2;
1968 
1973  public $total_ttc;
1974 
1975  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
1976  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
1977 
1978  public $buy_price_ht;
1979  public $buyprice; // For backward compatibility
1980  public $pa_ht; // For backward compatibility
1981 
1982  public $marge_tx;
1983  public $marque_tx;
1984 
1991  public $info_bits = 0;
1992 
1993  public $special_code = 0;
1994 
1995  public $fk_multicurrency;
1996  public $multicurrency_code;
1997  public $multicurrency_subprice;
1998  public $multicurrency_total_ht;
1999  public $multicurrency_total_tva;
2000  public $multicurrency_total_ttc;
2001 
2002  public $fk_user_author;
2003  public $fk_user_modif;
2004 
2005  public $fk_accounting_account;
2006 }
$object ref
Definition: info.php:78
Class to manage bank accounts.
Class to manage agenda events (actions)
Class to send emails (with attachments or not) Usage: $mailfile = new CMailFile($subject,...
Superclass for invoices classes.
const TYPE_CREDIT_NOTE
Credit note invoice.
demande_prelevement($fuser, $amount=0, $type='direct-debit', $sourcetype='facture')
Create a withdrawal request for a direct debit order or a credit transfer order.
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.
makeStripeSepaRequest($fuser, $did=0, $type='direct-debit', $sourcetype='facture')
Create a withdrawal request for a direct debit order or a credit transfer order.
buildZATCAQRString()
Build string for ZATCA QR Code (Arabi Saudia)
const TYPE_STANDARD
Standard invoice.
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...
getListOfPayments($filtertype='')
Return list of payments.
getLibStatut($mode=0, $alreadypaid=-1)
Return label of object status.
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.
fetchObjectLinked($sourceid=null, $sourcetype='', $targetid=null, $targettype='', $clause='OR', $alsosametype=1, $orderby='sourcetype', $loadalsoobjects=1)
Fetch array of objects linked to current object (object of enabled modules only).
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.
const TYPE_CREDIT_NOTE
Credit note invoice.
Classe permettant la generation du formulaire html d'envoi de mail unitaire Usage: $formail = new For...
Class to manage payments of customer invoices.
Class to manage third parties objects (customers, suppliers, prospects...)
Stripe class.
Class to manage translations.
trait CommonIncoterm
Superclass for incoterm classes.
if(isModEnabled('facture') &&!empty($user->rights->facture->lire)) if((isModEnabled('fournisseur') &&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') &&!empty($user->rights->don->lire)) if(isModEnabled('tax') &&!empty($user->rights->tax->charges->lire)) if(isModEnabled('facture') &&isModEnabled('commande') && $user->hasRight("commande", "lire") &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) $resql
Social contributions to pay.
Definition: index.php:745
dol_time_plus_duree($time, $duration_value, $duration_unit, $ruleforendofmonth=0)
Add a delay to a date.
Definition: date.lib.php:121
print *****$script_file(".$version.") pid cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
dol_most_recent_file($dir, $regexfilter='', $excludefilter=array('(\.meta|_preview.*\.png)$', '^\.'), $nohook=false, $mode='')
Return file(s) into a directory (by default most recent)
Definition: files.lib.php:2424
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed informations (by default a local PHP server timestamp) Re...
dol_mimetype($file, $default='application/octet-stream', $mode=0)
Return MIME type of a file from its name with extension.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
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_print_date($time, $format='', $tzoutput='auto', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
dol_now($mode='auto')
Return date for now.
dol_getIdFromCode($db, $key, $tablename, $fieldkey='code', $fieldid='id', $entityfilter=0, $filters='')
Return an id or code from a code or id.
complete_substitutions_array(&$substitutionarray, $outputlangs, $object=null, $parameters=null, $callfunc="completesubstitutionarray")
Complete the $substitutionarray with more entries coming from external module that had set the "subst...
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
make_substitutions($text, $substitutionarray, $outputlangs=null, $converttextinhtmlifnecessary=0)
Make substitution into a text string, replacing keys with vals from $substitutionarray (oldval=>newva...
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
getCommonSubstitutionArray($outputlangs, $onlykey=0, $exclude=null, $object=null)
Return array of possible common substitutions.
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.
getUserRemoteIP()
Return the IP of remote user.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
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:119
$conf db
API class for accounts.
Definition: inc.php:41