diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php index 6b99629437..ae5dde09c1 100644 --- a/app/Http/Controllers/Transaction/SingleController.php +++ b/app/Http/Controllers/Transaction/SingleController.php @@ -242,53 +242,56 @@ class SingleController extends Controller * * @return mixed */ - public function edit(TransactionJournal $journal) + public function edit(TransactionJournal $journal, JournalRepositoryInterface $repository) { - // @codeCoverageIgnoreStart - if ($this->isOpeningBalance($journal)) { + $transactionType = $repository->getTransactionType($journal); + + // redirect to account: + if ($transactionType === TransactionType::OPENING_BALANCE) { return $this->redirectToAccount($journal); } - // @codeCoverageIgnoreEnd + // redirect to reconcile edit: + if ($transactionType === TransactionType::RECONCILIATION) { + return redirect(route('accounts.reconcile.edit', [$journal->id])); + } + + // redirect to split edit: if ($this->isSplitJournal($journal)) { return redirect(route('transactions.split.edit', [$journal->id])); } - $what = strtolower($journal->transactionTypeStr()); + $what = strtolower($transactionType); $assetAccounts = $this->groupedAccountList(); $budgetList = ExpandedForm::makeSelectListWithEmpty($this->budgets->getBudgets()); - if (TransactionType::RECONCILIATION === $journal->transactionType->type) { - return redirect(route('accounts.reconcile.edit', [$journal->id])); - } - // view related code $subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]); // journal related code - $sourceAccounts = $journal->sourceAccountList(); - $destinationAccounts = $journal->destinationAccountList(); + $sourceAccounts = $repository->getJournalSourceAccounts($journal); + $destinationAccounts = $repository->getJournalDestinationAccounts($journal); $optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data; - $pTransaction = $journal->positiveTransaction(); + $pTransaction = $repository->getFirstPosTransaction($journal); $foreignCurrency = null !== $pTransaction->foreignCurrency ? $pTransaction->foreignCurrency : $pTransaction->transactionCurrency; $preFilled = [ - 'date' => $journal->dateAsString(), - 'interest_date' => $journal->dateAsString('interest_date'), - 'book_date' => $journal->dateAsString('book_date'), - 'process_date' => $journal->dateAsString('process_date'), - 'category' => $journal->categoryAsString(), - 'budget_id' => $journal->budgetId(), - 'tags' => join(',', $journal->tags->pluck('tag')->toArray()), + 'date' => $repository->getJournalDate($journal, null), // $journal->dateAsString() + 'interest_date' => $repository->getJournalDate($journal, 'interest_date'), + 'book_date' => $repository->getJournalDate($journal, 'book_date'), + 'process_date' => $repository->getJournalDate($journal, 'process_date'), + 'category' => $repository->getJournalCategoryName($journal), + 'budget_id' => $repository->getJournalBudgetId($journal), + 'tags' => join(',', $repository->getTags($journal)), 'source_account_id' => $sourceAccounts->first()->id, 'source_account_name' => $sourceAccounts->first()->edit_name, 'destination_account_id' => $destinationAccounts->first()->id, 'destination_account_name' => $destinationAccounts->first()->edit_name, // new custom fields: - 'due_date' => $journal->dateAsString('due_date'), - 'payment_date' => $journal->dateAsString('payment_date'), - 'invoice_date' => $journal->dateAsString('invoice_date'), - 'interal_reference' => $journal->getMeta('internal_reference'), - 'notes' => '', + 'due_date' => $repository->getJournalDate($journal, 'due_date'), + 'payment_date' => $repository->getJournalDate($journal, 'payment_date'), + 'invoice_date' => $repository->getJournalDate($journal, 'invoice_date'), + 'interal_reference' => $repository->getMetaField($journal, 'internal_reference'), + 'notes' => $repository->getNoteText($journal), // amount fields 'amount' => $pTransaction->amount, @@ -301,11 +304,6 @@ class SingleController extends Controller 'foreign_currency' => $foreignCurrency, 'destination_currency' => $foreignCurrency, ]; - /** @var Note $note */ - $note = $this->repository->getNote($journal); - if (null !== $note) { - $preFilled['notes'] = $note->text; - } // amounts for withdrawals and deposits: // amount, native_amount, source_amount, destination_amount @@ -333,17 +331,12 @@ class SingleController extends Controller * @param JournalRepositoryInterface $repository * * @return \Illuminate\Http\RedirectResponse - * @throws \FireflyIII\Exceptions\FireflyException */ public function store(JournalFormRequest $request, JournalRepositoryInterface $repository) { $doSplit = 1 === intval($request->get('split_journal')); $createAnother = 1 === intval($request->get('create_another')); $data = $request->getJournalData(); - - - var_dump($data);exit; - $journal = $repository->store($data); @@ -405,16 +398,7 @@ class SingleController extends Controller } // @codeCoverageIgnoreEnd - $data = $request->getJournalData(); - $data['transactions'][0]['currency_id'] = $journal->transaction_currency_id; - if ($data['currency_id'] !== $journal->transaction_currency_id) { - // currency ID is changed by user. Update transaction: - - $data['transactions'][0]['amount'] = $data['native_amount']; - $data['transactions'][0]['foreign_currency_id'] = $data['currency_id']; - $data['transactions'][0]['foreign_amount'] = $data['amount']; - } - + $data = $request->getJournalData(); $journal = $repository->update($journal, $data); /** @var array $files */ $files = $request->hasFile('attachments') ? $request->file('attachments') : null; diff --git a/app/Http/Requests/JournalFormRequest.php b/app/Http/Requests/JournalFormRequest.php index aec907fb6a..5df273d26f 100644 --- a/app/Http/Requests/JournalFormRequest.php +++ b/app/Http/Requests/JournalFormRequest.php @@ -46,14 +46,13 @@ class JournalFormRequest extends Request */ public function getJournalData() { - var_dump($this->all()); - $data = [ + $currencyId = $this->integer('amount_currency_id_amount'); + $data = [ 'type' => $this->get('what'), // type. can be 'deposit', 'withdrawal' or 'transfer' 'date' => $this->date('date'), 'tags' => explode(',', $this->string('tags')), 'user' => auth()->user()->id, - // all custom fields: 'interest_date' => $this->date('interest_date'), 'book_date' => $this->date('book_date'), @@ -71,13 +70,6 @@ class JournalFormRequest extends Request 'bill_id' => null, 'bill_name' => null, - // native amount and stuff like that: - //'currency_id' => $this->integer('amount_currency_id_amount'), - //'amount' => $this->string('amount'), - //'native_amount' => $this->string('native_amount'), - //'source_amount' => $this->string('source_amount'), - //'destination_amount' => $this->string('destination_amount'), - // transaction data: 'transactions' => [ [ @@ -101,15 +93,41 @@ class JournalFormRequest extends Request ], ], ]; - switch ($data['type']) { + switch (strtolower($data['type'])) { case 'withdrawal': - $data['transactions'][0]['currency_id'] = $this->integer('source_currency_id'); + $sourceCurrency = $this->integer('source_account_currency'); + $data['transactions'][0]['currency_id'] = $sourceCurrency; + $data['transactions'][0]['destination_id'] = null; // clear destination ID (transfer) + if ($sourceCurrency !== $currencyId) { + // user has selected a foreign currency. + $data['transactions'][0]['foreign_currency_id'] = $currencyId; + $data['transactions'][0]['foreign_amount'] = $this->string('amount'); + $data['transactions'][0]['amount'] = $this->string('native_amount'); + } + break; case 'deposit': - $data['transactions'][0]['currency_id'] = $this->integer('destination_currency_id'); + $destinationCurrency = $this->integer('destination_account_currency'); + $data['transactions'][0]['currency_id'] = $destinationCurrency; + $data['transactions'][0]['source_id'] = null; // clear destination ID (transfer) + if ($destinationCurrency !== $currencyId) { + // user has selected a foreign currency. + $data['transactions'][0]['foreign_currency_id'] = $currencyId; + $data['transactions'][0]['foreign_amount'] = $this->string('amount'); + $data['transactions'][0]['amount'] = $this->string('native_amount'); + } break; case 'transfer': - $data['transactions'][0]['currency_id'] = $this->integer('destination_currency_id'); + // by default just assume source currency + $sourceCurrency = $this->integer('source_account_currency'); + $destinationCurrency = $this->integer('destination_account_currency'); + $data['transactions'][0]['currency_id'] = $sourceCurrency; + if ($sourceCurrency !== $destinationCurrency) { + // user has selected a foreign currency. + $data['transactions'][0]['foreign_currency_id'] = $destinationCurrency; + $data['transactions'][0]['foreign_amount'] = $this->string('destination_amount'); + $data['transactions'][0]['amount'] = $this->string('source_amount'); + } break; } diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 1fa49a0174..f7d69e38f8 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -22,6 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Journal; +use Carbon\Carbon; use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\TransactionJournalFactory; @@ -33,6 +34,7 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Services\Internal\Destroy\JournalDestroyService; use FireflyIII\Services\Internal\Update\JournalUpdateService; +use FireflyIII\Support\CacheProperties; use FireflyIII\User; use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; @@ -207,6 +209,184 @@ class JournalRepository implements JournalRepositoryInterface return $journal->transactions()->where('amount', '<', 0)->first()->account; } + /** + * Returns the first positive transaction for the journal. Useful when editing journals. + * + * @param TransactionJournal $journal + * + * @return Transaction + */ + public function getFirstPosTransaction(TransactionJournal $journal): Transaction + { + /** @var Transaction $transaction */ + $transaction = $journal->transactions()->where('amount', '>', 0)->first(); + + return $transaction; + } + + /** + * Return the ID of the budget linked to the journal (if any) or the transactions (if any). + * + * @param TransactionJournal $journal + * + * @return int + */ + public function getJournalBudgetId(TransactionJournal $journal): int + { + $budget = $journal->budgets()->first(); + if (null !== $budget) { + return $budget->id; + } + $budget = $journal->transactions()->first()->budgets()->first(); + if (null !== $budget) { + return $budget->id; + } + + return 0; + } + + /** + * Return the name of the category linked to the journal (if any) or to the transactions (if any). + * + * @param TransactionJournal $journal + * + * @return string + */ + public function getJournalCategoryName(TransactionJournal $journal): string + { + $category = $journal->categories()->first(); + if (null !== $category) { + return $category->name; + } + $category = $journal->transactions()->first()->categories()->first(); + if (null !== $category) { + return $category->name; + } + + return ''; + } + + /** + * Return requested date as string. When it's a NULL return the date of journal, + * otherwise look for meta field and return that one. + * + * @param TransactionJournal $journal + * @param null|string $field + * + * @return string + */ + public function getJournalDate(TransactionJournal $journal, ?string $field): string + { + if (is_null($field)) { + return $journal->date->format('Y-m-d'); + } + if (null !== $journal->$field && $journal->$field instanceof Carbon) { + // make field NULL + $carbon = clone $journal->$field; + $journal->$field = null; + $journal->save(); + + // create meta entry + $journal->setMeta($field, $carbon); + + // return that one instead. + return $carbon->format('Y-m-d'); + } + $metaField = $journal->getMeta($field); + if (null !== $metaField) { + $carbon = new Carbon($metaField); + + return $carbon->format('Y-m-d'); + } + + return ''; + } + + /** + * Return a list of all destination accounts related to journal. + * + * @param TransactionJournal $journal + * + * @return Collection + */ + public function getJournalDestinationAccounts(TransactionJournal $journal): Collection + { + $cache = new CacheProperties; + $cache->addProperty($journal->id); + $cache->addProperty('destination-account-list'); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + $transactions = $journal->transactions()->where('amount', '>', 0)->orderBy('transactions.account_id')->with('account')->get(); + $list = new Collection; + /** @var Transaction $t */ + foreach ($transactions as $t) { + $list->push($t->account); + } + $list = $list->unique('id'); + $cache->store($list); + + return $list; + } + + /** + * Return a list of all source accounts related to journal. + * + * @param TransactionJournal $journal + * + * @return Collection + */ + public function getJournalSourceAccounts(TransactionJournal $journal): Collection + { + $cache = new CacheProperties; + $cache->addProperty($journal->id); + $cache->addProperty('source-account-list'); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + $transactions = $journal->transactions()->where('amount', '<', 0)->orderBy('transactions.account_id')->with('account')->get(); + $list = new Collection; + /** @var Transaction $t */ + foreach ($transactions as $t) { + $list->push($t->account); + } + $list = $list->unique('id'); + $cache->store($list); + + return $list; + } + + /** + * Return value of a meta field (or NULL) as a string. + * + * @param TransactionJournal $journal + * @param string $field + * + * @return null|string + */ + public function getMetaField(TransactionJournal $journal, string $field): ?string + { + $value = null; + $cache = new CacheProperties; + $cache->addProperty('journal-meta'); + $cache->addProperty($journal->id); + $cache->addProperty($field); + + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + + Log::debug(sprintf('Looking for journal #%d meta field "%s".', $journal->id, $field)); + $entry = $journal->transactionJournalMeta()->where('name', $field)->first(); + if (is_null($entry)) { + return null; + } + $value = $entry->data; + $cache->store($value); + + return $value; + } + /** * @param TransactionJournal $journal * @@ -217,6 +397,23 @@ class JournalRepository implements JournalRepositoryInterface return $journal->notes()->first(); } + /** + * Return text of a note attached to journal, or ''. + * + * @param TransactionJournal $journal + * + * @return string + */ + public function getNoteText(TransactionJournal $journal): string + { + $note = $this->getNote($journal); + if (is_null($note)) { + return ''; + } + + return $note->text; + } + /** * Get account of transaction that is less than zero. Only works with unsplit journals. * @@ -229,6 +426,30 @@ class JournalRepository implements JournalRepositoryInterface return $journal->transactions()->where('amount', '>', 0)->first()->account; } + /** + * Return all tags as strings in an array. + * + * @param TransactionJournal $journal + * + * @return array + */ + public function getTags(TransactionJournal $journal): array + { + return $journal->tags()->get()->pluck('tag')->toArray(); + } + + /** + * Return the transaction type of the journal. + * + * @param TransactionJournal $journal + * + * @return string + */ + public function getTransactionType(TransactionJournal $journal): string + { + return $journal->transactionType->type; + } + /** * @return Collection */ diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index 4a73ce0f62..af4fa2c2f0 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -109,6 +109,72 @@ interface JournalRepositoryInterface */ public function getDestinationAccount(TransactionJournal $journal): Account; + /** + * Returns the first positive transaction for the journal. Useful when editing journals. + * + * @param TransactionJournal $journal + * + * @return Transaction + */ + public function getFirstPosTransaction(TransactionJournal $journal): Transaction; + + /** + * Return the ID of the budget linked to the journal (if any) or the transactions (if any). + * + * @param TransactionJournal $journal + * + * @return int + */ + public function getJournalBudgetId(TransactionJournal $journal): int; + + /** + * Return the name of the category linked to the journal (if any) or to the transactions (if any). + * + * @param TransactionJournal $journal + * + * @return string + */ + public function getJournalCategoryName(TransactionJournal $journal): string; + + /** + * Return requested date as string. When it's a NULL return the date of journal, + * otherwise look for meta field and return that one. + * + * @param TransactionJournal $journal + * @param null|string $field + * + * @return string + */ + public function getJournalDate(TransactionJournal $journal, ?string $field): string; + + /** + * Return a list of all destination accounts related to journal. + * + * @param TransactionJournal $journal + * + * @return Collection + */ + public function getJournalDestinationAccounts(TransactionJournal $journal): Collection; + + /** + * Return a list of all source accounts related to journal. + * + * @param TransactionJournal $journal + * + * @return Collection + */ + public function getJournalSourceAccounts(TransactionJournal $journal): Collection; + + /** + * Return value of a meta field (or NULL). + * + * @param TransactionJournal $journal + * @param string $field + * + * @return null|string + */ + public function getMetaField(TransactionJournal $journal, string $field): ?string; + /** * @param TransactionJournal $journal * @@ -116,6 +182,15 @@ interface JournalRepositoryInterface */ public function getNote(TransactionJournal $journal): ?Note; + /** + * Return text of a note attached to journal, or ''. + * + * @param TransactionJournal $journal + * + * @return string + */ + public function getNoteText(TransactionJournal $journal): string; + /** * Get account of transaction that is less than zero. Only works with unsplit journals. * @@ -125,6 +200,24 @@ interface JournalRepositoryInterface */ public function getSourceAccount(TransactionJournal $journal): Account; + /** + * Return all tags as strings in an array. + * + * @param TransactionJournal $journal + * + * @return array + */ + public function getTags(TransactionJournal $journal): array; + + /** + * Return the transaction type of the journal. + * + * @param TransactionJournal $journal + * + * @return string + */ + public function getTransactionType(TransactionJournal $journal): string; + /** * @return Collection */ diff --git a/public/js/ff/transactions/single/common.js b/public/js/ff/transactions/single/common.js index 16980dd300..4da24c4b03 100644 --- a/public/js/ff/transactions/single/common.js +++ b/public/js/ff/transactions/single/common.js @@ -24,6 +24,7 @@ var countConversions = 0; $(document).ready(function () { "use strict"; + console.log('in common.js document.ready'); setCommonAutocomplete(); runModernizer(); }); @@ -45,6 +46,7 @@ function runModernizer() { * Auto complete things in both edit and create routines: */ function setCommonAutocomplete() { + console.log('In setCommonAutoComplete()'); $.getJSON('json/tags').done(function (data) { var opt = { typeahead: { @@ -82,12 +84,13 @@ function setCommonAutocomplete() { /** * When the user changes the currency in the amount drop down, it may jump from being - * the native currency to a foreign currency. This triggers the display of several + * the native currency to a foreign currency. Thi s triggers the display of several * information things that make sure that the user always supplies the amount in the native currency. * * @returns {boolean} */ function selectsForeignCurrency() { + console.log('In selectsForeignCurrency()'); var foreignCurrencyId = parseInt($('input[name="amount_currency_id_amount"]').val()); var selectedAccountId = getAccountId(); var nativeCurrencyId = parseInt(accountInfo[selectedAccountId].preferredCurrency); diff --git a/public/js/ff/transactions/single/edit.js b/public/js/ff/transactions/single/edit.js index d36d22bce5..4efa559332 100644 --- a/public/js/ff/transactions/single/edit.js +++ b/public/js/ff/transactions/single/edit.js @@ -18,30 +18,83 @@ * along with Firefly III. If not, see . */ -/** global: what, Modernizr, selectsForeignCurrency, convertForeignToNative, validateCurrencyForTransfer, convertSourceToDestination, journalData, journal, accountInfo, exchangeRateInstructions, currencyInfo */ +/** global: what, Modernizr, selectsForeignCurrency, accountInfo, convertForeignToNative, validateCurrencyForTransfer, convertSourceToDestination, journalData, journal, accountInfo, exchangeRateInstructions, currencyInfo */ $(document).ready(function () { "use strict"; + console.log('in edit.js document.ready'); setAutocompletes(); updateInitialPage(); + // update the two source account currency ID fields (initial value): + initCurrencyIdValues(); + + // respond to user input: - $('.currency-option').on('click', selectsForeignCurrency); + $('.currency-option').on('click', function () { + // to display instructions and stuff like that. + selectsForeignCurrency(); + }); $('#ffInput_amount').on('change', convertForeignToNative); // respond to transfer changes: - $('#ffInput_source_account_id').on('change', validateCurrencyForTransfer); - $('#ffInput_destination_account_id').on('change', validateCurrencyForTransfer); + $('#ffInput_source_account_id').on('change', function () { + validateCurrencyForTransfer(); + // update the two source account currency ID fields (initial value): + initCurrencyIdValues(); + }); + $('#ffInput_destination_account_id').on('change', function () { + validateCurrencyForTransfer(); + // update the two source account currency ID fields (initial value): + initCurrencyIdValues(); + }); // convert source currency to destination currency (slightly different routine for transfers) $('#ffInput_source_amount').on('change', convertSourceToDestination); + + }); +/** + * Fills two hidden variables with the correct currency ID. + */ +function initCurrencyIdValues() { + var currencyId; + if (journal.transaction_type.type === "Withdrawal") { + // update source from page load info: + currencyId = $('input[name="amount_currency_id_amount"]').val(); + console.log('Set source account currency to ' + currencyId); + $('input[name="source_account_currency"]').val(currencyId); + return; + } + + if (journal.transaction_type.type === "Deposit") { + // update destination from page load info: + currencyId = $('input[name="amount_currency_id_amount"]').val(); + console.log('Set destination account currency to ' + currencyId); + $('input[name="destination_account_currency"]').val(currencyId); + return; + } + var sourceAccount = $('select[name="source_account_id"]').val(); + console.log('Source account is ' + sourceAccount); + var destAccount = $('select[name="destination_account_id"]').val(); + console.log('Destination account is ' + destAccount); + + var sourceCurrency = parseInt(accountInfo[sourceAccount].preferredCurrency); + var destCurrency = parseInt(accountInfo[destAccount].preferredCurrency); + + console.log('Set source account currency to ' + sourceCurrency); + $('input[name="source_account_currency"]').val(sourceCurrency); + + console.log('Set destination account currency to ' + destCurrency); + $('input[name="destination_account_currency"]').val(destCurrency); +} + /** * Set some initial values for the user to see. */ function updateInitialPage() { - + console.log('in updateInitialPage()'); if (journal.transaction_type.type === "Transfer") { $('#native_amount_holder').hide(); $('#amount_holder').hide(); @@ -61,7 +114,6 @@ function updateInitialPage() { $('#source_amount_holder').hide(); $('#destination_amount_holder').hide(); - if (journalData.native_currency.id === journalData.currency.id) { $('#exchange_rate_instruction_holder').hide(); $('#native_amount_holder').hide(); @@ -78,6 +130,7 @@ function updateInitialPage() { * Get accountID based on some meta info. */ function getAccountId() { + console.log('in getAccountId()'); if (journal.transaction_type.type === "Withdrawal") { return $('select[name="source_account_id"]').val(); } @@ -104,6 +157,7 @@ function setAutocompletes() { * @returns {XML|string|void} */ function getExchangeInstructions() { + console.log('In getExchangeInstructions()'); var selectedAccountId = getAccountId(); var foreignCurrencyId = parseInt($('input[name="amount_currency_id_amount"]').val()); var nativeCurrencyId = parseInt(accountInfo[selectedAccountId].preferredCurrency); diff --git a/resources/views/transactions/single/edit.twig b/resources/views/transactions/single/edit.twig index e70e57ca2e..b3c2241db2 100644 --- a/resources/views/transactions/single/edit.twig +++ b/resources/views/transactions/single/edit.twig @@ -209,7 +209,8 @@ - + +