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