diff --git a/application/Espo/Core/Utils/Database/Orm/Fields/Currency.php b/application/Espo/Core/Utils/Database/Orm/Fields/Currency.php index 164604a8e1..c44f472d4e 100644 --- a/application/Espo/Core/Utils/Database/Orm/Fields/Currency.php +++ b/application/Espo/Core/Utils/Database/Orm/Fields/Currency.php @@ -29,17 +29,17 @@ namespace Espo\Core\Utils\Database\Orm\Fields; +use Espo\ORM\Query\Part\Expression as Expr; + class Currency extends Base { /** * @param string $fieldName * @param string $entityType - * @return array + * @return array */ protected function load($fieldName, $entityType) { - $alias = $fieldName . 'CurrencyRate'; - $defs = [ $entityType => [ 'fields' => [ @@ -50,109 +50,19 @@ class Currency extends Base ], ]; - $leftJoins = [ - [ - 'Currency', - $alias, - [$alias . '.id:' => $fieldName . 'Currency'], - ] - ]; - - $foreignCurrencyAlias = "{$alias}{$entityType}{alias}Foreign"; - - $mulExpression = "MUL:({$fieldName}, {$alias}.rate)"; - $params = $this->getFieldParams($fieldName); if (!empty($params['notStorable'])) { $defs[$entityType]['fields'][$fieldName]['notStorable'] = true; } else { - $defs[$entityType]['fields'][$fieldName . 'Converted'] = [ - 'type' => 'float', - 'select' => [ - 'select' => $mulExpression, - 'leftJoins' => $leftJoins, - ], - 'selectForeign' => [ - 'select' => "MUL:({alias}.{$fieldName}, {$foreignCurrencyAlias}.rate)", - 'leftJoins' => [ - [ - 'Currency', - $foreignCurrencyAlias, - [ - $foreignCurrencyAlias . '.id:' => "{alias}.{$fieldName}Currency", - ] - ] - ], - ], - 'where' => [ - "=" => [ - 'whereClause' => [ - $mulExpression . '=' => '{value}', - ], - 'leftJoins' => $leftJoins, - ], - ">" => [ - 'whereClause' => [ - $mulExpression . '>' => '{value}', - ], - 'leftJoins' => $leftJoins, - ], - "<" => [ - 'whereClause' => [ - $mulExpression . '<' => '{value}', - ], - 'leftJoins' => $leftJoins, - ], - ">=" => [ - 'whereClause' => [ - $mulExpression . '>=' => '{value}', - ], - 'leftJoins' => $leftJoins, - ], - "<=" => [ - 'whereClause' => [ - $mulExpression . '<=' => '{value}', - ], - 'leftJoins' => $leftJoins, - ], - "<>" => [ - 'whereClause' => [ - $mulExpression . '!=' => '{value}', - ], - 'leftJoins' => $leftJoins, - ], - "IS NULL" => [ - 'whereClause' => [ - $fieldName . '=' => null, - ], - ], - "IS NOT NULL" => [ - 'whereClause' => [ - $fieldName . '!=' => null, - ], - ], - ], - 'notStorable' => true, - 'order' => [ - 'order' => [ - [$mulExpression, '{direction}'], - ], - 'leftJoins' => $leftJoins, - 'additionalSelect' => ["{$alias}.rate"], - ], - 'attributeRole' => 'valueConverted', - 'fieldType' => 'currency', - ]; + if ($this->config->get('currencyNoJoinMode')) { + $this->applyNoJoinMode($fieldName, $entityType, $defs); - $defs[$entityType]['fields'][$fieldName]['order'] = [ - "order" => [ - [$mulExpression, '{direction}'], - ], - 'leftJoins' => $leftJoins, - 'additionalSelect' => ["{$alias}.rate"], - ]; + } + else { + $this->applyJoinMode($fieldName, $entityType, $defs); + } } $defs[$entityType]['fields'][$fieldName]['attributeRole'] = 'value'; @@ -163,4 +73,256 @@ class Currency extends Base return $defs; } + + /** + * @param string $fieldName + * @param string $entityType + * @param array $defs + */ + private function applyJoinMode( + string $fieldName, + string $entityType, + array &$defs + ): void { + + $alias = $fieldName . 'CurrencyRate'; + $leftJoins = [ + [ + 'Currency', + $alias, + [$alias . '.id:' => $fieldName . 'Currency'], + ] + ]; + $foreignCurrencyAlias = "{$alias}{$entityType}{alias}Foreign"; + $mulExpression = "MUL:({$fieldName}, {$alias}.rate)"; + + $defs[$entityType]['fields'][$fieldName . 'Converted'] = [ + 'type' => 'float', + 'select' => [ + 'select' => $mulExpression, + 'leftJoins' => $leftJoins, + ], + 'selectForeign' => [ + 'select' => "MUL:({alias}.{$fieldName}, {$foreignCurrencyAlias}.rate)", + 'leftJoins' => [ + [ + 'Currency', + $foreignCurrencyAlias, + [ + $foreignCurrencyAlias . '.id:' => "{alias}.{$fieldName}Currency", + ] + ] + ], + ], + 'where' => [ + "=" => [ + 'whereClause' => [ + $mulExpression . '=' => '{value}', + ], + 'leftJoins' => $leftJoins, + ], + ">" => [ + 'whereClause' => [ + $mulExpression . '>' => '{value}', + ], + 'leftJoins' => $leftJoins, + ], + "<" => [ + 'whereClause' => [ + $mulExpression . '<' => '{value}', + ], + 'leftJoins' => $leftJoins, + ], + ">=" => [ + 'whereClause' => [ + $mulExpression . '>=' => '{value}', + ], + 'leftJoins' => $leftJoins, + ], + "<=" => [ + 'whereClause' => [ + $mulExpression . '<=' => '{value}', + ], + 'leftJoins' => $leftJoins, + ], + "<>" => [ + 'whereClause' => [ + $mulExpression . '!=' => '{value}', + ], + 'leftJoins' => $leftJoins, + ], + "IS NULL" => [ + 'whereClause' => [ + $fieldName . '=' => null, + ], + ], + "IS NOT NULL" => [ + 'whereClause' => [ + $fieldName . '!=' => null, + ], + ], + ], + 'notStorable' => true, + 'order' => [ + 'order' => [ + [$mulExpression, '{direction}'], + ], + 'leftJoins' => $leftJoins, + 'additionalSelect' => ["{$alias}.rate"], + ], + 'attributeRole' => 'valueConverted', + 'fieldType' => 'currency', + ]; + + $defs[$entityType]['fields'][$fieldName]['order'] = [ + "order" => [ + [$mulExpression, '{direction}'], + ], + 'leftJoins' => $leftJoins, + 'additionalSelect' => ["{$alias}.rate"], + ]; + } + + /** + * @param string $fieldName + * @param string $entityType + * @param array $defs + */ + private function applyNoJoinMode( + string $fieldName, + string $entityType, + array &$defs + ): void { + + $currencyAttribute = $fieldName . 'Currency'; + + $defaultCurrency = $this->config->get('defaultCurrency'); + $baseCurrency = $this->config->get('baseCurrency'); + $rates = $this->config->get('currencyRates'); + + if ($defaultCurrency !== $baseCurrency) { + $rates = $this->exchangeRates($baseCurrency, $defaultCurrency, $rates); + } + + $expr = Expr::multiply( + Expr::column($fieldName), + Expr::if( + Expr::equal(Expr::column($currencyAttribute), $defaultCurrency), + 1.0, + $this->buildExpression($currencyAttribute, $rates) + ) + )->getValue(); + + $exprForeign = Expr::multiply( + Expr::column("ALIAS.{$fieldName}"), + Expr::if( + Expr::equal(Expr::column("ALIAS.{$fieldName}Currency"), $defaultCurrency), + 1.0, + $this->buildExpression("ALIAS.{$fieldName}Currency", $rates) + ) + )->getValue(); + + $exprForeign = str_replace('ALIAS', '{alias}', $exprForeign); + + $defs[$entityType]['fields'][$fieldName . 'Converted'] = [ + 'type' => 'float', + 'select' => [ + 'select' => $expr, + ], + 'selectForeign' => [ + 'select' => $exprForeign, + ], + 'where' => [ + "=" => [ + 'whereClause' => [ + $expr . '=' => '{value}', + ], + ], + ">" => [ + 'whereClause' => [ + $expr . '>' => '{value}', + ], + ], + "<" => [ + 'whereClause' => [ + $expr . '<' => '{value}', + ], + ], + ">=" => [ + 'whereClause' => [ + $expr . '>=' => '{value}', + ], + ], + "<=" => [ + 'whereClause' => [ + $expr . '<=' => '{value}', + ], + ], + "<>" => [ + 'whereClause' => [ + $expr . '!=' => '{value}', + ], + ], + "IS NULL" => [ + 'whereClause' => [ + $expr . '=' => null, + ], + ], + "IS NOT NULL" => [ + 'whereClause' => [ + $expr . '!=' => null, + ], + ], + ], + 'notStorable' => true, + 'order' => [ + 'order' => [ + [$expr, '{direction}'], + ], + ], + 'attributeRole' => 'valueConverted', + 'fieldType' => 'currency', + ]; + } + + /** + * @param array $currencyRates + * @return array + */ + private function exchangeRates(string $baseCurrency, string $defaultCurrency, array $currencyRates): array + { + $precision = 5; + $defaultCurrencyRate = round(1 / $currencyRates[$defaultCurrency], $precision); + + $exchangedRates = []; + $exchangedRates[$baseCurrency] = $defaultCurrencyRate; + + unset($currencyRates[$baseCurrency], $currencyRates[$defaultCurrency]); + + foreach ($currencyRates as $currencyName => $rate) { + $exchangedRates[$currencyName] = round($rate * $defaultCurrencyRate, $precision); + } + + return $exchangedRates; + } + + /** + * @param array $rates + */ + private function buildExpression(string $currencyAttribute, array $rates): Expr|float + { + if ($rates === []) { + return 0.0; + } + + $currency = array_key_first($rates); + $value = $rates[$currency]; + unset($rates[$currency]); + + return Expr::if( + Expr::equal(Expr::column($currencyAttribute), $currency), + $value, + $this->buildExpression($currencyAttribute, $rates) + ); + } } diff --git a/application/Espo/Resources/defaults/config.php b/application/Espo/Resources/defaults/config.php index 98068d6915..41d97a6bcc 100644 --- a/application/Espo/Resources/defaults/config.php +++ b/application/Espo/Resources/defaults/config.php @@ -65,6 +65,7 @@ return [ 'defaultCurrency' => 'USD', 'baseCurrency' => 'USD', 'currencyRates' => [], + 'currencyNoJoinMode' => false, 'outboundEmailIsShared' => true, 'outboundEmailFromName' => 'EspoCRM', 'outboundEmailFromAddress' => '', diff --git a/application/Espo/Resources/defaults/systemConfig.php b/application/Espo/Resources/defaults/systemConfig.php index 7fb0c234ff..a26ead483f 100644 --- a/application/Espo/Resources/defaults/systemConfig.php +++ b/application/Espo/Resources/defaults/systemConfig.php @@ -191,6 +191,7 @@ return [ 'thumbImageCacheDisabled', 'emailReminderPortionSize', 'outboundSmsFromNumber', + 'currencyNoJoinMode', 'latestVersion', ], 'superAdminItems' => [