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