dolibarr  19.0.0-dev
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  public $type = self::TYPE_STANDARD;
40 
44  public $subtype;
45 
49  const TYPE_STANDARD = 0;
50 
54  const TYPE_REPLACEMENT = 1;
55 
59  const TYPE_CREDIT_NOTE = 2;
60 
64  const TYPE_DEPOSIT = 3;
65 
70  const TYPE_PROFORMA = 4;
71 
75  const TYPE_SITUATION = 5;
76 
80  const STATUS_DRAFT = 0;
81 
85  const STATUS_VALIDATED = 1;
86 
94  const STATUS_CLOSED = 2;
95 
103  const STATUS_ABANDONED = 3;
104 
105 
106  public $totalpaid; // duplicate with sumpayed
107  public $totaldeposits; // duplicate with sumdeposit
108  public $totalcreditnotes; // duplicate with sumcreditnote
109 
110  public $sumpayed;
111  public $sumpayed_multicurrency;
112  public $sumdeposit;
113  public $sumdeposit_multicurrency;
114  public $sumcreditnote;
115  public $sumcreditnote_multicurrency;
116  public $remaintopay;
117 
118 
126  public function getRemainToPay($multicurrency = 0)
127  {
128  $alreadypaid = 0.0;
129  $alreadypaid += $this->getSommePaiement($multicurrency);
130  $alreadypaid += $this->getSumDepositsUsed($multicurrency);
131  $alreadypaid += $this->getSumCreditNotesUsed($multicurrency);
132 
133  $remaintopay = price2num($this->total_ttc - $alreadypaid, 'MT');
134  if ($this->statut == self::STATUS_CLOSED && $this->close_code == 'discount_vat') { // If invoice closed with discount for anticipated payment
135  $remaintopay = 0.0;
136  }
137  return $remaintopay;
138  }
139 
147  public function getSommePaiement($multicurrency = 0)
148  {
149  $table = 'paiement_facture';
150  $field = 'fk_facture';
151  if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
152  $table = 'paiementfourn_facturefourn';
153  $field = 'fk_facturefourn';
154  }
155 
156  $sql = "SELECT sum(amount) as amount, sum(multicurrency_amount) as multicurrency_amount";
157  $sql .= " FROM ".$this->db->prefix().$table;
158  $sql .= " WHERE ".$field." = ".((int) $this->id);
159 
160  dol_syslog(get_class($this)."::getSommePaiement", LOG_DEBUG);
161 
162  $resql = $this->db->query($sql);
163  if ($resql) {
164  $obj = $this->db->fetch_object($resql);
165 
166  $this->db->free($resql);
167 
168  if ($obj) {
169  if ($multicurrency < 0) {
170  $this->sumpayed = $obj->amount;
171  $this->sumpayed_multicurrency = $obj->multicurrency_amount;
172  return array('alreadypaid'=>(float) $obj->amount, 'alreadypaid_multicurrency'=>(float) $obj->multicurrency_amount);
173  } elseif ($multicurrency) {
174  $this->sumpayed_multicurrency = $obj->multicurrency_amount;
175  return (float) $obj->multicurrency_amount;
176  } else {
177  $this->sumpayed = $obj->amount;
178  return (float) $obj->amount;
179  }
180  } else {
181  return 0;
182  }
183  } else {
184  $this->error = $this->db->lasterror();
185  return -1;
186  }
187  }
188 
197  public function getSumDepositsUsed($multicurrency = 0)
198  {
199  /*if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
200  // 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.
201  return 0.0;
202  }*/
203 
204  require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
205 
206  $discountstatic = new DiscountAbsolute($this->db);
207  $result = $discountstatic->getSumDepositsUsed($this, $multicurrency);
208 
209  if ($result >= 0) {
210  if ($multicurrency) {
211  $this->sumdeposit_multicurrency = $result;
212  } else {
213  $this->sumdeposit = $result;
214  }
215 
216  return $result;
217  } else {
218  $this->error = $discountstatic->error;
219  return -1;
220  }
221  }
222 
229  public function getSumCreditNotesUsed($multicurrency = 0)
230  {
231  require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
232 
233  $discountstatic = new DiscountAbsolute($this->db);
234  $result = $discountstatic->getSumCreditNotesUsed($this, $multicurrency);
235  if ($result >= 0) {
236  if ($multicurrency) {
237  $this->sumcreditnote_multicurrency = $result;
238  } else {
239  $this->sumcreditnote = $result;
240  }
241 
242  return $result;
243  } else {
244  $this->error = $discountstatic->error;
245  return -1;
246  }
247  }
248 
255  public function getSumFromThisCreditNotesNotUsed($multicurrency = 0)
256  {
257  require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
258 
259  $discountstatic = new DiscountAbsolute($this->db);
260  $result = $discountstatic->getSumFromThisCreditNotesNotUsed($this, $multicurrency);
261  if ($result >= 0) {
262  return $result;
263  } else {
264  $this->error = $discountstatic->error;
265  return -1;
266  }
267  }
268 
274  public function getListIdAvoirFromInvoice()
275  {
276  $idarray = array();
277 
278  $sql = "SELECT rowid";
279  $sql .= " FROM ".$this->db->prefix().$this->table_element;
280  $sql .= " WHERE fk_facture_source = ".((int) $this->id);
281  $sql .= " AND type = 2";
282  $resql = $this->db->query($sql);
283  if ($resql) {
284  $num = $this->db->num_rows($resql);
285  $i = 0;
286  while ($i < $num) {
287  $row = $this->db->fetch_row($resql);
288  $idarray[] = $row[0];
289  $i++;
290  }
291  } else {
292  dol_print_error($this->db);
293  }
294  return $idarray;
295  }
296 
303  public function getIdReplacingInvoice($option = '')
304  {
305  $sql = "SELECT rowid";
306  $sql .= " FROM ".$this->db->prefix().$this->table_element;
307  $sql .= " WHERE fk_facture_source = ".((int) $this->id);
308  $sql .= " AND type < 2";
309  if ($option == 'validated') {
310  $sql .= ' AND fk_statut = 1';
311  }
312  // PROTECTION BAD DATA
313  // In case the database is corrupted and there is a valid replectement invoice
314  // and another no, priority is given to the valid one.
315  // Should not happen (unless concurrent access and 2 people have created a
316  // replacement invoice for the same invoice at the same time)
317  $sql .= " ORDER BY fk_statut DESC";
318 
319  $resql = $this->db->query($sql);
320  if ($resql) {
321  $obj = $this->db->fetch_object($resql);
322  if ($obj) {
323  // If there is any
324  return $obj->rowid;
325  } else {
326  // If no invoice replaces it
327  return 0;
328  }
329  } else {
330  return -1;
331  }
332  }
333 
340  public function getListOfPayments($filtertype = '')
341  {
342  $retarray = array();
343 
344  $table = 'paiement_facture';
345  $table2 = 'paiement';
346  $field = 'fk_facture';
347  $field2 = 'fk_paiement';
348  $field3 = ', p.ref_ext';
349  $field4 = ', p.fk_bank'; // Bank line id
350  $sharedentity = 'facture';
351  if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
352  $table = 'paiementfourn_facturefourn';
353  $table2 = 'paiementfourn';
354  $field = 'fk_facturefourn';
355  $field2 = 'fk_paiementfourn';
356  $field3 = '';
357  $sharedentity = 'facture_fourn';
358  }
359 
360  $sql = "SELECT p.ref, pf.amount, pf.multicurrency_amount, p.fk_paiement, p.datep, p.num_paiement as num, t.code".$field3 . $field4;
361  $sql .= " FROM ".$this->db->prefix().$table." as pf, ".$this->db->prefix().$table2." as p, ".$this->db->prefix()."c_paiement as t";
362  $sql .= " WHERE pf.".$field." = ".((int) $this->id);
363  $sql .= " AND pf.".$field2." = p.rowid";
364  $sql .= ' AND p.fk_paiement = t.id';
365  $sql .= ' AND p.entity IN ('.getEntity($sharedentity).')';
366  if ($filtertype) {
367  $sql .= " AND t.code='PRE'";
368  }
369 
370  dol_syslog(get_class($this)."::getListOfPayments", LOG_DEBUG);
371  $resql = $this->db->query($sql);
372  if ($resql) {
373  $num = $this->db->num_rows($resql);
374  $i = 0;
375  while ($i < $num) {
376  $obj = $this->db->fetch_object($resql);
377  $tmp = array('amount'=>$obj->amount, 'type'=>$obj->code, 'date'=>$obj->datep, 'num'=>$obj->num, 'ref'=>$obj->ref);
378  if (!empty($field3)) {
379  $tmp['ref_ext'] = $obj->ref_ext;
380  }
381  if (!empty($field4)) {
382  $tmp['fk_bank_line'] = $obj->fk_bank;
383  }
384  $retarray[] = $tmp;
385  $i++;
386  }
387  $this->db->free($resql);
388 
389  //look for credit notes and discounts and deposits
390  $sql = '';
391  if ($this->element == 'facture' || $this->element == 'invoice') {
392  $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";
393  $sql .= ' FROM '.$this->db->prefix().'societe_remise_except as rc, '.$this->db->prefix().'facture as f';
394  $sql .= ' WHERE rc.fk_facture_source=f.rowid AND rc.fk_facture = '.((int) $this->id);
395  $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)
396  } elseif ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
397  $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";
398  $sql .= ' FROM '.$this->db->prefix().'societe_remise_except as rc, '.$this->db->prefix().'facture_fourn as f';
399  $sql .= ' WHERE rc.fk_invoice_supplier_source=f.rowid AND rc.fk_invoice_supplier = '.((int) $this->id);
400  $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)
401  }
402 
403  if ($sql) {
404  $resql = $this->db->query($sql);
405  if ($resql) {
406  $num = $this->db->num_rows($resql);
407  $i = 0;
408  while ($i < $num) {
409  $obj = $this->db->fetch_object($resql);
410  if ($multicurrency) {
411  $retarray[] = array('amount'=>$obj->multicurrency_amount, 'type'=>$obj->type, 'date'=>$obj->date, 'num'=>'0', 'ref'=>$obj->ref);
412  } else {
413  $retarray[] = array('amount'=>$obj->amount, 'type'=>$obj->type, 'date'=>$obj->date, 'num'=>'', 'ref'=>$obj->ref);
414  }
415  $i++;
416  }
417  } else {
418  $this->error = $this->db->lasterror();
419  dol_print_error($this->db);
420  return array();
421  }
422  $this->db->free($resql);
423  }
424 
425  return $retarray;
426  } else {
427  $this->error = $this->db->lasterror();
428  dol_print_error($this->db);
429  return array();
430  }
431  }
432 
433 
434  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
448  public function is_erasable()
449  {
450  // phpcs:enable
451  global $conf;
452 
453  // We check if invoice is a temporary number (PROVxxxx)
454  $tmppart = substr($this->ref, 1, 4);
455 
456  if ($this->statut == self::STATUS_DRAFT && $tmppart === 'PROV') { // If draft invoice and ref not yet defined
457  return 1;
458  }
459 
460  if (!empty($conf->global->INVOICE_CAN_NEVER_BE_REMOVED)) {
461  return 0;
462  }
463 
464  // If not a draft invoice and not temporary invoice
465  if ($tmppart !== 'PROV') {
466  $ventilExportCompta = $this->getVentilExportCompta();
467  if ($ventilExportCompta != 0) {
468  return -1;
469  }
470 
471  // Get last number of validated invoice
472  if ($this->element != 'invoice_supplier') {
473  if (empty($this->thirdparty)) {
474  $this->fetch_thirdparty(); // We need to have this->thirdparty defined, in case of numbering rule use tags that depend on thirdparty (like {t} tag).
475  }
476  $maxref = $this->getNextNumRef($this->thirdparty, 'last');
477 
478  // If there is no invoice into the reset range and not already dispatched, we can delete
479  // If invoice to delete is last one and not already dispatched, we can delete
480  if (empty($conf->global->INVOICE_CAN_ALWAYS_BE_REMOVED) && $maxref != '' && $maxref != $this->ref) {
481  return -2;
482  }
483 
484  // TODO If there is payment in bookkeeping, check payment is not dispatched in accounting
485  // ...
486 
487  if ($this->situation_cycle_ref && method_exists($this, 'is_last_in_cycle')) {
488  $last = $this->is_last_in_cycle();
489  if (!$last) {
490  return -3;
491  }
492  }
493  }
494  }
495 
496  // Test if there is at least one payment. If yes, refuse to delete.
497  if (empty($conf->global->INVOICE_CAN_ALWAYS_BE_REMOVED) && $this->getSommePaiement() > 0) {
498  return -4;
499  }
500 
501  return 2;
502  }
503 
509  public function getVentilExportCompta()
510  {
511  $alreadydispatched = 0;
512 
513  $type = 'customer_invoice';
514  if ($this->element == 'invoice_supplier') {
515  $type = 'supplier_invoice';
516  }
517 
518  $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);
519  $resql = $this->db->query($sql);
520  if ($resql) {
521  $obj = $this->db->fetch_object($resql);
522  if ($obj) {
523  $alreadydispatched = $obj->nb;
524  }
525  } else {
526  $this->error = $this->db->lasterror();
527  return -1;
528  }
529 
530  if ($alreadydispatched) {
531  return 1;
532  }
533  return 0;
534  }
535 
536 
543  public function getLibType($withbadge = 0)
544  {
545  global $langs;
546 
547  $labellong = "Unknown";
548  if ($this->type == CommonInvoice::TYPE_STANDARD) {
549  $labellong = "InvoiceStandard";
550  $labelshort = "InvoiceStandardShort";
551  } elseif ($this->type == CommonInvoice::TYPE_REPLACEMENT) {
552  $labellong = "InvoiceReplacement";
553  $labelshort = "InvoiceReplacementShort";
554  } elseif ($this->type == CommonInvoice::TYPE_CREDIT_NOTE) {
555  $labellong = "InvoiceAvoir";
556  $labelshort = "CreditNote";
557  } elseif ($this->type == CommonInvoice::TYPE_DEPOSIT) {
558  $labellong = "InvoiceDeposit";
559  $labelshort = "Deposit";
560  } elseif ($this->type == CommonInvoice::TYPE_PROFORMA) {
561  $labellong = "InvoiceProForma"; // Not used.
562  $labelshort = "ProForma";
563  } elseif ($this->type == CommonInvoice::TYPE_SITUATION) {
564  $labellong = "InvoiceSituation";
565  $labelshort = "Situation";
566  }
567 
568  $out = '';
569  if ($withbadge) {
570  $out .= '<span class="badgeneutral" title="'.dol_escape_htmltag($langs->trans($labellong)).'">';
571  }
572  $out .= $langs->trans($withbadge == 2 ? $labelshort : $labellong);
573  if ($withbadge) {
574  $out .= '</span>';
575  }
576  return $out;
577  }
578 
586  public function getLibStatut($mode = 0, $alreadypaid = -1)
587  {
588  return $this->LibStatut($this->paye, $this->statut, $mode, $alreadypaid, $this->type);
589  }
590 
591  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
602  public function LibStatut($paye, $status, $mode = 0, $alreadypaid = -1, $type = -1)
603  {
604  // phpcs:enable
605  global $langs, $hookmanager;
606  $langs->load('bills');
607 
608  if ($type == -1) {
609  $type = $this->type;
610  }
611 
612  $statusType = 'status0';
613  $prefix = 'Short';
614  if (!$paye) {
615  if ($status == 0) {
616  $labelStatus = $langs->transnoentitiesnoconv('BillStatusDraft');
617  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusDraft');
618  } elseif (($status == 3 || $status == 2) && $alreadypaid <= 0) {
619  if ($status == 3) {
620  $labelStatus = $langs->transnoentitiesnoconv('BillStatusCanceled');
621  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusCanceled');
622  } else {
623  $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedUnpaid');
624  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedUnpaid');
625  }
626  $statusType = 'status5';
627  } elseif (($status == 3 || $status == 2) && $alreadypaid > 0) {
628  $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedPaidPartially');
629  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedPaidPartially');
630  $statusType = 'status9';
631  } elseif ($alreadypaid == 0) {
632  $labelStatus = $langs->transnoentitiesnoconv('BillStatusNotPaid');
633  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusNotPaid');
634  $statusType = 'status1';
635  } else {
636  $labelStatus = $langs->transnoentitiesnoconv('BillStatusStarted');
637  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusStarted');
638  $statusType = 'status3';
639  }
640  } else {
641  $statusType = 'status6';
642 
643  if ($type == self::TYPE_CREDIT_NOTE) {
644  $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaidBackOrConverted'); // credit note
645  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaidBackOrConverted'); // credit note
646  } elseif ($type == self::TYPE_DEPOSIT) {
647  $labelStatus = $langs->transnoentitiesnoconv('BillStatusConverted'); // deposit invoice
648  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusConverted'); // deposit invoice
649  } else {
650  $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaid');
651  $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaid');
652  }
653  }
654 
655  $parameters = array(
656  'status' => $status,
657  'mode' => $mode,
658  'paye' => $paye,
659  'alreadypaid' => $alreadypaid,
660  'type' => $type
661  );
662 
663  $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
664 
665  if ($reshook > 0) {
666  return $hookmanager->resPrint;
667  }
668 
669 
670 
671  return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
672  }
673 
674  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
682  public function calculate_date_lim_reglement($cond_reglement = 0)
683  {
684  // phpcs:enable
685  if (!$cond_reglement) {
686  $cond_reglement = $this->cond_reglement_code;
687  }
688  if (!$cond_reglement) {
689  $cond_reglement = $this->cond_reglement_id;
690  }
691  if (!$cond_reglement) {
692  return $this->date;
693  }
694 
695  $cdr_nbjour = 0;
696  $cdr_type = 0;
697  $cdr_decalage = 0;
698 
699  $sqltemp = "SELECT c.type_cdr, c.nbjour, c.decalage";
700  $sqltemp .= " FROM ".$this->db->prefix()."c_payment_term as c";
701  if (is_numeric($cond_reglement)) {
702  $sqltemp .= " WHERE c.rowid=".((int) $cond_reglement);
703  } else {
704  $sqltemp .= " WHERE c.entity IN (".getEntity('c_payment_term').")";
705  $sqltemp .= " AND c.code = '".$this->db->escape($cond_reglement)."'";
706  }
707 
708  dol_syslog(get_class($this).'::calculate_date_lim_reglement', LOG_DEBUG);
709  $resqltemp = $this->db->query($sqltemp);
710  if ($resqltemp) {
711  if ($this->db->num_rows($resqltemp)) {
712  $obj = $this->db->fetch_object($resqltemp);
713  $cdr_nbjour = $obj->nbjour;
714  $cdr_type = $obj->type_cdr;
715  $cdr_decalage = $obj->decalage;
716  }
717  } else {
718  $this->error = $this->db->error();
719  return -1;
720  }
721  $this->db->free($resqltemp);
722 
723  /* Definition de la date limite */
724 
725  // 0 : adding the number of days
726  if ($cdr_type == 0) {
727  $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
728 
729  $datelim += ($cdr_decalage * 3600 * 24);
730  } elseif ($cdr_type == 1) {
731  // 1 : application of the "end of the month" rule
732  $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
733 
734  $mois = date('m', $datelim);
735  $annee = date('Y', $datelim);
736  if ($mois == 12) {
737  $mois = 1;
738  $annee += 1;
739  } else {
740  $mois += 1;
741  }
742  // We move at the beginning of the next month, and we take a day off
743  $datelim = dol_mktime(12, 0, 0, $mois, 1, $annee);
744  $datelim -= (3600 * 24);
745 
746  $datelim += ($cdr_decalage * 3600 * 24);
747  } elseif ($cdr_type == 2 && !empty($cdr_decalage)) {
748  // 2 : application of the rule, the N of the current or next month
749  include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
750  $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
751 
752  $date_piece = dol_mktime(0, 0, 0, date('m', $datelim), date('d', $datelim), date('Y', $datelim)); // Sans les heures minutes et secondes
753  $date_lim_current = dol_mktime(0, 0, 0, date('m', $datelim), $cdr_decalage, date('Y', $datelim)); // Sans les heures minutes et secondes
754  $date_lim_next = dol_time_plus_duree($date_lim_current, 1, 'm'); // Add 1 month
755 
756  $diff = $date_piece - $date_lim_current;
757 
758  if ($diff < 0) {
759  $datelim = $date_lim_current;
760  } else {
761  $datelim = $date_lim_next;
762  }
763  } else {
764  return 'Bad value for type_cdr in database for record cond_reglement = '.$cond_reglement;
765  }
766 
767  return $datelim;
768  }
769 
770  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
781  public function demande_prelevement($fuser, $amount = 0, $type = 'direct-debit', $sourcetype = 'facture')
782  {
783  // phpcs:enable
784  global $conf;
785 
786  $error = 0;
787 
788  dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
789 
790  if ($this->statut > self::STATUS_DRAFT && $this->paye == 0) {
791  require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
792  $bac = new CompanyBankAccount($this->db);
793  $bac->fetch(0, $this->socid);
794 
795  $sql = "SELECT count(*)";
796  $sql .= " FROM ".$this->db->prefix()."prelevement_demande";
797  if ($type == 'bank-transfer') {
798  $sql .= " WHERE fk_facture_fourn = ".((int) $this->id);
799  } else {
800  $sql .= " WHERE fk_facture = ".((int) $this->id);
801  }
802  $sql .= " AND type = 'ban'"; // To exclude record done for some online payments
803  $sql .= " AND traite = 0";
804 
805  dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
806  $resql = $this->db->query($sql);
807  if ($resql) {
808  $row = $this->db->fetch_row($resql);
809  if ($row[0] == 0) {
810  $now = dol_now();
811 
812  $totalpaid = $this->getSommePaiement();
813  $totalcreditnotes = $this->getSumCreditNotesUsed();
814  $totaldeposits = $this->getSumDepositsUsed();
815  //print "totalpaid=".$totalpaid." totalcreditnotes=".$totalcreditnotes." totaldeposts=".$totaldeposits;
816 
817  // We can also use bcadd to avoid pb with floating points
818  // For example print 239.2 - 229.3 - 9.9; does not return 0.
819  //$resteapayer=bcadd($this->total_ttc,$totalpaid,$conf->global->MAIN_MAX_DECIMALS_TOT);
820  //$resteapayer=bcadd($resteapayer,$totalavoir,$conf->global->MAIN_MAX_DECIMALS_TOT);
821  if (empty($amount)) {
822  $amount = price2num($this->total_ttc - $totalpaid - $totalcreditnotes - $totaldeposits, 'MT');
823  }
824 
825  if (is_numeric($amount) && $amount != 0) {
826  $sql = 'INSERT INTO '.$this->db->prefix().'prelevement_demande(';
827  if ($type == 'bank-transfer') {
828  $sql .= 'fk_facture_fourn, ';
829  } else {
830  $sql .= 'fk_facture, ';
831  }
832  $sql .= ' amount, date_demande, fk_user_demande, code_banque, code_guichet, number, cle_rib, sourcetype, type, entity)';
833  $sql .= " VALUES (".((int) $this->id);
834  $sql .= ", ".((float) price2num($amount));
835  $sql .= ", '".$this->db->idate($now)."'";
836  $sql .= ", ".((int) $fuser->id);
837  $sql .= ", '".$this->db->escape($bac->code_banque)."'";
838  $sql .= ", '".$this->db->escape($bac->code_guichet)."'";
839  $sql .= ", '".$this->db->escape($bac->number)."'";
840  $sql .= ", '".$this->db->escape($bac->cle_rib)."'";
841  $sql .= ", '".$this->db->escape($sourcetype)."'";
842  $sql .= ", 'ban'";
843  $sql .= ", ".((int) $conf->entity);
844  $sql .= ")";
845 
846  dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
847  $resql = $this->db->query($sql);
848  if (!$resql) {
849  $this->error = $this->db->lasterror();
850  dol_syslog(get_class($this).'::demandeprelevement Erreur');
851  $error++;
852  }
853  } else {
854  $this->error = 'WithdrawRequestErrorNilAmount';
855  dol_syslog(get_class($this).'::demandeprelevement WithdrawRequestErrorNilAmount');
856  $error++;
857  }
858 
859  if (!$error) {
860  // Force payment mode of invoice to withdraw
861  $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
862  if ($payment_mode_id > 0) {
863  $result = $this->setPaymentMethods($payment_mode_id);
864  }
865  }
866 
867  if ($error) {
868  return -1;
869  }
870  return 1;
871  } else {
872  $this->error = "A request already exists";
873  dol_syslog(get_class($this).'::demandeprelevement Impossible de creer une demande, demande deja en cours');
874  return 0;
875  }
876  } else {
877  $this->error = $this->db->error();
878  dol_syslog(get_class($this).'::demandeprelevement Erreur -2');
879  return -2;
880  }
881  } else {
882  $this->error = "Status of invoice does not allow this";
883  dol_syslog(get_class($this)."::demandeprelevement ".$this->error." $this->statut, $this->paye, $this->mode_reglement_id");
884  return -3;
885  }
886  }
887 
888 
900  public function makeStripeCardRequest($fuser, $id, $sourcetype = 'facture')
901  {
902  // TODO See in sellyoursaas
903  }
904 
915  public function makeStripeSepaRequest($fuser, $did, $type = 'direct-debit', $sourcetype = 'facture')
916  {
917  global $conf, $user, $langs;
918 
919  if ($type != 'bank-transfer' && $type != 'credit-transfer' && empty($conf->global->STRIPE_SEPA_DIRECT_DEBIT)) {
920  return 0;
921  }
922  if ($type != 'direct-debit' && empty($conf->global->STRIPE_SEPA_CREDIT_TRANSFER)) {
923  return 0;
924  }
925 
926  $error = 0;
927 
928  dol_syslog(get_class($this)."::makeStripeSepaRequest start", LOG_DEBUG);
929 
930  if ($this->statut > self::STATUS_DRAFT && $this->paye == 0) {
931  // Get the default payment mode for BAN payment of the third party
932  require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
933  $bac = new CompanyBankAccount($this->db); // table societe_rib
934  $result = $bac->fetch(0, $this->socid, 1, 'ban');
935  if ($result <= 0 || empty($bac->id)) {
936  $this->error = $langs->trans("ThirdpartyHasNoDefaultBanAccount");
937  $this->errors[] = $this->error;
938  dol_syslog(get_class($this)."::makeStripeSepaRequest ".$this->error);
939  return -1;
940  }
941 
942  // Load the pending payment requests to process
943  $sql = "SELECT rowid, date_demande, amount, fk_facture, fk_facture_fourn, fk_prelevement_bons";
944  $sql .= " FROM ".$this->db->prefix()."prelevement_demande";
945  $sql .= " WHERE rowid = ".((int) $did);
946  if ($type != 'bank-transfer' && $type != 'credit-transfer') {
947  $sql .= " AND fk_facture = ".((int) $this->id); // Add a protection to not pay another invoice than current one
948  }
949  if ($type != 'direct-debit') {
950  $sql .= " AND fk_facture_fourn = ".((int) $this->id); // Add a protection to not pay another invoice than current one
951  }
952  $sql .= " AND traite = 0"; // To not process payment request that were already converted into a direct debit or credit transfer order (Note: fk_prelevement_bons is also empty when traite = 0)
953 
954  dol_syslog(get_class($this)."::makeStripeSepaRequest load requests to process", LOG_DEBUG);
955  $resql = $this->db->query($sql);
956  if ($resql) {
957  $obj = $this->db->fetch_object($resql);
958  if (!$obj) {
959  dol_print_error($this->db, 'CantFindRequestWithId');
960  return -2;
961  }
962 
963  // amount to pay
964  $amount = $obj->amount;
965 
966  if (is_numeric($amount) && $amount != 0) {
967  require_once DOL_DOCUMENT_ROOT.'/societe/class/companypaymentmode.class.php';
968  $companypaymentmode = new CompanyPaymentMode($this->db); // table societe_rib
969  $companypaymentmode->fetch($bac->id);
970 
971  // Start code for Stripe
972  // TODO We may have this coming as a parameter from the caller.
973  $service = 'StripeTest';
974  $servicestatus = 0;
975  if (!empty($conf->global->STRIPE_LIVE) && !GETPOST('forcesandbox', 'alpha')) {
976  $service = 'StripeLive';
977  $servicestatus = 1;
978  }
979 
980  dol_syslog("makeStripeSepaRequest amount = ".$amount." service=" . $service . " servicestatus=" . $servicestatus . " thirdparty_id=" . $this->socid." did=".$did);
981 
982  $this->stripechargedone = 0;
983  $this->stripechargeerror = 0;
984 
985  $now = dol_now();
986 
987  $currency = $conf->currency;
988 
989  global $stripearrayofkeysbyenv;
990 
991  $errorforinvoice = 0; // We reset the $errorforinvoice at each invoice loop
992 
993  $this->fetch_thirdparty();
994 
995  dol_syslog("--- Process payment request thirdparty_id=" . $this->thirdparty->id . ", thirdparty_name=" . $this->thirdparty->name . " ban id=" . $bac->id, LOG_DEBUG);
996 
997  //$alreadypayed = $this->getSommePaiement();
998  //$amount_credit_notes_included = $this->getSumCreditNotesUsed();
999  //$amounttopay = $this->total_ttc - $alreadypayed - $amount_credit_notes_included;
1000  $amounttopay = $amount;
1001 
1002  // Correct the amount according to unit of currency
1003  // See https://support.stripe.com/questions/which-zero-decimal-currencies-does-stripe-support
1004  $arrayzerounitcurrency = ['BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'];
1005  $amountstripe = $amounttopay;
1006  if (!in_array($currency, $arrayzerounitcurrency)) {
1007  $amountstripe = $amountstripe * 100;
1008  }
1009 
1010  $fk_bank_account = getDolGlobalInt('STRIPE_BANK_ACCOUNT_FOR_PAYMENTS'); // Bank account used for SEPA direct debit or credit transfer. Must be the Stripe account in Dolibarr.
1011  if (!($fk_bank_account > 0)) {
1012  $error++;
1013  $errorforinvoice++;
1014  dol_syslog("Error no bank account defined for Stripe payments", LOG_ERR);
1015  $this->errors[] = "Error bank account for Stripe payments not defined into Stripe module";
1016  }
1017 
1018  $this->db->begin();
1019 
1020  // Create a prelevement_bon
1021  require_once DOL_DOCUMENT_ROOT.'/compta/prelevement/class/bonprelevement.class.php';
1022  $bon = new BonPrelevement($this->db);
1023  if (!$error) {
1024  if (empty($obj->fk_prelevement_bons)) {
1025  // This create record into llx_prelevment_bons and update link with llx_prelevement_demande
1026  $nbinvoices = $bon->create(0, 0, 'real', 'ALL', '', 0, $type, $did, $fk_bank_account);
1027  if ($nbinvoices <= 0) {
1028  $error++;
1029  $errorforinvoice++;
1030  dol_syslog("Error on BonPrelevement creation", LOG_ERR);
1031  $this->errors[] = "Error on BonPrelevement creation";
1032  }
1033  /*
1034  if (!$error) {
1035  // Update the direct debit payment request of the processed request to save the id of the prelevement_bon
1036  $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
1037  $sql .= " fk_prelevement_bons = ".((int) $bon->id);
1038  $sql .= " WHERE rowid = ".((int) $did);
1039 
1040  $result = $this->db->query($sql);
1041  if ($result < 0) {
1042  $error++;
1043  $this->errors[] = "Error on updateing fk_prelevement_bons to ".$bon->id;
1044  }
1045  }
1046  */
1047  } else {
1048  $error++;
1049  $errorforinvoice++;
1050  dol_syslog("Error Line already part of a bank payment order", LOG_ERR);
1051  $this->errors[] = "The line is already included into a bank payment order. Delete the bank payment order first.";
1052  }
1053  }
1054 
1055  if (!$error) {
1056  if ($amountstripe > 0) {
1057  try {
1058  //var_dump($companypaymentmode);
1059  dol_syslog("We will try to pay with companypaymentmodeid=" . $companypaymentmode->id . " stripe_card_ref=" . $companypaymentmode->stripe_card_ref . " mode=" . $companypaymentmode->status, LOG_DEBUG);
1060 
1061  $thirdparty = new Societe($this->db);
1062  $resultthirdparty = $thirdparty->fetch($this->socid);
1063 
1064  include_once DOL_DOCUMENT_ROOT . '/stripe/class/stripe.class.php'; // This include the include of htdocs/stripe/config.php
1065  // So it inits or erases the $stripearrayofkeysbyenv
1066  $stripe = new Stripe($this->db);
1067 
1068  dol_syslog("makeStripeSepaRequest Current Stripe environment is " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key']);
1069 
1070  $stripearrayofkeys = $stripearrayofkeysbyenv[$servicestatus];
1071  \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1072 
1073 
1074  dol_syslog("makeStripeSepaRequest get stripe connet account", LOG_DEBUG);
1075  $stripeacc = $stripe->getStripeAccount($service, $this->socid); // Get Stripe OAuth connect account if it exists (no network access here)
1076  dol_syslog("makeStripeSepaRequest get stripe connect account return " . json_encode($stripeacc), LOG_DEBUG);
1077 
1078  $customer = $stripe->customerStripe($thirdparty, $stripeacc, $servicestatus, 0);
1079  if (empty($customer) && !empty($stripe->error)) {
1080  $this->errors[] = $stripe->error;
1081  }
1082 
1083  // $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)
1084  // $nbdaysbeforeendoftries = (empty($conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES) ? 35 : $conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES);
1085  $postactionmessages = [];
1086 
1087  if ($resultthirdparty > 0 && !empty($customer)) {
1088  if (!$error) { // Payment was not canceled
1089  $stripecard = null;
1090  if ($companypaymentmode->type == 'ban') {
1091  // Check into societe_rib if a payment mode for Stripe and ban payment exists
1092  // To make a Stripe SEPA payment request, we must have the payment mode source already saved into societe_rib and retreived with ->sepaStripe
1093  // The payment mode source is created when we create the bank account on Stripe with paymentmodes.php?action=create
1094  $stripecard = $stripe->sepaStripe($customer, $companypaymentmode, $stripeacc, $servicestatus, 0);
1095  } else {
1096  $error++;
1097  $this->error = 'The payment mode type is not "ban"';
1098  }
1099 
1100  if ($stripecard) { // Can be src_... (for sepa) or pm_... (new card mode). Note that card_... (old card mode) should not happen here.
1101  $FULLTAG = 'DID='.$did.'-INV=' . $this->id . '-CUS=' . $thirdparty->id;
1102  $description = 'Stripe payment from makeStripeSepaRequest: ' . $FULLTAG . ' did='.$did.' ref=' . $this->ref;
1103 
1104  $stripefailurecode = '';
1105  $stripefailuremessage = '';
1106  $stripefailuredeclinecode = '';
1107 
1108  // Using new SCA method
1109  dol_syslog("* Create payment on SEPA " . $stripecard->id . ", amounttopay=" . $amounttopay . ", amountstripe=" . $amountstripe . ", FULLTAG=" . $FULLTAG, LOG_DEBUG);
1110 
1111  // Create payment intent and charge payment (confirmnow = true)
1112  $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $this, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1, 1, $did);
1113 
1114  $charge = new stdClass();
1115 
1116  if ($paymentintent->status === 'succeeded' || $paymentintent->status === 'processing') {
1117  $charge->status = 'ok';
1118  $charge->id = $paymentintent->id;
1119  $charge->customer = $customer->id;
1120  } elseif ($paymentintent->status === 'requires_action') {
1121  //paymentintent->status may be => 'requires_action' (no error in such a case)
1122  dol_syslog(var_export($paymentintent, true), LOG_DEBUG);
1123 
1124  $charge->status = 'failed';
1125  $charge->customer = $customer->id;
1126  $charge->failure_code = $stripe->code;
1127  $charge->failure_message = $stripe->error;
1128  $charge->failure_declinecode = $stripe->declinecode;
1129  $stripefailurecode = $stripe->code;
1130  $stripefailuremessage = 'Action required. Contact the support at ';// . $conf->global->SELLYOURSAAS_MAIN_EMAIL;
1131  $stripefailuredeclinecode = $stripe->declinecode;
1132  } else {
1133  dol_syslog(var_export($paymentintent, true), LOG_DEBUG);
1134 
1135  $charge->status = 'failed';
1136  $charge->customer = $customer->id;
1137  $charge->failure_code = $stripe->code;
1138  $charge->failure_message = $stripe->error;
1139  $charge->failure_declinecode = $stripe->declinecode;
1140  $stripefailurecode = $stripe->code;
1141  $stripefailuremessage = $stripe->error;
1142  $stripefailuredeclinecode = $stripe->declinecode;
1143  }
1144 
1145  //var_dump("stripefailurecode=".$stripefailurecode." stripefailuremessage=".$stripefailuremessage." stripefailuredeclinecode=".$stripefailuredeclinecode);
1146  //exit;
1147 
1148 
1149  // Return $charge = array('id'=>'ch_XXXX', 'status'=>'succeeded|pending|failed', 'failure_code'=>, 'failure_message'=>...)
1150  if (empty($charge) || $charge->status == 'failed') {
1151  dol_syslog('Failed to charge payment mode ' . $stripecard->id . ' stripefailurecode=' . $stripefailurecode . ' stripefailuremessage=' . $stripefailuremessage . ' stripefailuredeclinecode=' . $stripefailuredeclinecode, LOG_WARNING);
1152 
1153  // Save a stripe payment was in error
1154  $this->stripechargeerror++;
1155 
1156  $error++;
1157  $errorforinvoice++;
1158  $errmsg = $langs->trans("FailedToChargeCard");
1159  if (!empty($charge)) {
1160  if ($stripefailuredeclinecode == 'authentication_required') {
1161  $errauthenticationmessage = $langs->trans("ErrSCAAuthentication");
1162  $errmsg = $errauthenticationmessage;
1163  } elseif (in_array($stripefailuredeclinecode, ['insufficient_funds', 'generic_decline'])) {
1164  $errmsg .= ': ' . $charge->failure_code;
1165  $errmsg .= ($charge->failure_message ? ' - ' : '') . ' ' . $charge->failure_message;
1166  if (empty($stripefailurecode)) {
1167  $stripefailurecode = $charge->failure_code;
1168  }
1169  if (empty($stripefailuremessage)) {
1170  $stripefailuremessage = $charge->failure_message;
1171  }
1172  } else {
1173  $errmsg .= ': failure_code=' . $charge->failure_code;
1174  $errmsg .= ($charge->failure_message ? ' - ' : '') . ' failure_message=' . $charge->failure_message;
1175  if (empty($stripefailurecode)) {
1176  $stripefailurecode = $charge->failure_code;
1177  }
1178  if (empty($stripefailuremessage)) {
1179  $stripefailuremessage = $charge->failure_message;
1180  }
1181  }
1182  } else {
1183  $errmsg .= ': ' . $stripefailurecode . ' - ' . $stripefailuremessage;
1184  $errmsg .= ($stripefailuredeclinecode ? ' - ' . $stripefailuredeclinecode : '');
1185  }
1186 
1187  $description = 'Stripe payment ERROR from makeStripeSepaRequest: ' . $FULLTAG;
1188  $postactionmessages[] = $errmsg . ' (' . $stripearrayofkeys['publishable_key'] . ')';
1189  $this->errors[] = $errmsg;
1190  } else {
1191  dol_syslog('Successfuly request '.$type.' '.$stripecard->id);
1192 
1193  $postactionmessages[] = 'Success to request '.$type.' (' . $charge->id . ' with ' . $stripearrayofkeys['publishable_key'] . ')';
1194 
1195  // Save a stripe payment was done in realy life so later we will be able to force a commit on recorded payments
1196  // even if in batch mode (method doTakePaymentStripe), we will always make all action in one transaction with a forced commit.
1197  $this->stripechargedone++;
1198 
1199  // Default description used for label of event. Will be overwrite by another value later.
1200  $description = 'Stripe payment request OK (' . $charge->id . ') from makeStripeSepaRequest: ' . $FULLTAG;
1201  }
1202 
1203  $object = $this;
1204 
1205  // Track an event
1206  if (empty($charge) || $charge->status == 'failed') {
1207  $actioncode = 'PAYMENT_STRIPE_KO';
1208  $extraparams = $stripefailurecode;
1209  $extraparams .= (($extraparams && $stripefailuremessage) ? ' - ' : '') . $stripefailuremessage;
1210  $extraparams .= (($extraparams && $stripefailuredeclinecode) ? ' - ' : '') . $stripefailuredeclinecode;
1211  } else {
1212  $actioncode = 'PAYMENT_STRIPE_OK';
1213  $extraparams = '';
1214  }
1215  } else {
1216  $error++;
1217  $errorforinvoice++;
1218  dol_syslog("No ban payment method found for this stripe customer " . $customer->id, LOG_WARNING);
1219  $this->errors[] = 'Failed to get direct debit payment method for stripe customer = ' . $customer->id;
1220 
1221  $description = 'Failed to find or use the payment mode - no ban defined for the thirdparty account';
1222  $stripefailurecode = 'BADPAYMENTMODE';
1223  $stripefailuremessage = 'Failed to find or use the payment mode - no ban defined for the thirdparty account';
1224  $postactionmessages[] = $description . ' (' . $stripearrayofkeys['publishable_key'] . ')';
1225 
1226  $object = $this;
1227 
1228  $actioncode = 'PAYMENT_STRIPE_KO';
1229  $extraparams = '';
1230  }
1231  } else {
1232  // If error because payment was canceled for a logical reason, we do nothing (no event added)
1233  $description = '';
1234  $stripefailurecode = '';
1235  $stripefailuremessage = '';
1236 
1237  $object = $this;
1238 
1239  $actioncode = '';
1240  $extraparams = '';
1241  }
1242  } else { // Else of the if ($resultthirdparty > 0 && ! empty($customer)) {
1243  if ($resultthirdparty <= 0) {
1244  dol_syslog('SellYourSaasUtils Failed to load customer for thirdparty_id = ' . $thirdparty->id, LOG_WARNING);
1245  $this->errors[] = 'Failed to load Stripe account for thirdparty_id = ' . $thirdparty->id;
1246  } else { // $customer stripe not found
1247  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);
1248  $this->errors[] = 'Failed to get Stripe account id for thirdparty_id = ' . $thirdparty->id . " in mode " . $servicestatus . " in Stripe env " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key'];
1249  }
1250  $error++;
1251  $errorforinvoice++;
1252 
1253  $description = 'Failed to find or use your payment mode (no payment mode for this customer id)';
1254  $stripefailurecode = 'BADPAYMENTMODE';
1255  $stripefailuremessage = 'Failed to find or use your payment mode (no payment mode for this customer id)';
1256  $postactionmessages = [];
1257 
1258  $object = $this;
1259 
1260  $actioncode = 'PAYMENT_STRIPE_KO';
1261  $extraparams = '';
1262  }
1263 
1264  if ($description) {
1265  dol_syslog("* Record event for credit transfer or direct debit request result - " . $description);
1266  require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
1267 
1268  // Insert record of payment (success or error)
1269  $actioncomm = new ActionComm($this->db);
1270 
1271  $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
1272  $actioncomm->code = 'AC_' . $actioncode;
1273  $actioncomm->label = $description;
1274  $actioncomm->note_private = join(",\n", $postactionmessages);
1275  $actioncomm->fk_project = $this->fk_project;
1276  $actioncomm->datep = $now;
1277  $actioncomm->datef = $now;
1278  $actioncomm->percentage = -1; // Not applicable
1279  $actioncomm->socid = $thirdparty->id;
1280  $actioncomm->contactid = 0;
1281  $actioncomm->authorid = $user->id; // User saving action
1282  $actioncomm->userownerid = $user->id; // Owner of action
1283  // Fields when action is a real email (content is already into note)
1284  /*$actioncomm->email_msgid = $object->email_msgid;
1285  $actioncomm->email_from = $object->email_from;
1286  $actioncomm->email_sender= $object->email_sender;
1287  $actioncomm->email_to = $object->email_to;
1288  $actioncomm->email_tocc = $object->email_tocc;
1289  $actioncomm->email_tobcc = $object->email_tobcc;
1290  $actioncomm->email_subject = $object->email_subject;
1291  $actioncomm->errors_to = $object->errors_to;*/
1292  $actioncomm->fk_element = $this->id;
1293  $actioncomm->elementtype = $this->element;
1294  $actioncomm->extraparams = dol_trunc($extraparams, 250);
1295 
1296  $actioncomm->create($user);
1297  }
1298 
1299  $this->description = $description;
1300  $this->postactionmessages = $postactionmessages;
1301  } catch (Exception $e) {
1302  $error++;
1303  $errorforinvoice++;
1304  dol_syslog('Error ' . $e->getMessage(), LOG_ERR);
1305  $this->errors[] = 'Error ' . $e->getMessage();
1306  }
1307  } else { // If remain to pay is null
1308  $error++;
1309  $errorforinvoice++;
1310  dol_syslog("Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?", LOG_WARNING);
1311  $this->errors[] = "Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?";
1312  }
1313  }
1314 
1315  // Set status of the order to "Transferred" with method 'api'
1316  if (!$error && !$errorforinvoice) {
1317  $result = $bon->set_infotrans($user, $now, 3);
1318  if ($result < 0) {
1319  $error++;
1320  $errorforinvoice++;
1321  dol_syslog("Error on BonPrelevement creation", LOG_ERR);
1322  $this->errors[] = "Error on BonPrelevement creation";
1323  }
1324  }
1325 
1326  if (!$error && !$errorforinvoice) {
1327  // Update the direct debit payment request of the processed invoice to save the id of the prelevement_bon
1328  $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
1329  $sql .= " ext_payment_id = '".$this->db->escape($paymentintent->id)."',";
1330  $sql .= " ext_payment_site = '".$this->db->escape($service)."'";
1331  $sql .= " WHERE rowid = ".((int) $did);
1332 
1333  dol_syslog(get_class($this)."::makeStripeSepaRequest update to save stripe paymentintent ids", LOG_DEBUG);
1334  $resql = $this->db->query($sql);
1335  if (!$resql) {
1336  $this->error = $this->db->lasterror();
1337  dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur');
1338  $error++;
1339  }
1340  }
1341 
1342  if (!$error && !$errorforinvoice) {
1343  $this->db->commit();
1344  } else {
1345  $this->db->rollback();
1346  }
1347  } else {
1348  $this->error = 'WithdrawRequestErrorNilAmount';
1349  dol_syslog(get_class($this).'::makeStripeSepaRequest WithdrawRequestErrorNilAmount');
1350  $error++;
1351  }
1352 
1353  /*
1354  if (!$error) {
1355  // Force payment mode of the invoice to withdraw
1356  $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
1357  if ($payment_mode_id > 0) {
1358  $result = $this->setPaymentMethods($payment_mode_id);
1359  }
1360  }*/
1361 
1362  if ($error) {
1363  return -1;
1364  }
1365  return 1;
1366  } else {
1367  $this->error = $this->db->error();
1368  dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur -2');
1369  return -2;
1370  }
1371  } else {
1372  $this->error = "Status of invoice does not allow this";
1373  dol_syslog(get_class($this)."::makeStripeSepaRequest ".$this->error." $this->statut, $this->paye, $this->mode_reglement_id");
1374  return -3;
1375  }
1376  }
1377 
1378  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1386  public function demande_prelevement_delete($fuser, $did)
1387  {
1388  // phpcs:enable
1389  $sql = 'DELETE FROM '.$this->db->prefix().'prelevement_demande';
1390  $sql .= ' WHERE rowid = '.((int) $did);
1391  $sql .= ' AND traite = 0';
1392  if ($this->db->query($sql)) {
1393  return 0;
1394  } else {
1395  $this->error = $this->db->lasterror();
1396  dol_syslog(get_class($this).'::demande_prelevement_delete Error '.$this->error);
1397  return -1;
1398  }
1399  }
1400 
1401 
1407  public function buildZATCAQRString()
1408  {
1409  global $conf, $mysoc;
1410 
1411  $tmplang = new Translate('', $conf);
1412  $tmplang->setDefaultLang('en_US');
1413  $tmplang->load("main");
1414 
1415  $datestring = dol_print_date($this->date, 'dayhourrfc');
1416  //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
1417  //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
1418  $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
1419  $pricetaxstring = price2num($this->total_tva, 2, 1);
1420 
1421  /*
1422  $name = implode(unpack("H*", $this->thirdparty->name));
1423  $vatnumber = implode(unpack("H*", $this->thirdparty->tva_intra));
1424  $date = implode(unpack("H*", $datestring));
1425  $pricewithtax = implode(unpack("H*", price2num($pricewithtaxstring, 2)));
1426  $pricetax = implode(unpack("H*", $pricetaxstring));
1427 
1428  //var_dump(strlen($this->thirdparty->name));
1429  //var_dump(str_pad(dechex('9'), 2, '0', STR_PAD_LEFT));
1430  //var_dump($this->thirdparty->name);
1431  //var_dump(implode(unpack("H*", $this->thirdparty->name)));
1432  //var_dump(price($this->total_tva, 0, $tmplang, 0, -1, 2));
1433 
1434  $s = '01'.str_pad(dechex(strlen($this->thirdparty->name)), 2, '0', STR_PAD_LEFT).$name;
1435  $s .= '02'.str_pad(dechex(strlen($this->thirdparty->tva_intra)), 2, '0', STR_PAD_LEFT).$vatnumber;
1436  $s .= '03'.str_pad(dechex(strlen($datestring)), 2, '0', STR_PAD_LEFT).$date;
1437  $s .= '04'.str_pad(dechex(strlen($pricewithtaxstring)), 2, '0', STR_PAD_LEFT).$pricewithtax;
1438  $s .= '05'.str_pad(dechex(strlen($pricetaxstring)), 2, '0', STR_PAD_LEFT).$pricetax;
1439  $s .= ''; // Hash of xml invoice
1440  $s .= ''; // ecda signature
1441  $s .= ''; // ecda public key
1442  $s .= ''; // ecda signature of public key stamp
1443  */
1444 
1445  // Using TLV format
1446  $s = pack('C1', 1).pack('C1', strlen($mysoc->name)).$mysoc->name;
1447  $s .= pack('C1', 2).pack('C1', strlen($mysoc->tva_intra)).$mysoc->tva_intra;
1448  $s .= pack('C1', 3).pack('C1', strlen($datestring)).$datestring;
1449  $s .= pack('C1', 4).pack('C1', strlen($pricewithtaxstring)).$pricewithtaxstring;
1450  $s .= pack('C1', 5).pack('C1', strlen($pricetaxstring)).$pricetaxstring;
1451  $s .= ''; // Hash of xml invoice
1452  $s .= ''; // ecda signature
1453  $s .= ''; // ecda public key
1454  $s .= ''; // ecda signature of public key stamp
1455 
1456  $s = base64_encode($s);
1457 
1458  return $s;
1459  }
1460 
1461 
1467  public function buildSwitzerlandQRString()
1468  {
1469  global $conf, $mysoc;
1470 
1471  $tmplang = new Translate('', $conf);
1472  $tmplang->setDefaultLang('en_US');
1473  $tmplang->load("main");
1474 
1475  $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
1476  $pricetaxstring = price2num($this->total_tva, 2, 1);
1477 
1478  $complementaryinfo = '';
1479  /*
1480  Example: //S1/10/10201409/11/190512/20/1400.000-53/30/106017086/31/180508/32/7.7/40/2:10;0:30
1481  /10/ Numéro de facture – 10201409
1482  /11/ Date de facture – 12.05.2019
1483  /20/ Référence client – 1400.000-53
1484  /30/ Numéro IDE pour la TVA – CHE-106.017.086 TVA
1485  /31/ Date de la prestation pour la comptabilisation de la TVA – 08.05.2018
1486  /32/ Taux de TVA sur le montant total de la facture – 7.7%
1487  /40/ Conditions – 2% d’escompte à 10 jours, paiement net à 30 jours
1488  */
1489  $datestring = dol_print_date($this->date, '%y%m%d');
1490  //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
1491  //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
1492  $complementaryinfo = '//S1/10/'.str_replace('/', '', $this->ref).'/11/'.$datestring;
1493  if ($this->ref_client) {
1494  $complementaryinfo .= '/20/'.$this->ref_client;
1495  }
1496  if ($this->thirdparty->tva_intra) {
1497  $complementaryinfo .= '/30/'.$this->thirdparty->tva_intra;
1498  }
1499 
1500  include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
1501  $bankaccount = new Account($this->db);
1502 
1503  // Header
1504  $s = '';
1505  $s .= "SPC\n";
1506  $s .= "0200\n";
1507  $s .= "1\n";
1508  // Info Seller ("Compte / Payable à")
1509  if ($this->fk_account > 0) {
1510  // Bank BAN if country is LI or CH. TODO Add a test to check than IBAN start with CH or LI
1511  $bankaccount->fetch($this->fk_account);
1512  $s .= $bankaccount->iban."\n";
1513  } else {
1514  $s .= "\n";
1515  }
1516  if ($bankaccount->id > 0 && getDolGlobalString('PDF_SWISS_QRCODE_USE_OWNER_OF_ACCOUNT_AS_CREDITOR')) {
1517  // If a bank account is prodived and we ask to use it as creditor, we use the bank address
1518  // 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 ?
1519  $s .= "S\n";
1520  $s .= dol_trunc($bankaccount->proprio, 70, 'right', 'UTF-8', 1)."\n";
1521  $addresslinearray = explode("\n", $bankaccount->owner_address);
1522  $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
1523  $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
1524  /*$s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
1525  $s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
1526  $s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";*/
1527  } else {
1528  $s .= "S\n";
1529  $s .= dol_trunc($mysoc->name, 70, 'right', 'UTF-8', 1)."\n";
1530  $addresslinearray = explode("\n", $mysoc->address);
1531  $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
1532  $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
1533  $s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
1534  $s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
1535  $s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";
1536  }
1537  // Final seller (Ultimate seller) ("Créancier final" = "En faveur de")
1538  $s .= "\n";
1539  $s .= "\n";
1540  $s .= "\n";
1541  $s .= "\n";
1542  $s .= "\n";
1543  $s .= "\n";
1544  $s .= "\n";
1545  // Amount of payment (to do?)
1546  $s .= price($pricewithtaxstring, 0, 'none', 0, 0, 2)."\n";
1547  $s .= ($this->multicurrency_code ? $this->multicurrency_code : $conf->currency)."\n";
1548  // Buyer
1549  $s .= "S\n";
1550  $s .= dol_trunc($this->thirdparty->name, 70, 'right', 'UTF-8', 1)."\n";
1551  $addresslinearray = explode("\n", $this->thirdparty->address);
1552  $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
1553  $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
1554  $s .= dol_trunc($this->thirdparty->zip, 16, 'right', 'UTF-8', 1)."\n";
1555  $s .= dol_trunc($this->thirdparty->town, 35, 'right', 'UTF-8', 1)."\n";
1556  $s .= dol_trunc($this->thirdparty->country_code, 2, 'right', 'UTF-8', 1)."\n";
1557  // ID of payment
1558  $s .= "NON\n"; // NON or QRR
1559  $s .= "\n"; // QR Code reference if previous field is QRR
1560  // Free text
1561  if ($complementaryinfo) {
1562  $s .= $complementaryinfo."\n";
1563  } else {
1564  $s .= "\n";
1565  }
1566  $s .= "EPD\n";
1567  // More text, complementary info
1568  if ($complementaryinfo) {
1569  $s .= $complementaryinfo."\n";
1570  }
1571  $s .= "\n";
1572  //var_dump($s);exit;
1573  return $s;
1574  }
1575 }
1576 
1577 
1578 
1579 require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
1580 
1584 abstract class CommonInvoiceLine extends CommonObjectLine
1585 {
1590  public $label;
1591 
1596  public $ref; // Product ref (deprecated)
1601  public $libelle; // Product label (deprecated)
1602 
1607  public $product_type = 0;
1608 
1613  public $product_ref;
1614 
1619  public $product_label;
1620 
1625  public $product_desc;
1626 
1631  public $qty;
1632 
1637  public $subprice;
1638 
1644  public $price;
1645 
1650  public $fk_product;
1651 
1656  public $vat_src_code;
1657 
1662  public $tva_tx;
1663 
1668  public $localtax1_tx;
1669 
1674  public $localtax2_tx;
1675 
1680  public $localtax1_type;
1681 
1686  public $localtax2_type;
1687 
1692  public $remise_percent;
1693 
1699  public $remise;
1700 
1705  public $total_ht;
1706 
1711  public $total_tva;
1712 
1717  public $total_localtax1;
1718 
1723  public $total_localtax2;
1724 
1729  public $total_ttc;
1730 
1731  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
1732  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
1733 
1734  public $buy_price_ht;
1735  public $buyprice; // For backward compatibility
1736  public $pa_ht; // For backward compatibility
1737 
1738  public $marge_tx;
1739  public $marque_tx;
1740 
1747  public $info_bits = 0;
1748 
1749  public $special_code = 0;
1750 
1751  public $fk_multicurrency;
1752  public $multicurrency_code;
1753  public $multicurrency_subprice;
1754  public $multicurrency_total_ht;
1755  public $multicurrency_total_tva;
1756  public $multicurrency_total_ttc;
1757 
1758  public $fk_user_author;
1759  public $fk_user_modif;
1760 
1761  public $fk_accounting_account;
1762 }
$object ref
Definition: info.php:78
Class to manage bank accounts.
Class to manage agenda events (actions)
Class to manage withdrawal receipts.
Superclass for invoices classes.
const TYPE_CREDIT_NOTE
Credit note invoice.
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.
buildZATCAQRString()
Build string for ZATCA QR Code (Arabi Saudia)
makeStripeCardRequest($fuser, $id, $sourcetype='facture')
Create a payment with Stripe card Must take amount using Stripe and record an event into llx_actionco...
makeStripeSepaRequest($fuser, $did, $type='direct-debit', $sourcetype='facture')
Create a direct debit order into prelevement_bons then Send the payment order to Stripe (for a direct...
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.
setPaymentMethods($id)
Change the payments methods.
Parent class for class inheritance lines of business objects This class is useless for the moment so ...
Class to manage bank accounts description of third parties.
Class for CompanyPaymentMode.
Class to manage absolute discounts.
Class to manage third parties objects (customers, suppliers, prospects...)
Stripe class.
Class to manage translations.
trait CommonIncoterm
Superclass for incoterm classes.
if(isModEnabled('facture') && $user->hasRight('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') && $user->hasRight('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)) $sql
Social contributions to pay.
Definition: index.php:746
dol_time_plus_duree($time, $duration_value, $duration_unit, $ruleforendofmonth=0)
Add a delay to a date.
Definition: date.lib.php:122
print *****$script_file(".$version.") pid cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed informations (by default a local PHP server timestamp) Re...
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.
getDolGlobalInt($key, $default=0)
Return dolibarr global constant int value.
dol_getIdFromCode($db, $key, $tablename, $fieldkey='code', $fieldid='id', $entityfilter=0, $filters='')
Return an id or code from a code or id.
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
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:120