dolibarr 22.0.5
multicurrency.class.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2007-2020 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2014 Juanjo Menent <jmenent@2byte.es>
4 * Copyright (C) 2015 Florian Henry <florian.henry@open-concept.pro>
5 * Copyright (C) 2015 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
6 * Copyright (C) 2016 Pierre-Henry Favre <phf@atm-consulting.fr>
7 * Copyright (C) 2024 Frédéric France <frederic.france@free.fr>
8 * Copyright (C) 2024-2025 MDW <mdeweerd@users.noreply.github.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.'/multicurrency/class/currencyrate.class.php';
32
40{
44 public $element = 'multicurrency';
45
49 public $table_element = 'multicurrency';
50
54 public $table_element_line = "multicurrency_rate";
55
59 public $rates = array();
60
64 public $id;
65
69 public $code;
70
74 public $name;
75
79 public $entity;
80
84 public $date_create;
85
89 public $fk_user;
90
94 public $rate;
95
99 public $urlendpoint;
100
101
102 const MULTICURRENCY_APP_ENDPOINT_DEFAULT = 'https://api.currencylayer.com/live?access_key=__MULTICURRENCY_APP_KEY__&source=__MULTICURRENCY_APP_SOURCE__';
103
104
110 public function __construct(DoliDB $db)
111 {
112 $this->db = $db;
113
114 $key = getDolGlobalString("MULTICURRENCY_APP_KEY");
115 $source = getDolGlobalString('MULTICURRENCY_APP_SOURCE', 'USD');
116 $urlendpoint = getDolGlobalString("MULTICURRENCY_APP_ENDPOINT", self::MULTICURRENCY_APP_ENDPOINT_DEFAULT);
117
118 $this->urlendpoint = str_replace(array('__MULTICURRENCY_APP_KEY__', '__MULTICURRENCY_APP_SOURCE__'), array($key, $source), $urlendpoint);
119 }
120
128 public function create(User $user, $notrigger = 0)
129 {
130 global $conf, $langs;
131
132 dol_syslog('MultiCurrency::create', LOG_DEBUG);
133
134 $error = 0;
135
136 if (self::checkCodeAlreadyExists($this->code)) {
137 $error++;
138 $this->errors[] = $langs->trans('multicurrency_code_already_added');
139 return -1;
140 }
141
142 if (empty($this->entity) || $this->entity <= 0) {
143 $this->entity = $conf->entity;
144 }
145 $now = dol_now();
146
147 // Insert request
148 $sql = "INSERT INTO ".MAIN_DB_PREFIX.$this->table_element."(";
149 $sql .= ' code,';
150 $sql .= ' name,';
151 $sql .= ' entity,';
152 $sql .= ' date_create,';
153 $sql .= ' fk_user';
154 $sql .= ') VALUES (';
155 $sql .= " '".$this->db->escape($this->code)."',";
156 $sql .= " '".$this->db->escape($this->name)."',";
157 $sql .= " ".((int) $this->entity).",";
158 $sql .= " '".$this->db->idate($now)."',";
159 $sql .= " ".((int) $user->id);
160 $sql .= ')';
161
162 $this->db->begin();
163
164 dol_syslog(__METHOD__, LOG_DEBUG);
165 $resql = $this->db->query($sql);
166 if (!$resql) {
167 $error++;
168 $this->errors[] = 'Error '.$this->db->lasterror();
169 dol_syslog('MultiCurrency::create '.implode(',', $this->errors), LOG_ERR);
170 }
171
172 if (!$error) {
173 $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX.$this->table_element);
174 $this->date_create = $now;
175 $this->fk_user = $user->id;
176
177 if (empty($notrigger)) {
178 $result = $this->call_trigger('CURRENCY_CREATE', $user);
179 if ($result < 0) {
180 $error++;
181 }
182 }
183 }
184
185 if ($error) {
186 $this->db->rollback();
187
188 return -1 * $error;
189 } else {
190 $this->db->commit();
191
192 return $this->id;
193 }
194 }
195
203 public function fetch($id, $code = null)
204 {
205 dol_syslog('MultiCurrency::fetch', LOG_DEBUG);
206
207 $sql = "SELECT c.rowid, c.name, c.code, c.entity, c.date_create, c.fk_user";
208 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element." AS c";
209 if (!empty($code)) {
210 $sql .= " WHERE c.code = '".$this->db->escape($code)."'";
211 $sql .= " AND c.entity IN (".getEntity($this->element).")";
212 } else {
213 $sql .= ' WHERE c.rowid = '.((int) $id);
214 }
215
216 dol_syslog(__METHOD__, LOG_DEBUG);
217 $resql = $this->db->query($sql);
218
219 if ($resql) {
220 $numrows = $this->db->num_rows($resql);
221 if ($numrows) {
222 $obj = $this->db->fetch_object($resql);
223
224 $this->id = $obj->rowid;
225 $this->name = $obj->name;
226 $this->code = $obj->code;
227 $this->entity = $obj->entity;
228 $this->date_create = $obj->date_create;
229 $this->fk_user = $obj->fk_user;
230
231 $this->fetchAllCurrencyRate();
232 $this->getRate();
233 }
234 $this->db->free($resql);
235
236 if ($numrows) {
237 return 1;
238 } else {
239 return 0;
240 }
241 } else {
242 $this->errors[] = 'Error '.$this->db->lasterror();
243 dol_syslog('MultiCurrency::fetch '.implode(',', $this->errors), LOG_ERR);
244
245 return -1;
246 }
247 }
248
254 public function fetchAllCurrencyRate()
255 {
256 $sql = "SELECT cr.rowid";
257 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element_line." as cr";
258 $sql .= " WHERE cr.entity IN (".getEntity($this->element).")";
259 $sql .= " AND cr.fk_multicurrency = ".((int) $this->id);
260 $sql .= " ORDER BY cr.date_sync DESC";
261
262 $this->rates = array();
263
264 dol_syslog(__METHOD__, LOG_DEBUG);
265 $resql = $this->db->query($sql);
266 if ($resql) {
267 $num = $this->db->num_rows($resql);
268
269 while ($obj = $this->db->fetch_object($resql)) {
270 $rate = new CurrencyRate($this->db);
271 $rate->fetch($obj->rowid);
272
273 $this->rates[] = $rate;
274 }
275 $this->db->free($resql);
276
277 return $num;
278 } else {
279 $this->errors[] = 'Error '.$this->db->lasterror();
280 dol_syslog('MultiCurrency::fetchAllCurrencyRate '.implode(',', $this->errors), LOG_ERR);
281
282 return -1;
283 }
284 }
285
293 public function update(User $user, $notrigger = 0)
294 {
295 $error = 0;
296
297 dol_syslog('MultiCurrency::update', LOG_DEBUG);
298
299 // Clean parameters
300 $this->name = trim($this->name);
301 $this->code = trim($this->code);
302
303 // Check parameters
304 if (empty($this->code)) {
305 $error++;
306 dol_syslog('MultiCurrency::update $this->code can not be empty', LOG_ERR);
307
308 return -1;
309 }
310
311 // Update request
312 $sql = "UPDATE ".MAIN_DB_PREFIX.$this->table_element." SET";
313 $sql .= " name = '".$this->db->escape($this->name)."',";
314 $sql .= " code = '".$this->db->escape($this->code)."'";
315 $sql .= " WHERE rowid = ".((int) $this->id);
316
317 $this->db->begin();
318
319 $resql = $this->db->query($sql);
320 if (!$resql) {
321 $error++;
322 $this->errors[] = 'Error '.$this->db->lasterror();
323 dol_syslog('MultiCurrency::update '.implode(',', $this->errors), LOG_ERR);
324 }
325
326 if (!$error && empty($notrigger)) {
327 $result = $this->call_trigger('CURRENCY_MODIFY', $user);
328 if ($result < 0) {
329 $error++;
330 }
331 }
332
333 // Commit or rollback
334 if ($error) {
335 $this->db->rollback();
336
337 return -1 * $error;
338 } else {
339 $this->db->commit();
340
341 return 1;
342 }
343 }
344
352 public function delete(User $user, $notrigger = 0)
353 {
354 dol_syslog('MultiCurrency::delete', LOG_DEBUG);
355
356 $error = 0;
357
358 $this->db->begin();
359
360 if (empty($notrigger)) {
361 $result = $this->call_trigger('CURRENCY_DELETE', $user);
362 if ($result < 0) {
363 $error++;
364 }
365 }
366
367 if (!$error) {
368 // Delete all rates before
369 if (!$this->deleteRates()) {
370 $error++;
371 $this->errors[] = 'Error '.$this->db->lasterror();
372 dol_syslog('Currency::delete '.implode(',', $this->errors), LOG_ERR);
373 }
374
375 $sql = "DELETE FROM ".MAIN_DB_PREFIX.$this->table_element;
376 $sql .= " WHERE rowid = ".((int) $this->id);
377
378 dol_syslog(__METHOD__, LOG_DEBUG);
379 $resql = $this->db->query($sql);
380 if (!$resql) {
381 $error++;
382 $this->errors[] = 'Error '.$this->db->lasterror();
383 dol_syslog('MultiCurrency::delete '.implode(',', $this->errors), LOG_ERR);
384 }
385 }
386
387 // Commit or rollback
388 if ($error) {
389 $this->db->rollback();
390
391 return -1 * $error;
392 } else {
393 $this->db->commit();
394
395 return 1;
396 }
397 }
398
404 public function deleteRates()
405 {
406 global $user;
407
408 foreach ($this->rates as &$rate) {
409 if ($rate->delete($user) <= 0) {
410 return false;
411 }
412 }
413
414 return true;
415 }
416
423 public function addRate($rate)
424 {
425 global $user;
426
427 $currencyRate = new CurrencyRate($this->db);
428 $currencyRate->rate = (float) price2num($rate);
429
430 if ($currencyRate->create($user, $this->id) > 0) {
431 $this->rate = $currencyRate;
432 return 1;
433 } else {
434 $this->rate = null;
435 $this->errors = $currencyRate->errors;
436 return -1;
437 }
438 }
439
447 public function addRateFromDolibarr($code, $rate)
448 {
449 global $user;
450
451 $currency = new MultiCurrency($this->db);
452 $currency->code = $code;
453 $currency->name = $code;
454
455 $sql = "SELECT label FROM ".MAIN_DB_PREFIX."c_currencies WHERE code_iso = '".$this->db->escape($code)."'";
456
457 dol_syslog(__METHOD__, LOG_DEBUG);
458 $resql = $this->db->query($sql);
459 if ($resql && ($line = $this->db->fetch_object($resql))) {
460 $currency->name = $line->label;
461 }
462
463 if ($currency->create($user) > 0) {
464 $currency->addRate($rate);
465
466 if (!empty($line)) {
467 return 2;
468 } else {
469 return 1;
470 }
471 }
472
473 return -1;
474 }
475
482 public function updateRate($rate)
483 {
484 return $this->addRate($rate);
485 }
486
492 public function getRate()
493 {
494 $sql = "SELECT cr.rowid";
495 $sql .= " FROM ".MAIN_DB_PREFIX.$this->table_element_line." as cr";
496 $sql .= " WHERE cr.entity IN (".getEntity($this->element).")";
497 $sql .= " AND cr.fk_multicurrency = ".((int) $this->id);
498 $sql .= " AND cr.date_sync = (SELECT MAX(cr2.date_sync) FROM ".MAIN_DB_PREFIX.$this->table_element_line." AS cr2";
499 $sql .= " WHERE cr2.entity IN (".getEntity($this->element).") AND cr2.fk_multicurrency = ".((int) $this->id).")";
500
501 dol_syslog(__METHOD__, LOG_DEBUG);
502
503 $resql = $this->db->query($sql);
504 if ($resql && ($obj = $this->db->fetch_object($resql))) {
505 $this->rate = new CurrencyRate($this->db);
506 return $this->rate->fetch($obj->rowid);
507 }
508
509 return -1;
510 }
511
520 public static function getIdFromCode($dbs, $code)
521 {
522 global $conf;
523
524 $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."multicurrency WHERE code = '".$dbs->escape($code)."' AND entity = ".((int) $conf->entity);
525
526 dol_syslog(__METHOD__, LOG_DEBUG);
527 $resql = $dbs->query($sql);
528 if ($resql && $obj = $dbs->fetch_object($resql)) {
529 return $obj->rowid;
530 } else {
531 return 0;
532 }
533 }
534
545 public static function getIdAndTxFromCode($dbs, $code, $date_document = 0)
546 {
547 $sql1 = "SELECT m.rowid, mc.rate FROM ".MAIN_DB_PREFIX."multicurrency m";
548 $sql1 .= ' LEFT JOIN '.MAIN_DB_PREFIX.'multicurrency_rate mc ON (m.rowid = mc.fk_multicurrency)';
549 $sql1 .= " WHERE m.code = '".$dbs->escape($code)."'";
550 $sql1 .= " AND m.entity IN (".getEntity('multicurrency').")";
551 $sql2 = '';
552 if (getDolGlobalString('MULTICURRENCY_USE_RATE_ON_DOCUMENT_DATE') && !empty($date_document)) { // Use last known rate compared to document date
553 $tmparray = dol_getdate($date_document);
554 $sql2 .= " AND mc.date_sync <= '".$dbs->idate(dol_mktime(23, 59, 59, $tmparray['mon'], $tmparray['mday'], $tmparray['year'], true))."'";
555 }
556 $sql3 = " ORDER BY mc.date_sync DESC LIMIT 1";
557
558 dol_syslog(__METHOD__, LOG_DEBUG);
559 $resql = $dbs->query($sql1.$sql2.$sql3);
560
561 if ($resql && $obj = $dbs->fetch_object($resql)) {
562 return array($obj->rowid, $obj->rate);
563 } else {
564 if (getDolGlobalString('MULTICURRENCY_USE_RATE_ON_DOCUMENT_DATE')) {
565 $resql = $dbs->query($sql1.$sql3);
566 if ($resql && $obj = $dbs->fetch_object($resql)) {
567 return array($obj->rowid, $obj->rate);
568 }
569 }
570
571 return array(0, 1);
572 }
573 }
574
585 public static function getAmountConversionFromInvoiceRate($fk_facture, $amount, $way = 'dolibarr', $table = 'facture', $invoice_rate = null)
586 {
587 if (!is_null($invoice_rate)) {
588 $multicurrency_tx = $invoice_rate;
589 } else {
590 $tmparray = self::getInvoiceRate($fk_facture, $table);
591 $multicurrency_tx = $tmparray['invoice_multicurrency_tx'];
592 }
593
594 if ($multicurrency_tx) {
595 if ($way == 'dolibarr') {
596 return (float) price2num($amount * $multicurrency_tx, 'MU');
597 } else {
598 return (float) price2num($amount / $multicurrency_tx, 'MU');
599 }
600 } else {
601 return false;
602 }
603 }
604
612 public static function getInvoiceRate($fk_facture, $table = 'facture')
613 {
614 global $db;
615
616 $sql = "SELECT multicurrency_tx, multicurrency_code";
617 $sql .= " FROM ".MAIN_DB_PREFIX.$db->sanitize($table);
618 $sql .= " WHERE rowid = ".((int) $fk_facture);
619
620 dol_syslog(__METHOD__, LOG_DEBUG);
621 $resql = $db->query($sql);
622 if ($resql && ($line = $db->fetch_object($resql))) {
623 return array('invoice_multicurrency_tx' => $line->multicurrency_tx, 'invoice_multicurrency_code' => $line->multicurrency_code);
624 }
625
626 return false;
627 }
628
637 public function recalculRates(&$TRate)
638 {
639 global $conf;
640
641 if ($conf->currency != getDolGlobalString('MULTICURRENCY_APP_SOURCE')) {
642 $alternate_source = 'USD'.$conf->currency;
643 if (!empty($TRate->$alternate_source)) {
644 $coef = 1 / $TRate->$alternate_source;
645 foreach ($TRate as $attr => &$rate) {
646 $rate *= $coef;
647 }
648 $TRate->USDUSD = $coef;
649 return 1;
650 }
651
652 return -1; // Alternate source not found
653 }
654
655 return 0; // Nothing to do
656 }
657
667 public function syncRates($nu = 0, $addifnotfound = 0, $mode = "")
668 {
669 global $db, $langs;
670
671 if (getDolGlobalString('MULTICURRENCY_DISABLE_SYNC_CURRENCYLAYER')) {
672 if ($mode == "cron") {
673 $this->output = $langs->trans('Use of API for currency update is disabled by option MULTICURRENCY_DISABLE_SYNC_CURRENCYLAYER');
674 } else {
675 setEventMessages($langs->trans('Use of API for currency update is disabled by option MULTICURRENCY_DISABLE_SYNC_CURRENCYLAYER'), null, 'errors');
676 }
677 return -1;
678 }
679
680 include_once DOL_DOCUMENT_ROOT.'/core/lib/geturl.lib.php';
681
682 $urlendpoint = $this->urlendpoint;
683
684 dol_syslog("Call url endpoint ".$urlendpoint);
685
686 $addheaders = array('apikey: '.getDolGlobalString('MULTICURRENCY_APP_KEY'));
687
688 $resget = getURLContent($urlendpoint, 'GET', '', 1, $addheaders);
689
690 // Example of result with https://currencylayer.com/live and https://api.apilayer.com/currency_data/live
691 // 'content' => string '{"success":true,"terms":"https:\/\/currencylayer.com\/terms","privacy":"https:\/\/currencylayer.com\/privacy","timestamp":1742562251,"source":"USD","quotes":{"USDAED":3.67302,"USDAFN":70.6213,"USDALL":91.042287,"USDAMD":390.984233,"USDANG":1.802039,"USDAOA":913.498241,"USDARS":1068.745088,"USDAUD":1.591824,"USDAWG":1.8,"USDAZN":1.699323,"USDBAM":1.80224,"USDBBD":2.018881,"USDBDT":121.488567,"USDBGN":1.802745,"USDBHD":0.376878,"USDBIF":2963.403228,"USDBMD":1,"USDBND":1.333573,"USDBOB":6.909262,"USDBRL":5.721'... (length=3337)
692 //var_dump($urlendpoint);
693 //var_dump($resget);
694
695 if (!empty($resget['content'])) {
696 $response = $resget['content'];
697 $response = json_decode($response);
698
699 if ($response->success) {
700 $TRate = $response->quotes;
701 //$timestamp = $response->timestamp;
702
703 // Recalculate rate and update it (or add it) into database
704 if ($this->recalculRates($TRate) >= 0) {
705 foreach ($TRate as $currency_code => $rate) {
706 $code = substr($currency_code, 3, 3);
707 $obj = new MultiCurrency($db);
708 if ($obj->fetch(0, $code) > 0) {
709 $obj->updateRate($rate);
710 } elseif ($addifnotfound) {
711 $this->addRateFromDolibarr($code, $rate);
712 }
713 }
714 }
715
716 if ($mode == "cron") {
717 return 0;
718 }
719 return 1;
720 } else {
721 if (isset($response->error->info)) {
722 $error_info_syslog = $response->error->info; // @phan-suppress-current-line PhanTypeExpectedObjectPropAccess
723 $error_info = $error_info_syslog;
724 } else {
725 $error_info_syslog = json_encode($response);
726 if (empty($resget['content'])) {
727 $error_info = "No error information found (see syslog)";
728 } else {
729 $error_info = $resget['content'];
730 }
731 }
732
733 dol_syslog("Failed to call endpoint ".$error_info_syslog, LOG_WARNING);
734
735 $this->output = $langs->trans('multicurrency_syncronize_error', $error_info);
736
737 return -1;
738 }
739 } else {
740 $this->output = $resget['curl_error_msg'];
741
742 return -1;
743 }
744 }
745
752 public function checkCodeAlreadyExists($code)
753 {
754 $currencytmp = new MultiCurrency($this->db);
755 if ($currencytmp->fetch(0, $code) > 0) {
756 return true;
757 } else {
758 return false;
759 }
760 }
761}
Parent class of all other business classes (invoices, contracts, proposals, orders,...
call_trigger($triggerName, $user)
Call trigger based on this instance.
Class CurrencyRate.
Class to manage Dolibarr database access.
Class Currency.
static getInvoiceRate($fk_facture, $table='facture')
Get current invoite rate.
getRate()
Fetch CurrencyRate object in $this->rate.
static getIdAndTxFromCode($dbs, $code, $date_document=0)
Get id and rate of currency from code.
update(User $user, $notrigger=0)
Update object into database.
syncRates($nu=0, $addifnotfound=0, $mode="")
Sync rates from API.
fetchAllCurrencyRate()
Load all rates in object from the database.
static getAmountConversionFromInvoiceRate($fk_facture, $amount, $way='dolibarr', $table='facture', $invoice_rate=null)
Get the conversion of amount with invoice rate.
checkCodeAlreadyExists($code)
Check in database if the current code already exists.
updateRate($rate)
Add new entry into llx_multicurrency_rate.
static getIdFromCode($dbs, $code)
Get id of currency from code.
__construct(DoliDB $db)
Constructor.
deleteRates()
Delete rates in database.
create(User $user, $notrigger=0)
Create object into database.
addRateFromDolibarr($code, $rate)
Try get label of code in llx_currency then add rate.
recalculRates(&$TRate)
With free account we can't set source to something else than US, to we recalculate all rates to force...
fetch($id, $code=null)
Load object in memory from the database.
addRate($rate)
Add a Rate into database.
Class to manage Dolibarr users.
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...
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='', $noduplicate=0, $attop=0)
Set event messages in dol_events session object.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_now($mode='auto')
Return date for now.
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.
dol_getdate($timestamp, $fast=false, $forcetimezone='')
Return an array with locale date info.
getURLContent($url, $postorget='GET', $param='', $followlocation=1, $addheaders=array(), $allowedschemes=array('http', 'https'), $localurl=0, $ssl_verifypeer=-1, $timeoutconnect=0, $timeoutresponse=0)
Function to get a content from an URL (use proxy if proxy defined).
global $conf
The following vars must be defined: $type2label $form $conf, $lang, The following vars may also be de...
Definition member.php:79
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition repair.php:161