mirror of
https://github.com/espocrm/espocrm.git
synced 2026-03-03 02:27:01 +00:00
Currency rates as entities (#3543)
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\Acl\CurrencyRecordRate;
|
||||
|
||||
use Espo\Core\Acl\AccessEntityCREDChecker;
|
||||
use Espo\Core\Acl\DefaultAccessChecker;
|
||||
use Espo\Core\Acl\ScopeData;
|
||||
use Espo\Core\Acl\Table;
|
||||
use Espo\Core\Acl\Traits\DefaultAccessCheckerDependency;
|
||||
use Espo\Entities\CurrencyRecordRate;
|
||||
use Espo\Entities\User;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements AccessEntityCREDChecker<CurrencyRecordRate>
|
||||
*/
|
||||
class AccessChecker implements AccessEntityCREDChecker
|
||||
{
|
||||
use DefaultAccessCheckerDependency;
|
||||
|
||||
public function __construct(
|
||||
private DefaultAccessChecker $defaultAccessChecker,
|
||||
) {}
|
||||
|
||||
public function checkCreate(User $user, ScopeData $data): bool
|
||||
{
|
||||
if ($data->getEdit() === Table::LEVEL_YES) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkDelete(User $user, ScopeData $data): bool
|
||||
{
|
||||
if ($data->getEdit() === Table::LEVEL_YES) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function checkEntityCreate(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkCreate($user, $data);
|
||||
}
|
||||
|
||||
public function checkEntityDelete(User $user, Entity $entity, ScopeData $data): bool
|
||||
{
|
||||
return $this->checkDelete($user, $data);
|
||||
}
|
||||
}
|
||||
59
application/Espo/Classes/AppParams/CurrencyRates.php
Normal file
59
application/Espo/Classes/AppParams/CurrencyRates.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\AppParams;
|
||||
|
||||
use Espo\Core\Currency\ConfigDataProvider;
|
||||
use Espo\Core\Utils\NumberUtil;
|
||||
use Espo\Tools\App\AppParam;
|
||||
use stdClass;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class CurrencyRates implements AppParam
|
||||
{
|
||||
private const int PRECISION = 6;
|
||||
|
||||
public function __construct(
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
private NumberUtil $numberUtil,
|
||||
) {}
|
||||
|
||||
public function get(): stdClass
|
||||
{
|
||||
$rates = $this->configDataProvider->getCurrencyRates()->toAssoc();
|
||||
|
||||
foreach ($rates as $code => $value) {
|
||||
$rates[$code] = $this->numberUtil->format($value, self::PRECISION);
|
||||
}
|
||||
|
||||
return (object) $rates;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldProcessing\CurrencyRecord;
|
||||
|
||||
use Espo\Core\Currency\ConfigDataProvider;
|
||||
use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Entities\CurrencyRecord;
|
||||
use Espo\ORM\Entity;
|
||||
use ValueError;
|
||||
|
||||
/**
|
||||
* @implements Loader<CurrencyRecord>
|
||||
*/
|
||||
class IsBase implements Loader
|
||||
{
|
||||
public function __construct(
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
) {}
|
||||
|
||||
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
try {
|
||||
$code = $entity->getCode();
|
||||
} catch (ValueError) {
|
||||
$entity->setIsBase(false);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$isBase = $code === $this->configDataProvider->getBaseCurrency();
|
||||
|
||||
$entity->setIsBase($isBase);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldProcessing\CurrencyRecord;
|
||||
|
||||
use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Core\Utils\Language;
|
||||
use Espo\Entities\CurrencyRecord;
|
||||
use Espo\ORM\Entity;
|
||||
use ValueError;
|
||||
|
||||
/**
|
||||
* @implements Loader<CurrencyRecord>
|
||||
*/
|
||||
class Label implements Loader
|
||||
{
|
||||
public function __construct(
|
||||
private Language $defaultLanguage
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
try {
|
||||
$code = $entity->getCode();
|
||||
} catch (ValueError) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $this->defaultLanguage->translateLabel($code, 'names', 'Currency');
|
||||
|
||||
$entity->setLabel($name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldProcessing\CurrencyRecord;
|
||||
|
||||
use Espo\Core\Currency\ConfigDataProvider;
|
||||
use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Entities\CurrencyRecord;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Currency\RateEntryProvider;
|
||||
|
||||
/**
|
||||
* @implements Loader<CurrencyRecord>
|
||||
*/
|
||||
class Rate implements Loader
|
||||
{
|
||||
public function __construct(
|
||||
private RateEntryProvider $rateEntryProvider,
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
$rate = $entity->getCode() !== $this->configDataProvider->getBaseCurrency() ?
|
||||
$this->rateEntryProvider->getCurrentRateEntry($entity)?->getRate() :
|
||||
'1';
|
||||
|
||||
$entity->setRate($rate);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldProcessing\CurrencyRecord;
|
||||
|
||||
use Espo\Core\Currency\ConfigDataProvider;
|
||||
use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Entities\CurrencyRecord;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Currency\RateEntryProvider;
|
||||
|
||||
/**
|
||||
* @implements Loader<CurrencyRecord>
|
||||
*/
|
||||
class RateDate implements Loader
|
||||
{
|
||||
public function __construct(
|
||||
private RateEntryProvider $rateEntryProvider,
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
$date = $entity->getCode() !== $this->configDataProvider->getBaseCurrency() ?
|
||||
$this->rateEntryProvider->getCurrentRateEntry($entity)?->getDate() :
|
||||
null;
|
||||
|
||||
$entity->setRateDate($date);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldProcessing\CurrencyRecord;
|
||||
|
||||
use Espo\Core\FieldProcessing\Loader;
|
||||
use Espo\Core\FieldProcessing\Loader\Params;
|
||||
use Espo\Core\Utils\Metadata;
|
||||
use Espo\Entities\CurrencyRecord;
|
||||
use Espo\ORM\Entity;
|
||||
use ValueError;
|
||||
|
||||
/**
|
||||
* @implements Loader<CurrencyRecord>
|
||||
*/
|
||||
class Symbol implements Loader
|
||||
{
|
||||
public function __construct(
|
||||
private Metadata $metadata,
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity, Params $params): void
|
||||
{
|
||||
try {
|
||||
$code = $entity->getCode();
|
||||
} catch (ValueError) {
|
||||
return;
|
||||
}
|
||||
$symbol = $this->metadata->get("app.currency.symbolMap.$code");
|
||||
|
||||
$entity->setSymbol($symbol);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\FieldValidators\CurrencyRecordRate\Record;
|
||||
|
||||
use Espo\Core\Currency\ConfigDataProvider;
|
||||
use Espo\Core\FieldValidation\Validator;
|
||||
use Espo\Core\FieldValidation\Validator\Data;
|
||||
use Espo\Core\FieldValidation\Validator\Failure;
|
||||
use Espo\Entities\CurrencyRecordRate;
|
||||
use Espo\ORM\Entity;
|
||||
|
||||
/**
|
||||
* @implements Validator<CurrencyRecordRate>
|
||||
*/
|
||||
class NonBase implements Validator
|
||||
{
|
||||
public function __construct(
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
) {}
|
||||
|
||||
public function validate(Entity $entity, string $field, Data $data): ?Failure
|
||||
{
|
||||
if ($entity->getRecord()->getCode() === $this->configDataProvider->getBaseCurrency()) {
|
||||
return Failure::create();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\CurrencyRecordRate;
|
||||
|
||||
use Espo\Core\Exceptions\Conflict;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Record\DeleteParams;
|
||||
use Espo\Core\Record\Hook\DeleteHook;
|
||||
use Espo\Entities\CurrencyRecordRate;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Currency\Exceptions\NotEnabled;
|
||||
use Espo\Tools\Currency\RecordManager;
|
||||
|
||||
/**
|
||||
* @implements DeleteHook<CurrencyRecordRate>
|
||||
*/
|
||||
class AfterDelete implements DeleteHook
|
||||
{
|
||||
public function __construct(
|
||||
private RecordManager $recordManager,
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity, DeleteParams $params): void
|
||||
{
|
||||
$code = $entity->getRecord()->getCode();
|
||||
|
||||
try {
|
||||
$this->recordManager->syncCodeToConfig($code);
|
||||
} catch (NotEnabled $e) {
|
||||
throw new Conflict($e->getMessage(), previous: $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\CurrencyRecordRate;
|
||||
|
||||
use Espo\Core\Exceptions\Conflict;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\CurrencyRecordRate;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\Tools\Currency\Exceptions\NotEnabled;
|
||||
use Espo\Tools\Currency\RecordManager;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<CurrencyRecordRate>
|
||||
*/
|
||||
class AfterSave implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private RecordManager $recordManager,
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
$code = $entity->getRecord()->getCode();
|
||||
|
||||
try {
|
||||
$this->recordManager->syncCodeToConfig($code);
|
||||
} catch (NotEnabled $e) {
|
||||
throw new Conflict($e->getMessage(), previous: $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\RecordHooks\CurrencyRecordRate;
|
||||
|
||||
use Espo\Core\Exceptions\Conflict;
|
||||
use Espo\Core\Exceptions\Error\Body;
|
||||
use Espo\Core\Record\Hook\SaveHook;
|
||||
use Espo\Entities\CurrencyRecordRate;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
/**
|
||||
* @implements SaveHook<CurrencyRecordRate>
|
||||
*/
|
||||
class BeforeSaveValidation implements SaveHook
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
) {}
|
||||
|
||||
public function process(Entity $entity): void
|
||||
{
|
||||
$this->validateDate($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Conflict
|
||||
*/
|
||||
private function validateDate(CurrencyRecordRate $entity): void
|
||||
{
|
||||
if (!$entity->isNew()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$recordId = $entity->getRecord()->getId();
|
||||
$date = $entity->getDate();
|
||||
|
||||
$one = $this->entityManager
|
||||
->getRDBRepositoryByClass(CurrencyRecordRate::class)
|
||||
->where([
|
||||
CurrencyRecordRate::ATTR_RECORD_ID => $recordId,
|
||||
CurrencyRecordRate::FIELD_DATE => $date->toString(),
|
||||
])
|
||||
->findOne();
|
||||
|
||||
if ($one) {
|
||||
throw Conflict::createWithBody(
|
||||
'rateOnDateAlreadyExists',
|
||||
Body::create()->withMessageTranslation('rateOnDateAlreadyExists', 'Currency')
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Classes\Select\CurrencyRecord\PrimaryFilters;
|
||||
|
||||
use Espo\Core\Select\Primary\Filter;
|
||||
use Espo\Entities\CurrencyRecord;
|
||||
use Espo\ORM\Query\SelectBuilder;
|
||||
|
||||
class Active implements Filter
|
||||
{
|
||||
public function apply(SelectBuilder $queryBuilder): void
|
||||
{
|
||||
$queryBuilder->where([
|
||||
CurrencyRecord::FIELD_STATUS => CurrencyRecord::STATUS_ACTIVE,
|
||||
]);
|
||||
}
|
||||
}
|
||||
56
application/Espo/Controllers/CurrencyRecord.php
Normal file
56
application/Espo/Controllers/CurrencyRecord.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Controllers;
|
||||
|
||||
use Espo\Core\Api\Request;
|
||||
use Espo\Core\Api\Response;
|
||||
use Espo\Core\Controllers\Record;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class CurrencyRecord extends Record
|
||||
{
|
||||
public function postActionCreate(Request $request, Response $response): never
|
||||
{
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
public function putActionUpdate(Request $request, Response $response): never
|
||||
{
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
public function deleteActionDelete(Request $request, Response $response): never
|
||||
{
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
38
application/Espo/Controllers/CurrencyRecordRate.php
Normal file
38
application/Espo/Controllers/CurrencyRecordRate.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Controllers;
|
||||
|
||||
use Espo\Core\Controllers\RecordBase;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class CurrencyRecordRate extends RecordBase
|
||||
{}
|
||||
@@ -32,6 +32,9 @@ namespace Espo\Core\Rebuild\Actions;
|
||||
use Espo\Core\Rebuild\RebuildAction;
|
||||
use Espo\Core\Utils\Currency\DatabasePopulator;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class CurrencyRates implements RebuildAction
|
||||
{
|
||||
public function __construct(private DatabasePopulator $databasePopulator) {}
|
||||
|
||||
48
application/Espo/Core/Rebuild/Actions/SyncCurrency.php
Normal file
48
application/Espo/Core/Rebuild/Actions/SyncCurrency.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Core\Rebuild\Actions;
|
||||
|
||||
use Espo\Core\Rebuild\RebuildAction;
|
||||
use Espo\Tools\Currency\RecordManager;
|
||||
|
||||
/**
|
||||
* @noinspection PhpUnused
|
||||
*/
|
||||
class SyncCurrency implements RebuildAction
|
||||
{
|
||||
public function __construct(
|
||||
private RecordManager $recordManager,
|
||||
) {}
|
||||
|
||||
public function process(): void
|
||||
{
|
||||
$this->recordManager->sync();
|
||||
}
|
||||
}
|
||||
@@ -33,30 +33,28 @@ use Espo\Core\Currency\ConfigDataProvider;
|
||||
use Espo\Entities\Currency;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Name\Attribute;
|
||||
use Espo\Tools\Currency\RateEntryProvider;
|
||||
use Espo\Tools\Currency\Exceptions\NotEnabled;
|
||||
|
||||
/**
|
||||
* Populates currency rates into database.
|
||||
*/
|
||||
class DatabasePopulator
|
||||
{
|
||||
private const PRECISION = 5;
|
||||
private const int PRECISION = 6;
|
||||
|
||||
public function __construct(
|
||||
private EntityManager $entityManager,
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
private RateEntryProvider $rateEntryProvider,
|
||||
) {}
|
||||
|
||||
public function process(): void
|
||||
{
|
||||
$defaultCurrency = $this->configDataProvider->getDefaultCurrency();
|
||||
$baseCurrency = $this->configDataProvider->getBaseCurrency();
|
||||
$currencyRates = $this->configDataProvider->getCurrencyRates()->toAssoc();
|
||||
|
||||
if ($defaultCurrency !== $baseCurrency) {
|
||||
$currencyRates = $this->exchangeRates($baseCurrency, $defaultCurrency, $currencyRates);
|
||||
}
|
||||
|
||||
$currencyRates[$defaultCurrency] = 1.00;
|
||||
$currencyRates = $this->prepareRates($defaultCurrency, $baseCurrency);
|
||||
|
||||
$this->entityManager->getTransactionManager()->start();
|
||||
|
||||
@@ -96,4 +94,28 @@ class DatabasePopulator
|
||||
|
||||
return $exchangedRates;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, float>
|
||||
*/
|
||||
private function prepareRates(string $defaultCurrency, string $baseCurrency): array
|
||||
{
|
||||
$currencyRates = [];
|
||||
|
||||
foreach ($this->configDataProvider->getCurrencyList() as $itCode) {
|
||||
try {
|
||||
$currencyRates[$itCode] = (float) ($this->rateEntryProvider->getRate($itCode) ?? 1);
|
||||
} catch (NotEnabled) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($defaultCurrency !== $baseCurrency) {
|
||||
$currencyRates = $this->exchangeRates($baseCurrency, $defaultCurrency, $currencyRates);
|
||||
}
|
||||
|
||||
$currencyRates[$defaultCurrency] = 1.00;
|
||||
|
||||
return $currencyRates;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -263,7 +263,7 @@ class Currency implements FieldConverter
|
||||
{
|
||||
$name = $fieldDefs->getName();
|
||||
|
||||
$alias = $name . 'CurrencyRate';
|
||||
$alias = $name . 'CurrencyRecordRate';
|
||||
$leftJoins = [
|
||||
[
|
||||
'Currency',
|
||||
|
||||
92
application/Espo/Entities/CurrencyRecord.php
Normal file
92
application/Espo/Entities/CurrencyRecord.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Entities;
|
||||
|
||||
use Espo\Core\Field\Date;
|
||||
use Espo\Core\ORM\Entity;
|
||||
|
||||
class CurrencyRecord extends Entity
|
||||
{
|
||||
public const string ENTITY_TYPE = 'CurrencyRecord';
|
||||
|
||||
public const string FIELD_STATUS = 'status';
|
||||
public const string FIELD_CODE = 'code';
|
||||
|
||||
public const string STATUS_ACTIVE = 'Active';
|
||||
public const string STATUS_INACTIVE = 'Inactive';
|
||||
|
||||
public function getCode(): string
|
||||
{
|
||||
return $this->get(self::FIELD_CODE);
|
||||
}
|
||||
|
||||
public function setCode(string $code): self
|
||||
{
|
||||
return $this->set(self::FIELD_CODE, $code);
|
||||
}
|
||||
|
||||
public function getStatus(): string
|
||||
{
|
||||
return $this->get(self::FIELD_STATUS);
|
||||
}
|
||||
|
||||
public function setStatus(string $status): self
|
||||
{
|
||||
return $this->set(self::FIELD_STATUS, $status);
|
||||
}
|
||||
|
||||
public function setLabel(?string $label): self
|
||||
{
|
||||
return $this->set('label', $label);
|
||||
}
|
||||
|
||||
public function setSymbol(?string $label): self
|
||||
{
|
||||
return $this->set('symbol', $label);
|
||||
}
|
||||
|
||||
public function setIsBase(bool $isBase): self
|
||||
{
|
||||
return $this->set('isBase', $isBase);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ?numeric-string $rate
|
||||
*/
|
||||
public function setRate(?string $rate): self
|
||||
{
|
||||
return $this->set('rate', $rate);
|
||||
}
|
||||
|
||||
public function setRateDate(?Date $date): self
|
||||
{
|
||||
return $this->setValueObject('rateDate', $date);
|
||||
}
|
||||
}
|
||||
100
application/Espo/Entities/CurrencyRecordRate.php
Normal file
100
application/Espo/Entities/CurrencyRecordRate.php
Normal file
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Entities;
|
||||
|
||||
use Espo\Core\Field\Date;
|
||||
use Espo\Core\ORM\Entity;
|
||||
use ValueError;
|
||||
|
||||
class CurrencyRecordRate extends Entity
|
||||
{
|
||||
public const string ENTITY_TYPE = 'CurrencyRecordRate';
|
||||
|
||||
public const string FIELD_DATE = 'date';
|
||||
public const string FIELD_BASE_CODE = 'baseCode';
|
||||
public const string FIELD_RATE = 'rate';
|
||||
public const string FIELD_RECORD = 'record';
|
||||
|
||||
public const string ATTR_RECORD_ID = 'recordId';
|
||||
|
||||
/**
|
||||
* @return numeric-string
|
||||
*/
|
||||
public function getRate(): string
|
||||
{
|
||||
/** @var numeric-string */
|
||||
return $this->get(self::FIELD_RATE) ?? '1';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param numeric-string $rate
|
||||
*/
|
||||
public function setRate(string $rate): self
|
||||
{
|
||||
return $this->set(self::FIELD_RATE, $rate);
|
||||
}
|
||||
|
||||
public function setBaseCode(string $code): self
|
||||
{
|
||||
return $this->set(self::FIELD_BASE_CODE, $code);
|
||||
}
|
||||
|
||||
public function setRecord(CurrencyRecord $record): self
|
||||
{
|
||||
return $this->setRelatedLinkOrEntity(self::FIELD_RECORD, $record);
|
||||
}
|
||||
|
||||
public function setDate(Date $date): self
|
||||
{
|
||||
return $this->setValueObject(self::DATE, $date);
|
||||
}
|
||||
|
||||
public function getRecord(): CurrencyRecord
|
||||
{
|
||||
$record = $this->relations->getOne(self::FIELD_RECORD);
|
||||
|
||||
if (!$record instanceof CurrencyRecord) {
|
||||
throw new ValueError("No record.");
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
|
||||
public function getDate(): Date
|
||||
{
|
||||
$date = $this->getValueObject(self::FIELD_DATE);
|
||||
|
||||
if (!$date instanceof Date) {
|
||||
throw new ValueError("No date.");
|
||||
}
|
||||
|
||||
return $date;
|
||||
}
|
||||
}
|
||||
56
application/Espo/Hooks/CurrencyRecordRate/SetFields.php
Normal file
56
application/Espo/Hooks/CurrencyRecordRate/SetFields.php
Normal file
@@ -0,0 +1,56 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Hooks\CurrencyRecordRate;
|
||||
|
||||
use Espo\Core\Currency\ConfigDataProvider;
|
||||
use Espo\Core\Hook\Hook\BeforeSave;
|
||||
use Espo\Entities\CurrencyRecordRate;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\Repository\Option\SaveOptions;
|
||||
|
||||
/**
|
||||
* @implements BeforeSave<CurrencyRecordRate>
|
||||
*/
|
||||
class SetFields implements BeforeSave
|
||||
{
|
||||
public function __construct(
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
) {}
|
||||
|
||||
|
||||
public function beforeSave(Entity $entity, SaveOptions $options): void
|
||||
{
|
||||
if ($entity->isNew()) {
|
||||
$baseCode = $this->configDataProvider->getBaseCurrency();
|
||||
|
||||
$entity->setBaseCode($baseCode);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -164,5 +164,8 @@
|
||||
"ZAR":"South African Rand",
|
||||
"ZMW":"Zambian Kwacha",
|
||||
"ZWL": "Zimbabwe Dollar"
|
||||
},
|
||||
"messages": {
|
||||
"rateOnDateAlreadyExists": "A rate on this date already exists."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
20
application/Espo/Resources/i18n/en_US/CurrencyRecord.json
Normal file
20
application/Espo/Resources/i18n/en_US/CurrencyRecord.json
Normal file
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"fields": {
|
||||
"code": "Code",
|
||||
"status": "Status",
|
||||
"label": "Label",
|
||||
"symbol": "Symbol",
|
||||
"isBase": "Is Base",
|
||||
"rate": "Rate",
|
||||
"rateDate": "Rate Date"
|
||||
},
|
||||
"links": {
|
||||
"rates": "Rates"
|
||||
},
|
||||
"options": {
|
||||
"status": {
|
||||
"Active": "Active",
|
||||
"Inactive": "Inactive"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"labels": {
|
||||
"Create CurrencyRecordRate": "Create Exchange Rate"
|
||||
},
|
||||
"fields": {
|
||||
"record": "Currency",
|
||||
"baseCode": "Base",
|
||||
"date": "Date",
|
||||
"rate": "Rate",
|
||||
"number": "Number"
|
||||
},
|
||||
"links": {
|
||||
"record": "Currency"
|
||||
}
|
||||
}
|
||||
@@ -68,7 +68,9 @@
|
||||
"AppSecret": "App Secret",
|
||||
"OAuthProvider": "OAuth Provider",
|
||||
"OAuthAccount": "OAuth Account",
|
||||
"OpenApi": "OpenAPI"
|
||||
"OpenApi": "OpenAPI",
|
||||
"CurrencyRecord": "Currency Record",
|
||||
"CurrencyRecordRate": "Currency Rate"
|
||||
},
|
||||
"scopeNamesPlural": {
|
||||
"Note": "Notes",
|
||||
@@ -127,7 +129,9 @@
|
||||
"AppSecret": "App Secrets",
|
||||
"OAuthProvider": "OAuth Providers",
|
||||
"OAuthAccount": "OAuth Accounts",
|
||||
"OpenApi": "OpenAPI"
|
||||
"OpenApi": "OpenAPI",
|
||||
"CurrencyRecord": "Currencies",
|
||||
"CurrencyRecordRate": "Currency Rates"
|
||||
},
|
||||
"labels": {
|
||||
"Previous Page": "Previous Page",
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"rates": {
|
||||
"index": 0
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"rows": [
|
||||
[
|
||||
{
|
||||
"name": "code"
|
||||
},
|
||||
{
|
||||
"name": "status"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"name": "label"
|
||||
},
|
||||
{
|
||||
"name": "isBase"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"name": "rate"
|
||||
},
|
||||
{
|
||||
"name": "rateDate"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,30 @@
|
||||
[
|
||||
{
|
||||
"rows": [
|
||||
[
|
||||
{
|
||||
"name": "code"
|
||||
},
|
||||
{
|
||||
"name": "status"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"name": "label"
|
||||
},
|
||||
{
|
||||
"name": "isBase"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"name": "rate"
|
||||
},
|
||||
{
|
||||
"name": "rateDate"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
31
application/Espo/Resources/layouts/CurrencyRecord/list.json
Normal file
31
application/Espo/Resources/layouts/CurrencyRecord/list.json
Normal file
@@ -0,0 +1,31 @@
|
||||
[
|
||||
{
|
||||
"name": "code",
|
||||
"link": true,
|
||||
"width": 16
|
||||
},
|
||||
{
|
||||
"name": "label",
|
||||
"width": 16
|
||||
},
|
||||
{
|
||||
"name": "symbol",
|
||||
"width": 10
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"width": 12
|
||||
},
|
||||
{
|
||||
"name": "isBase",
|
||||
"width": 10
|
||||
},
|
||||
{
|
||||
"name": "rateDate",
|
||||
"width": 12
|
||||
},
|
||||
{
|
||||
"name": "rate",
|
||||
"align": "right"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,23 @@
|
||||
[
|
||||
{
|
||||
"name": "code",
|
||||
"link": true,
|
||||
"width": 18
|
||||
},
|
||||
{
|
||||
"name": "label",
|
||||
"width": 28
|
||||
},
|
||||
{
|
||||
"name": "status",
|
||||
"width": 20
|
||||
},
|
||||
{
|
||||
"name": "isBase",
|
||||
"width": 15
|
||||
},
|
||||
{
|
||||
"name": "rate",
|
||||
"align": "right"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
[
|
||||
{
|
||||
"rows": [
|
||||
[
|
||||
{
|
||||
"name": "record"
|
||||
},
|
||||
{
|
||||
"name": "baseCode"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"name": "date"
|
||||
},
|
||||
{
|
||||
"name": "rate"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,22 @@
|
||||
[
|
||||
{
|
||||
"rows": [
|
||||
[
|
||||
{
|
||||
"name": "record"
|
||||
},
|
||||
{
|
||||
"name": "baseCode"
|
||||
}
|
||||
],
|
||||
[
|
||||
{
|
||||
"name": "date"
|
||||
},
|
||||
{
|
||||
"name": "rate"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,3 @@
|
||||
[
|
||||
"date"
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"name": "date",
|
||||
"width": 25
|
||||
},
|
||||
{
|
||||
"name": "record",
|
||||
"width": 25
|
||||
},
|
||||
{
|
||||
"name": "baseCode",
|
||||
"width": 25
|
||||
},
|
||||
{
|
||||
"name": "rate",
|
||||
"align": "right"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"name": "date",
|
||||
"width": 25
|
||||
},
|
||||
{
|
||||
"name": "rate",
|
||||
"align": "right"
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"name": "date",
|
||||
"width": 25
|
||||
},
|
||||
{
|
||||
"name": "record",
|
||||
"width": 25
|
||||
},
|
||||
{
|
||||
"name": "baseCode",
|
||||
"width": 25
|
||||
},
|
||||
{
|
||||
"name": "rate",
|
||||
"align": "right"
|
||||
}
|
||||
]
|
||||
@@ -7,9 +7,8 @@
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Currency Rates",
|
||||
"rows": [
|
||||
[{"name": "baseCurrency"}, {"name": "currencyRates"}]
|
||||
[{"name": "baseCurrency"}, false]
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"accessCheckerClassName": "Espo\\Classes\\Acl\\CurrencyRecordRate\\AccessChecker"
|
||||
}
|
||||
@@ -63,7 +63,9 @@
|
||||
"ImportError": "Import",
|
||||
"ImportEml": "Import",
|
||||
"WorkingTimeRange": "WorkingTimeCalendar",
|
||||
"Stream": true
|
||||
"Stream": true,
|
||||
"CurrencyRecord": "boolean:Currency",
|
||||
"CurrencyRecordRate": "Currency"
|
||||
},
|
||||
"fieldLevel": {
|
||||
},
|
||||
@@ -155,7 +157,11 @@
|
||||
"delete": "all"
|
||||
},
|
||||
"Stream": true,
|
||||
"ImportEml": "Import"
|
||||
"ImportEml": "Import",
|
||||
"CurrencyRecordRate": {
|
||||
"read": "yes",
|
||||
"edit": "yes"
|
||||
}
|
||||
}
|
||||
},
|
||||
"valuePermissionList": [
|
||||
|
||||
@@ -34,7 +34,8 @@
|
||||
"label": "Currency",
|
||||
"iconClass": "fas fa-euro-sign",
|
||||
"description": "currency",
|
||||
"recordView": "views/admin/currency"
|
||||
"recordView": "views/admin/currency",
|
||||
"view": "views/admin/currency-main"
|
||||
},
|
||||
{
|
||||
"url": "#Admin/notifications",
|
||||
|
||||
@@ -7,5 +7,8 @@
|
||||
},
|
||||
"addressCountryData": {
|
||||
"className": "Espo\\Classes\\AppParams\\AddressCountryData"
|
||||
},
|
||||
"currencyRates": {
|
||||
"className": "Espo\\Classes\\AppParams\\CurrencyRates"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,6 +168,9 @@
|
||||
},
|
||||
"baselineRole": {
|
||||
"level": "admin"
|
||||
},
|
||||
"currencyRates": {
|
||||
"readOnly": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
"actionClassNameList": [
|
||||
"Espo\\Core\\Rebuild\\Actions\\AddSystemUser",
|
||||
"Espo\\Core\\Rebuild\\Actions\\AddSystemData",
|
||||
"Espo\\Core\\Rebuild\\Actions\\CurrencyRates",
|
||||
"Espo\\Core\\Rebuild\\Actions\\ScheduledJobs",
|
||||
"Espo\\Core\\Rebuild\\Actions\\ConfigMetadataCheck",
|
||||
"Espo\\Core\\Rebuild\\Actions\\GenerateInstanceId",
|
||||
"Espo\\Core\\Rebuild\\Actions\\SetIntegrationDefaults"
|
||||
"Espo\\Core\\Rebuild\\Actions\\SetIntegrationDefaults",
|
||||
"Espo\\Core\\Rebuild\\Actions\\SyncCurrency",
|
||||
"Espo\\Core\\Rebuild\\Actions\\CurrencyRates"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"controller": "controllers/record",
|
||||
"createDisabled": true,
|
||||
"editDisabled": true,
|
||||
"removeDisabled": true,
|
||||
"nameAttribute": "code",
|
||||
"defaultFilterData": {
|
||||
"primary": "active"
|
||||
},
|
||||
"filterList": [
|
||||
"active"
|
||||
],
|
||||
"viewSetupHandlers": {
|
||||
"record/detail": [
|
||||
"handlers/currency-record/record-detail"
|
||||
]
|
||||
},
|
||||
"relationshipPanels": {
|
||||
"rates": {
|
||||
"layout": "listForRecord",
|
||||
"createAttributeMap": {
|
||||
"code": "recordName"
|
||||
},
|
||||
"view": "views/currency-record/record/panels/rates",
|
||||
"unlinkDisabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"controller": "controllers/record",
|
||||
"modelDefaultsPreparator": "handlers/currency-record-rate/default-preparator",
|
||||
"acl": "acl/currency-record-rate",
|
||||
"textFilterDisabled": true
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
{
|
||||
"fields": {
|
||||
"code": {
|
||||
"type": "varchar",
|
||||
"maxLength": 3,
|
||||
"required": true,
|
||||
"readOnly": true,
|
||||
"index": true
|
||||
},
|
||||
"status": {
|
||||
"type": "enum",
|
||||
"options": [
|
||||
"Active",
|
||||
"Inactive"
|
||||
],
|
||||
"default": "Active",
|
||||
"maxLength": 8,
|
||||
"style": {
|
||||
"Inactive": "info"
|
||||
}
|
||||
},
|
||||
"label": {
|
||||
"type": "varchar",
|
||||
"notStorable": true,
|
||||
"readOnly": true,
|
||||
"orderDisabled": true,
|
||||
"loaderClassName": "Espo\\Classes\\FieldProcessing\\CurrencyRecord\\Label"
|
||||
},
|
||||
"symbol": {
|
||||
"type": "varchar",
|
||||
"notStorable": true,
|
||||
"readOnly": true,
|
||||
"orderDisabled": true,
|
||||
"loaderClassName": "Espo\\Classes\\FieldProcessing\\CurrencyRecord\\Symbol"
|
||||
},
|
||||
"rateDate": {
|
||||
"type": "date",
|
||||
"readOnly": true,
|
||||
"notStorable": true,
|
||||
"orderDisabled": true,
|
||||
"loaderClassName": "Espo\\Classes\\FieldProcessing\\CurrencyRecord\\RateDate"
|
||||
},
|
||||
"rate": {
|
||||
"type": "decimal",
|
||||
"readOnly": true,
|
||||
"notStorable": true,
|
||||
"orderDisabled": true,
|
||||
"decimalPlaces": 6,
|
||||
"loaderClassName": "Espo\\Classes\\FieldProcessing\\CurrencyRecord\\Rate",
|
||||
"view": "views/currency-record-rate/fields/rate"
|
||||
},
|
||||
"isBase": {
|
||||
"type": "bool",
|
||||
"readOnly": true,
|
||||
"notStorable": true,
|
||||
"orderDisabled": true,
|
||||
"loaderClassName": "Espo\\Classes\\FieldProcessing\\CurrencyRecord\\IsBase"
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"rates": {
|
||||
"type": "hasMany",
|
||||
"entity": "CurrencyRecordRate",
|
||||
"foreign": "record",
|
||||
"readOnly": true,
|
||||
"orderBy": "date",
|
||||
"order": "desc"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"codeDeleteId": {
|
||||
"type": "unique",
|
||||
"columns": [
|
||||
"code",
|
||||
"deleteId"
|
||||
]
|
||||
}
|
||||
},
|
||||
"deleteId": true,
|
||||
"collection": {
|
||||
"textFilterFields": [
|
||||
"code"
|
||||
],
|
||||
"orderBy": "code",
|
||||
"order": "asc"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"fields": {
|
||||
"record": {
|
||||
"type": "link",
|
||||
"required": true,
|
||||
"readOnlyAfterCreate": true,
|
||||
"validatorClassNameList": [
|
||||
"Espo\\Classes\\FieldValidators\\CurrencyRecordRate\\Record\\NonBase"
|
||||
]
|
||||
},
|
||||
"baseCode": {
|
||||
"type": "varchar",
|
||||
"readOnly": true,
|
||||
"maxLength": 3
|
||||
},
|
||||
"date": {
|
||||
"type": "date",
|
||||
"required": true,
|
||||
"readOnlyAfterCreate": true,
|
||||
"default": "javascript: return this.dateTime.getToday();"
|
||||
},
|
||||
"rate": {
|
||||
"type": "decimal",
|
||||
"decimalPlaces": 6,
|
||||
"min": 0.0001,
|
||||
"precision": 15,
|
||||
"scale": 8,
|
||||
"required": true,
|
||||
"audited": true,
|
||||
"view": "views/currency-record-rate/fields/rate"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "datetime",
|
||||
"readOnly": true
|
||||
},
|
||||
"modifiedAt": {
|
||||
"type": "datetime",
|
||||
"readOnly": true
|
||||
},
|
||||
"createdBy": {
|
||||
"type": "link",
|
||||
"readOnly": true,
|
||||
"view": "views/fields/user",
|
||||
"fieldManagerParamList": []
|
||||
},
|
||||
"modifiedBy": {
|
||||
"type": "link",
|
||||
"readOnly": true,
|
||||
"view": "views/fields/user",
|
||||
"fieldManagerParamList": []
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"record": {
|
||||
"type": "belongsTo",
|
||||
"entity": "CurrencyRecord",
|
||||
"foreignName": "code"
|
||||
},
|
||||
"createdBy": {
|
||||
"type": "belongsTo",
|
||||
"entity": "User"
|
||||
},
|
||||
"modifiedBy": {
|
||||
"type": "belongsTo",
|
||||
"entity": "User"
|
||||
}
|
||||
},
|
||||
"indexes": {
|
||||
"recordIdBaseCodeDate": {
|
||||
"type": "unique",
|
||||
"columns": [
|
||||
"recordId",
|
||||
"baseCode",
|
||||
"date",
|
||||
"deleteId"
|
||||
]
|
||||
}
|
||||
},
|
||||
"deleteId": true,
|
||||
"collection": {
|
||||
"orderBy": "date",
|
||||
"order": "desc"
|
||||
}
|
||||
}
|
||||
@@ -191,8 +191,7 @@
|
||||
"view": "views/settings/fields/default-currency"
|
||||
},
|
||||
"currencyRates": {
|
||||
"type": "base",
|
||||
"view": "views/settings/fields/currency-rates"
|
||||
"type": "base"
|
||||
},
|
||||
"outboundEmailIsShared": {
|
||||
"type": "bool",
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"fields": {
|
||||
"baseCode": {
|
||||
"visible": {
|
||||
"conditionGroup": [
|
||||
{
|
||||
"type": "isNotEmpty",
|
||||
"attribute": "id"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"beforeCreateHookClassNameList": [
|
||||
"Espo\\Classes\\RecordHooks\\CurrencyRecordRate\\BeforeSaveValidation"
|
||||
],
|
||||
"beforeUpdateHookClassNameList": [
|
||||
"Espo\\Classes\\RecordHooks\\CurrencyRecordRate\\BeforeSaveValidation"
|
||||
],
|
||||
"afterCreateHookClassNameList": [
|
||||
"Espo\\Classes\\RecordHooks\\CurrencyRecordRate\\AfterSave"
|
||||
],
|
||||
"afterUpdateHookClassNameList": [
|
||||
"Espo\\Classes\\RecordHooks\\CurrencyRecordRate\\AfterSave"
|
||||
],
|
||||
"afterDeleteHookClassNameList": [
|
||||
"Espo\\Classes\\RecordHooks\\CurrencyRecordRate\\AfterDelete"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"entity": true,
|
||||
"tab": true
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"entity": true,
|
||||
"preserveAuditLog": true
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"primaryFilterClassNameMap": {
|
||||
"active": "Espo\\Classes\\Select\\CurrencyRecord\\PrimaryFilters\\Active"
|
||||
},
|
||||
"selectAttributesDependencyMap": {
|
||||
"id": [
|
||||
"code"
|
||||
]
|
||||
},
|
||||
"accessControlFilterResolverClassName": "Espo\\Core\\Select\\AccessControl\\FilterResolvers\\Boolean"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"selectAttributesDependencyMap": {
|
||||
"id": [
|
||||
"recordId",
|
||||
"recordName",
|
||||
"baseCode"
|
||||
]
|
||||
},
|
||||
"accessControlFilterResolverClassName": "Espo\\Core\\Select\\AccessControl\\FilterResolvers\\Boolean"
|
||||
}
|
||||
@@ -35,7 +35,6 @@ use Espo\Entities\Email;
|
||||
use Espo\Entities\Settings;
|
||||
use Espo\ORM\Entity;
|
||||
use Espo\ORM\EntityManager;
|
||||
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Error;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
@@ -50,10 +49,9 @@ use Espo\Core\Utils\Metadata;
|
||||
use Espo\Core\Utils\Config;
|
||||
use Espo\Core\Utils\Config\ConfigWriter;
|
||||
use Espo\Core\Utils\Config\Access;
|
||||
|
||||
use Espo\Entities\Portal;
|
||||
use Espo\Repositories\Portal as PortalRepository;
|
||||
|
||||
use Espo\Tools\Currency\RecordManager as CurrencyRecordManager;
|
||||
use stdClass;
|
||||
|
||||
class SettingsService
|
||||
@@ -74,6 +72,8 @@ class SettingsService
|
||||
private Config\SystemConfig $systemConfig,
|
||||
private EmailConfigDataProvider $emailConfigDataProvider,
|
||||
private Acl\Cache\Clearer $aclCacheClearer,
|
||||
private CurrencyRecordManager $currencyRecordManager,
|
||||
private CurrencyDatabasePopulator $currencyDatabasePopulator,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -243,8 +243,9 @@ class SettingsService
|
||||
$this->aclCacheClearer->clearForAllInternalUsers();
|
||||
}
|
||||
|
||||
if (isset($data->defaultCurrency) || isset($data->baseCurrency) || isset($data->currencyRates)) {
|
||||
$this->populateDatabaseWithCurrencyRates();
|
||||
if (isset($data->baseCurrency) || isset($data->currencyList) || isset($data->defaultCurrency)) {
|
||||
$this->currencyRecordManager->sync();
|
||||
$this->currencyDatabasePopulator->process();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,11 +341,6 @@ class SettingsService
|
||||
}
|
||||
}
|
||||
|
||||
private function populateDatabaseWithCurrencyRates(): void
|
||||
{
|
||||
$this->injectableFactory->create(CurrencyDatabasePopulator::class)->process();
|
||||
}
|
||||
|
||||
private function filterData(stdClass $data): void
|
||||
{
|
||||
$user = $this->applicationState->getUser();
|
||||
|
||||
35
application/Espo/Tools/Currency/Exceptions/NotEnabled.php
Normal file
35
application/Espo/Tools/Currency/Exceptions/NotEnabled.php
Normal file
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\Currency\Exceptions;
|
||||
|
||||
use Exception;
|
||||
|
||||
class NotEnabled extends Exception
|
||||
{}
|
||||
163
application/Espo/Tools/Currency/RateEntryProvider.php
Normal file
163
application/Espo/Tools/Currency/RateEntryProvider.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\Currency;
|
||||
|
||||
use Espo\Core\Currency\ConfigDataProvider;
|
||||
use Espo\Core\Field\Date;
|
||||
use Espo\Core\Utils\DateTime;
|
||||
use Espo\Entities\CurrencyRecord;
|
||||
use Espo\Entities\CurrencyRecordRate;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Query\Part\Order;
|
||||
use Espo\Tools\Currency\Exceptions\NotEnabled;
|
||||
use WeakMap;
|
||||
|
||||
/**
|
||||
* @since 9.3.0
|
||||
* @internal
|
||||
*/
|
||||
class RateEntryProvider
|
||||
{
|
||||
/** @var WeakMap<CurrencyRecord, ?CurrencyRecordRate> */
|
||||
private WeakMap $map;
|
||||
|
||||
public function __construct(
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
private EntityManager $entityManager,
|
||||
private DateTime $dateTime,
|
||||
) {
|
||||
$this->map = new WeakMap();
|
||||
}
|
||||
|
||||
public function getCurrentRateEntry(CurrencyRecord $record): ?CurrencyRecordRate
|
||||
{
|
||||
if (!$this->map->offsetExists($record)) {
|
||||
$this->map[$record] = $this->entityManager
|
||||
->getRDBRepositoryByClass(CurrencyRecordRate::class)
|
||||
->where([
|
||||
CurrencyRecordRate::ATTR_RECORD_ID => $record->getId(),
|
||||
CurrencyRecordRate::FIELD_BASE_CODE => $this->configDataProvider->getBaseCurrency(),
|
||||
CurrencyRecordRate::FIELD_DATE . '<=' => $this->dateTime->getToday()->toString(),
|
||||
])
|
||||
->order(CurrencyRecordRate::FIELD_DATE, Order::DESC)
|
||||
->findOne();
|
||||
}
|
||||
|
||||
return $this->map[$record];
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotEnabled
|
||||
*/
|
||||
public function prepareNew(string $code, Date $date): CurrencyRecordRate
|
||||
{
|
||||
$record = $this->getRecordByCode($code);
|
||||
|
||||
$entry = $this->entityManager->getRDBRepositoryByClass(CurrencyRecordRate::class)->getNew();
|
||||
|
||||
$entry
|
||||
->setRecord($record)
|
||||
->setDate($date);
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rate against the base currency by a record.
|
||||
*
|
||||
* @return ?numeric-string
|
||||
*/
|
||||
private function getRateByRecord(CurrencyRecord $record): ?string
|
||||
{
|
||||
$rateEntry = $this->entityManager
|
||||
->getRDBRepositoryByClass(CurrencyRecordRate::class)
|
||||
->where([
|
||||
CurrencyRecordRate::ATTR_RECORD_ID => $record->getId(),
|
||||
CurrencyRecordRate::FIELD_BASE_CODE => $this->configDataProvider->getBaseCurrency(),
|
||||
CurrencyRecordRate::FIELD_DATE . '<=' => $this->dateTime->getToday()->toString(),
|
||||
])
|
||||
->order(CurrencyRecordRate::FIELD_DATE, Order::DESC)
|
||||
->findOne();
|
||||
|
||||
return $rateEntry?->getRate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rate against the base currency.
|
||||
*
|
||||
* @param string $code
|
||||
* @return ?numeric-string
|
||||
* @throws NotEnabled
|
||||
*/
|
||||
public function getRate(string $code): ?string
|
||||
{
|
||||
$record = $this->getRecordByCode($code);
|
||||
|
||||
return $this->getRateByRecord($record);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotEnabled
|
||||
*/
|
||||
public function getRateEntryOnDate(string $code, Date $date): ?CurrencyRecordRate
|
||||
{
|
||||
$record = $this->getRecordByCode($code);
|
||||
|
||||
return $this->entityManager
|
||||
->getRDBRepositoryByClass(CurrencyRecordRate::class)
|
||||
->where([
|
||||
CurrencyRecordRate::ATTR_RECORD_ID => $record->getId(),
|
||||
CurrencyRecordRate::FIELD_BASE_CODE => $this->configDataProvider->getBaseCurrency(),
|
||||
CurrencyRecordRate::FIELD_DATE => $date->toString(),
|
||||
])
|
||||
->order(CurrencyRecordRate::FIELD_DATE, Order::DESC)
|
||||
->findOne();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotEnabled
|
||||
*/
|
||||
private function getRecordByCode(string $code): CurrencyRecord
|
||||
{
|
||||
$record = $this->entityManager
|
||||
->getRDBRepositoryByClass(CurrencyRecord::class)
|
||||
->where([
|
||||
CurrencyRecord::FIELD_CODE => $code,
|
||||
CurrencyRecord::FIELD_STATUS => CurrencyRecord::STATUS_ACTIVE,
|
||||
])
|
||||
->findOne();
|
||||
|
||||
if (!$record) {
|
||||
throw new NotEnabled("Currency $code is not enabled.");
|
||||
}
|
||||
|
||||
return $record;
|
||||
}
|
||||
}
|
||||
@@ -35,18 +35,23 @@ use Espo\Core\Currency\Rates;
|
||||
use Espo\Core\Exceptions\BadRequest;
|
||||
use Espo\Core\Exceptions\Forbidden;
|
||||
use Espo\Core\Acl;
|
||||
use Espo\Core\Utils\Config\ConfigWriter;
|
||||
use Espo\Core\Utils\Currency\DatabasePopulator;
|
||||
use Espo\Core\Utils\DateTime;
|
||||
use Espo\ORM\EntityManager;
|
||||
use RuntimeException;
|
||||
|
||||
class RateService
|
||||
{
|
||||
private const SCOPE = 'Currency';
|
||||
private const string SCOPE = 'Currency';
|
||||
|
||||
public function __construct(
|
||||
private ConfigWriter $configWriter,
|
||||
private Acl $acl,
|
||||
private DatabasePopulator $databasePopulator,
|
||||
private ConfigDataProvider $configDataProvider
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
private RecordManager $recordManager,
|
||||
private RateEntryProvider $rateEntryProvider,
|
||||
private DateTime $dateTime,
|
||||
private EntityManager $entityManager,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -54,13 +59,7 @@ class RateService
|
||||
*/
|
||||
public function get(): Rates
|
||||
{
|
||||
if (!$this->acl->check(self::SCOPE)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
if ($this->acl->getLevel(self::SCOPE, Table::ACTION_READ) !== Table::LEVEL_YES) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
$this->checkReadAccess();
|
||||
|
||||
$rates = Rates::create($this->configDataProvider->getBaseCurrency());
|
||||
|
||||
@@ -76,6 +75,62 @@ class RateService
|
||||
* @throws Forbidden
|
||||
*/
|
||||
public function set(Rates $rates): void
|
||||
{
|
||||
$this->checkEditAccess();
|
||||
|
||||
$codeList = $this->configDataProvider->getCurrencyList();
|
||||
$base = $this->configDataProvider->getBaseCurrency();
|
||||
|
||||
foreach ($rates->toAssoc() as $code => $value) {
|
||||
if ($value < 0) {
|
||||
throw new BadRequest("Bad value.");
|
||||
}
|
||||
|
||||
if (!in_array($code, $codeList) || $code === $base) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->writeOne($code, $value);
|
||||
}
|
||||
|
||||
$this->recordManager->syncToConfig();
|
||||
$this->databasePopulator->process();
|
||||
}
|
||||
|
||||
private function writeOne(string $code, float $value): void
|
||||
{
|
||||
$date = $this->dateTime->getToday();
|
||||
|
||||
try {
|
||||
$rateEntry = $this->rateEntryProvider->getRateEntryOnDate($code, $date) ??
|
||||
$this->rateEntryProvider->prepareNew($code, $date);
|
||||
} catch (Exceptions\NotEnabled $e) {
|
||||
throw new RuntimeException($e->getMessage(), previous: $e);
|
||||
}
|
||||
|
||||
$rateEntry->setRate((string) $value);
|
||||
|
||||
$this->entityManager->saveEntity($rateEntry);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function checkReadAccess(): void
|
||||
{
|
||||
if (!$this->acl->check(self::SCOPE)) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
if ($this->acl->getLevel(self::SCOPE, Table::ACTION_READ) !== Table::LEVEL_YES) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Forbidden
|
||||
*/
|
||||
private function checkEditAccess(): void
|
||||
{
|
||||
if (!$this->acl->check(self::SCOPE)) {
|
||||
throw new Forbidden();
|
||||
@@ -84,39 +139,5 @@ class RateService
|
||||
if ($this->acl->getLevel(self::SCOPE, Table::ACTION_EDIT) !== Table::LEVEL_YES) {
|
||||
throw new Forbidden();
|
||||
}
|
||||
|
||||
$currencyList = $this->configDataProvider->getCurrencyList();
|
||||
$baseCurrency = $this->configDataProvider->getBaseCurrency();
|
||||
|
||||
$set = [];
|
||||
|
||||
foreach ($rates->toAssoc() as $key => $value) {
|
||||
if ($value < 0) {
|
||||
throw new BadRequest("Bad value.");
|
||||
}
|
||||
|
||||
if (!in_array($key, $currencyList)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($key === $baseCurrency) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$set[$key] = $value;
|
||||
}
|
||||
|
||||
foreach ($currencyList as $currency) {
|
||||
if ($currency === $baseCurrency) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$set[$currency] ??= $this->configDataProvider->getCurrencyRate($currency);
|
||||
}
|
||||
|
||||
$this->configWriter->set('currencyRates', $set);
|
||||
$this->configWriter->save();
|
||||
|
||||
$this->databasePopulator->process();
|
||||
}
|
||||
}
|
||||
|
||||
144
application/Espo/Tools/Currency/RecordManager.php
Normal file
144
application/Espo/Tools/Currency/RecordManager.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
namespace Espo\Tools\Currency;
|
||||
|
||||
use Espo\Core\Currency\ConfigDataProvider;
|
||||
use Espo\Core\Utils\Config\ConfigWriter;
|
||||
use Espo\Entities\CurrencyRecord;
|
||||
use Espo\ORM\EntityManager;
|
||||
use Espo\ORM\Query\UpdateBuilder;
|
||||
use Espo\Tools\Currency\Exceptions\NotEnabled;
|
||||
|
||||
class RecordManager
|
||||
{
|
||||
public function __construct(
|
||||
private ConfigDataProvider $configDataProvider,
|
||||
private EntityManager $entityManager,
|
||||
private ConfigWriter $configWriter,
|
||||
private RateEntryProvider $rateEntryProvider,
|
||||
) {}
|
||||
|
||||
public function sync(): void
|
||||
{
|
||||
$this->entityManager->getTransactionManager()->run(function () {
|
||||
$this->syncInTransaction();
|
||||
});
|
||||
|
||||
$this->syncToConfig();
|
||||
}
|
||||
|
||||
private function syncInTransaction(): void
|
||||
{
|
||||
$this->lock();
|
||||
|
||||
foreach ($this->configDataProvider->getCurrencyList() as $code) {
|
||||
$this->syncCode($code);
|
||||
}
|
||||
|
||||
$this->deactivateNotListed();
|
||||
}
|
||||
|
||||
private function syncCode(string $code): void
|
||||
{
|
||||
$record = $this->entityManager
|
||||
->getRDBRepositoryByClass(CurrencyRecord::class)
|
||||
->where([CurrencyRecord::FIELD_CODE => $code])
|
||||
->findOne();
|
||||
|
||||
if (!$record) {
|
||||
$record = $this->entityManager->getRDBRepositoryByClass(CurrencyRecord::class)->getNew();
|
||||
|
||||
$record->setCode($code);
|
||||
}
|
||||
|
||||
$record->setStatus(CurrencyRecord::STATUS_ACTIVE);
|
||||
|
||||
$this->entityManager->saveEntity($record);
|
||||
}
|
||||
|
||||
private function deactivateNotListed(): void
|
||||
{
|
||||
$list = $this->configDataProvider->getCurrencyList();
|
||||
|
||||
$updateQuery = UpdateBuilder::create()
|
||||
->in(CurrencyRecord::ENTITY_TYPE)
|
||||
->set([
|
||||
CurrencyRecord::FIELD_STATUS => CurrencyRecord::STATUS_INACTIVE,
|
||||
])
|
||||
->where([
|
||||
CurrencyRecord::FIELD_CODE . '!=' => $list,
|
||||
])
|
||||
->build();
|
||||
|
||||
$this->entityManager->getQueryExecutor()->execute($updateQuery);
|
||||
}
|
||||
|
||||
private function lock(): void
|
||||
{
|
||||
$this->entityManager
|
||||
->getRDBRepositoryByClass(CurrencyRecord::class)
|
||||
->forUpdate()
|
||||
->sth()
|
||||
->find();
|
||||
}
|
||||
|
||||
public function syncToConfig(): void
|
||||
{
|
||||
$rates = [];
|
||||
|
||||
foreach ($this->configDataProvider->getCurrencyList() as $code) {
|
||||
try {
|
||||
$rate = $this->rateEntryProvider->getRate($code) ?? '1.0';
|
||||
} catch (Exceptions\NotEnabled) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rates[$code] = (float) $rate;
|
||||
}
|
||||
|
||||
$this->configWriter->set('currencyRates', $rates);
|
||||
$this->configWriter->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws NotEnabled
|
||||
*/
|
||||
public function syncCodeToConfig(string $code): void
|
||||
{
|
||||
$rates = $this->configDataProvider->getCurrencyRates()->toAssoc();
|
||||
|
||||
$rate = $this->rateEntryProvider->getRate($code) ?? '1.0';
|
||||
|
||||
$rates[$code] = (float) $rate;
|
||||
|
||||
$this->configWriter->set('currencyRates', $rates);
|
||||
$this->configWriter->save();
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
data-currency="{{@key}}"
|
||||
value="{{./this}}"
|
||||
style="text-align: right;"
|
||||
readonly="readonly"
|
||||
>
|
||||
</span>
|
||||
<span class="input-group-addon radius-right" style="width: 22%">{{../baseCurrency}}</span>
|
||||
|
||||
40
client/src/acl/currency-record-rate.js
Normal file
40
client/src/acl/currency-record-rate.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
import Acl from 'acl';
|
||||
|
||||
export default class extends Acl {
|
||||
|
||||
checkScope(data, action, precise, entityAccessData) {
|
||||
if (action === 'create' || action === 'delete') {
|
||||
action = 'edit';
|
||||
}
|
||||
|
||||
return super.checkScope(data, action, precise, entityAccessData);
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,7 @@ import SettingsEditView from 'views/settings/edit';
|
||||
import AdminIndexView from 'views/admin/index';
|
||||
import {inject} from 'di';
|
||||
import Language from 'language';
|
||||
import EditView from 'views/edit';
|
||||
|
||||
class AdminController extends Controller {
|
||||
|
||||
@@ -51,7 +52,7 @@ class AdminController extends Controller {
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
actionPage(options) {
|
||||
async actionPage(options) {
|
||||
const page = options.page;
|
||||
|
||||
if (options.options) {
|
||||
@@ -81,7 +82,7 @@ class AdminController extends Controller {
|
||||
throw new Espo.Exceptions.NotFound();
|
||||
}
|
||||
|
||||
if (defs.view) {
|
||||
if (defs.view && !defs.recordView) {
|
||||
this.main(defs.view, options);
|
||||
|
||||
return;
|
||||
@@ -93,23 +94,32 @@ class AdminController extends Controller {
|
||||
|
||||
const model = this.getSettingsModel();
|
||||
|
||||
model.fetch().then(() => {
|
||||
model.id = '1';
|
||||
await model.fetch();
|
||||
|
||||
const editView = new SettingsEditView({
|
||||
model: model,
|
||||
headerTemplate: 'admin/settings/headers/page',
|
||||
recordView: defs.recordView,
|
||||
page: page,
|
||||
label: defs.label,
|
||||
optionsToPass: [
|
||||
'page',
|
||||
'label',
|
||||
],
|
||||
});
|
||||
model.id = '1';
|
||||
|
||||
this.main(editView);
|
||||
const view = defs.view ?? 'views/settings/edit';
|
||||
|
||||
const ViewClass = await Espo.loader.requirePromise(view);
|
||||
|
||||
if (!EditView.isPrototypeOf(ViewClass)) {
|
||||
throw new Error("View should inherit views/edit.");
|
||||
}
|
||||
|
||||
const editView = new ViewClass({
|
||||
model: model,
|
||||
headerTemplate: 'admin/settings/headers/page',
|
||||
recordView: defs.recordView,
|
||||
page: page,
|
||||
label: defs.label,
|
||||
optionsToPass: [
|
||||
'page',
|
||||
'label',
|
||||
],
|
||||
});
|
||||
|
||||
this.main(editView);
|
||||
|
||||
}
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
import DefaultsPreparator from 'handlers/model/defaults-preparator';
|
||||
import {inject} from 'di';
|
||||
import Settings from 'models/settings';
|
||||
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export default class extends DefaultsPreparator {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Settings}
|
||||
*/
|
||||
@inject(Settings)
|
||||
config
|
||||
|
||||
async prepare(model) {
|
||||
return {
|
||||
baseCode: this.config.get('baseCurrency'),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
45
client/src/handlers/currency-record/record-detail.js
Normal file
45
client/src/handlers/currency-record/record-detail.js
Normal file
@@ -0,0 +1,45 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
export default class CurrencyRecordRecordDetailHandler {
|
||||
|
||||
/**
|
||||
* @param {import('views/record/detail').default} view
|
||||
*/
|
||||
constructor(view) {
|
||||
this.view = view;
|
||||
}
|
||||
|
||||
process() {
|
||||
const model = this.view.model;
|
||||
|
||||
this.view.listenTo(model, 'after:relate:rates after:unrelate:rates after:related-change:rates', () => {
|
||||
model.fetch();
|
||||
});
|
||||
}
|
||||
}
|
||||
49
client/src/views/admin/currency-main.js
Normal file
49
client/src/views/admin/currency-main.js
Normal file
@@ -0,0 +1,49 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
import EditView from 'views/edit';
|
||||
|
||||
export default class CurrencyMainView extends EditView {
|
||||
|
||||
scope = 'Settings'
|
||||
|
||||
getHeader() {
|
||||
return this.buildHeaderHtml([
|
||||
(() => {
|
||||
const a = document.createElement('a');
|
||||
a.href = '#Admin';
|
||||
a.text = this.translate('Administration');
|
||||
|
||||
return a;
|
||||
})(),
|
||||
(() => {
|
||||
return this.options.label;
|
||||
})(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,7 @@
|
||||
************************************************************************/
|
||||
|
||||
import SettingsEditRecordView from 'views/settings/record/edit';
|
||||
import EditView from 'views/edit';
|
||||
|
||||
export default class extends SettingsEditRecordView {
|
||||
|
||||
@@ -65,6 +66,22 @@ export default class extends SettingsEditRecordView {
|
||||
});
|
||||
|
||||
this.controlCurrencyRatesVisibility();
|
||||
|
||||
this.whenReady().then(() => {
|
||||
const view = /** @type {EditView} view */
|
||||
this.getParentView();
|
||||
|
||||
if (!view instanceof EditView) {
|
||||
return;
|
||||
}
|
||||
|
||||
view.addMenuItem('buttons', {
|
||||
name: 'currencyRecords',
|
||||
link: '#CurrencyRecord',
|
||||
labelTranslation: 'Settings.labels.Currency Rates',
|
||||
iconClass: 'fas fa-euro-sign',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
controlCurrencyRatesVisibility() {
|
||||
|
||||
98
client/src/views/currency-record-rate/fields/rate.js
Normal file
98
client/src/views/currency-record-rate/fields/rate.js
Normal file
@@ -0,0 +1,98 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
import DecimalFieldView from 'views/fields/decimal';
|
||||
|
||||
export default class CurrencyRecordRateRateFieldView extends DecimalFieldView {
|
||||
|
||||
// language=Handlebars
|
||||
listTemplateContent = `
|
||||
{{#if isNotEmpty~}}
|
||||
<span class="text-soft">{{targetCode}} = </span>
|
||||
<span class="numeric-text">{{value}}</span>
|
||||
<span class="text-soft">{{baseCode}}</span>
|
||||
{{~/if~}}
|
||||
`
|
||||
|
||||
// language=Handlebars
|
||||
detailTemplateContent = `
|
||||
{{~#if isNotEmpty~}}
|
||||
<span class="text-soft">{{targetCode}} = </span>
|
||||
<span class="numeric-text">{{value}}</span>
|
||||
<span class="text-soft">{{baseCode}}</span>
|
||||
{{~else~}}
|
||||
{{~#if valueIsSet~}}
|
||||
<span class="none-value">{{translate 'None'}}</span>
|
||||
{{~else~}}<span class="loading-value"></span>
|
||||
{{~/if}}
|
||||
{{~/if~}}
|
||||
`
|
||||
|
||||
// language=Handlebars
|
||||
editTemplateContent = `
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon radius-left" style="width: 24%">1 {{targetCode}} = </span>
|
||||
<span class="input-group-item">
|
||||
<input
|
||||
type="text"
|
||||
class="main-element form-control numeric-text"
|
||||
data-name="{{name}}"
|
||||
value="{{value}}"
|
||||
autocomplete="espo-{{name}}"
|
||||
pattern="[\\-]?[0-9]*"
|
||||
style="text-align: end;"
|
||||
>
|
||||
</span>
|
||||
<span class="input-group-addon radius-right" style="width: 21%">{{baseCode}}</span>
|
||||
</div>
|
||||
`
|
||||
|
||||
getAttributeList() {
|
||||
return [
|
||||
...super.getAttributeList(),
|
||||
'baseCode',
|
||||
'recordName',
|
||||
];
|
||||
}
|
||||
|
||||
data() {
|
||||
let baseCode = this.model.attributes.baseCode;
|
||||
let targetCode = this.model.attributes.recordName;
|
||||
|
||||
if (this.model.entityType === 'CurrencyRecord') {
|
||||
baseCode = this.getConfig().get('baseCurrency');
|
||||
targetCode = this.model.attributes.code;
|
||||
}
|
||||
|
||||
return {
|
||||
...super.data(),
|
||||
baseCode,
|
||||
targetCode,
|
||||
}
|
||||
}
|
||||
}
|
||||
40
client/src/views/currency-record/record/panels/rates.js
Normal file
40
client/src/views/currency-record/record/panels/rates.js
Normal file
@@ -0,0 +1,40 @@
|
||||
/************************************************************************
|
||||
* This file is part of EspoCRM.
|
||||
*
|
||||
* EspoCRM – Open Source CRM application.
|
||||
* Copyright (C) 2014-2025 EspoCRM, Inc.
|
||||
* Website: https://www.espocrm.com
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* The interactive user interfaces in modified source and object code versions
|
||||
* of this program must display Appropriate Legal Notices, as required under
|
||||
* Section 5 of the GNU Affero General Public License version 3.
|
||||
*
|
||||
* In accordance with Section 7(b) of the GNU Affero General Public License version 3,
|
||||
* these Appropriate Legal Notices must retain the display of the "EspoCRM" word.
|
||||
************************************************************************/
|
||||
|
||||
import RelationshipPanelView from 'views/record/panels/relationship';
|
||||
|
||||
export default class RatesPanelView extends RelationshipPanelView {
|
||||
|
||||
setup() {
|
||||
if (this.model.attributes.code === this.getConfig().get('baseCurrency')) {
|
||||
this.defs.createDisabled = true;
|
||||
}
|
||||
|
||||
super.setup();
|
||||
}
|
||||
}
|
||||
@@ -28,23 +28,33 @@
|
||||
|
||||
import BaseFieldView from 'views/fields/base';
|
||||
|
||||
/**
|
||||
* Not used.
|
||||
* @todo Remove.
|
||||
*/
|
||||
export default class extends BaseFieldView {
|
||||
|
||||
editTemplate = 'settings/fields/currency-rates/edit'
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {string}
|
||||
*/
|
||||
baseCode
|
||||
|
||||
data() {
|
||||
const baseCurrency = this.model.get('baseCurrency');
|
||||
const baseCode = this.baseCode;
|
||||
const currencyRates = this.model.get('currencyRates') || {};
|
||||
|
||||
const rateValues = {};
|
||||
|
||||
(this.model.get('currencyList') || []).forEach(currency => {
|
||||
if (currency !== baseCurrency) {
|
||||
if (currency !== baseCode) {
|
||||
rateValues[currency] = currencyRates[currency];
|
||||
|
||||
if (!rateValues[currency]) {
|
||||
if (currencyRates[baseCurrency]) {
|
||||
rateValues[currency] = Math.round(1 / currencyRates[baseCurrency] * 1000) / 1000;
|
||||
if (currencyRates[baseCode]) {
|
||||
rateValues[currency] = Math.round(1 / currencyRates[baseCode] * 1000) / 1000;
|
||||
}
|
||||
|
||||
if (!rateValues[currency]) {
|
||||
@@ -56,36 +66,23 @@ export default class extends BaseFieldView {
|
||||
|
||||
return {
|
||||
rateValues: rateValues,
|
||||
baseCurrency: baseCurrency,
|
||||
baseCurrency: baseCode,
|
||||
};
|
||||
}
|
||||
|
||||
fetch() {
|
||||
const data = {};
|
||||
const currencyRates = {};
|
||||
setup() {
|
||||
const sync = () => {
|
||||
this.baseCode = this.model.get('baseCurrency');
|
||||
};
|
||||
|
||||
const baseCurrency = this.model.get('baseCurrency');
|
||||
sync();
|
||||
|
||||
const currencyList = this.model.get('currencyList') || [];
|
||||
|
||||
currencyList.forEach(currency => {
|
||||
if (currency !== baseCurrency) {
|
||||
const value = this.$el.find(`input[data-currency="${currency}"]`).val() || '1';
|
||||
|
||||
currencyRates[currency] = parseFloat(value);
|
||||
}
|
||||
this.listenTo(this.model, 'sync', () => {
|
||||
sync();
|
||||
});
|
||||
}
|
||||
|
||||
delete currencyRates[baseCurrency];
|
||||
|
||||
for (const c in currencyRates) {
|
||||
if (!~currencyList.indexOf(c)) {
|
||||
delete currencyRates[c];
|
||||
}
|
||||
}
|
||||
|
||||
data[this.name] = currencyRates;
|
||||
|
||||
return data;
|
||||
fetch() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,11 +80,11 @@
|
||||
"description": "A 'create' access level."
|
||||
},
|
||||
"read": {
|
||||
"$ref": "#/definitions/levels",
|
||||
"$ref": "#/definitions/levelsAll",
|
||||
"description": "A 'read' access level."
|
||||
},
|
||||
"edit": {
|
||||
"$ref": "#/definitions/levels",
|
||||
"$ref": "#/definitions/levelsAll",
|
||||
"description": "An 'edit' access level."
|
||||
},
|
||||
"delete": {
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
},
|
||||
"view": {
|
||||
"type": "string",
|
||||
"description": "A view. Not compatible with `recordView`."
|
||||
"description": "A view. Compatible with `recordView` as of v9.3."
|
||||
},
|
||||
"tabQuickSearch": {
|
||||
"type": "boolean",
|
||||
|
||||
@@ -37,15 +37,17 @@ use Espo\Core\Currency\Rates;
|
||||
use Espo\Core\Field\Currency;
|
||||
use Espo\Core\InjectableFactory;
|
||||
use Espo\Core\Utils\Config\ConfigWriter;
|
||||
use Espo\Tools\Currency\RecordManager;
|
||||
use tests\integration\Core\BaseTestCase;
|
||||
|
||||
class CurrencyTest extends \tests\integration\Core\BaseTestCase
|
||||
class CurrencyTest extends BaseTestCase
|
||||
{
|
||||
/**
|
||||
* @noinspection PhpUnhandledExceptionInspection
|
||||
*/
|
||||
public function testSetCurrencyRates(): void
|
||||
{
|
||||
$app = $this->createApplication();
|
||||
|
||||
/** @var InjectableFactory $factory */
|
||||
$factory = $app->getContainer()->get('injectableFactory');
|
||||
$factory = $this->getInjectableFactory();
|
||||
|
||||
$configWriter = $factory->create(ConfigWriter::class);
|
||||
|
||||
@@ -55,8 +57,11 @@ class CurrencyTest extends \tests\integration\Core\BaseTestCase
|
||||
$configWriter->set('currencyRates', [
|
||||
'EUR' => 1.2,
|
||||
]);
|
||||
|
||||
$configWriter->save();
|
||||
|
||||
$this->getInjectableFactory()->create(RecordManager::class)->sync();
|
||||
|
||||
$service = $factory->create(RateService::class);
|
||||
|
||||
$rates = Rates::fromAssoc(['EUR' => 1.3], '___');
|
||||
|
||||
Reference in New Issue
Block a user