dolibarr 21.0.0-alpha
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 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 3 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 */
22
29require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
30require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
31
35abstract class CommonInvoice extends CommonObject
36{
37 use CommonIncoterm;
38
42 public $title;
43
47 public $type = self::TYPE_STANDARD;
48
52 public $subtype;
53
59 public $fk_soc;
63 public $socid;
64
68 public $paye;
69
75 public $date;
76
80 public $date_lim_reglement;
81
85 public $cond_reglement_id; // Id in llx_c_paiement
89 public $cond_reglement_code; // Code in llx_c_paiement
93 public $cond_reglement_label;
97 public $cond_reglement_doc;
98
102 public $mode_reglement_id;
106 public $mode_reglement_code;
107
111 public $mode_reglement;
112
116 public $revenuestamp;
117
121 public $totalpaid; // duplicate with sumpayed
125 public $totaldeposits; // duplicate with sumdeposit
129 public $totalcreditnotes; // duplicate with sumcreditnote
130
134 public $sumpayed;
138 public $sumpayed_multicurrency;
142 public $sumdeposit;
146 public $sumdeposit_multicurrency;
150 public $sumcreditnote;
154 public $sumcreditnote_multicurrency;
158 public $remaintopay;
159
163 public $stripechargedone;
164
168 public $stripechargeerror;
169
174 public $description;
175
181 public $ref_client;
182
186 public $situation_cycle_ref;
187
193 public $close_code;
194
199 public $close_note;
200
201
206 public $postactionmessages;
207
208
212 const TYPE_STANDARD = 0;
213
218
223
227 const TYPE_DEPOSIT = 3;
228
233 const TYPE_PROFORMA = 4;
234
238 const TYPE_SITUATION = 5;
239
243 const STATUS_DRAFT = 0;
244
249
257 const STATUS_CLOSED = 2;
258
267
268
269
277 public function getRemainToPay($multicurrency = 0)
278 {
279 $alreadypaid = 0.0;
280 $alreadypaid += $this->getSommePaiement($multicurrency);
281 $alreadypaid += $this->getSumDepositsUsed($multicurrency);
282 $alreadypaid += $this->getSumCreditNotesUsed($multicurrency);
283
284 $remaintopay = price2num($this->total_ttc - $alreadypaid, 'MT');
285 if ($this->status == self::STATUS_CLOSED && $this->close_code == 'discount_vat') { // If invoice closed with discount for anticipated payment
286 $remaintopay = 0.0;
287 }
288 return $remaintopay;
289 }
290
299 public function getSommePaiement($multicurrency = 0)
300 {
301 $table = 'paiement_facture';
302 $field = 'fk_facture';
303 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
304 $table = 'paiementfourn_facturefourn';
305 $field = 'fk_facturefourn';
306 }
307
308 $sql = "SELECT sum(amount) as amount, sum(multicurrency_amount) as multicurrency_amount";
309 $sql .= " FROM ".$this->db->prefix().$table;
310 $sql .= " WHERE ".$field." = ".((int) $this->id);
311
312 dol_syslog(get_class($this)."::getSommePaiement", LOG_DEBUG);
313
314 $resql = $this->db->query($sql);
315 if ($resql) {
316 $obj = $this->db->fetch_object($resql);
317
318 $this->db->free($resql);
319
320 if ($obj) {
321 if ($multicurrency < 0) {
322 $this->sumpayed = $obj->amount;
323 $this->sumpayed_multicurrency = $obj->multicurrency_amount;
324 return array('alreadypaid' => (float) $obj->amount, 'alreadypaid_multicurrency' => (float) $obj->multicurrency_amount);
325 } elseif ($multicurrency) {
326 $this->sumpayed_multicurrency = $obj->multicurrency_amount;
327 return (float) $obj->multicurrency_amount;
328 } else {
329 $this->sumpayed = $obj->amount;
330 return (float) $obj->amount;
331 }
332 } else {
333 return 0;
334 }
335 } else {
336 $this->error = $this->db->lasterror();
337 return -1;
338 }
339 }
340
350 public function getSumDepositsUsed($multicurrency = 0)
351 {
352 /*if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
353 // 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.
354 return 0.0;
355 }*/
356
357 require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
358
359 $discountstatic = new DiscountAbsolute($this->db);
360 $result = $discountstatic->getSumDepositsUsed($this, $multicurrency);
361
362 if ($result >= 0) {
363 if ($multicurrency) {
364 $this->sumdeposit_multicurrency = $result;
365 } else {
366 $this->sumdeposit = $result;
367 }
368
369 return $result;
370 } else {
371 $this->error = $discountstatic->error;
372 return -1;
373 }
374 }
375
383 public function getSumCreditNotesUsed($multicurrency = 0)
384 {
385 require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
386
387 $discountstatic = new DiscountAbsolute($this->db);
388 $result = $discountstatic->getSumCreditNotesUsed($this, $multicurrency);
389 if ($result >= 0) {
390 if ($multicurrency) {
391 $this->sumcreditnote_multicurrency = $result;
392 } else {
393 $this->sumcreditnote = $result;
394 }
395
396 return $result;
397 } else {
398 $this->error = $discountstatic->error;
399 return -1;
400 }
401 }
402
409 public function getSumFromThisCreditNotesNotUsed($multicurrency = 0)
410 {
411 require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
412
413 $discountstatic = new DiscountAbsolute($this->db);
414 $result = $discountstatic->getSumFromThisCreditNotesNotUsed($this, $multicurrency);
415 if ($result >= 0) {
416 return $result;
417 } else {
418 $this->error = $discountstatic->error;
419 return -1;
420 }
421 }
422
429 {
430 $idarray = array();
431
432 $sql = "SELECT rowid";
433 $sql .= " FROM ".$this->db->prefix().$this->table_element;
434 $sql .= " WHERE fk_facture_source = ".((int) $this->id);
435 $sql .= " AND type = 2";
436 $resql = $this->db->query($sql);
437 if ($resql) {
438 $num = $this->db->num_rows($resql);
439 $i = 0;
440 while ($i < $num) {
441 $row = $this->db->fetch_row($resql);
442 $idarray[] = $row[0];
443 $i++;
444 }
445 } else {
446 dol_print_error($this->db);
447 }
448 return $idarray;
449 }
450
457 public function getIdReplacingInvoice($option = '')
458 {
459 $sql = "SELECT rowid";
460 $sql .= " FROM ".$this->db->prefix().$this->table_element;
461 $sql .= " WHERE fk_facture_source = ".((int) $this->id);
462 $sql .= " AND type < 2";
463 if ($option == 'validated') {
464 $sql .= ' AND fk_statut = 1';
465 }
466 // PROTECTION BAD DATA
467 // In case the database is corrupted and there is a valid replectement invoice
468 // and another no, priority is given to the valid one.
469 // Should not happen (unless concurrent access and 2 people have created a
470 // replacement invoice for the same invoice at the same time)
471 $sql .= " ORDER BY fk_statut DESC";
472
473 $resql = $this->db->query($sql);
474 if ($resql) {
475 $obj = $this->db->fetch_object($resql);
476 if ($obj) {
477 // If there is any
478 return $obj->rowid;
479 } else {
480 // If no invoice replaces it
481 return 0;
482 }
483 } else {
484 return -1;
485 }
486 }
487
498 public function getListOfPayments($filtertype = '', $multicurrency = 0, $mode = 0)
499 {
500 $retarray = array();
501 $this->error = ''; // By default no error, list can be empty.
502
503 $table = 'paiement_facture';
504 $table2 = 'paiement';
505 $field = 'fk_facture';
506 $field2 = 'fk_paiement';
507 $field3 = ', p.ref_ext';
508 $field4 = ', p.fk_bank'; // Bank line id
509 $sharedentity = 'facture';
510 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
511 $table = 'paiementfourn_facturefourn';
512 $table2 = 'paiementfourn';
513 $field = 'fk_facturefourn';
514 $field2 = 'fk_paiementfourn';
515 $field3 = '';
516 $sharedentity = 'facture_fourn';
517 }
518
519 // List of payments
520 if (empty($mode) || $mode == 1) {
521 $sql = "SELECT p.ref, pf.amount, pf.multicurrency_amount, p.fk_paiement, p.datep, p.num_paiement as num, t.code".$field3 . $field4;
522 $sql .= " FROM ".$this->db->prefix().$table." as pf, ".$this->db->prefix().$table2." as p, ".$this->db->prefix()."c_paiement as t";
523 $sql .= " WHERE pf.".$field." = ".((int) $this->id);
524 $sql .= " AND pf.".$field2." = p.rowid";
525 $sql .= ' AND p.fk_paiement = t.id';
526 $sql .= ' AND p.entity IN ('.getEntity($sharedentity).')';
527 if ($filtertype) {
528 $sql .= " AND t.code='PRE'";
529 }
530
531 dol_syslog(get_class($this)."::getListOfPayments filtertype=".$filtertype." multicurrency=".$multicurrency." mode=".$mode, LOG_DEBUG);
532
533 $resql = $this->db->query($sql);
534 if ($resql) {
535 $num = $this->db->num_rows($resql);
536 $i = 0;
537 while ($i < $num) {
538 $obj = $this->db->fetch_object($resql);
539 if ($multicurrency) {
540 $tmp = array('amount' => $obj->multicurrency_amount, 'type' => $obj->code, 'typeline' => 'payment', 'date' => $obj->datep, 'num' => $obj->num, 'ref' => $obj->ref);
541 } else {
542 $tmp = array('amount' => $obj->amount, 'type' => $obj->code, 'typeline' => 'payment', 'date' => $obj->datep, 'num' => $obj->num, 'ref' => $obj->ref);
543 }
544 if (!empty($field3)) {
545 $tmp['ref_ext'] = $obj->ref_ext;
546 }
547 if (!empty($field4)) {
548 $tmp['fk_bank_line'] = $obj->fk_bank;
549 }
550 $retarray[] = $tmp;
551 $i++;
552 }
553 $this->db->free($resql);
554 } else {
555 $this->error = $this->db->lasterror();
556 dol_print_error($this->db);
557 return array();
558 }
559 }
560
561 // Look for credit notes and discounts and deposits
562 if (empty($mode) || $mode == 2) {
563 $sql = '';
564 if ($this->element == 'facture' || $this->element == 'invoice') {
565 $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";
566 $sql .= ' FROM '.$this->db->prefix().'societe_remise_except as rc, '.$this->db->prefix().'facture as f';
567 $sql .= ' WHERE rc.fk_facture_source=f.rowid AND rc.fk_facture = '.((int) $this->id);
568 $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)
569 } elseif ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
570 $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";
571 $sql .= ' FROM '.$this->db->prefix().'societe_remise_except as rc, '.$this->db->prefix().'facture_fourn as f';
572 $sql .= ' WHERE rc.fk_invoice_supplier_source=f.rowid AND rc.fk_invoice_supplier = '.((int) $this->id);
573 $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)
574 }
575
576 if ($sql) {
577 $resql = $this->db->query($sql);
578 if ($resql) {
579 $num = $this->db->num_rows($resql);
580 $i = 0;
581 while ($i < $num) {
582 $obj = $this->db->fetch_object($resql);
583 if ($multicurrency) {
584 $retarray[] = array('amount' => $obj->multicurrency_amount, 'type' => $obj->type, 'typeline' => 'discount', 'date' => $obj->date, 'num' => '0', 'ref' => $obj->ref);
585 } else {
586 $retarray[] = array('amount' => $obj->amount, 'type' => $obj->type, 'typeline' => 'discount', 'date' => $obj->date, 'num' => '', 'ref' => $obj->ref);
587 }
588 $i++;
589 }
590 } else {
591 $this->error = $this->db->lasterror();
592 dol_print_error($this->db);
593 return array();
594 }
595 $this->db->free($resql);
596 } else {
597 $this->error = $this->db->lasterror();
598 dol_print_error($this->db);
599 return array();
600 }
601 }
602
603 return $retarray;
604 }
605
606
607 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
621 public function is_erasable()
622 {
623 // phpcs:enable
624
625 // We check if invoice is a temporary number (PROVxxxx)
626 $tmppart = substr($this->ref, 1, 4);
627
628 if ($this->status == self::STATUS_DRAFT && $tmppart === 'PROV') { // If draft invoice and ref not yet defined
629 return 1;
630 }
631
632 if (getDolGlobalString('INVOICE_CAN_NEVER_BE_REMOVED')) {
633 return 0;
634 }
635
636 // If not a draft invoice and not temporary invoice
637 if ($tmppart !== 'PROV') {
638 $ventilExportCompta = $this->getVentilExportCompta();
639 if ($ventilExportCompta != 0) {
640 return -1;
641 }
642
643 // Get last number of validated invoice
644 if ($this->element != 'invoice_supplier') {
645 if (empty($this->thirdparty)) {
646 $this->fetch_thirdparty(); // We need to have this->thirdparty defined, in case of numbering rule use tags that depend on thirdparty (like {t} tag).
647 }
648 $maxref = $this->getNextNumRef($this->thirdparty, 'last');
649
650 // If there is no invoice into the reset range and not already dispatched, we can delete
651 // If invoice to delete is last one and not already dispatched, we can delete
652 if (!getDolGlobalString('INVOICE_CAN_ALWAYS_BE_REMOVED') && $maxref != '' && $maxref != $this->ref) {
653 return -2;
654 }
655
656 // TODO If there is payment in bookkeeping, check payment is not dispatched in accounting
657 // ...
658
659 if ($this->situation_cycle_ref && method_exists($this, 'is_last_in_cycle')) {
660 $last = $this->is_last_in_cycle();
661 if (!$last) {
662 return -3;
663 }
664 }
665 }
666 }
667
668 // Test if there is at least one payment. If yes, refuse to delete.
669 if (!getDolGlobalString('INVOICE_CAN_ALWAYS_BE_REMOVED') && $this->getSommePaiement() > 0) {
670 return -4;
671 }
672
673 return 2;
674 }
675
682 public function getVentilExportCompta()
683 {
684 $alreadydispatched = 0;
685
686 $type = 'customer_invoice';
687 if ($this->element == 'invoice_supplier') {
688 $type = 'supplier_invoice';
689 }
690
691 $sql = " SELECT COUNT(ab.rowid) as nb FROM ".$this->db->prefix()."accounting_bookkeeping as ab";
692 $sql .= " WHERE ab.doc_type='".$this->db->escape($type)."' AND ab.fk_doc = ".((int) $this->id);
693
694 $resql = $this->db->query($sql);
695 if ($resql) {
696 $obj = $this->db->fetch_object($resql);
697 if ($obj) {
698 $alreadydispatched = $obj->nb;
699 }
700 } else {
701 $this->error = $this->db->lasterror();
702 return -1;
703 }
704
705 if ($alreadydispatched) {
706 return 1;
707 }
708 return 0;
709 }
710
718 public function getNextNumRef($soc, $mode = 'next')
719 {
720 // TODO Must be implemented into main class
721 return '';
722 }
723
730 public function getLibType($withbadge = 0)
731 {
732 global $langs;
733
734 $labellong = "Unknown";
735 $labelshort = "Unknown";
736 if ($this->type == CommonInvoice::TYPE_STANDARD) {
737 $labellong = "InvoiceStandard";
738 $labelshort = "InvoiceStandardShort";
739 } elseif ($this->type == CommonInvoice::TYPE_REPLACEMENT) {
740 $labellong = "InvoiceReplacement";
741 $labelshort = "InvoiceReplacementShort";
742 } elseif ($this->type == CommonInvoice::TYPE_CREDIT_NOTE) {
743 $labellong = "InvoiceAvoir";
744 $labelshort = "CreditNote";
745 } elseif ($this->type == CommonInvoice::TYPE_DEPOSIT) {
746 $labellong = "InvoiceDeposit";
747 $labelshort = "Deposit";
748 } elseif ($this->type == CommonInvoice::TYPE_PROFORMA) { // @phan-suppress-current-line PhanDeprecatedClassConstant
749 $labellong = "InvoiceProForma"; // Not used.
750 $labelshort = "ProForma";
751 } elseif ($this->type == CommonInvoice::TYPE_SITUATION) {
752 $labellong = "InvoiceSituation";
753 $labelshort = "Situation";
754 }
755
756 $out = '';
757 if ($withbadge) {
758 $out .= '<span class="badgeneutral" title="'.dol_escape_htmltag($langs->trans($labellong)).'">';
759 }
760 $out .= $langs->trans($withbadge == 2 ? $labelshort : $labellong);
761 if ($withbadge) {
762 $out .= '</span>';
763 }
764 return $out;
765 }
766
773 public function getSubtypeLabel($table = '')
774 {
775 $subtypeLabel = '';
776 if ($table === 'facture' || $table === 'facture_fourn') {
777 $sql = "SELECT s.label FROM " . $this->db->prefix() . $table . " AS f";
778 $sql .= " INNER JOIN " . $this->db->prefix() . "c_invoice_subtype AS s ON f.subtype = s.rowid";
779 $sql .= " WHERE f.ref = '".$this->db->escape($this->ref)."'";
780 } elseif ($table === 'facture_rec' || $table === 'facture_fourn_rec') {
781 $sql = "SELECT s.label FROM " . $this->db->prefix() . $table . " AS f";
782 $sql .= " INNER JOIN " . $this->db->prefix() . "c_invoice_subtype AS s ON f.subtype = s.rowid";
783 $sql .= " WHERE f.titre = '".$this->db->escape($this->title)."'";
784 } else {
785 return -1;
786 }
787
788 $resql = $this->db->query($sql);
789 if ($resql) {
790 while ($obj = $this->db->fetch_object($resql)) {
791 $subtypeLabel = $obj->label;
792 }
793 } else {
794 dol_print_error($this->db);
795 return -1;
796 }
797
798 return $subtypeLabel;
799 }
800
807 public function getArrayOfInvoiceSubtypes($mode = 0)
808 {
809 global $mysoc;
810
811 $effs = array();
812
813 $sql = "SELECT rowid, code, label as label";
814 $sql .= " FROM " . MAIN_DB_PREFIX . 'c_invoice_subtype';
815 $sql .= " WHERE active = 1 AND fk_country = ".((int) $mysoc->country_id)." AND entity IN(".getEntity('c_invoice_subtype').")";
816 $sql .= " ORDER by rowid, code";
817
818 dol_syslog(get_class($this) . '::getArrayOfInvoiceSubtypes', LOG_DEBUG);
819 $resql = $this->db->query($sql);
820 if ($resql) {
821 $num = $this->db->num_rows($resql);
822 $i = 0;
823
824 while ($i < $num) {
825 $objp = $this->db->fetch_object($resql);
826 if (!$mode) {
827 $key = $objp->rowid;
828 $effs[$key] = $objp->label;
829 } else {
830 $key = $objp->code;
831 $effs[$key] = $objp->rowid;
832 }
833
834 $i++;
835 }
836 $this->db->free($resql);
837 }
838
839 return $effs;
840 }
841
849 public function getLibStatut($mode = 0, $alreadypaid = -1)
850 {
851 return $this->LibStatut($this->paye, $this->status, $mode, $alreadypaid, $this->type);
852 }
853
854 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
865 public function LibStatut($paye, $status, $mode = 0, $alreadypaid = -1, $type = -1)
866 {
867 // phpcs:enable
868 global $langs, $hookmanager;
869 $langs->load('bills');
870
871 if ($type == -1) {
872 $type = $this->type;
873 }
874
875 $statusType = 'status0';
876 $prefix = 'Short';
877 if (!$paye) {
878 if ($status == 0) {
879 $labelStatus = $langs->transnoentitiesnoconv('BillStatusDraft');
880 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusDraft');
881 } elseif (($status == 3 || $status == 2) && $alreadypaid <= 0) {
882 if ($status == 3) {
883 $labelStatus = $langs->transnoentitiesnoconv('BillStatusCanceled');
884 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusCanceled');
885 } else {
886 $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedUnpaid');
887 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedUnpaid');
888 }
889 $statusType = 'status5';
890 } elseif (($status == 3 || $status == 2) && $alreadypaid > 0) {
891 $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedPaidPartially');
892 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedPaidPartially');
893 $statusType = 'status9';
894 } elseif ($alreadypaid == 0) {
895 $labelStatus = $langs->transnoentitiesnoconv('BillStatusNotPaid');
896 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusNotPaid');
897 $statusType = 'status1';
898 } else {
899 $labelStatus = $langs->transnoentitiesnoconv('BillStatusStarted');
900 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusStarted');
901 $statusType = 'status3';
902 }
903 } else {
904 $statusType = 'status6';
905
906 if ($type == self::TYPE_CREDIT_NOTE) {
907 $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaidBackOrConverted'); // credit note
908 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaidBackOrConverted'); // credit note
909 } elseif ($type == self::TYPE_DEPOSIT) {
910 $labelStatus = $langs->transnoentitiesnoconv('BillStatusConverted'); // deposit invoice
911 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusConverted'); // deposit invoice
912 } else {
913 $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaid');
914 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaid');
915 }
916 }
917
918 $parameters = array(
919 'status' => $status,
920 'mode' => $mode,
921 'paye' => $paye,
922 'alreadypaid' => $alreadypaid,
923 'type' => $type
924 );
925
926 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
927
928 if ($reshook > 0) {
929 return $hookmanager->resPrint;
930 }
931
932
933
934 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
935 }
936
937 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
945 public function calculate_date_lim_reglement($cond_reglement = 0)
946 {
947 // phpcs:enable
948 if (!$cond_reglement) {
949 $cond_reglement = $this->cond_reglement_code;
950 }
951 if (!$cond_reglement) {
952 $cond_reglement = $this->cond_reglement_id;
953 }
954 if (!$cond_reglement) {
955 return $this->date;
956 }
957
958 $cdr_nbjour = 0;
959 $cdr_type = 0;
960 $cdr_decalage = 0;
961
962 $sqltemp = "SELECT c.type_cdr, c.nbjour, c.decalage";
963 $sqltemp .= " FROM ".$this->db->prefix()."c_payment_term as c";
964 if (is_numeric($cond_reglement)) {
965 $sqltemp .= " WHERE c.rowid=".((int) $cond_reglement);
966 } else {
967 $sqltemp .= " WHERE c.entity IN (".getEntity('c_payment_term').")";
968 $sqltemp .= " AND c.code = '".$this->db->escape($cond_reglement)."'";
969 }
970
971 dol_syslog(get_class($this).'::calculate_date_lim_reglement', LOG_DEBUG);
972 $resqltemp = $this->db->query($sqltemp);
973 if ($resqltemp) {
974 if ($this->db->num_rows($resqltemp)) {
975 $obj = $this->db->fetch_object($resqltemp);
976 $cdr_nbjour = $obj->nbjour;
977 $cdr_type = $obj->type_cdr;
978 $cdr_decalage = $obj->decalage;
979 }
980 } else {
981 $this->error = $this->db->error();
982 return -1;
983 }
984 $this->db->free($resqltemp);
985
986 /* Definition de la date limit */
987
988 // 0 : adding the number of days
989 if ($cdr_type == 0) {
990 $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
991
992 $datelim += ($cdr_decalage * 3600 * 24);
993 } elseif ($cdr_type == 1) {
994 // 1 : application of the "end of the month" rule
995 $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
996
997 $mois = date('m', $datelim);
998 $annee = date('Y', $datelim);
999 if ($mois == 12) {
1000 $mois = 1;
1001 $annee += 1;
1002 } else {
1003 $mois += 1;
1004 }
1005 // We move at the beginning of the next month, and we take a day off
1006 $datelim = dol_mktime(12, 0, 0, $mois, 1, $annee);
1007 $datelim -= (3600 * 24);
1008
1009 $datelim += ($cdr_decalage * 3600 * 24);
1010 } elseif ($cdr_type == 2 && !empty($cdr_decalage)) {
1011 // 2 : application of the rule, the N of the current or next month
1012 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
1013 $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
1014
1015 $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
1016 $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
1017 $date_lim_next = dol_time_plus_duree((int) $date_lim_current, 1, 'm'); // Add 1 month
1018
1019 $diff = $date_piece - $date_lim_current;
1020
1021 if ($diff < 0) {
1022 $datelim = $date_lim_current;
1023 } else {
1024 $datelim = $date_lim_next;
1025 }
1026 } else {
1027 return 'Bad value for type_cdr in database for record cond_reglement = '.$cond_reglement;
1028 }
1029
1030 return $datelim;
1031 }
1032
1033 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1046 public function demande_prelevement(User $fuser, float $amount = 0, string $type = 'direct-debit', string $sourcetype = 'facture', int $checkduplicateamongall = 0, int $ribId = 0)
1047 {
1048 // phpcs:enable
1049 global $conf;
1050
1051 $error = 0;
1052
1053 dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
1054
1055 if ($this->status > self::STATUS_DRAFT && $this->paye == 0) {
1056 require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
1057 $bac = new CompanyBankAccount($this->db);
1058 $bac->fetch($ribId, '', $this->socid);
1059
1060 $sql = "SELECT count(rowid) as nb";
1061 $sql .= " FROM ".$this->db->prefix()."prelevement_demande";
1062 if ($type == 'bank-transfer') {
1063 $sql .= " WHERE fk_facture_fourn = ".((int) $this->id);
1064 } else {
1065 $sql .= " WHERE fk_facture = ".((int) $this->id);
1066 }
1067 $sql .= " AND type = 'ban'"; // To exclude record done for some online payments
1068 if (empty($checkduplicateamongall)) {
1069 $sql .= " AND traite = 0";
1070 }
1071
1072 dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
1073
1074 $resql = $this->db->query($sql);
1075 if ($resql) {
1076 $obj = $this->db->fetch_object($resql);
1077 if ($obj && $obj->nb == 0) { // If no request found yet
1078 $now = dol_now();
1079
1080 $totalpaid = $this->getSommePaiement();
1081 $totalcreditnotes = $this->getSumCreditNotesUsed();
1082 $totaldeposits = $this->getSumDepositsUsed();
1083 //print "totalpaid=".$totalpaid." totalcreditnotes=".$totalcreditnotes." totaldeposts=".$totaldeposits;
1084
1085 // We can also use bcadd to avoid pb with floating points
1086 // For example print 239.2 - 229.3 - 9.9; does not return 0.
1087 //$resteapayer=bcadd($this->total_ttc,$totalpaid,$conf->global->MAIN_MAX_DECIMALS_TOT);
1088 //$resteapayer=bcadd($resteapayer,$totalavoir,$conf->global->MAIN_MAX_DECIMALS_TOT);
1089 if (empty($amount)) {
1090 $amount = price2num($this->total_ttc - $totalpaid - $totalcreditnotes - $totaldeposits, 'MT');
1091 }
1092
1093 if (is_numeric($amount) && $amount != 0) {
1094 $sql = 'INSERT INTO '.$this->db->prefix().'prelevement_demande(';
1095 if ($type == 'bank-transfer') {
1096 $sql .= 'fk_facture_fourn, ';
1097 } else {
1098 $sql .= 'fk_facture, ';
1099 }
1100 $sql .= ' amount, date_demande, fk_user_demande, code_banque, code_guichet, number, cle_rib, sourcetype, type, entity';
1101 if (empty($bac->id)) {
1102 $sql .= ')';
1103 } else {
1104 $sql .= ', fk_societe_rib)';
1105 }
1106 $sql .= " VALUES (".((int) $this->id);
1107 $sql .= ", ".((float) price2num($amount));
1108 $sql .= ", '".$this->db->idate($now)."'";
1109 $sql .= ", ".((int) $fuser->id);
1110 $sql .= ", '".$this->db->escape($bac->code_banque)."'";
1111 $sql .= ", '".$this->db->escape($bac->code_guichet)."'";
1112 $sql .= ", '".$this->db->escape($bac->number)."'";
1113 $sql .= ", '".$this->db->escape($bac->cle_rib)."'";
1114 $sql .= ", '".$this->db->escape($sourcetype)."'";
1115 $sql .= ", 'ban'";
1116 $sql .= ", ".((int) $conf->entity);
1117 if (!empty($bac->id)) {
1118 $sql .= ", '".$this->db->escape($bac->id)."'";
1119 }
1120 $sql .= ")";
1121
1122 dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
1123 $resql = $this->db->query($sql);
1124 if (!$resql) {
1125 $this->error = $this->db->lasterror();
1126 dol_syslog(get_class($this).'::demandeprelevement Erreur');
1127 $error++;
1128 }
1129 } else {
1130 $this->error = 'WithdrawRequestErrorNilAmount';
1131 dol_syslog(get_class($this).'::demandeprelevement WithdrawRequestErrorNilAmount');
1132 $error++;
1133 }
1134
1135 if (!$error) {
1136 // Force payment mode of invoice to withdraw
1137 $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
1138 if ($payment_mode_id > 0) {
1139 $result = $this->setPaymentMethods($payment_mode_id);
1140 }
1141 }
1142
1143 if ($error) {
1144 return -1;
1145 }
1146 return 1;
1147 } else {
1148 $this->error = "A request already exists";
1149 dol_syslog(get_class($this).'::demandeprelevement Can t create a request to generate a direct debit, a request already exists.');
1150 return 0;
1151 }
1152 } else {
1153 $this->error = $this->db->error();
1154 dol_syslog(get_class($this).'::demandeprelevement Error -2');
1155 return -2;
1156 }
1157 } else {
1158 $this->error = "Status of invoice does not allow this";
1159 dol_syslog(get_class($this)."::demandeprelevement ".$this->error." $this->status, $this->paye, $this->mode_reglement_id");
1160 return -3;
1161 }
1162 }
1163
1164
1176 public function makeStripeCardRequest($fuser, $id, $sourcetype = 'facture')
1177 {
1178 // TODO See in sellyoursaas
1179 return 0;
1180 }
1181
1194 public function makeStripeSepaRequest($fuser, $did, $type = 'direct-debit', $sourcetype = 'facture', $service = '', $forcestripe = '')
1195 {
1196 global $conf, $user, $langs;
1197
1198 if ($type != 'bank-transfer' && $type != 'credit-transfer' && !getDolGlobalString('STRIPE_SEPA_DIRECT_DEBIT')) {
1199 return 0;
1200 }
1201 if ($type != 'direct-debit' && !getDolGlobalString('STRIPE_SEPA_CREDIT_TRANSFER')) {
1202 return 0;
1203 }
1204 // Set a default value for service if not provided
1205 if (empty($service)) {
1206 $service = 'StripeTest';
1207 if (getDolGlobalString('STRIPE_LIVE') && !GETPOST('forcesandbox', 'alpha')) {
1208 $service = 'StripeLive';
1209 }
1210 }
1211
1212 $error = 0;
1213
1214 dol_syslog(get_class($this)."::makeStripeSepaRequest start did=".$did." type=".$type." service=".$service." sourcetype=".$sourcetype." forcestripe=".$forcestripe, LOG_DEBUG);
1215
1216 if ($this->status > self::STATUS_DRAFT && $this->paye == 0) {
1217 // Get the default payment mode for BAN payment of the third party
1218 require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
1219 $bac = new CompanyBankAccount($this->db); // Table societe_rib
1220 $result = $bac->fetch(0, '', $this->socid, 1, 'ban');
1221 if ($result <= 0 || empty($bac->id)) {
1222 $this->error = $langs->trans("ThirdpartyHasNoDefaultBanAccount");
1223 $this->errors[] = $this->error;
1224 dol_syslog(get_class($this)."::makeStripeSepaRequest ".$this->error);
1225 return -1;
1226 }
1227
1228 // Load the pending payment request to process (with rowid=$did)
1229 $sql = "SELECT rowid, date_demande, amount, fk_facture, fk_facture_fourn, fk_salary, fk_prelevement_bons";
1230 $sql .= " FROM ".$this->db->prefix()."prelevement_demande";
1231 $sql .= " WHERE rowid = ".((int) $did);
1232 if ($type != 'bank-transfer' && $type != 'credit-transfer') {
1233 $sql .= " AND fk_facture = ".((int) $this->id); // Add a protection to not pay another invoice than current one
1234 }
1235 if ($type != 'direct-debit') {
1236 if ($sourcetype == 'salary') {
1237 $sql .= " AND fk_salary = ".((int) $this->id); // Add a protection to not pay another salary than current one
1238 } else {
1239 $sql .= " AND fk_facture_fourn = ".((int) $this->id); // Add a protection to not pay another invoice than current one
1240 }
1241 }
1242 $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)
1243
1244 dol_syslog(get_class($this)."::makeStripeSepaRequest load requests to process", LOG_DEBUG);
1245 $resql = $this->db->query($sql);
1246 if ($resql) {
1247 $obj = $this->db->fetch_object($resql);
1248 if (!$obj) {
1249 dol_print_error($this->db, 'CantFindRequestWithId');
1250 return -2;
1251 }
1252
1253 // amount to pay
1254 $amount = $obj->amount;
1255
1256 if (is_numeric($amount) && $amount != 0) {
1257 require_once DOL_DOCUMENT_ROOT.'/societe/class/companypaymentmode.class.php';
1258 $companypaymentmode = new CompanyPaymentMode($this->db); // table societe_rib
1259 $companypaymentmode->fetch($bac->id);
1260
1261 $this->stripechargedone = 0;
1262 $this->stripechargeerror = 0;
1263
1264 $now = dol_now();
1265
1266 $currency = $conf->currency;
1267
1268 $errorforinvoice = 0; // We reset the $errorforinvoice at each invoice loop
1269
1270 $this->fetch_thirdparty();
1271
1272 dol_syslog("makeStripeSepaRequest Process payment request amount=".$amount." thirdparty_id=" . $this->thirdparty->id . ", thirdparty_name=" . $this->thirdparty->name . " ban id=" . $bac->id, LOG_DEBUG);
1273
1274 //$alreadypayed = $this->getSommePaiement();
1275 //$amount_credit_notes_included = $this->getSumCreditNotesUsed();
1276 //$amounttopay = $this->total_ttc - $alreadypayed - $amount_credit_notes_included;
1277 $amounttopay = $amount;
1278
1279 // Correct the amount according to unit of currency
1280 // See https://support.stripe.com/questions/which-zero-decimal-currencies-does-stripe-support
1281 $arrayzerounitcurrency = ['BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'];
1282 $amountstripe = $amounttopay;
1283 if (!in_array($currency, $arrayzerounitcurrency)) {
1284 $amountstripe *= 100;
1285 }
1286
1287 $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.
1288 if (!($fk_bank_account > 0)) {
1289 $error++;
1290 $errorforinvoice++;
1291 dol_syslog("makeStripeSepaRequest Error no bank account defined for Stripe payments", LOG_ERR);
1292 $this->errors[] = "Error bank account for Stripe payments not defined into Stripe module";
1293 }
1294
1295 $this->db->begin();
1296
1297 // Create a prelevement_bon
1298 require_once DOL_DOCUMENT_ROOT.'/compta/prelevement/class/bonprelevement.class.php';
1299 $bon = new BonPrelevement($this->db);
1300 if (!$error) {
1301 if (empty($obj->fk_prelevement_bons)) {
1302 // This creates a record into llx_prelevement_bons and updates link with llx_prelevement_demande
1303 $nbinvoices = $bon->create(0, 0, 'real', 'ALL', '', 0, $type, $did, $fk_bank_account);
1304 if ($nbinvoices <= 0) {
1305 $error++;
1306 $errorforinvoice++;
1307 dol_syslog("makeStripeSepaRequest Error on BonPrelevement creation", LOG_ERR);
1308 $this->errors[] = "Error on BonPrelevement creation";
1309 }
1310 /*
1311 if (!$error) {
1312 // Update the direct debit payment request of the processed request to save the id of the prelevement_bon
1313 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
1314 $sql .= " fk_prelevement_bons = ".((int) $bon->id);
1315 $sql .= " WHERE rowid = ".((int) $did);
1316
1317 $result = $this->db->query($sql);
1318 if ($result < 0) {
1319 $error++;
1320 $this->errors[] = "Error on updating fk_prelevement_bons to ".$bon->id;
1321 }
1322 }
1323 */
1324 } else {
1325 $error++;
1326 $errorforinvoice++;
1327 dol_syslog("makeStripeSepaRequest Error Line already part of a bank payment order", LOG_ERR);
1328 $this->errors[] = "The line is already included into a bank payment order. Delete the bank payment order first.";
1329 }
1330 }
1331
1332 $paymentintent = null;
1333 if (!$error) {
1334 if ($amountstripe > 0) {
1335 try {
1336 global $savstripearrayofkeysbyenv;
1337 global $stripearrayofkeysbyenv;
1338 $servicestatus = 0;
1339 if ($service == 'StripeLive') {
1340 $servicestatus = 1;
1341 }
1342
1343 //var_dump($companypaymentmode);
1344 dol_syslog("makeStripeSepaRequest We will try to pay with companypaymentmodeid=" . $companypaymentmode->id . " stripe_card_ref=" . $companypaymentmode->stripe_card_ref . " mode=" . $companypaymentmode->status, LOG_DEBUG);
1345
1346 $thirdparty = new Societe($this->db);
1347 $resultthirdparty = $thirdparty->fetch($this->socid);
1348
1349 include_once DOL_DOCUMENT_ROOT . '/stripe/class/stripe.class.php'; // This include the include of htdocs/stripe/config.php
1350 // So it inits or erases the $stripearrayofkeysbyenv
1351 $stripe = new Stripe($this->db);
1352
1353 if (empty($savstripearrayofkeysbyenv)) {
1354 $savstripearrayofkeysbyenv = $stripearrayofkeysbyenv;
1355 }
1356 dol_syslog("makeStripeSepaRequest Current Stripe environment is " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key']);
1357 dol_syslog("makeStripeSepaRequest Current Saved Stripe environment is ".$savstripearrayofkeysbyenv[$servicestatus]['publishable_key']);
1358
1359 $foundalternativestripeaccount = '';
1360
1361 // Force stripe to another value (by default this value is empty)
1362 if (! empty($forcestripe)) {
1363 dol_syslog("makeStripeSepaRequest A dedicated stripe account was forced, so we switch to it.");
1364
1365 $tmparray = explode('@', $forcestripe);
1366 if (! empty($tmparray[1])) {
1367 $tmparray2 = explode(':', $tmparray[1]);
1368 if (! empty($tmparray2[1])) {
1369 $stripearrayofkeysbyenv[$servicestatus]["publishable_key"] = $tmparray2[0];
1370 $stripearrayofkeysbyenv[$servicestatus]["secret_key"] = $tmparray2[1];
1371
1372 $stripearrayofkeys = $stripearrayofkeysbyenv[$servicestatus];
1373 \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1374
1375 $foundalternativestripeaccount = $tmparray[0]; // Store the customer id
1376
1377 dol_syslog("makeStripeSepaRequest We use now customer=".$foundalternativestripeaccount." publishable_key=".$stripearrayofkeys['publishable_key'], LOG_DEBUG);
1378 }
1379 }
1380
1381 if (! $foundalternativestripeaccount) {
1382 $stripearrayofkeysbyenv = $savstripearrayofkeysbyenv;
1383
1384 $stripearrayofkeys = $savstripearrayofkeysbyenv[$servicestatus];
1385 \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1386 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);
1387 }
1388 } else {
1389 $stripearrayofkeysbyenv = $savstripearrayofkeysbyenv;
1390
1391 $stripearrayofkeys = $savstripearrayofkeysbyenv[$servicestatus];
1392 \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1393 dol_syslog("makeStripeSepaRequest No dedicated Stripe Account requested, so we use global one, so ".$stripearrayofkeys['publishable_key'], LOG_DEBUG);
1394 }
1395
1396 $stripeacc = $stripe->getStripeAccount($service, $this->socid); // Get Stripe OAuth connect account if it exists (no network access here)
1397
1398 if ($foundalternativestripeaccount) {
1399 if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
1400 $customer = \Stripe\Customer::retrieve(array('id' => "$foundalternativestripeaccount", 'expand[]' => 'sources'));
1401 } else {
1402 $customer = \Stripe\Customer::retrieve(array('id' => "$foundalternativestripeaccount", 'expand[]' => 'sources'), array("stripe_account" => $stripeacc));
1403 }
1404 } else {
1405 $customer = $stripe->customerStripe($thirdparty, $stripeacc, $servicestatus, 0);
1406 if (empty($customer) && ! empty($stripe->error)) {
1407 $this->errors[] = $stripe->error;
1408 }
1409 /*if (!empty($customer) && empty($customer->sources)) {
1410 $customer = null;
1411 $this->errors[] = '\Stripe\Customer::retrieve did not returned the sources';
1412 }*/
1413 }
1414
1415 // $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)
1416 // $nbdaysbeforeendoftries = (empty($conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES) ? 35 : $conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES);
1417 $postactionmessages = [];
1418
1419 if ($resultthirdparty > 0 && !empty($customer)) {
1420 if (!$error) { // Payment was not canceled
1421 $stripecard = null;
1422 if ($companypaymentmode->type == 'ban') {
1423 // Check into societe_rib if a payment mode for Stripe and ban payment exists
1424 // To make a Stripe SEPA payment request, we must have the payment mode source already saved into societe_rib and retrieved with ->sepaStripe
1425 // The payment mode source is created when we create the bank account on Stripe with paymentmodes.php?action=create
1426 $stripecard = $stripe->sepaStripe($customer, $companypaymentmode, $stripeacc, $servicestatus, 0);
1427 } else {
1428 $error++;
1429 $this->error = 'The payment mode type is not "ban"';
1430 }
1431
1432 if ($stripecard) { // Can be src_... (for sepa) or pm_... (new card mode). Note that card_... (old card mode) should not happen here.
1433 $FULLTAG = 'DID='.$did.'-INV=' . $this->id . '-CUS=' . $thirdparty->id;
1434 $description = 'Stripe payment from makeStripeSepaRequest: ' . $FULLTAG . ' did='.$did.' ref=' . $this->ref;
1435
1436 $stripefailurecode = '';
1437 $stripefailuremessage = '';
1438 $stripefailuredeclinecode = '';
1439
1440 // Using new SCA method
1441 dol_syslog("* Create payment on SEPA " . $stripecard->id . ", amounttopay=" . $amounttopay . ", amountstripe=" . $amountstripe . ", FULLTAG=" . $FULLTAG, LOG_DEBUG);
1442
1443 // Create payment intent and charge payment (confirmnow = true)
1444 $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $this, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1, 1, $did);
1445
1446 $charge = new stdClass();
1447
1448 if ($paymentintent->status === 'succeeded' || $paymentintent->status === 'processing') {
1449 $charge->status = 'ok';
1450 $charge->id = $paymentintent->id;
1451 $charge->customer = $customer->id;
1452 } elseif ($paymentintent->status === 'requires_action') {
1453 //paymentintent->status may be => 'requires_action' (no error in such a case)
1454 dol_syslog(var_export($paymentintent, true), LOG_DEBUG);
1455
1456 $charge->status = 'failed';
1457 $charge->customer = $customer->id;
1458 $charge->failure_code = $stripe->code;
1459 $charge->failure_message = $stripe->error;
1460 $charge->failure_declinecode = $stripe->declinecode;
1461 $stripefailurecode = $stripe->code;
1462 $stripefailuremessage = 'Action required. Contact the support at ';// . $conf->global->SELLYOURSAAS_MAIN_EMAIL;
1463 $stripefailuredeclinecode = $stripe->declinecode;
1464 } else {
1465 dol_syslog(var_export($paymentintent, true), LOG_DEBUG);
1466
1467 $charge->status = 'failed';
1468 $charge->customer = $customer->id;
1469 $charge->failure_code = $stripe->code;
1470 $charge->failure_message = $stripe->error;
1471 $charge->failure_declinecode = $stripe->declinecode;
1472 $stripefailurecode = $stripe->code;
1473 $stripefailuremessage = $stripe->error;
1474 $stripefailuredeclinecode = $stripe->declinecode;
1475 }
1476
1477 //var_dump("stripefailurecode=".$stripefailurecode." stripefailuremessage=".$stripefailuremessage." stripefailuredeclinecode=".$stripefailuredeclinecode);
1478 //exit;
1479
1480
1481 // Return $charge = array('id'=>'ch_XXXX', 'status'=>'succeeded|pending|failed', 'failure_code'=>, 'failure_message'=>...)
1482 if (empty($charge) || $charge->status == 'failed') {
1483 dol_syslog('Failed to charge payment mode ' . $stripecard->id . ' stripefailurecode=' . $stripefailurecode . ' stripefailuremessage=' . $stripefailuremessage . ' stripefailuredeclinecode=' . $stripefailuredeclinecode, LOG_WARNING);
1484
1485 // Save a stripe payment was in error
1486 $this->stripechargeerror++;
1487
1488 $error++;
1489 $errorforinvoice++;
1490 $errmsg = $langs->trans("FailedToChargeCard");
1491 if (!empty($charge)) {
1492 if ($stripefailuredeclinecode == 'authentication_required') {
1493 $errauthenticationmessage = $langs->trans("ErrSCAAuthentication");
1494 $errmsg = $errauthenticationmessage;
1495 } elseif (in_array($stripefailuredeclinecode, ['insufficient_funds', 'generic_decline'])) {
1496 $errmsg .= ': ' . $charge->failure_code;
1497 $errmsg .= ($charge->failure_message ? ' - ' : '') . ' ' . $charge->failure_message;
1498 if (empty($stripefailurecode)) {
1499 $stripefailurecode = $charge->failure_code;
1500 }
1501 if (empty($stripefailuremessage)) {
1502 $stripefailuremessage = $charge->failure_message;
1503 }
1504 } else {
1505 $errmsg .= ': failure_code=' . $charge->failure_code;
1506 $errmsg .= ($charge->failure_message ? ' - ' : '') . ' failure_message=' . $charge->failure_message;
1507 if (empty($stripefailurecode)) {
1508 $stripefailurecode = $charge->failure_code;
1509 }
1510 if (empty($stripefailuremessage)) {
1511 $stripefailuremessage = $charge->failure_message;
1512 }
1513 }
1514 } else {
1515 $errmsg .= ': ' . $stripefailurecode . ' - ' . $stripefailuremessage;
1516 $errmsg .= ($stripefailuredeclinecode ? ' - ' . $stripefailuredeclinecode : '');
1517 }
1518
1519 $description = 'Stripe payment ERROR from makeStripeSepaRequest: ' . $FULLTAG;
1520 $postactionmessages[] = $errmsg . ' (' . $stripearrayofkeys['publishable_key'] . ')';
1521 $this->errors[] = $errmsg;
1522 } else {
1523 dol_syslog('Successfuly request '.$type.' '.$stripecard->id);
1524
1525 $postactionmessages[] = 'Success to request '.$type.' (' . $charge->id . ' with ' . $stripearrayofkeys['publishable_key'] . ')';
1526
1527 // Save a stripe payment was done in real life so later we will be able to force a commit on recorded payments
1528 // even if in batch mode (method doTakePaymentStripe), we will always make all action in one transaction with a forced commit.
1529 $this->stripechargedone++;
1530
1531 // Default description used for label of event. Will be overwrite by another value later.
1532 $description = 'Stripe payment request OK (' . $charge->id . ') from makeStripeSepaRequest: ' . $FULLTAG;
1533 }
1534
1535 $object = $this;
1536
1537 // Track an event
1538 if (empty($charge) || $charge->status == 'failed') {
1539 $actioncode = 'PAYMENT_STRIPE_KO';
1540 $extraparams = $stripefailurecode;
1541 $extraparams .= (($extraparams && $stripefailuremessage) ? ' - ' : '') . $stripefailuremessage;
1542 $extraparams .= (($extraparams && $stripefailuredeclinecode) ? ' - ' : '') . $stripefailuredeclinecode;
1543 } else {
1544 $actioncode = 'PAYMENT_STRIPE_OK';
1545 $extraparams = '';
1546 }
1547 } else {
1548 $error++;
1549 $errorforinvoice++;
1550 dol_syslog("No ban payment method found for this stripe customer " . $customer->id, LOG_WARNING);
1551 $this->errors[] = 'Failed to get direct debit payment method for stripe customer = ' . $customer->id;
1552
1553 $description = 'Failed to find or use the payment mode - no ban defined for the thirdparty account';
1554 $stripefailurecode = 'BADPAYMENTMODE';
1555 $stripefailuremessage = 'Failed to find or use the payment mode - no ban defined for the thirdparty account';
1556 $postactionmessages[] = $description . ' (' . $stripearrayofkeys['publishable_key'] . ')';
1557
1558 $object = $this;
1559
1560 $actioncode = 'PAYMENT_STRIPE_KO';
1561 $extraparams = '';
1562 }
1563 } else {
1564 // If error because payment was canceled for a logical reason, we do nothing (no event added)
1565 $description = '';
1566 $stripefailurecode = '';
1567 $stripefailuremessage = '';
1568
1569 $object = $this;
1570
1571 $actioncode = '';
1572 $extraparams = '';
1573 }
1574 } else { // Else of the if ($resultthirdparty > 0 && ! empty($customer)) {
1575 if ($resultthirdparty <= 0) {
1576 dol_syslog('SellYourSaasUtils Failed to load customer for thirdparty_id = ' . $thirdparty->id, LOG_WARNING);
1577 $this->errors[] = 'Failed to load Stripe account for thirdparty_id = ' . $thirdparty->id;
1578 } else { // $customer stripe not found
1579 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);
1580 $this->errors[] = 'Failed to get Stripe account id for thirdparty_id = ' . $thirdparty->id . " in mode " . $servicestatus . " in Stripe env " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key'];
1581 }
1582 $error++;
1583 $errorforinvoice++;
1584
1585 $description = 'Failed to find or use your payment mode (no payment mode for this customer id)';
1586 $stripefailurecode = 'BADPAYMENTMODE';
1587 $stripefailuremessage = 'Failed to find or use your payment mode (no payment mode for this customer id)';
1588 $postactionmessages = [];
1589
1590 $object = $this;
1591
1592 $actioncode = 'PAYMENT_STRIPE_KO';
1593 $extraparams = '';
1594 }
1595
1596 if ($description) {
1597 dol_syslog("* Record event for credit transfer or direct debit request result - " . $description);
1598 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
1599
1600 // Insert record of payment (success or error)
1601 $actioncomm = new ActionComm($this->db);
1602
1603 $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
1604 $actioncomm->code = 'AC_' . $actioncode;
1605 $actioncomm->label = $description;
1606 $actioncomm->note_private = implode(",\n", $postactionmessages);
1607 $actioncomm->fk_project = $this->fk_project;
1608 $actioncomm->datep = $now;
1609 $actioncomm->datef = $now;
1610 $actioncomm->percentage = -1; // Not applicable
1611 $actioncomm->socid = $thirdparty->id;
1612 $actioncomm->contactid = 0;
1613 $actioncomm->authorid = $user->id; // User saving action
1614 $actioncomm->userownerid = $user->id; // Owner of action
1615 // Fields when action is a real email (content is already into note)
1616 /*$actioncomm->email_msgid = $object->email_msgid;
1617 $actioncomm->email_from = $object->email_from;
1618 $actioncomm->email_sender= $object->email_sender;
1619 $actioncomm->email_to = $object->email_to;
1620 $actioncomm->email_tocc = $object->email_tocc;
1621 $actioncomm->email_tobcc = $object->email_tobcc;
1622 $actioncomm->email_subject = $object->email_subject;
1623 $actioncomm->errors_to = $object->errors_to;*/
1624 $actioncomm->fk_element = $this->id;
1625 $actioncomm->elementtype = $this->element;
1626 $actioncomm->extraparams = dol_trunc($extraparams, 250);
1627
1628 $actioncomm->create($user);
1629 }
1630
1631 $this->description = $description;
1632 $this->postactionmessages = $postactionmessages;
1633 } catch (Exception $e) {
1634 $error++;
1635 $errorforinvoice++;
1636 dol_syslog('Error ' . $e->getMessage(), LOG_ERR);
1637 $this->errors[] = 'Error ' . $e->getMessage();
1638 }
1639 } else { // If remain to pay is null
1640 $error++;
1641 $errorforinvoice++;
1642 dol_syslog("Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?", LOG_WARNING);
1643 $this->errors[] = "Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?";
1644 }
1645 }
1646
1647 // Set status of the order to "Transferred" with method 'api'
1648 if (!$error && !$errorforinvoice) {
1649 $result = $bon->set_infotrans($user, $now, 3);
1650 if ($result < 0) {
1651 $error++;
1652 $errorforinvoice++;
1653 dol_syslog("Error on BonPrelevement creation", LOG_ERR);
1654 $this->errors[] = "Error on BonPrelevement creation";
1655 }
1656 }
1657
1658 if (!$error && !$errorforinvoice && $paymentintent !== null) {
1659 // Update the direct debit payment request of the processed invoice to save the id of the prelevement_bon
1660 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
1661 $sql .= " ext_payment_id = '".$this->db->escape($paymentintent->id)."',";
1662 $sql .= " ext_payment_site = '".$this->db->escape($service)."'";
1663 $sql .= " WHERE rowid = ".((int) $did);
1664
1665 dol_syslog(get_class($this)."::makeStripeSepaRequest update to save stripe paymentintent ids", LOG_DEBUG);
1666 $resql = $this->db->query($sql);
1667 if (!$resql) {
1668 $this->error = $this->db->lasterror();
1669 dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur');
1670 $error++;
1671 }
1672 }
1673
1674 if (!$error && !$errorforinvoice) {
1675 $this->db->commit();
1676 } else {
1677 $this->db->rollback();
1678 }
1679 } else {
1680 $this->error = 'WithdrawRequestErrorNilAmount';
1681 dol_syslog(get_class($this).'::makeStripeSepaRequest WithdrawRequestErrorNilAmount');
1682 $error++;
1683 }
1684
1685 /*
1686 if (!$error) {
1687 // Force payment mode of the invoice to withdraw
1688 $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
1689 if ($payment_mode_id > 0) {
1690 $result = $this->setPaymentMethods($payment_mode_id);
1691 }
1692 }*/
1693
1694 if ($error) {
1695 return -1;
1696 }
1697 return 1;
1698 } else {
1699 $this->error = $this->db->error();
1700 dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur -2');
1701 return -2;
1702 }
1703 } else {
1704 $this->error = "Status of invoice does not allow this";
1705 dol_syslog(get_class($this)."::makeStripeSepaRequest ".$this->error." ".$this->status." ,".$this->paye.", ".$this->mode_reglement_id, LOG_WARNING);
1706 return -3;
1707 }
1708 }
1709
1710 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1718 public function demande_prelevement_delete($fuser, $did)
1719 {
1720 // phpcs:enable
1721 $sql = 'DELETE FROM '.$this->db->prefix().'prelevement_demande';
1722 $sql .= ' WHERE rowid = '.((int) $did);
1723 $sql .= ' AND traite = 0';
1724 if ($this->db->query($sql)) {
1725 return 0;
1726 } else {
1727 $this->error = $this->db->lasterror();
1728 dol_syslog(get_class($this).'::demande_prelevement_delete Error '.$this->error);
1729 return -1;
1730 }
1731 }
1732
1738 public function buildEPCQrCodeString()
1739 {
1740 global $mysoc;
1741
1742 // Convert total_ttc to a string with 2 decimal places
1743 $totalTTCString = number_format($this->total_ttc, 2, '.', '');
1744
1745 // Initialize an array to hold the lines of the QR code
1746 $lines = array();
1747
1748 // Add the standard elements to the QR code
1749 $lines = [
1750 'BCD', // Service Tag (optional)
1751 '002', // Version (optional)
1752 '1', // Character set (optional)
1753 'SCT', // Identification (optional)
1754 ];
1755
1756 // Add the bank account information
1757 include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
1758 $bankAccount = new Account($this->db);
1759 if ($this->fk_account > 0) {
1760 $bankAccount->fetch($this->fk_account);
1761 $lines[] = $bankAccount->bic; //BIC (required)
1762 $lines[] = $mysoc->name; //Name (required)
1763 $lines[] = $bankAccount->iban; //IBAN (required)
1764 } else {
1765 $lines[] = ""; //BIC (required)
1766 $lines[] = $mysoc->name; //Name (required)
1767 $lines[] = ""; //IBAN (required)
1768 }
1769
1770 // Add the amount and reference
1771 $lines[] = 'EUR' . $totalTTCString; // Amount (optional)
1772 $lines[] = ''; // Purpose (optional)
1773 $lines[] = ''; // Payment reference (optional)
1774 $lines[] = $this->ref; // Remittance Information (optional)
1775
1776 // Join the lines with newline characters and return the result
1777 return implode("\n", $lines);
1778 }
1784 public function buildZATCAQRString()
1785 {
1786 global $conf, $mysoc;
1787
1788 $tmplang = new Translate('', $conf);
1789 $tmplang->setDefaultLang('en_US');
1790 $tmplang->load("main");
1791
1792 $datestring = dol_print_date($this->date, 'dayhourrfc');
1793 //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
1794 //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
1795 $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
1796 $pricetaxstring = price2num($this->total_tva, 2, 1);
1797
1798 /*
1799 $name = implode(unpack("H*", $this->thirdparty->name));
1800 $vatnumber = implode(unpack("H*", $this->thirdparty->tva_intra));
1801 $date = implode(unpack("H*", $datestring));
1802 $pricewithtax = implode(unpack("H*", price2num($pricewithtaxstring, 2)));
1803 $pricetax = implode(unpack("H*", $pricetaxstring));
1804
1805 //var_dump(strlen($this->thirdparty->name));
1806 //var_dump(str_pad(dechex('9'), 2, '0', STR_PAD_LEFT));
1807 //var_dump($this->thirdparty->name);
1808 //var_dump(implode(unpack("H*", $this->thirdparty->name)));
1809 //var_dump(price($this->total_tva, 0, $tmplang, 0, -1, 2));
1810
1811 $s = '01'.str_pad(dechex(strlen($this->thirdparty->name)), 2, '0', STR_PAD_LEFT).$name;
1812 $s .= '02'.str_pad(dechex(strlen($this->thirdparty->tva_intra)), 2, '0', STR_PAD_LEFT).$vatnumber;
1813 $s .= '03'.str_pad(dechex(strlen($datestring)), 2, '0', STR_PAD_LEFT).$date;
1814 $s .= '04'.str_pad(dechex(strlen($pricewithtaxstring)), 2, '0', STR_PAD_LEFT).$pricewithtax;
1815 $s .= '05'.str_pad(dechex(strlen($pricetaxstring)), 2, '0', STR_PAD_LEFT).$pricetax;
1816 $s .= ''; // Hash of xml invoice
1817 $s .= ''; // ecda signature
1818 $s .= ''; // ecda public key
1819 $s .= ''; // ecda signature of public key stamp
1820 */
1821
1822 // Using TLV format
1823 $s = pack('C1', 1).pack('C1', strlen($mysoc->name)).$mysoc->name;
1824 $s .= pack('C1', 2).pack('C1', strlen($mysoc->tva_intra)).$mysoc->tva_intra;
1825 $s .= pack('C1', 3).pack('C1', strlen($datestring)).$datestring;
1826 $s .= pack('C1', 4).pack('C1', strlen($pricewithtaxstring)).$pricewithtaxstring;
1827 $s .= pack('C1', 5).pack('C1', strlen($pricetaxstring)).$pricetaxstring;
1828 $s .= ''; // Hash of xml invoice
1829 $s .= ''; // ecda signature
1830 $s .= ''; // ecda public key
1831 $s .= ''; // ecda signature of public key stamp
1832
1833 $s = base64_encode($s);
1834
1835 return $s;
1836 }
1837
1838
1845 {
1846 global $conf, $mysoc;
1847
1848 $tmplang = new Translate('', $conf);
1849 $tmplang->setDefaultLang('en_US');
1850 $tmplang->load("main");
1851
1852 $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
1853 $pricetaxstring = price2num($this->total_tva, 2, 1);
1854
1855 $complementaryinfo = '';
1856 /*
1857 Example: //S1/10/10201409/11/190512/20/1400.000-53/30/106017086/31/180508/32/7.7/40/2:10;0:30
1858 /10/ Numéro de facture – 10201409
1859 /11/ Date de facture – 12.05.2019
1860 /20/ Référence client – 1400.000-53
1861 /30/ Numéro IDE pour la TVA – CHE-106.017.086 TVA
1862 /31/ Date de la prestation pour la comptabilisation de la TVA – 08.05.2018
1863 /32/ Taux de TVA sur le montant total de la facture – 7.7%
1864 /40/ Conditions – 2% d’escompte à 10 jours, paiement net à 30 jours
1865 */
1866 $datestring = dol_print_date($this->date, '%y%m%d');
1867 //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
1868 //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
1869 $complementaryinfo = '//S1/10/'.str_replace('/', '', $this->ref).'/11/'.$datestring;
1870 if ($this->ref_client) {
1871 $complementaryinfo .= '/20/'.$this->ref_client;
1872 }
1873 if ($this->thirdparty->tva_intra) {
1874 $complementaryinfo .= '/30/'.$this->thirdparty->tva_intra;
1875 }
1876
1877 include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
1878 $bankaccount = new Account($this->db);
1879
1880 // Header
1881 $s = '';
1882 $s .= "SPC\n";
1883 $s .= "0200\n";
1884 $s .= "1\n";
1885 // Info Seller ("Compte / Payable à")
1886 if ($this->fk_account > 0) {
1887 // Bank BAN if country is LI or CH. TODO Add a test to check than IBAN start with CH or LI
1888 $bankaccount->fetch($this->fk_account);
1889 $s .= $bankaccount->iban."\n";
1890 } else {
1891 $s .= "\n";
1892 }
1893 if ($bankaccount->id > 0 && getDolGlobalString('PDF_SWISS_QRCODE_USE_OWNER_OF_ACCOUNT_AS_CREDITOR')) {
1894 // If a bank account is provided and we ask to use it as creditor, we use the bank address
1895 // 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 ?
1896 $s .= "S\n";
1897 $s .= dol_trunc($bankaccount->owner_name, 70, 'right', 'UTF-8', 1)."\n";
1898 $addresslinearray = explode("\n", $bankaccount->owner_address);
1899 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
1900 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
1901 /*$s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
1902 $s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
1903 $s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";*/
1904 } else {
1905 $s .= "S\n";
1906 $s .= dol_trunc($mysoc->name, 70, 'right', 'UTF-8', 1)."\n";
1907 $addresslinearray = explode("\n", $mysoc->address);
1908 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
1909 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
1910 $s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
1911 $s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
1912 $s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";
1913 }
1914 // Final seller (Ultimate seller) ("Créancier final" = "En faveur de")
1915 $s .= "\n";
1916 $s .= "\n";
1917 $s .= "\n";
1918 $s .= "\n";
1919 $s .= "\n";
1920 $s .= "\n";
1921 $s .= "\n";
1922 // Amount of payment (to do?)
1923 $s .= price($pricewithtaxstring, 0, 'none', 0, 0, 2)."\n";
1924 $s .= ($this->multicurrency_code ? $this->multicurrency_code : $conf->currency)."\n";
1925 // Buyer
1926 $s .= "S\n";
1927 $s .= dol_trunc($this->thirdparty->name, 70, 'right', 'UTF-8', 1)."\n";
1928 $addresslinearray = explode("\n", $this->thirdparty->address);
1929 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
1930 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
1931 $s .= dol_trunc($this->thirdparty->zip, 16, 'right', 'UTF-8', 1)."\n";
1932 $s .= dol_trunc($this->thirdparty->town, 35, 'right', 'UTF-8', 1)."\n";
1933 $s .= dol_trunc($this->thirdparty->country_code, 2, 'right', 'UTF-8', 1)."\n";
1934 // ID of payment
1935 $s .= "NON\n"; // NON or QRR
1936 $s .= "\n"; // QR Code reference if previous field is QRR
1937 // Free text
1938 if ($complementaryinfo) {
1939 $s .= $complementaryinfo."\n";
1940 } else {
1941 $s .= "\n";
1942 }
1943 $s .= "EPD\n";
1944 // More text, complementary info
1945 if ($complementaryinfo) {
1946 $s .= $complementaryinfo."\n";
1947 }
1948 $s .= "\n";
1949 //var_dump($s);exit;
1950 return $s;
1951 }
1952}
1953
1954
1955
1956require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
1957
1962{
1968 public $label;
1969
1975 public $ref; // Product ref (deprecated)
1981 public $libelle; // Product label (deprecated)
1982
1987 public $product_type = 0;
1988
1993 public $product_ref;
1994
1999 public $product_label;
2000
2005 public $product_desc;
2006
2011 public $qty;
2012
2017 public $subprice;
2018
2024 public $price;
2025
2030 public $fk_product;
2031
2036 public $vat_src_code;
2037
2042 public $tva_tx;
2043
2048 public $localtax1_tx;
2049
2054 public $localtax2_tx;
2055
2061 public $localtax1_type;
2062
2068 public $localtax2_type;
2069
2074 public $remise_percent;
2075
2081 public $remise;
2082
2087 public $total_ht;
2088
2093 public $total_tva;
2094
2099 public $total_localtax1;
2100
2105 public $total_localtax2;
2106
2111 public $total_ttc;
2112
2116 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
2120 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
2121
2125 public $buy_price_ht;
2130 public $buyprice;
2135 public $pa_ht;
2136
2140 public $marge_tx;
2144 public $marque_tx;
2145
2152 public $info_bits = 0;
2153
2162 public $special_code = 0;
2163
2168 public $fk_user_author;
2169
2174 public $fk_user_modif;
2175
2179 public $fk_accounting_account;
2180}
if( $user->socid > 0) if(! $user->hasRight('accounting', 'chartofaccount')) $object
Definition card.php:58
$object ref
Definition info.php:79
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,...
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.
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.
LibStatut($paye, $status, $mode=0, $alreadypaid=-1, $type=-1)
Return label of a status.
const STATUS_ABANDONED
Classified abandoned and no payment done.
calculate_date_lim_reglement($cond_reglement=0)
Returns an invoice payment deadline based on the invoice settlement conditions and billing date.
const TYPE_REPLACEMENT
Replacement invoice.
getSumFromThisCreditNotesNotUsed($multicurrency=0)
Return amount (with tax) of all converted amount for this credit note.
getListIdAvoirFromInvoice()
Returns array of credit note ids from the invoice.
const STATUS_DRAFT
Draft status.
getIdReplacingInvoice($option='')
Returns the id of the invoice that replaces it.
getLibType($withbadge=0)
Return label of type of invoice.
is_erasable()
Return if an invoice can be deleted Rule is: If invoice is draft and has a temporary ref -> yes (1) I...
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:162
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...
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_getIdFromCode($db, $key, $tablename, $fieldkey='code', $fieldid='id', $entityfilter=0, $filters='')
Return an id or code from a code or id.
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).
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.
if(preg_match('/crypted:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition repair.php:137