From 6d34cfb9402f6184dd569789b56f791b2ab8d35d Mon Sep 17 00:00:00 2001 From: James Cole Date: Sat, 20 Jul 2019 06:47:34 +0200 Subject: [PATCH] Implemented new link thing. --- .../Json/AutoCompleteController.php | 35 ++ .../Transaction/EditController.php | 5 +- .../Transaction/LinkController.php | 18 +- .../Transaction/ShowController.php | 8 +- app/Http/Requests/JournalLinkRequest.php | 9 +- app/Models/TransactionJournal.php | 4 +- .../TransactionGroupRepository.php | 12 +- config/firefly.php | 5 - public/v1/js/ff/transactions/show.js | 57 +- resources/lang/en_US/firefly.php | 5 +- .../views/v1/transactions/links/modal.twig | 52 ++ resources/views/v1/transactions/show.twig | 577 +++++++++--------- routes/web.php | 28 +- .../Transaction/LinkControllerTest.php | 147 ++--- tests/TestCase.php | 9 + 15 files changed, 581 insertions(+), 390 deletions(-) create mode 100644 resources/views/v1/transactions/links/modal.twig diff --git a/app/Http/Controllers/Json/AutoCompleteController.php b/app/Http/Controllers/Json/AutoCompleteController.php index 09bf53be76..902a1cc8b0 100644 --- a/app/Http/Controllers/Json/AutoCompleteController.php +++ b/app/Http/Controllers/Json/AutoCompleteController.php @@ -149,7 +149,42 @@ class AutoCompleteController extends Controller return response()->json($array); + } + /** + * Searches in the titles of all transaction journals. + * The result is limited to the top 15 unique results. + * + * If the query is numeric, it will append the journal with that particular ID. + * + * @param Request $request + * @return JsonResponse + */ + public function allJournalsWithID(Request $request): JsonResponse + { + $search = (string)$request->get('search'); + /** @var JournalRepositoryInterface $repository */ + $repository = app(JournalRepositoryInterface::class); + $result = $repository->searchJournalDescriptions($search); + $array = []; + if (is_numeric($search)) { + $firstResult = $repository->findNull((int)$search); + if (null !== $firstResult) { + $array[] = $firstResult->toArray(); + } + } + // if not numeric, search ahead! + + // limit and unique + $limited = $result->slice(0, 15); + $array = array_merge($array, $limited->toArray()); + foreach ($array as $index => $item) { + // give another key for consistency + $array[$index]['name'] = sprintf('#%d: %s', $item['id'], $item['description']); + } + + + return response()->json($array); } /** diff --git a/app/Http/Controllers/Transaction/EditController.php b/app/Http/Controllers/Transaction/EditController.php index b0a8646d6a..3212a533a3 100644 --- a/app/Http/Controllers/Transaction/EditController.php +++ b/app/Http/Controllers/Transaction/EditController.php @@ -33,7 +33,8 @@ class EditController extends Controller { /** - * SingleController constructor. + * EditController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -48,7 +49,7 @@ class EditController extends Controller // some useful repositories: $this->middleware( - function ($request, $next) { + static function ($request, $next) { app('view')->share('title', (string)trans('firefly.transactions')); app('view')->share('mainTitleIcon', 'fa-repeat'); diff --git a/app/Http/Controllers/Transaction/LinkController.php b/app/Http/Controllers/Transaction/LinkController.php index ab938ff166..124bfef1da 100644 --- a/app/Http/Controllers/Transaction/LinkController.php +++ b/app/Http/Controllers/Transaction/LinkController.php @@ -28,6 +28,8 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournalLink; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; +use Illuminate\Contracts\View\Factory; +use Illuminate\View\View; use Log; use URL; @@ -43,6 +45,7 @@ class LinkController extends Controller /** * LinkController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -61,12 +64,23 @@ class LinkController extends Controller ); } + /** + * @param TransactionJournal $journal + * @return Factory|View + */ + public function modal(TransactionJournal $journal) + { + $linkTypes = $this->repository->get(); + + return view('transactions.links.modal', compact('journal', 'linkTypes')); + } + /** * Delete a link. * * @param TransactionJournalLink $link * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @return Factory|View */ public function delete(TransactionJournalLink $link) { @@ -104,9 +118,9 @@ class LinkController extends Controller */ public function store(JournalLinkRequest $request, TransactionJournal $journal) { + $linkInfo = $request->getLinkInfo(); Log::debug('We are here (store)'); - $linkInfo = $request->getLinkInfo(); $other = $this->journalRepository->findNull($linkInfo['transaction_journal_id']); if (null === $other) { session()->flash('error', (string)trans('firefly.invalid_link_selection')); diff --git a/app/Http/Controllers/Transaction/ShowController.php b/app/Http/Controllers/Transaction/ShowController.php index d9f0165922..1f203e4bd8 100644 --- a/app/Http/Controllers/Transaction/ShowController.php +++ b/app/Http/Controllers/Transaction/ShowController.php @@ -73,7 +73,7 @@ class ShowController extends Controller $type = (string)trans(sprintf('firefly.%s', strtolower($first->transactionType->type))); $title = 1 === $splits ? $first->description : $transactionGroup->title; $subTitle = sprintf('%s: "%s"', $type, $title); - $message = $request->get('message'); + $message = $request->get('message'); /** @var TransactionGroupTransformer $transformer */ $transformer = app(TransactionGroupTransformer::class); @@ -112,9 +112,9 @@ class ShowController extends Controller return view( 'transactions.show', compact( - 'transactionGroup', 'amounts', 'first', 'type', 'subTitle', 'splits', 'groupArray', - 'events', 'attachments', 'links','message' - ) + 'transactionGroup', 'amounts', 'first', 'type', 'subTitle', 'splits', 'groupArray', + 'events', 'attachments', 'links', 'message' + ) ); } } \ No newline at end of file diff --git a/app/Http/Requests/JournalLinkRequest.php b/app/Http/Requests/JournalLinkRequest.php index d5fac5708f..aefb3a7cd0 100644 --- a/app/Http/Requests/JournalLinkRequest.php +++ b/app/Http/Requests/JournalLinkRequest.php @@ -52,12 +52,9 @@ class JournalLinkRequest extends Request $linkType = $this->get('link_type'); $parts = explode('_', $linkType); $return['link_type_id'] = (int)$parts[0]; - $return['transaction_journal_id'] = $this->integer('link_journal_id'); + $return['transaction_journal_id'] = $this->integer('opposing'); $return['notes'] = $this->string('notes'); $return['direction'] = $parts[1]; - if (0 === $return['transaction_journal_id'] && ctype_digit($this->string('link_other'))) { - $return['transaction_journal_id'] = $this->integer('link_other'); - } return $return; } @@ -81,8 +78,8 @@ class JournalLinkRequest extends Request // fixed return [ - 'link_type' => sprintf('required|in:%s', $string), - 'link_journal_id' => 'belongsToUser:transaction_journals', + 'link_type' => sprintf('required|in:%s', $string), + 'opposing' => 'belongsToUser:transaction_journals', ]; } } diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 372ddbc37c..1675f69bf3 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -174,14 +174,12 @@ class TransactionJournal extends Model */ public static function routeBinder(string $value): TransactionJournal { - throw new FireflyException('Journal binder is permanently out of order.'); if (auth()->check()) { $journalId = (int)$value; /** @var User $user */ $user = auth()->user(); /** @var TransactionJournal $journal */ - $journal = $user->transactionJournals()->where('transaction_journals.id', $journalId) - ->first(['transaction_journals.*']); + $journal = $user->transactionJournals()->where('transaction_journals.id', $journalId)->first(['transaction_journals.*']); if (null !== $journal) { return $journal; } diff --git a/app/Repositories/TransactionGroup/TransactionGroupRepository.php b/app/Repositories/TransactionGroup/TransactionGroupRepository.php index c745c803b1..d606ec5a7c 100644 --- a/app/Repositories/TransactionGroup/TransactionGroupRepository.php +++ b/app/Repositories/TransactionGroup/TransactionGroupRepository.php @@ -119,6 +119,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface $amount = $this->getFormattedAmount($entry->destination); $foreignAmount = $this->getFormattedForeignAmount($entry->destination); $return[$journalId][] = [ + 'id' => $entry->id, 'link' => $entry->outward, 'group' => $entry->destination->transaction_group_id, 'description' => $entry->destination->description, @@ -130,6 +131,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface $amount = $this->getFormattedAmount($entry->source); $foreignAmount = $this->getFormattedForeignAmount($entry->source); $return[$journalId][] = [ + 'id' => $entry->id, 'link' => $entry->inward, 'group' => $entry->source->transaction_group_id, 'description' => $entry->source->description, @@ -145,7 +147,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface /** * Return object with all found meta field things as Carbon objects. * - * @param int $journalId + * @param int $journalId * @param array $fields * * @return NullArrayObject @@ -171,7 +173,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface /** * Return object with all found meta field things. * - * @param int $journalId + * @param int $journalId * @param array $fields * * @return NullArrayObject @@ -247,9 +249,9 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface $return[$journalId] = $return[$journalId] ?? []; $return[$journalId][] = [ - 'piggy' => $row->piggyBank->name, + 'piggy' => $row->piggyBank->name, 'piggy_id' => $row->piggy_bank_id, - 'amount' => app('amount')->formatAnything($currency, $row->amount), + 'amount' => app('amount')->formatAnything($currency, $row->amount), ]; } @@ -300,7 +302,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface /** * @param TransactionGroup $transactionGroup - * @param array $data + * @param array $data * * @return TransactionGroup * diff --git a/config/firefly.php b/config/firefly.php index d9b56e599d..6293af0e59 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -214,10 +214,6 @@ return [ 'application/vnd.oasis.opendocument.image', ], 'list_length' => 10, - 'export_formats' => [ - 'csv' => CsvExporter::class, - ], - 'default_export_format' => 'csv', 'default_import_format' => 'csv', 'bill_periods' => ['weekly', 'monthly', 'quarterly', 'half-year', 'yearly'], 'accountRoles' => ['defaultAsset', 'sharedAsset', 'savingAsset', 'ccAsset', 'cashWalletAsset'], @@ -381,7 +377,6 @@ return [ 'recurrence' => Recurrence::class, 'rule' => Rule::class, 'ruleGroup' => RuleGroup::class, - 'exportJob' => ExportJob::class, 'importJob' => ImportJob::class, 'transaction' => Transaction::class, 'transactionGroup' => TransactionGroup::class, diff --git a/public/v1/js/ff/transactions/show.js b/public/v1/js/ff/transactions/show.js index e1c4ec76c0..8207a8df98 100644 --- a/public/v1/js/ff/transactions/show.js +++ b/public/v1/js/ff/transactions/show.js @@ -22,5 +22,60 @@ $(function () { "use strict"; + $('.link-modal').click(getLinkModal); + $('#linkJournalModal').on('shown.bs.modal', function () { + makeAutoComplete(); + }) +}); -}); \ No newline at end of file +function getLinkModal(e) { + var button = $(e.currentTarget); + var journalId = parseInt(button.data('journal')); + var url = modalDialogURI.replace('%JOURNAL%', journalId); + console.log(url); + $.get(url).done(function (data) { + $('#linkJournalModal').html(data).modal('show'); + + }).fail(function () { + alert('Could not load the data to link journals. Sorry :('); + button.prop('disabled', true); + }); + + return false; +} + +function makeAutoComplete() { + + // input link-journal + var source = new Bloodhound({ + datumTokenizer: Bloodhound.tokenizers.obj.whitespace('name'), + queryTokenizer: Bloodhound.tokenizers.whitespace, + prefetch: { + url: acURI + '?uid=' + uid, + filter: function (list) { + return $.map(list, function (item) { + return item; + }); + } + }, + remote: { + url: acURI + '?search=%QUERY&uid=' + uid, + wildcard: '%QUERY', + filter: function (list) { + return $.map(list, function (item) { + return item; + }); + } + } + }); + source.initialize(); + $('.link-journal').typeahead({hint: true, highlight: true,}, {source: source, displayKey: 'name', autoSelect: false}) + .on('typeahead:select', selectedJournal); +} + +function selectedJournal(event, journal) { + $('#journal-selector').hide(); + $('#journal-selection').show(); + $('#selected-journal').html('' + journal.description + '').show(); + $('input[name="opposing"]').val(journal.id); +} \ No newline at end of file diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 0751d90f59..9140d5815c 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -1215,12 +1215,13 @@ return [ 'do_not_save_connection' => '(do not save connection)', 'link_transaction' => 'Link transaction', 'link_to_other_transaction' => 'Link this transaction to another transaction', - 'select_transaction_to_link' => 'Select a transaction to link this transaction to', + 'select_transaction_to_link' => 'Select a transaction to link this transaction to. The links are currently unused in Firefly III (apart from being shown), but I plan to change this in the future. Use the search box to select a transaction either by title or by ID. If you want to add custom link types, check out the administration section.', 'this_transaction' => 'This transaction', 'transaction' => 'Transaction', 'comments' => 'Comments', - 'to_link_not_found' => 'If the transaction you want to link to is not listed, simply enter its ID.', + 'link_notes' => 'Any notes you wish to store with the link.', 'invalid_link_selection' => 'Cannot link these transactions', + 'selected_transaction' => 'Selected transaction', 'journals_linked' => 'Transactions are linked.', 'journals_error_linked' => 'These transactions are already linked.', 'journals_link_to_self' => 'You cannot link a transaction to itself', diff --git a/resources/views/v1/transactions/links/modal.twig b/resources/views/v1/transactions/links/modal.twig new file mode 100644 index 0000000000..3fc07d9ef2 --- /dev/null +++ b/resources/views/v1/transactions/links/modal.twig @@ -0,0 +1,52 @@ +
+ + + +
\ No newline at end of file diff --git a/resources/views/v1/transactions/show.twig b/resources/views/v1/transactions/show.twig index 6f5d7ca69f..80b8d0e487 100644 --- a/resources/views/v1/transactions/show.twig +++ b/resources/views/v1/transactions/show.twig @@ -6,18 +6,18 @@ {% block content %} {% if message == 'created' %} -
-
- {% endif %} {% if message == 'updated' %} @@ -69,335 +69,362 @@
- -
-
-
-

{{ 'transaction_journal_meta'|_ }}

-
-
- - - {% if type != 'Withdrawal' or splits == 1 %} - - - - - {% endif %} - - {% if type != 'Deposit' or splits == 1 %} - - - - - {% endif %} - - - - - -
- {{ 'source_accounts'|_ }} - - {% for journal in groupArray.transactions %} - - {{ journal.source_name }} - - {% if loop.index0 != groupArray.transactions|length -1 %}, {% endif %} - {% endfor %} -
- {{ 'destination_accounts'|_ }} - - - {% for journal in groupArray.transactions %} - - {{ journal.destination_name }} - - {% if loop.index0 != groupArray.transactions|length -1 %}, {% endif %} - {% endfor %} -
{{ 'total_amount'|_ }} - {% for amount in amounts %} - {% if type == 'Withdrawal' or type == 'Deposit' %} - {{ formatAmountBySymbol(amount.amount*-1,amount.symbol, amount.decimal_places) }}, - {% elseif type == 'Transfer' %} - - {{ formatAmountBySymbol(amount.amount, amount.symbol, amount.decimal_places, false) }}, - - {% endif %} - {% endfor %} -
-
-
-
- -
- -
-{% if splits > 1 %} -
-
-

{{ 'splits'|_ }}

-
-
-{% endif %} -{% set boxSize=12 %} -{% if(splits == 2) %} - {% set boxSize=6 %} -{% endif %} -{% if (splits > 2) %} - {% set boxSize = 4 %} -{% endif %} -
- {% for index,journal in groupArray.transactions %} -
-
+
+
-

- {{ journal.description }} - {% if journal.reconciled %} - - {% endif %} - {% if splits > 1 %} - - {{ index+1 }} / {{ splits }} - - {% endif %} -

+

{{ 'transaction_journal_meta'|_ }}

- - - - - {% if null != journal.category_id %} +
- {{ journal.source_name }} → - {% if type == 'Withdrawal' or type == 'Deposit' %} - {{ formatAmountBySymbol(journal.amount*-1, journal.currency_symbol, journal.currency_decimal_places) }} - {% elseif type == 'Transfer' %} - - {{ formatAmountBySymbol(journal.amount, journal.currency_symbol, journal.currency_decimal_places, false) }} - - {% endif %} - - - {% if null != journal.foreign_amount %} - {% if type == 'Withdrawal' or type == 'Deposit' %} - ({{ formatAmountBySymbol(journal.foreign_amount*-1, journal.foreign_currency_symbol, journal.foreign_currency_decimal_places) }}) - {% elseif type == 'Transfer' %} - - ({{ formatAmountBySymbol(journal.foreign_amount, journal.foreign_currency_symbol, journal.foreign_currency_decimal_places, false) }}) - - {% endif %} - {% endif %} - - - → - {{ journal.destination_name }} -
+ + {% if type != 'Withdrawal' or splits == 1 %} - - - - {% endif %} - {% if null != journal.budget_id and type == 'Withdrawal' %} - - - - - {% endif %} - {% if null != journal.bill_id and type == 'Withdrawal' %} - - - - - {% endif %} - - {% for dateField in ['interest_date','book_date','process_date','due_date','payment_date','invoice_date'] %} - {% if journalHasMeta(journal.transaction_journal_id, dateField) %} - - - - - {% endif %} - {% endfor %} - {% for metaField in ['external_id','bunq_payment_id','internal_reference','sepa_batch_id','sepa_ct_id','sepa_ct_op','sepa_db','sepa_country','sepa_cc','sepa_ep','sepa_ci'] %} - {% if journalHasMeta(journal.transaction_journal_id, metaField) %} - - - - - {% endif %} - {% endfor %} - {% if null != journal.notes and '' != journal.notes %} - - - - - {% endif %} - {% if journal.tags|length > 0 %} - - + {% endif %} + + {% if type != 'Deposit' or splits == 1 %} + + + + + {% endif %} + + + + +
{{ 'category'|_ }}{{ journal.category_name }}
{{ 'budget'|_ }}{{ journal.budget_name }}
{{ 'bill'|_ }}{{ journal.bill_name }}
{{ trans('list.'~dateField) }}{{ journalGetMetaDate(journal.transaction_journal_id, dateField).formatLocalized(monthAndDayFormat) }}
{{ trans('list.'~metaField) }}{{ journalGetMetaField(journal.transaction_journal_id, metaField) }}
{{ trans('list.notes') }}{{ journal.notes|markdown }}
{{ 'tags'|_ }} - {% for tag in journal.tags %} -

- - {{ tag }} -

+ {{ 'source_accounts'|_ }} +
+ {% for journal in groupArray.transactions %} + + {{ journal.source_name }} + + {% if loop.index0 != groupArray.transactions|length -1 %}, {% endif %} {% endfor %}
+ {{ 'destination_accounts'|_ }} + + + {% for journal in groupArray.transactions %} + + {{ journal.destination_name }} + + {% if loop.index0 != groupArray.transactions|length -1 %}, {% endif %} + {% endfor %} +
{{ 'total_amount'|_ }} + {% for amount in amounts %} + {% if type == 'Withdrawal' or type == 'Deposit' %} + {{ formatAmountBySymbol(amount.amount*-1,amount.symbol, amount.decimal_places) }}, + {% elseif type == 'Transfer' %} + + {{ formatAmountBySymbol(amount.amount, amount.symbol, amount.decimal_places, false) }}, + + {% endif %} + {% endfor %} +
+
+
+
- - {% if links[journal.transaction_journal_id]|length > 0 %} +
+ {% if splits > 1 %} +
+
+

{{ 'splits'|_ }}

+
+
+ {% endif %} + {% set boxSize=6 %} + {% if(splits == 2) %} + {% set boxSize=6 %} + {% endif %} + {% if (splits > 2) %} + {% set boxSize = 4 %} + {% endif %} +
+ {% for index,journal in groupArray.transactions %} +

- {{ 'journal_links'|_ }} + {{ journal.description }} + {% if journal.reconciled %} + + {% endif %} + {% if splits > 1 %} + + {{ index+1 }} / {{ splits }} + + {% endif %}

- {% for link in links[journal.transaction_journal_id] %} - - - + + + {% if null != journal.category_id %} + + + + + {% endif %} + {% if null != journal.budget_id and type == 'Withdrawal' %} + + + + + {% endif %} + {% if null != journal.bill_id and type == 'Withdrawal' %} + + + + + {% endif %} + + {% for dateField in ['interest_date','book_date','process_date','due_date','payment_date','invoice_date'] %} + {% if journalHasMeta(journal.transaction_journal_id, dateField) %} + + + + + {% endif %} + {% endfor %} + {% for metaField in ['external_id','bunq_payment_id','internal_reference','sepa_batch_id','sepa_ct_id','sepa_ct_op','sepa_db','sepa_country','sepa_cc','sepa_ep','sepa_ci'] %} + {% if journalHasMeta(journal.transaction_journal_id, metaField) %} + + + + + {% endif %} + {% endfor %} + {% if null != journal.notes and '' != journal.notes %} + + + + + {% endif %} + {% if journal.tags|length > 0 %} + + + - {% endfor %} - + {% endif %}
-
- - -
-
{{ link.link }} "{{ link.description }}" +
+ {{ journal.source_name }} → + {% if type == 'Withdrawal' or type == 'Deposit' %} + {{ formatAmountBySymbol(journal.amount*-1, journal.currency_symbol, journal.currency_decimal_places) }} + {% elseif type == 'Transfer' %} + + {{ formatAmountBySymbol(journal.amount, journal.currency_symbol, journal.currency_decimal_places, false) }} + + {% endif %} - ({{ link.amount|raw }}) - {% if '' != link.foreign_amount %} - ({{ link.foreign_amount|raw }}) + + {% if null != journal.foreign_amount %} + {% if type == 'Withdrawal' or type == 'Deposit' %} + ({{ formatAmountBySymbol(journal.foreign_amount*-1, journal.foreign_currency_symbol, journal.foreign_currency_decimal_places) }}) + {% elseif type == 'Transfer' %} + + ({{ formatAmountBySymbol(journal.foreign_amount, journal.foreign_currency_symbol, journal.foreign_currency_decimal_places, false) }}) + {% endif %} + {% endif %} + + + → + {{ journal.destination_name }} +
{{ 'category'|_ }}{{ journal.category_name }}
{{ 'budget'|_ }}{{ journal.budget_name }}
{{ 'bill'|_ }}{{ journal.bill_name }}
{{ trans('list.'~dateField) }}{{ journalGetMetaDate(journal.transaction_journal_id, dateField).formatLocalized(monthAndDayFormat) }}
{{ trans('list.'~metaField) }}{{ journalGetMetaField(journal.transaction_journal_id, metaField) }}
{{ trans('list.notes') }}{{ journal.notes|markdown }}
{{ 'tags'|_ }} + {% for tag in journal.tags %} +

+ + {{ tag }} +

+ {% endfor %}
-
- {% endif %} - - - {% if attachments[journal.transaction_journal_id]|length > 0 %} -
-
-

{{ 'attachments'|_ }}

+ -
- - {% for attachment in attachments[journal.transaction_journal_id] %} - - + + {% endfor %} +
-
- - +
+ + + {% if links[journal.transaction_journal_id]|length > 0 %} +
+
+

+ {{ 'journal_links'|_ }} +

+
+
+ + {% for link in links[journal.transaction_journal_id] %} + + + + + {% endfor %} + +
+
+ + +
+
{{ link.link }} "{{ link.description }}" + + ({{ link.amount|raw }}) + {% if '' != link.foreign_amount %} + ({{ link.foreign_amount|raw }}) + {% endif %} +
+
+
+ {% endif %} + + + {% if attachments[journal.transaction_journal_id]|length > 0 %} +
+
+

{{ 'attachments'|_ }}

+
+
+ + {% for attachment in attachments[journal.transaction_journal_id] %} + + + - - - {% endfor %} -
+
+ + + {% if attachment.file_exists %} + + {% endif %} + {% if not attachment.file_exists %} + + {% endif %} +
+
{% if attachment.file_exists %} - + + + {% if attachment.title %} + {{ attachment.title }} + {% else %} + {{ attachment.filename }} + {% endif %} + + ({{ attachment.size|filesize }}) + {% if null != attachment.notes and '' != attachment.notes %} + {{ attachment.notes|markdown }} + {% endif %} {% endif %} {% if not attachment.file_exists %} - - {% endif %} - - - {% if attachment.file_exists %} - - + {% if attachment.title %} {{ attachment.title }} {% else %} {{ attachment.filename }} {% endif %} - - ({{ attachment.size|filesize }}) - {% if null != attachment.notes and '' != attachment.notes %} - {{ attachment.notes|markdown }} +
+ {{ 'attachment_not_found'|_ }} {% endif %} - {% endif %} - {% if not attachment.file_exists %} - - {% if attachment.title %} - {{ attachment.title }} - {% else %} - {{ attachment.filename }} - {% endif %} -
- {{ 'attachment_not_found'|_ }} - {% endif %} -
+
+
-
- {% endif %} + {% endif %} - - {% if events[journal.transaction_journal_id]|length > 0 %} -
-
-

{{ 'piggy_events'|_ }}

+ + {% if events[journal.transaction_journal_id]|length > 0 %} +
+
+

{{ 'piggy_events'|_ }}

+
+
+ + {% for event in events[journal.transaction_journal_id] %} + + + + + + {% endfor %} +
{{ event.amount|raw }} + {{ event.piggy }}
+
-
- - {% for event in events[journal.transaction_journal_id] %} - - - + {% endif %} + + {% endfor %} + + + + + {# modal for linking journals. Will be filled by AJAX #} + + - - {% endfor %} -
{{ event.amount|raw }} - {{ event.piggy }}
-
-
- {% endif %} -
- {% endfor %} -
{% endblock %} {% block scripts %} + + + {% endblock %} {# diff --git a/routes/web.php b/routes/web.php index f7677ffe3e..e41269f88c 100644 --- a/routes/web.php +++ b/routes/web.php @@ -540,6 +540,7 @@ Route::group( Route::get('piggy-banks', ['uses' => 'Json\AutoCompleteController@piggyBanks', 'as' => 'autocomplete.piggy-banks']); Route::get('tags', ['uses' => 'Json\AutoCompleteController@tags', 'as' => 'autocomplete.tags']); Route::get('transaction-journals/all', ['uses' => 'Json\AutoCompleteController@allJournals', 'as' => 'autocomplete.all-journals']); + Route::get('transaction-journals/with-id', ['uses' => 'Json\AutoCompleteController@allJournalsWithID', 'as' => 'autocomplete.all-journals-with-id']); Route::get('currency-names', ['uses' => 'Json\AutoCompleteController@currencyNames', 'as' => 'autocomplete.currency-names']); @@ -884,8 +885,8 @@ Route::group( // clone group: Route::get('clone/{transactionGroup}', ['uses' => 'Transaction\CloneController@clone', 'as' => 'clone']); - Route::get('debug/{tj}', ['uses' => 'Transaction\SingleController@debugShow', 'as' => 'debug']); - Route::get('debug/{tj}', ['uses' => 'Transaction\SingleController@debugShow', 'as' => 'debug']); + //Route::get('debug/{tj}', ['uses' => 'Transaction\SingleController@debugShow', 'as' => 'debug']); + //Route::get('debug/{tj}', ['uses' => 'Transaction\SingleController@debugShow', 'as' => 'debug']); Route::post('reorder', ['uses' => 'TransactionController@reorder', 'as' => 'reorder']); Route::post('reconcile', ['uses' => 'TransactionController@reconcile', 'as' => 'reconcile']); @@ -942,16 +943,16 @@ Route::group( /** * Transaction Split Controller */ -Route::group( - ['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions/split', - 'as' => 'transactions.split.'], function () { - // TODO improve these routes - Route::get('edit/{tj}', ['uses' => 'SplitController@edit', 'as' => 'edit']); - Route::post('update/{tj}', ['uses' => 'SplitController@update', 'as' => 'update']); - // TODO end of todo. - -} -); +//Route::group( +// ['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions/split', +// 'as' => 'transactions.split.'], function () { +// // TODO improve these routes +// Route::get('edit/{tj}', ['uses' => 'SplitController@edit', 'as' => 'edit']); +// Route::post('update/{tj}', ['uses' => 'SplitController@update', 'as' => 'update']); +// // TODO end of todo. +// +//} +//); /** * Transaction Convert Controller @@ -970,6 +971,9 @@ Route::group( Route::group( ['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions/link', 'as' => 'transactions.link.'], function () { + + Route::get('modal/{tj}', ['uses' => 'LinkController@modal', 'as' => 'modal']); + // TODO improve this route: Route::post('store/{tj}', ['uses' => 'LinkController@store', 'as' => 'store']); Route::get('delete/{journalLink}', ['uses' => 'LinkController@delete', 'as' => 'delete']); diff --git a/tests/Feature/Controllers/Transaction/LinkControllerTest.php b/tests/Feature/Controllers/Transaction/LinkControllerTest.php index f7029e8d3e..34ca53ac93 100644 --- a/tests/Feature/Controllers/Transaction/LinkControllerTest.php +++ b/tests/Feature/Controllers/Transaction/LinkControllerTest.php @@ -28,8 +28,10 @@ use FireflyIII\Models\TransactionJournalLink; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface; +use Illuminate\Support\Collection; use Log; use Mockery; +use Preferences; use Tests\TestCase; /** @@ -48,23 +50,39 @@ class LinkControllerTest extends TestCase /** - * @covers \FireflyIII\Http\Controllers\Transaction\LinkController * @covers \FireflyIII\Http\Controllers\Transaction\LinkController */ public function testDelete(): void - { $this->markTestIncomplete('Needs to be rewritten for v4.8.0'); + { + $this->mock(LinkTypeRepositoryInterface::class); + $link = $this->getRandomLink(); + $userRepos = $this->mock(UserRepositoryInterface::class); + + $this->mockDefaultSession(); + - return; - $journalRepos = $this->mock(JournalRepositoryInterface::class); - $linkRepos = $this->mock(LinkTypeRepositoryInterface::class); - $userRepos = $this->mock(UserRepositoryInterface::class); $userRepos->shouldReceive('hasRole')->withArgs([Mockery::any(), 'owner'])->atLeast()->once()->andReturn(true); + $this->be($this->user()); + $response = $this->get(route('transactions.link.delete', [$link->id])); + $response->assertStatus(200); + } - $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); + + /** + * @covers \FireflyIII\Http\Controllers\Transaction\LinkController + */ + public function testModal(): void + { + $journal = $this->getRandomWithdrawal(); + $linkRepos = $this->mock(LinkTypeRepositoryInterface::class); + + $this->mockDefaultSession(); + + $linkRepos->shouldReceive('get')->atLeast()->once()->andReturn(new Collection); $this->be($this->user()); - $response = $this->get(route('transactions.link.delete', [1])); + $response = $this->get(route('transactions.link.modal', [$journal->id])); $response->assertStatus(200); } @@ -72,22 +90,18 @@ class LinkControllerTest extends TestCase * @covers \FireflyIII\Http\Controllers\Transaction\LinkController */ public function testDestroy(): void - { $this->markTestIncomplete('Needs to be rewritten for v4.8.0'); - - return; - $journalRepos = $this->mock(JournalRepositoryInterface::class); + { + $link = $this->getRandomLink(); $repository = $this->mock(LinkTypeRepositoryInterface::class); - $userRepos = $this->mock(UserRepositoryInterface::class); + Preferences::shouldReceive('mark')->once(); + $this->mockDefaultSession(); - $repository->shouldReceive('destroyLink'); - $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); - + $repository->shouldReceive('destroyLink')->atLeast()->once(); $this->be($this->user()); - $this->session(['journal_links.delete.uri' => 'http://localhost/']); - $response = $this->post(route('transactions.link.destroy', [1])); + $response = $this->post(route('transactions.link.destroy', [$link->id])); $response->assertStatus(302); $response->assertSessionHas('success'); @@ -98,29 +112,28 @@ class LinkControllerTest extends TestCase * @covers \FireflyIII\Http\Requests\JournalLinkRequest */ public function testStore(): void - { $this->markTestIncomplete('Needs to be rewritten for v4.8.0'); - - return; + { + $withdrawal = $this->getRandomWithdrawal(); + $deposit = $this->getRandomDeposit(); $repository = $this->mock(LinkTypeRepositoryInterface::class); - $journalRepos = $this->mock(JournalRepositoryInterface::class); - $userRepos = $this->mock(UserRepositoryInterface::class); + $journalRepos = $this->mockDefaultSession(); $data = [ - 'link_other' => 8, - 'link_type' => '1_inward', + 'opposing' => $deposit->id, + 'link_type' => '1_inward', ]; - $journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal); - $journalRepos->shouldReceive('findNull')->andReturn(new TransactionJournal); - $repository->shouldReceive('findLink')->andReturn(false); - $repository->shouldReceive('storeLink')->andReturn(new TransactionJournalLink); + //$journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal); + $journalRepos->shouldReceive('findNull')->andReturn($deposit)->atLeast()->once(); + $repository->shouldReceive('findLink')->andReturn(false)->atLeast()->once(); + $repository->shouldReceive('storeLink')->andReturn(new TransactionJournalLink)->atLeast()->once(); $this->be($this->user()); - $response = $this->post(route('transactions.link.store', [1]), $data); + $response = $this->post(route('transactions.link.store', [$withdrawal->id]), $data); $response->assertStatus(302); $response->assertSessionHas('success'); - $response->assertRedirect(route('transactions.show', [1])); + $response->assertRedirect(route('transactions.show', [$withdrawal->id])); } /** @@ -128,28 +141,25 @@ class LinkControllerTest extends TestCase * @covers \FireflyIII\Http\Requests\JournalLinkRequest */ public function testStoreAlreadyLinked(): void - { $this->markTestIncomplete('Needs to be rewritten for v4.8.0'); - - return; + { $repository = $this->mock(LinkTypeRepositoryInterface::class); - $journalRepos = $this->mock(JournalRepositoryInterface::class); - $userRepos = $this->mock(UserRepositoryInterface::class); + $journalRepos = $this->mockDefaultSession(); + $link = $this->getRandomLink(); $data = [ - 'link_other' => 8, - 'link_type' => '1_inward', + 'opposing' => $link->source_id, + 'link_type' => '1_inward', ]; - $journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal); - $journalRepos->shouldReceive('findNull')->andReturn(new TransactionJournal); - $repository->shouldReceive('findLink')->andReturn(true); + $journalRepos->shouldReceive('findNull')->andReturn(new TransactionJournal)->atLeast()->once(); + $repository->shouldReceive('findLink')->andReturn(true)->atLeast()->once(); $this->be($this->user()); - $response = $this->post(route('transactions.link.store', [1]), $data); + $response = $this->post(route('transactions.link.store', [$link->destination_id]), $data); $response->assertStatus(302); $response->assertSessionHas('error'); - $response->assertRedirect(route('transactions.show', [1])); + $response->assertRedirect(route('transactions.show', [$link->destination_id])); } /** @@ -157,26 +167,23 @@ class LinkControllerTest extends TestCase * @covers \FireflyIII\Http\Requests\JournalLinkRequest */ public function testStoreInvalid(): void - { $this->markTestIncomplete('Needs to be rewritten for v4.8.0'); + { + $this->mock(LinkTypeRepositoryInterface::class); + $journalRepos = $this->mockDefaultSession(); + $withdrawal = $this->getRandomWithdrawal(); - return; $data = [ - 'link_other' => 0, - 'link_type' => '1_inward', + 'opposing' => 0, + 'link_type' => '1_inward', ]; - $journalRepos = $this->mock(JournalRepositoryInterface::class); - $userRepos = $this->mock(UserRepositoryInterface::class); - $repository = $this->mock(LinkTypeRepositoryInterface::class); - - $journalRepos->shouldReceive('firstNull')->andReturn(null); - $journalRepos->shouldReceive('findNull')->andReturn(null); + $journalRepos->shouldReceive('findNull')->andReturn(null)->atLeast()->once(); $this->be($this->user()); - $response = $this->post(route('transactions.link.store', [1]), $data); + $response = $this->post(route('transactions.link.store', [$withdrawal->id]), $data); $response->assertStatus(302); $response->assertSessionHas('error'); - $response->assertRedirect(route('transactions.show', [1])); + $response->assertRedirect(route('transactions.show', [$withdrawal->id])); } /** @@ -184,39 +191,33 @@ class LinkControllerTest extends TestCase * @covers \FireflyIII\Http\Requests\JournalLinkRequest */ public function testStoreSame(): void - { $this->markTestIncomplete('Needs to be rewritten for v4.8.0'); - - return; + { + $withdrawal = $this->getRandomWithdrawal(); $repository = $this->mock(LinkTypeRepositoryInterface::class); - $journalRepos = $this->mock(JournalRepositoryInterface::class); - $userRepos = $this->mock(UserRepositoryInterface::class); + $journalRepos = $this->mockDefaultSession(); $data = [ - 'link_other' => 8, + 'link_other' => $withdrawal->id, 'link_type' => '1_inward', ]; - $journal = $this->user()->transactionJournals()->first(); - - $journalRepos->shouldReceive('firstNull')->andReturn($journal); - $journalRepos->shouldReceive('findNull')->andReturn($journal); - $repository->shouldReceive('findLink')->andReturn(false); - $repository->shouldReceive('storeLink')->andReturn(new TransactionJournalLink); + $journalRepos->shouldReceive('findNull')->andReturn($withdrawal)->atLeast()->once(); + $repository->shouldReceive('findLink')->andReturn(false)->atLeast()->once(); $this->be($this->user()); - $response = $this->post(route('transactions.link.store', [$journal->id]), $data); + $response = $this->post(route('transactions.link.store', [$withdrawal->id]), $data); $response->assertStatus(302); $response->assertSessionHas('error'); - $response->assertRedirect(route('transactions.show', [1])); + $response->assertRedirect(route('transactions.show', [$withdrawal->id])); } /** * @covers \FireflyIII\Http\Controllers\Transaction\LinkController */ public function testSwitchLink(): void - { $this->markTestIncomplete('Needs to be rewritten for v4.8.0'); - - return; + { + $link = $this->getRandomLink(); + $withdrawal = $this->getRandomWithdrawal(); $journalRepos = $this->mock(JournalRepositoryInterface::class); $repository = $this->mock(LinkTypeRepositoryInterface::class); $userRepos = $this->mock(UserRepositoryInterface::class); @@ -224,7 +225,7 @@ class LinkControllerTest extends TestCase $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); $repository->shouldReceive('switchLink')->andReturn(false); $this->be($this->user()); - $response = $this->get(route('transactions.link.switch', [1])); + $response = $this->get(route('transactions.link.switch', [$link->id])); $response->assertStatus(302); diff --git a/tests/TestCase.php b/tests/TestCase.php index bf28445822..9ff41c80db 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -40,6 +40,7 @@ use FireflyIII\Models\Preference; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionJournalLink; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Transformers\TransactionTransformer; @@ -58,6 +59,14 @@ use RuntimeException; abstract class TestCase extends BaseTestCase { + /** + * @return TransactionJournalLink + */ + public function getRandomLink(): TransactionJournalLink + { + return TransactionJournalLink::inRandomOrder()->first(); + } + /** * @return Budget */