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