diff --git a/app/Http/Requests/JournalFormRequest.php b/app/Http/Requests/JournalFormRequest.php index 45c387bc4e..12d35d8617 100644 --- a/app/Http/Requests/JournalFormRequest.php +++ b/app/Http/Requests/JournalFormRequest.php @@ -67,8 +67,10 @@ class JournalFormRequest extends Request 'destination_account_name' => $this->string('destination_account_name'), 'piggy_bank_id' => $this->integer('piggy_bank_id'), - // native amount + // native amount and stuff like that: 'native_amount' => $this->float('native_amount'), + 'source_amount' => $this->float('source_amount'), + 'destination_amount' => $this->float('destination_amount'), ]; @@ -105,8 +107,10 @@ class JournalFormRequest extends Request 'destination_account_name' => 'between:1,255', 'piggy_bank_id' => 'between:1,255', - // exchange rate data: - 'native_amount' => 'numeric|more:0', + // foreign currency amounts + 'native_amount' => 'numeric|more:0', + 'source_amount' => 'numeric|more:0', + 'destination_amount' => 'numeric|more:0', ]; // some rules get an upgrade depending on the type of data: diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index d35b0d6ae9..2b44dbda42 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -778,29 +778,38 @@ class JournalRepository implements JournalRepositoryInterface /** @var TransactionType $transactionType */ $transactionType = TransactionType::where('type', ucfirst($data['what']))->first(); $submittedCurrencyId = $data['currency_id']; - $amount = strval($data['amount']); // which account to check for what the native currency is? $check = 'source'; if ($transactionType->type === TransactionType::DEPOSIT) { $check = 'destination'; } - if ($transactionType->type === TransactionType::TRANSFER) { - throw new FireflyException('Cannot handle transfers in verifyNativeAmount()'); - } + switch ($transactionType->type) { + case TransactionType::DEPOSIT: + case TransactionType::WITHDRAWAL: + // continue: + $nativeCurrencyId = intval($accounts[$check]->getMeta('currency_id')); - // continue: - $nativeCurrencyId = intval($accounts[$check]->getMeta('currency_id')); + // does not match? Then user has submitted amount in a foreign currency: + if ($nativeCurrencyId !== $submittedCurrencyId) { + // store amount and submitted currency in "foreign currency" fields: + $data['foreign_amount'] = $data['amount']; + $data['foreign_currency_id'] = $submittedCurrencyId; - // does not match? Then user has submitted amount in a foreign currency: - if ($nativeCurrencyId !== $submittedCurrencyId) { - // store amount and submitted currency in "foreign currency" fields: - $data['foreign_amount'] = $data['amount']; - $data['foreign_currency_id'] = $submittedCurrencyId; - - // overrule the amount and currency ID fields to be the original again: - $data['amount'] = strval($data['native_amount']); - $data['currency_id'] = $nativeCurrencyId; + // overrule the amount and currency ID fields to be the original again: + $data['amount'] = strval($data['native_amount']); + $data['currency_id'] = $nativeCurrencyId; + } + break; + case TransactionType::TRANSFER: + // source gets the original amount. + $data['amount'] = strval($data['source_amount']); + $data['currency_id'] = intval($accounts['source']->getMeta('currency_id')); + $data['foreign_amount'] = strval($data['destination_amount']); + $data['foreign_currency_id'] = intval($accounts['destination']->getMeta('currency_id')); + break; + default: + throw new FireflyException(sprintf('Cannot handle %s in verifyNativeAmount()', $transactionType->type)); } return $data; diff --git a/public/js/ff/transactions/single/create.js b/public/js/ff/transactions/single/create.js index a115dae312..c30ef4a2fd 100644 --- a/public/js/ff/transactions/single/create.js +++ b/public/js/ff/transactions/single/create.js @@ -11,27 +11,68 @@ $(document).ready(function () { "use strict"; + // hide ALL exchange things and AMOUNT things + $('#exchange_rate_instruction_holder').hide(); + $('#native_amount_holder').hide(); + $('#amount_holder').hide(); + $('#source_amount_holder').hide(); + $('#destination_amount_holder').hide(); + // respond to switch buttons (first time always triggers) updateButtons(); updateForm(); updateLayout(); updateDescription(); - updateNativeCurrency(); // verify native currency by first account (may be different). + updateNativeCurrency(); - // hide ALL exchange things - $('#exchange_rate_instruction_holder').hide(); - $('#native_amount_holder').hide(); - // when user changes source account, native currency may be different. + + // when user changes source account or destination, native currency may be different. $('select[name="source_account_id"]').on('change', updateNativeCurrency); + $('select[name="destination_account_id"]').on('change', updateNativeCurrency); - // convert foreign currency to native currency. + // convert foreign currency to native currency (when input changes, exchange rate) $('#ffInput_amount').on('change', convertForeignToNative); + // convert source currency to destination currency (slightly different routine for transfers) + $('#ffInput_source_amount').on('change', convertSourceToDestination); + // when user selects different currency, $('.currency-option').on('click', selectsForeignCurrency); }); +/** + * Convert from source amount currency to destination currency for transfers. + * + */ +function convertSourceToDestination() { + var sourceAccount = $('select[name="source_account_id"]').val(); + var destAccount = $('select[name="destination_account_id"]').val(); + + var sourceCurrency = accountInfo[sourceAccount].preferredCurrency; + var destinationCurrency = accountInfo[destAccount].preferredCurrency; + + var sourceCurrencyCode = currencyInfo[sourceCurrency].code; + var destinationCurrencyCode = currencyInfo[destinationCurrency].code; + + var date = $('#ffInput_date').val(); + var amount = $('#ffInput_source_amount').val(); + $('#ffInput_amount').val(amount); + var uri = 'json/rate/' + sourceCurrencyCode + '/' + destinationCurrencyCode + '/' + date + '?amount=' + amount; + console.log('Will grab ' + uri); + $.get(uri).done(updateDestinationAmount); +} + +/** + * Once the data has been grabbed will update the field (for transfers) + * @param data + */ +function updateDestinationAmount(data) { + console.log('Returned data:'); + console.log(data); + $('#ffInput_destination_amount').val(data.amount); +} + /** * This function generates a small helper text to explain the user * that they have selected a foreign currency. @@ -48,7 +89,21 @@ function getExchangeInstructions() { return text; } +/** + * Same as above but for transfers + */ +function getTransferExchangeInstructions() { + var sourceAccount = $('select[name="source_account_id"]').val(); + var destAccount = $('select[name="destination_account_id"]').val(); + var sourceCurrency = accountInfo[sourceAccount].preferredCurrency; + var destinationCurrency = accountInfo[destAccount].preferredCurrency; + + return transferInstructions.replace('@source_name', accountInfo[sourceAccount].name) + .replace('@dest_name', accountInfo[destAccount].name) + .replace(/@source_currency/g, currencyInfo[sourceCurrency].name) + .replace(/@dest_currency/g, currencyInfo[destinationCurrency].name); +} /** * There is an input that shows the currency symbol that is native to the selected @@ -63,6 +118,41 @@ function updateNativeCurrency() { $('.currency-option[data-id="' + nativeCurrencyId + '"]').click(); $('[data-toggle="dropdown"]').parent().removeClass('open'); $('select[name="source_account_id"]').focus(); + + validateCurrencyForTransfer(); +} + +/** + * When the transaction to create is a transfer some more checks are necessary. + */ +function validateCurrencyForTransfer() { + if (what !== "transfer") { + return; + } + $('#source_amount_holder').show(); + var sourceAccount = $('select[name="source_account_id"]').val(); + var destAccount = $('select[name="destination_account_id"]').val(); + var sourceCurrency = accountInfo[sourceAccount].preferredCurrency; + var sourceSymbol = currencyInfo[sourceCurrency].symbol; + var destinationCurrency = accountInfo[destAccount].preferredCurrency; + var destinationSymbol = currencyInfo[destinationCurrency].symbol; + + $('#source_amount_holder').show().find('.non-selectable-currency-symbol').text(sourceSymbol); + + if (sourceCurrency === destinationCurrency) { + console.log('Both accounts accept ' + sourceCurrency); + $('#destination_amount_holder').hide(); + $('#amount_holder').hide(); + return; + } + console.log('Source accepts #' + sourceCurrency + ', destination #' + destinationCurrency); + $('#ffInput_exchange_rate_instruction').text(getTransferExchangeInstructions()); + $('#exchange_rate_instruction_holder').show(); + $('input[name="source_amount"]').val($('input[name="amount"]').val()); + convertSourceToDestination(); + + $('#destination_amount_holder').show().find('.non-selectable-currency-symbol').text(destinationSymbol); + $('#amount_holder').hide(); } /** @@ -94,7 +184,7 @@ function updateForm() { $('input[name="what"]').val(what); switch (what) { case 'withdrawal': - // show source_id and dest_name: + // show source_id and dest_name $('#source_account_id_holder').show(); $('#destination_account_name_holder').show(); @@ -102,7 +192,7 @@ function updateForm() { $('#source_account_name_holder').hide(); $('#destination_account_id_holder').hide(); - // show budget: + // $('#budget_id_holder').show(); // hide piggy bank: @@ -114,6 +204,17 @@ function updateForm() { $('#ffInput_source_account_name').val($('#ffInput_destination_account_name').val()); } + // exchange / foreign currencies: + // hide explanation, hide source and destination amounts: + $('#exchange_rate_instruction_holder').hide(); + $('#source_amount_holder').hide(); + $('#destination_amount_holder').hide(); + // show normal amount: + $('#amount_holder').show(); + + // update the amount thing: + updateNativeCurrency(); + break; case 'deposit': // show source_name and dest_id: @@ -134,6 +235,17 @@ function updateForm() { $('#ffInput_destination_account_name').val($('#ffInput_source_account_name').val()); } + // exchange / foreign currencies: + // hide explanation, hide source and destination amounts: + $('#exchange_rate_instruction_holder').hide(); + $('#source_amount_holder').hide(); + $('#destination_amount_holder').hide(); + // show normal amount: + $('#amount_holder').show(); + + // update the amount thing: + updateNativeCurrency(); + break; case 'transfer': // show source_id and dest_id: @@ -144,7 +256,6 @@ function updateForm() { $('#source_account_name_holder').hide(); $('#destination_account_name_holder').hide(); - // hide budget $('#budget_id_holder').hide(); if (piggiesLength === 0) { @@ -152,6 +263,10 @@ function updateForm() { } else { $('#piggy_bank_id_holder').show(); } + + // update the amount thing: + updateNativeCurrency(); + break; default: // no action. @@ -206,8 +321,7 @@ function getAccountId() { if (what === "withdrawal") { return $('select[name="source_account_id"]').val(); } - if (what === "deposit") { + if (what === "deposit" || what === "transfer") { return $('select[name="destination_account_id"]').val(); } - alert('Cannot handle ' + what); } diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 79a1cec347..8ea6c261f0 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -136,6 +136,7 @@ return [ '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 @native_currency. If you wish to use @foreign_currency instead, make sure that the amount in @native_currency is known as well:', + 'transfer_exchange_rate_instructions' => 'Source asset account "@source_name" only accepts transactions in @source_currency. Destination asset account "@dest_name" only accepts transactions in @dest_currency. You must provide the transferred amount correctly in both currencies.', // repeat frequencies: 'repeat_freq_yearly' => 'yearly', diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index 749e579854..3b6a5b3ddc 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -66,6 +66,8 @@ return [ 'decimal_places' => 'Decimal places', 'exchange_rate_instruction' => 'Foreign currencies', 'exchanged_amount' => 'Exchanged amount', + 'source_amount' => 'Amount (source)', + 'destination_amount' => 'Amount (destination)', 'revenue_account_source' => 'Revenue account (source)', 'source_account_asset' => 'Source account (asset account)', diff --git a/resources/views/transactions/single/create.twig b/resources/views/transactions/single/create.twig index a2662e2cbf..a9425ea8db 100644 --- a/resources/views/transactions/single/create.twig +++ b/resources/views/transactions/single/create.twig @@ -52,6 +52,10 @@ {{ ExpandedForm.nonSelectableAmount('native_amount') }} + {{ ExpandedForm.nonSelectableAmount('source_amount') }} + + {{ ExpandedForm.nonSelectableAmount('destination_amount') }} + {# ALWAYS SHOW DATE #} {{ ExpandedForm.date('date', preFilled.date|default(phpdate('Y-m-d'))) }} @@ -215,6 +219,8 @@ var middleCrumbUrl = []; var button = []; var exchangeRateInstructions = "{{ 'exchange_rate_instructions'|_|escape('js') }}"; + var transferInstructions = "{{ 'transfer_exchange_rate_instructions'|_|escape('js') }}"; + {% for type in {0:'withdrawal',1:'deposit',2:'transfer'} %} txt['{{ type }}'] = '{{ type|_ }}';