mirror of
				https://github.com/firefly-iii/firefly-iii.git
				synced 2025-10-31 10:47:00 +00:00 
			
		
		
		
	Initial user ability to set foreign currency
This commit is contained in:
		| @@ -15,6 +15,7 @@ use Amount; | ||||
| use FireflyIII\Exceptions\FireflyException; | ||||
| use FireflyIII\Models\Account; | ||||
| use FireflyIII\Models\AccountType; | ||||
| use FireflyIII\Models\TransactionCurrency; | ||||
| use FireflyIII\Repositories\Account\AccountRepositoryInterface; | ||||
| use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; | ||||
| use Illuminate\Http\Request; | ||||
| @@ -29,7 +30,6 @@ use Session; | ||||
|  */ | ||||
| class JavascriptController extends Controller | ||||
| { | ||||
|  | ||||
|     /** | ||||
|      * @param AccountRepositoryInterface  $repository | ||||
|      * @param CurrencyRepositoryInterface $currencyRepository | ||||
| @@ -50,7 +50,7 @@ class JavascriptController extends Controller | ||||
|             $accountId                    = $account->id; | ||||
|             $currency                     = intval($account->getMeta('currency_id')); | ||||
|             $currency                     = $currency === 0 ? $default->id : $currency; | ||||
|             $entry                        = ['preferredCurrency' => $currency]; | ||||
|             $entry                        = ['preferredCurrency' => $currency, 'name' => $account->name]; | ||||
|             $data['accounts'][$accountId] = $entry; | ||||
|         } | ||||
|  | ||||
| @@ -60,6 +60,27 @@ class JavascriptController extends Controller | ||||
|             ->header('Content-Type', 'text/javascript'); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param CurrencyRepositoryInterface $repository | ||||
|      * | ||||
|      * @return $this | ||||
|      */ | ||||
|     public function currencies(CurrencyRepositoryInterface $repository) | ||||
|     { | ||||
|         $currencies = $repository->get(); | ||||
|         $data       = ['currencies' => [],]; | ||||
|         /** @var TransactionCurrency $currency */ | ||||
|         foreach ($currencies as $currency) { | ||||
|             $currencyId                    = $currency->id; | ||||
|             $entry                         = ['name' => $currency->name, 'code' => $currency->code, 'symbol' => $currency->symbol]; | ||||
|             $data['currencies'][$currencyId] = $entry; | ||||
|         } | ||||
|  | ||||
|         return response() | ||||
|             ->view('javascript.currencies', $data, 200) | ||||
|             ->header('Content-Type', 'text/javascript'); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param Request $request | ||||
|      * | ||||
|   | ||||
| @@ -14,10 +14,12 @@ namespace FireflyIII\Http\Controllers\Json; | ||||
|  | ||||
| use Carbon\Carbon; | ||||
| use FireflyIII\Http\Controllers\Controller; | ||||
| use FireflyIII\Models\CurrencyExchangeRate; | ||||
| use FireflyIII\Models\TransactionCurrency; | ||||
| use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; | ||||
| use FireflyIII\Services\Currency\ExchangeRateInterface; | ||||
| use Illuminate\Http\Request; | ||||
| use Log; | ||||
| use Response; | ||||
|  | ||||
| /** | ||||
| @@ -42,6 +44,7 @@ class ExchangeController extends Controller | ||||
|         $rate       = $repository->getExchangeRate($fromCurrency, $toCurrency, $date); | ||||
|         $amount     = null; | ||||
|         if (is_null($rate->id)) { | ||||
|             Log::debug(sprintf('No cached exchange rate in database for %s to %s on %s', $fromCurrency->code, $toCurrency->code, $date->format('Y-m-d'))); | ||||
|             $preferred = env('EXCHANGE_RATE_SERVICE', config('firefly.preferred_exchange_service')); | ||||
|             $class     = config('firefly.currency_exchange_services.' . $preferred); | ||||
|             /** @var ExchangeRateInterface $object */ | ||||
| @@ -53,7 +56,9 @@ class ExchangeController extends Controller | ||||
|         $return['amount'] = null; | ||||
|         if (!is_null($request->get('amount'))) { | ||||
|             // assume amount is in "from" currency: | ||||
|             $return['amount'] = bcmul($request->get('amount'), strval($rate->rate)); | ||||
|             $return['amount'] = bcmul($request->get('amount'), strval($rate->rate), 12); | ||||
|             // round to toCurrency decimal places: | ||||
|             $return['amount'] = round($return['amount'], $toCurrency->decimal_places); | ||||
|         } | ||||
|  | ||||
|         return Response::json($return); | ||||
|   | ||||
| @@ -20,6 +20,7 @@ use FireflyIII\Models\Preference; | ||||
| use FireflyIII\Models\TransactionCurrency; | ||||
| use FireflyIII\User; | ||||
| use Illuminate\Support\Collection; | ||||
| use Log; | ||||
| use Preferences; | ||||
|  | ||||
| /** | ||||
| @@ -189,11 +190,21 @@ class CurrencyRepository implements CurrencyRepositoryInterface | ||||
|      */ | ||||
|     public function getExchangeRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate | ||||
|     { | ||||
|         if ($fromCurrency->id === $toCurrency->id) { | ||||
|             $rate       = new CurrencyExchangeRate; | ||||
|             $rate->rate = 1; | ||||
|             $rate->id   = 0; | ||||
|  | ||||
|             return $rate; | ||||
|         } | ||||
|  | ||||
|         $rate = $this->user->currencyExchangeRates() | ||||
|                            ->where('from_currency_id', $fromCurrency->id) | ||||
|                            ->where('to_currency_id', $toCurrency->id) | ||||
|                            ->where('date', $date->format('Y-m-d'))->first(); | ||||
|         if (!is_null($rate)) { | ||||
|             Log::debug(sprintf('Found cached exchange rate in database for %s to %s on %s', $fromCurrency->code, $toCurrency->code, $date->format('Y-m-d'))); | ||||
|  | ||||
|             return $rate; | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -261,6 +261,37 @@ class ExpandedForm | ||||
|         return $html; | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * @param string $name | ||||
|      * @param null   $value | ||||
|      * @param array  $options | ||||
|      * | ||||
|      * @return string | ||||
|      */ | ||||
|     public function nonSelectableAmount(string $name, $value = null, array $options = []): string | ||||
|     { | ||||
|         $label            = $this->label($name, $options); | ||||
|         $options          = $this->expandOptionArray($name, $label, $options); | ||||
|         $classes          = $this->getHolderClasses($name); | ||||
|         $value            = $this->fillFieldValue($name, $value); | ||||
|         $options['step']  = 'any'; | ||||
|         $options['min']  = '0.01'; | ||||
|         $selectedCurrency = isset($options['currency']) ? $options['currency'] : Amt::getDefaultCurrency(); | ||||
|         unset($options['currency']); | ||||
|         unset($options['placeholder']); | ||||
|  | ||||
|         // make sure value is formatted nicely: | ||||
|         if (!is_null($value) && $value !== '') { | ||||
|             $value = round($value, $selectedCurrency->decimal_places); | ||||
|         } | ||||
|  | ||||
|  | ||||
|         $html = view('form.non-selectable-amount', compact('selectedCurrency', 'classes', 'name', 'label', 'value', 'options'))->render(); | ||||
|  | ||||
|         return $html; | ||||
|     } | ||||
|  | ||||
|  | ||||
|     /** | ||||
|      * @param string $name | ||||
|      * @param null   $value | ||||
| @@ -270,7 +301,6 @@ class ExpandedForm | ||||
|      */ | ||||
|     public function nonSelectableBalance(string $name, $value = null, array $options = []): string | ||||
|     { | ||||
|  | ||||
|         $label            = $this->label($name, $options); | ||||
|         $options          = $this->expandOptionArray($name, $label, $options); | ||||
|         $classes          = $this->getHolderClasses($name); | ||||
| @@ -286,7 +316,7 @@ class ExpandedForm | ||||
|         } | ||||
|  | ||||
|  | ||||
|         $html = view('form.non-selectable-balance', compact('selectedCurrency', 'classes', 'name', 'label', 'value', 'options'))->render(); | ||||
|         $html = view('form.non-selectable-amount', compact('selectedCurrency', 'classes', 'name', 'label', 'value', 'options'))->render(); | ||||
|  | ||||
|         return $html; | ||||
|     } | ||||
|   | ||||
| @@ -153,6 +153,7 @@ return [ | ||||
|         'tagList'           => 'FireflyIII\Support\Binder\TagList', | ||||
|         'start_date'        => 'FireflyIII\Support\Binder\Date', | ||||
|         'end_date'          => 'FireflyIII\Support\Binder\Date', | ||||
|         'date'              => 'FireflyIII\Support\Binder\Date', | ||||
|     ], | ||||
|     'rule-triggers'              => [ | ||||
|         'user_action'           => 'FireflyIII\Rules\Triggers\UserAction', | ||||
| @@ -210,7 +211,7 @@ return [ | ||||
|                                      'budget', 'bill', 'type', 'date', 'date_before', 'date_after', 'on', 'before', 'after'], | ||||
|     // tag notes has_attachments | ||||
|     'currency_exchange_services' => [ | ||||
|         'fixerio'           => 'FireflyIII\Services\Currency\FixerIO', | ||||
|         'fixerio' => 'FireflyIII\Services\Currency\FixerIO', | ||||
|     ], | ||||
|     'preferred_exchange_service' => 'fixerio', | ||||
|  | ||||
|   | ||||
| @@ -159,7 +159,7 @@ return [ | ||||
|             'ExpandedForm' => [ | ||||
|                 'is_safe' => [ | ||||
|                     'date', 'text', 'select', 'balance', 'optionsList', 'checkbox', 'amount', 'tags', 'integer', 'textarea', 'location', | ||||
|                     'multiRadio', 'file', 'multiCheckbox', 'staticText', 'amountSmall', 'password','nonSelectableBalance' | ||||
|                     'multiRadio', 'file', 'multiCheckbox', 'staticText', 'amountSmall', 'password', 'nonSelectableBalance', 'nonSelectableAmount', | ||||
|                 ], | ||||
|             ], | ||||
|             'Form'         => [ | ||||
|   | ||||
| @@ -20,7 +20,7 @@ $(function () { | ||||
|                 }); | ||||
|  | ||||
|     // when you click on a currency, this happens: | ||||
|     $('.currency-option').click(currencySelect); | ||||
|     $('.currency-option').on('click', currencySelect); | ||||
|  | ||||
|     var ranges = {}; | ||||
|     ranges[dateRangeConfig.currentPeriod] = [moment(dateRangeConfig.ranges.current[0]), moment(dateRangeConfig.ranges.current[1])]; | ||||
|   | ||||
| @@ -6,7 +6,7 @@ | ||||
|  * See the LICENSE file for details. | ||||
|  */ | ||||
|  | ||||
| /** global: what,Modernizr, title, breadcrumbs, middleCrumbName, button, piggiesLength, txt, middleCrumbUrl */ | ||||
| /** global: what,Modernizr, title, breadcrumbs, middleCrumbName, button, piggiesLength, txt, middleCrumbUrl,exchangeRateInstructions */ | ||||
|  | ||||
| $(document).ready(function () { | ||||
|     "use strict"; | ||||
| @@ -17,6 +17,10 @@ $(document).ready(function () { | ||||
|     updateLayout(); | ||||
|     updateDescription(); | ||||
|  | ||||
|     // hide exchange rate instructions: | ||||
|     $('#exchange_rate_instruction_holder').hide(); | ||||
|     $('#exchanged_amount_holder').hide(); | ||||
|  | ||||
|     if (!Modernizr.inputtypes.date) { | ||||
|         $('input[type="date"]').datepicker( | ||||
|             { | ||||
| @@ -27,11 +31,65 @@ $(document).ready(function () { | ||||
|  | ||||
|     // update currency | ||||
|     $('select[name="source_account_id"]').on('change', updateCurrency); | ||||
|     updateCurrency(); | ||||
|     $('#ffInput_amount').on('change', getExchangeRate); | ||||
|  | ||||
|     // respond to changes to the hidden input, | ||||
|     // so we can show the "exchange rate" thing if necessary: | ||||
|     $('.currency-option').on('click', triggerCurrencyChange); | ||||
|  | ||||
|     // get JSON things: | ||||
|     getJSONautocomplete(); | ||||
| }); | ||||
|  | ||||
| function getExchangeRate() { | ||||
|     var accountId = $('select[name="source_account_id"]').val(); | ||||
|     var selectedCurrencyId = parseInt($('input[name="amount_currency_id_amount"]').val()); | ||||
|     var accountCurrencyId = parseInt(accountInfo[accountId].preferredCurrency); | ||||
|     var selectedCurrencyCode = currencyInfo[selectedCurrencyId].code; | ||||
|     var accountCurrencyCode = currencyInfo[accountCurrencyId].code; | ||||
|     var date = $('#ffInput_date').val(); | ||||
|     var amount = $('#ffInput_amount').val(); | ||||
|     var uri = 'json/rate/' + selectedCurrencyCode + '/' + accountCurrencyCode + '/' + date + '?amount=' + amount; | ||||
|     console.log('Will grab ' + uri); | ||||
|     $.get(uri).done(updateExchangedAmount); | ||||
|  | ||||
| } | ||||
|  | ||||
| function updateExchangedAmount(data) { | ||||
|     console.log('Returned data:'); | ||||
|     console.log(data); | ||||
|     $('#ffInput_exchanged_amount').val(data.amount); | ||||
| } | ||||
|  | ||||
|  | ||||
| function triggerCurrencyChange() { | ||||
|     var selectedCurrencyId = parseInt($('input[name="amount_currency_id_amount"]').val()); | ||||
|     var accountId = $('select[name="source_account_id"]').val(); | ||||
|     var accountCurrencyId = parseInt(accountInfo[accountId].preferredCurrency); | ||||
|     console.log('Selected currency is ' + selectedCurrencyId); | ||||
|     console.log('Account prefers ' + accountCurrencyId); | ||||
|     if (selectedCurrencyId !== accountCurrencyId) { | ||||
|         var text = exchangeRateInstructions.replace('@name', accountInfo[accountId].name); | ||||
|         text = text.replace(/@account_currency/g, currencyInfo[accountCurrencyId].name); | ||||
|         text = text.replace(/@transaction_currency/g, currencyInfo[selectedCurrencyId].name); | ||||
|         $('.non-selectable-currency-symbol').text(currencyInfo[accountCurrencyId].symbol); | ||||
|         getExchangeRate(); | ||||
|  | ||||
|         $('#ffInput_exchange_rate_instruction').text(text); | ||||
|         $('#exchange_rate_instruction_holder').show(); | ||||
|         $('#exchanged_amount_holder').show(); | ||||
|     } | ||||
|     if (selectedCurrencyId === accountCurrencyId) { | ||||
|         $('#exchange_rate_instruction_holder').hide(); | ||||
|         $('#exchanged_amount_holder').hide(); | ||||
|     } | ||||
|  | ||||
|     // if the value of the selected currency does not match the account's currency | ||||
|     // show the exchange rate thing! | ||||
|     return false; | ||||
| } | ||||
|  | ||||
|  | ||||
| function updateCurrency() { | ||||
|     // get value: | ||||
| @@ -41,7 +99,6 @@ function updateCurrency() { | ||||
|     $('.currency-option[data-id="' + currencyPreference + '"]').click(); | ||||
|     $('[data-toggle="dropdown"]').parent().removeClass('open'); | ||||
|     $('select[name="source_account_id"]').focus(); | ||||
|  | ||||
| } | ||||
|  | ||||
| function updateDescription() { | ||||
|   | ||||
| @@ -135,7 +135,7 @@ return [ | ||||
|     'all_journals_for_category'                  => 'All transactions for category :name', | ||||
|     'journals_in_period_for_category'            => 'All transactions for category :name between :start and :end', | ||||
|     'not_available_demo_user'                    => 'The feature you try to access is not available to demo users.', | ||||
|  | ||||
|     'exchange_rate_instructions'                 => 'Asset account "@name" only accepts transactions in @account_currency. If you wish to use @transaction_currency instead, make sure that the amount in @account_currency is known as well:', | ||||
|  | ||||
|     // repeat frequencies: | ||||
|     'repeat_freq_yearly'                         => 'yearly', | ||||
|   | ||||
| @@ -64,6 +64,8 @@ return [ | ||||
|     'expense_account'                => 'Expense account', | ||||
|     'revenue_account'                => 'Revenue account', | ||||
|     'decimal_places'                 => 'Decimal places', | ||||
|     'exchange_rate_instruction'      => 'Foreign currencies', | ||||
|     'exchanged_amount'               => 'Exchanged amount', | ||||
|  | ||||
|     'revenue_account_source'      => 'Revenue account (source)', | ||||
|     'source_account_asset'        => 'Source account (asset account)', | ||||
|   | ||||
| @@ -2,6 +2,6 @@ | ||||
|     <label for="{{ options.id }}" class="col-sm-4 control-label">{{ label }}</label> | ||||
|  | ||||
|     <div class="col-sm-8"> | ||||
|         <p class="form-control-static">{{ value|raw }}</p> | ||||
|         <p class="form-control-static" id="{{ options.id }}">{{ value|raw }}</p> | ||||
|     </div> | ||||
| </div> | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| var accountInfo = []; | ||||
| {% for id, account in accounts %} | ||||
|     accountInfo[{{ id }}] = {preferredCurrency: {{ account.preferredCurrency }}}; | ||||
|     accountInfo[{{ id }}] = {preferredCurrency: {{ account.preferredCurrency }}, name: "{{ account.name }}"}; | ||||
| {% endfor %} | ||||
|   | ||||
							
								
								
									
										4
									
								
								resources/views/javascript/currencies.twig
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								resources/views/javascript/currencies.twig
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,4 @@ | ||||
| var currencyInfo = []; | ||||
| {% for id, currency in currencies %} | ||||
|     currencyInfo[{{ id }}] = {name: "{{ currency.name }}", symbol: "{{ currency.symbol }}", code: "{{ currency.code }}"}; | ||||
| {% endfor %} | ||||
| @@ -47,6 +47,11 @@ | ||||
|                         {# ALWAYS SHOW AMOUNT #} | ||||
|                         {{ ExpandedForm.amount('amount') }} | ||||
|  | ||||
|                         {# INSTRUCTIONS FOR EXCHANGE RATES #} | ||||
|                         {{ ExpandedForm.staticText('exchange_rate_instruction','(here be text)') }} | ||||
|  | ||||
|                         {{ ExpandedForm.nonSelectableAmount('exchanged_amount') }} | ||||
|  | ||||
|                         {# ALWAYS SHOW DATE #} | ||||
|                         {{ ExpandedForm.date('date', preFilled.date|default(phpdate('Y-m-d'))) }} | ||||
|                     </div> | ||||
| @@ -209,6 +214,7 @@ | ||||
|         var middleCrumbName = []; | ||||
|         var middleCrumbUrl = []; | ||||
|         var button = []; | ||||
|         var exchangeRateInstructions = "{{ 'exchange_rate_instructions'|_|escape('js') }}"; | ||||
|         {% for type in {0:'withdrawal',1:'deposit',2:'transfer'} %} | ||||
|  | ||||
|         txt['{{ type }}'] = '{{ type|_ }}'; | ||||
| @@ -225,6 +231,7 @@ | ||||
|     <script type="text/javascript" src="js/lib/modernizr-custom.js"></script> | ||||
|     <script type="text/javascript" src="js/lib/jquery-ui.min.js"></script> | ||||
|     <script type="text/javascript" src="javascript/accounts?ext=.js"></script> | ||||
|     <script type="text/javascript" src="javascript/currencies?ext=.js"></script> | ||||
|     <script type="text/javascript" src="js/ff/transactions/single/create.js"></script> | ||||
| {% endblock %} | ||||
|  | ||||
|   | ||||
| @@ -410,6 +410,7 @@ Route::group( | ||||
|     ['middleware' => 'user-full-auth', 'prefix' => 'javascript', 'as' => 'javascript.'], function () { | ||||
|     Route::get('variables', ['uses' => 'JavascriptController@variables', 'as' => 'variables']); | ||||
|     Route::get('accounts', ['uses' => 'JavascriptController@accounts', 'as' => 'accounts']); | ||||
|     Route::get('currencies', ['uses' => 'JavascriptController@currencies', 'as' => 'currencies']); | ||||
| } | ||||
| ); | ||||
|  | ||||
|   | ||||
		Reference in New Issue
	
	Block a user