dolibarr 23.0.3
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-2025 Frédéric France <frederic.france@free.fr>
7 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.com>
8 * Copyright (C) 2024-2026 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 $total_ht;
121 public $total_tva;
125 public $total_localtax1;
129 public $total_localtax2;
133 public $total_ttc;
137 public $revenuestamp;
138
142 public $totalpaid;
146 public $totaldeposits;
150 public $totalcreditnotes;
151
155 public $remaintopay;
159 public $nbofopendirectdebitorcredittransfer;
160
164 public $creditnote_ids;
165
169 public $stripechargedone;
170
174 public $stripechargeerror;
175
179 public $payment_reference;
180
184 public $dispute_status = 0;
185
186
191 public $description;
192
198 public $ref_client;
199
203 public $situation_cycle_ref;
204
210 public $close_code;
211
216 public $close_note;
217
218
223 public $postactionmessages;
224
225
229 const TYPE_STANDARD = 0;
230
235
240
244 const TYPE_DEPOSIT = 3;
245
250 const TYPE_PROFORMA = 4;
251
255 const TYPE_SITUATION = 5;
256
260 const STATUS_DRAFT = 0;
261
266
274 const STATUS_CLOSED = 2;
275
284
285
286 const CLOSECODE_DISCOUNTVAT = 'discount_vat'; // Abandoned remain - escompte
287 const CLOSECODE_BADDEBT = 'badcustomer'; // Abandoned remain - bad customer
288 const CLOSECODE_BANKCHARGE = 'bankcharge'; // Abandoned remain - bank charge
289 const CLOSECODE_WITHHOLDINGTAX = 'withholdingtax'; // Abandoned remain - source tax
290 const CLOSECODE_OTHER = 'other'; // Abandoned remain - other
291
292 const CLOSECODE_ABANDONED = 'abandon'; // Abandoned - other
293 const CLOSECODE_REPLACED = 'replaced'; // Closed after doing a replacement invoice
294
295
303 public function getRemainToPay($multicurrency = 0)
304 {
305 $alreadypaid = 0.0;
306 $alreadypaid += $this->getSommePaiement($multicurrency);
307 $alreadypaid += $this->getSumDepositsUsed($multicurrency);
308 $alreadypaid += $this->getSumCreditNotesUsed($multicurrency);
309
310 if ((int) $multicurrency > 0) {
311 $totalamount = $this->multicurrency_total_ttc;
312 } else {
313 $totalamount = $this->total_ttc;
314 }
315 $remaintopay = price2num($totalamount - $alreadypaid, 'MT');
316 if ($this->status == self::STATUS_CLOSED && $this->close_code == 'discount_vat') { // If invoice closed with discount for anticipated payment
317 $remaintopay = 0.0;
318 }
319 return $remaintopay;
320 }
321
331 public function getSommePaiement($multicurrency = 0)
332 {
333 $table = 'paiement_facture';
334 $field = 'fk_facture';
335 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
336 $table = 'paiementfourn_facturefourn';
337 $field = 'fk_facturefourn';
338 }
339
340 $sql = "SELECT sum(amount) as amount, sum(multicurrency_amount) as multicurrency_amount";
341 $sql .= " FROM ".$this->db->prefix().$table;
342 $sql .= " WHERE ".$field." = ".((int) $this->id);
343
344 dol_syslog(get_class($this)."::getSommePaiement", LOG_DEBUG);
345
346 $resql = $this->db->query($sql);
347 if ($resql) {
348 $obj = $this->db->fetch_object($resql);
349
350 $this->db->free($resql);
351
352 if ($obj) {
353 if ($multicurrency < 0) {
354 $this->totalpaid = $obj->amount;
355 $this->totalpaid_multicurrency = $obj->multicurrency_amount;
356 return array('alreadypaid' => (float) $obj->amount, 'alreadypaid_multicurrency' => (float) $obj->multicurrency_amount);
357 } elseif ($multicurrency) {
358 $this->totalpaid_multicurrency = $obj->multicurrency_amount;
359 return (float) $obj->multicurrency_amount;
360 } else {
361 $this->totalpaid = $obj->amount;
362 return (float) $obj->amount;
363 }
364 } else {
365 return 0;
366 }
367 } else {
368 $this->error = $this->db->lasterror();
369 return -1;
370 }
371 }
372
382 public function getSumDepositsUsed($multicurrency = 0)
383 {
384 /*if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
385 // 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.
386 return 0.0;
387 }*/
388
389 require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
390
391 $discountstatic = new DiscountAbsolute($this->db);
392 $result = $discountstatic->getSumDepositsUsed($this, $multicurrency);
393
394 if ($result >= 0) {
395 if ($multicurrency) {
396 $this->totaldeposits_multicurrency = $result;
397 } else {
398 $this->totaldeposits = $result;
399 }
400
401 return $result;
402 } else {
403 $this->error = $discountstatic->error;
404 return -1;
405 }
406 }
407
415 public function getSumCreditNotesUsed($multicurrency = 0)
416 {
417 require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
418
419 $discountstatic = new DiscountAbsolute($this->db);
420 $result = $discountstatic->getSumCreditNotesUsed($this, $multicurrency);
421 if (is_numeric($result)) {
422 if ($multicurrency) {
423 $this->totalcreditnotes_multicurrency = $result;
424 } else {
425 $this->totalcreditnotes = $result;
426 }
427
428 return $result;
429 } else {
430 $this->error = $discountstatic->error;
431 return $result;
432 }
433 }
434
441 public function getSumFromThisCreditNotesNotUsed($multicurrency = 0)
442 {
443 require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
444
445 $discountstatic = new DiscountAbsolute($this->db);
446 $result = $discountstatic->getSumFromThisCreditNotesNotUsed($this, $multicurrency);
447 if ($result >= 0) {
448 return $result;
449 } else {
450 $this->error = $discountstatic->error;
451 return -1;
452 }
453 }
454
461 {
462 $idarray = array();
463
464 $sql = "SELECT rowid";
465 $sql .= " FROM ".$this->db->prefix().$this->table_element;
466 $sql .= " WHERE fk_facture_source = ".((int) $this->id);
467 $sql .= " AND type = ".self::TYPE_CREDIT_NOTE;
468
469 $resql = $this->db->query($sql);
470 if ($resql) {
471 $num = $this->db->num_rows($resql);
472 $i = 0;
473 while ($i < $num) {
474 $row = $this->db->fetch_row($resql);
475 $idarray[] = $row[0];
476 $i++;
477 }
478 } else {
479 dol_print_error($this->db);
480 }
481
482 $this->creditnote_ids = $idarray;
483
484 return $idarray;
485 }
486
493 public function getIdReplacingInvoice($option = '')
494 {
495 $sql = "SELECT rowid";
496 $sql .= " FROM ".$this->db->prefix().$this->table_element;
497 $sql .= " WHERE fk_facture_source = ".((int) $this->id);
498 $sql .= " AND type < 2";
499 if ($option == 'validated') {
500 $sql .= ' AND fk_statut = 1';
501 }
502 // PROTECTION BAD DATA
503 // In case the database is corrupted and there is a valid replectement invoice
504 // and another no, priority is given to the valid one.
505 // Should not happen (unless concurrent access and 2 people have created a
506 // replacement invoice for the same invoice at the same time)
507 $sql .= " ORDER BY fk_statut DESC";
508
509 $resql = $this->db->query($sql);
510 if ($resql) {
511 $obj = $this->db->fetch_object($resql);
512 if ($obj) {
513 // If there is any
514 return $obj->rowid;
515 } else {
516 // If no invoice replaces it
517 return 0;
518 }
519 } else {
520 return -1;
521 }
522 }
523
531 {
532 $listofopendirectdebitorcredittransfer = array();
533
534 // TODO Add a cache to store array of open requests for each invoice ID
535
536 $sql = "SELECT pfd.rowid, pfd.traite, pfd.date_demande as date_demande, pfd.date_traite as date_traite, pfd.amount";
537 $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as pfd";
538 if ($type == 'bank-transfer') {
539 $sql .= " WHERE fk_facture_fourn = ".((int) $this->id);
540 } else {
541 $sql .= " WHERE fk_facture = ".((int) $this->id);
542 }
543 $sql .= " AND pfd.traite = 0";
544 $sql .= " AND pfd.type = 'ban'";
545 $sql .= " ORDER BY pfd.date_demande DESC";
546
547 $resql = $this->db->query($sql);
548 $num = 0;
549 if ($resql) {
550 $num = $this->db->num_rows($resql);
551 $i = 0;
552 while ($i < $num) {
553 $obj = $this->db->fetch_object($resql);
554 if ($obj) {
555 $listofopendirectdebitorcredittransfer[] = array(
556 'id' => (int) $obj->rowid,
557 'invoiceid' => (int) $this->id,
558 'date' => $this->db->jdate($obj->date_demande),
559 'amount' => (float) $obj->amount
560 );
561 }
562
563 $i++;
564 }
565 } else {
566 $this->error = $this->db->lasterror();
567 }
568
569 $this->nbofopendirectdebitorcredittransfer = $num;
570
571 return $listofopendirectdebitorcredittransfer;
572 }
573
584 public function getListOfPayments($filtertype = '', $multicurrency = 0, $mode = 0)
585 {
586 $retarray = array();
587 $this->error = ''; // By default no error, list can be empty.
588
589 $table = 'paiement_facture';
590 $table2 = 'paiement';
591 $field = 'fk_facture';
592 $field2 = 'fk_paiement';
593 $field3 = ', p.ref_ext';
594 $field4 = ', p.fk_bank'; // Bank line id
595 $sharedentity = 'facture';
596 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
597 $table = 'paiementfourn_facturefourn';
598 $table2 = 'paiementfourn';
599 $field = 'fk_facturefourn';
600 $field2 = 'fk_paiementfourn';
601 $field3 = '';
602 $sharedentity = 'facture_fourn';
603 }
604
605 // List of payments
606 if (empty($mode) || $mode == 1) {
607 $sql = "SELECT p.ref, pf.amount, pf.multicurrency_amount, p.fk_paiement, p.datep, p.num_paiement as num, t.code".$field3 . $field4;
608 $sql .= " FROM ".$this->db->prefix().$table." as pf, ".$this->db->prefix().$table2." as p, ".$this->db->prefix()."c_paiement as t";
609 $sql .= " WHERE pf.".$field." = ".((int) $this->id);
610 $sql .= " AND pf.".$field2." = p.rowid";
611 $sql .= ' AND p.fk_paiement = t.id';
612 $sql .= ' AND p.entity IN ('.getEntity($sharedentity).')';
613 if ($filtertype) {
614 $sql .= " AND t.code='PRE'";
615 }
616
617 dol_syslog(get_class($this)."::getListOfPayments filtertype=".$filtertype." multicurrency=".$multicurrency." mode=".$mode, LOG_DEBUG);
618
619 $resql = $this->db->query($sql);
620 if ($resql) {
621 $num = $this->db->num_rows($resql);
622 $i = 0;
623 while ($i < $num) {
624 $obj = $this->db->fetch_object($resql);
625 if ($multicurrency) {
626 $tmp = array('amount' => $obj->multicurrency_amount, 'type' => $obj->code, 'typeline' => 'payment', 'date' => $obj->datep, 'num' => $obj->num, 'ref' => $obj->ref);
627 } else {
628 $tmp = array('amount' => $obj->amount, 'type' => $obj->code, 'typeline' => 'payment', 'date' => $obj->datep, 'num' => $obj->num, 'ref' => $obj->ref);
629 }
630 if (!empty($field3)) {
631 $tmp['ref_ext'] = $obj->ref_ext;
632 }
633 if (!empty($field4)) {
634 $tmp['fk_bank_line'] = $obj->fk_bank;
635 }
636 $retarray[] = $tmp;
637 $i++;
638 }
639 $this->db->free($resql);
640 } else {
641 $this->error = $this->db->lasterror();
642 dol_print_error($this->db);
643 return array();
644 }
645 }
646
647 // Look for credit notes and discounts and deposits
648 if (empty($mode) || $mode == 2) {
649 $sql = '';
650 if ($this->element == 'facture' || $this->element == 'invoice') {
651 $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";
652 $sql .= ' FROM '.$this->db->prefix().'societe_remise_except as rc, '.$this->db->prefix().'facture as f';
653 $sql .= ' WHERE rc.fk_facture_source=f.rowid AND rc.fk_facture = '.((int) $this->id);
654 $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)
655 } elseif ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
656 $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";
657 $sql .= ' FROM '.$this->db->prefix().'societe_remise_except as rc, '.$this->db->prefix().'facture_fourn as f';
658 $sql .= ' WHERE rc.fk_invoice_supplier_source=f.rowid AND rc.fk_invoice_supplier = '.((int) $this->id);
659 $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)
660 }
661
662 if ($sql) {
663 $resql = $this->db->query($sql);
664 if ($resql) {
665 $num = $this->db->num_rows($resql);
666 $i = 0;
667 while ($i < $num) {
668 $obj = $this->db->fetch_object($resql);
669 if ($multicurrency) {
670 $retarray[] = array('amount' => $obj->multicurrency_amount, 'type' => $obj->type, 'typeline' => 'discount', 'date' => $obj->date, 'num' => '0', 'ref' => $obj->ref);
671 } else {
672 $retarray[] = array('amount' => $obj->amount, 'type' => $obj->type, 'typeline' => 'discount', 'date' => $obj->date, 'num' => '', 'ref' => $obj->ref);
673 }
674 $i++;
675 }
676 } else {
677 $this->error = $this->db->lasterror();
678 dol_print_error($this->db);
679 return array();
680 }
681 $this->db->free($resql);
682 } else {
683 $this->error = $this->db->lasterror();
684 dol_print_error($this->db);
685 return array();
686 }
687 }
688
689 return $retarray;
690 }
691
692 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
707 public function isEditable()
708 {
709 // phpcs:enable
710 global $hookmanager, $action;
711
712 // We check if invoice is a temporary number (PROVxxxx)
713 $tmppart = substr($this->ref, 1, 4);
714
715 if ($this->status == self::STATUS_DRAFT && $tmppart === 'PROV') { // If draft invoice and ref not yet defined
716 return 1;
717 }
718
719 /*
720 if (getDolGlobalInt('INVOICE_CAN_NEVER_BE_REMOVED')) {
721 return 0;
722 }
723 */
724
725 // If not a draft invoice and not temporary invoice
726 if ($tmppart !== 'PROV') {
727 if ($this instanceOf Facture) {
728 /* @var Facture $this */
729 // If sent by email, we refuse
730 if ((int) $this->email_sent_counter > 0) {
731 return -5;
732 }
733
734 // If printed, we refuse
735 if ((int) $this->pos_print_counter > 0) {
736 return -6;
737 }
738
739 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
740 if (isALNERunningVersion()) {
741 $this->error = 'Action not allowed on the certified version';
742 return -7;
743 }
744 }
745
746 // If in accountancy, we refuse
747 $ventilExportCompta = $this->getVentilExportCompta();
748 if ($ventilExportCompta != 0) {
749 return -1;
750 }
751
752 // Get last number of validated invoice
753 if ($this->element != 'invoice_supplier') {
754 /*
755 if (empty($this->thirdparty)) {
756 $this->fetch_thirdparty(); // We need to have this->thirdparty defined, in case of the numbering rule uses tags that depend on thirdparty (like {t} tag).
757 }
758 $maxref = $this->getNextNumRef($this->thirdparty, 'last');
759 // $maxref can be '' (means not found) if there is no invoice yet, but also if there is no invoice for the new period when there is a reset at each period
760
761 // If invoice to delete is not the last one, we refuse
762 if ($maxref != '' && $maxref != $this->ref) {
763 return -2;
764 }
765 */
766
767 // TODO If there is payment in bookkeeping, check the payment is not dispatched in accounting and return -2.
768 // ...
769
770 // If invoice is situation type, we refuse if it is not the last in situation cycle
771 if (!getDolGlobalString('INVOICE_SITUATION_CAN_BE_REMOVED_EVEN_IF_NOT_LAST') && $this->situation_cycle_ref && method_exists($this, 'is_last_in_cycle')) {
772 $last = $this->is_last_in_cycle();
773 if (!$last) {
774 return -3;
775 }
776 }
777 }
778
779 // Test if there is at least one payment. If yes, we refuse.
780 if ($this->getSommePaiement() > 0) {
781 return -4;
782 }
783
784 $parameters = array();
785 $reshook = $hookmanager->executeHooks('isEditable', $parameters, $this, $action);
786 if (!empty($hookmanager->resArray['result'])) {
787 $this->error = $hookmanager->resArray['error'];
788 if (!empty($hookmanager->resArray['errors'])) {
789 $this->errors = array_merge($this->errors, $hookmanager->resArray['errors']);
790 }
791 if (!in_array($this->error, $this->errors)) {
792 $this->errors[] = $this->error;
793 }
794 return $hookmanager->resArray['result'];
795 }
796 }
797
798 return 2;
799 }
800
801 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
819 public function is_erasable()
820 {
821 // phpcs:enable
822 global $hookmanager, $action;
823
824 // We check if invoice is a temporary number (PROVxxxx)
825 $tmppart = substr($this->ref, 1, 4);
826
827 if ($this->status == self::STATUS_DRAFT && $tmppart === 'PROV') { // If draft invoice and ref not yet defined
828 return 1;
829 }
830
831 if (getDolGlobalInt('INVOICE_CAN_NEVER_BE_REMOVED')) {
832 return 0;
833 }
834
835 // If not a draft invoice and not temporary invoice
836 if ($tmppart !== 'PROV') {
837 if ($this instanceOf Facture) {
838 /* @var Facture $this */
839 // If sent by email, we refuse
840 if ((int) $this->email_sent_counter > 0) {
841 return -5;
842 }
843
844 // If printed, we refuse
845 if ((int) $this->pos_print_counter > 0) {
846 return -6;
847 }
848
849 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
850 if (isALNERunningVersion()) {
851 $this->error = 'Action not allowed on the certified version';
852 return -7;
853 }
854 }
855
856 // If in accountancy, we refuse
857 $ventilExportCompta = $this->getVentilExportCompta();
858 if ($ventilExportCompta != 0) {
859 return -1;
860 }
861
862 // Get last number of validated invoice
863 if ($this->element != 'invoice_supplier') {
864 if (empty($this->thirdparty)) {
865 $this->fetch_thirdparty(); // We need to have this->thirdparty defined, in case of the numbering rule uses tags that depend on thirdparty (like {t} tag).
866 }
867 $maxref = $this->getNextNumRef($this->thirdparty, 'last');
868 // $maxref can be '' (means not found) if there is no invoice yet, but also if there is no invoice for the new period when there is a reset at each period
869
870 // If invoice to delete is not the last one, we refuse
871 if ($maxref != '' && $maxref != $this->ref) {
872 return -2;
873 }
874
875 // TODO If there is payment in bookkeeping, check the payment is not dispatched in accounting and return -2.
876 // ...
877
878 // If invoice is situation type, we refuse if it is not the last in situation cycle
879 if (!getDolGlobalString('INVOICE_SITUATION_CAN_BE_REMOVED_EVEN_IF_NOT_LAST') && $this->situation_cycle_ref && method_exists($this, 'is_last_in_cycle')) {
880 $last = $this->is_last_in_cycle();
881 if (!$last) {
882 return -3;
883 }
884 }
885 }
886
887 // Test if there is at least one payment. If yes, we refuse.
888 if ($this->getSommePaiement() > 0) {
889 return -4;
890 }
891
892 $parameters = array();
893 $reshook = $hookmanager->executeHooks('isErasable', $parameters, $this, $action);
894 if (!empty($hookmanager->resArray['result'])) {
895 $this->error = $hookmanager->resArray['error'];
896 if (!empty($hookmanager->resArray['errors'])) {
897 $this->errors = array_merge($this->errors, $hookmanager->resArray['errors']);
898 }
899 if (!in_array($this->error, $this->errors)) {
900 $this->errors[] = $this->error;
901 }
902 return $hookmanager->resArray['result'];
903 }
904 }
905
906 return 2;
907 }
908
916 public function getVentilExportCompta($mode = 0)
917 {
918 if (!isModEnabled('accounting')) {
919 return 0;
920 }
921
922 $alreadydispatched = 0;
923
924 $type = 'customer_invoice';
925 if ($this->element == 'invoice_supplier') {
926 $type = 'supplier_invoice';
927 }
928
929 $sql = " SELECT ".($mode ? 'DISTINCT piece_num' : 'COUNT(ab.rowid)')." as nb";
930 $sql .= " FROM ".$this->db->prefix()."accounting_bookkeeping as ab";
931 $sql .= " WHERE ab.doc_type = '".$this->db->escape($type)."' AND ab.fk_doc = ".((int) $this->id);
932
933 $resql = $this->db->query($sql);
934 if ($resql) {
935 $obj = $this->db->fetch_object($resql);
936 if ($obj) {
937 $alreadydispatched = $obj->nb;
938 }
939 } else {
940 $this->error = $this->db->lasterror();
941 return -1;
942 }
943
944 if ($alreadydispatched) {
945 return $alreadydispatched;
946 }
947 return 0;
948 }
949
957 public function getNextNumRef($soc, $mode = 'next')
958 {
959 // TODO Must be implemented into main class
960 return '';
961 }
962
969 public function getLibType($withbadge = 0)
970 {
971 global $langs;
972
973 $labellong = "Unknown";
974 $labelshort = "Unknown";
975 if ($this->type == CommonInvoice::TYPE_STANDARD) {
976 $labellong = "InvoiceStandard";
977 $labelshort = "InvoiceStandardShort";
978 } elseif ($this->type == CommonInvoice::TYPE_REPLACEMENT) {
979 $labellong = "InvoiceReplacement";
980 $labelshort = "InvoiceReplacementShort";
981 } elseif ($this->type == CommonInvoice::TYPE_CREDIT_NOTE) {
982 $labellong = "InvoiceAvoir";
983 $labelshort = "CreditNote";
984 } elseif ($this->type == CommonInvoice::TYPE_DEPOSIT) {
985 $labellong = "InvoiceDeposit";
986 $labelshort = "Deposit";
987 } elseif ($this->type == CommonInvoice::TYPE_PROFORMA) { // @phan-suppress-current-line PhanDeprecatedClassConstant
988 $labellong = "InvoiceProForma"; // Not used.
989 $labelshort = "ProForma";
990 } elseif ($this->type == CommonInvoice::TYPE_SITUATION) {
991 $labellong = "InvoiceSituation";
992 $labelshort = "Situation";
993 }
994
995 $out = '';
996 if ($withbadge) {
997 $out .= '<span class="badgeneutral" title="'.dol_escape_htmltag($langs->trans($labellong)).'">';
998 }
999 $out .= $langs->trans($withbadge == 2 ? $labelshort : $labellong);
1000 if ($withbadge) {
1001 $out .= '</span>';
1002 }
1003 return $out;
1004 }
1005
1012 public function getSubtypeLabel($table = '')
1013 {
1014 $subtypeLabel = '';
1015 if ($table === 'facture' || $table === 'facture_fourn') {
1016 $sql = "SELECT s.label FROM " . $this->db->prefix() . $table . " AS f";
1017 $sql .= " INNER JOIN " . $this->db->prefix() . "c_invoice_subtype AS s ON f.subtype = s.rowid";
1018 $sql .= " WHERE f.ref = '".$this->db->escape($this->ref)."'";
1019 } elseif ($table === 'facture_rec' || $table === 'facture_fourn_rec') {
1020 $sql = "SELECT s.label FROM " . $this->db->prefix() . $table . " AS f";
1021 $sql .= " INNER JOIN " . $this->db->prefix() . "c_invoice_subtype AS s ON f.subtype = s.rowid";
1022 $sql .= " WHERE f.titre = '".$this->db->escape($this->title)."'";
1023 } else {
1024 return -1;
1025 }
1026
1027 $resql = $this->db->query($sql);
1028 if ($resql) {
1029 while ($obj = $this->db->fetch_object($resql)) {
1030 $subtypeLabel = $obj->label;
1031 }
1032 } else {
1033 dol_print_error($this->db);
1034 return -1;
1035 }
1036
1037 return $subtypeLabel;
1038 }
1039
1046 public function getArrayOfInvoiceSubtypes($mode = 0)
1047 {
1048 global $mysoc;
1049
1050 $effs = array();
1051
1052 $sql = "SELECT rowid, code, label as label";
1053 $sql .= " FROM " . MAIN_DB_PREFIX . 'c_invoice_subtype';
1054 $sql .= " WHERE active = 1 AND fk_country = ".((int) $mysoc->country_id)." AND entity IN(".getEntity('c_invoice_subtype').")";
1055 $sql .= " ORDER by rowid, code";
1056
1057 dol_syslog(get_class($this) . '::getArrayOfInvoiceSubtypes', LOG_DEBUG);
1058 $resql = $this->db->query($sql);
1059 if ($resql) {
1060 $num = $this->db->num_rows($resql);
1061 $i = 0;
1062
1063 while ($i < $num) {
1064 $objp = $this->db->fetch_object($resql);
1065 if (!$mode) {
1066 $key = $objp->rowid;
1067 $effs[$key] = $objp->label;
1068 } else {
1069 $key = $objp->code;
1070 $effs[$key] = $objp->rowid;
1071 }
1072
1073 $i++;
1074 }
1075 $this->db->free($resql);
1076 }
1077
1078 return $effs;
1079 }
1080
1088 public function getLibStatut($mode = 0, $alreadypaid = -1)
1089 {
1090 $moreparams = array(
1091 'nbofopendirectdebitorcredittransfer' => $this->nbofopendirectdebitorcredittransfer,
1092 'close_code' => $this->close_code,
1093 'close_note' => $this->close_note,
1094 'dispute_status' => $this->dispute_status
1095 );
1096
1097 return $this->LibStatut($this->paye, $this->status, $mode, $alreadypaid, $this->type, $moreparams);
1098 }
1099
1100 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1112 public function LibStatut($paye, $status, $mode = 0, $alreadypaid = -1, $type = -1, $moreparams = array())
1113 {
1114 // phpcs:enable
1115 global $langs, $hookmanager;
1116 $langs->load('bills');
1117
1118 if ($type == -1) {
1119 $type = $this->type;
1120 }
1121
1122 // For backward compatibility
1123 if (is_int($moreparams)) {
1124 $moreparams = array('nbofopendirectdebitorcredittransfer' => (int) $moreparams);
1125 }
1126
1127 $nbofopendirectdebitorcredittransfer = 0;
1128 foreach ($moreparams as $moreparamkey => $moreparamvalue) {
1129 if ($moreparamkey == 'nbofopendirectdebitorcredittransfer') {
1130 $nbofopendirectdebitorcredittransfer = $moreparamvalue;
1131 }
1132 }
1133
1134 $statusType = 'status0';
1135 $prefix = 'Short';
1136 if (!$paye) {
1137 if ($status == 0) {
1138 $labelStatus = $langs->transnoentitiesnoconv('BillStatusDraft');
1139 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusDraft');
1140 } elseif (($status == 3 || $status == 2) && $alreadypaid <= 0) {
1141 if ($status == 3) {
1142 $labelStatus = $langs->transnoentitiesnoconv('BillStatusCanceled');
1143 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusCanceled');
1144 } else {
1145 $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedUnpaid');
1146 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedUnpaid');
1147 }
1148 $statusType = 'status5';
1149 } elseif (($status == 3 || $status == 2) && $alreadypaid > 0) {
1150 $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedPaidPartially');
1151 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedPaidPartially');
1152 $statusType = 'status9';
1153 } elseif ($alreadypaid == 0 && $nbofopendirectdebitorcredittransfer == 0) {
1154 $labelStatus = $langs->transnoentitiesnoconv('BillStatusNotPaid');
1155 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusNotPaid');
1156 $statusType = 'status1';
1157 } else {
1158 $labelStatus = $langs->transnoentitiesnoconv('BillStatusStarted');
1159 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusStarted');
1160 $statusType = 'status3';
1161 }
1162 } else {
1163 $statusType = 'status6';
1164 if ($type == self::TYPE_CREDIT_NOTE) {
1165 $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaidBackOrConverted'); // credit note
1166 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaidBackOrConverted'); // credit note
1167 } elseif ($type == self::TYPE_DEPOSIT) {
1168 $labelStatus = $langs->transnoentitiesnoconv('BillStatusConverted'); // deposit invoice
1169 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusConverted'); // deposit invoice
1170 } else {
1171 $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaid');
1172 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaid');
1173 }
1174 }
1175
1176 $paramsBadge = array('badgeParams' => array('attr' => array(
1177 'data-status-element' => $this->element,
1178 'data-already-paid' => $alreadypaid > 0 ? 1 : 0,
1179 'data-status' => (int) $status
1180 )));
1181
1182 $parameters = array(
1183 'status' => $status,
1184 'mode' => $mode,
1185 'paye' => $paye,
1186 'alreadypaid' => $alreadypaid,
1187 'type' => $type,
1188 'paramsBadge'=>& $paramsBadge
1189 );
1190
1191 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
1192
1193 if ($reshook > 0) {
1194 return $hookmanager->resPrint;
1195 }
1196
1197 if (!empty($moreparams['close_code'])) {
1198 $titlestringtoshow = '';
1199
1200 if ($moreparams['close_code'] == self::CLOSECODE_DISCOUNTVAT) {
1201 $titlestringtoshow = $langs->trans("HelpEscompte");
1202 } elseif ($moreparams['close_code'] == self::CLOSECODE_BADDEBT) {
1203 $titlestringtoshow = $langs->trans("ConfirmClassifyPaidPartiallyReasonBadCustomer");
1204 } elseif ($moreparams['close_code'] == self::CLOSECODE_BANKCHARGE) {
1205 $titlestringtoshow = $langs->trans("ConfirmClassifyPaidPartiallyReasonBankCharge");
1206 } elseif ($moreparams['close_code'] == self::CLOSECODE_WITHHOLDINGTAX) {
1207 $titlestringtoshow = $langs->trans("ConfirmClassifyPaidPartiallyReasonWithholdingTax");
1208 } elseif ($moreparams['close_code'] == self::CLOSECODE_OTHER) {
1209 $titlestringtoshow = $langs->trans("Other");
1210 }
1211
1212 //$paramsbutton = array('badgeParams' => array('attr' => array('title' => 'rrrr')));
1213 $paramsBadge['badgeParams' ]['attr']['title'] = $titlestringtoshow;
1214 }
1215
1216 if (isset($moreparams['dispute_status']) && $moreparams['dispute_status']) {
1217 $labelStatus .= ' - ';
1218 if ($moreparams['dispute_status'] == 8) {
1219 $labelStatus .= $langs->trans("DisputeLost");
1220 } elseif ($moreparams['dispute_status'] == 9) {
1221 $labelStatus .= $langs->trans("DisputeWon");
1222 } else {
1223 $labelStatus .= $langs->trans("DisputeOpen");
1224 $statusType = 'status8';
1225 }
1226 }
1227
1228 $statusbadge = dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode, '', $paramsBadge);
1229
1230 return $statusbadge;
1231 }
1232
1233 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1241 public function calculate_date_lim_reglement($cond_reglement = 0)
1242 {
1243 // phpcs:enable
1244 if (!$cond_reglement) {
1245 $cond_reglement = $this->cond_reglement_code;
1246 }
1247 if (!$cond_reglement) {
1248 $cond_reglement = $this->cond_reglement_id;
1249 }
1250 if (!$cond_reglement) {
1251 return $this->date;
1252 }
1253
1254 $cdr_nbjour = 0;
1255 $cdr_type = 0;
1256 $cdr_decalage = 0;
1257
1258 $sqltemp = "SELECT c.type_cdr, c.nbjour, c.decalage";
1259 $sqltemp .= " FROM ".$this->db->prefix()."c_payment_term as c";
1260 if (is_numeric($cond_reglement)) {
1261 $sqltemp .= " WHERE c.rowid=".((int) $cond_reglement);
1262 } else {
1263 $sqltemp .= " WHERE c.entity IN (".getEntity('c_payment_term').")";
1264 $sqltemp .= " AND c.code = '".$this->db->escape($cond_reglement)."'";
1265 }
1266
1267 dol_syslog(get_class($this).'::calculate_date_lim_reglement', LOG_DEBUG);
1268 $resqltemp = $this->db->query($sqltemp);
1269 if ($resqltemp) {
1270 if ($this->db->num_rows($resqltemp)) {
1271 $obj = $this->db->fetch_object($resqltemp);
1272 $cdr_nbjour = $obj->nbjour;
1273 $cdr_type = $obj->type_cdr;
1274 $cdr_decalage = $obj->decalage;
1275 }
1276 } else {
1277 $this->error = $this->db->error();
1278 return -1;
1279 }
1280 $this->db->free($resqltemp);
1281
1282 /* Define date limit */
1283
1284 // 0 : adding the number of days
1285 if ($cdr_type == 0) {
1286 $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
1287
1288 $datelim += ($cdr_decalage * 3600 * 24);
1289 } elseif ($cdr_type == 1) {
1290 // 1 : application of the "end of the month" rule
1291 $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
1292
1293 $mois = date('m', $datelim);
1294 $annee = date('Y', $datelim);
1295 if ($mois == 12) {
1296 $mois = 1;
1297 $annee += 1;
1298 } else {
1299 $mois += 1;
1300 }
1301 // We move at the beginning of the next month, and we take a day off
1302 $datelim = dol_mktime(12, 0, 0, $mois, 1, $annee);
1303 $datelim -= (3600 * 24);
1304
1305 $datelim += ($cdr_decalage * 3600 * 24);
1306 } elseif ($cdr_type == 2 && !empty($cdr_decalage)) {
1307 // 2 : application of the rule, the N of the current or next month
1308 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
1309 $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
1310
1311 $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
1312 $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
1313 $date_lim_next = dol_time_plus_duree((int) $date_lim_current, 1, 'm'); // Add 1 month
1314
1315 $diff = $date_piece - $date_lim_current;
1316
1317 if ($diff <= 0) {
1318 $datelim = $date_lim_current;
1319 } else {
1320 $datelim = $date_lim_next;
1321 }
1322 } else {
1323 return 'Bad value for type_cdr in database for record cond_reglement = '.$cond_reglement;
1324 }
1325
1326 return $datelim;
1327 }
1328
1329 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1342 public function demande_prelevement(User $fuser, float $amount = 0, string $type = 'direct-debit', string $sourcetype = 'facture', int $checkduplicateamongall = 0, int $ribId = 0)
1343 {
1344 // phpcs:enable
1345 global $conf;
1346
1347 $error = 0;
1348
1349 dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
1350
1351 if ($this->status > self::STATUS_DRAFT && $this->paye == 0) {
1352 require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
1353 $bac = new CompanyBankAccount($this->db);
1354 $bac->fetch($ribId, '', $this->socid);
1355
1356 $sql = "SELECT count(rowid) as nb";
1357 $sql .= " FROM ".$this->db->prefix()."prelevement_demande";
1358 if ($type == 'bank-transfer') {
1359 $sql .= " WHERE fk_facture_fourn = ".((int) $this->id);
1360 } else {
1361 $sql .= " WHERE fk_facture = ".((int) $this->id);
1362 }
1363 $sql .= " AND type = 'ban'"; // To exclude record done for some online payments
1364 if (empty($checkduplicateamongall)) {
1365 $sql .= " AND traite = 0";
1366 }
1367
1368 dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
1369
1370 $resql = $this->db->query($sql);
1371 if ($resql) {
1372 $obj = $this->db->fetch_object($resql);
1373 if ($obj && $obj->nb == 0) { // If no request found yet
1374 $now = dol_now();
1375
1376 $totalpaid = $this->getSommePaiement();
1377 $totalcreditnotes = $this->getSumCreditNotesUsed();
1378 $totaldeposits = $this->getSumDepositsUsed();
1379 //print "totalpaid=".$totalpaid." totalcreditnotes=".$totalcreditnotes." totaldeposts=".$totaldeposits;
1380
1381 // We can also use bcadd to avoid pb with floating points
1382 // For example print 239.2 - 229.3 - 9.9; does not return 0.
1383 //$resteapayer=bcadd($this->total_ttc,$totalpaid,$conf->global->MAIN_MAX_DECIMALS_TOT);
1384 //$resteapayer=bcadd($resteapayer,$totalavoir,$conf->global->MAIN_MAX_DECIMALS_TOT);
1385 if (empty($amount)) {
1386 $amount = price2num($this->total_ttc - $totalpaid - $totalcreditnotes - $totaldeposits, 'MT');
1387 }
1388
1389 if (is_numeric($amount) && $amount != 0) {
1390 $sql = 'INSERT INTO '.$this->db->prefix().'prelevement_demande(';
1391 if ($type == 'bank-transfer') {
1392 $sql .= 'fk_facture_fourn, ';
1393 } else {
1394 $sql .= 'fk_facture, ';
1395 }
1396 $sql .= ' amount, date_demande, fk_user_demande, code_banque, code_guichet, number, cle_rib, sourcetype, type, entity';
1397 if (empty($bac->id)) {
1398 $sql .= ')';
1399 } else {
1400 $sql .= ', fk_societe_rib)';
1401 }
1402 $sql .= " VALUES (".((int) $this->id);
1403 $sql .= ", ".((float) price2num($amount));
1404 $sql .= ", '".$this->db->idate($now)."'";
1405 $sql .= ", ".((int) $fuser->id);
1406 $sql .= ", '".$this->db->escape($bac->code_banque)."'";
1407 $sql .= ", '".$this->db->escape($bac->code_guichet)."'";
1408 $sql .= ", '".$this->db->escape($bac->number)."'";
1409 $sql .= ", '".$this->db->escape($bac->cle_rib)."'";
1410 $sql .= ", '".$this->db->escape($sourcetype)."'";
1411 $sql .= ", 'ban'";
1412 $sql .= ", ".((int) $conf->entity);
1413 if (!empty($bac->id)) {
1414 $sql .= ", '".$this->db->escape((string) $bac->id)."'";
1415 }
1416 $sql .= ")";
1417
1418 dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
1419 $resql = $this->db->query($sql);
1420 if (!$resql) {
1421 $this->error = $this->db->lasterror();
1422 dol_syslog(get_class($this).'::demandeprelevement Erreur');
1423 $error++;
1424 }
1425 } else {
1426 $this->error = 'WithdrawRequestErrorNilAmount';
1427 dol_syslog(get_class($this).'::demandeprelevement WithdrawRequestErrorNilAmount');
1428 $error++;
1429 }
1430
1431 if (!$error) {
1432 // Force payment mode of invoice to withdraw
1433 $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
1434 if ($payment_mode_id > 0) {
1435 $result = $this->setPaymentMethods($payment_mode_id);
1436 }
1437 }
1438
1439 if ($error) {
1440 return -1;
1441 }
1442 return 1;
1443 } else {
1444 $this->error = "A request already exists";
1445 dol_syslog(get_class($this).'::demandeprelevement Can t create a request to generate a direct debit, a request already exists.');
1446 return 0;
1447 }
1448 } else {
1449 $this->error = $this->db->error();
1450 dol_syslog(get_class($this).'::demandeprelevement Error -2');
1451 return -2;
1452 }
1453 } else {
1454 $this->error = "Status of invoice does not allow this";
1455 dol_syslog(get_class($this)."::demandeprelevement ".$this->error." $this->status, $this->paye, $this->mode_reglement_id");
1456 return -3;
1457 }
1458 }
1459
1460
1472 public function makeStripeCardRequest($fuser, $id, $sourcetype = 'facture')
1473 {
1474 // TODO See in sellyoursaas
1475 return 0;
1476 }
1477
1490 public function makeStripeSepaRequest($fuser, $did, $type = 'direct-debit', $sourcetype = 'facture', $service = '', $forcestripe = '')
1491 {
1492 global $conf, $user, $langs;
1493
1494 if ($type != 'bank-transfer' && $type != 'credit-transfer' && !getDolGlobalString('STRIPE_SEPA_DIRECT_DEBIT')) {
1495 return 0;
1496 }
1497 if ($type != 'direct-debit' && !getDolGlobalString('STRIPE_SEPA_CREDIT_TRANSFER')) {
1498 return 0;
1499 }
1500 // Set a default value for service if not provided
1501 if (empty($service)) {
1502 $service = 'StripeTest';
1503 if (getDolGlobalString('STRIPE_LIVE')/* && !GETPOST('forcesandbox', 'alpha')*/) {
1504 $service = 'StripeLive';
1505 }
1506 }
1507
1508 $error = 0;
1509
1510 dol_syslog(get_class($this)."::makeStripeSepaRequest start did=".$did." type=".$type." service=".$service." sourcetype=".$sourcetype." forcestripe=".$forcestripe, LOG_DEBUG);
1511
1512 if ($this->status > self::STATUS_DRAFT && $this->paye == 0) {
1513 // Get the default payment mode for BAN payment of the third party
1514 require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
1515 $bac = new CompanyBankAccount($this->db); // Table societe_rib
1516 $result = $bac->fetch(0, '', $this->socid, 1, 'ban');
1517 if ($result <= 0 || empty($bac->id)) {
1518 $this->error = $langs->trans("ThirdpartyHasNoDefaultBankAccount");
1519 $this->errors[] = $this->error;
1520 dol_syslog(get_class($this)."::makeStripeSepaRequest ".$this->error);
1521 return -1;
1522 }
1523
1524 // Load the pending payment request to process (with rowid=$did)
1525 $sql = "SELECT rowid, date_demande, amount, fk_facture, fk_facture_fourn, fk_salary, fk_prelevement_bons";
1526 $sql .= " FROM ".$this->db->prefix()."prelevement_demande";
1527 $sql .= " WHERE rowid = ".((int) $did);
1528 if ($type != 'bank-transfer' && $type != 'credit-transfer') {
1529 $sql .= " AND fk_facture = ".((int) $this->id); // Add a protection to not pay another invoice than current one
1530 }
1531 if ($type != 'direct-debit') {
1532 if ($sourcetype == 'salary') {
1533 $sql .= " AND fk_salary = ".((int) $this->id); // Add a protection to not pay another salary than current one
1534 } else {
1535 $sql .= " AND fk_facture_fourn = ".((int) $this->id); // Add a protection to not pay another invoice than current one
1536 }
1537 }
1538 $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)
1539
1540 dol_syslog(get_class($this)."::makeStripeSepaRequest load requests to process", LOG_DEBUG);
1541 $resql = $this->db->query($sql);
1542 if ($resql) {
1543 $obj = $this->db->fetch_object($resql);
1544 if (!$obj) {
1545 dol_print_error($this->db, 'CantFindRequestWithId');
1546 return -2;
1547 }
1548
1549 // amount to pay
1550 $amount = $obj->amount;
1551
1552 if (is_numeric($amount) && $amount != 0) {
1553 require_once DOL_DOCUMENT_ROOT.'/societe/class/companypaymentmode.class.php';
1554 $companypaymentmode = new CompanyPaymentMode($this->db); // table societe_rib
1555 $companypaymentmode->fetch($bac->id);
1556
1557 $this->stripechargedone = 0;
1558 $this->stripechargeerror = 0;
1559
1560 $now = dol_now();
1561
1562 $currency = $conf->currency;
1563
1564 $errorforinvoice = 0; // We reset the $errorforinvoice at each invoice loop
1565
1566 $this->fetch_thirdparty();
1567
1568 dol_syslog("makeStripeSepaRequest Process payment request amount=".$amount." thirdparty_id=" . $this->thirdparty->id . ", thirdparty_name=" . $this->thirdparty->name . " ban id=" . $bac->id, LOG_DEBUG);
1569
1570 //$alreadypayed = $this->getSommePaiement();
1571 //$amount_credit_notes_included = $this->getSumCreditNotesUsed();
1572 //$amounttopay = $this->total_ttc - $alreadypayed - $amount_credit_notes_included;
1573 $amounttopay = $amount;
1574
1575 // Correct the amount according to unit of currency
1576 // See https://support.stripe.com/questions/which-zero-decimal-currencies-does-stripe-support
1577 $arrayzerounitcurrency = ['BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'];
1578 $amountstripe = $amounttopay;
1579 if (!in_array($currency, $arrayzerounitcurrency)) {
1580 $amountstripe *= 100;
1581 }
1582
1583 $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.
1584 if (!($fk_bank_account > 0)) {
1585 $error++;
1586 $errorforinvoice++;
1587 dol_syslog("makeStripeSepaRequest Error no bank account defined for Stripe payments", LOG_ERR);
1588 $this->error = "Error bank account for Stripe payments not defined into Stripe module";
1589 $this->errors[] = $this->error;
1590 }
1591
1592 $this->db->begin();
1593
1594 // Create a prelevement_bon
1595 require_once DOL_DOCUMENT_ROOT.'/compta/prelevement/class/bonprelevement.class.php';
1596 $bon = new BonPrelevement($this->db);
1597 if (!$error) {
1598 if (empty($obj->fk_prelevement_bons)) {
1599 // This creates a record into llx_prelevement_bons and updates link with llx_prelevement_demande
1600 $nbinvoices = $bon->create('0', '0', 'real', 'ALL', 0, 0, $type, $did, $fk_bank_account);
1601 if ($nbinvoices <= 0) {
1602 $error++;
1603 $errorforinvoice++;
1604 dol_syslog("makeStripeSepaRequest Error on BonPrelevement creation", LOG_ERR);
1605 $this->error = "Error on BonPrelevement creation";
1606 $this->errors[] = $this->error;
1607 }
1608 /*
1609 if (!$error) {
1610 // Update the direct debit payment request of the processed request to save the id of the prelevement_bon
1611 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
1612 $sql .= " fk_prelevement_bons = ".((int) $bon->id);
1613 $sql .= " WHERE rowid = ".((int) $did);
1614
1615 $result = $this->db->query($sql);
1616 if ($result < 0) {
1617 $error++;
1618 $this->error = "Error on updating fk_prelevement_bons to ".$bon->id;
1619 $this->errors[] = $this->error;
1620 }
1621 }
1622 */
1623 } else {
1624 $error++;
1625 $errorforinvoice++;
1626 dol_syslog("makeStripeSepaRequest Error Line already part of a bank payment order", LOG_ERR);
1627 $this->error = "The line is already included into a bank payment order. Delete the bank payment order first.";
1628 $this->errors[] = $this->error;
1629 }
1630 }
1631
1632 $paymentintent = null;
1633 if (!$error) {
1634 if ($amountstripe > 0) {
1635 try {
1636 global $savstripearrayofkeysbyenv;
1637 global $stripearrayofkeysbyenv;
1638 $servicestatus = 0;
1639 if ($service == 'StripeLive') {
1640 $servicestatus = 1;
1641 }
1642
1643 //var_dump($companypaymentmode);
1644 dol_syslog("makeStripeSepaRequest We will try to pay with companypaymentmodeid=" . $companypaymentmode->id . " stripe_card_ref=" . $companypaymentmode->stripe_card_ref . " mode=" . $companypaymentmode->status, LOG_DEBUG);
1645
1646 $thirdparty = new Societe($this->db);
1647 $resultthirdparty = $thirdparty->fetch($this->socid);
1648
1649 include_once DOL_DOCUMENT_ROOT . '/stripe/class/stripe.class.php'; // This include the include of htdocs/stripe/config.php
1650 // So it inits or erases the $stripearrayofkeysbyenv
1651 $stripe = new Stripe($this->db);
1652
1653 if (empty($savstripearrayofkeysbyenv)) {
1654 $savstripearrayofkeysbyenv = $stripearrayofkeysbyenv;
1655 }
1656 dol_syslog("makeStripeSepaRequest Current Stripe environment is " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key']);
1657 dol_syslog("makeStripeSepaRequest Current Saved Stripe environment is ".$savstripearrayofkeysbyenv[$servicestatus]['publishable_key']);
1658
1659 $foundalternativestripeaccount = '';
1660
1661 // Force stripe to another value (by default this value is empty)
1662 if (! empty($forcestripe)) {
1663 dol_syslog("makeStripeSepaRequest A dedicated stripe account was forced, so we switch to it.");
1664
1665 $tmparray = explode('@', $forcestripe);
1666 if (! empty($tmparray[1])) {
1667 $tmparray2 = explode(':', $tmparray[1]);
1668 if (! empty($tmparray2[1])) {
1669 $stripearrayofkeysbyenv[$servicestatus]["publishable_key"] = $tmparray2[0];
1670 $stripearrayofkeysbyenv[$servicestatus]["secret_key"] = $tmparray2[1];
1671
1672 $stripearrayofkeys = $stripearrayofkeysbyenv[$servicestatus];
1673 \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1674
1675 $foundalternativestripeaccount = $tmparray[0]; // Store the customer id
1676
1677 dol_syslog("makeStripeSepaRequest We use now customer=".$foundalternativestripeaccount." publishable_key=".$stripearrayofkeys['publishable_key'], LOG_DEBUG);
1678 }
1679 }
1680
1681 if (! $foundalternativestripeaccount) {
1682 $stripearrayofkeysbyenv = $savstripearrayofkeysbyenv;
1683
1684 $stripearrayofkeys = $savstripearrayofkeysbyenv[$servicestatus];
1685 \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1686 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);
1687 }
1688 } else {
1689 $stripearrayofkeysbyenv = $savstripearrayofkeysbyenv;
1690
1691 $stripearrayofkeys = $savstripearrayofkeysbyenv[$servicestatus];
1692 \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1693 dol_syslog("makeStripeSepaRequest No dedicated Stripe Account requested, so we use global one, so ".$stripearrayofkeys['publishable_key'], LOG_DEBUG);
1694 }
1695
1696 $stripeacc = $stripe->getStripeAccount($service, $this->socid); // Get Stripe OAuth connect account if it exists (no network access here)
1697
1698 if ($foundalternativestripeaccount) {
1699 if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
1700 $customer = \Stripe\Customer::retrieve(array('id' => "$foundalternativestripeaccount", 'expand[]' => 'sources'));
1701 } else {
1702 $customer = \Stripe\Customer::retrieve(array('id' => "$foundalternativestripeaccount", 'expand[]' => 'sources'), array("stripe_account" => $stripeacc));
1703 }
1704 } else {
1705 $customer = $stripe->customerStripe($thirdparty, $stripeacc, $servicestatus, 0);
1706 if (empty($customer) && ! empty($stripe->error)) {
1707 $this->error = $stripe->error;
1708 $this->errors[] = $this->error;
1709 }
1710 /*if (!empty($customer) && empty($customer->sources)) {
1711 $customer = null;
1712 $this->error = '\Stripe\Customer::retrieve did not returned the sources';
1713 $this->errors[] = $this->error;
1714 }*/
1715 }
1716
1717 // $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)
1718 // $nbdaysbeforeendoftries = (empty($conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES) ? 35 : $conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES);
1719 $postactionmessages = [];
1720
1721 if ($resultthirdparty > 0 && !empty($customer)) {
1722 if (!$error) { // Payment was not canceled
1723 $stripecard = null;
1724 if ($companypaymentmode->type == 'ban') {
1725 // Check into societe_rib if a payment mode for Stripe and ban payment exists
1726 // To make a Stripe SEPA payment request, we must have the payment mode source already saved into societe_rib and retrieved with ->sepaStripe
1727 // The payment mode source is created when we create the bank account on Stripe with paymentmodes.php?action=create
1728 $stripecard = $stripe->sepaStripe($customer, $companypaymentmode, $stripeacc, $servicestatus, 0);
1729 } else {
1730 $error++;
1731 $this->error = 'The payment mode type is not "ban"';
1732 }
1733
1734 if ($stripecard) { // Can be src_... (for sepa) or pm_... (new card mode). Note that card_... (old card mode) should not happen here.
1735 $FULLTAG = 'DID='.$did.'-INV=' . $this->id . '-CUS=' . $thirdparty->id;
1736 $description = 'Stripe payment from makeStripeSepaRequest: ' . $FULLTAG . ' did='.$did.' ref=' . $this->ref;
1737
1738 $stripefailurecode = '';
1739 $stripefailuremessage = '';
1740 $stripefailuredeclinecode = '';
1741
1742 // Using new SCA method
1743 dol_syslog("* Create payment on SEPA " . $stripecard->id . ", amounttopay=" . $amounttopay . ", amountstripe=" . $amountstripe . ", FULLTAG=" . $FULLTAG, LOG_DEBUG);
1744
1745 // Create payment intent and charge payment (confirmnow = true)
1746 $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $this, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1, 1, $did);
1747
1748 $charge = new stdClass();
1749
1750 if ($paymentintent->status === 'succeeded' || $paymentintent->status === 'processing') {
1751 $charge->status = 'ok';
1752 $charge->id = $paymentintent->id;
1753 $charge->customer = $customer->id;
1754 } elseif ($paymentintent->status === 'requires_action') {
1755 //paymentintent->status may be => 'requires_action' (no error in such a case)
1756 dol_syslog(var_export($paymentintent, true), LOG_DEBUG);
1757
1758 $charge->status = 'failed';
1759 $charge->customer = $customer->id;
1760 $charge->failure_code = $stripe->code;
1761 $charge->failure_message = $stripe->error;
1762 $charge->failure_declinecode = $stripe->declinecode;
1763 $stripefailurecode = $stripe->code;
1764 $stripefailuremessage = 'Action required. Contact the support at ';// . $conf->global->SELLYOURSAAS_MAIN_EMAIL;
1765 $stripefailuredeclinecode = $stripe->declinecode;
1766 } else {
1767 dol_syslog(var_export($paymentintent, true), LOG_DEBUG);
1768
1769 $charge->status = 'failed';
1770 $charge->customer = $customer->id;
1771 $charge->failure_code = $stripe->code;
1772 $charge->failure_message = $stripe->error;
1773 $charge->failure_declinecode = $stripe->declinecode;
1774 $stripefailurecode = $stripe->code;
1775 $stripefailuremessage = $stripe->error;
1776 $stripefailuredeclinecode = $stripe->declinecode;
1777 }
1778
1779 //var_dump("stripefailurecode=".$stripefailurecode." stripefailuremessage=".$stripefailuremessage." stripefailuredeclinecode=".$stripefailuredeclinecode);
1780 //exit;
1781
1782
1783 // Return $charge = array('id'=>'ch_XXXX', 'status'=>'succeeded|pending|failed', 'failure_code'=>, 'failure_message'=>...)
1784 if (empty($charge) || $charge->status == 'failed') {
1785 dol_syslog('Failed to charge payment mode ' . $stripecard->id . ' stripefailurecode=' . $stripefailurecode . ' stripefailuremessage=' . $stripefailuremessage . ' stripefailuredeclinecode=' . $stripefailuredeclinecode, LOG_WARNING);
1786
1787 // Save a stripe payment was in error
1788 $this->stripechargeerror++;
1789
1790 $error++;
1791 $errorforinvoice++;
1792 $errmsg = $langs->trans("FailedToChargeSEPA");
1793 if (!empty($charge)) {
1794 if ($stripefailuredeclinecode == 'authentication_required') {
1795 $errauthenticationmessage = $langs->trans("ErrSCAAuthentication");
1796 $errmsg = $errauthenticationmessage;
1797 } elseif (in_array($stripefailuredeclinecode, ['insufficient_funds', 'generic_decline'])) {
1798 $errmsg .= ': ' . $charge->failure_code;
1799 $errmsg .= ($charge->failure_message ? ' - ' : '') . ' ' . $charge->failure_message;
1800 if (empty($stripefailurecode)) {
1801 $stripefailurecode = $charge->failure_code;
1802 }
1803 if (empty($stripefailuremessage)) {
1804 $stripefailuremessage = $charge->failure_message;
1805 }
1806 } else {
1807 $errmsg .= ': failure_code=' . $charge->failure_code;
1808 $errmsg .= ($charge->failure_message ? ' - ' : '') . ' failure_message=' . $charge->failure_message;
1809 if (empty($stripefailurecode)) {
1810 $stripefailurecode = $charge->failure_code;
1811 }
1812 if (empty($stripefailuremessage)) {
1813 $stripefailuremessage = $charge->failure_message;
1814 }
1815 }
1816 } else {
1817 $errmsg .= ': ' . $stripefailurecode . ' - ' . $stripefailuremessage;
1818 $errmsg .= ($stripefailuredeclinecode ? ' - ' . $stripefailuredeclinecode : '');
1819 }
1820
1821 $description = 'Stripe payment ERROR from makeStripeSepaRequest: ' . $FULLTAG;
1822 $postactionmessages[] = $errmsg . ' (' . $stripearrayofkeys['publishable_key'] . ')';
1823
1824 $this->error = $errmsg;
1825 $this->errors[] = $this->error;
1826 } else {
1827 dol_syslog('Successfuly request '.$type.' '.$stripecard->id);
1828
1829 $postactionmessages[] = 'Success to request '.$type.' (' . $charge->id . ' with ' . $stripearrayofkeys['publishable_key'] . ')';
1830
1831 // Save a stripe payment was done in real life so later we will be able to force a commit on recorded payments
1832 // even if in batch mode (method doTakePaymentStripe), we will always make all action in one transaction with a forced commit.
1833 $this->stripechargedone++;
1834
1835 // Default description used for label of event. Will be overwrite by another value later.
1836 $description = 'Stripe payment request OK (' . $charge->id . ') from makeStripeSepaRequest: ' . $FULLTAG;
1837 }
1838
1839 $object = $this;
1840
1841 // Track an event
1842 if (empty($charge) || $charge->status == 'failed') {
1843 $actioncode = 'PAYMENT_STRIPE_KO';
1844 $extraparams = $stripefailurecode;
1845 $extraparams .= (($extraparams && $stripefailuremessage) ? ' - ' : '') . $stripefailuremessage;
1846 $extraparams .= (($extraparams && $stripefailuredeclinecode) ? ' - ' : '') . $stripefailuredeclinecode;
1847 } else {
1848 $actioncode = 'PAYMENT_STRIPE_OK';
1849 $extraparams = array();
1850 }
1851 } else {
1852 $error++;
1853 $errorforinvoice++;
1854 dol_syslog("No ban payment method found for this stripe customer " . $customer->id, LOG_WARNING);
1855
1856 $this->error = 'Failed to get direct debit payment method for stripe customer = ' . $customer->id;
1857 $this->errors[] = $this->error;
1858
1859 $description = 'Failed to find or use the payment mode - no ban defined for the thirdparty account';
1860 $stripefailurecode = 'BADPAYMENTMODE';
1861 $stripefailuremessage = 'Failed to find or use the payment mode - no ban defined for the thirdparty account';
1862 $postactionmessages[] = $description . ' (' . $stripearrayofkeys['publishable_key'] . ')';
1863
1864 $object = $this;
1865
1866 $actioncode = 'PAYMENT_STRIPE_KO';
1867 $extraparams = array();
1868 }
1869 } else {
1870 // If error because payment was canceled for a logical reason, we do nothing (no event added)
1871 $description = '';
1872 $stripefailurecode = '';
1873 $stripefailuremessage = '';
1874
1875 $object = $this;
1876
1877 $actioncode = '';
1878 $extraparams = array();
1879 }
1880 } else { // Else of the if ($resultthirdparty > 0 && ! empty($customer)) {
1881 if ($resultthirdparty <= 0) {
1882 dol_syslog('SellYourSaasUtils Failed to load customer for thirdparty_id = ' . $thirdparty->id, LOG_WARNING);
1883 $this->error = 'Failed to load Stripe account for thirdparty_id = ' . $thirdparty->id;
1884 $this->errors[] = $this->error;
1885 } else { // $customer stripe not found
1886 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);
1887 $this->error = 'Failed to get Stripe account id for thirdparty_id = ' . $thirdparty->id . " in mode " . $servicestatus . " in Stripe env " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key'];
1888 $this->errors[] = $this->error;
1889 }
1890 $error++;
1891 $errorforinvoice++;
1892
1893 $description = 'Failed to find or use your payment mode (no payment mode for this customer id)';
1894 $stripefailurecode = 'BADPAYMENTMODE';
1895 $stripefailuremessage = 'Failed to find or use your payment mode (no payment mode for this customer id)';
1896 $postactionmessages = [];
1897
1898 $object = $this;
1899
1900 $actioncode = 'PAYMENT_STRIPE_KO';
1901 $extraparams = array();
1902 }
1903
1904 if ($description) {
1905 dol_syslog("* Record event for credit transfer or direct debit request result - " . $description);
1906 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
1907
1908 // Insert record of payment (success or error)
1909 $actioncomm = new ActionComm($this->db);
1910
1911 $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
1912 $actioncomm->code = 'AC_' . $actioncode;
1913 $actioncomm->label = $description;
1914 $actioncomm->note_private = implode(",\n", $postactionmessages);
1915 $actioncomm->fk_project = $this->fk_project;
1916 $actioncomm->datep = $now;
1917 $actioncomm->datef = $now;
1918 $actioncomm->percentage = -1; // Not applicable
1919 $actioncomm->socid = $thirdparty->id;
1920 $actioncomm->contactid = 0;
1921 $actioncomm->authorid = $user->id; // User saving action
1922 $actioncomm->userownerid = $user->id; // Owner of action
1923 // Fields when action is a real email (content is already into note)
1924 /*$actioncomm->email_msgid = $object->email_msgid;
1925 $actioncomm->email_from = $object->email_from;
1926 $actioncomm->email_sender= $object->email_sender;
1927 $actioncomm->email_to = $object->email_to;
1928 $actioncomm->email_tocc = $object->email_tocc;
1929 $actioncomm->email_tobcc = $object->email_tobcc;
1930 $actioncomm->email_subject = $object->email_subject;
1931 $actioncomm->errors_to = $object->errors_to;*/
1932 $actioncomm->fk_element = $this->id;
1933 $actioncomm->elementid = $this->id;
1934 $actioncomm->elementtype = $this->element;
1935 $actioncomm->extraparams = $extraparams; // Can be null, empty string or array()
1936
1937 $actioncomm->create($user);
1938 }
1939
1940 $this->description = $description;
1941 $this->postactionmessages = $postactionmessages;
1942 } catch (Exception $e) {
1943 $error++;
1944 $errorforinvoice++;
1945 dol_syslog('Error ' . $e->getMessage(), LOG_ERR);
1946 $this->error = 'Error ' . $e->getMessage();
1947 $this->errors[] = $this->error;
1948 }
1949 } else { // If remain to pay is null
1950 $error++;
1951 $errorforinvoice++;
1952 dol_syslog("Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?", LOG_WARNING);
1953 $this->error = "Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?";
1954 $this->errors[] = $this->error;
1955 }
1956 }
1957
1958 // Set status of the order to "Transferred" with method 'api'
1959 if (!$error && !$errorforinvoice) {
1960 $result = $bon->set_infotrans($user, $now, 3);
1961 if ($result < 0) {
1962 $error++;
1963 $errorforinvoice++;
1964 dol_syslog("Error on BonPrelevement creation", LOG_ERR);
1965 $this->error = "Error on BonPrelevement creation";
1966 $this->errors[] = $this->error;
1967 }
1968 }
1969
1970 if (!$error && !$errorforinvoice && $paymentintent !== null) {
1971 // Update the direct debit payment request of the processed invoice to save the id of the prelevement_bon
1972 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
1973 $sql .= " ext_payment_id = '".$this->db->escape($paymentintent->id)."',";
1974 $sql .= " ext_payment_site = '".$this->db->escape($service)."'";
1975 $sql .= " WHERE rowid = ".((int) $did);
1976
1977 dol_syslog(get_class($this)."::makeStripeSepaRequest update to save stripe paymentintent ids", LOG_DEBUG);
1978 $resql = $this->db->query($sql);
1979 if (!$resql) {
1980 $this->error = $this->db->lasterror();
1981 dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur');
1982 $error++;
1983 }
1984 }
1985
1986 if (!$error && !$errorforinvoice) {
1987 $this->db->commit();
1988 } else {
1989 $this->db->rollback();
1990 }
1991 } else {
1992 $this->error = 'WithdrawRequestErrorNilAmount';
1993 dol_syslog(get_class($this).'::makeStripeSepaRequest WithdrawRequestErrorNilAmount');
1994 $error++;
1995 }
1996
1997 /*
1998 if (!$error) {
1999 // Force payment mode of the invoice to withdraw
2000 $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
2001 if ($payment_mode_id > 0) {
2002 $result = $this->setPaymentMethods($payment_mode_id);
2003 }
2004 }*/
2005
2006 if ($error) {
2007 return -1;
2008 }
2009 return 1;
2010 } else {
2011 $this->error = $this->db->error();
2012 dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur -2');
2013 return -2;
2014 }
2015 } else {
2016 $this->error = "Status of invoice does not allow this";
2017 dol_syslog(get_class($this)."::makeStripeSepaRequest ".$this->error." ".$this->status." ,".$this->paye.", ".$this->mode_reglement_id, LOG_WARNING);
2018 return -3;
2019 }
2020 }
2021
2022 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2030 public function demande_prelevement_delete($fuser, $did)
2031 {
2032 // phpcs:enable
2033 $sql = 'DELETE FROM '.$this->db->prefix().'prelevement_demande';
2034 $sql .= ' WHERE rowid = '.((int) $did);
2035 $sql .= ' AND traite = 0';
2036 if ($this->db->query($sql)) {
2037 return 0;
2038 } else {
2039 $this->error = $this->db->lasterror();
2040 dol_syslog(get_class($this).'::demande_prelevement_delete Error '.$this->error);
2041 return -1;
2042 }
2043 }
2044
2050 public function buildEPCQrCodeString()
2051 {
2052 global $mysoc;
2053
2054 // Get the amount to pay
2055 $amount_to_pay = $this->getRemainToPay();
2056
2057 // Prevent negative values (e.g. overpayments)
2058 $amount_to_pay = max(0, $amount_to_pay);
2059
2060 // Ensure numeric formatting for EPC QR code
2061 $amount_to_pay = price2num($amount_to_pay, 'MT');
2062
2063 // Initialize an array to hold the lines of the QR code
2064 $lines = array();
2065
2066 // Add the standard elements to the QR code
2067 $lines = [
2068 'BCD', // Service Tag (optional)
2069 '002', // Version (optional)
2070 '1', // Character set (optional)
2071 'SCT', // Identification (optional)
2072 ];
2073
2074 // Add the bank account information
2075 include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
2076
2077 $idofbankaccountouse = $this->fk_account;
2078 if (empty($idofbankaccountouse)) {
2079 $idofbankaccountouse = $this->fk_bank; // for backward compatibility
2080 }
2081 if (empty($idofbankaccountouse)) {
2082 $idofbankaccountouse = getDolGlobalInt('FACTURE_RIB_NUMBER');
2083 }
2084
2085 if ($idofbankaccountouse > 0) {
2086 $bankAccount = new Account($this->db);
2087 $bankAccount->fetch($idofbankaccountouse);
2088 $lines[] = $bankAccount->bic; //BIC (required)
2089 if (!empty($bankAccount->owner_name)) {
2090 $lines[] = $bankAccount->owner_name; //Owner of the bank account, if present (required)
2091 } else {
2092 $lines[] = $mysoc->name; //Name (required)
2093 }
2094 $lines[] = $bankAccount->iban; //IBAN (required)
2095 } else {
2096 $lines[] = ""; //BIC (required)
2097 $lines[] = $mysoc->name; //Name (required)
2098 $lines[] = ""; //IBAN (required)
2099 }
2100
2101 // Add the amount and reference
2102 $lines[] = 'EUR' . $amount_to_pay; // Amount (optional)
2103 $lines[] = ''; // Purpose (optional)
2104 $lines[] = ''; // Payment reference (optional)
2105 $lines[] = $this->ref; // Remittance Information (optional)
2106
2107 // Join the lines with newline characters and return the result
2108 return implode("\n", $lines);
2109 }
2115 public function buildZATCAQRString()
2116 {
2117 global $conf, $mysoc;
2118
2119 $tmplang = new Translate('', $conf);
2120 $tmplang->setDefaultLang('en_US');
2121 $tmplang->load("main");
2122
2123 $datestring = dol_print_date($this->date, 'dayhourrfc');
2124 //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
2125 //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
2126 $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
2127 $pricetaxstring = price2num($this->total_tva, 2, 1);
2128
2129 /*
2130 $name = implode(unpack("H*", $this->thirdparty->name));
2131 $vatnumber = implode(unpack("H*", $this->thirdparty->tva_intra));
2132 $date = implode(unpack("H*", $datestring));
2133 $pricewithtax = implode(unpack("H*", price2num($pricewithtaxstring, 2)));
2134 $pricetax = implode(unpack("H*", $pricetaxstring));
2135
2136 //var_dump(strlen($this->thirdparty->name));
2137 //var_dump(str_pad(dechex('9'), 2, '0', STR_PAD_LEFT));
2138 //var_dump($this->thirdparty->name);
2139 //var_dump(implode(unpack("H*", $this->thirdparty->name)));
2140 //var_dump(price($this->total_tva, 0, $tmplang, 0, -1, 2));
2141
2142 $s = '01'.str_pad(dechex(strlen($this->thirdparty->name)), 2, '0', STR_PAD_LEFT).$name;
2143 $s .= '02'.str_pad(dechex(strlen($this->thirdparty->tva_intra)), 2, '0', STR_PAD_LEFT).$vatnumber;
2144 $s .= '03'.str_pad(dechex(strlen($datestring)), 2, '0', STR_PAD_LEFT).$date;
2145 $s .= '04'.str_pad(dechex(strlen($pricewithtaxstring)), 2, '0', STR_PAD_LEFT).$pricewithtax;
2146 $s .= '05'.str_pad(dechex(strlen($pricetaxstring)), 2, '0', STR_PAD_LEFT).$pricetax;
2147 $s .= ''; // Hash of xml invoice
2148 $s .= ''; // ecda signature
2149 $s .= ''; // ecda public key
2150 $s .= ''; // ecda signature of public key stamp
2151 */
2152 $mysocname = $mysoc->name ?? '';
2153 $mysoctva_intra = $mysoc->tva_intra ?? '';
2154 // Using TLV format
2155 $s = pack('C1', 1).pack('C1', strlen($mysocname)).$mysocname;
2156 $s .= pack('C1', 2).pack('C1', strlen($mysoctva_intra)).$mysoctva_intra;
2157 $s .= pack('C1', 3).pack('C1', strlen($datestring)).$datestring;
2158 $s .= pack('C1', 4).pack('C1', strlen($pricewithtaxstring)).$pricewithtaxstring;
2159 $s .= pack('C1', 5).pack('C1', strlen($pricetaxstring)).$pricetaxstring;
2160 $s .= ''; // Hash of xml invoice
2161 $s .= ''; // ecda signature
2162 $s .= ''; // ecda public key
2163 $s .= ''; // ecda signature of public key stamp
2164
2165 $s = base64_encode($s);
2166
2167 return $s;
2168 }
2169
2170
2177 {
2178 global $conf, $mysoc;
2179
2180 $tmplang = new Translate('', $conf);
2181 $tmplang->setDefaultLang('en_US');
2182 $tmplang->load("main");
2183
2184 $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
2185 $pricetaxstring = price2num($this->total_tva, 2, 1);
2186
2187 $complementaryinfo = '';
2188 /*
2189 Example: //S1/10/10201409/11/190512/20/1400.000-53/30/106017086/31/180508/32/7.7/40/2:10;0:30
2190 /10/ Numéro de facture – 10201409
2191 /11/ Date de facture – 12.05.2019
2192 /20/ Référence client – 1400.000-53
2193 /30/ Numéro IDE pour la TVA – CHE-106.017.086 TVA
2194 /31/ Date de la prestation pour la comptabilisation de la TVA – 08.05.2018
2195 /32/ Taux de TVA sur le montant total de la facture – 7.7%
2196 /40/ Conditions – 2% d’escompte à 10 jours, paiement net à 30 jours
2197 */
2198 $datestring = dol_print_date($this->date, '%y%m%d');
2199 //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
2200 //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
2201 $complementaryinfo = '//S1/10/'.str_replace('/', '', $this->ref).'/11/'.$datestring;
2202 if ($this->ref_client) {
2203 $complementaryinfo .= '/20/'.$this->ref_client;
2204 }
2205 if ($this->thirdparty->tva_intra) {
2206 $complementaryinfo .= '/30/'.$this->thirdparty->tva_intra;
2207 }
2208
2209 include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
2210 $bankaccount = new Account($this->db);
2211
2212 // Header
2213 $s = '';
2214 $s .= "SPC\n";
2215 $s .= "0200\n";
2216 $s .= "1\n";
2217 // Info Seller ("Compte / Payable à")
2218 if ($this->fk_account > 0) {
2219 // Bank BAN if country is LI or CH. TODO Add a test to check than IBAN start with CH or LI
2220 $bankaccount->fetch($this->fk_account);
2221 $s .= $bankaccount->iban."\n";
2222 } else {
2223 $s .= "\n";
2224 }
2225 if ($bankaccount->id > 0 && getDolGlobalString('PDF_SWISS_QRCODE_USE_OWNER_OF_ACCOUNT_AS_CREDITOR')) {
2226 // If a bank account is provided and we ask to use it as creditor, we use the bank address
2227 // 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 ?
2228 $s .= "S\n";
2229 $s .= dol_trunc($bankaccount->owner_name, 70, 'right', 'UTF-8', 1)."\n";
2230 $addresslinearray = explode("\n", $bankaccount->owner_address);
2231 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
2232 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
2233 /*$s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
2234 $s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
2235 $s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";*/
2236 } else {
2237 $s .= "S\n";
2238 $s .= dol_trunc((string) $mysoc->name, 70, 'right', 'UTF-8', 1)."\n";
2239 $addresslinearray = explode("\n", $mysoc->address);
2240 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
2241 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
2242 $s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
2243 $s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
2244 $s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";
2245 }
2246 // Final seller (Ultimate seller) ("Créancier final" = "En faveur de")
2247 $s .= "\n";
2248 $s .= "\n";
2249 $s .= "\n";
2250 $s .= "\n";
2251 $s .= "\n";
2252 $s .= "\n";
2253 $s .= "\n";
2254 // Amount of payment (to do?)
2255 $s .= price($pricewithtaxstring, 0, 'none', 0, 0, 2)."\n";
2256 $s .= ($this->multicurrency_code ? $this->multicurrency_code : $conf->currency)."\n";
2257 // Buyer
2258 $s .= "S\n";
2259 $s .= dol_trunc((string) $this->thirdparty->name, 70, 'right', 'UTF-8', 1)."\n";
2260 $addresslinearray = explode("\n", $this->thirdparty->address);
2261 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
2262 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
2263 $s .= dol_trunc($this->thirdparty->zip, 16, 'right', 'UTF-8', 1)."\n";
2264 $s .= dol_trunc($this->thirdparty->town, 35, 'right', 'UTF-8', 1)."\n";
2265 $s .= dol_trunc($this->thirdparty->country_code, 2, 'right', 'UTF-8', 1)."\n";
2266 // ID of payment
2267 $s .= "NON\n"; // NON or QRR
2268 $s .= "\n"; // QR Code reference if previous field is QRR
2269 // Free text
2270 if ($complementaryinfo) {
2271 $s .= $complementaryinfo."\n";
2272 } else {
2273 $s .= "\n";
2274 }
2275 $s .= "EPD\n";
2276 // More text, complementary info
2277 if ($complementaryinfo) {
2278 $s .= $complementaryinfo."\n";
2279 }
2280 $s .= "\n";
2281 //var_dump($s);exit;
2282 return $s;
2283 }
2284}
2285
2286
2287
2288require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
2289
2294{
2300 public $label;
2301
2307 public $ref; // Product ref (deprecated)
2313 public $libelle; // Product label (deprecated)
2314
2319 public $product_type = 0;
2320
2325 public $product_ref;
2326
2331 public $product_label;
2332
2337 public $product_desc;
2338
2343 public $qty;
2344
2349 public $subprice;
2350
2356 public $price;
2357
2362 public $fk_product;
2363
2368 public $vat_src_code;
2369
2374 public $tva_tx;
2375
2380 public $localtax1_tx;
2381
2386 public $localtax2_tx;
2387
2393 public $localtax1_type;
2394
2400 public $localtax2_type;
2401
2406 public $remise_percent;
2407
2413 public $remise;
2414
2419 public $total_ht;
2420
2425 public $total_tva;
2426
2431 public $total_localtax1;
2432
2437 public $total_localtax2;
2438
2443 public $total_ttc;
2444
2448 public $revenuestamp;
2449
2450
2454 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
2458 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
2459
2463 public $buy_price_ht;
2468 public $buyprice;
2473 public $pa_ht;
2474
2478 public $marge_tx;
2482 public $marque_tx;
2483
2490 public $info_bits = 0;
2491
2500 public $special_code = 0;
2501
2506 public $fk_user_author;
2507
2512 public $fk_user_modif;
2513
2517 public $fk_code_ventilation;
2518}
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
isALNERunningVersion($blockedlogtestalreadydone=0)
Return if the application is executed with the LNE requirements on.
$object ref
Definition info.php:90
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...
getVentilExportCompta($mode=0)
Return if an invoice was transferred into accountnancy.
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,...
getSubtypeLabel($table='')
Return label of invoice subtype.
demande_prelevement_delete($fuser, $did)
Remove a direct debit request or a credit transfer request.
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.
LibStatut($paye, $status, $mode=0, $alreadypaid=-1, $type=-1, $moreparams=array())
Return label of a status.
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.
isEditable()
Return if an invoice can be set back to draft.
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 invoices.
Class to manage third parties objects (customers, suppliers, prospects...)
Stripe class @TODO No reason to extend 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
global $mysoc
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_now($mode='gmt')
Return date for now.
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.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
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.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
if(getDolGlobalString( 'TAKEPOS_SHOW_CUSTOMER')) print $langs trans('Date')." left Label right Qty right Price right TotalHT right TotalTTC right right right right right right right right right centpercent right TotalHT right n right VAT right n right TotalVAT right n No sujeto a RE IRPF right TotalLT1 right n right TotalLT2 right n right TotalTTC right n takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency right TotalTTC takeposcustomercurrency right takeposcustomercurrency n right PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:464
if(preg_match('/(crypted|dolcrypt):/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition repair.php:125