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