dolibarr 24.0.0-beta
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 the certified version';
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';
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);
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)); // Sans les heures minutes et secondes
1421 $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
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
1734 $this->stripechargedone = 0;
1735 $this->stripechargeerror = 0;
1736
1737 $now = dol_now();
1738
1739 $currency = $conf->currency;
1740
1741 $errorforinvoice = 0; // We reset the $errorforinvoice at each invoice loop
1742
1743 $this->fetch_thirdparty();
1744
1745 dol_syslog("makeStripeSepaRequest Process payment request amount=".$amount." thirdparty_id=" . $this->thirdparty->id . ", thirdparty_name=" . $this->thirdparty->name . " ban id=" . $bac->id, LOG_DEBUG);
1746
1747 //$alreadypayed = $this->getSommePaiement();
1748 //$amount_credit_notes_included = $this->getSumCreditNotesUsed();
1749 //$amounttopay = $this->total_ttc - $alreadypayed - $amount_credit_notes_included;
1750 $amounttopay = $amount;
1751
1752 // Correct the amount according to unit of currency
1753 // See https://support.stripe.com/questions/which-zero-decimal-currencies-does-stripe-support
1754 $arrayzerounitcurrency = ['BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'];
1755 $amountstripe = $amounttopay;
1756 if (!in_array($currency, $arrayzerounitcurrency)) {
1757 $amountstripe *= 100;
1758 }
1759
1760 $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.
1761 if (!($fk_bank_account > 0)) {
1762 $error++;
1763 $errorforinvoice++;
1764 dol_syslog("makeStripeSepaRequest Error no bank account defined for Stripe payments", LOG_ERR);
1765 $this->error = "Error bank account for Stripe payments not defined into Stripe module";
1766 $this->errors[] = $this->error;
1767 }
1768
1769 $this->db->begin();
1770
1771 // Create a prelevement_bon
1772 require_once DOL_DOCUMENT_ROOT.'/compta/prelevement/class/bonprelevement.class.php';
1773 $bon = new BonPrelevement($this->db);
1774 if (!$error) {
1775 if (empty($obj->fk_prelevement_bons)) {
1776 // This creates a record into llx_prelevement_bons and updates link with llx_prelevement_demande
1777 $nbinvoices = $bon->create('0', '0', 'real', 'ALL', 0, 0, $type, $did, $fk_bank_account);
1778 if ($nbinvoices <= 0) {
1779 $error++;
1780 $errorforinvoice++;
1781 dol_syslog("makeStripeSepaRequest Error on BonPrelevement creation", LOG_ERR);
1782 $this->error = "Error on BonPrelevement creation";
1783 $this->errors[] = $this->error;
1784 }
1785 /*
1786 if (!$error) {
1787 // Update the direct debit payment request of the processed request to save the id of the prelevement_bon
1788 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
1789 $sql .= " fk_prelevement_bons = ".((int) $bon->id);
1790 $sql .= " WHERE rowid = ".((int) $did);
1791
1792 $result = $this->db->query($sql);
1793 if ($result < 0) {
1794 $error++;
1795 $this->error = "Error on updating fk_prelevement_bons to ".$bon->id;
1796 $this->errors[] = $this->error;
1797 }
1798 }
1799 */
1800 } else {
1801 $error++;
1802 $errorforinvoice++;
1803 dol_syslog("makeStripeSepaRequest Error Line already part of a bank payment order", LOG_ERR);
1804 $this->error = "The line is already included into a bank payment order. Delete the bank payment order first.";
1805 $this->errors[] = $this->error;
1806 }
1807 }
1808
1809 $paymentintent = null;
1810 if (!$error) {
1811 if ($amountstripe > 0) {
1812 try {
1813 global $savstripearrayofkeysbyenv;
1814 global $stripearrayofkeysbyenv;
1815 $servicestatus = 0;
1816 if ($service == 'StripeLive') {
1817 $servicestatus = 1;
1818 }
1819
1820 //var_dump($companypaymentmode);
1821 dol_syslog("makeStripeSepaRequest We will try to pay with companypaymentmodeid=" . $companypaymentmode->id . " stripe_card_ref=" . $companypaymentmode->stripe_card_ref . " mode=" . $companypaymentmode->status, LOG_DEBUG);
1822
1823 $thirdparty = new Societe($this->db);
1824 $resultthirdparty = $thirdparty->fetch($this->socid);
1825
1826 include_once DOL_DOCUMENT_ROOT . '/stripe/class/stripe.class.php'; // This include the include of htdocs/stripe/config.php
1827 // So it inits or erases the $stripearrayofkeysbyenv
1828 $stripe = new Stripe($this->db);
1829
1830 if (empty($savstripearrayofkeysbyenv)) {
1831 $savstripearrayofkeysbyenv = $stripearrayofkeysbyenv;
1832 }
1833 dol_syslog("makeStripeSepaRequest Current Stripe environment is " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key']);
1834 dol_syslog("makeStripeSepaRequest Current Saved Stripe environment is ".$savstripearrayofkeysbyenv[$servicestatus]['publishable_key']);
1835
1836 $foundalternativestripeaccount = '';
1837
1838 // Force stripe to another value (by default this value is empty)
1839 if (! empty($forcestripe)) {
1840 dol_syslog("makeStripeSepaRequest A dedicated stripe account was forced, so we switch to it.");
1841
1842 $tmparray = explode('@', $forcestripe);
1843 if (! empty($tmparray[1])) {
1844 $tmparray2 = explode(':', $tmparray[1]);
1845 if (! empty($tmparray2[1])) {
1846 $stripearrayofkeysbyenv[$servicestatus]["publishable_key"] = $tmparray2[0];
1847 $stripearrayofkeysbyenv[$servicestatus]["secret_key"] = $tmparray2[1];
1848
1849 $stripearrayofkeys = $stripearrayofkeysbyenv[$servicestatus];
1850 \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1851
1852 $foundalternativestripeaccount = $tmparray[0]; // Store the customer id
1853
1854 dol_syslog("makeStripeSepaRequest We use now customer=".$foundalternativestripeaccount." publishable_key=".$stripearrayofkeys['publishable_key'], LOG_DEBUG);
1855 }
1856 }
1857
1858 if (! $foundalternativestripeaccount) {
1859 $stripearrayofkeysbyenv = $savstripearrayofkeysbyenv;
1860
1861 $stripearrayofkeys = $savstripearrayofkeysbyenv[$servicestatus];
1862 \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1863 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);
1864 }
1865 } else {
1866 $stripearrayofkeysbyenv = $savstripearrayofkeysbyenv;
1867
1868 $stripearrayofkeys = $savstripearrayofkeysbyenv[$servicestatus];
1869 \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1870 dol_syslog("makeStripeSepaRequest No dedicated Stripe Account requested, so we use global one, so ".$stripearrayofkeys['publishable_key'], LOG_DEBUG);
1871 }
1872
1873 $stripeacc = $stripe->getStripeAccount($service, $this->socid); // Get Stripe OAuth connect account if it exists (no network access here)
1874
1875 if ($foundalternativestripeaccount) {
1876 if (empty($stripeacc)) { // If the Stripe connect account not set, we use common API usage
1877 $customer = \Stripe\Customer::retrieve(array('id' => "$foundalternativestripeaccount", 'expand[]' => 'sources'));
1878 } else {
1879 $customer = \Stripe\Customer::retrieve(array('id' => "$foundalternativestripeaccount", 'expand[]' => 'sources'), array("stripe_account" => $stripeacc));
1880 }
1881 } else {
1882 $customer = $stripe->customerStripe($thirdparty, $stripeacc, $servicestatus, 0);
1883 if (empty($customer) && ! empty($stripe->error)) {
1884 $this->error = $stripe->error;
1885 $this->errors[] = $this->error;
1886 }
1887 /*if (!empty($customer) && empty($customer->sources)) {
1888 $customer = null;
1889 $this->error = '\Stripe\Customer::retrieve did not returned the sources';
1890 $this->errors[] = $this->error;
1891 }*/
1892 }
1893
1894 // $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)
1895 // $nbdaysbeforeendoftries = (empty($conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES) ? 35 : $conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES);
1896 $postactionmessages = [];
1897
1898 if ($resultthirdparty > 0 && !empty($customer)) {
1899 if (!$error) { // Payment was not canceled
1900 $stripecard = null;
1901 if ($companypaymentmode->type == 'ban') {
1902 // Check into societe_rib if a payment mode for Stripe and ban payment exists
1903 // To make a Stripe SEPA payment request, we must have the payment mode source already saved into societe_rib and retrieved with ->sepaStripe
1904 // The payment mode source is created when we create the bank account on Stripe with paymentmodes.php?action=create
1905 $stripecard = $stripe->sepaStripe($customer, $companypaymentmode, $stripeacc, $servicestatus, 0);
1906 } else {
1907 $error++;
1908 $this->error = 'The payment mode type is not "ban"';
1909 }
1910
1911 if ($stripecard) { // Can be src_... (for sepa) or pm_... (new card mode). Note that card_... (old card mode) should not happen here.
1912 $FULLTAG = 'DID='.$did.'-INV=' . $this->id . '-CUS=' . $thirdparty->id;
1913 $description = 'Stripe payment from makeStripeSepaRequest: ' . $FULLTAG . ' did='.$did.' ref=' . $this->ref;
1914
1915 $stripefailurecode = '';
1916 $stripefailuremessage = '';
1917 $stripefailuredeclinecode = '';
1918
1919 // Using new SCA method
1920 dol_syslog("* Create payment on SEPA " . $stripecard->id . ", amounttopay=" . $amounttopay . ", amountstripe=" . $amountstripe . ", FULLTAG=" . $FULLTAG, LOG_DEBUG);
1921
1922 // Create payment intent and charge payment (confirmnow = true)
1923 $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $this, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1, 1, $did);
1924
1925 $charge = new stdClass();
1926
1927 if ($paymentintent->status === 'succeeded' || $paymentintent->status === 'processing') {
1928 $charge->status = 'ok';
1929 $charge->id = $paymentintent->id;
1930 $charge->customer = $customer->id;
1931 } elseif ($paymentintent->status === 'requires_action') {
1932 //paymentintent->status may be => 'requires_action' (no error in such a case)
1933 dol_syslog(formatLogObject($paymentintent), LOG_DEBUG);
1934
1935 $charge->status = 'failed';
1936 $charge->customer = $customer->id;
1937 $charge->failure_code = $stripe->code;
1938 $charge->failure_message = $stripe->error;
1939 $charge->failure_declinecode = $stripe->declinecode;
1940 $stripefailurecode = $stripe->code;
1941 $stripefailuremessage = 'Action required. Contact the support at ';// . $conf->global->SELLYOURSAAS_MAIN_EMAIL;
1942 $stripefailuredeclinecode = $stripe->declinecode;
1943 } else {
1944 dol_syslog(formatLogObject($paymentintent), LOG_DEBUG);
1945
1946 $charge->status = 'failed';
1947 $charge->customer = $customer->id;
1948 $charge->failure_code = $stripe->code;
1949 $charge->failure_message = $stripe->error;
1950 $charge->failure_declinecode = $stripe->declinecode;
1951 $stripefailurecode = $stripe->code;
1952 $stripefailuremessage = $stripe->error;
1953 $stripefailuredeclinecode = $stripe->declinecode;
1954 }
1955
1956 //var_dump("stripefailurecode=".$stripefailurecode." stripefailuremessage=".$stripefailuremessage." stripefailuredeclinecode=".$stripefailuredeclinecode);
1957 //exit;
1958
1959
1960 // Return $charge = array('id'=>'ch_XXXX', 'status'=>'succeeded|pending|failed', 'failure_code'=>, 'failure_message'=>...)
1961 if (empty($charge) || $charge->status == 'failed') {
1962 dol_syslog('Failed to charge payment mode ' . $stripecard->id . ' stripefailurecode=' . $stripefailurecode . ' stripefailuremessage=' . $stripefailuremessage . ' stripefailuredeclinecode=' . $stripefailuredeclinecode, LOG_WARNING);
1963
1964 // Save a stripe payment was in error
1965 $this->stripechargeerror++;
1966
1967 $error++;
1968 $errorforinvoice++;
1969 $errmsg = $langs->trans("FailedToChargeSEPA");
1970 if (!empty($charge)) {
1971 if ($stripefailuredeclinecode == 'authentication_required') {
1972 $errauthenticationmessage = $langs->trans("ErrSCAAuthentication");
1973 $errmsg = $errauthenticationmessage;
1974 } elseif (in_array($stripefailuredeclinecode, ['insufficient_funds', 'generic_decline'])) {
1975 $errmsg .= ': ' . $charge->failure_code;
1976 $errmsg .= ($charge->failure_message ? ' - ' : '') . ' ' . $charge->failure_message;
1977 if (empty($stripefailurecode)) {
1978 $stripefailurecode = $charge->failure_code;
1979 }
1980 if (empty($stripefailuremessage)) {
1981 $stripefailuremessage = $charge->failure_message;
1982 }
1983 } else {
1984 $errmsg .= ': failure_code=' . $charge->failure_code;
1985 $errmsg .= ($charge->failure_message ? ' - ' : '') . ' failure_message=' . $charge->failure_message;
1986 if (empty($stripefailurecode)) {
1987 $stripefailurecode = $charge->failure_code;
1988 }
1989 if (empty($stripefailuremessage)) {
1990 $stripefailuremessage = $charge->failure_message;
1991 }
1992 }
1993 } else {
1994 $errmsg .= ': ' . $stripefailurecode . ' - ' . $stripefailuremessage;
1995 $errmsg .= ($stripefailuredeclinecode ? ' - ' . $stripefailuredeclinecode : '');
1996 }
1997
1998 $description = 'Stripe payment ERROR from makeStripeSepaRequest: ' . $FULLTAG;
1999 $postactionmessages[] = $errmsg . ' (' . $stripearrayofkeys['publishable_key'] . ')';
2000
2001 $this->error = $errmsg;
2002 $this->errors[] = $this->error;
2003 } else {
2004 dol_syslog('Successfuly request '.$type.' '.$stripecard->id);
2005
2006 $postactionmessages[] = 'Success to request '.$type.' (' . $charge->id . ' with ' . $stripearrayofkeys['publishable_key'] . ')';
2007
2008 // Save a stripe payment was done in real life so later we will be able to force a commit on recorded payments
2009 // even if in batch mode (method doTakePaymentStripe), we will always make all action in one transaction with a forced commit.
2010 $this->stripechargedone++;
2011
2012 // Default description used for label of event. Will be overwrite by another value later.
2013 $description = 'Stripe payment request OK (' . $charge->id . ') from makeStripeSepaRequest: ' . $FULLTAG;
2014 }
2015
2016 $object = $this;
2017
2018 // Track an event
2019 if (empty($charge) || $charge->status == 'failed') {
2020 $actioncode = 'PAYMENT_STRIPE_KO';
2021 $extraparams = $stripefailurecode;
2022 $extraparams .= (($extraparams && $stripefailuremessage) ? ' - ' : '') . $stripefailuremessage;
2023 $extraparams .= (($extraparams && $stripefailuredeclinecode) ? ' - ' : '') . $stripefailuredeclinecode;
2024 } else {
2025 $actioncode = 'PAYMENT_STRIPE_OK';
2026 $extraparams = array();
2027 }
2028 } else {
2029 $error++;
2030 $errorforinvoice++;
2031 dol_syslog("No ban payment method found for this stripe customer " . $customer->id, LOG_WARNING);
2032
2033 $this->error = 'Failed to get direct debit payment method for stripe customer = ' . $customer->id;
2034 $this->errors[] = $this->error;
2035
2036 $description = 'Failed to find or use the payment mode - no ban defined for the thirdparty account';
2037 $stripefailurecode = 'BADPAYMENTMODE';
2038 $stripefailuremessage = 'Failed to find or use the payment mode - no ban defined for the thirdparty account';
2039 $postactionmessages[] = $description . ' (' . $stripearrayofkeys['publishable_key'] . ')';
2040
2041 $object = $this;
2042
2043 $actioncode = 'PAYMENT_STRIPE_KO';
2044 $extraparams = array();
2045 }
2046 } else {
2047 // If error because payment was canceled for a logical reason, we do nothing (no event added)
2048 $description = '';
2049 $stripefailurecode = '';
2050 $stripefailuremessage = '';
2051
2052 $object = $this;
2053
2054 $actioncode = '';
2055 $extraparams = array();
2056 }
2057 } else { // Else of the if ($resultthirdparty > 0 && ! empty($customer)) {
2058 if ($resultthirdparty <= 0) {
2059 dol_syslog('SellYourSaasUtils Failed to load customer for thirdparty_id = ' . $thirdparty->id, LOG_WARNING);
2060 $this->error = 'Failed to load Stripe account for thirdparty_id = ' . $thirdparty->id;
2061 $this->errors[] = $this->error;
2062 } else { // $customer stripe not found
2063 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);
2064 $this->error = 'Failed to get Stripe account id for thirdparty_id = ' . $thirdparty->id . " in mode " . $servicestatus . " in Stripe env " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key'];
2065 $this->errors[] = $this->error;
2066 }
2067 $error++;
2068 $errorforinvoice++;
2069
2070 $description = 'Failed to find or use your payment mode (no payment mode for this customer id)';
2071 $stripefailurecode = 'BADPAYMENTMODE';
2072 $stripefailuremessage = 'Failed to find or use your payment mode (no payment mode for this customer id)';
2073 $postactionmessages = [];
2074
2075 $object = $this;
2076
2077 $actioncode = 'PAYMENT_STRIPE_KO';
2078 $extraparams = array();
2079 }
2080
2081 if ($description) {
2082 dol_syslog("* Record event for credit transfer or direct debit request result - " . $description);
2083 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
2084
2085 // Insert record of payment (success or error)
2086 $actioncomm = new ActionComm($this->db);
2087
2088 $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
2089 $actioncomm->code = 'AC_' . $actioncode;
2090 $actioncomm->label = $description;
2091 $actioncomm->note_private = implode(",\n", $postactionmessages);
2092 $actioncomm->fk_project = $this->fk_project;
2093 $actioncomm->datep = $now;
2094 $actioncomm->datef = $now;
2095 $actioncomm->percentage = -1; // Not applicable
2096 $actioncomm->socid = $thirdparty->id;
2097 $actioncomm->contactid = 0;
2098 $actioncomm->authorid = $user->id; // User saving action
2099 $actioncomm->userownerid = $user->id; // Owner of action
2100 // Fields when action is a real email (content is already into note)
2101 /*$actioncomm->email_msgid = $object->email_msgid;
2102 $actioncomm->email_from = $object->email_from;
2103 $actioncomm->email_sender= $object->email_sender;
2104 $actioncomm->email_to = $object->email_to;
2105 $actioncomm->email_tocc = $object->email_tocc;
2106 $actioncomm->email_tobcc = $object->email_tobcc;
2107 $actioncomm->email_subject = $object->email_subject;
2108 $actioncomm->errors_to = $object->errors_to;*/
2109 $actioncomm->fk_element = $this->id;
2110 $actioncomm->elementid = $this->id;
2111 $actioncomm->elementtype = $this->element;
2112 $actioncomm->extraparams = $extraparams; // Can be null, empty string or array()
2113
2114 $actioncomm->create($user);
2115 }
2116
2117 $this->description = $description;
2118 $this->postactionmessages = $postactionmessages;
2119 } catch (Exception $e) {
2120 $error++;
2121 $errorforinvoice++;
2122 dol_syslog('Error ' . $e->getMessage(), LOG_ERR);
2123 $this->error = 'Error ' . $e->getMessage();
2124 $this->errors[] = $this->error;
2125 }
2126 } else { // If remain to pay is null
2127 $error++;
2128 $errorforinvoice++;
2129 dol_syslog("Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?", LOG_WARNING);
2130 $this->error = "Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?";
2131 $this->errors[] = $this->error;
2132 }
2133 }
2134
2135 // Set status of the order to "Transferred" with method 'api'
2136 if (!$error && !$errorforinvoice) {
2137 $result = $bon->set_infotrans($user, $now, 3);
2138 if ($result < 0) {
2139 $error++;
2140 $errorforinvoice++;
2141 dol_syslog("Error on BonPrelevement creation", LOG_ERR);
2142 $this->error = "Error on BonPrelevement creation";
2143 $this->errors[] = $this->error;
2144 }
2145 }
2146
2147 if (!$error && !$errorforinvoice && $paymentintent !== null) {
2148 // Update the direct debit payment request of the processed invoice to save the id of the prelevement_bon
2149 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
2150 $sql .= " ext_payment_id = '".$this->db->escape($paymentintent->id)."',";
2151 $sql .= " ext_payment_site = '".$this->db->escape($service)."'";
2152 $sql .= " WHERE rowid = ".((int) $did);
2153
2154 dol_syslog(get_class($this)."::makeStripeSepaRequest update to save stripe paymentintent ids", LOG_DEBUG);
2155 $resql = $this->db->query($sql);
2156 if (!$resql) {
2157 $this->error = $this->db->lasterror();
2158 dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur');
2159 $error++;
2160 }
2161 }
2162
2163 if (!$error && !$errorforinvoice) {
2164 $this->db->commit();
2165 } else {
2166 $this->db->rollback();
2167 }
2168 } else {
2169 $this->error = 'WithdrawRequestErrorNilAmount';
2170 dol_syslog(get_class($this).'::makeStripeSepaRequest WithdrawRequestErrorNilAmount');
2171 $error++;
2172 }
2173
2174 /*
2175 if (!$error) {
2176 // Force payment mode of the invoice to withdraw
2177 $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
2178 if ($payment_mode_id > 0) {
2179 $result = $this->setPaymentMethods($payment_mode_id);
2180 }
2181 }*/
2182
2183 if ($error) {
2184 return -1;
2185 }
2186 return 1;
2187 } else {
2188 $this->error = $this->db->error();
2189 dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur -2');
2190 return -2;
2191 }
2192 } else {
2193 $this->error = "Status of invoice does not allow this";
2194 dol_syslog(get_class($this)."::makeStripeSepaRequest ".$this->error." ".$this->status." ,".$this->paye.", ".$this->mode_reglement_id, LOG_WARNING);
2195 return -3;
2196 }
2197 }
2198
2199 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2207 public function demande_prelevement_delete($fuser, $did)
2208 {
2209 // phpcs:enable
2210 $sql = 'DELETE FROM '.$this->db->prefix().'prelevement_demande';
2211 $sql .= ' WHERE rowid = '.((int) $did);
2212 $sql .= ' AND traite = 0';
2213 if ($this->db->query($sql)) {
2214 return 0;
2215 } else {
2216 $this->error = $this->db->lasterror();
2217 dol_syslog(get_class($this).'::demande_prelevement_delete Error '.$this->error);
2218 return -1;
2219 }
2220 }
2221
2227 public function buildEPCQrCodeString()
2228 {
2229 global $mysoc;
2230
2231 // Get the amount to pay
2232 $amount_to_pay = $this->getRemainToPay();
2233
2234 // Prevent negative values (e.g. overpayments)
2235 $amount_to_pay = max(0, $amount_to_pay);
2236
2237 // Ensure numeric formatting for EPC QR code
2238 $amount_to_pay = price2num($amount_to_pay, 'MT');
2239
2240 // Initialize an array to hold the lines of the QR code
2241 $lines = array();
2242
2243 // Add the standard elements to the QR code
2244 $lines = [
2245 'BCD', // Service Tag (optional)
2246 '002', // Version (optional)
2247 '1', // Character set (optional)
2248 'SCT', // Identification (optional)
2249 ];
2250
2251 // Add the bank account information
2252 include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
2253
2254 $idofbankaccountouse = $this->fk_account;
2255 if (empty($idofbankaccountouse)) {
2256 $idofbankaccountouse = $this->fk_bank; // for backward compatibility
2257 }
2258 if (empty($idofbankaccountouse)) {
2259 $idofbankaccountouse = getDolGlobalInt('FACTURE_RIB_NUMBER');
2260 }
2261
2262 if ($idofbankaccountouse > 0) {
2263 $bankAccount = new Account($this->db);
2264 $bankAccount->fetch($idofbankaccountouse);
2265 $lines[] = $bankAccount->bic; //BIC (required)
2266 if (!empty($bankAccount->owner_name)) {
2267 $lines[] = $bankAccount->owner_name; //Owner of the bank account, if present (required)
2268 } else {
2269 $lines[] = $mysoc->name; //Name (required)
2270 }
2271 $lines[] = $bankAccount->iban; //IBAN (required)
2272 } else {
2273 $lines[] = ""; //BIC (required)
2274 $lines[] = $mysoc->name; //Name (required)
2275 $lines[] = ""; //IBAN (required)
2276 }
2277
2278 // Add the amount and reference
2279 $lines[] = 'EUR' . $amount_to_pay; // Amount (optional)
2280 $lines[] = ''; // Purpose (optional)
2281 $lines[] = ''; // Payment reference (optional)
2282 $lines[] = $this->ref; // Remittance Information (optional)
2283
2284 // Join the lines with newline characters and return the result
2285 return implode("\n", $lines);
2286 }
2292 public function buildZATCAQRString()
2293 {
2294 global $conf, $mysoc;
2295
2296 $tmplang = new Translate('', $conf);
2297 $tmplang->setDefaultLang('en_US');
2298 $tmplang->load("main");
2299
2300 $datestring = dol_print_date($this->date, 'dayhourrfc');
2301 //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
2302 //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
2303 $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
2304 $pricetaxstring = price2num($this->total_tva, 2, 1);
2305
2306 /*
2307 $name = implode(unpack("H*", $this->thirdparty->name));
2308 $vatnumber = implode(unpack("H*", $this->thirdparty->tva_intra));
2309 $date = implode(unpack("H*", $datestring));
2310 $pricewithtax = implode(unpack("H*", price2num($pricewithtaxstring, 2)));
2311 $pricetax = implode(unpack("H*", $pricetaxstring));
2312
2313 //var_dump(strlen($this->thirdparty->name));
2314 //var_dump(str_pad(dechex('9'), 2, '0', STR_PAD_LEFT));
2315 //var_dump($this->thirdparty->name);
2316 //var_dump(implode(unpack("H*", $this->thirdparty->name)));
2317 //var_dump(price($this->total_tva, 0, $tmplang, 0, -1, 2));
2318
2319 $s = '01'.str_pad(dechex(strlen($this->thirdparty->name)), 2, '0', STR_PAD_LEFT).$name;
2320 $s .= '02'.str_pad(dechex(strlen($this->thirdparty->tva_intra)), 2, '0', STR_PAD_LEFT).$vatnumber;
2321 $s .= '03'.str_pad(dechex(strlen($datestring)), 2, '0', STR_PAD_LEFT).$date;
2322 $s .= '04'.str_pad(dechex(strlen($pricewithtaxstring)), 2, '0', STR_PAD_LEFT).$pricewithtax;
2323 $s .= '05'.str_pad(dechex(strlen($pricetaxstring)), 2, '0', STR_PAD_LEFT).$pricetax;
2324 $s .= ''; // Hash of xml invoice
2325 $s .= ''; // ecda signature
2326 $s .= ''; // ecda public key
2327 $s .= ''; // ecda signature of public key stamp
2328 */
2329 $mysocname = $mysoc->name ?? '';
2330 $mysoctva_intra = $mysoc->tva_intra ?? '';
2331 // Using TLV format
2332 $s = pack('C1', 1).pack('C1', strlen($mysocname)).$mysocname;
2333 $s .= pack('C1', 2).pack('C1', strlen($mysoctva_intra)).$mysoctva_intra;
2334 $s .= pack('C1', 3).pack('C1', strlen($datestring)).$datestring;
2335 $s .= pack('C1', 4).pack('C1', strlen($pricewithtaxstring)).$pricewithtaxstring;
2336 $s .= pack('C1', 5).pack('C1', strlen($pricetaxstring)).$pricetaxstring;
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 $s = base64_encode($s);
2343
2344 return $s;
2345 }
2346
2347
2354 {
2355 global $conf, $mysoc;
2356
2357 $tmplang = new Translate('', $conf);
2358 $tmplang->setDefaultLang('en_US');
2359 $tmplang->load("main");
2360
2361 $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
2362 $pricetaxstring = price2num($this->total_tva, 2, 1);
2363
2364 $complementaryinfo = '';
2365 /*
2366 Example: //S1/10/10201409/11/190512/20/1400.000-53/30/106017086/31/180508/32/7.7/40/2:10;0:30
2367 /10/ Numéro de facture – 10201409
2368 /11/ Date de facture – 12.05.2019
2369 /20/ Référence client – 1400.000-53
2370 /30/ Numéro IDE pour la TVA – CHE-106.017.086 TVA
2371 /31/ Date de la prestation pour la comptabilisation de la TVA – 08.05.2018
2372 /32/ Taux de TVA sur le montant total de la facture – 7.7%
2373 /40/ Conditions – 2% d’escompte à 10 jours, paiement net à 30 jours
2374 */
2375 $datestring = dol_print_date($this->date, '%y%m%d');
2376 //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
2377 //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
2378 $complementaryinfo = '//S1/10/'.str_replace('/', '', $this->ref).'/11/'.$datestring;
2379 if ($this->ref_client) {
2380 $complementaryinfo .= '/20/'.$this->ref_client;
2381 }
2382 if ($this->thirdparty->tva_intra) {
2383 $complementaryinfo .= '/30/'.$this->thirdparty->tva_intra;
2384 }
2385
2386 include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
2387 $bankaccount = new Account($this->db);
2388
2389 // Header
2390 $s = '';
2391 $s .= "SPC\n";
2392 $s .= "0200\n";
2393 $s .= "1\n";
2394 // Info Seller ("Compte / Payable à")
2395 if ($this->fk_account > 0) {
2396 // Bank BAN if country is LI or CH. TODO Add a test to check than IBAN start with CH or LI
2397 $bankaccount->fetch($this->fk_account);
2398 $s .= $bankaccount->iban."\n";
2399 } else {
2400 $s .= "\n";
2401 }
2402 if ($bankaccount->id > 0 && getDolGlobalString('PDF_SWISS_QRCODE_USE_OWNER_OF_ACCOUNT_AS_CREDITOR')) {
2403 // If a bank account is provided and we ask to use it as creditor, we use the bank address
2404 // 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 ?
2405 $s .= "S\n";
2406 $s .= dol_trunc($bankaccount->owner_name, 70, 'right', 'UTF-8', 1)."\n";
2407 $addresslinearray = explode("\n", $bankaccount->owner_address);
2408 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
2409 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
2410 /*$s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
2411 $s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
2412 $s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";*/
2413 } else {
2414 $s .= "S\n";
2415 $s .= dol_trunc((string) $mysoc->name, 70, 'right', 'UTF-8', 1)."\n";
2416 $addresslinearray = explode("\n", $mysoc->address);
2417 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
2418 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
2419 $s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
2420 $s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
2421 $s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";
2422 }
2423 // Final seller (Ultimate seller) ("Créancier final" = "En faveur de")
2424 $s .= "\n";
2425 $s .= "\n";
2426 $s .= "\n";
2427 $s .= "\n";
2428 $s .= "\n";
2429 $s .= "\n";
2430 $s .= "\n";
2431 // Amount of payment (to do?)
2432 $s .= price($pricewithtaxstring, 0, 'none', 0, 0, 2)."\n";
2433 $s .= ($this->multicurrency_code ? $this->multicurrency_code : $conf->currency)."\n";
2434 // Buyer
2435 $s .= "S\n";
2436 $s .= dol_trunc((string) $this->thirdparty->name, 70, 'right', 'UTF-8', 1)."\n";
2437 $addresslinearray = explode("\n", $this->thirdparty->address);
2438 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
2439 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
2440 $s .= dol_trunc($this->thirdparty->zip, 16, 'right', 'UTF-8', 1)."\n";
2441 $s .= dol_trunc($this->thirdparty->town, 35, 'right', 'UTF-8', 1)."\n";
2442 $s .= dol_trunc($this->thirdparty->country_code, 2, 'right', 'UTF-8', 1)."\n";
2443 // ID of payment
2444 $s .= "NON\n"; // NON or QRR
2445 $s .= "\n"; // QR Code reference if previous field is QRR
2446 // Free text
2447 if ($complementaryinfo) {
2448 $s .= $complementaryinfo."\n";
2449 } else {
2450 $s .= "\n";
2451 }
2452 $s .= "EPD\n";
2453 // More text, complementary info
2454 if ($complementaryinfo) {
2455 $s .= $complementaryinfo."\n";
2456 }
2457 $s .= "\n";
2458 //var_dump($s);exit;
2459 return $s;
2460 }
2461}
2462
2463
2464
2465require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
2466
2471{
2477 public $label;
2478
2484 public $ref; // Product ref (deprecated)
2490 public $libelle; // Product label (deprecated)
2491
2496 public $product_type = 0;
2497
2502 public $product_ref;
2503
2508 public $product_label;
2509
2514 public $product_desc;
2515
2520 public $qty;
2521
2526 public $subprice;
2527
2533 public $price;
2534
2539 public $fk_product;
2540
2545 public $vat_src_code;
2546
2551 public $tva_tx;
2552
2557 public $localtax1_tx;
2558
2563 public $localtax2_tx;
2564
2570 public $localtax1_type;
2571
2577 public $localtax2_type;
2578
2583 public $remise_percent;
2584
2590 public $remise;
2591
2596 public $total_ht;
2597
2602 public $total_tva;
2603
2608 public $total_localtax1;
2609
2614 public $total_localtax2;
2615
2620 public $total_ttc;
2621
2625 public $revenuestamp;
2626
2627
2631 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
2635 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
2636
2640 public $buy_price_ht;
2645 public $buyprice;
2650 public $pa_ht;
2651
2655 public $marge_tx;
2659 public $marque_tx;
2660
2667 public $info_bits = 0;
2668
2677 public $special_code = 0;
2678
2683 public $fk_user_author;
2684
2689 public $fk_user_modif;
2690
2694 public $fk_code_ventilation;
2695
2699 public $situation_percent = 100;
2700
2706 public function isDepositLine()
2707 {
2708 // Do not take into account lines of the type "deposit."
2709 $reg = array();
2710 if (preg_match('/^\‍((.*)\‍)$/', $this->desc, $reg)) {
2711 if ($reg[1] == 'DEPOSIT') {
2712 return true;
2713 }
2714 }
2715 return false;
2716 }
2717}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
isALNERunningVersion($blockedlogtestalreadydone=0, $blockedlogmodulealreadydone=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.
Parent class of all other business classes (invoices, contracts, proposals, orders,...
fetch_thirdparty($force_thirdparty_id=0)
Load the third party of object, from id $this->socid or $this->fk_soc, into this->thirdparty.
setPaymentMethods($id)
Change the payments methods.
Parent class for class inheritance lines of business objects This class is useless for the moment so ...
Class to manage bank accounts description of third parties.
Class for CompanyPaymentMode.
Class to manage absolute discounts.
Class to manage invoices.
Class to manage third parties objects (customers, suppliers, prospects...)
Stripe class @TODO No reason to extend CommonObject.
Class to manage translations.
Class to manage Dolibarr users.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:168
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_print_error($db=null, $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '…' if string larger than length.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
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:487
if(preg_match('/(crypted|dolcrypt):/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition repair.php:130