dolibarr 18.0.6
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 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
26require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
27require_once DOL_DOCUMENT_ROOT.'/core/class/commonincoterm.class.php';
28
32abstract class CommonInvoice extends CommonObject
33{
35
39 public $type = self::TYPE_STANDARD;
40
44 public $subtype;
45
49 const TYPE_STANDARD = 0;
50
55
60
64 const TYPE_DEPOSIT = 3;
65
70 const TYPE_PROFORMA = 4;
71
75 const TYPE_SITUATION = 5;
76
80 const STATUS_DRAFT = 0;
81
86
94 const STATUS_CLOSED = 2;
95
104
105
106 public $totalpaid; // duplicate with sumpayed
107 public $totaldeposits; // duplicate with sumdeposit
108 public $totalcreditnotes; // duplicate with sumcreditnote
109
110 public $sumpayed;
111 public $sumpayed_multicurrency;
112 public $sumdeposit;
113 public $sumdeposit_multicurrency;
114 public $sumcreditnote;
115 public $sumcreditnote_multicurrency;
116 public $remaintopay;
117
118
126 public function getRemainToPay($multicurrency = 0)
127 {
128 $alreadypaid = 0.0;
129 $alreadypaid += $this->getSommePaiement($multicurrency);
130 $alreadypaid += $this->getSumDepositsUsed($multicurrency);
131 $alreadypaid += $this->getSumCreditNotesUsed($multicurrency);
132
133 $remaintopay = price2num($this->total_ttc - $alreadypaid, 'MT');
134 if ($this->statut == self::STATUS_CLOSED && $this->close_code == 'discount_vat') { // If invoice closed with discount for anticipated payment
135 $remaintopay = 0.0;
136 }
137 return $remaintopay;
138 }
139
147 public function getSommePaiement($multicurrency = 0)
148 {
149 $table = 'paiement_facture';
150 $field = 'fk_facture';
151 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
152 $table = 'paiementfourn_facturefourn';
153 $field = 'fk_facturefourn';
154 }
155
156 $sql = "SELECT sum(amount) as amount, sum(multicurrency_amount) as multicurrency_amount";
157 $sql .= " FROM ".$this->db->prefix().$table;
158 $sql .= " WHERE ".$field." = ".((int) $this->id);
159
160 dol_syslog(get_class($this)."::getSommePaiement", LOG_DEBUG);
161
162 $resql = $this->db->query($sql);
163 if ($resql) {
164 $obj = $this->db->fetch_object($resql);
165
166 $this->db->free($resql);
167
168 if ($obj) {
169 if ($multicurrency < 0) {
170 $this->sumpayed = $obj->amount;
171 $this->sumpayed_multicurrency = $obj->multicurrency_amount;
172 return array('alreadypaid'=>(float) $obj->amount, 'alreadypaid_multicurrency'=>(float) $obj->multicurrency_amount);
173 } elseif ($multicurrency) {
174 $this->sumpayed_multicurrency = $obj->multicurrency_amount;
175 return (float) $obj->multicurrency_amount;
176 } else {
177 $this->sumpayed = $obj->amount;
178 return (float) $obj->amount;
179 }
180 } else {
181 return 0;
182 }
183 } else {
184 $this->error = $this->db->lasterror();
185 return -1;
186 }
187 }
188
197 public function getSumDepositsUsed($multicurrency = 0)
198 {
199 /*if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
200 // 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.
201 return 0.0;
202 }*/
203
204 require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
205
206 $discountstatic = new DiscountAbsolute($this->db);
207 $result = $discountstatic->getSumDepositsUsed($this, $multicurrency);
208
209 if ($result >= 0) {
210 if ($multicurrency) {
211 $this->sumdeposit_multicurrency = $result;
212 } else {
213 $this->sumdeposit = $result;
214 }
215
216 return $result;
217 } else {
218 $this->error = $discountstatic->error;
219 return -1;
220 }
221 }
222
229 public function getSumCreditNotesUsed($multicurrency = 0)
230 {
231 require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
232
233 $discountstatic = new DiscountAbsolute($this->db);
234 $result = $discountstatic->getSumCreditNotesUsed($this, $multicurrency);
235 if ($result >= 0) {
236 if ($multicurrency) {
237 $this->sumcreditnote_multicurrency = $result;
238 } else {
239 $this->sumcreditnote = $result;
240 }
241
242 return $result;
243 } else {
244 $this->error = $discountstatic->error;
245 return -1;
246 }
247 }
248
255 public function getSumFromThisCreditNotesNotUsed($multicurrency = 0)
256 {
257 require_once DOL_DOCUMENT_ROOT.'/core/class/discount.class.php';
258
259 $discountstatic = new DiscountAbsolute($this->db);
260 $result = $discountstatic->getSumFromThisCreditNotesNotUsed($this, $multicurrency);
261 if ($result >= 0) {
262 return $result;
263 } else {
264 $this->error = $discountstatic->error;
265 return -1;
266 }
267 }
268
275 {
276 $idarray = array();
277
278 $sql = "SELECT rowid";
279 $sql .= " FROM ".$this->db->prefix().$this->table_element;
280 $sql .= " WHERE fk_facture_source = ".((int) $this->id);
281 $sql .= " AND type = 2";
282 $resql = $this->db->query($sql);
283 if ($resql) {
284 $num = $this->db->num_rows($resql);
285 $i = 0;
286 while ($i < $num) {
287 $row = $this->db->fetch_row($resql);
288 $idarray[] = $row[0];
289 $i++;
290 }
291 } else {
292 dol_print_error($this->db);
293 }
294 return $idarray;
295 }
296
303 public function getIdReplacingInvoice($option = '')
304 {
305 $sql = "SELECT rowid";
306 $sql .= " FROM ".$this->db->prefix().$this->table_element;
307 $sql .= " WHERE fk_facture_source = ".((int) $this->id);
308 $sql .= " AND type < 2";
309 if ($option == 'validated') {
310 $sql .= ' AND fk_statut = 1';
311 }
312 // PROTECTION BAD DATA
313 // In case the database is corrupted and there is a valid replectement invoice
314 // and another no, priority is given to the valid one.
315 // Should not happen (unless concurrent access and 2 people have created a
316 // replacement invoice for the same invoice at the same time)
317 $sql .= " ORDER BY fk_statut DESC";
318
319 $resql = $this->db->query($sql);
320 if ($resql) {
321 $obj = $this->db->fetch_object($resql);
322 if ($obj) {
323 // If there is any
324 return $obj->rowid;
325 } else {
326 // If no invoice replaces it
327 return 0;
328 }
329 } else {
330 return -1;
331 }
332 }
333
340 public function getListOfPayments($filtertype = '')
341 {
342 $retarray = array();
343
344 $table = 'paiement_facture';
345 $table2 = 'paiement';
346 $field = 'fk_facture';
347 $field2 = 'fk_paiement';
348 $field3 = ', p.ref_ext';
349 $field4 = ', p.fk_bank'; // Bank line id
350 $sharedentity = 'facture';
351 if ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
352 $table = 'paiementfourn_facturefourn';
353 $table2 = 'paiementfourn';
354 $field = 'fk_facturefourn';
355 $field2 = 'fk_paiementfourn';
356 $field3 = '';
357 $sharedentity = 'facture_fourn';
358 }
359
360 $sql = "SELECT p.ref, pf.amount, pf.multicurrency_amount, p.fk_paiement, p.datep, p.num_paiement as num, t.code".$field3 . $field4;
361 $sql .= " FROM ".$this->db->prefix().$table." as pf, ".$this->db->prefix().$table2." as p, ".$this->db->prefix()."c_paiement as t";
362 $sql .= " WHERE pf.".$field." = ".((int) $this->id);
363 $sql .= " AND pf.".$field2." = p.rowid";
364 $sql .= ' AND p.fk_paiement = t.id';
365 $sql .= ' AND p.entity IN ('.getEntity($sharedentity).')';
366 if ($filtertype) {
367 $sql .= " AND t.code='PRE'";
368 }
369
370 dol_syslog(get_class($this)."::getListOfPayments", LOG_DEBUG);
371 $resql = $this->db->query($sql);
372 if ($resql) {
373 $num = $this->db->num_rows($resql);
374 $i = 0;
375 while ($i < $num) {
376 $obj = $this->db->fetch_object($resql);
377 $tmp = array('amount'=>$obj->amount, 'type'=>$obj->code, 'date'=>$obj->datep, 'num'=>$obj->num, 'ref'=>$obj->ref);
378 if (!empty($field3)) {
379 $tmp['ref_ext'] = $obj->ref_ext;
380 }
381 if (!empty($field4)) {
382 $tmp['fk_bank_line'] = $obj->fk_bank;
383 }
384 $retarray[] = $tmp;
385 $i++;
386 }
387 $this->db->free($resql);
388
389 //look for credit notes and discounts and deposits
390 $sql = '';
391 if ($this->element == 'facture' || $this->element == 'invoice') {
392 $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";
393 $sql .= ' FROM '.$this->db->prefix().'societe_remise_except as rc, '.$this->db->prefix().'facture as f';
394 $sql .= ' WHERE rc.fk_facture_source=f.rowid AND rc.fk_facture = '.((int) $this->id);
395 $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)
396 } elseif ($this->element == 'facture_fourn' || $this->element == 'invoice_supplier') {
397 $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";
398 $sql .= ' FROM '.$this->db->prefix().'societe_remise_except as rc, '.$this->db->prefix().'facture_fourn as f';
399 $sql .= ' WHERE rc.fk_invoice_supplier_source=f.rowid AND rc.fk_invoice_supplier = '.((int) $this->id);
400 $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)
401 }
402
403 if ($sql) {
404 $resql = $this->db->query($sql);
405 if ($resql) {
406 $num = $this->db->num_rows($resql);
407 $i = 0;
408 while ($i < $num) {
409 $obj = $this->db->fetch_object($resql);
410 if ($multicurrency) {
411 $retarray[] = array('amount'=>$obj->multicurrency_amount, 'type'=>$obj->type, 'date'=>$obj->date, 'num'=>'0', 'ref'=>$obj->ref);
412 } else {
413 $retarray[] = array('amount'=>$obj->amount, 'type'=>$obj->type, 'date'=>$obj->date, 'num'=>'', 'ref'=>$obj->ref);
414 }
415 $i++;
416 }
417 } else {
418 $this->error = $this->db->lasterror();
419 dol_print_error($this->db);
420 return array();
421 }
422 $this->db->free($resql);
423 }
424
425 return $retarray;
426 } else {
427 $this->error = $this->db->lasterror();
428 dol_print_error($this->db);
429 return array();
430 }
431 }
432
433
434 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
448 public function is_erasable()
449 {
450 // phpcs:enable
451 global $conf;
452
453 // We check if invoice is a temporary number (PROVxxxx)
454 $tmppart = substr($this->ref, 1, 4);
455
456 if ($this->statut == self::STATUS_DRAFT && $tmppart === 'PROV') { // If draft invoice and ref not yet defined
457 return 1;
458 }
459
460 if (!empty($conf->global->INVOICE_CAN_NEVER_BE_REMOVED)) {
461 return 0;
462 }
463
464 // If not a draft invoice and not temporary invoice
465 if ($tmppart !== 'PROV') {
466 $ventilExportCompta = $this->getVentilExportCompta();
467 if ($ventilExportCompta != 0) {
468 return -1;
469 }
470
471 // Get last number of validated invoice
472 if ($this->element != 'invoice_supplier') {
473 if (empty($this->thirdparty)) {
474 $this->fetch_thirdparty(); // We need to have this->thirdparty defined, in case of numbering rule use tags that depend on thirdparty (like {t} tag).
475 }
476 $maxref = $this->getNextNumRef($this->thirdparty, 'last');
477
478 // If there is no invoice into the reset range and not already dispatched, we can delete
479 // If invoice to delete is last one and not already dispatched, we can delete
480 if (empty($conf->global->INVOICE_CAN_ALWAYS_BE_REMOVED) && $maxref != '' && $maxref != $this->ref) {
481 return -2;
482 }
483
484 // TODO If there is payment in bookkeeping, check payment is not dispatched in accounting
485 // ...
486
487 if ($this->situation_cycle_ref && method_exists($this, 'is_last_in_cycle')) {
488 $last = $this->is_last_in_cycle();
489 if (!$last) {
490 return -3;
491 }
492 }
493 }
494 }
495
496 // Test if there is at least one payment. If yes, refuse to delete.
497 if (empty($conf->global->INVOICE_CAN_ALWAYS_BE_REMOVED) && $this->getSommePaiement() > 0) {
498 return -4;
499 }
500
501 return 2;
502 }
503
509 public function getVentilExportCompta()
510 {
511 $alreadydispatched = 0;
512
513 $type = 'customer_invoice';
514 if ($this->element == 'invoice_supplier') {
515 $type = 'supplier_invoice';
516 }
517
518 $sql = " SELECT COUNT(ab.rowid) as nb FROM ".$this->db->prefix()."accounting_bookkeeping as ab WHERE ab.doc_type='".$this->db->escape($type)."' AND ab.fk_doc = ".((int) $this->id);
519 $resql = $this->db->query($sql);
520 if ($resql) {
521 $obj = $this->db->fetch_object($resql);
522 if ($obj) {
523 $alreadydispatched = $obj->nb;
524 }
525 } else {
526 $this->error = $this->db->lasterror();
527 return -1;
528 }
529
530 if ($alreadydispatched) {
531 return 1;
532 }
533 return 0;
534 }
535
536
543 public function getLibType($withbadge = 0)
544 {
545 global $langs;
546
547 $labellong = "Unknown";
548 if ($this->type == CommonInvoice::TYPE_STANDARD) {
549 $labellong = "InvoiceStandard";
550 $labelshort = "InvoiceStandardShort";
551 } elseif ($this->type == CommonInvoice::TYPE_REPLACEMENT) {
552 $labellong = "InvoiceReplacement";
553 $labelshort = "InvoiceReplacementShort";
554 } elseif ($this->type == CommonInvoice::TYPE_CREDIT_NOTE) {
555 $labellong = "InvoiceAvoir";
556 $labelshort = "CreditNote";
557 } elseif ($this->type == CommonInvoice::TYPE_DEPOSIT) {
558 $labellong = "InvoiceDeposit";
559 $labelshort = "Deposit";
560 } elseif ($this->type == CommonInvoice::TYPE_PROFORMA) {
561 $labellong = "InvoiceProForma"; // Not used.
562 $labelshort = "ProForma";
563 } elseif ($this->type == CommonInvoice::TYPE_SITUATION) {
564 $labellong = "InvoiceSituation";
565 $labelshort = "Situation";
566 }
567
568 $out = '';
569 if ($withbadge) {
570 $out .= '<span class="badgeneutral" title="'.dol_escape_htmltag($langs->trans($labellong)).'">';
571 }
572 $out .= $langs->trans($withbadge == 2 ? $labelshort : $labellong);
573 if ($withbadge) {
574 $out .= '</span>';
575 }
576 return $out;
577 }
578
586 public function getLibStatut($mode = 0, $alreadypaid = -1)
587 {
588 return $this->LibStatut($this->paye, $this->statut, $mode, $alreadypaid, $this->type);
589 }
590
591 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
602 public function LibStatut($paye, $status, $mode = 0, $alreadypaid = -1, $type = -1)
603 {
604 // phpcs:enable
605 global $langs, $hookmanager;
606 $langs->load('bills');
607
608 if ($type == -1) {
609 $type = $this->type;
610 }
611
612 $statusType = 'status0';
613 $prefix = 'Short';
614 if (!$paye) {
615 if ($status == 0) {
616 $labelStatus = $langs->transnoentitiesnoconv('BillStatusDraft');
617 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusDraft');
618 } elseif (($status == 3 || $status == 2) && $alreadypaid <= 0) {
619 if ($status == 3) {
620 $labelStatus = $langs->transnoentitiesnoconv('BillStatusCanceled');
621 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusCanceled');
622 } else {
623 $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedUnpaid');
624 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedUnpaid');
625 }
626 $statusType = 'status5';
627 } elseif (($status == 3 || $status == 2) && $alreadypaid > 0) {
628 $labelStatus = $langs->transnoentitiesnoconv('BillStatusClosedPaidPartially');
629 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusClosedPaidPartially');
630 $statusType = 'status9';
631 } elseif ($alreadypaid == 0) {
632 $labelStatus = $langs->transnoentitiesnoconv('BillStatusNotPaid');
633 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusNotPaid');
634 $statusType = 'status1';
635 } else {
636 $labelStatus = $langs->transnoentitiesnoconv('BillStatusStarted');
637 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusStarted');
638 $statusType = 'status3';
639 }
640 } else {
641 $statusType = 'status6';
642
643 if ($type == self::TYPE_CREDIT_NOTE) {
644 $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaidBackOrConverted'); // credit note
645 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaidBackOrConverted'); // credit note
646 } elseif ($type == self::TYPE_DEPOSIT) {
647 $labelStatus = $langs->transnoentitiesnoconv('BillStatusConverted'); // deposit invoice
648 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusConverted'); // deposit invoice
649 } else {
650 $labelStatus = $langs->transnoentitiesnoconv('BillStatusPaid');
651 $labelStatusShort = $langs->transnoentitiesnoconv('Bill'.$prefix.'StatusPaid');
652 }
653 }
654
655 $parameters = array(
656 'status' => $status,
657 'mode' => $mode,
658 'paye' => $paye,
659 'alreadypaid' => $alreadypaid,
660 'type' => $type
661 );
662
663 $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this); // Note that $action and $object may have been modified by hook
664
665 if ($reshook > 0) {
666 return $hookmanager->resPrint;
667 }
668
669
670
671 return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode);
672 }
673
674 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
682 public function calculate_date_lim_reglement($cond_reglement = 0)
683 {
684 // phpcs:enable
685 if (!$cond_reglement) {
686 $cond_reglement = $this->cond_reglement_code;
687 }
688 if (!$cond_reglement) {
689 $cond_reglement = $this->cond_reglement_id;
690 }
691 if (!$cond_reglement) {
692 return $this->date;
693 }
694
695 $cdr_nbjour = 0;
696 $cdr_type = 0;
697 $cdr_decalage = 0;
698
699 $sqltemp = "SELECT c.type_cdr, c.nbjour, c.decalage";
700 $sqltemp .= " FROM ".$this->db->prefix()."c_payment_term as c";
701 if (is_numeric($cond_reglement)) {
702 $sqltemp .= " WHERE c.rowid=".((int) $cond_reglement);
703 } else {
704 $sqltemp .= " WHERE c.entity IN (".getEntity('c_payment_term').")";
705 $sqltemp .= " AND c.code = '".$this->db->escape($cond_reglement)."'";
706 }
707
708 dol_syslog(get_class($this).'::calculate_date_lim_reglement', LOG_DEBUG);
709 $resqltemp = $this->db->query($sqltemp);
710 if ($resqltemp) {
711 if ($this->db->num_rows($resqltemp)) {
712 $obj = $this->db->fetch_object($resqltemp);
713 $cdr_nbjour = $obj->nbjour;
714 $cdr_type = $obj->type_cdr;
715 $cdr_decalage = $obj->decalage;
716 }
717 } else {
718 $this->error = $this->db->error();
719 return -1;
720 }
721 $this->db->free($resqltemp);
722
723 /* Definition de la date limite */
724
725 // 0 : adding the number of days
726 if ($cdr_type == 0) {
727 $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
728
729 $datelim += ($cdr_decalage * 3600 * 24);
730 } elseif ($cdr_type == 1) {
731 // 1 : application of the "end of the month" rule
732 $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
733
734 $mois = date('m', $datelim);
735 $annee = date('Y', $datelim);
736 if ($mois == 12) {
737 $mois = 1;
738 $annee += 1;
739 } else {
740 $mois += 1;
741 }
742 // We move at the beginning of the next month, and we take a day off
743 $datelim = dol_mktime(12, 0, 0, $mois, 1, $annee);
744 $datelim -= (3600 * 24);
745
746 $datelim += ($cdr_decalage * 3600 * 24);
747 } elseif ($cdr_type == 2 && !empty($cdr_decalage)) {
748 // 2 : application of the rule, the N of the current or next month
749 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
750 $datelim = $this->date + ($cdr_nbjour * 3600 * 24);
751
752 $date_piece = dol_mktime(0, 0, 0, date('m', $datelim), date('d', $datelim), date('Y', $datelim)); // Sans les heures minutes et secondes
753 $date_lim_current = dol_mktime(0, 0, 0, date('m', $datelim), $cdr_decalage, date('Y', $datelim)); // Sans les heures minutes et secondes
754 $date_lim_next = dol_time_plus_duree($date_lim_current, 1, 'm'); // Add 1 month
755
756 $diff = $date_piece - $date_lim_current;
757
758 if ($diff < 0) {
759 $datelim = $date_lim_current;
760 } else {
761 $datelim = $date_lim_next;
762 }
763 } else {
764 return 'Bad value for type_cdr in database for record cond_reglement = '.$cond_reglement;
765 }
766
767 return $datelim;
768 }
769
770 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
782 public function demande_prelevement($fuser, $amount = 0, $type = 'direct-debit', $sourcetype = 'facture', $checkduplicateamongall = 0)
783 {
784 // phpcs:enable
785 global $conf;
786
787 $error = 0;
788
789 dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
790
791 if ($this->statut > self::STATUS_DRAFT && $this->paye == 0) {
792 require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
793 $bac = new CompanyBankAccount($this->db);
794 $bac->fetch(0, $this->socid);
795
796 $sql = "SELECT count(rowid) as nb";
797 $sql .= " FROM ".$this->db->prefix()."prelevement_demande";
798 if ($type == 'bank-transfer') {
799 $sql .= " WHERE fk_facture_fourn = ".((int) $this->id);
800 } else {
801 $sql .= " WHERE fk_facture = ".((int) $this->id);
802 }
803 $sql .= " AND type = 'ban'"; // To exclude record done for some online payments
804 if (empty($checkduplicateamongall)) {
805 $sql .= " AND traite = 0";
806 }
807
808 dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
809
810 $resql = $this->db->query($sql);
811 if ($resql) {
812 $obj = $this->db->fetch_object($resql);
813 if ($obj && $obj->nb == 0) { // If no request found yet
814 $now = dol_now();
815
816 $totalpaid = $this->getSommePaiement();
817 $totalcreditnotes = $this->getSumCreditNotesUsed();
818 $totaldeposits = $this->getSumDepositsUsed();
819 //print "totalpaid=".$totalpaid." totalcreditnotes=".$totalcreditnotes." totaldeposts=".$totaldeposits;
820
821 // We can also use bcadd to avoid pb with floating points
822 // For example print 239.2 - 229.3 - 9.9; does not return 0.
823 //$resteapayer=bcadd($this->total_ttc,$totalpaid,$conf->global->MAIN_MAX_DECIMALS_TOT);
824 //$resteapayer=bcadd($resteapayer,$totalavoir,$conf->global->MAIN_MAX_DECIMALS_TOT);
825 if (empty($amount)) {
826 $amount = price2num($this->total_ttc - $totalpaid - $totalcreditnotes - $totaldeposits, 'MT');
827 }
828
829 if (is_numeric($amount) && $amount != 0) {
830 $sql = 'INSERT INTO '.$this->db->prefix().'prelevement_demande(';
831 if ($type == 'bank-transfer') {
832 $sql .= 'fk_facture_fourn, ';
833 } else {
834 $sql .= 'fk_facture, ';
835 }
836 $sql .= ' amount, date_demande, fk_user_demande, code_banque, code_guichet, number, cle_rib, sourcetype, type, entity)';
837 $sql .= " VALUES (".((int) $this->id);
838 $sql .= ", ".((float) price2num($amount));
839 $sql .= ", '".$this->db->idate($now)."'";
840 $sql .= ", ".((int) $fuser->id);
841 $sql .= ", '".$this->db->escape($bac->code_banque)."'";
842 $sql .= ", '".$this->db->escape($bac->code_guichet)."'";
843 $sql .= ", '".$this->db->escape($bac->number)."'";
844 $sql .= ", '".$this->db->escape($bac->cle_rib)."'";
845 $sql .= ", '".$this->db->escape($sourcetype)."'";
846 $sql .= ", 'ban'";
847 $sql .= ", ".((int) $conf->entity);
848 $sql .= ")";
849
850 dol_syslog(get_class($this)."::demande_prelevement", LOG_DEBUG);
851 $resql = $this->db->query($sql);
852 if (!$resql) {
853 $this->error = $this->db->lasterror();
854 dol_syslog(get_class($this).'::demandeprelevement Erreur');
855 $error++;
856 }
857 } else {
858 $this->error = 'WithdrawRequestErrorNilAmount';
859 dol_syslog(get_class($this).'::demandeprelevement WithdrawRequestErrorNilAmount');
860 $error++;
861 }
862
863 if (!$error) {
864 // Force payment mode of invoice to withdraw
865 $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
866 if ($payment_mode_id > 0) {
867 $result = $this->setPaymentMethods($payment_mode_id);
868 }
869 }
870
871 if ($error) {
872 return -1;
873 }
874 return 1;
875 } else {
876 $this->error = "A request already exists";
877 dol_syslog(get_class($this).'::demandeprelevement Impossible de creer une demande, demande deja en cours');
878 return 0;
879 }
880 } else {
881 $this->error = $this->db->error();
882 dol_syslog(get_class($this).'::demandeprelevement Erreur -2');
883 return -2;
884 }
885 } else {
886 $this->error = "Status of invoice does not allow this";
887 dol_syslog(get_class($this)."::demandeprelevement ".$this->error." $this->statut, $this->paye, $this->mode_reglement_id");
888 return -3;
889 }
890 }
891
892
902 public function makeStripeSepaRequest($fuser, $did = 0, $type = 'direct-debit', $sourcetype = 'facture')
903 {
904 global $conf, $mysoc, $user, $langs;
905
906 if ($type != 'bank-transfer' && $type != 'credit-transfer' && empty($conf->global->STRIPE_SEPA_DIRECT_DEBIT)) {
907 return 0;
908 }
909 if ($type != 'direct-debit' && empty($conf->global->STRIPE_SEPA_CREDIT_TRANSFER)) {
910 return 0;
911 }
912
913 $error = 0;
914
915 dol_syslog(get_class($this)."::makeStripeSepaRequest start", LOG_DEBUG);
916
917 if ($this->statut > self::STATUS_DRAFT && $this->paye == 0) {
918 // Get the default payment mode for BAN payment of the third party
919 require_once DOL_DOCUMENT_ROOT.'/societe/class/companybankaccount.class.php';
920 $bac = new CompanyBankAccount($this->db); // table societe_rib
921 $result = $bac->fetch(0, $this->socid, 1, 'ban');
922 if ($result <= 0 || empty($bac->id)) {
923 $this->error = $langs->trans("ThirdpartyHasNoDefaultBanAccount");
924 $this->errors[] = $this->error;
925 dol_syslog(get_class($this)."::makeStripeSepaRequest ".$this->error);
926 return -1;
927 }
928
929 // Load the request to process
930 $sql = "SELECT rowid, date_demande, amount, fk_facture, fk_facture_fourn, fk_prelevement_bons";
931 $sql .= " FROM ".$this->db->prefix()."prelevement_demande";
932 $sql .= " WHERE rowid = ".((int) $did);
933 if ($type != 'bank-transfer' && $type != 'credit-transfer') {
934 $sql .= " AND fk_facture = ".((int) $this->id); // Add a protection to not pay another invoice than current one
935 }
936 if ($type != 'direct-debit') {
937 $sql .= " AND fk_facture_fourn = ".((int) $this->id); // Add a protection to not pay another invoice than current one
938 }
939 $sql .= " AND traite = 0"; // Add a protection to not process twice
940
941 dol_syslog(get_class($this)."::makeStripeSepaRequest load requests to process", LOG_DEBUG);
942 $resql = $this->db->query($sql);
943 if ($resql) {
944 $obj = $this->db->fetch_object($resql);
945 if (!$obj) {
946 dol_print_error($this->db, 'CantFindRequestWithId');
947 return -2;
948 }
949
950 // amount to pay
951 $amount = $obj->amount;
952
953 if (is_numeric($amount) && $amount != 0) {
954 require_once DOL_DOCUMENT_ROOT.'/societe/class/companypaymentmode.class.php';
955 $companypaymentmode = new CompanyPaymentMode($this->db); // table societe_rib
956 $companypaymentmode->fetch($bac->id);
957
958 // Start code for Stripe
959 // TODO We may have this coming as a parameter from the caller.
960 $service = 'StripeTest';
961 $servicestatus = 0;
962 if (!empty($conf->global->STRIPE_LIVE) && !GETPOST('forcesandbox', 'alpha')) {
963 $service = 'StripeLive';
964 $servicestatus = 1;
965 }
966
967 dol_syslog("makeStripeSepaRequest amount = ".$amount." service=" . $service . " servicestatus=" . $servicestatus . " thirdparty_id=" . $this->socid." did=".$did);
968
969 $this->stripechargedone = 0;
970 $this->stripechargeerror = 0;
971
972 $now = dol_now();
973
974 $currency = $conf->currency;
975
976 global $stripearrayofkeysbyenv;
977
978 $errorforinvoice = 0; // We reset the $errorforinvoice at each invoice loop
979
980 $this->fetch_thirdparty();
981
982 dol_syslog("--- Process payment request thirdparty_id=" . $this->thirdparty->id . ", thirdparty_name=" . $this->thirdparty->name . " ban id=" . $bac->id, LOG_DEBUG);
983
984 //$alreadypayed = $this->getSommePaiement();
985 //$amount_credit_notes_included = $this->getSumCreditNotesUsed();
986 //$amounttopay = $this->total_ttc - $alreadypayed - $amount_credit_notes_included;
987 $amounttopay = $amount;
988
989 // Correct the amount according to unit of currency
990 // See https://support.stripe.com/questions/which-zero-decimal-currencies-does-stripe-support
991 $arrayzerounitcurrency = ['BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 'MGA', 'PYG', 'RWF', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'];
992 $amountstripe = $amounttopay;
993 if (!in_array($currency, $arrayzerounitcurrency)) {
994 $amountstripe = $amountstripe * 100;
995 }
996
997 $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.
998 if (!($fk_bank_account > 0)) {
999 $error++;
1000 $errorforinvoice++;
1001 dol_syslog("Error no bank account defined for Stripe payments", LOG_ERR);
1002 $this->errors[] = "Error bank account for Stripe payments not defined into Stripe module";
1003 }
1004
1005 $this->db->begin();
1006
1007 // Create a prelevement_bon
1008 require_once DOL_DOCUMENT_ROOT.'/compta/prelevement/class/bonprelevement.class.php';
1009 $bon = new BonPrelevement($this->db);
1010 if (!$error) {
1011 if (empty($obj->fk_prelevement_bons)) {
1012 // This create record into llx_prelevment_bons and update link with llx_prelevement_demande
1013 $nbinvoices = $bon->create(0, 0, 'real', 'ALL', '', 0, $type, $did, $fk_bank_account);
1014 if ($nbinvoices <= 0) {
1015 $error++;
1016 $errorforinvoice++;
1017 dol_syslog("Error on BonPrelevement creation", LOG_ERR);
1018 $this->errors[] = "Error on BonPrelevement creation";
1019 }
1020 /*
1021 if (!$error) {
1022 // Update the direct debit payment request of the processed request to save the id of the prelevement_bon
1023 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
1024 $sql .= " fk_prelevement_bons = ".((int) $bon->id);
1025 $sql .= " WHERE rowid = ".((int) $did);
1026
1027 $result = $this->db->query($sql);
1028 if ($result < 0) {
1029 $error++;
1030 $this->errors[] = "Error on updateing fk_prelevement_bons to ".$bon->id;
1031 }
1032 }
1033 */
1034 } else {
1035 $error++;
1036 $errorforinvoice++;
1037 dol_syslog("Error Line already part of a bank payment order", LOG_ERR);
1038 $this->errors[] = "The line is already included into a bank payment order. Delete the bank payment order first.";
1039 }
1040 }
1041
1042 if (!$error) {
1043 if ($amountstripe > 0) {
1044 try {
1045 //var_dump($companypaymentmode);
1046 dol_syslog("We will try to pay with companypaymentmodeid=" . $companypaymentmode->id . " stripe_card_ref=" . $companypaymentmode->stripe_card_ref . " mode=" . $companypaymentmode->status, LOG_DEBUG);
1047
1048 $thirdparty = new Societe($this->db);
1049 $resultthirdparty = $thirdparty->fetch($this->socid);
1050
1051 include_once DOL_DOCUMENT_ROOT . '/stripe/class/stripe.class.php'; // This include the include of htdocs/stripe/config.php
1052 // So it inits or erases the $stripearrayofkeysbyenv
1053 $stripe = new Stripe($this->db);
1054
1055 dol_syslog("makeStripeSepaRequest Current Stripe environment is " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key']);
1056
1057 $stripearrayofkeys = $stripearrayofkeysbyenv[$servicestatus];
1058 \Stripe\Stripe::setApiKey($stripearrayofkeys['secret_key']);
1059
1060
1061 dol_syslog("makeStripeSepaRequest get stripe connet account", LOG_DEBUG);
1062 $stripeacc = $stripe->getStripeAccount($service, $this->socid); // Get Stripe OAuth connect account if it exists (no network access here)
1063 dol_syslog("makeStripeSepaRequest get stripe connect account return " . json_encode($stripeacc), LOG_DEBUG);
1064
1065 $customer = $stripe->customerStripe($thirdparty, $stripeacc, $servicestatus, 0);
1066 if (empty($customer) && !empty($stripe->error)) {
1067 $this->errors[] = $stripe->error;
1068 }
1069
1070 // $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)
1071 // $nbdaysbeforeendoftries = (empty($conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES) ? 35 : $conf->global->SELLYOURSAAS_NBDAYSBEFOREENDOFTRIES);
1072 $postactionmessages = [];
1073
1074 if ($resultthirdparty > 0 && !empty($customer)) {
1075 if (!$error) { // Payment was not canceled
1076 $stripecard = null;
1077 if ($companypaymentmode->type == 'ban') {
1078 // Check into societe_rib if a payment mode for Stripe and ban payment exists
1079 // To make a Stripe SEPA payment request, we must have the payment mode source already saved into societe_rib and retreived with ->sepaStripe
1080 // The payment mode source is created when we create the bank account on Stripe with paymentmodes.php?action=create
1081 $stripecard = $stripe->sepaStripe($customer, $companypaymentmode, $stripeacc, $servicestatus, 0);
1082 } else {
1083 $error++;
1084 $this->error = 'The payment mode type is not "ban"';
1085 }
1086
1087 if ($stripecard) { // Can be src_... (for sepa) or pm_... (new card mode). Note that card_... (old card mode) should not happen here.
1088 $FULLTAG = 'DID='.$did.'-INV=' . $this->id . '-CUS=' . $thirdparty->id;
1089 $description = 'Stripe payment from makeStripeSepaRequest: ' . $FULLTAG . ' did='.$did.' ref=' . $this->ref;
1090
1091 $stripefailurecode = '';
1092 $stripefailuremessage = '';
1093 $stripefailuredeclinecode = '';
1094
1095 // Using new SCA method
1096 dol_syslog("* Create payment on SEPA " . $stripecard->id . ", amounttopay=" . $amounttopay . ", amountstripe=" . $amountstripe . ", FULLTAG=" . $FULLTAG, LOG_DEBUG);
1097
1098 // Create payment intent and charge payment (confirmnow = true)
1099 $paymentintent = $stripe->getPaymentIntent($amounttopay, $currency, $FULLTAG, $description, $this, $customer->id, $stripeacc, $servicestatus, 0, 'automatic', true, $stripecard->id, 1, 1, $did);
1100
1101 $charge = new stdClass();
1102
1103 if ($paymentintent->status === 'succeeded' || $paymentintent->status === 'processing') {
1104 $charge->status = 'ok';
1105 $charge->id = $paymentintent->id;
1106 $charge->customer = $customer->id;
1107 } elseif ($paymentintent->status === 'requires_action') {
1108 //paymentintent->status may be => 'requires_action' (no error in such a case)
1109 dol_syslog(var_export($paymentintent, true), LOG_DEBUG);
1110
1111 $charge->status = 'failed';
1112 $charge->customer = $customer->id;
1113 $charge->failure_code = $stripe->code;
1114 $charge->failure_message = $stripe->error;
1115 $charge->failure_declinecode = $stripe->declinecode;
1116 $stripefailurecode = $stripe->code;
1117 $stripefailuremessage = 'Action required. Contact the support at ';// . $conf->global->SELLYOURSAAS_MAIN_EMAIL;
1118 $stripefailuredeclinecode = $stripe->declinecode;
1119 } else {
1120 dol_syslog(var_export($paymentintent, true), LOG_DEBUG);
1121
1122 $charge->status = 'failed';
1123 $charge->customer = $customer->id;
1124 $charge->failure_code = $stripe->code;
1125 $charge->failure_message = $stripe->error;
1126 $charge->failure_declinecode = $stripe->declinecode;
1127 $stripefailurecode = $stripe->code;
1128 $stripefailuremessage = $stripe->error;
1129 $stripefailuredeclinecode = $stripe->declinecode;
1130 }
1131
1132 //var_dump("stripefailurecode=".$stripefailurecode." stripefailuremessage=".$stripefailuremessage." stripefailuredeclinecode=".$stripefailuredeclinecode);
1133 //exit;
1134
1135
1136 // Return $charge = array('id'=>'ch_XXXX', 'status'=>'succeeded|pending|failed', 'failure_code'=>, 'failure_message'=>...)
1137 if (empty($charge) || $charge->status == 'failed') {
1138 dol_syslog('Failed to charge payment mode ' . $stripecard->id . ' stripefailurecode=' . $stripefailurecode . ' stripefailuremessage=' . $stripefailuremessage . ' stripefailuredeclinecode=' . $stripefailuredeclinecode, LOG_WARNING);
1139
1140 // Save a stripe payment was in error
1141 $this->stripechargeerror++;
1142
1143 $error++;
1144 $errorforinvoice++;
1145 $errmsg = $langs->trans("FailedToChargeCard");
1146 if (!empty($charge)) {
1147 if ($stripefailuredeclinecode == 'authentication_required') {
1148 $errauthenticationmessage = $langs->trans("ErrSCAAuthentication");
1149 $errmsg = $errauthenticationmessage;
1150 } elseif (in_array($stripefailuredeclinecode, ['insufficient_funds', 'generic_decline'])) {
1151 $errmsg .= ': ' . $charge->failure_code;
1152 $errmsg .= ($charge->failure_message ? ' - ' : '') . ' ' . $charge->failure_message;
1153 if (empty($stripefailurecode)) {
1154 $stripefailurecode = $charge->failure_code;
1155 }
1156 if (empty($stripefailuremessage)) {
1157 $stripefailuremessage = $charge->failure_message;
1158 }
1159 } else {
1160 $errmsg .= ': failure_code=' . $charge->failure_code;
1161 $errmsg .= ($charge->failure_message ? ' - ' : '') . ' failure_message=' . $charge->failure_message;
1162 if (empty($stripefailurecode)) {
1163 $stripefailurecode = $charge->failure_code;
1164 }
1165 if (empty($stripefailuremessage)) {
1166 $stripefailuremessage = $charge->failure_message;
1167 }
1168 }
1169 } else {
1170 $errmsg .= ': ' . $stripefailurecode . ' - ' . $stripefailuremessage;
1171 $errmsg .= ($stripefailuredeclinecode ? ' - ' . $stripefailuredeclinecode : '');
1172 }
1173
1174 $description = 'Stripe payment ERROR from makeStripeSepaRequest: ' . $FULLTAG;
1175 $postactionmessages[] = $errmsg . ' (' . $stripearrayofkeys['publishable_key'] . ')';
1176 $this->errors[] = $errmsg;
1177 } else {
1178 dol_syslog('Successfuly request '.$type.' '.$stripecard->id);
1179
1180 $postactionmessages[] = 'Success to request '.$type.' (' . $charge->id . ' with ' . $stripearrayofkeys['publishable_key'] . ')';
1181
1182 // Save a stripe payment was done in realy life so later we will be able to force a commit on recorded payments
1183 // even if in batch mode (method doTakePaymentStripe), we will always make all action in one transaction with a forced commit.
1184 $this->stripechargedone++;
1185
1186 // Default description used for label of event. Will be overwrite by another value later.
1187 $description = 'Stripe payment request OK (' . $charge->id . ') from makeStripeSepaRequest: ' . $FULLTAG;
1188 }
1189
1190 $object = $this;
1191
1192 // Track an event
1193 if (empty($charge) || $charge->status == 'failed') {
1194 $actioncode = 'PAYMENT_STRIPE_KO';
1195 $extraparams = $stripefailurecode;
1196 $extraparams .= (($extraparams && $stripefailuremessage) ? ' - ' : '') . $stripefailuremessage;
1197 $extraparams .= (($extraparams && $stripefailuredeclinecode) ? ' - ' : '') . $stripefailuredeclinecode;
1198 } else {
1199 $actioncode = 'PAYMENT_STRIPE_OK';
1200 $extraparams = '';
1201 }
1202 } else {
1203 $error++;
1204 $errorforinvoice++;
1205 dol_syslog("No ban payment method found for this stripe customer " . $customer->id, LOG_WARNING);
1206 $this->errors[] = 'Failed to get direct debit payment method for stripe customer = ' . $customer->id;
1207
1208 $description = 'Failed to find or use the payment mode - no ban defined for the thirdparty account';
1209 $stripefailurecode = 'BADPAYMENTMODE';
1210 $stripefailuremessage = 'Failed to find or use the payment mode - no ban defined for the thirdparty account';
1211 $postactionmessages[] = $description . ' (' . $stripearrayofkeys['publishable_key'] . ')';
1212
1213 $object = $this;
1214
1215 $actioncode = 'PAYMENT_STRIPE_KO';
1216 $extraparams = '';
1217 }
1218 } else {
1219 // If error because payment was canceled for a logical reason, we do nothing (no event added)
1220 $description = '';
1221 $stripefailurecode = '';
1222 $stripefailuremessage = '';
1223
1224 $object = $this;
1225
1226 $actioncode = '';
1227 $extraparams = '';
1228 }
1229 } else { // Else of the if ($resultthirdparty > 0 && ! empty($customer)) {
1230 if ($resultthirdparty <= 0) {
1231 dol_syslog('SellYourSaasUtils Failed to load customer for thirdparty_id = ' . $thirdparty->id, LOG_WARNING);
1232 $this->errors[] = 'Failed to load Stripe account for thirdparty_id = ' . $thirdparty->id;
1233 } else { // $customer stripe not found
1234 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);
1235 $this->errors[] = 'Failed to get Stripe account id for thirdparty_id = ' . $thirdparty->id . " in mode " . $servicestatus . " in Stripe env " . $stripearrayofkeysbyenv[$servicestatus]['publishable_key'];
1236 }
1237 $error++;
1238 $errorforinvoice++;
1239
1240 $description = 'Failed to find or use your payment mode (no payment mode for this customer id)';
1241 $stripefailurecode = 'BADPAYMENTMODE';
1242 $stripefailuremessage = 'Failed to find or use your payment mode (no payment mode for this customer id)';
1243 $postactionmessages = [];
1244
1245 $object = $this;
1246
1247 $actioncode = 'PAYMENT_STRIPE_KO';
1248 $extraparams = '';
1249 }
1250
1251 if ($description) {
1252 dol_syslog("* Record event for credit transfer or direct debit request result - " . $description);
1253 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
1254
1255 // Insert record of payment (success or error)
1256 $actioncomm = new ActionComm($this->db);
1257
1258 $actioncomm->type_code = 'AC_OTH_AUTO'; // Type of event ('AC_OTH', 'AC_OTH_AUTO', 'AC_XXX'...)
1259 $actioncomm->code = 'AC_' . $actioncode;
1260 $actioncomm->label = $description;
1261 $actioncomm->note_private = join(",\n", $postactionmessages);
1262 $actioncomm->fk_project = $this->fk_project;
1263 $actioncomm->datep = $now;
1264 $actioncomm->datef = $now;
1265 $actioncomm->percentage = -1; // Not applicable
1266 $actioncomm->socid = $thirdparty->id;
1267 $actioncomm->contactid = 0;
1268 $actioncomm->authorid = $user->id; // User saving action
1269 $actioncomm->userownerid = $user->id; // Owner of action
1270 // Fields when action is a real email (content is already into note)
1271 /*$actioncomm->email_msgid = $object->email_msgid;
1272 $actioncomm->email_from = $object->email_from;
1273 $actioncomm->email_sender= $object->email_sender;
1274 $actioncomm->email_to = $object->email_to;
1275 $actioncomm->email_tocc = $object->email_tocc;
1276 $actioncomm->email_tobcc = $object->email_tobcc;
1277 $actioncomm->email_subject = $object->email_subject;
1278 $actioncomm->errors_to = $object->errors_to;*/
1279 $actioncomm->fk_element = $this->id;
1280 $actioncomm->elementtype = $this->element;
1281 $actioncomm->extraparams = dol_trunc($extraparams, 250);
1282
1283 $actioncomm->create($user);
1284 }
1285
1286 $this->description = $description;
1287 $this->postactionmessages = $postactionmessages;
1288 } catch (Exception $e) {
1289 $error++;
1290 $errorforinvoice++;
1291 dol_syslog('Error ' . $e->getMessage(), LOG_ERR);
1292 $this->errors[] = 'Error ' . $e->getMessage();
1293 }
1294 } else { // If remain to pay is null
1295 $error++;
1296 $errorforinvoice++;
1297 dol_syslog("Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?", LOG_WARNING);
1298 $this->errors[] = "Remain to pay is null for the invoice " . $this->id . " " . $this->ref . ". Why is the invoice not classified 'Paid' ?";
1299 }
1300 }
1301
1302 // Set status of the order to "Transferred" with method 'api'
1303 if (!$error && !$errorforinvoice) {
1304 $result = $bon->set_infotrans($user, $now, 3);
1305 if ($result < 0) {
1306 $error++;
1307 $errorforinvoice++;
1308 dol_syslog("Error on BonPrelevement creation", LOG_ERR);
1309 $this->errors[] = "Error on BonPrelevement creation";
1310 }
1311 }
1312
1313 if (!$error && !$errorforinvoice) {
1314 // Update the direct debit payment request of the processed invoice to save the id of the prelevement_bon
1315 $sql = "UPDATE ".MAIN_DB_PREFIX."prelevement_demande SET";
1316 $sql .= " ext_payment_id = '".$this->db->escape($paymentintent->id)."',";
1317 $sql .= " ext_payment_site = '".$this->db->escape($service)."'";
1318 $sql .= " WHERE rowid = ".((int) $did);
1319
1320 dol_syslog(get_class($this)."::makeStripeSepaRequest update to save stripe paymentintent ids", LOG_DEBUG);
1321 $resql = $this->db->query($sql);
1322 if (!$resql) {
1323 $this->error = $this->db->lasterror();
1324 dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur');
1325 $error++;
1326 }
1327 }
1328
1329 if (!$error && !$errorforinvoice) {
1330 $this->db->commit();
1331 } else {
1332 $this->db->rollback();
1333 }
1334 } else {
1335 $this->error = 'WithdrawRequestErrorNilAmount';
1336 dol_syslog(get_class($this).'::makeStripeSepaRequest WithdrawRequestErrorNilAmount');
1337 $error++;
1338 }
1339
1340 /*
1341 if (!$error) {
1342 // Force payment mode of the invoice to withdraw
1343 $payment_mode_id = dol_getIdFromCode($this->db, ($type == 'bank-transfer' ? 'VIR' : 'PRE'), 'c_paiement', 'code', 'id', 1);
1344 if ($payment_mode_id > 0) {
1345 $result = $this->setPaymentMethods($payment_mode_id);
1346 }
1347 }*/
1348
1349 if ($error) {
1350 return -1;
1351 }
1352 return 1;
1353 } else {
1354 $this->error = $this->db->error();
1355 dol_syslog(get_class($this).'::makeStripeSepaRequest Erreur -2');
1356 return -2;
1357 }
1358 } else {
1359 $this->error = "Status of invoice does not allow this";
1360 dol_syslog(get_class($this)."::makeStripeSepaRequest ".$this->error." $this->statut, $this->paye, $this->mode_reglement_id");
1361 return -3;
1362 }
1363 }
1364
1365 // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1373 public function demande_prelevement_delete($fuser, $did)
1374 {
1375 // phpcs:enable
1376 $sql = 'DELETE FROM '.$this->db->prefix().'prelevement_demande';
1377 $sql .= ' WHERE rowid = '.((int) $did);
1378 $sql .= ' AND traite = 0';
1379 if ($this->db->query($sql)) {
1380 return 0;
1381 } else {
1382 $this->error = $this->db->lasterror();
1383 dol_syslog(get_class($this).'::demande_prelevement_delete Error '.$this->error);
1384 return -1;
1385 }
1386 }
1387
1388
1394 public function buildZATCAQRString()
1395 {
1396 global $conf, $mysoc;
1397
1398 $tmplang = new Translate('', $conf);
1399 $tmplang->setDefaultLang('en_US');
1400 $tmplang->load("main");
1401
1402 $datestring = dol_print_date($this->date, 'dayhourrfc');
1403 //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
1404 //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
1405 $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
1406 $pricetaxstring = price2num($this->total_tva, 2, 1);
1407
1408 /*
1409 $name = implode(unpack("H*", $this->thirdparty->name));
1410 $vatnumber = implode(unpack("H*", $this->thirdparty->tva_intra));
1411 $date = implode(unpack("H*", $datestring));
1412 $pricewithtax = implode(unpack("H*", price2num($pricewithtaxstring, 2)));
1413 $pricetax = implode(unpack("H*", $pricetaxstring));
1414
1415 //var_dump(strlen($this->thirdparty->name));
1416 //var_dump(str_pad(dechex('9'), 2, '0', STR_PAD_LEFT));
1417 //var_dump($this->thirdparty->name);
1418 //var_dump(implode(unpack("H*", $this->thirdparty->name)));
1419 //var_dump(price($this->total_tva, 0, $tmplang, 0, -1, 2));
1420
1421 $s = '01'.str_pad(dechex(strlen($this->thirdparty->name)), 2, '0', STR_PAD_LEFT).$name;
1422 $s .= '02'.str_pad(dechex(strlen($this->thirdparty->tva_intra)), 2, '0', STR_PAD_LEFT).$vatnumber;
1423 $s .= '03'.str_pad(dechex(strlen($datestring)), 2, '0', STR_PAD_LEFT).$date;
1424 $s .= '04'.str_pad(dechex(strlen($pricewithtaxstring)), 2, '0', STR_PAD_LEFT).$pricewithtax;
1425 $s .= '05'.str_pad(dechex(strlen($pricetaxstring)), 2, '0', STR_PAD_LEFT).$pricetax;
1426 $s .= ''; // Hash of xml invoice
1427 $s .= ''; // ecda signature
1428 $s .= ''; // ecda public key
1429 $s .= ''; // ecda signature of public key stamp
1430 */
1431
1432 // Using TLV format
1433 $s = pack('C1', 1).pack('C1', strlen($mysoc->name)).$mysoc->name;
1434 $s .= pack('C1', 2).pack('C1', strlen($mysoc->tva_intra)).$mysoc->tva_intra;
1435 $s .= pack('C1', 3).pack('C1', strlen($datestring)).$datestring;
1436 $s .= pack('C1', 4).pack('C1', strlen($pricewithtaxstring)).$pricewithtaxstring;
1437 $s .= pack('C1', 5).pack('C1', strlen($pricetaxstring)).$pricetaxstring;
1438 $s .= ''; // Hash of xml invoice
1439 $s .= ''; // ecda signature
1440 $s .= ''; // ecda public key
1441 $s .= ''; // ecda signature of public key stamp
1442
1443 $s = base64_encode($s);
1444
1445 return $s;
1446 }
1447
1448
1455 {
1456 global $conf, $mysoc;
1457
1458 $tmplang = new Translate('', $conf);
1459 $tmplang->setDefaultLang('en_US');
1460 $tmplang->load("main");
1461
1462 $pricewithtaxstring = price2num($this->total_ttc, 2, 1);
1463 $pricetaxstring = price2num($this->total_tva, 2, 1);
1464
1465 $complementaryinfo = '';
1466 /*
1467 Example: //S1/10/10201409/11/190512/20/1400.000-53/30/106017086/31/180508/32/7.7/40/2:10;0:30
1468 /10/ Numéro de facture – 10201409
1469 /11/ Date de facture – 12.05.2019
1470 /20/ Référence client – 1400.000-53
1471 /30/ Numéro IDE pour la TVA – CHE-106.017.086 TVA
1472 /31/ Date de la prestation pour la comptabilisation de la TVA – 08.05.2018
1473 /32/ Taux de TVA sur le montant total de la facture – 7.7%
1474 /40/ Conditions – 2% d’escompte à 10 jours, paiement net à 30 jours
1475 */
1476 $datestring = dol_print_date($this->date, '%y%m%d');
1477 //$pricewithtaxstring = price($this->total_ttc, 0, $tmplang, 0, -1, 2);
1478 //$pricetaxstring = price($this->total_tva, 0, $tmplang, 0, -1, 2);
1479 $complementaryinfo = '//S1/10/'.str_replace('/', '', $this->ref).'/11/'.$datestring;
1480 if ($this->ref_client) {
1481 $complementaryinfo .= '/20/'.$this->ref_client;
1482 }
1483 if ($this->thirdparty->tva_intra) {
1484 $complementaryinfo .= '/30/'.$this->thirdparty->tva_intra;
1485 }
1486
1487 include_once DOL_DOCUMENT_ROOT.'/compta/bank/class/account.class.php';
1488 $bankaccount = new Account($this->db);
1489
1490 // Header
1491 $s = '';
1492 $s .= "SPC\n";
1493 $s .= "0200\n";
1494 $s .= "1\n";
1495 // Info Seller ("Compte / Payable à")
1496 if ($this->fk_account > 0) {
1497 // Bank BAN if country is LI or CH. TODO Add a test to check than IBAN start with CH or LI
1498 $bankaccount->fetch($this->fk_account);
1499 $s .= $bankaccount->iban."\n";
1500 } else {
1501 $s .= "\n";
1502 }
1503 if ($bankaccount->id > 0 && getDolGlobalString('PDF_SWISS_QRCODE_USE_OWNER_OF_ACCOUNT_AS_CREDITOR')) {
1504 // If a bank account is prodived and we ask to use it as creditor, we use the bank address
1505 // 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 ?
1506 $s .= "S\n";
1507 $s .= dol_trunc($bankaccount->proprio, 70, 'right', 'UTF-8', 1)."\n";
1508 $addresslinearray = explode("\n", $bankaccount->owner_address);
1509 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
1510 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
1511 /*$s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
1512 $s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
1513 $s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";*/
1514 } else {
1515 $s .= "S\n";
1516 $s .= dol_trunc($mysoc->name, 70, 'right', 'UTF-8', 1)."\n";
1517 $addresslinearray = explode("\n", $mysoc->address);
1518 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
1519 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
1520 $s .= dol_trunc($mysoc->zip, 16, 'right', 'UTF-8', 1)."\n";
1521 $s .= dol_trunc($mysoc->town, 35, 'right', 'UTF-8', 1)."\n";
1522 $s .= dol_trunc($mysoc->country_code, 2, 'right', 'UTF-8', 1)."\n";
1523 }
1524 // Final seller (Ultimate seller) ("Créancier final" = "En faveur de")
1525 $s .= "\n";
1526 $s .= "\n";
1527 $s .= "\n";
1528 $s .= "\n";
1529 $s .= "\n";
1530 $s .= "\n";
1531 $s .= "\n";
1532 // Amount of payment (to do?)
1533 $s .= price($pricewithtaxstring, 0, 'none', 0, 0, 2)."\n";
1534 $s .= ($this->multicurrency_code ? $this->multicurrency_code : $conf->currency)."\n";
1535 // Buyer
1536 $s .= "S\n";
1537 $s .= dol_trunc($this->thirdparty->name, 70, 'right', 'UTF-8', 1)."\n";
1538 $addresslinearray = explode("\n", $this->thirdparty->address);
1539 $s .= dol_trunc(empty($addresslinearray[1]) ? '' : $addresslinearray[1], 70, 'right', 'UTF-8', 1)."\n"; // address line 1
1540 $s .= dol_trunc(empty($addresslinearray[2]) ? '' : $addresslinearray[2], 70, 'right', 'UTF-8', 1)."\n"; // address line 2
1541 $s .= dol_trunc($this->thirdparty->zip, 16, 'right', 'UTF-8', 1)."\n";
1542 $s .= dol_trunc($this->thirdparty->town, 35, 'right', 'UTF-8', 1)."\n";
1543 $s .= dol_trunc($this->thirdparty->country_code, 2, 'right', 'UTF-8', 1)."\n";
1544 // ID of payment
1545 $s .= "NON\n"; // NON or QRR
1546 $s .= "\n"; // QR Code reference if previous field is QRR
1547 // Free text
1548 if ($complementaryinfo) {
1549 $s .= $complementaryinfo."\n";
1550 } else {
1551 $s .= "\n";
1552 }
1553 $s .= "EPD\n";
1554 // More text, complementary info
1555 if ($complementaryinfo) {
1556 $s .= $complementaryinfo."\n";
1557 }
1558 $s .= "\n";
1559 //var_dump($s);exit;
1560 return $s;
1561 }
1562}
1563
1564
1565
1566require_once DOL_DOCUMENT_ROOT.'/core/class/commonobjectline.class.php';
1567
1572{
1577 public $label;
1578
1583 public $ref; // Product ref (deprecated)
1588 public $libelle; // Product label (deprecated)
1589
1594 public $product_type = 0;
1595
1600 public $product_ref;
1601
1606 public $product_label;
1607
1612 public $product_desc;
1613
1618 public $qty;
1619
1624 public $subprice;
1625
1631 public $price;
1632
1637 public $fk_product;
1638
1643 public $vat_src_code;
1644
1649 public $tva_tx;
1650
1655 public $localtax1_tx;
1656
1661 public $localtax2_tx;
1662
1667 public $localtax1_type;
1668
1673 public $localtax2_type;
1674
1679 public $remise_percent;
1680
1686 public $remise;
1687
1692 public $total_ht;
1693
1698 public $total_tva;
1699
1704 public $total_localtax1;
1705
1710 public $total_localtax2;
1711
1716 public $total_ttc;
1717
1718 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
1719 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
1720
1721 public $buy_price_ht;
1722 public $buyprice; // For backward compatibility
1723 public $pa_ht; // For backward compatibility
1724
1725 public $marge_tx;
1726 public $marque_tx;
1727
1734 public $info_bits = 0;
1735
1736 public $special_code = 0;
1737
1738 public $fk_multicurrency;
1739 public $multicurrency_code;
1740 public $multicurrency_subprice;
1741 public $multicurrency_total_ht;
1742 public $multicurrency_total_tva;
1743 public $multicurrency_total_ttc;
1744
1745 public $fk_user_author;
1746 public $fk_user_modif;
1747
1748 public $fk_accounting_account;
1749}
$object ref
Definition info.php:78
Class to manage bank accounts.
Class to manage agenda events (actions)
Class to manage withdrawal receipts.
Superclass for invoices 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.
makeStripeSepaRequest($fuser, $did=0, $type='direct-debit', $sourcetype='facture')
Create a payment order into prelevement_demande then send the payment order to Stripe (for a direct d...
buildZATCAQRString()
Build string for ZATCA QR Code (Arabi Saudia)
const TYPE_STANDARD
Standard invoice.
demande_prelevement($fuser, $amount=0, $type='direct-debit', $sourcetype='facture', $checkduplicateamongall=0)
Create a withdrawal request for a direct debit order or a credit transfer order.
demande_prelevement_delete($fuser, $did)
Remove a direct debit request or a credit transfer request.
getVentilExportCompta()
Return if an invoice was dispatched into bookkeeping.
const TYPE_PROFORMA
Proforma invoice.
buildSwitzerlandQRString()
Build string for QR-Bill (Switzerland)
const STATUS_VALIDATED
Validated (need to be paid)
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.
const TYPE_DEPOSIT
Deposit invoice.
LibStatut($paye, $status, $mode=0, $alreadypaid=-1, $type=-1)
Return label of a status.
const STATUS_ABANDONED
Classified abandoned and no payment done.
calculate_date_lim_reglement($cond_reglement=0)
Returns an invoice payment deadline based on the invoice settlement conditions and billing date.
const TYPE_REPLACEMENT
Replacement invoice.
getSumFromThisCreditNotesNotUsed($multicurrency=0)
Return amount (with tax) of all converted amount for this credit note.
getListIdAvoirFromInvoice()
Returns array of credit note ids from the invoice.
const STATUS_DRAFT
Draft status.
getIdReplacingInvoice($option='')
Returns the id of the invoice that replaces it.
getLibType($withbadge=0)
Return label of type of invoice.
is_erasable()
Return if an invoice can be deleted Rule is: If invoice is draft and has a temporary ref -> yes (1) I...
getListOfPayments($filtertype='')
Return list of payments.
getLibStatut($mode=0, $alreadypaid=-1)
Return label of object status.
Parent class of all other business classes for details of elements (invoices, contracts,...
$label
Custom label of 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 third parties objects (customers, suppliers, prospects...)
Stripe class.
Class to manage translations.
print $langs trans("Ref").' m m m statut
Definition index.php:152
trait CommonIncoterm
Superclass for incoterm classes.
dol_time_plus_duree($time, $duration_value, $duration_unit, $ruleforendofmonth=0)
Add a delay to a date.
Definition date.lib.php:123
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 informations (by default a local PHP server timestamp) Re...
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
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_print_date($time, $format='', $tzoutput='auto', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
dol_now($mode='auto')
Return date for now.
getDolGlobalInt($key, $default=0)
Return 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.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
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 dolibarr global constant string value.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
if(preg_match('/crypted:/i', $dolibarr_main_db_pass)||!empty($dolibarr_main_db_encrypted_pass)) $conf db type
Definition repair.php:120