dolibarr 25.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-2026 Frédéric France <frederic.france@free.fr>
7 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
8 * Copyright (C) 2024-2026 Alexandre Spangaro <alexandre@inovea-conseil.com>
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 3 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22 */
23
30require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
31require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
32
36abstract class CommonInvoice extends CommonObject
37{
38 use CommonIncoterm;
39
43 public $title;
44
48 public $type = self::TYPE_STANDARD;
49
53 public $subtype;
54
60 public $fk_soc;
64 public $socid;
65
69 public $paye;
70
76 public $date;
77
81 public $date_lim_reglement;
82
86 public $cond_reglement_id; // Id in llx_c_paiement
90 public $cond_reglement_code; // Code in llx_c_paiement
94 public $cond_reglement_label;
98 public $cond_reglement_doc;
99
103 public $mode_reglement_id;
107 public $mode_reglement_code;
108
112 public $mode_reglement;
113
117 public $total_ht;
121 public $total_tva;
125 public $total_localtax1;
129 public $total_localtax2;
133 public $total_ttc;
137 public $revenuestamp;
138
142 public $totalpaid;
146 public $totaldeposits;
150 public $totalcreditnotes;
151
155 public $remaintopay;
159 public $nbofopendirectdebitorcredittransfer;
160
164 public $creditnote_ids;
165
169 public $stripechargedone;
170
174 public $stripechargeerror;
175
179 public $payment_reference;
180
184 public $dispute_status = 0;
185
186
191 public $description;
192
198 public $ref_client;
199
203 public $situation_cycle_ref;
204
208 public $retained_warranty_fk_cond_reglement;
209
215 public $close_code;
216
221 public $close_note;
222
223
228 public $postactionmessages;
229
230
234 const TYPE_STANDARD = 0;
235
240
245
249 const TYPE_DEPOSIT = 3;
250
255 const TYPE_PROFORMA = 4;
256
260 const TYPE_SITUATION = 5;
261
265 const STATUS_DRAFT = 0;
266
271
279 const STATUS_CLOSED = 2;
280
289
290
291 const CLOSECODE_DISCOUNTVAT = 'discount_vat'; // Abandoned (the remain to pay) - escompte
292 const CLOSECODE_BADDEBT = 'badcustomer'; // Abandoned (the remain to pay) - bad customer
293 const CLOSECODE_BANKCHARGE = 'bankcharge'; // Abandoned (the remain to pay) - bank charge
294 const CLOSECODE_WITHHOLDINGTAX = 'withholdingtax'; // Abandoned (the remain to pay) - source tax
295 const CLOSECODE_OTHER = 'other'; // Abandoned (the remain to pay) - other
296 const CLOSECODE_ABANDONED = 'abandon'; // Abandoned (no payment at all) - other
297 const CLOSECODE_REPLACED = 'replaced'; // Abandoned (no payment at all) - replacedby another invoice (feature disabled by default)
298
299 const ARRAY_OF_DISPUTE_STATUS = array(
300 0 => array('label' => "None"),
301 1 => array('label' => "DisputeOpen"),
302 8 => array('label' => "DisputeLost"),
303 9 => array('label' => "DisputeWon")
304 );
305
306
314 public function getRemainToPay($multicurrency = 0)
315 {
316 $alreadypaid = 0.0;
317 $alreadypaid += $this->getSommePaiement($multicurrency);
318 $alreadypaid += $this->getSumDepositsUsed($multicurrency);
319 $alreadypaid += $this->getSumCreditNotesUsed($multicurrency);
320
321 if ((int) $multicurrency > 0) {
322 $totalamount = $this->multicurrency_total_ttc;
323 } else {
324 $totalamount = $this->total_ttc;
325 }
326 $remaintopay = price2num($totalamount - $alreadypaid, 'MT');
327 if ($this->status == self::STATUS_CLOSED && $this->close_code == 'discount_vat') { // If invoice closed with discount for anticipated payment
328 $remaintopay = 0.0;
329 }
330 return $remaintopay;
331 }
332
342 public function getSommePaiement($multicurrency = 0)
343 {
344 $table = 'paiement_facture';
345 $field = 'fk_facture';
346 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
347 $table = 'paiementfourn_facturefourn';
348 $field = 'fk_facturefourn';
349 }
350
351 $sql = "SELECT sum(amount) as amount, sum(multicurrency_amount) as multicurrency_amount";
352 $sql .= " FROM ".$this->db->prefix().$table;
353 $sql .= " WHERE ".$this->db->sanitize($field)." = ".((int) $this->id);
354
355 dol_syslog(get_class($this)."::getSommePaiement", LOG_DEBUG);
356
357 $resql = $this->db->query($sql);
358 if ($resql) {
359 $obj = $this->db->fetch_object($resql);
360
361 $this->db->free($resql);
362
363 if ($obj) {
364 if ($multicurrency < 0) {
365 $this->totalpaid = $obj->amount;
366 $this->totalpaid_multicurrency = $obj->multicurrency_amount;
367 return array('alreadypaid' => (float) $obj->amount, 'alreadypaid_multicurrency' => (float) $obj->multicurrency_amount);
368 } elseif ($multicurrency) {
369 $this->totalpaid_multicurrency = $obj->multicurrency_amount;
370 return (float) $obj->multicurrency_amount;
371 } else {
372 $this->totalpaid = $obj->amount;
373 return (float) $obj->amount;
374 }
375 } else {
376 return 0;
377 }
378 } else {
379 $this->error = $this->db->lasterror();
380 return -1;
381 }
382 }
383
393 public function getSumDepositsUsed($multicurrency = 0)
394 {
395 /*if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
396 // 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.
397 return 0.0;
398 }*/
399
400 require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
401
402 $discountstatic = new DiscountAbsolute($this->db);
403 $result = $discountstatic->getSumDepositsUsed($this, $multicurrency);
404
405 if ($result >= 0) {
406 if ($multicurrency) {
407 $this->totaldeposits_multicurrency = $result;
408 } else {
409 $this->totaldeposits = $result;
410 }
411
412 return $result;
413 } else {
414 $this->error = $discountstatic->error;
415 return -1;
416 }
417 }
418
426 public function getSumCreditNotesUsed($multicurrency = 0)
427 {
428 require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
429
430 $discountstatic = new DiscountAbsolute($this->db);
431 $result = $discountstatic->getSumCreditNotesUsed($this, $multicurrency);
432 if (is_numeric($result)) {
433 if ($multicurrency) {
434 $this->totalcreditnotes_multicurrency = $result;
435 } else {
436 $this->totalcreditnotes = $result;
437 }
438
439 return $result;
440 } else {
441 $this->error = $discountstatic->error;
442 return $result;
443 }
444 }
445
452 public function getSumFromThisCreditNotesNotUsed($multicurrency = 0)
453 {
454 require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
455
456 $discountstatic = new DiscountAbsolute($this->db);
457 $result = $discountstatic->getSumFromThisCreditNotesNotUsed($this, $multicurrency);
458 if ($result >= 0) {
459 return $result;
460 } else {
461 $this->error = $discountstatic->error;
462 return -1;
463 }
464 }
465
472 {
473 $idarray = array();
474
475 $sql = "SELECT rowid";
476 $sql .= " FROM ".$this->db->prefix().$this->table_element;
477 $sql .= " WHERE fk_facture_source = ".((int) $this->id);
478 $sql .= " AND type = ".self::TYPE_CREDIT_NOTE;
479
480 $resql = $this->db->query($sql);
481 if ($resql) {
482 $num = $this->db->num_rows($resql);
483 $i = 0;
484 while ($i < $num) {
485 $row = $this->db->fetch_row($resql);
486 $idarray[] = $row[0];
487 $i++;
488 }
489 } else {
490 dol_print_error($this->db);
491 }
492
493 $this->creditnote_ids = $idarray;
494
495 return $idarray;
496 }
497
504 public function getIdReplacingInvoice($option = '')
505 {
506 $sql = "SELECT rowid";
507 $sql .= " FROM ".$this->db->prefix().$this->table_element;
508 $sql .= " WHERE fk_facture_source = ".((int) $this->id);
509 $sql .= " AND type < 2";
510 if ($option == 'validated') {
511 $sql .= ' AND fk_statut = 1';
512 }
513 // PROTECTION BAD DATA
514 // In case the database is corrupted and there is a valid replectement invoice
515 // and another no, priority is given to the valid one.
516 // Should not happen (unless concurrent access and 2 people have created a
517 // replacement invoice for the same invoice at the same time)
518 $sql .= " ORDER BY fk_statut DESC";
519
520 $resql = $this->db->query($sql);
521 if ($resql) {
522 $obj = $this->db->fetch_object($resql);
523 if ($obj) {
524 // If there is any
525 return $obj->rowid;
526 } else {
527 // If no invoice replaces it
528 return 0;
529 }
530 } else {
531 return -1;
532 }
533 }
534
542 {
543 $listofopendirectdebitorcredittransfer = array();
544
545 // TODO Add a cache to store array of open requests for each invoice ID
546
547 $sql = "SELECT pfd.rowid, pfd.traite, pfd.date_demande as date_demande, pfd.date_traite as date_traite, pfd.amount";
548 $sql .= " FROM ".MAIN_DB_PREFIX."prelevement_demande as pfd";
549 if ($type == 'bank-transfer') {
550 $sql .= " WHERE fk_facture_fourn = ".((int) $this->id);
551 } else {
552 $sql .= " WHERE fk_facture = ".((int) $this->id);
553 }
554 $sql .= " AND pfd.traite = 0";
555 $sql .= " AND pfd.type = 'ban'";
556 $sql .= " ORDER BY pfd.date_demande DESC";
557
558 $resql = $this->db->query($sql);
559 $num = 0;
560 if ($resql) {
561 $num = $this->db->num_rows($resql);
562 $i = 0;
563 while ($i < $num) {
564 $obj = $this->db->fetch_object($resql);
565 if ($obj) {
566 $listofopendirectdebitorcredittransfer[] = array(
567 'id' => (int) $obj->rowid,
568 'invoiceid' => (int) $this->id,
569 'date' => $this->db->jdate($obj->date_demande),
570 'amount' => (float) $obj->amount
571 );
572 }
573
574 $i++;
575 }
576 } else {
577 $this->error = $this->db->lasterror();
578 }
579
580 $this->nbofopendirectdebitorcredittransfer = $num;
581
582 return $listofopendirectdebitorcredittransfer;
583 }
584
592 {
593 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms('.$id.')');
594 if ($this->status >= 0 || $this->element == 'societe') {
595 $fieldname = 'retained_warranty_fk_cond_reglement';
596
597 $sql = 'UPDATE '.$this->db->prefix().$this->table_element;
598 $sql .= " SET ".$this->db->sanitize($fieldname)." = ".((int) $id);
599 $sql .= ' WHERE rowid='.((int) $this->id);
600
601 if ($this->db->query($sql)) {
602 $this->retained_warranty_fk_cond_reglement = $id;
603 return 1;
604 } else {
605 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms Error '.$sql.' - '.$this->db->error());
606 $this->error = $this->db->error();
607 return -1;
608 }
609 } else {
610 dol_syslog(get_class($this).'::setRetainedWarrantyPaymentTerms, status of the object is incompatible');
611 $this->error = 'Status of the object is incompatible '.$this->status;
612 return -2;
613 }
614 }
615
626 public function getListOfPayments($filtertype = '', $multicurrency = 0, $mode = 0)
627 {
628 $retarray = array();
629 $this->error = ''; // By default no error, list can be empty.
630
631 $table = 'paiement_facture';
632 $table2 = 'paiement';
633
634 $field = 'fk_facture';
635 $field2 = 'fk_paiement';
636 $field3 = 'p.ref_ext';
637 $field4 = 'p.fk_bank'; // Bank line id
638
639 $sharedentity = 'facture';
640
641 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
642 $table = 'paiementfourn_facturefourn';
643 $table2 = 'paiementfourn';
644
645 $field = 'fk_facturefourn';
646 $field2 = 'fk_paiementfourn';
647 $field3 = '';
648
649 $sharedentity = 'facture_fourn';
650 }
651
652 // List of payments
653 if (empty($mode) || $mode == 1) {
654 $sql = "SELECT p.ref, pf.amount, pf.multicurrency_amount, p.fk_paiement, p.datep, p.num_paiement as num, t.code".($field3 ? ", ".$this->db->sanitize($field3) : "") . (", ".$this->db->sanitize($field4));
655 $sql .= " FROM ".$this->db->prefix().$table." as pf, ".$this->db->prefix().$table2." as p, ".$this->db->prefix()."c_paiement as t";
656 $sql .= " WHERE pf.".$this->db->sanitize($field)." = ".((int) $this->id);
657 $sql .= " AND pf.".$this->db->sanitize($field2)." = p.rowid";
658 $sql .= ' AND p.fk_paiement = t.id';
659 $sql .= ' AND p.entity IN ('.getEntity($sharedentity).')';
660 if ($filtertype) {
661 $sql .= " AND t.code='PRE'";
662 }
663
664 dol_syslog(get_class($this)."::getListOfPayments filtertype=".$filtertype." multicurrency=".$multicurrency." mode=".$mode, LOG_DEBUG);
665
666 $resql = $this->db->query($sql);
667 if ($resql) {
668 $num = $this->db->num_rows($resql);
669 $i = 0;
670 while ($i < $num) {
671 $obj = $this->db->fetch_object($resql);
672 if ($multicurrency) {
673 $tmp = array('amount' => $obj->multicurrency_amount, 'type' => $obj->code, 'typeline' => 'payment', 'date' => $obj->datep, 'num' => $obj->num, 'ref' => $obj->ref);
674 } else {
675 $tmp = array('amount' => $obj->amount, 'type' => $obj->code, 'typeline' => 'payment', 'date' => $obj->datep, 'num' => $obj->num, 'ref' => $obj->ref);
676 }
677 if (!empty($field3)) {
678 $tmp['ref_ext'] = $obj->ref_ext;
679 }
680 if (!empty($field4)) {
681 $tmp['fk_bank_line'] = $obj->fk_bank;
682 }
683 $retarray[] = $tmp;
684 $i++;
685 }
686 $this->db->free($resql);
687 } else {
688 $this->error = $this->db->lasterror();
689 dol_print_error($this->db);
690 return array();
691 }
692 }
693
694 // Look for credit notes and discounts and deposits
695 if (empty($mode) || $mode == 2) {
696 $sql = '';
697 if ($this->element == 'facture' || $this->element == 'invoice') {
698 $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";
699 $sql .= ' FROM '.$this->db->prefix().'societe_remise_except as rc, '.$this->db->prefix().'facture as f';
700 $sql .= ' WHERE rc.fk_facture_source=f.rowid AND rc.fk_facture = '.((int) $this->id);
701 $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)
702 } elseif ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
703 $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";
704 $sql .= ' FROM '.$this->db->prefix().'societe_remise_except as rc, '.$this->db->prefix().'facture_fourn as f';
705 $sql .= ' WHERE rc.fk_invoice_supplier_source=f.rowid AND rc.fk_invoice_supplier = '.((int) $this->id);
706 $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)
707 }
708
709 if ($sql) {
710 $resql = $this->db->query($sql);
711 if ($resql) {
712 $num = $this->db->num_rows($resql);
713 $i = 0;
714 while ($i < $num) {
715 $obj = $this->db->fetch_object($resql);
716 if ($multicurrency) {
717 $retarray[] = array('amount' => $obj->multicurrency_amount, 'type' => $obj->type, 'typeline' => 'discount', 'date' => $obj->date, 'num' => '0', 'ref' => $obj->ref);
718 } else {
719 $retarray[] = array('amount' => $obj->amount, 'type' => $obj->type, 'typeline' => 'discount', 'date' => $obj->date, 'num' => '', 'ref' => $obj->ref);
720 }
721 $i++;
722 }
723 } else {
724 $this->error = $this->db->lasterror();
725 dol_print_error($this->db);
726 return array();
727 }
728 $this->db->free($resql);
729 } else {
730 $this->error = $this->db->lasterror();
731 dol_print_error($this->db);
732 return array();
733 }
734 }
735
736 return $retarray;
737 }
738
753 public function isEditable()
754 {
755 // phpcs:enable
756 global $hookmanager, $action;
757
758 // We check if invoice is a temporary number (PROVxxxx)
759 $tmppart = substr($this->ref, 1, 4);
760
761 if ($this->status == self::STATUS_DRAFT && $tmppart === 'PROV') { // If draft invoice and ref not yet defined
762 return 1;
763 }
764
765 /*
766 if (getDolGlobalInt('INVOICE_CAN_NEVER_BE_REMOVED')) {
767 return 0;
768 }
769 */
770
771 // If not a draft invoice and not temporary invoice
772 if ($tmppart !== 'PROV') {
773 if ($this instanceof Facture) {
774 /* @var Facture $this */
775 // If sent by email, we refuse
776 if ((int) $this->email_sent_counter > 0) {
777 return -5;
778 }
779
780 // If printed, we refuse
781 if ((int) $this->pos_print_counter > 0) {
782 return -6;
783 }
784
785 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
786 if (isALNERunningVersion() && !empty($this->module_source)) {
787 $this->error = 'Action to modify an invoice from an external module like the Point Of Sale is not allowed';
788 return -7;
789 // Note, edit status to draft is also blocked by the trigger of blockedlog module for action BILL_UNVALIDATE that do a test on isEditable().
790 }
791 }
792
793 // If in accountancy, we refuse
794 $ventilExportCompta = $this->getVentilExportCompta();
795 if ($ventilExportCompta != 0) {
796 return -1;
797 }
798
799 // Get last number of validated invoice
800 if ($this->element != 'invoice_supplier') {
801 /*
802 if (empty($this->thirdparty)) {
803 $this->fetch_thirdparty(); // We need to have this->thirdparty defined, in case of the numbering rule uses tags that depend on thirdparty (like {t} tag).
804 }
805 $maxref = $this->getNextNumRef($this->thirdparty, 'last');
806 // $maxref can be '' (means not found) if there is no invoice yet, but also if there is no invoice for the new period when there is a reset at each period
807
808 // If invoice to delete is not the last one, we refuse
809 if ($maxref != '' && $maxref != $this->ref) {
810 return -2;
811 }
812 */
813
814 // TODO If there is payment in bookkeeping, check the payment is not dispatched in accounting and return -2.
815 // ...
816
817 // If invoice is situation type, we refuse if it is not the last in situation cycle
818 if (!getDolGlobalString('INVOICE_SITUATION_CAN_BE_REMOVED_EVEN_IF_NOT_LAST') && $this->situation_cycle_ref && method_exists($this, 'is_last_in_cycle')) {
819 $last = $this->is_last_in_cycle();
820 if (!$last) {
821 return -3;
822 }
823 }
824 }
825
826 // Test if there is at least one payment. If yes, we refuse.
827 if ($this->getSommePaiement() > 0) {
828 return -4;
829 }
830
831 $parameters = array();
832 $reshook = $hookmanager->executeHooks('isEditable', $parameters, $this, $action);
833 if (!empty($hookmanager->resArray['result'])) {
834 $this->error = $hookmanager->resArray['error'];
835 if (!empty($hookmanager->resArray['errors'])) {
836 $this->errors = array_merge($this->errors, $hookmanager->resArray['errors']);
837 }
838 if (!in_array($this->error, $this->errors)) {
839 $this->errors[] = $this->error;
840 }
841 return $hookmanager->resArray['result'];
842 }
843 }
844
845 return 2;
846 }
847
853 public function isReplacable()
854 {
855 global $hookmanager, $action;
856
857 // We check if invoice is a temporary number (PROVxxxx)
858 $tmppart = substr($this->ref, 1, 4);
859
860 if ($this->status == self::STATUS_DRAFT) { // If draft invoice, no
861 return -1;
862 }
863
864 // If not a draft invoice and not temporary invoice
865 if ($tmppart !== 'PROV') {
866 // If in accountancy, we refuse
867 $ventilExportCompta = $this->getVentilExportCompta();
868 if ($ventilExportCompta != 0) {
869 return -1;
870 }
871
872 // If invoice is situation type, we refuse if it is not the last in situation cycle
873 if (!getDolGlobalString('INVOICE_SITUATION_CAN_BE_REMOVED_EVEN_IF_NOT_LAST') && $this->situation_cycle_ref && method_exists($this, 'is_last_in_cycle')) {
874 $last = $this->is_last_in_cycle();
875 if (!$last) {
876 return -3;
877 }
878 }
879
880 // Test if there is at least one payment. If yes, we refuse to delete.
881 if ($this->getSommePaiement() > 0) {
882 return -4;
883 }
884
885 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
886 if (isALNERunningVersion()) {
887 $this->error = 'Action not allowed on a certified version (or candidate for certification)';
888 return -7;
889 }
890
891 $parameters = array();
892 $reshook = $hookmanager->executeHooks('isReplacable', $parameters, $this, $action);
893 if (!empty($hookmanager->resArray['result'])) {
894 $this->error = $hookmanager->resArray['error'];
895 if (!empty($hookmanager->resArray['errors'])) {
896 $this->errors = array_merge($this->errors, $hookmanager->resArray['errors']);
897 }
898 if (!in_array($this->error, $this->errors)) {
899 $this->errors[] = $this->error;
900 }
901 return $hookmanager->resArray['result'];
902 }
903
904 return 1;
905 }
906
907 return 0;
908 }
909
910 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
928 public function is_erasable()
929 {
930 // phpcs:enable
931 global $hookmanager, $action;
932
933 // We check if invoice is a temporary number (PROVxxxx)
934 $tmppart = substr($this->ref, 1, 4);
935
936 if ($this->status == self::STATUS_DRAFT && $tmppart === 'PROV') { // If draft invoice and ref not yet defined
937 return 1;
938 }
939
940 if (getDolGlobalInt('INVOICE_CAN_NEVER_BE_REMOVED')) {
941 return 0;
942 }
943
944 // If not a draft invoice and not temporary invoice
945 if ($tmppart !== 'PROV') {
946 if ($this instanceof Facture) {
947 /* @var Facture $this */
948 // If sent by email, we refuse
949 if ((int) $this->email_sent_counter > 0) {
950 return -5;
951 }
952
953 // If printed, we refuse
954 if ((int) $this->pos_print_counter > 0) {
955 return -6;
956 }
957
958 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
959 if (isALNERunningVersion()) {
960 $this->error = 'Action not allowed on the certified version (or candidate for certification)';
961 return -7;
962 }
963 }
964
965 // If in accountancy, we refuse
966 $ventilExportCompta = $this->getVentilExportCompta();
967 if ($ventilExportCompta != 0) {
968 return -1;
969 }
970
971 // Get last number of validated invoice
972 if ($this->element != 'invoice_supplier') {
973 if (empty($this->thirdparty)) {
974 $this->fetch_thirdparty(); // We need to have this->thirdparty defined, in case of the numbering rule uses tags that depend on thirdparty (like {t} tag).
975 }
976 $maxref = $this->getNextNumRef($this->thirdparty, 'last');
977 // $maxref can be '' (means not found) if there is no invoice yet, but also if there is no invoice for the new period when there is a reset at each period
978
979 // If invoice to delete is not the last one, we refuse
980 if ($maxref != '' && $maxref != $this->ref) {
981 return -2;
982 }
983
984 // TODO If there is payment in bookkeeping, check the payment is not dispatched in accounting and return -2.
985 // ...
986
987 // If invoice is situation type, we refuse if it is not the last in situation cycle
988 if (!getDolGlobalString('INVOICE_SITUATION_CAN_BE_REMOVED_EVEN_IF_NOT_LAST') && $this->situation_cycle_ref && method_exists($this, 'is_last_in_cycle')) {
989 $last = $this->is_last_in_cycle();
990 if (!$last) {
991 return -3;
992 }
993 }
994 }
995
996 // Test if there is at least one payment. If yes, we refuse.
997 if ($this->getSommePaiement() > 0) {
998 return -4;
999 }
1000
1001 $parameters = array();
1002 $reshook = $hookmanager->executeHooks('isErasable', $parameters, $this, $action);
1003 if (!empty($hookmanager->resArray['result'])) {
1004 $this->error = $hookmanager->resArray['error'];
1005 if (!empty($hookmanager->resArray['errors'])) {
1006 $this->errors = array_merge($this->errors, $hookmanager->resArray['errors']);
1007 }
1008 if (!in_array($this->error, $this->errors)) {
1009 $this->errors[] = $this->error;
1010 }
1011 return $hookmanager->resArray['result'];
1012 }
1013 }
1014
1015 return 2;
1016 }
1017
1025 public function getVentilExportCompta($mode = 0)
1026 {
1027 if (!isModEnabled('accounting')) {
1028 return 0;
1029 }
1030
1031 $alreadydispatched = 0;
1032
1033 $type = 'customer_invoice';
1034 if ($this->element == 'invoice_supplier') {
1035 $type = 'supplier_invoice';
1036 }
1037
1038 $sql = " SELECT ".($mode ? 'DISTINCT piece_num' : 'COUNT(ab.rowid)')." as nb";
1039 $sql .= " FROM ".$this->db->prefix()."accounting_bookkeeping as ab";
1040 $sql .= " WHERE ab.doc_type = '".$this->db->escape($type)."' AND ab.fk_doc = ".((int) $this->id);
1041
1042 $resql = $this->db->query($sql);
1043 if ($resql) {
1044 $obj = $this->db->fetch_object($resql);
1045 if ($obj) {
1046 $alreadydispatched = $obj->nb;
1047 }
1048 } else {
1049 $this->error = $this->db->lasterror();
1050 return -1;
1051 }
1052
1053 if ($alreadydispatched) {
1054 return $alreadydispatched;
1055 }
1056 return 0;
1057 }
1058
1066 public function getNextNumRef($soc, $mode = 'next')
1067 {
1068 // TODO Must be implemented into main class
1069 return '';
1070 }
1071
1078 public function getLibType($withbadge = 0)
1079 {
1080 global $langs;
1081
1082 $labellong = "Unknown";
1083 $labelshort = "Unknown";
1084 if ($this->type == CommonInvoice::TYPE_STANDARD) {
1085 $labellong = "InvoiceStandard";
1086 $labelshort = "InvoiceStandardShort";
1087 } elseif ($this->type == CommonInvoice::TYPE_REPLACEMENT) {
1088 $labellong = "InvoiceReplacement";
1089 $labelshort = "InvoiceReplacementShort";
1090 } elseif ($this->type == CommonInvoice::TYPE_CREDIT_NOTE) {
1091 $labellong = "InvoiceAvoir";
1092 $labelshort = "CreditNote";
1093 } elseif ($this->type == CommonInvoice::TYPE_DEPOSIT) {
1094 $labellong = "InvoiceDeposit";
1095 $labelshort = "Deposit";
1096 } elseif ($this->type == CommonInvoice::TYPE_PROFORMA) { // @phan-suppress-current-line PhanDeprecatedClassConstant
1097 $labellong = "InvoiceProForma"; // Not used.
1098 $labelshort = "ProForma";
1099 } elseif ($this->type == CommonInvoice::TYPE_SITUATION) {
1100 $labellong = "InvoiceSituation";
1101 $labelshort = "Situation";
1102 }
1103
1104 $out = '';
1105 if ($withbadge) {
1106 $out .= '<span class="badgeneutral" title="'.dol_escape_htmltag($langs->trans($labellong)).'">';
1107 }
1108 $out .= $langs->trans($withbadge == 2 ? $labelshort : $labellong);
1109 if ($withbadge) {
1110 $out .= '</span>';
1111 }
1112 return $out;
1113 }
1114
1121 public function getSubtypeLabel($table = '')
1122 {
1123 $subtypeLabel = '';
1124 if ($table === 'facture' || $table === 'facture_fourn') {
1125 $sql = "SELECT s.label FROM " . $this->db->prefix() . $table . " AS f";
1126 $sql .= " INNER JOIN " . $this->db->prefix() . "c_invoice_subtype AS s ON f.subtype = s.rowid";
1127 $sql .= " WHERE f.ref = '".$this->db->escape($this->ref)."'";
1128 } elseif ($table === 'facture_rec' || $table === 'facture_fourn_rec') {
1129 $sql = "SELECT s.label FROM " . $this->db->prefix() . $table . " AS f";
1130 $sql .= " INNER JOIN " . $this->db->prefix() . "c_invoice_subtype AS s ON f.subtype = s.rowid";
1131 $sql .= " WHERE f.titre = '".$this->db->escape($this->title)."'";
1132 } else {
1133 return -1;
1134 }
1135
1136 $resql = $this->db->query($sql);
1137 if ($resql) {
1138 while ($obj = $this->db->fetch_object($resql)) {
1139 $subtypeLabel = $obj->label;
1140 }
1141 } else {
1142 dol_print_error($this->db);
1143 return -1;
1144 }
1145
1146 return $subtypeLabel;
1147 }
1148
1155 public function getArrayOfInvoiceSubtypes($mode = 0)
1156 {
1157 global $mysoc;
1158
1159 $effs = array();
1160
1161 $sql = "SELECT rowid, code, label as label";
1162 $sql .= " FROM " . MAIN_DB_PREFIX . 'c_invoice_subtype';
1163 $sql .= " WHERE active = 1 AND fk_country = ".((int) $mysoc->country_id)." AND entity IN(".getEntity('c_invoice_subtype').")";
1164 $sql .= " ORDER by rowid, code";
1165
1166 dol_syslog(get_class($this) . '::getArrayOfInvoiceSubtypes', LOG_DEBUG);
1167 $resql = $this->db->query($sql);
1168 if ($resql) {
1169 $num = $this->db->num_rows($resql);
1170 $i = 0;
1171
1172 while ($i < $num) {
1173 $objp = $this->db->fetch_object($resql);
1174 if (!$mode) {
1175 $key = $objp->rowid;
1176 $effs[$key] = $objp->label;
1177 } else {
1178 $key = $objp->code;
1179 $effs[$key] = $objp->rowid;
1180 }
1181
1182 $i++;
1183 }
1184 $this->db->free($resql);
1185 }
1186
1187 return $effs;
1188 }
1189
1197 public function getLibStatut($mode = 0, $alreadypaid = -1)
1198 {
1199 $moreparams = array(
1200 'nbofopendirectdebitorcredittransfer' => $this->nbofopendirectdebitorcredittransfer,
1201 'close_code' => $this->close_code,
1202 'close_note' => $this->close_note,
1203 'dispute_status' => $this->dispute_status
1204 );
1205
1206 return $this->LibStatut($this->paye, $this->status, $mode, $alreadypaid, $this->type, $moreparams);
1207 }
1208
1209 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1221 public function LibStatut($paye, $status, $mode = 0, $alreadypaid = -1, $type = -1, $moreparams = array())
1222 {
1223 // phpcs:enable
1224 global $langs, $hookmanager;
1225 $langs->load('bills');
1226
1227 if ($type == -1) {
1228 $type = $this->type;
1229 }
1230
1231 // For backward compatibility
1232 if (is_int($moreparams)) {
1233 $moreparams = array('nbofopendirectdebitorcredittransfer' => (int) $moreparams);
1234 }
1235
1236 $nbofopendirectdebitorcredittransfer = 0;
1237 foreach ($moreparams as $moreparamkey => $moreparamvalue) {
1238 if ($moreparamkey == 'nbofopendirectdebitorcredittransfer') {
1239 $nbofopendirectdebitorcredittransfer = $moreparamvalue;
1240 }
1241 }
1242
1243 $statusType = 'status0';
1244 $prefix = 'Short';
1245 if (!$paye) {
1246 if ($status == 0) {
1247 $labelStatus = $langs->transnoentitiesnoconv('BillStatusDraft');
1248 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusDraft');
1249 } elseif (($status == 3 || $status == 2) && $alreadypaid <= 0) {
1250 if ($status == 3) {
1251 $labelStatus = $langs->transnoentitiesnoconv('BillStatusCanceled');
1252 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusCanceled');
1253 } else {
1254 $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedUnpaid');
1255 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedUnpaid');
1256 }
1257 $statusType = 'status5';
1258 } elseif (($status == 3 || $status == 2) && $alreadypaid > 0) {
1259 $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedPaidPartially');
1260 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedPaidPartially');
1261 $statusType = 'status9';
1262 } elseif ($alreadypaid == 0 && $nbofopendirectdebitorcredittransfer == 0) {
1263 $labelStatus = $langs->transnoentitiesnoconv('BillStatusNotPaid');
1264 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusNotPaid');
1265 $statusType = 'status1';
1266 } else {
1267 $labelStatus = $langs->transnoentitiesnoconv('BillStatusStarted');
1268 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusStarted');
1269 $statusType = 'status3';
1270 }
1271 } else {
1272 $statusType = 'status6';
1273 if ($type == self::TYPE_CREDIT_NOTE) {
1274 $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaidBackOrConverted'); // credit note
1275 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaidBackOrConverted'); // credit note
1276 } elseif ($type == self::TYPE_DEPOSIT) {
1277 $labelStatus = $langs->transnoentitiesnoconv('BillStatusConverted'); // deposit invoice
1278 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusConverted'); // deposit invoice
1279 } else {
1280 $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaid');
1281 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaid');
1282 }
1283 }
1284
1285 $paramsBadge = array('badgeParams' => array('attr' => array(
1286 'data-status-element' => $this->element,
1287 'data-already-paid' => $alreadypaid > 0 ? 1 : 0,
1288 'data-status' => (int) $status
1289 )));
1290
1291 $parameters = array(
1292 'status' => $status,
1293 'mode' => $mode,
1294 'paye' => $paye,
1295 'alreadypaid' => $alreadypaid,
1296 'type' => $type,
1297 'paramsBadge' => & $paramsBadge
1298 );
1299
1300 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
1301
1302 if ($reshook > 0) {
1303 return $hookmanager->resPrint;
1304 }
1305
1306 if (!empty($moreparams['close_code'])) {
1307 $titlestringtoshow = '';
1308
1309 if ($moreparams['close_code'] == self::CLOSECODE_DISCOUNTVAT) {
1310 $titlestringtoshow = $langs->trans("HelpEscompte");
1311 } elseif ($moreparams['close_code'] == self::CLOSECODE_BADDEBT) {
1312 $titlestringtoshow = $langs->trans("ConfirmClassifyPaidPartiallyReasonBadCustomer");
1313 } elseif ($moreparams['close_code'] == self::CLOSECODE_BANKCHARGE) {
1314 $titlestringtoshow = $langs->trans("ConfirmClassifyPaidPartiallyReasonBankCharge");
1315 } elseif ($moreparams['close_code'] == self::CLOSECODE_WITHHOLDINGTAX) {
1316 $titlestringtoshow = $langs->trans("ConfirmClassifyPaidPartiallyReasonWithholdingTax");
1317 } elseif ($moreparams['close_code'] == self::CLOSECODE_OTHER) {
1318 $titlestringtoshow = $langs->trans("Other");
1319 }
1320
1321 //$paramsbutton = array('badgeParams' => array('attr' => array('title' => 'rrrr')));
1322 $paramsBadge['badgeParams' ]['attr']['title'] = $titlestringtoshow;
1323 }
1324
1325 if (isset($moreparams['dispute_status']) && $moreparams['dispute_status']) {
1326 $labelStatus .= ' - ';
1327 if ($moreparams['dispute_status'] == 8) {
1328 $labelStatus .= $langs->trans("DisputeLost");
1329 } elseif ($moreparams['dispute_status'] == 9) {
1330 $labelStatus .= $langs->trans("DisputeWon");
1331 } else {
1332 $labelStatus .= $langs->trans("DisputeOpen");
1333 $statusType = 'status8';
1334 }
1335 }
1336
1337 $statusbadge = dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode, '', $paramsBadge);
1338
1339 return $statusbadge;
1340 }
1341
1342 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1350 public function calculate_date_lim_reglement($cond_reglement = 0)
1351 {
1352 // phpcs:enable
1353 if (!$cond_reglement) {
1354 $cond_reglement = $this->cond_reglement_code;
1355 }
1356 if (!$cond_reglement) {
1357 $cond_reglement = $this->cond_reglement_id;
1358 }
1359 if (!$cond_reglement) {
1360 return $this->date;
1361 }
1362
1363 $cdr_nbjour = 0;
1364 $cdr_type = 0;
1365 $cdr_decalage = 0;
1366
1367 $sqltemp = "SELECT c.type_cdr, c.nbjour, c.decalage";
1368 $sqltemp .= " FROM ".$this->db->prefix()."c_payment_term as c";
1369 if (is_numeric($cond_reglement)) {
1370 $sqltemp .= " WHERE c.rowid=".((int) $cond_reglement);
1371 } else {
1372 $sqltemp .= " WHERE c.entity IN (".getEntity('c_payment_term').")";
1373 $sqltemp .= " AND c.code = '".$this->db->escape($cond_reglement)."'";
1374 }
1375
1376 dol_syslog(get_class($this).'::calculate_date_lim_reglement', LOG_DEBUG);
1377 $resqltemp = $this->db->query($sqltemp);
1378 if ($resqltemp) {
1379 if ($this->db->num_rows($resqltemp)) {
1380 $obj = $this->db->fetch_object($resqltemp);
1381 $cdr_nbjour = $obj->nbjour;
1382 $cdr_type = $obj->type_cdr;
1383 $cdr_decalage = $obj->decalage;
1384 }
1385 } else {
1386 $this->error = $this->db->error();
1387 return -1;
1388 }
1389 $this->db->free($resqltemp);
1390
1391 /* Define date limit */
1392
1393 // 0 : adding the number of days
1394 if ($cdr_type == 0) {
1395 $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
1396
1397 $datelim += ($cdr_decalage * 3600 * 24);
1398 } elseif ($cdr_type == 1) {
1399 // 1 : application of the "end of the month" rule
1400 $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
1401
1402 $mois = date('m', $datelim);
1403 $annee = date('Y', $datelim);
1404 if ($mois == 12) {
1405 $mois = 1;
1406 $annee += 1;
1407 } else {
1408 $mois += 1;
1409 }
1410 // We move at the beginning of the next month, and we take a day off
1411 $datelim = dol_mktime(12, 0, 0, $mois, 1, $annee, 'gmt');
1412 $datelim -= (3600 * 24);
1413
1414 $datelim += ($cdr_decalage * 3600 * 24);
1415 } elseif ($cdr_type == 2 && !empty($cdr_decalage)) {
1416 // 2 : application of the rule, the N of the current or next month
1417 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
1418 $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
1419
1420 $date_piece = dol_mktime(0, 0, 0, (int) date('m', $datelim), (int) date('d', $datelim), (int) date('Y', $datelim), 'gmt'); // without hours, minutes and seconds
1421 $date_lim_current = dol_mktime(0, 0, 0, (int) date('m', $datelim), (int) $cdr_decalage, (int) date('Y', $datelim), 'gmt'); // without hours, minutes and seconds
1422 $date_lim_next = dol_time_plus_duree((int) $date_lim_current, 1, 'm'); // Add 1 month
1423
1424 $diff = $date_piece - $date_lim_current;
1425
1426 if ($diff <= 0) {
1427 $datelim = $date_lim_current;
1428 } else {
1429 $datelim = $date_lim_next;
1430 }
1431 } else {
1432 return 'Bad value for type_cdr in database for record cond_reglement = '.$cond_reglement;
1433 }
1434
1435 return $datelim;
1436 }
1437
1438 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1451 public function demande_prelevement(User $fuser, float $amount = 0, string $type = 'direct-debit', string $sourcetype = 'facture', int $checkduplicateamongall = 0, int $ribId = 0)
1452 {
1453 // phpcs:enable
1454 global $conf;
1455
1456 $error = 0;
1457
1458 dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
1459
1460 if ($this->status > self::STATUS_DRAFT && $this->paye == 0) {
1461 require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
1462 $bac = new CompanyBankAccount($this->db);
1463 $bac->fetch($ribId, '', $this->socid);
1464
1465 // Option to allow multiple partial requests
1466 // If WITHDRAW_STRICT_CHECK_AMOUNT is enabled, we check the sum of amounts instead of just counting requests
1467 if (getDolGlobalString('WITHDRAW_STRICT_CHECK_AMOUNT')) {
1468 // Calculate sum of amounts already requested instead of just counting requests
1469 // This allows multiple partial requests as long as the total doesn't exceed the remain to pay
1470 $total_already_requested = 0;
1471 // Step 1: Get pending requests with no transfer receipt yet (traite = 0)
1472 $sql = "SELECT COALESCE(SUM(amount), 0) as total_requested";
1473 $sql .= " FROM ".$this->db->prefix()."prelevement_demande";
1474 if ($type == 'bank-transfer') {
1475 $sql .= " WHERE fk_facture_fourn = ".((int) $this->id);
1476 } else {
1477 $sql .= " WHERE fk_facture = ".((int) $this->id);
1478 }
1479 $sql .= " AND type = 'ban'"; // To exclude record done for some online payments
1480 $sql .= " AND traite = 0"; // Not yet in a transfer receipt
1481 dol_syslog(get_class($this)."::demande_prelevement - get pending requests not yet in receipt", LOG_DEBUG);
1482 $resql = $this->db->query($sql);
1483 if ($resql) {
1484 $obj = $this->db->fetch_object($resql);
1485 $total_already_requested += $obj ? (float) $obj->total_requested : 0;
1486 } else {
1487 $this->error = $this->db->error();
1488 dol_syslog(get_class($this).'::demandeprelevement Error -2a');
1489 return -2;
1490 }
1491 // Step 2: Get pending requests in transfer receipt but not yet credited
1492 $sql = "SELECT COALESCE(SUM(pl.amount), 0) as total_requested";
1493 $sql .= " FROM ".$this->db->prefix()."prelevement_lignes as pl";
1494 $sql .= " INNER JOIN ".$this->db->prefix()."prelevement as p ON p.fk_prelevement_lignes = pl.rowid";
1495 if ($type == 'bank-transfer') {
1496 $sql .= " WHERE p.fk_facture_fourn = ".((int) $this->id);
1497 } else {
1498 $sql .= " WHERE p.fk_facture = ".((int) $this->id);
1499 }
1500 $sql .= " AND (pl.statut IS NULL OR pl.statut = 0)"; // Not yet processed (statut 2 = credited)
1501 dol_syslog(get_class($this)."::demande_prelevement - get requests in non-credited receipts", LOG_DEBUG);
1502 $resql = $this->db->query($sql);
1503 if ($resql) {
1504 $obj = $this->db->fetch_object($resql);
1505 $total_already_requested += $obj ? (float) $obj->total_requested : 0;
1506 // Calculate remain to pay
1507 $totalpaid = $this->getSommePaiement();
1508 $totalcreditnotes = $this->getSumCreditNotesUsed();
1509 $totaldeposits = $this->getSumDepositsUsed();
1510 $resteapayer = (float) price2num($this->total_ttc - $totalpaid - $totalcreditnotes - $totaldeposits, 'MT');
1511 // Calculate remaining amount available for new requests
1512 $remaining_for_request = (float) price2num($resteapayer - $total_already_requested, 'MT');
1513 // If no amount specified, use the remaining available
1514 if (empty($amount)) {
1515 $amount = $remaining_for_request;
1516 }
1517 // Check if there's still room for new requests
1518 $can_create_request = ($remaining_for_request > 0 && $amount <= $remaining_for_request);
1519 } else {
1520 $this->error = $this->db->error();
1521 dol_syslog(get_class($this).'::demandeprelevement Error -2b');
1522 return -2;
1523 }
1524 } else {
1525 // Original behavior: only check if a request already exists (count)
1526 $sql = "SELECT count(rowid) as nb";
1527 $sql .= " FROM ".$this->db->prefix()."prelevement_demande";
1528 if ($type == 'bank-transfer') {
1529 $sql .= " WHERE fk_facture_fourn = ".((int) $this->id);
1530 } else {
1531 $sql .= " WHERE fk_facture = ".((int) $this->id);
1532 }
1533 $sql .= " AND type = 'ban'"; // To exclude record done for some online payments
1534 if (empty($checkduplicateamongall)) {
1535 $sql .= " AND traite = 0";
1536 }
1537
1538 dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
1539
1540 $resql = $this->db->query($sql);
1541 if ($resql) {
1542 $obj = $this->db->fetch_object($resql);
1543 $can_create_request = ($obj && $obj->nb == 0);
1544 // Calculate amount if not specified
1545 if (empty($amount)) {
1546 $totalpaid = $this->getSommePaiement();
1547 $totalcreditnotes = $this->getSumCreditNotesUsed();
1548 $totaldeposits = $this->getSumDepositsUsed();
1549 //print "totalpaid=".$totalpaid." totalcreditnotes=".$totalcreditnotes." totaldeposts=".$totaldeposits;
1550 // We can also use bcadd to avoid pb with floating points
1551 // For example print 239.2 - 229.3 - 9.9; does not return 0.
1552 //$resteapayer=bcadd($this->total_ttc,$totalpaid,$conf->global->MAIN_MAX_DECIMALS_TOT);
1553 //$resteapayer=bcadd($resteapayer,$totalavoir,$conf->global->MAIN_MAX_DECIMALS_TOT);
1554 $amount = price2num($this->total_ttc - $totalpaid - $totalcreditnotes - $totaldeposits, 'MT');
1555 }
1556 $remaining_for_request = $amount; // For error message compatibility
1557 } else {
1558 $this->error = $this->db->error();
1559 dol_syslog(get_class($this).'::demandeprelevement Error -2');
1560 return -2;
1561 }
1562 }
1563 // Common code for both modes
1564 if ($can_create_request) {
1565 $now = dol_now();
1566 if (is_numeric($amount) && $amount != 0) {
1567 $sql = 'INSERT INTO '.$this->db->prefix().'prelevement_demande(';
1568 if ($type == 'bank-transfer') {
1569 $sql .= 'fk_facture_fourn, ';
1570 } else {
1571 $sql .= 'fk_facture, ';
1572 }
1573 $sql .= ' amount, date_demande, fk_user_demande, code_banque, code_guichet, number, cle_rib, sourcetype, type, entity';
1574 if (empty($bac->id)) {
1575 $sql .= ')';
1576 } else {
1577 $sql .= ', fk_societe_rib)';
1578 }
1579 $sql .= " VALUES (".((int) $this->id);
1580 $sql .= ", ".((float) price2num($amount));
1581 $sql .= ", '".$this->db->idate($now)."'";
1582 $sql .= ", ".((int) $fuser->id);
1583 $sql .= ", '".$this->db->escape($bac->code_banque)."'";
1584 $sql .= ", '".$this->db->escape($bac->code_guichet)."'";
1585 $sql .= ", '".$this->db->escape($bac->number)."'";
1586 $sql .= ", '".$this->db->escape($bac->cle_rib)."'";
1587 $sql .= ", '".$this->db->escape($sourcetype)."'";
1588 $sql .= ", 'ban'";
1589 $sql .= ", ".((int) $conf->entity);
1590 if (!empty($bac->id)) {
1591 $sql .= ", '".$this->db->escape((string) $bac->id)."'";
1592 }
1593 $sql .= ")";
1594 dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
1595 $resql = $this->db->query($sql);
1596 if (!$resql) {
1597 $this->error = $this->db->lasterror();
1598 dol_syslog(get_class($this).'::demandeprelevement Erreur');
1599 $error++;
1600 }
1601 } else {
1602 $this->error = 'WithdrawRequestErrorNilAmount';
1603 dol_syslog(get_class($this).'::demandeprelevement WithdrawRequestErrorNilAmount');
1604 $error++;
1605 }
1606 if (!$error) {
1607 // Force payment mode of invoice to withdraw
1608 $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
1609 if ($payment_mode_id > 0) {
1610 $result = $this->setPaymentMethods($payment_mode_id);
1611 }
1612 }
1613 if ($error) {
1614 return -1;
1615 }
1616 return 1;
1617 } else {
1618 // Updated error message for when amount exceeds remaining or request already exists
1619 if (getDolGlobalString('WITHDRAW_STRICT_CHECK_AMOUNT')) {
1620 if ($remaining_for_request <= 0) {
1621 $this->error = "AmountRequestedAlreadyReachesTotal";
1622 } else {
1623 $this->error = "AmountExceedsRemainingToRequest";
1624 }
1625 } else {
1626 $this->error = "A request already exists";
1627 }
1628 return 0;
1629 }
1630 } else {
1631 $this->error = "Status of invoice does not allow this";
1632 dol_syslog(get_class($this)."::demandeprelevement ".$this->error." $this->status, $this->paye, $this->mode_reglement_id");
1633 return -3;
1634 }
1635 }
1636
1637
1649 public function makeStripeCardRequest($fuser, $id, $sourcetype = 'facture')
1650 {
1651 // TODO See in sellyoursaas
1652 return 0;
1653 }
1654
1667 public function makeStripeSepaRequest($fuser, $did, $type = 'direct-debit', $sourcetype = 'facture', $service = '', $forcestripe = '')
1668 {
1669 global $conf, $user, $langs;
1670
1671 if ($type != 'bank-transfer' && $type != 'credit-transfer' && !getDolGlobalString('STRIPE_SEPA_DIRECT_DEBIT')) {
1672 return 0;
1673 }
1674 if ($type != 'direct-debit' && !getDolGlobalString('STRIPE_SEPA_CREDIT_TRANSFER')) {
1675 return 0;
1676 }
1677 // Set a default value for service if not provided
1678 if (empty($service)) {
1679 $service = 'StripeTest';
1680 if (getDolGlobalString('STRIPE_LIVE')/* && !GETPOST('forcesandbox', 'alpha')*/) {
1681 $service = 'StripeLive';
1682 }
1683 }
1684
1685 $error = 0;
1686
1687 dol_syslog(get_class($this)."::makeStripeSepaRequest start did=".$did." type=".$type." service=".$service." sourcetype=".$sourcetype." forcestripe=".$forcestripe, LOG_DEBUG);
1688
1689 if ($this->status > self::STATUS_DRAFT && $this->paye == 0) {
1690 // Get the default payment mode for BAN payment of the third party
1691 require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
1692 $bac = new CompanyBankAccount($this->db); // Table societe_rib
1693 $result = $bac->fetch(0, '', $this->socid, 1, 'ban');
1694 if ($result <= 0 || empty($bac->id)) {
1695 $this->error = $langs->trans("ThirdpartyHasNoDefaultBankAccount");
1696 $this->errors[] = $this->error;
1697 dol_syslog(get_class($this)."::makeStripeSepaRequest ".$this->error);
1698 return -1;
1699 }
1700
1701 // Load the pending payment request to process (with rowid=$did)
1702 $sql = "SELECT rowid, date_demande, amount, fk_facture, fk_facture_fourn, fk_salary, fk_prelevement_bons";
1703 $sql .= " FROM ".$this->db->prefix()."prelevement_demande";
1704 $sql .= " WHERE rowid = ".((int) $did);
1705 if ($type != 'bank-transfer' && $type != 'credit-transfer') {
1706 $sql .= " AND fk_facture = ".((int) $this->id); // Add a protection to not pay another invoice than current one
1707 }
1708 if ($type != 'direct-debit') {
1709 if ($sourcetype == 'salary') {
1710 $sql .= " AND fk_salary = ".((int) $this->id); // Add a protection to not pay another salary than current one
1711 } else {
1712 $sql .= " AND fk_facture_fourn = ".((int) $this->id); // Add a protection to not pay another invoice than current one
1713 }
1714 }
1715 $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)
1716
1717 dol_syslog(get_class($this)."::makeStripeSepaRequest load requests to process", LOG_DEBUG);
1718 $resql = $this->db->query($sql);
1719 if ($resql) {
1720 $obj = $this->db->fetch_object($resql);
1721 if (!$obj) {
1722 dol_print_error($this->db, 'CantFindRequestWithId');
1723 return -2;
1724 }
1725
1726 // amount to pay
1727 $amount = $obj->amount;
1728
1729 if (is_numeric($amount) && $amount != 0) {
1730 require_once DOL_DOCUMENT_ROOT.'/societe/class/companypaymentmode.class.php';
1731 $companypaymentmode = new CompanyPaymentMode($this->db); // table societe_rib
1732 $companypaymentmode->fetch($bac->id);
1733 $sepaSequenceType = strtoupper((string) $companypaymentmode->frstrecur);
1734 if (!in_array($sepaSequenceType, array('FRST', 'RCUR', 'OOFF', 'FNAL'), true)) {
1735 $sepaSequenceType = 'FRST';
1736 }
1737
1738 $this->stripechargedone = 0;
1739 $this->stripechargeerror = 0;
1740
1741 $now = dol_now();
1742
1743 $currency = $conf->currency;
1744
1745 $errorforinvoice = 0; // We reset the $errorforinvoice at each invoice loop
1746
1747 $this->fetch_thirdparty();
1748
1749 dol_syslog("makeStripeSepaRequest Process payment request amount=".$amount." thirdparty_id=" . $this->thirdparty->id . ", thirdparty_name=" . $this->thirdparty->name . " ban id=" . $bac->id, LOG_DEBUG);
1750
1751 //$alreadypayed = $this->getSommePaiement();
1752 //$amount_credit_notes_included = $this->getSumCreditNotesUsed();
1753 //$amounttopay = $this->total_ttc - $alreadypayed - $amount_credit_notes_included;
1754 $amounttopay = $amount;
1755
1756 // Correct the amount according to unit of currency
1757 // See https://support.stripe.com/questions/which-zero-decimal-currencies-does-stripe-support
1758 $arrayzerounitcurrency = ['BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'];
1759 $amountstripe = $amounttopay;
1760 if (!in_array($currency, $arrayzerounitcurrency)) {
1761 $amountstripe *= 100;
1762 }
1763
1764 $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.
1765 if (!($fk_bank_account > 0)) {
1766 $error++;
1767 $errorforinvoice++;
1768 dol_syslog("makeStripeSepaRequest Error no bank account defined for Stripe payments", LOG_ERR);
1769 $this->error = "Error bank account for Stripe payments not defined into Stripe module";
1770 $this->errors[] = $this->error;
1771 }
1772
1773 $this->db->begin();
1774
1775 // Create a prelevement_bon
1776 require_once DOL_DOCUMENT_ROOT.'/compta/prelevement/class/bonprelevement.class.php';
1777 $bon = new BonPrelevement($this->db);
1778 if (!$error) {
1779 if (empty($obj->fk_prelevement_bons)) {
1780 // This creates a record into llx_prelevement_bons and updates link with llx_prelevement_demande
1781 $nbinvoices = $bon->create('0', '0', 'real', $sepaSequenceType, 0, 0, $type, $did, $fk_bank_account);
1782 if ($nbinvoices <= 0) {
1783 $error++;
1784 $errorforinvoice++;
1785 dol_syslog("makeStripeSepaRequest Error on BonPrelevement creation", LOG_ERR);
1786 $this->error = "Error on BonPrelevement creation";
1787 $this->errors[] = $this->error;
1788 }
1789 /*
1790 if (!$error) {
1791 // Update the direct debit payment request of the processed request to save the id of the prelevement_bon
1792 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
1793 $sql .= " fk_prelevement_bons = ".((int) $bon->id);
1794 $sql .= " WHERE rowid = ".((int) $did);
1795
1796 $result = $this->db->query($sql);
1797 if ($result < 0) {
1798 $error++;
1799 $this->error = "Error on updating fk_prelevement_bons to ".$bon->id;
1800 $this->errors[] = $this->error;
1801 }
1802 }
1803 */
1804 } else {
1805 $error++;
1806 $errorforinvoice++;
1807 dol_syslog("makeStripeSepaRequest Error Line already part of a bank payment order", LOG_ERR);
1808 $this->error = "The line is already included into a bank payment order. Delete the bank payment order first.";
1809 $this->errors[] = $this->error;
1810 }
1811 }
1812
1813 $paymentintent = null;
1814 if (!$error) {
1815 if ($amountstripe > 0) {
1816 try {
1817 global $savstripearrayofkeysbyenv;
1818 global $stripearrayofkeysbyenv;
1819 $servicestatus = 0;
1820 if ($service == 'StripeLive') {
1821 $servicestatus = 1;
1822 }
1823
1824 //var_dump($companypaymentmode);
1825 dol_syslog("makeStripeSepaRequest We will try to pay with companypaymentmodeid=" . $companypaymentmode->id . " stripe_card_ref=" . $companypaymentmode->stripe_card_ref . " mode=" . $companypaymentmode->status, LOG_DEBUG);
1826
1827 $thirdparty = new Societe($this->db);
1828 $resultthirdparty = $thirdparty->fetch($this->socid);
1829
1830 include_once DOL_DOCUMENT_ROOT . '/stripe/class/stripe.class.php'; // This include the include of htdocs/stripe/config.php
1831 // So it inits or erases the $stripearrayofkeysbyenv
1832 $stripe = new Stripe($this->db);
1833
1834 if (empty($savstripearrayofkeysbyenv)) {
1835 $savstripearrayofkeysbyenv = $stripearrayofkeysbyenv;
1836 }
1837 dol_syslog("makeStripeSepaRequest Current Stripe environment is " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key']);
1838 dol_syslog("makeStripeSepaRequest Current Saved Stripe environment is ".$savstripearrayofkeysbyenv[$servicestatus]['publishable_key']);
1839
1840 $foundalternativestripeaccount = '';
1841 $stripearrayofkeys = array();
1842
1843 // Force stripe to another value (by default this value is empty)
1844 if (! empty($forcestripe)) {
1845 dol_syslog("makeStripeSepaRequest A dedicated stripe account was forced, so we switch to it.");
1846
1847 $tmparray = explode('@', $forcestripe);
1848 if (! empty($tmparray[1])) {
1849 $tmparray2 = explode(':', $tmparray[1]);
1850 if (! empty($tmparray2[1])) {
1851 $stripearrayofkeysbyenv[$servicestatus]["publishable_key"] = $tmparray2[0];
1852 $stripearrayofkeysbyenv[$servicestatus]["secret_key"] = $tmparray2[1];
1853
1854 $stripearrayofkeys = $stripearrayofkeysbyenv[$servicestatus];
1855 \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1856
1857 $foundalternativestripeaccount = $tmparray[0]; // Store the customer id
1858
1859 dol_syslog("makeStripeSepaRequest We use now customer=".$foundalternativestripeaccount." publishable_key=".$stripearrayofkeys['publishable_key'], LOG_DEBUG);
1860 }
1861 }
1862
1863 if (! $foundalternativestripeaccount) {
1864 $stripearrayofkeysbyenv = $savstripearrayofkeysbyenv;
1865
1866 $stripearrayofkeys = $savstripearrayofkeysbyenv[$servicestatus];
1867 \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1868 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);
1869 }
1870 } else {
1871 $stripearrayofkeysbyenv = $savstripearrayofkeysbyenv;
1872
1873 $stripearrayofkeys = $savstripearrayofkeysbyenv[$servicestatus];
1874 \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1875 dol_syslog("makeStripeSepaRequest No dedicated Stripe Account requested, so we use global one, so ".$stripearrayofkeys['publishable_key'], LOG_DEBUG);
1876 }
1877
1878 $stripeacc = $stripe->getStripeAccount($service, $this->socid); // Get Stripe OAuth connect account if it exists (no network access here)
1879
1880 if ($foundalternativestripeaccount) {
1881 if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
1882 $customer = \Stripe\Customer::retrieve(array('id' => "$foundalternativestripeaccount", 'expand[]' => 'sources'));
1883 } else {
1884 $customer = \Stripe\Customer::retrieve(array('id' => "$foundalternativestripeaccount", 'expand[]' => 'sources'), array("stripe_account" => $stripeacc));
1885 }
1886 } else {
1887 $customer = $stripe->customerStripe($thirdparty, $stripeacc, $servicestatus, 0);
1888 if (empty($customer) && ! empty($stripe->error)) {
1889 $this->error = $stripe->error;
1890 $this->errors[] = $this->error;
1891 }
1892 /*if (!empty($customer) && empty($customer->sources)) {
1893 $customer = null;
1894 $this->error = '\Stripe\Customer::retrieve did not returned the sources';
1895 $this->errors[] = $this->error;
1896 }*/
1897 }
1898
1899 // $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)
1900 // $nbdaysbeforeendoftries = (empty($conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES) ? 35 : $conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES);
1901 $postactionmessages = [];
1902
1903 if ($resultthirdparty > 0 && !empty($customer)) {
1904 if (!$error) { // Payment was not canceled
1905 $stripecard = null;
1906 if ($companypaymentmode->type == 'ban') {
1907 // Check into societe_rib if a payment mode for Stripe and ban payment exists
1908 // To make a Stripe SEPA payment request, we must have the payment mode source already saved into societe_rib and retrieved with ->sepaStripe
1909 // The payment mode source is created when we create the bank account on Stripe with paymentmodes.php?action=create
1910 $stripecard = $stripe->sepaStripe($customer, $companypaymentmode, $stripeacc, $servicestatus, 0);
1911 } else {
1912 $error++;
1913 $this->error = 'The payment mode type is not "ban"';
1914 }
1915
1916 if ($stripecard) { // Can be src_... (for sepa) or pm_... (new card mode). Note that card_... (old card mode) should not happen here.
1917 $FULLTAG = 'DID='.$did.'-INV=' . $this->id . '-CUS=' . $thirdparty->id;
1918 $description = 'Stripe payment from makeStripeSepaRequest: ' . $FULLTAG . ' did='.$did.' ref=' . $this->ref;
1919
1920 $stripefailurecode = '';
1921 $stripefailuremessage = '';
1922 $stripefailuredeclinecode = '';
1923
1924 // Using new SCA method
1925 dol_syslog("* Create payment on SEPA " . $stripecard->id . ", amounttopay=" . $amounttopay . ", amountstripe=" . $amountstripe . ", FULLTAG=" . $FULLTAG, LOG_DEBUG);
1926
1927 // Create payment intent and charge payment (confirmnow = true)
1928 $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $this, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1, 1, $did);
1929
1930 $charge = new stdClass();
1931
1932 if ($paymentintent->status === 'succeeded' || $paymentintent->status === 'processing') {
1933 $charge->status = 'ok';
1934 $charge->id = $paymentintent->id;
1935 $charge->customer = $customer->id;
1936 } elseif ($paymentintent->status === 'requires_action') {
1937 //paymentintent->status may be => 'requires_action' (no error in such a case)
1938 dol_syslog(formatLogObject($paymentintent), LOG_DEBUG);
1939
1940 $charge->status = 'failed';
1941 $charge->customer = $customer->id;
1942 $charge->failure_code = $stripe->code;
1943 $charge->failure_message = $stripe->error;
1944 $charge->failure_declinecode = $stripe->declinecode;
1945 $stripefailurecode = $stripe->code;
1946 $stripefailuremessage = 'Action required. Contact the support at ';// . $conf->global->SELLYOURSAAS_MAIN_EMAIL;
1947 $stripefailuredeclinecode = $stripe->declinecode;
1948 } else {
1949 dol_syslog(formatLogObject($paymentintent), LOG_DEBUG);
1950
1951 $charge->status = 'failed';
1952 $charge->customer = $customer->id;
1953 $charge->failure_code = $stripe->code;
1954 $charge->failure_message = $stripe->error;
1955 $charge->failure_declinecode = $stripe->declinecode;
1956 $stripefailurecode = $stripe->code;
1957 $stripefailuremessage = $stripe->error;
1958 $stripefailuredeclinecode = $stripe->declinecode;
1959 }
1960
1961 //var_dump("stripefailurecode=".$stripefailurecode." stripefailuremessage=".$stripefailuremessage." stripefailuredeclinecode=".$stripefailuredeclinecode);
1962 //exit;
1963
1964
1965 // Return $charge = array('id'=>'ch_XXXX', 'status'=>'succeeded|pending|failed', 'failure_code'=>, 'failure_message'=>...)
1966 if (empty($charge) || $charge->status == 'failed') {
1967 dol_syslog('Failed to charge payment mode ' . $stripecard->id . ' stripefailurecode=' . $stripefailurecode . ' stripefailuremessage=' . $stripefailuremessage . ' stripefailuredeclinecode=' . $stripefailuredeclinecode, LOG_WARNING);
1968
1969 // Save a stripe payment was in error
1970 $this->stripechargeerror++;
1971
1972 $error++;
1973 $errorforinvoice++;
1974 $errmsg = $langs->trans("FailedToChargeSEPA");
1975 if (!empty($charge)) {
1976 if ($stripefailuredeclinecode == 'authentication_required') {
1977 $errauthenticationmessage = $langs->trans("ErrSCAAuthentication");
1978 $errmsg = $errauthenticationmessage;
1979 } elseif (in_array($stripefailuredeclinecode, ['insufficient_funds', 'generic_decline'])) {
1980 $errmsg .= ': ' . $charge->failure_code;
1981 $errmsg .= ($charge->failure_message ? ' - ' : '') . ' ' . $charge->failure_message;
1982 if (empty($stripefailurecode)) {
1983 $stripefailurecode = $charge->failure_code;
1984 }
1985 if (empty($stripefailuremessage)) {
1986 $stripefailuremessage = $charge->failure_message;
1987 }
1988 } else {
1989 $errmsg .= ': failure_code=' . $charge->failure_code;
1990 $errmsg .= ($charge->failure_message ? ' - ' : '') . ' failure_message=' . $charge->failure_message;
1991 if (empty($stripefailurecode)) {
1992 $stripefailurecode = $charge->failure_code;
1993 }
1994 if (empty($stripefailuremessage)) {
1995 $stripefailuremessage = $charge->failure_message;
1996 }
1997 }
1998 } else {
1999 $errmsg .= ': ' . $stripefailurecode . ' - ' . $stripefailuremessage;
2000 $errmsg .= ($stripefailuredeclinecode ? ' - ' . $stripefailuredeclinecode : '');
2001 }
2002
2003 $description = 'Stripe payment ERROR from makeStripeSepaRequest: ' . $FULLTAG;
2004 $postactionmessages[] = $errmsg . ' (' . $stripearrayofkeys['publishable_key'] . ')';
2005
2006 $this->error = $errmsg;
2007 $this->errors[] = $this->error;
2008 } else {
2009 dol_syslog('Successfuly request '.$type.' '.$stripecard->id);
2010
2011 $postactionmessages[] = 'Success to request '.$type.' (' . $charge->id . ' with ' . $stripearrayofkeys['publishable_key'] . ')';
2012
2013 // Save a stripe payment was done in real life so later we will be able to force a commit on recorded payments
2014 // even if in batch mode (method doTakePaymentStripe), we will always make all action in one transaction with a forced commit.
2015 $this->stripechargedone++;
2016
2017 // Default description used for label of event. Will be overwrite by another value later.
2018 $description = 'Stripe payment request OK (' . $charge->id . ') from makeStripeSepaRequest: ' . $FULLTAG;
2019 }
2020
2021 $object = $this;
2022
2023 // Track an event
2024 if (empty($charge) || $charge->status == 'failed') {
2025 $actioncode = 'PAYMENT_STRIPE_KO';
2026 $extraparams = $stripefailurecode;
2027 $extraparams .= (($extraparams && $stripefailuremessage) ? ' - ' : '') . $stripefailuremessage;
2028 $extraparams .= (($extraparams && $stripefailuredeclinecode) ? ' - ' : '') . $stripefailuredeclinecode;
2029 } else {
2030 $actioncode = 'PAYMENT_STRIPE_OK';
2031 $extraparams = array();
2032 }
2033 } else {
2034 $error++;
2035 $errorforinvoice++;
2036 dol_syslog("No ban payment method found for this stripe customer " . $customer->id, LOG_WARNING);
2037
2038 $this->error = 'Failed to get direct debit payment method for stripe customer = ' . $customer->id;
2039 $this->errors[] = $this->error;
2040
2041 $description = 'Failed to find or use the payment mode - no ban defined for the thirdparty account';
2042 $stripefailurecode = 'BADPAYMENTMODE';
2043 $stripefailuremessage = 'Failed to find or use the payment mode - no ban defined for the thirdparty account';
2044 $postactionmessages[] = $description . ' (' . $stripearrayofkeys['publishable_key'] . ')';
2045
2046 $object = $this;
2047
2048 $actioncode = 'PAYMENT_STRIPE_KO';
2049 $extraparams = array();
2050 }
2051 } else {
2052 // If error because payment was canceled for a logical reason, we do nothing (no event added)
2053 $description = '';
2054 $stripefailurecode = '';
2055 $stripefailuremessage = '';
2056
2057 $object = $this;
2058
2059 $actioncode = '';
2060 $extraparams = array();
2061 }
2062 } else { // Else of the if ($resultthirdparty > 0 && ! empty($customer)) {
2063 if ($resultthirdparty <= 0) {
2064 dol_syslog('SellYourSaasUtils Failed to load customer for thirdparty_id = ' . $thirdparty->id, LOG_WARNING);
2065 $this->error = 'Failed to load Stripe account for thirdparty_id = ' . $thirdparty->id;
2066 $this->errors[] = $this->error;
2067 } else { // $customer stripe not found
2068 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);
2069 $this->error = 'Failed to get Stripe account id for thirdparty_id = ' . $thirdparty->id . " in mode " . $servicestatus . " in Stripe env " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key'];
2070 $this->errors[] = $this->error;
2071 }
2072 $error++;
2073 $errorforinvoice++;
2074
2075 $description = 'Failed to find or use your payment mode (no payment mode for this customer id)';
2076 $stripefailurecode = 'BADPAYMENTMODE';
2077 $stripefailuremessage = 'Failed to find or use your payment mode (no payment mode for this customer id)';
2078 $postactionmessages = [];
2079
2080 $object = $this;
2081
2082 $actioncode = 'PAYMENT_STRIPE_KO';
2083 $extraparams = array();
2084 }
2085
2086 if ($description) {
2087 dol_syslog("* Record event for credit transfer or direct debit request result - " . $description);
2088 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
2089
2090 // Insert record of payment (success or error)
2091 $actioncomm = new ActionComm($this->db);
2092
2093 $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
2094 $actioncomm->code = 'AC_' . $actioncode;
2095 $actioncomm->label = $description;
2096 $actioncomm->note_private = implode(",\n", $postactionmessages);
2097 $actioncomm->fk_project = $this->fk_project;
2098 $actioncomm->datep = $now;
2099 $actioncomm->datef = $now;
2100 $actioncomm->percentage = -1; // Not applicable
2101 $actioncomm->socid = $thirdparty->id;
2102 $actioncomm->contactid = 0;
2103 $actioncomm->authorid = $user->id; // User saving action
2104 $actioncomm->userownerid = $user->id; // Owner of action
2105 // Fields when action is a real email (content is already into note)
2106 /*$actioncomm->email_msgid = $object->email_msgid;
2107 $actioncomm->email_from = $object->email_from;
2108 $actioncomm->email_sender= $object->email_sender;
2109 $actioncomm->email_to = $object->email_to;
2110 $actioncomm->email_tocc = $object->email_tocc;
2111 $actioncomm->email_tobcc = $object->email_tobcc;
2112 $actioncomm->email_subject = $object->email_subject;
2113 $actioncomm->errors_to = $object->errors_to;*/
2114 $actioncomm->fk_element = $this->id;
2115 $actioncomm->elementid = $this->id;
2116 $actioncomm->elementtype = $this->element;
2117 $actioncomm->extraparams = $extraparams; // Can be null, empty string or array()
2118
2119 $actioncomm->create($user);
2120 }
2121
2122 $this->description = $description;
2123 $this->postactionmessages = $postactionmessages;
2124 } catch (Exception $e) {
2125 $error++;
2126 $errorforinvoice++;
2127 dol_syslog('Error ' . $e->getMessage(), LOG_ERR);
2128 $this->error = 'Error ' . $e->getMessage();
2129 $this->errors[] = $this->error;
2130 }
2131 } else { // If remain to pay is null
2132 $error++;
2133 $errorforinvoice++;
2134 dol_syslog("Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?", LOG_WARNING);
2135 $this->error = "Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?";
2136 $this->errors[] = $this->error;
2137 }
2138 }
2139
2140 // Set status of the order to "Transferred" with method 'api'
2141 if (!$error && !$errorforinvoice) {
2142 $result = $bon->set_infotrans($user, $now, 3);
2143 if ($result < 0) {
2144 $error++;
2145 $errorforinvoice++;
2146 dol_syslog("Error on BonPrelevement creation", LOG_ERR);
2147 $this->error = "Error on BonPrelevement creation";
2148 $this->errors[] = $this->error;
2149 }
2150 }
2151
2152 if (!$error && !$errorforinvoice && $paymentintent !== null) {
2153 // Update the direct debit payment request of the processed invoice to save the id of the prelevement_bon
2154 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
2155 $sql .= " ext_payment_id = '".$this->db->escape($paymentintent->id)."',";
2156 $sql .= " ext_payment_site = '".$this->db->escape($service)."'";
2157 $sql .= " WHERE rowid = ".((int) $did);
2158
2159 dol_syslog(get_class($this)."::makeStripeSepaRequest update to save stripe paymentintent ids", LOG_DEBUG);
2160 $resql = $this->db->query($sql);
2161 if (!$resql) {
2162 $this->error = $this->db->lasterror();
2163 dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur');
2164 $error++;
2165 }
2166 }
2167
2168 if (!$error && !$errorforinvoice) {
2169 $this->db->commit();
2170 } else {
2171 $this->db->rollback();
2172 }
2173 } else {
2174 $this->error = 'WithdrawRequestErrorNilAmount';
2175 dol_syslog(get_class($this).'::makeStripeSepaRequest WithdrawRequestErrorNilAmount');
2176 $error++;
2177 }
2178
2179 /*
2180 if (!$error) {
2181 // Force payment mode of the invoice to withdraw
2182 $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
2183 if ($payment_mode_id > 0) {
2184 $result = $this->setPaymentMethods($payment_mode_id);
2185 }
2186 }*/
2187
2188 if ($error) {
2189 return -1;
2190 }
2191 return 1;
2192 } else {
2193 $this->error = $this->db->error();
2194 dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur -2');
2195 return -2;
2196 }
2197 } else {
2198 $this->error = "Status of invoice does not allow this";
2199 dol_syslog(get_class($this)."::makeStripeSepaRequest ".$this->error." ".$this->status." ,".$this->paye.", ".$this->mode_reglement_id, LOG_WARNING);
2200 return -3;
2201 }
2202 }
2203
2204 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2212 public function demande_prelevement_delete($fuser, $did)
2213 {
2214 // phpcs:enable
2215 $sql = 'DELETE FROM '.$this->db->prefix().'prelevement_demande';
2216 $sql .= ' WHERE rowid = '.((int) $did);
2217 $sql .= ' AND traite = 0';
2218 if ($this->db->query($sql)) {
2219 return 0;
2220 } else {
2221 $this->error = $this->db->lasterror();
2222 dol_syslog(get_class($this).'::demande_prelevement_delete Error '.$this->error);
2223 return -1;
2224 }
2225 }
2226
2232 public function buildEPCQrCodeString()
2233 {
2234 global $mysoc;
2235
2236 // Get the amount to pay
2237 $amount_to_pay = $this->getRemainToPay();
2238
2239 // Prevent negative values (e.g. overpayments)
2240 $amount_to_pay = max(0, $amount_to_pay);
2241
2242 // Ensure numeric formatting for EPC QR code
2243 $amount_to_pay = price2num($amount_to_pay, 'MT');
2244
2245 // Initialize an array to hold the lines of the QR code
2246 $lines = array();
2247
2248 // Add the standard elements to the QR code
2249 $lines = [
2250 'BCD', // Service Tag (optional)
2251 '002', // Version (optional)
2252 '1', // Character set (optional)
2253 'SCT', // Identification (optional)
2254 ];
2255
2256 // Add the bank account information
2257 include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
2258
2259 $idofbankaccountouse = $this->fk_account;
2260 if (empty($idofbankaccountouse)) {
2261 $idofbankaccountouse = $this->fk_bank; // for backward compatibility
2262 }
2263 if (empty($idofbankaccountouse)) {
2264 $idofbankaccountouse = getDolGlobalInt('FACTURE_RIB_NUMBER');
2265 }
2266
2267 if ($idofbankaccountouse > 0) {
2268 $bankAccount = new Account($this->db);
2269 $bankAccount->fetch($idofbankaccountouse);
2270 $lines[] = $bankAccount->bic; //BIC (required)
2271 if (!empty($bankAccount->owner_name)) {
2272 $lines[] = $bankAccount->owner_name; //Owner of the bank account, if present (required)
2273 } else {
2274 $lines[] = $mysoc->name; //Name (required)
2275 }
2276 $lines[] = $bankAccount->iban; //IBAN (required)
2277 } else {
2278 $lines[] = ""; //BIC (required)
2279 $lines[] = $mysoc->name; //Name (required)
2280 $lines[] = ""; //IBAN (required)
2281 }
2282
2283 $structuredCommunication = '';
2284 $remittanceInformation = $this->ref;
2285 if (getDolGlobalString('INVOICE_PAYMENT_ENABLE_STRUCTURED_COMMUNICATION')) {
2286 include_once DOL_DOCUMENT_ROOT.'/core/lib/functions_be.lib.php';
2287 $structuredCommunication = dolBECalculateStructuredCommunication((string) $this->ref, $this->type);
2288 $remittanceInformation = '';
2289 }
2290
2291 // Add the amount and reference
2292 $lines[] = 'EUR' . $amount_to_pay; // Amount (optional)
2293 $lines[] = ''; // Purpose (optional)
2294 $lines[] = $structuredCommunication; // Payment reference (optional)
2295 $lines[] = $remittanceInformation; // Remittance Information (optional)
2296
2297 // Join the lines with newline characters and return the result
2298 return implode("\n", $lines);
2299 }
2305 public function buildZATCAQRString()
2306 {
2307 global $conf, $mysoc;
2308
2309 $tmplang = new Translate('', $conf);
2310 $tmplang->setDefaultLang('en_US');
2311 $tmplang->load("main");
2312
2313 $datestring = dol_print_date($this->date, 'dayhourrfc');
2314 //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
2315 //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
2316 $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
2317 $pricetaxstring = price2num($this->total_tva, 2, 1);
2318
2319 /*
2320 $name = implode(unpack("H*", $this->thirdparty->name));
2321 $vatnumber = implode(unpack("H*", $this->thirdparty->tva_intra));
2322 $date = implode(unpack("H*", $datestring));
2323 $pricewithtax = implode(unpack("H*", price2num($pricewithtaxstring, 2)));
2324 $pricetax = implode(unpack("H*", $pricetaxstring));
2325
2326 //var_dump(strlen($this->thirdparty->name));
2327 //var_dump(str_pad(dechex('9'), 2, '0', STR_PAD_LEFT));
2328 //var_dump($this->thirdparty->name);
2329 //var_dump(implode(unpack("H*", $this->thirdparty->name)));
2330 //var_dump(price($this->total_tva, 0, $tmplang, 0, -1, 2));
2331
2332 $s = '01'.str_pad(dechex(strlen($this->thirdparty->name)), 2, '0', STR_PAD_LEFT).$name;
2333 $s .= '02'.str_pad(dechex(strlen($this->thirdparty->tva_intra)), 2, '0', STR_PAD_LEFT).$vatnumber;
2334 $s .= '03'.str_pad(dechex(strlen($datestring)), 2, '0', STR_PAD_LEFT).$date;
2335 $s .= '04'.str_pad(dechex(strlen($pricewithtaxstring)), 2, '0', STR_PAD_LEFT).$pricewithtax;
2336 $s .= '05'.str_pad(dechex(strlen($pricetaxstring)), 2, '0', STR_PAD_LEFT).$pricetax;
2337 $s .= ''; // Hash of xml invoice
2338 $s .= ''; // ecda signature
2339 $s .= ''; // ecda public key
2340 $s .= ''; // ecda signature of public key stamp
2341 */
2342 $mysocname = $mysoc->name ?? '';
2343 $mysoctva_intra = $mysoc->tva_intra ?? '';
2344 // Using TLV format
2345 $s = pack('C1', 1).pack('C1', strlen($mysocname)).$mysocname;
2346 $s .= pack('C1', 2).pack('C1', strlen($mysoctva_intra)).$mysoctva_intra;
2347 $s .= pack('C1', 3).pack('C1', strlen($datestring)).$datestring;
2348 $s .= pack('C1', 4).pack('C1', strlen($pricewithtaxstring)).$pricewithtaxstring;
2349 $s .= pack('C1', 5).pack('C1', strlen($pricetaxstring)).$pricetaxstring;
2350 $s .= ''; // Hash of xml invoice
2351 $s .= ''; // ecda signature
2352 $s .= ''; // ecda public key
2353 $s .= ''; // ecda signature of public key stamp
2354
2355 $s = base64_encode($s);
2356
2357 return $s;
2358 }
2359
2360
2367 {
2368 global $conf, $mysoc;
2369
2370 $tmplang = new Translate('', $conf);
2371 $tmplang->setDefaultLang('en_US');
2372 $tmplang->load("main");
2373
2374 $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
2375 $pricetaxstring = price2num($this->total_tva, 2, 1);
2376
2377 $complementaryinfo = '';
2378 /*
2379 Example: //S1/10/10201409/11/190512/20/1400.000-53/30/106017086/31/180508/32/7.7/40/2:10;0:30
2380 /10/ Invoice number -- 10201409
2381 /11/ Invoice date -- 12.05.2019
2382 /20/ Customer reference -- 1400.000-53
2383 /30/ VAT IDE number -- CHE-106.017.086 TVA
2384 /31/ Service date for VAT accounting -- 08.05.2018
2385 /32/ VAT rate on total invoice amount -- 7.7%
2386 /40/ Terms -- 2% discount at 10 days, net payment at 30 days
2387 */
2388 $datestring = dol_print_date($this->date, '%y%m%d');
2389 //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
2390 //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
2391 $complementaryinfo = '//S1/10/'.str_replace('/', '', $this->ref).'/11/'.$datestring;
2392 if ($this->ref_client) {
2393 $complementaryinfo .= '/20/'.$this->ref_client;
2394 }
2395 if ($this->thirdparty->tva_intra) {
2396 $complementaryinfo .= '/30/'.$this->thirdparty->tva_intra;
2397 }
2398
2399 include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
2400 $bankaccount = new Account($this->db);
2401
2402 // Header
2403 $s = '';
2404 $s .= "SPC\n";
2405 $s .= "0200\n";
2406 $s .= "1\n";
2407 // Info Seller ("Account / Payable to")
2408 if ($this->fk_account > 0) {
2409 // Bank BAN if country is LI or CH. TODO Add a test to check than IBAN start with CH or LI
2410 $bankaccount->fetch($this->fk_account);
2411 $s .= $bankaccount->iban."\n";
2412 } else {
2413 $s .= "\n";
2414 }
2415 if ($bankaccount->id > 0 && getDolGlobalString('PDF_SWISS_QRCODE_USE_OWNER_OF_ACCOUNT_AS_CREDITOR')) {
2416 // If a bank account is provided and we ask to use it as creditor, we use the bank address
2417 // 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 ?
2418 $s .= "S\n";
2419 $s .= dol_trunc($bankaccount->owner_name, 70, 'right', 'UTF-8', 1)."\n";
2420 $addresslinearray = explode("\n", $bankaccount->owner_address);
2421 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
2422 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
2423 /*$s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
2424 $s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
2425 $s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";*/
2426 } else {
2427 $s .= "S\n";
2428 $s .= dol_trunc((string) $mysoc->name, 70, 'right', 'UTF-8', 1)."\n";
2429 $addresslinearray = explode("\n", $mysoc->address);
2430 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
2431 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
2432 $s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
2433 $s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
2434 $s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";
2435 }
2436 // Final seller (Ultimate seller) ("Final creditor" = "In favour of")
2437 $s .= "\n";
2438 $s .= "\n";
2439 $s .= "\n";
2440 $s .= "\n";
2441 $s .= "\n";
2442 $s .= "\n";
2443 $s .= "\n";
2444 // Amount of payment (to do?)
2445 $s .= price($pricewithtaxstring, 0, 'none', 0, 0, 2)."\n";
2446 $s .= ($this->multicurrency_code ? $this->multicurrency_code : $conf->currency)."\n";
2447 // Buyer
2448 $s .= "S\n";
2449 $s .= dol_trunc((string) $this->thirdparty->name, 70, 'right', 'UTF-8', 1)."\n";
2450 $addresslinearray = explode("\n", $this->thirdparty->address);
2451 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
2452 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
2453 $s .= dol_trunc($this->thirdparty->zip, 16, 'right', 'UTF-8', 1)."\n";
2454 $s .= dol_trunc($this->thirdparty->town, 35, 'right', 'UTF-8', 1)."\n";
2455 $s .= dol_trunc($this->thirdparty->country_code, 2, 'right', 'UTF-8', 1)."\n";
2456 // ID of payment
2457 $s .= "NON\n"; // NON or QRR
2458 $s .= "\n"; // QR Code reference if previous field is QRR
2459 // Free text
2460 if ($complementaryinfo) {
2461 $s .= $complementaryinfo."\n";
2462 } else {
2463 $s .= "\n";
2464 }
2465 $s .= "EPD\n";
2466 // More text, complementary info
2467 if ($complementaryinfo) {
2468 $s .= $complementaryinfo."\n";
2469 }
2470 $s .= "\n";
2471 //var_dump($s);exit;
2472 return $s;
2473 }
2474}
2475
2476
2477
2478require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
2479
2484{
2490 public $label;
2491
2497 public $ref; // Product ref (deprecated)
2503 public $libelle; // Product label (deprecated)
2504
2509 public $product_type = 0;
2510
2515 public $product_ref;
2516
2521 public $product_label;
2522
2527 public $product_desc;
2528
2533 public $qty;
2534
2539 public $subprice;
2540
2546 public $price;
2547
2552 public $fk_product;
2553
2558 public $vat_src_code;
2559
2564 public $tva_tx;
2565
2570 public $localtax1_tx;
2571
2576 public $localtax2_tx;
2577
2583 public $localtax1_type;
2584
2590 public $localtax2_type;
2591
2596 public $remise_percent;
2597
2602 public $fk_remise_except;
2603
2608 public $total_ht;
2609
2614 public $total_tva;
2615
2620 public $total_localtax1;
2621
2626 public $total_localtax2;
2627
2632 public $total_ttc;
2633
2637 public $revenuestamp;
2638
2639
2643 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
2647 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
2648
2652 public $buy_price_ht;
2657 public $buyprice;
2662 public $pa_ht;
2663
2667 public $marge_tx;
2671 public $marque_tx;
2672
2679 public $info_bits = 0;
2680
2689 public $special_code = 0;
2690
2695 public $fk_user_author;
2696
2701 public $fk_user_modif;
2702
2706 public $fk_code_ventilation;
2707
2711 public $situation_percent = 100;
2712
2713
2719 public function isDepositLine()
2720 {
2721 // Do not take into account lines of the type "deposit."
2722 $reg = array();
2723 if (preg_match('/^\‍((.*)\‍)$/', $this->desc, $reg)) {
2724 if ($reg[1] == 'DEPOSIT') {
2725 return true;
2726 }
2727 }
2728 return false;
2729 }
2730}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
$propal type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition propal.php:280
isALNERunningVersion($blockedlogusagealreadychecked=0, $blockedlogmoduleonalreadychecked=0)
Return if the application is executed with the LNE requirements on.
$object ref
Definition info.php:90
Class to manage bank accounts.
Class to manage agenda events (actions)
Class to manage withdrawal receipts.
Superclass for invoice classes.
const TYPE_CREDIT_NOTE
Credit note invoice.
const STATUS_CLOSED
Classified paid.
getSumCreditNotesUsed($multicurrency=0)
Return amount (with tax) of all credit notes invoices + excess received used by invoice.
getRemainToPay($multicurrency=0)
Return remain amount to pay.
buildZATCAQRString()
Build string for ZATCA QR Code (Arabi Saudia)
makeStripeCardRequest($fuser, $id, $sourcetype='facture')
Create a payment with Stripe card Must take amount using Stripe and record an event into llx_actionco...
getVentilExportCompta($mode=0)
Return if an invoice was transferred into accountnancy.
const TYPE_STANDARD
Standard invoice.
buildEPCQrCodeString()
Build string for EPC QR Code.
makeStripeSepaRequest($fuser, $did, $type='direct-debit', $sourcetype='facture', $service='', $forcestripe='')
Create a direct debit order into prelevement_bons for a given prelevement_request,...
getSubtypeLabel($table='')
Return label of invoice subtype.
demande_prelevement_delete($fuser, $did)
Remove a direct debit request or a credit transfer request.
const TYPE_PROFORMA
Proforma invoice.
buildSwitzerlandQRString()
Build string for QR-Bill (Switzerland)
const STATUS_VALIDATED
Validated (need to be paid)
getListOfPayments($filtertype='', $multicurrency=0, $mode=0)
Return list of payments.
getListOfOpenDirectDebitOrCreditTransfer($type)
Return list of open direct debit or credit transfer.
const TYPE_SITUATION
Situation invoice.
getSumDepositsUsed($multicurrency=0)
Return amount (with tax) of all deposits invoices used by invoice.
LibStatut($paye, $status, $mode=0, $alreadypaid=-1, $type=-1, $moreparams=array())
Return label of a status.
getSommePaiement($multicurrency=0)
Return amount of payments already done.
isReplacable()
Return if an invoice can be replaced by a Replacement invoice (also called "Correction invoice").
demande_prelevement(User $fuser, float $amount=0, string $type='direct-debit', string $sourcetype='facture', int $checkduplicateamongall=0, int $ribId=0)
Create a withdrawal request for a direct debit order or a credit transfer order.
const TYPE_DEPOSIT
Deposit invoice.
const STATUS_ABANDONED
Classified abandoned and no payment done.
calculate_date_lim_reglement($cond_reglement=0)
Returns an invoice payment deadline based on the invoice settlement conditions and billing date.
const TYPE_REPLACEMENT
Replacement invoice.
isEditable()
Return if an invoice can be set back to draft.
getSumFromThisCreditNotesNotUsed($multicurrency=0)
Return amount (with tax) of all converted amount for this credit note.
getListIdAvoirFromInvoice()
Returns array of credit note ids from the invoice.
setRetainedWarrantyPaymentTerms($id)
Change the retained warranty payments terms.
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,...
isDepositLine()
Check if a line is a deposit line.
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
setPaymentMethods($id)
Change the payments methods.
Parent class for class inheritance lines of business objects This class is useless for the moment so ...
Class to manage bank accounts description of third parties.
Class for CompanyPaymentMode.
Class to manage absolute discounts.
Class to manage invoices.
Class to manage third parties objects (customers, suppliers, prospects...)
Stripe class @TODO No reason to extend CommonObject.
Class to manage translations.
Class to manage Dolibarr users.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:169
global $mysoc
dol_time_plus_duree($time, $duration_value, $duration_unit, $ruleforendofmonth=0)
Add a delay to a date.
Definition date.lib.php:126
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.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
dol_getIdFromCode($db, $key, $tablename, $fieldkey='code', $fieldid='id', $entityfilter=0, $filters='', $useCache=true)
Return an id or code from a code or id.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
if(!function_exists( 'utf8_encode')) if(!function_exists('utf8_decode')) if(!function_exists( 'str_starts_with')) if(!function_exists('str_ends_with')) if(!function_exists( 'str_contains')) formatLogObject($data)
Return a string serialized to be output on log with dol_syslog() An option allow to output log in one...
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dolBECalculateStructuredCommunication($invoice_number, $invoice_type)
Calculate Structured Communication / BE Bank payment reference number.
print $langs trans('Date')." left Ref Label right Qty right Price right TotalHT right TotalTTC right right right right right right right right right centpercent right TotalHT right n right VAT right n right TotalVAT right n No sujeto a RE IRPF right TotalLT1 right n right TotalLT2 right n right TotalTTC right n takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency right TotalTTC takeposcustomercurrency right takeposcustomercurrency n right Paid right PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:492