dolibarr 22.0.5
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; // duplicate with sumpayed
146 public $totaldeposits; // duplicate with sumdeposit
150 public $totalcreditnotes; // duplicate with sumcreditnote
151
155 public $sumpayed;
159 public $sumpayed_multicurrency;
163 public $sumdeposit;
167 public $sumdeposit_multicurrency;
171 public $sumcreditnote;
175 public $sumcreditnote_multicurrency;
179 public $remaintopay;
183 public $nbofopendirectdebitorcredittransfer;
184
188 public $creditnote_ids;
189
193 public $stripechargedone;
194
198 public $stripechargeerror;
199
204 public $description;
205
211 public $ref_client;
212
216 public $situation_cycle_ref;
217
223 public $close_code;
224
229 public $close_note;
230
231
236 public $postactionmessages;
237
238
242 const TYPE_STANDARD = 0;
243
248
253
257 const TYPE_DEPOSIT = 3;
258
263 const TYPE_PROFORMA = 4;
264
268 const TYPE_SITUATION = 5;
269
273 const STATUS_DRAFT = 0;
274
279
287 const STATUS_CLOSED = 2;
288
297
298
299 const CLOSECODE_DISCOUNTVAT = 'discount_vat'; // Abandoned remain - escompte
300 const CLOSECODE_BADDEBT = 'badcustomer'; // Abandoned remain - bad customer
301 const CLOSECODE_BANKCHARGE = 'bankcharge'; // Abandoned remain - bank charge
302 const CLOSECODE_WITHHOLDINGTAX = 'withholdingtax'; // Abandoned remain - source tax
303 const CLOSECODE_OTHER = 'other'; // Abandoned remain - other
304
305 const CLOSECODE_ABANDONED = 'abandon'; // Abandoned - other
306 const CLOSECODE_REPLACED = 'replaced'; // Closed after doing a replacement invoice
307
308
316 public function getRemainToPay($multicurrency = 0)
317 {
318 $alreadypaid = 0.0;
319 $alreadypaid += $this->getSommePaiement($multicurrency);
320 $alreadypaid += $this->getSumDepositsUsed($multicurrency);
321 $alreadypaid += $this->getSumCreditNotesUsed($multicurrency);
322
323 if ((int) $multicurrency > 0) {
324 $totalamount = $this->multicurrency_total_ttc;
325 } else {
326 $totalamount = $this->total_ttc;
327 }
328 $remaintopay = price2num($totalamount - $alreadypaid, 'MT');
329 if ($this->status == self::STATUS_CLOSED && $this->close_code == 'discount_vat') { // If invoice closed with discount for anticipated payment
330 $remaintopay = 0.0;
331 }
332 return $remaintopay;
333 }
334
344 public function getSommePaiement($multicurrency = 0)
345 {
346 $table = 'paiement_facture';
347 $field = 'fk_facture';
348 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
349 $table = 'paiementfourn_facturefourn';
350 $field = 'fk_facturefourn';
351 }
352
353 $sql = "SELECT sum(amount) as amount, sum(multicurrency_amount) as multicurrency_amount";
354 $sql .= " FROM ".$this->db->prefix().$table;
355 $sql .= " WHERE ".$field." = ".((int) $this->id);
356
357 dol_syslog(get_class($this)."::getSommePaiement", LOG_DEBUG);
358
359 $resql = $this->db->query($sql);
360 if ($resql) {
361 $obj = $this->db->fetch_object($resql);
362
363 $this->db->free($resql);
364
365 if ($obj) {
366 if ($multicurrency < 0) {
367 $this->totalpaid = $obj->amount;
368 $this->sumpayed = $obj->amount;
369 $this->sumpayed_multicurrency = $obj->multicurrency_amount;
370 return array('alreadypaid' => (float) $obj->amount, 'alreadypaid_multicurrency' => (float) $obj->multicurrency_amount);
371 } elseif ($multicurrency) {
372 $this->sumpayed_multicurrency = $obj->multicurrency_amount;
373 return (float) $obj->multicurrency_amount;
374 } else {
375 $this->totalpaid = $obj->amount;
376 $this->sumpayed = $obj->amount;
377 return (float) $obj->amount;
378 }
379 } else {
380 return 0;
381 }
382 } else {
383 $this->error = $this->db->lasterror();
384 return -1;
385 }
386 }
387
397 public function getSumDepositsUsed($multicurrency = 0)
398 {
399 /*if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
400 // 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.
401 return 0.0;
402 }*/
403
404 require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
405
406 $discountstatic = new DiscountAbsolute($this->db);
407 $result = $discountstatic->getSumDepositsUsed($this, $multicurrency);
408
409 if ($result >= 0) {
410 if ($multicurrency) {
411 $this->sumdeposit_multicurrency = $result;
412 } else {
413 $this->totaldeposits = $result;
414 $this->sumdeposit = $result;
415 }
416
417 return $result;
418 } else {
419 $this->error = $discountstatic->error;
420 return -1;
421 }
422 }
423
431 public function getSumCreditNotesUsed($multicurrency = 0)
432 {
433 require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
434
435 $discountstatic = new DiscountAbsolute($this->db);
436 $result = $discountstatic->getSumCreditNotesUsed($this, $multicurrency);
437 if (is_numeric($result)) {
438 if ($multicurrency) {
439 $this->sumcreditnote_multicurrency = $result;
440 } else {
441 $this->totalcreditnotes = $result;
442 $this->sumcreditnote = $result;
443 }
444
445 return $result;
446 } else {
447 $this->error = $discountstatic->error;
448 return $result;
449 }
450 }
451
458 public function getSumFromThisCreditNotesNotUsed($multicurrency = 0)
459 {
460 require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
461
462 $discountstatic = new DiscountAbsolute($this->db);
463 $result = $discountstatic->getSumFromThisCreditNotesNotUsed($this, $multicurrency);
464 if ($result >= 0) {
465 return $result;
466 } else {
467 $this->error = $discountstatic->error;
468 return -1;
469 }
470 }
471
478 {
479 $idarray = array();
480
481 $sql = "SELECT rowid";
482 $sql .= " FROM ".$this->db->prefix().$this->table_element;
483 $sql .= " WHERE fk_facture_source = ".((int) $this->id);
484 $sql .= " AND type = 2";
485 $resql = $this->db->query($sql);
486 if ($resql) {
487 $num = $this->db->num_rows($resql);
488 $i = 0;
489 while ($i < $num) {
490 $row = $this->db->fetch_row($resql);
491 $idarray[] = $row[0];
492 $i++;
493 }
494 } else {
495 dol_print_error($this->db);
496 }
497
498 $this->creditnote_ids = $idarray;
499
500 return $idarray;
501 }
502
509 public function getIdReplacingInvoice($option = '')
510 {
511 $sql = "SELECT rowid";
512 $sql .= " FROM ".$this->db->prefix().$this->table_element;
513 $sql .= " WHERE fk_facture_source = ".((int) $this->id);
514 $sql .= " AND type < 2";
515 if ($option == 'validated') {
516 $sql .= ' AND fk_statut = 1';
517 }
518 // PROTECTION BAD DATA
519 // In case the database is corrupted and there is a valid replectement invoice
520 // and another no, priority is given to the valid one.
521 // Should not happen (unless concurrent access and 2 people have created a
522 // replacement invoice for the same invoice at the same time)
523 $sql .= " ORDER BY fk_statut DESC";
524
525 $resql = $this->db->query($sql);
526 if ($resql) {
527 $obj = $this->db->fetch_object($resql);
528 if ($obj) {
529 // If there is any
530 return $obj->rowid;
531 } else {
532 // If no invoice replaces it
533 return 0;
534 }
535 } else {
536 return -1;
537 }
538 }
539
547 {
548 $listofopendirectdebitorcredittransfer = array();
549
550 // TODO Add a cache to store array of open requests for each invoice ID
551
552 $sql = "SELECT pfd.rowid, pfd.traite, pfd.date_demande as date_demande, pfd.date_traite as date_traite, pfd.amount";
553 $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as pfd";
554 if ($type == 'bank-transfer') {
555 $sql .= " WHERE fk_facture_fourn = ".((int) $this->id);
556 } else {
557 $sql .= " WHERE fk_facture = ".((int) $this->id);
558 }
559 $sql .= " AND pfd.traite = 0";
560 $sql .= " AND pfd.type = 'ban'";
561 $sql .= " ORDER BY pfd.date_demande DESC";
562
563 $resql = $this->db->query($sql);
564 $num = 0;
565 if ($resql) {
566 $num = $this->db->num_rows($resql);
567 $i = 0;
568 while ($i < $num) {
569 $obj = $this->db->fetch_object($resql);
570 if ($obj) {
571 $listofopendirectdebitorcredittransfer[] = array(
572 'id' => (int) $obj->rowid,
573 'invoiceid' => (int) $this->id,
574 'date' => $this->db->jdate($obj->date_demande),
575 'amount' => (float) $obj->amount
576 );
577 }
578
579 $i++;
580 }
581 } else {
582 $this->error = $this->db->lasterror();
583 }
584
585 $this->nbofopendirectdebitorcredittransfer = $num;
586
587 return $listofopendirectdebitorcredittransfer;
588 }
589
600 public function getListOfPayments($filtertype = '', $multicurrency = 0, $mode = 0)
601 {
602 $retarray = array();
603 $this->error = ''; // By default no error, list can be empty.
604
605 $table = 'paiement_facture';
606 $table2 = 'paiement';
607 $field = 'fk_facture';
608 $field2 = 'fk_paiement';
609 $field3 = ', p.ref_ext';
610 $field4 = ', p.fk_bank'; // Bank line id
611 $sharedentity = 'facture';
612 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
613 $table = 'paiementfourn_facturefourn';
614 $table2 = 'paiementfourn';
615 $field = 'fk_facturefourn';
616 $field2 = 'fk_paiementfourn';
617 $field3 = '';
618 $sharedentity = 'facture_fourn';
619 }
620
621 // List of payments
622 if (empty($mode) || $mode == 1) {
623 $sql = "SELECT p.ref, pf.amount, pf.multicurrency_amount, p.fk_paiement, p.datep, p.num_paiement as num, t.code".$field3 . $field4;
624 $sql .= " FROM ".$this->db->prefix().$table." as pf, ".$this->db->prefix().$table2." as p, ".$this->db->prefix()."c_paiement as t";
625 $sql .= " WHERE pf.".$field." = ".((int) $this->id);
626 $sql .= " AND pf.".$field2." = p.rowid";
627 $sql .= ' AND p.fk_paiement = t.id';
628 $sql .= ' AND p.entity IN ('.getEntity($sharedentity).')';
629 if ($filtertype) {
630 $sql .= " AND t.code='PRE'";
631 }
632
633 dol_syslog(get_class($this)."::getListOfPayments filtertype=".$filtertype." multicurrency=".$multicurrency." mode=".$mode, LOG_DEBUG);
634
635 $resql = $this->db->query($sql);
636 if ($resql) {
637 $num = $this->db->num_rows($resql);
638 $i = 0;
639 while ($i < $num) {
640 $obj = $this->db->fetch_object($resql);
641 if ($multicurrency) {
642 $tmp = array('amount' => $obj->multicurrency_amount, 'type' => $obj->code, 'typeline' => 'payment', 'date' => $obj->datep, 'num' => $obj->num, 'ref' => $obj->ref);
643 } else {
644 $tmp = array('amount' => $obj->amount, 'type' => $obj->code, 'typeline' => 'payment', 'date' => $obj->datep, 'num' => $obj->num, 'ref' => $obj->ref);
645 }
646 if (!empty($field3)) {
647 $tmp['ref_ext'] = $obj->ref_ext;
648 }
649 if (!empty($field4)) {
650 $tmp['fk_bank_line'] = $obj->fk_bank;
651 }
652 $retarray[] = $tmp;
653 $i++;
654 }
655 $this->db->free($resql);
656 } else {
657 $this->error = $this->db->lasterror();
658 dol_print_error($this->db);
659 return array();
660 }
661 }
662
663 // Look for credit notes and discounts and deposits
664 if (empty($mode) || $mode == 2) {
665 $sql = '';
666 if ($this->element == 'facture' || $this->element == 'invoice') {
667 $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";
668 $sql .= ' FROM '.$this->db->prefix().'societe_remise_except as rc, '.$this->db->prefix().'facture as f';
669 $sql .= ' WHERE rc.fk_facture_source=f.rowid AND rc.fk_facture = '.((int) $this->id);
670 $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)
671 } elseif ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
672 $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";
673 $sql .= ' FROM '.$this->db->prefix().'societe_remise_except as rc, '.$this->db->prefix().'facture_fourn as f';
674 $sql .= ' WHERE rc.fk_invoice_supplier_source=f.rowid AND rc.fk_invoice_supplier = '.((int) $this->id);
675 $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)
676 }
677
678 if ($sql) {
679 $resql = $this->db->query($sql);
680 if ($resql) {
681 $num = $this->db->num_rows($resql);
682 $i = 0;
683 while ($i < $num) {
684 $obj = $this->db->fetch_object($resql);
685 if ($multicurrency) {
686 $retarray[] = array('amount' => $obj->multicurrency_amount, 'type' => $obj->type, 'typeline' => 'discount', 'date' => $obj->date, 'num' => '0', 'ref' => $obj->ref);
687 } else {
688 $retarray[] = array('amount' => $obj->amount, 'type' => $obj->type, 'typeline' => 'discount', 'date' => $obj->date, 'num' => '', 'ref' => $obj->ref);
689 }
690 $i++;
691 }
692 } else {
693 $this->error = $this->db->lasterror();
694 dol_print_error($this->db);
695 return array();
696 }
697 $this->db->free($resql);
698 } else {
699 $this->error = $this->db->lasterror();
700 dol_print_error($this->db);
701 return array();
702 }
703 }
704
705 return $retarray;
706 }
707
708
709 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
723 public function is_erasable()
724 {
725 // phpcs:enable
726
727 // We check if invoice is a temporary number (PROVxxxx)
728 $tmppart = substr($this->ref, 1, 4);
729
730 if ($this->status == self::STATUS_DRAFT && $tmppart === 'PROV') { // If draft invoice and ref not yet defined
731 return 1;
732 }
733
734 if (getDolGlobalInt('INVOICE_CAN_NEVER_BE_REMOVED')) {
735 return 0;
736 }
737
738 // If not a draft invoice and not temporary invoice
739 if ($tmppart !== 'PROV') {
740 $ventilExportCompta = $this->getVentilExportCompta();
741 if ($ventilExportCompta != 0) {
742 return -1;
743 }
744
745 // Get last number of validated invoice
746 if ($this->element != 'invoice_supplier') {
747 if (empty($this->thirdparty)) {
748 $this->fetch_thirdparty(); // We need to have this->thirdparty defined, in case of numbering rule use tags that depend on thirdparty (like {t} tag).
749 }
750 $maxref = $this->getNextNumRef($this->thirdparty, 'last');
751
752 // If there is no invoice into the reset range and not already transferred in accounting, we can delete
753 // If invoice to delete is last one and not already transferred, we can delete
754 if (!getDolGlobalString('INVOICE_CAN_ALWAYS_BE_REMOVED') && $maxref != '' && $maxref != $this->ref) {
755 return -2;
756 }
757
758 // TODO If there is payment in bookkeeping, check the payment is not dispatched in accounting and return -2.
759 // ...
760
761 if (!getDolGlobalString('INVOICE_CAN_ALWAYS_BE_REMOVED') && $this->situation_cycle_ref && method_exists($this, 'is_last_in_cycle')) {
762 $last = $this->is_last_in_cycle();
763 if (!$last) {
764 return -3;
765 }
766 }
767 }
768 }
769
770 // Test if there is at least one payment. If yes, refuse to delete.
771 if (!getDolGlobalString('INVOICE_CAN_ALWAYS_BE_REMOVED') && $this->getSommePaiement() > 0) {
772 return -4;
773 }
774
775 return 2;
776 }
777
785 public function getVentilExportCompta($mode = 0)
786 {
787 $alreadydispatched = 0;
788
789 $type = 'customer_invoice';
790 if ($this->element == 'invoice_supplier') {
791 $type = 'supplier_invoice';
792 }
793
794 $sql = " SELECT ".($mode ? 'DISTINCT piece_num' : 'COUNT(ab.rowid)')." as nb";
795 $sql .= " FROM ".$this->db->prefix()."accounting_bookkeeping as ab";
796 $sql .= " WHERE ab.doc_type = '".$this->db->escape($type)."' AND ab.fk_doc = ".((int) $this->id);
797
798 $resql = $this->db->query($sql);
799 if ($resql) {
800 $obj = $this->db->fetch_object($resql);
801 if ($obj) {
802 $alreadydispatched = $obj->nb;
803 }
804 } else {
805 $this->error = $this->db->lasterror();
806 return -1;
807 }
808
809 if ($alreadydispatched) {
810 return $alreadydispatched;
811 }
812 return 0;
813 }
814
822 public function getNextNumRef($soc, $mode = 'next')
823 {
824 // TODO Must be implemented into main class
825 return '';
826 }
827
834 public function getLibType($withbadge = 0)
835 {
836 global $langs;
837
838 $labellong = "Unknown";
839 $labelshort = "Unknown";
840 if ($this->type == CommonInvoice::TYPE_STANDARD) {
841 $labellong = "InvoiceStandard";
842 $labelshort = "InvoiceStandardShort";
843 } elseif ($this->type == CommonInvoice::TYPE_REPLACEMENT) {
844 $labellong = "InvoiceReplacement";
845 $labelshort = "InvoiceReplacementShort";
846 } elseif ($this->type == CommonInvoice::TYPE_CREDIT_NOTE) {
847 $labellong = "InvoiceAvoir";
848 $labelshort = "CreditNote";
849 } elseif ($this->type == CommonInvoice::TYPE_DEPOSIT) {
850 $labellong = "InvoiceDeposit";
851 $labelshort = "Deposit";
852 } elseif ($this->type == CommonInvoice::TYPE_PROFORMA) { // @phan-suppress-current-line PhanDeprecatedClassConstant
853 $labellong = "InvoiceProForma"; // Not used.
854 $labelshort = "ProForma";
855 } elseif ($this->type == CommonInvoice::TYPE_SITUATION) {
856 $labellong = "InvoiceSituation";
857 $labelshort = "Situation";
858 }
859
860 $out = '';
861 if ($withbadge) {
862 $out .= '<span class="badgeneutral" title="'.dol_escape_htmltag($langs->trans($labellong)).'">';
863 }
864 $out .= $langs->trans($withbadge == 2 ? $labelshort : $labellong);
865 if ($withbadge) {
866 $out .= '</span>';
867 }
868 return $out;
869 }
870
877 public function getSubtypeLabel($table = '')
878 {
879 $subtypeLabel = '';
880 if ($table === 'facture' || $table === 'facture_fourn') {
881 $sql = "SELECT s.label FROM " . $this->db->prefix() . $table . " AS f";
882 $sql .= " INNER JOIN " . $this->db->prefix() . "c_invoice_subtype AS s ON f.subtype = s.rowid";
883 $sql .= " WHERE f.ref = '".$this->db->escape($this->ref)."'";
884 } elseif ($table === 'facture_rec' || $table === 'facture_fourn_rec') {
885 $sql = "SELECT s.label FROM " . $this->db->prefix() . $table . " AS f";
886 $sql .= " INNER JOIN " . $this->db->prefix() . "c_invoice_subtype AS s ON f.subtype = s.rowid";
887 $sql .= " WHERE f.titre = '".$this->db->escape($this->title)."'";
888 } else {
889 return -1;
890 }
891
892 $resql = $this->db->query($sql);
893 if ($resql) {
894 while ($obj = $this->db->fetch_object($resql)) {
895 $subtypeLabel = $obj->label;
896 }
897 } else {
898 dol_print_error($this->db);
899 return -1;
900 }
901
902 return $subtypeLabel;
903 }
904
911 public function getArrayOfInvoiceSubtypes($mode = 0)
912 {
913 global $mysoc;
914
915 $effs = array();
916
917 $sql = "SELECT rowid, code, label as label";
918 $sql .= " FROM " . MAIN_DB_PREFIX . 'c_invoice_subtype';
919 $sql .= " WHERE active = 1 AND fk_country = ".((int) $mysoc->country_id)." AND entity IN(".getEntity('c_invoice_subtype').")";
920 $sql .= " ORDER by rowid, code";
921
922 dol_syslog(get_class($this) . '::getArrayOfInvoiceSubtypes', LOG_DEBUG);
923 $resql = $this->db->query($sql);
924 if ($resql) {
925 $num = $this->db->num_rows($resql);
926 $i = 0;
927
928 while ($i < $num) {
929 $objp = $this->db->fetch_object($resql);
930 if (!$mode) {
931 $key = $objp->rowid;
932 $effs[$key] = $objp->label;
933 } else {
934 $key = $objp->code;
935 $effs[$key] = $objp->rowid;
936 }
937
938 $i++;
939 }
940 $this->db->free($resql);
941 }
942
943 return $effs;
944 }
945
953 public function getLibStatut($mode = 0, $alreadypaid = -1)
954 {
955 $moreparams = array(
956 'nbofopendirectdebitorcredittransfer' => $this->nbofopendirectdebitorcredittransfer,
957 'close_code' => $this->close_code,
958 'close_note' => $this->close_note,
959 );
960
961 return $this->LibStatut($this->paye, $this->status, $mode, $alreadypaid, $this->type, $moreparams);
962 }
963
964 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
976 public function LibStatut($paye, $status, $mode = 0, $alreadypaid = -1, $type = -1, $moreparams = array())
977 {
978 // phpcs:enable
979 global $langs, $hookmanager;
980 $langs->load('bills');
981
982 if ($type == -1) {
983 $type = $this->type;
984 }
985
986 // For backward compatibility
987 if (is_int($moreparams)) {
988 $moreparams = array('nbofopendirectdebitorcredittransfer' => (int) $moreparams);
989 }
990
991 $nbofopendirectdebitorcredittransfer = 0;
992 foreach ($moreparams as $moreparamkey => $moreparamvalue) {
993 if ($moreparamkey == 'nbofopendirectdebitorcredittransfer') {
994 $nbofopendirectdebitorcredittransfer = $moreparamvalue;
995 }
996 }
997
998 $statusType = 'status0';
999 $prefix = 'Short';
1000 if (!$paye) {
1001 if ($status == 0) {
1002 $labelStatus = $langs->transnoentitiesnoconv('BillStatusDraft');
1003 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusDraft');
1004 } elseif (($status == 3 || $status == 2) && $alreadypaid <= 0) {
1005 if ($status == 3) {
1006 $labelStatus = $langs->transnoentitiesnoconv('BillStatusCanceled');
1007 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusCanceled');
1008 } else {
1009 $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedUnpaid');
1010 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedUnpaid');
1011 }
1012 $statusType = 'status5';
1013 } elseif (($status == 3 || $status == 2) && $alreadypaid > 0) {
1014 $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedPaidPartially');
1015 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedPaidPartially');
1016 $statusType = 'status9';
1017 } elseif ($alreadypaid == 0 && $nbofopendirectdebitorcredittransfer == 0) {
1018 $labelStatus = $langs->transnoentitiesnoconv('BillStatusNotPaid');
1019 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusNotPaid');
1020 $statusType = 'status1';
1021 } else {
1022 $labelStatus = $langs->transnoentitiesnoconv('BillStatusStarted');
1023 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusStarted');
1024 $statusType = 'status3';
1025 }
1026 } else {
1027 $statusType = 'status6';
1028 if ($type == self::TYPE_CREDIT_NOTE) {
1029 $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaidBackOrConverted'); // credit note
1030 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaidBackOrConverted'); // credit note
1031 } elseif ($type == self::TYPE_DEPOSIT) {
1032 $labelStatus = $langs->transnoentitiesnoconv('BillStatusConverted'); // deposit invoice
1033 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusConverted'); // deposit invoice
1034 } else {
1035 $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaid');
1036 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaid');
1037 }
1038 }
1039
1040 $parameters = array(
1041 'status' => $status,
1042 'mode' => $mode,
1043 'paye' => $paye,
1044 'alreadypaid' => $alreadypaid,
1045 'type' => $type
1046 );
1047
1048 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
1049
1050 if ($reshook > 0) {
1051 return $hookmanager->resPrint;
1052 }
1053
1054 if (!empty($moreparams['close_code'])) {
1055 $titlestringtoshow = '';
1056
1057 if ($moreparams['close_code'] == self::CLOSECODE_DISCOUNTVAT) {
1058 $titlestringtoshow = $langs->trans("HelpEscompte");
1059 } elseif ($moreparams['close_code'] == self::CLOSECODE_BADDEBT) {
1060 $titlestringtoshow = $langs->trans("ConfirmClassifyPaidPartiallyReasonBadCustomer");
1061 } elseif ($moreparams['close_code'] == self::CLOSECODE_BANKCHARGE) {
1062 $titlestringtoshow = $langs->trans("ConfirmClassifyPaidPartiallyReasonBankCharge");
1063 } elseif ($moreparams['close_code'] == self::CLOSECODE_WITHHOLDINGTAX) {
1064 $titlestringtoshow = $langs->trans("ConfirmClassifyPaidPartiallyReasonWithholdingTax");
1065 } elseif ($moreparams['close_code'] == self::CLOSECODE_OTHER) {
1066 $titlestringtoshow = $langs->trans("Other");
1067 }
1068
1069 //$paramsbutton = array('badgeParams' => array('attr' => array('title' => 'rrrr')));
1070 $paramsbutton = array('badgeParams' => array('attr' => array('title' => $titlestringtoshow)));
1071 } else {
1072 $paramsbutton = array();
1073 }
1074
1075 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode, '', $paramsbutton);
1076 }
1077
1078 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1086 public function calculate_date_lim_reglement($cond_reglement = 0)
1087 {
1088 // phpcs:enable
1089 if (!$cond_reglement) {
1090 $cond_reglement = $this->cond_reglement_code;
1091 }
1092 if (!$cond_reglement) {
1093 $cond_reglement = $this->cond_reglement_id;
1094 }
1095 if (!$cond_reglement) {
1096 return $this->date;
1097 }
1098
1099 $cdr_nbjour = 0;
1100 $cdr_type = 0;
1101 $cdr_decalage = 0;
1102
1103 $sqltemp = "SELECT c.type_cdr, c.nbjour, c.decalage";
1104 $sqltemp .= " FROM ".$this->db->prefix()."c_payment_term as c";
1105 if (is_numeric($cond_reglement)) {
1106 $sqltemp .= " WHERE c.rowid=".((int) $cond_reglement);
1107 } else {
1108 $sqltemp .= " WHERE c.entity IN (".getEntity('c_payment_term').")";
1109 $sqltemp .= " AND c.code = '".$this->db->escape($cond_reglement)."'";
1110 }
1111
1112 dol_syslog(get_class($this).'::calculate_date_lim_reglement', LOG_DEBUG);
1113 $resqltemp = $this->db->query($sqltemp);
1114 if ($resqltemp) {
1115 if ($this->db->num_rows($resqltemp)) {
1116 $obj = $this->db->fetch_object($resqltemp);
1117 $cdr_nbjour = $obj->nbjour;
1118 $cdr_type = $obj->type_cdr;
1119 $cdr_decalage = $obj->decalage;
1120 }
1121 } else {
1122 $this->error = $this->db->error();
1123 return -1;
1124 }
1125 $this->db->free($resqltemp);
1126
1127 /* Definition de la date limit */
1128
1129 // 0 : adding the number of days
1130 if ($cdr_type == 0) {
1131 $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
1132
1133 $datelim += ($cdr_decalage * 3600 * 24);
1134 } elseif ($cdr_type == 1) {
1135 // 1 : application of the "end of the month" rule
1136 $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
1137
1138 $mois = date('m', $datelim);
1139 $annee = date('Y', $datelim);
1140 if ($mois == 12) {
1141 $mois = 1;
1142 $annee += 1;
1143 } else {
1144 $mois += 1;
1145 }
1146 // We move at the beginning of the next month, and we take a day off
1147 $datelim = dol_mktime(12, 0, 0, $mois, 1, $annee);
1148 $datelim -= (3600 * 24);
1149
1150 $datelim += ($cdr_decalage * 3600 * 24);
1151 } elseif ($cdr_type == 2 && !empty($cdr_decalage)) {
1152 // 2 : application of the rule, the N of the current or next month
1153 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
1154 $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
1155
1156 $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
1157 $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
1158 $date_lim_next = dol_time_plus_duree((int) $date_lim_current, 1, 'm'); // Add 1 month
1159
1160 $diff = $date_piece - $date_lim_current;
1161
1162 if ($diff <= 0) {
1163 $datelim = $date_lim_current;
1164 } else {
1165 $datelim = $date_lim_next;
1166 }
1167 } else {
1168 return 'Bad value for type_cdr in database for record cond_reglement = '.$cond_reglement;
1169 }
1170
1171 return $datelim;
1172 }
1173
1174 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1187 public function demande_prelevement(User $fuser, float $amount = 0, string $type = 'direct-debit', string $sourcetype = 'facture', int $checkduplicateamongall = 0, int $ribId = 0)
1188 {
1189 // phpcs:enable
1190 global $conf;
1191
1192 $error = 0;
1193
1194 dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
1195
1196 if ($this->status > self::STATUS_DRAFT && $this->paye == 0) {
1197 require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
1198 $bac = new CompanyBankAccount($this->db);
1199 $bac->fetch($ribId, '', $this->socid);
1200
1201 $sql = "SELECT count(rowid) as nb";
1202 $sql .= " FROM ".$this->db->prefix()."prelevement_demande";
1203 if ($type == 'bank-transfer') {
1204 $sql .= " WHERE fk_facture_fourn = ".((int) $this->id);
1205 } else {
1206 $sql .= " WHERE fk_facture = ".((int) $this->id);
1207 }
1208 $sql .= " AND type = 'ban'"; // To exclude record done for some online payments
1209 if (empty($checkduplicateamongall)) {
1210 $sql .= " AND traite = 0";
1211 }
1212
1213 dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
1214
1215 $resql = $this->db->query($sql);
1216 if ($resql) {
1217 $obj = $this->db->fetch_object($resql);
1218 if ($obj && $obj->nb == 0) { // If no request found yet
1219 $now = dol_now();
1220
1221 $totalpaid = $this->getSommePaiement();
1222 $totalcreditnotes = $this->getSumCreditNotesUsed();
1223 $totaldeposits = $this->getSumDepositsUsed();
1224 //print "totalpaid=".$totalpaid." totalcreditnotes=".$totalcreditnotes." totaldeposts=".$totaldeposits;
1225
1226 // We can also use bcadd to avoid pb with floating points
1227 // For example print 239.2 - 229.3 - 9.9; does not return 0.
1228 //$resteapayer=bcadd($this->total_ttc,$totalpaid,$conf->global->MAIN_MAX_DECIMALS_TOT);
1229 //$resteapayer=bcadd($resteapayer,$totalavoir,$conf->global->MAIN_MAX_DECIMALS_TOT);
1230 if (empty($amount)) {
1231 $amount = price2num($this->total_ttc - $totalpaid - $totalcreditnotes - $totaldeposits, 'MT');
1232 }
1233
1234 if (is_numeric($amount) && $amount != 0) {
1235 $sql = 'INSERT INTO '.$this->db->prefix().'prelevement_demande(';
1236 if ($type == 'bank-transfer') {
1237 $sql .= 'fk_facture_fourn, ';
1238 } else {
1239 $sql .= 'fk_facture, ';
1240 }
1241 $sql .= ' amount, date_demande, fk_user_demande, code_banque, code_guichet, number, cle_rib, sourcetype, type, entity';
1242 if (empty($bac->id)) {
1243 $sql .= ')';
1244 } else {
1245 $sql .= ', fk_societe_rib)';
1246 }
1247 $sql .= " VALUES (".((int) $this->id);
1248 $sql .= ", ".((float) price2num($amount));
1249 $sql .= ", '".$this->db->idate($now)."'";
1250 $sql .= ", ".((int) $fuser->id);
1251 $sql .= ", '".$this->db->escape($bac->code_banque)."'";
1252 $sql .= ", '".$this->db->escape($bac->code_guichet)."'";
1253 $sql .= ", '".$this->db->escape($bac->number)."'";
1254 $sql .= ", '".$this->db->escape($bac->cle_rib)."'";
1255 $sql .= ", '".$this->db->escape($sourcetype)."'";
1256 $sql .= ", 'ban'";
1257 $sql .= ", ".((int) $conf->entity);
1258 if (!empty($bac->id)) {
1259 $sql .= ", '".$this->db->escape((string) $bac->id)."'";
1260 }
1261 $sql .= ")";
1262
1263 dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
1264 $resql = $this->db->query($sql);
1265 if (!$resql) {
1266 $this->error = $this->db->lasterror();
1267 dol_syslog(get_class($this).'::demandeprelevement Erreur');
1268 $error++;
1269 }
1270 } else {
1271 $this->error = 'WithdrawRequestErrorNilAmount';
1272 dol_syslog(get_class($this).'::demandeprelevement WithdrawRequestErrorNilAmount');
1273 $error++;
1274 }
1275
1276 if (!$error) {
1277 // Force payment mode of invoice to withdraw
1278 $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
1279 if ($payment_mode_id > 0) {
1280 $result = $this->setPaymentMethods($payment_mode_id);
1281 }
1282 }
1283
1284 if ($error) {
1285 return -1;
1286 }
1287 return 1;
1288 } else {
1289 $this->error = "A request already exists";
1290 dol_syslog(get_class($this).'::demandeprelevement Can t create a request to generate a direct debit, a request already exists.');
1291 return 0;
1292 }
1293 } else {
1294 $this->error = $this->db->error();
1295 dol_syslog(get_class($this).'::demandeprelevement Error -2');
1296 return -2;
1297 }
1298 } else {
1299 $this->error = "Status of invoice does not allow this";
1300 dol_syslog(get_class($this)."::demandeprelevement ".$this->error." $this->status, $this->paye, $this->mode_reglement_id");
1301 return -3;
1302 }
1303 }
1304
1305
1317 public function makeStripeCardRequest($fuser, $id, $sourcetype = 'facture')
1318 {
1319 // TODO See in sellyoursaas
1320 return 0;
1321 }
1322
1335 public function makeStripeSepaRequest($fuser, $did, $type = 'direct-debit', $sourcetype = 'facture', $service = '', $forcestripe = '')
1336 {
1337 global $conf, $user, $langs;
1338
1339 if ($type != 'bank-transfer' && $type != 'credit-transfer' && !getDolGlobalString('STRIPE_SEPA_DIRECT_DEBIT')) {
1340 return 0;
1341 }
1342 if ($type != 'direct-debit' && !getDolGlobalString('STRIPE_SEPA_CREDIT_TRANSFER')) {
1343 return 0;
1344 }
1345 // Set a default value for service if not provided
1346 if (empty($service)) {
1347 $service = 'StripeTest';
1348 if (getDolGlobalString('STRIPE_LIVE')/* && !GETPOST('forcesandbox', 'alpha')*/) {
1349 $service = 'StripeLive';
1350 }
1351 }
1352
1353 $error = 0;
1354
1355 dol_syslog(get_class($this)."::makeStripeSepaRequest start did=".$did." type=".$type." service=".$service." sourcetype=".$sourcetype." forcestripe=".$forcestripe, LOG_DEBUG);
1356
1357 if ($this->status > self::STATUS_DRAFT && $this->paye == 0) {
1358 // Get the default payment mode for BAN payment of the third party
1359 require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
1360 $bac = new CompanyBankAccount($this->db); // Table societe_rib
1361 $result = $bac->fetch(0, '', $this->socid, 1, 'ban');
1362 if ($result <= 0 || empty($bac->id)) {
1363 $this->error = $langs->trans("ThirdpartyHasNoDefaultBanAccount");
1364 $this->errors[] = $this->error;
1365 dol_syslog(get_class($this)."::makeStripeSepaRequest ".$this->error);
1366 return -1;
1367 }
1368
1369 // Load the pending payment request to process (with rowid=$did)
1370 $sql = "SELECT rowid, date_demande, amount, fk_facture, fk_facture_fourn, fk_salary, fk_prelevement_bons";
1371 $sql .= " FROM ".$this->db->prefix()."prelevement_demande";
1372 $sql .= " WHERE rowid = ".((int) $did);
1373 if ($type != 'bank-transfer' && $type != 'credit-transfer') {
1374 $sql .= " AND fk_facture = ".((int) $this->id); // Add a protection to not pay another invoice than current one
1375 }
1376 if ($type != 'direct-debit') {
1377 if ($sourcetype == 'salary') {
1378 $sql .= " AND fk_salary = ".((int) $this->id); // Add a protection to not pay another salary than current one
1379 } else {
1380 $sql .= " AND fk_facture_fourn = ".((int) $this->id); // Add a protection to not pay another invoice than current one
1381 }
1382 }
1383 $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)
1384
1385 dol_syslog(get_class($this)."::makeStripeSepaRequest load requests to process", LOG_DEBUG);
1386 $resql = $this->db->query($sql);
1387 if ($resql) {
1388 $obj = $this->db->fetch_object($resql);
1389 if (!$obj) {
1390 dol_print_error($this->db, 'CantFindRequestWithId');
1391 return -2;
1392 }
1393
1394 // amount to pay
1395 $amount = $obj->amount;
1396
1397 if (is_numeric($amount) && $amount != 0) {
1398 require_once DOL_DOCUMENT_ROOT.'/societe/class/companypaymentmode.class.php';
1399 $companypaymentmode = new CompanyPaymentMode($this->db); // table societe_rib
1400 $companypaymentmode->fetch($bac->id);
1401
1402 $this->stripechargedone = 0;
1403 $this->stripechargeerror = 0;
1404
1405 $now = dol_now();
1406
1407 $currency = $conf->currency;
1408
1409 $errorforinvoice = 0; // We reset the $errorforinvoice at each invoice loop
1410
1411 $this->fetch_thirdparty();
1412
1413 dol_syslog("makeStripeSepaRequest Process payment request amount=".$amount." thirdparty_id=" . $this->thirdparty->id . ", thirdparty_name=" . $this->thirdparty->name . " ban id=" . $bac->id, LOG_DEBUG);
1414
1415 //$alreadypayed = $this->getSommePaiement();
1416 //$amount_credit_notes_included = $this->getSumCreditNotesUsed();
1417 //$amounttopay = $this->total_ttc - $alreadypayed - $amount_credit_notes_included;
1418 $amounttopay = $amount;
1419
1420 // Correct the amount according to unit of currency
1421 // See https://support.stripe.com/questions/which-zero-decimal-currencies-does-stripe-support
1422 $arrayzerounitcurrency = ['BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'];
1423 $amountstripe = $amounttopay;
1424 if (!in_array($currency, $arrayzerounitcurrency)) {
1425 $amountstripe *= 100;
1426 }
1427
1428 $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.
1429 if (!($fk_bank_account > 0)) {
1430 $error++;
1431 $errorforinvoice++;
1432 dol_syslog("makeStripeSepaRequest Error no bank account defined for Stripe payments", LOG_ERR);
1433 $this->error = "Error bank account for Stripe payments not defined into Stripe module";
1434 $this->errors[] = $this->error;
1435 }
1436
1437 $this->db->begin();
1438
1439 // Create a prelevement_bon
1440 require_once DOL_DOCUMENT_ROOT.'/compta/prelevement/class/bonprelevement.class.php';
1441 $bon = new BonPrelevement($this->db);
1442 if (!$error) {
1443 if (empty($obj->fk_prelevement_bons)) {
1444 // This creates a record into llx_prelevement_bons and updates link with llx_prelevement_demande
1445 $nbinvoices = $bon->create('0', '0', 'real', 'ALL', 0, 0, $type, $did, $fk_bank_account);
1446 if ($nbinvoices <= 0) {
1447 $error++;
1448 $errorforinvoice++;
1449 dol_syslog("makeStripeSepaRequest Error on BonPrelevement creation", LOG_ERR);
1450 $this->error = "Error on BonPrelevement creation";
1451 $this->errors[] = $this->error;
1452 }
1453 /*
1454 if (!$error) {
1455 // Update the direct debit payment request of the processed request to save the id of the prelevement_bon
1456 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
1457 $sql .= " fk_prelevement_bons = ".((int) $bon->id);
1458 $sql .= " WHERE rowid = ".((int) $did);
1459
1460 $result = $this->db->query($sql);
1461 if ($result < 0) {
1462 $error++;
1463 $this->error = "Error on updating fk_prelevement_bons to ".$bon->id;
1464 $this->errors[] = $this->error;
1465 }
1466 }
1467 */
1468 } else {
1469 $error++;
1470 $errorforinvoice++;
1471 dol_syslog("makeStripeSepaRequest Error Line already part of a bank payment order", LOG_ERR);
1472 $this->error = "The line is already included into a bank payment order. Delete the bank payment order first.";
1473 $this->errors[] = $this->error;
1474 }
1475 }
1476
1477 $paymentintent = null;
1478 if (!$error) {
1479 if ($amountstripe > 0) {
1480 try {
1481 global $savstripearrayofkeysbyenv;
1482 global $stripearrayofkeysbyenv;
1483 $servicestatus = 0;
1484 if ($service == 'StripeLive') {
1485 $servicestatus = 1;
1486 }
1487
1488 //var_dump($companypaymentmode);
1489 dol_syslog("makeStripeSepaRequest We will try to pay with companypaymentmodeid=" . $companypaymentmode->id . " stripe_card_ref=" . $companypaymentmode->stripe_card_ref . " mode=" . $companypaymentmode->status, LOG_DEBUG);
1490
1491 $thirdparty = new Societe($this->db);
1492 $resultthirdparty = $thirdparty->fetch($this->socid);
1493
1494 include_once DOL_DOCUMENT_ROOT . '/stripe/class/stripe.class.php'; // This include the include of htdocs/stripe/config.php
1495 // So it inits or erases the $stripearrayofkeysbyenv
1496 $stripe = new Stripe($this->db);
1497
1498 if (empty($savstripearrayofkeysbyenv)) {
1499 $savstripearrayofkeysbyenv = $stripearrayofkeysbyenv;
1500 }
1501 dol_syslog("makeStripeSepaRequest Current Stripe environment is " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key']);
1502 dol_syslog("makeStripeSepaRequest Current Saved Stripe environment is ".$savstripearrayofkeysbyenv[$servicestatus]['publishable_key']);
1503
1504 $foundalternativestripeaccount = '';
1505
1506 // Force stripe to another value (by default this value is empty)
1507 if (! empty($forcestripe)) {
1508 dol_syslog("makeStripeSepaRequest A dedicated stripe account was forced, so we switch to it.");
1509
1510 $tmparray = explode('@', $forcestripe);
1511 if (! empty($tmparray[1])) {
1512 $tmparray2 = explode(':', $tmparray[1]);
1513 if (! empty($tmparray2[1])) {
1514 $stripearrayofkeysbyenv[$servicestatus]["publishable_key"] = $tmparray2[0];
1515 $stripearrayofkeysbyenv[$servicestatus]["secret_key"] = $tmparray2[1];
1516
1517 $stripearrayofkeys = $stripearrayofkeysbyenv[$servicestatus];
1518 \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1519
1520 $foundalternativestripeaccount = $tmparray[0]; // Store the customer id
1521
1522 dol_syslog("makeStripeSepaRequest We use now customer=".$foundalternativestripeaccount." publishable_key=".$stripearrayofkeys['publishable_key'], LOG_DEBUG);
1523 }
1524 }
1525
1526 if (! $foundalternativestripeaccount) {
1527 $stripearrayofkeysbyenv = $savstripearrayofkeysbyenv;
1528
1529 $stripearrayofkeys = $savstripearrayofkeysbyenv[$servicestatus];
1530 \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1531 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);
1532 }
1533 } else {
1534 $stripearrayofkeysbyenv = $savstripearrayofkeysbyenv;
1535
1536 $stripearrayofkeys = $savstripearrayofkeysbyenv[$servicestatus];
1537 \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1538 dol_syslog("makeStripeSepaRequest No dedicated Stripe Account requested, so we use global one, so ".$stripearrayofkeys['publishable_key'], LOG_DEBUG);
1539 }
1540
1541 $stripeacc = $stripe->getStripeAccount($service, $this->socid); // Get Stripe OAuth connect account if it exists (no network access here)
1542
1543 if ($foundalternativestripeaccount) {
1544 if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
1545 $customer = \Stripe\Customer::retrieve(array('id' => "$foundalternativestripeaccount", 'expand[]' => 'sources'));
1546 } else {
1547 $customer = \Stripe\Customer::retrieve(array('id' => "$foundalternativestripeaccount", 'expand[]' => 'sources'), array("stripe_account" => $stripeacc));
1548 }
1549 } else {
1550 $customer = $stripe->customerStripe($thirdparty, $stripeacc, $servicestatus, 0);
1551 if (empty($customer) && ! empty($stripe->error)) {
1552 $this->error = $stripe->error;
1553 $this->errors[] = $this->error;
1554 }
1555 /*if (!empty($customer) && empty($customer->sources)) {
1556 $customer = null;
1557 $this->error = '\Stripe\Customer::retrieve did not returned the sources';
1558 $this->errors[] = $this->error;
1559 }*/
1560 }
1561
1562 // $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)
1563 // $nbdaysbeforeendoftries = (empty($conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES) ? 35 : $conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES);
1564 $postactionmessages = [];
1565
1566 if ($resultthirdparty > 0 && !empty($customer)) {
1567 if (!$error) { // Payment was not canceled
1568 $stripecard = null;
1569 if ($companypaymentmode->type == 'ban') {
1570 // Check into societe_rib if a payment mode for Stripe and ban payment exists
1571 // To make a Stripe SEPA payment request, we must have the payment mode source already saved into societe_rib and retrieved with ->sepaStripe
1572 // The payment mode source is created when we create the bank account on Stripe with paymentmodes.php?action=create
1573 $stripecard = $stripe->sepaStripe($customer, $companypaymentmode, $stripeacc, $servicestatus, 0);
1574 } else {
1575 $error++;
1576 $this->error = 'The payment mode type is not "ban"';
1577 }
1578
1579 if ($stripecard) { // Can be src_... (for sepa) or pm_... (new card mode). Note that card_... (old card mode) should not happen here.
1580 $FULLTAG = 'DID='.$did.'-INV=' . $this->id . '-CUS=' . $thirdparty->id;
1581 $description = 'Stripe payment from makeStripeSepaRequest: ' . $FULLTAG . ' did='.$did.' ref=' . $this->ref;
1582
1583 $stripefailurecode = '';
1584 $stripefailuremessage = '';
1585 $stripefailuredeclinecode = '';
1586
1587 // Using new SCA method
1588 dol_syslog("* Create payment on SEPA " . $stripecard->id . ", amounttopay=" . $amounttopay . ", amountstripe=" . $amountstripe . ", FULLTAG=" . $FULLTAG, LOG_DEBUG);
1589
1590 // Create payment intent and charge payment (confirmnow = true)
1591 $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $this, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1, 1, $did);
1592
1593 $charge = new stdClass();
1594
1595 if ($paymentintent->status === 'succeeded' || $paymentintent->status === 'processing') {
1596 $charge->status = 'ok';
1597 $charge->id = $paymentintent->id;
1598 $charge->customer = $customer->id;
1599 } elseif ($paymentintent->status === 'requires_action') {
1600 //paymentintent->status may be => 'requires_action' (no error in such a case)
1601 dol_syslog(var_export($paymentintent, true), LOG_DEBUG);
1602
1603 $charge->status = 'failed';
1604 $charge->customer = $customer->id;
1605 $charge->failure_code = $stripe->code;
1606 $charge->failure_message = $stripe->error;
1607 $charge->failure_declinecode = $stripe->declinecode;
1608 $stripefailurecode = $stripe->code;
1609 $stripefailuremessage = 'Action required. Contact the support at ';// . $conf->global->SELLYOURSAAS_MAIN_EMAIL;
1610 $stripefailuredeclinecode = $stripe->declinecode;
1611 } else {
1612 dol_syslog(var_export($paymentintent, true), LOG_DEBUG);
1613
1614 $charge->status = 'failed';
1615 $charge->customer = $customer->id;
1616 $charge->failure_code = $stripe->code;
1617 $charge->failure_message = $stripe->error;
1618 $charge->failure_declinecode = $stripe->declinecode;
1619 $stripefailurecode = $stripe->code;
1620 $stripefailuremessage = $stripe->error;
1621 $stripefailuredeclinecode = $stripe->declinecode;
1622 }
1623
1624 //var_dump("stripefailurecode=".$stripefailurecode." stripefailuremessage=".$stripefailuremessage." stripefailuredeclinecode=".$stripefailuredeclinecode);
1625 //exit;
1626
1627
1628 // Return $charge = array('id'=>'ch_XXXX', 'status'=>'succeeded|pending|failed', 'failure_code'=>, 'failure_message'=>...)
1629 if (empty($charge) || $charge->status == 'failed') {
1630 dol_syslog('Failed to charge payment mode ' . $stripecard->id . ' stripefailurecode=' . $stripefailurecode . ' stripefailuremessage=' . $stripefailuremessage . ' stripefailuredeclinecode=' . $stripefailuredeclinecode, LOG_WARNING);
1631
1632 // Save a stripe payment was in error
1633 $this->stripechargeerror++;
1634
1635 $error++;
1636 $errorforinvoice++;
1637 $errmsg = $langs->trans("FailedToChargeSEPA");
1638 if (!empty($charge)) {
1639 if ($stripefailuredeclinecode == 'authentication_required') {
1640 $errauthenticationmessage = $langs->trans("ErrSCAAuthentication");
1641 $errmsg = $errauthenticationmessage;
1642 } elseif (in_array($stripefailuredeclinecode, ['insufficient_funds', 'generic_decline'])) {
1643 $errmsg .= ': ' . $charge->failure_code;
1644 $errmsg .= ($charge->failure_message ? ' - ' : '') . ' ' . $charge->failure_message;
1645 if (empty($stripefailurecode)) {
1646 $stripefailurecode = $charge->failure_code;
1647 }
1648 if (empty($stripefailuremessage)) {
1649 $stripefailuremessage = $charge->failure_message;
1650 }
1651 } else {
1652 $errmsg .= ': failure_code=' . $charge->failure_code;
1653 $errmsg .= ($charge->failure_message ? ' - ' : '') . ' failure_message=' . $charge->failure_message;
1654 if (empty($stripefailurecode)) {
1655 $stripefailurecode = $charge->failure_code;
1656 }
1657 if (empty($stripefailuremessage)) {
1658 $stripefailuremessage = $charge->failure_message;
1659 }
1660 }
1661 } else {
1662 $errmsg .= ': ' . $stripefailurecode . ' - ' . $stripefailuremessage;
1663 $errmsg .= ($stripefailuredeclinecode ? ' - ' . $stripefailuredeclinecode : '');
1664 }
1665
1666 $description = 'Stripe payment ERROR from makeStripeSepaRequest: ' . $FULLTAG;
1667 $postactionmessages[] = $errmsg . ' (' . $stripearrayofkeys['publishable_key'] . ')';
1668
1669 $this->error = $errmsg;
1670 $this->errors[] = $this->error;
1671 } else {
1672 dol_syslog('Successfuly request '.$type.' '.$stripecard->id);
1673
1674 $postactionmessages[] = 'Success to request '.$type.' (' . $charge->id . ' with ' . $stripearrayofkeys['publishable_key'] . ')';
1675
1676 // Save a stripe payment was done in real life so later we will be able to force a commit on recorded payments
1677 // even if in batch mode (method doTakePaymentStripe), we will always make all action in one transaction with a forced commit.
1678 $this->stripechargedone++;
1679
1680 // Default description used for label of event. Will be overwrite by another value later.
1681 $description = 'Stripe payment request OK (' . $charge->id . ') from makeStripeSepaRequest: ' . $FULLTAG;
1682 }
1683
1684 $object = $this;
1685
1686 // Track an event
1687 if (empty($charge) || $charge->status == 'failed') {
1688 $actioncode = 'PAYMENT_STRIPE_KO';
1689 $extraparams = $stripefailurecode;
1690 $extraparams .= (($extraparams && $stripefailuremessage) ? ' - ' : '') . $stripefailuremessage;
1691 $extraparams .= (($extraparams && $stripefailuredeclinecode) ? ' - ' : '') . $stripefailuredeclinecode;
1692 } else {
1693 $actioncode = 'PAYMENT_STRIPE_OK';
1694 $extraparams = array();
1695 }
1696 } else {
1697 $error++;
1698 $errorforinvoice++;
1699 dol_syslog("No ban payment method found for this stripe customer " . $customer->id, LOG_WARNING);
1700
1701 $this->error = 'Failed to get direct debit payment method for stripe customer = ' . $customer->id;
1702 $this->errors[] = $this->error;
1703
1704 $description = 'Failed to find or use the payment mode - no ban defined for the thirdparty account';
1705 $stripefailurecode = 'BADPAYMENTMODE';
1706 $stripefailuremessage = 'Failed to find or use the payment mode - no ban defined for the thirdparty account';
1707 $postactionmessages[] = $description . ' (' . $stripearrayofkeys['publishable_key'] . ')';
1708
1709 $object = $this;
1710
1711 $actioncode = 'PAYMENT_STRIPE_KO';
1712 $extraparams = array();
1713 }
1714 } else {
1715 // If error because payment was canceled for a logical reason, we do nothing (no event added)
1716 $description = '';
1717 $stripefailurecode = '';
1718 $stripefailuremessage = '';
1719
1720 $object = $this;
1721
1722 $actioncode = '';
1723 $extraparams = array();
1724 }
1725 } else { // Else of the if ($resultthirdparty > 0 && ! empty($customer)) {
1726 if ($resultthirdparty <= 0) {
1727 dol_syslog('SellYourSaasUtils Failed to load customer for thirdparty_id = ' . $thirdparty->id, LOG_WARNING);
1728 $this->error = 'Failed to load Stripe account for thirdparty_id = ' . $thirdparty->id;
1729 $this->errors[] = $this->error;
1730 } else { // $customer stripe not found
1731 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);
1732 $this->error = 'Failed to get Stripe account id for thirdparty_id = ' . $thirdparty->id . " in mode " . $servicestatus . " in Stripe env " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key'];
1733 $this->errors[] = $this->error;
1734 }
1735 $error++;
1736 $errorforinvoice++;
1737
1738 $description = 'Failed to find or use your payment mode (no payment mode for this customer id)';
1739 $stripefailurecode = 'BADPAYMENTMODE';
1740 $stripefailuremessage = 'Failed to find or use your payment mode (no payment mode for this customer id)';
1741 $postactionmessages = [];
1742
1743 $object = $this;
1744
1745 $actioncode = 'PAYMENT_STRIPE_KO';
1746 $extraparams = array();
1747 }
1748
1749 if ($description) {
1750 dol_syslog("* Record event for credit transfer or direct debit request result - " . $description);
1751 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
1752
1753 // Insert record of payment (success or error)
1754 $actioncomm = new ActionComm($this->db);
1755
1756 $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
1757 $actioncomm->code = 'AC_' . $actioncode;
1758 $actioncomm->label = $description;
1759 $actioncomm->note_private = implode(",\n", $postactionmessages);
1760 $actioncomm->fk_project = $this->fk_project;
1761 $actioncomm->datep = $now;
1762 $actioncomm->datef = $now;
1763 $actioncomm->percentage = -1; // Not applicable
1764 $actioncomm->socid = $thirdparty->id;
1765 $actioncomm->contactid = 0;
1766 $actioncomm->authorid = $user->id; // User saving action
1767 $actioncomm->userownerid = $user->id; // Owner of action
1768 // Fields when action is a real email (content is already into note)
1769 /*$actioncomm->email_msgid = $object->email_msgid;
1770 $actioncomm->email_from = $object->email_from;
1771 $actioncomm->email_sender= $object->email_sender;
1772 $actioncomm->email_to = $object->email_to;
1773 $actioncomm->email_tocc = $object->email_tocc;
1774 $actioncomm->email_tobcc = $object->email_tobcc;
1775 $actioncomm->email_subject = $object->email_subject;
1776 $actioncomm->errors_to = $object->errors_to;*/
1777 $actioncomm->fk_element = $this->id;
1778 $actioncomm->elementid = $this->id;
1779 $actioncomm->elementtype = $this->element;
1780 $actioncomm->extraparams = $extraparams; // Can be null, empty string or array()
1781
1782 $actioncomm->create($user);
1783 }
1784
1785 $this->description = $description;
1786 $this->postactionmessages = $postactionmessages;
1787 } catch (Exception $e) {
1788 $error++;
1789 $errorforinvoice++;
1790 dol_syslog('Error ' . $e->getMessage(), LOG_ERR);
1791 $this->error = 'Error ' . $e->getMessage();
1792 $this->errors[] = $this->error;
1793 }
1794 } else { // If remain to pay is null
1795 $error++;
1796 $errorforinvoice++;
1797 dol_syslog("Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?", LOG_WARNING);
1798 $this->error = "Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?";
1799 $this->errors[] = $this->error;
1800 }
1801 }
1802
1803 // Set status of the order to "Transferred" with method 'api'
1804 if (!$error && !$errorforinvoice) {
1805 $result = $bon->set_infotrans($user, $now, 3);
1806 if ($result < 0) {
1807 $error++;
1808 $errorforinvoice++;
1809 dol_syslog("Error on BonPrelevement creation", LOG_ERR);
1810 $this->error = "Error on BonPrelevement creation";
1811 $this->errors[] = $this->error;
1812 }
1813 }
1814
1815 if (!$error && !$errorforinvoice && $paymentintent !== null) {
1816 // Update the direct debit payment request of the processed invoice to save the id of the prelevement_bon
1817 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
1818 $sql .= " ext_payment_id = '".$this->db->escape($paymentintent->id)."',";
1819 $sql .= " ext_payment_site = '".$this->db->escape($service)."'";
1820 $sql .= " WHERE rowid = ".((int) $did);
1821
1822 dol_syslog(get_class($this)."::makeStripeSepaRequest update to save stripe paymentintent ids", LOG_DEBUG);
1823 $resql = $this->db->query($sql);
1824 if (!$resql) {
1825 $this->error = $this->db->lasterror();
1826 dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur');
1827 $error++;
1828 }
1829 }
1830
1831 if (!$error && !$errorforinvoice) {
1832 $this->db->commit();
1833 } else {
1834 $this->db->rollback();
1835 }
1836 } else {
1837 $this->error = 'WithdrawRequestErrorNilAmount';
1838 dol_syslog(get_class($this).'::makeStripeSepaRequest WithdrawRequestErrorNilAmount');
1839 $error++;
1840 }
1841
1842 /*
1843 if (!$error) {
1844 // Force payment mode of the invoice to withdraw
1845 $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
1846 if ($payment_mode_id > 0) {
1847 $result = $this->setPaymentMethods($payment_mode_id);
1848 }
1849 }*/
1850
1851 if ($error) {
1852 return -1;
1853 }
1854 return 1;
1855 } else {
1856 $this->error = $this->db->error();
1857 dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur -2');
1858 return -2;
1859 }
1860 } else {
1861 $this->error = "Status of invoice does not allow this";
1862 dol_syslog(get_class($this)."::makeStripeSepaRequest ".$this->error." ".$this->status." ,".$this->paye.", ".$this->mode_reglement_id, LOG_WARNING);
1863 return -3;
1864 }
1865 }
1866
1867 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1875 public function demande_prelevement_delete($fuser, $did)
1876 {
1877 // phpcs:enable
1878 $sql = 'DELETE FROM '.$this->db->prefix().'prelevement_demande';
1879 $sql .= ' WHERE rowid = '.((int) $did);
1880 $sql .= ' AND traite = 0';
1881 if ($this->db->query($sql)) {
1882 return 0;
1883 } else {
1884 $this->error = $this->db->lasterror();
1885 dol_syslog(get_class($this).'::demande_prelevement_delete Error '.$this->error);
1886 return -1;
1887 }
1888 }
1889
1895 public function buildEPCQrCodeString()
1896 {
1897 global $mysoc;
1898
1899 // Get the amount to pay
1900 $amount_to_pay = $this->getRemainToPay();
1901
1902 // Prevent negative values (e.g. overpayments)
1903 $amount_to_pay = max(0, $amount_to_pay);
1904
1905 // Ensure numeric formatting for EPC QR code
1906 $amount_to_pay = price2num($amount_to_pay, 'MT');
1907
1908 // Initialize an array to hold the lines of the QR code
1909 $lines = array();
1910
1911 // Add the standard elements to the QR code
1912 $lines = [
1913 'BCD', // Service Tag (optional)
1914 '002', // Version (optional)
1915 '1', // Character set (optional)
1916 'SCT', // Identification (optional)
1917 ];
1918
1919 // Add the bank account information
1920 include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
1921
1922 $idofbankaccountouse = $this->fk_account;
1923 if (empty($idofbankaccountouse)) {
1924 $idofbankaccountouse = $this->fk_bank; // for backward compatibility
1925 }
1926 if (empty($idofbankaccountouse)) {
1927 $idofbankaccountouse = getDolGlobalInt('FACTURE_RIB_NUMBER');
1928 }
1929
1930 if ($idofbankaccountouse > 0) {
1931 $bankAccount = new Account($this->db);
1932 $bankAccount->fetch($idofbankaccountouse);
1933 $lines[] = $bankAccount->bic; //BIC (required)
1934 if (!empty($bankAccount->owner_name)) {
1935 $lines[] = $bankAccount->owner_name; //Owner of the bank account, if present (required)
1936 } else {
1937 $lines[] = $mysoc->name; //Name (required)
1938 }
1939 $lines[] = $bankAccount->iban; //IBAN (required)
1940 } else {
1941 $lines[] = ""; //BIC (required)
1942 $lines[] = $mysoc->name; //Name (required)
1943 $lines[] = ""; //IBAN (required)
1944 }
1945
1946 // Add the amount and reference
1947 $lines[] = 'EUR' . $amount_to_pay; // Amount (optional)
1948 $lines[] = ''; // Purpose (optional)
1949 $lines[] = ''; // Payment reference (optional)
1950 $lines[] = $this->ref; // Remittance Information (optional)
1951
1952 // Join the lines with newline characters and return the result
1953 return implode("\n", $lines);
1954 }
1960 public function buildZATCAQRString()
1961 {
1962 global $conf, $mysoc;
1963
1964 $tmplang = new Translate('', $conf);
1965 $tmplang->setDefaultLang('en_US');
1966 $tmplang->load("main");
1967
1968 $datestring = dol_print_date($this->date, 'dayhourrfc');
1969 //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
1970 //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
1971 $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
1972 $pricetaxstring = price2num($this->total_tva, 2, 1);
1973
1974 /*
1975 $name = implode(unpack("H*", $this->thirdparty->name));
1976 $vatnumber = implode(unpack("H*", $this->thirdparty->tva_intra));
1977 $date = implode(unpack("H*", $datestring));
1978 $pricewithtax = implode(unpack("H*", price2num($pricewithtaxstring, 2)));
1979 $pricetax = implode(unpack("H*", $pricetaxstring));
1980
1981 //var_dump(strlen($this->thirdparty->name));
1982 //var_dump(str_pad(dechex('9'), 2, '0', STR_PAD_LEFT));
1983 //var_dump($this->thirdparty->name);
1984 //var_dump(implode(unpack("H*", $this->thirdparty->name)));
1985 //var_dump(price($this->total_tva, 0, $tmplang, 0, -1, 2));
1986
1987 $s = '01'.str_pad(dechex(strlen($this->thirdparty->name)), 2, '0', STR_PAD_LEFT).$name;
1988 $s .= '02'.str_pad(dechex(strlen($this->thirdparty->tva_intra)), 2, '0', STR_PAD_LEFT).$vatnumber;
1989 $s .= '03'.str_pad(dechex(strlen($datestring)), 2, '0', STR_PAD_LEFT).$date;
1990 $s .= '04'.str_pad(dechex(strlen($pricewithtaxstring)), 2, '0', STR_PAD_LEFT).$pricewithtax;
1991 $s .= '05'.str_pad(dechex(strlen($pricetaxstring)), 2, '0', STR_PAD_LEFT).$pricetax;
1992 $s .= ''; // Hash of xml invoice
1993 $s .= ''; // ecda signature
1994 $s .= ''; // ecda public key
1995 $s .= ''; // ecda signature of public key stamp
1996 */
1997 $mysocname = $mysoc->name ?? '';
1998 $mysoctva_intra = $mysoc->tva_intra ?? '';
1999 // Using TLV format
2000 $s = pack('C1', 1).pack('C1', strlen($mysocname)).$mysocname;
2001 $s .= pack('C1', 2).pack('C1', strlen($mysoctva_intra)).$mysoctva_intra;
2002 $s .= pack('C1', 3).pack('C1', strlen($datestring)).$datestring;
2003 $s .= pack('C1', 4).pack('C1', strlen($pricewithtaxstring)).$pricewithtaxstring;
2004 $s .= pack('C1', 5).pack('C1', strlen($pricetaxstring)).$pricetaxstring;
2005 $s .= ''; // Hash of xml invoice
2006 $s .= ''; // ecda signature
2007 $s .= ''; // ecda public key
2008 $s .= ''; // ecda signature of public key stamp
2009
2010 $s = base64_encode($s);
2011
2012 return $s;
2013 }
2014
2015
2022 {
2023 global $conf, $mysoc;
2024
2025 $tmplang = new Translate('', $conf);
2026 $tmplang->setDefaultLang('en_US');
2027 $tmplang->load("main");
2028
2029 $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
2030 $pricetaxstring = price2num($this->total_tva, 2, 1);
2031
2032 $complementaryinfo = '';
2033 /*
2034 Example: //S1/10/10201409/11/190512/20/1400.000-53/30/106017086/31/180508/32/7.7/40/2:10;0:30
2035 /10/ Numéro de facture – 10201409
2036 /11/ Date de facture – 12.05.2019
2037 /20/ Référence client – 1400.000-53
2038 /30/ Numéro IDE pour la TVA – CHE-106.017.086 TVA
2039 /31/ Date de la prestation pour la comptabilisation de la TVA – 08.05.2018
2040 /32/ Taux de TVA sur le montant total de la facture – 7.7%
2041 /40/ Conditions – 2% d’escompte à 10 jours, paiement net à 30 jours
2042 */
2043 $datestring = dol_print_date($this->date, '%y%m%d');
2044 //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
2045 //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
2046 $complementaryinfo = '//S1/10/'.str_replace('/', '', $this->ref).'/11/'.$datestring;
2047 if ($this->ref_client) {
2048 $complementaryinfo .= '/20/'.$this->ref_client;
2049 }
2050 if ($this->thirdparty->tva_intra) {
2051 $complementaryinfo .= '/30/'.$this->thirdparty->tva_intra;
2052 }
2053
2054 include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
2055 $bankaccount = new Account($this->db);
2056
2057 // Header
2058 $s = '';
2059 $s .= "SPC\n";
2060 $s .= "0200\n";
2061 $s .= "1\n";
2062 // Info Seller ("Compte / Payable à")
2063 if ($this->fk_account > 0) {
2064 // Bank BAN if country is LI or CH. TODO Add a test to check than IBAN start with CH or LI
2065 $bankaccount->fetch($this->fk_account);
2066 $s .= $bankaccount->iban."\n";
2067 } else {
2068 $s .= "\n";
2069 }
2070 if ($bankaccount->id > 0 && getDolGlobalString('PDF_SWISS_QRCODE_USE_OWNER_OF_ACCOUNT_AS_CREDITOR')) {
2071 // If a bank account is provided and we ask to use it as creditor, we use the bank address
2072 // 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 ?
2073 $s .= "S\n";
2074 $s .= dol_trunc($bankaccount->owner_name, 70, 'right', 'UTF-8', 1)."\n";
2075 $addresslinearray = explode("\n", $bankaccount->owner_address);
2076 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
2077 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
2078 /*$s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
2079 $s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
2080 $s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";*/
2081 } else {
2082 $s .= "S\n";
2083 $s .= dol_trunc((string) $mysoc->name, 70, 'right', 'UTF-8', 1)."\n";
2084 $addresslinearray = explode("\n", $mysoc->address);
2085 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
2086 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
2087 $s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
2088 $s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
2089 $s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";
2090 }
2091 // Final seller (Ultimate seller) ("Créancier final" = "En faveur de")
2092 $s .= "\n";
2093 $s .= "\n";
2094 $s .= "\n";
2095 $s .= "\n";
2096 $s .= "\n";
2097 $s .= "\n";
2098 $s .= "\n";
2099 // Amount of payment (to do?)
2100 $s .= price($pricewithtaxstring, 0, 'none', 0, 0, 2)."\n";
2101 $s .= ($this->multicurrency_code ? $this->multicurrency_code : $conf->currency)."\n";
2102 // Buyer
2103 $s .= "S\n";
2104 $s .= dol_trunc((string) $this->thirdparty->name, 70, 'right', 'UTF-8', 1)."\n";
2105 $addresslinearray = explode("\n", $this->thirdparty->address);
2106 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
2107 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
2108 $s .= dol_trunc($this->thirdparty->zip, 16, 'right', 'UTF-8', 1)."\n";
2109 $s .= dol_trunc($this->thirdparty->town, 35, 'right', 'UTF-8', 1)."\n";
2110 $s .= dol_trunc($this->thirdparty->country_code, 2, 'right', 'UTF-8', 1)."\n";
2111 // ID of payment
2112 $s .= "NON\n"; // NON or QRR
2113 $s .= "\n"; // QR Code reference if previous field is QRR
2114 // Free text
2115 if ($complementaryinfo) {
2116 $s .= $complementaryinfo."\n";
2117 } else {
2118 $s .= "\n";
2119 }
2120 $s .= "EPD\n";
2121 // More text, complementary info
2122 if ($complementaryinfo) {
2123 $s .= $complementaryinfo."\n";
2124 }
2125 $s .= "\n";
2126 //var_dump($s);exit;
2127 return $s;
2128 }
2129}
2130
2131
2132
2133require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
2134
2139{
2145 public $label;
2146
2152 public $ref; // Product ref (deprecated)
2158 public $libelle; // Product label (deprecated)
2159
2164 public $product_type = 0;
2165
2170 public $product_ref;
2171
2176 public $product_label;
2177
2182 public $product_desc;
2183
2188 public $qty;
2189
2194 public $subprice;
2195
2201 public $price;
2202
2207 public $fk_product;
2208
2213 public $vat_src_code;
2214
2219 public $tva_tx;
2220
2225 public $localtax1_tx;
2226
2231 public $localtax2_tx;
2232
2238 public $localtax1_type;
2239
2245 public $localtax2_type;
2246
2251 public $remise_percent;
2252
2258 public $remise;
2259
2264 public $total_ht;
2265
2270 public $total_tva;
2271
2276 public $total_localtax1;
2277
2282 public $total_localtax2;
2283
2288 public $total_ttc;
2289
2293 public $revenuestamp;
2294
2295
2299 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
2303 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
2304
2308 public $buy_price_ht;
2313 public $buyprice;
2318 public $pa_ht;
2319
2323 public $marge_tx;
2327 public $marque_tx;
2328
2335 public $info_bits = 0;
2336
2345 public $special_code = 0;
2346
2351 public $fk_user_author;
2352
2357 public $fk_user_modif;
2358
2362 public $fk_code_ventilation;
2363}
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:67
$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.
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 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
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.
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|dolcrypt):/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition repair.php:158