Implemented new link thing.

This commit is contained in:
James Cole
2019-07-20 06:47:34 +02:00
parent 63832b31f8
commit 6d34cfb940
15 changed files with 581 additions and 390 deletions

View File

@@ -149,7 +149,42 @@ class AutoCompleteController extends Controller
return response()->json($array); 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);
} }
/** /**

View File

@@ -33,7 +33,8 @@ class EditController extends Controller
{ {
/** /**
* SingleController constructor. * EditController constructor.
* @codeCoverageIgnore
*/ */
public function __construct() public function __construct()
{ {
@@ -48,7 +49,7 @@ class EditController extends Controller
// some useful repositories: // some useful repositories:
$this->middleware( $this->middleware(
function ($request, $next) { static function ($request, $next) {
app('view')->share('title', (string)trans('firefly.transactions')); app('view')->share('title', (string)trans('firefly.transactions'));
app('view')->share('mainTitleIcon', 'fa-repeat'); app('view')->share('mainTitleIcon', 'fa-repeat');

View File

@@ -28,6 +28,8 @@ use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionJournalLink; use FireflyIII\Models\TransactionJournalLink;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface;
use Illuminate\Contracts\View\Factory;
use Illuminate\View\View;
use Log; use Log;
use URL; use URL;
@@ -43,6 +45,7 @@ class LinkController extends Controller
/** /**
* LinkController constructor. * LinkController constructor.
* @codeCoverageIgnore
*/ */
public function __construct() 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. * Delete a link.
* *
* @param TransactionJournalLink $link * @param TransactionJournalLink $link
* *
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View * @return Factory|View
*/ */
public function delete(TransactionJournalLink $link) public function delete(TransactionJournalLink $link)
{ {
@@ -104,9 +118,9 @@ class LinkController extends Controller
*/ */
public function store(JournalLinkRequest $request, TransactionJournal $journal) public function store(JournalLinkRequest $request, TransactionJournal $journal)
{ {
$linkInfo = $request->getLinkInfo();
Log::debug('We are here (store)'); Log::debug('We are here (store)');
$linkInfo = $request->getLinkInfo();
$other = $this->journalRepository->findNull($linkInfo['transaction_journal_id']); $other = $this->journalRepository->findNull($linkInfo['transaction_journal_id']);
if (null === $other) { if (null === $other) {
session()->flash('error', (string)trans('firefly.invalid_link_selection')); session()->flash('error', (string)trans('firefly.invalid_link_selection'));

View File

@@ -73,7 +73,7 @@ class ShowController extends Controller
$type = (string)trans(sprintf('firefly.%s', strtolower($first->transactionType->type))); $type = (string)trans(sprintf('firefly.%s', strtolower($first->transactionType->type)));
$title = 1 === $splits ? $first->description : $transactionGroup->title; $title = 1 === $splits ? $first->description : $transactionGroup->title;
$subTitle = sprintf('%s: "%s"', $type, $title); $subTitle = sprintf('%s: "%s"', $type, $title);
$message = $request->get('message'); $message = $request->get('message');
/** @var TransactionGroupTransformer $transformer */ /** @var TransactionGroupTransformer $transformer */
$transformer = app(TransactionGroupTransformer::class); $transformer = app(TransactionGroupTransformer::class);
@@ -112,9 +112,9 @@ class ShowController extends Controller
return view( return view(
'transactions.show', compact( 'transactions.show', compact(
'transactionGroup', 'amounts', 'first', 'type', 'subTitle', 'splits', 'groupArray', 'transactionGroup', 'amounts', 'first', 'type', 'subTitle', 'splits', 'groupArray',
'events', 'attachments', 'links','message' 'events', 'attachments', 'links', 'message'
) )
); );
} }
} }

View File

@@ -52,12 +52,9 @@ class JournalLinkRequest extends Request
$linkType = $this->get('link_type'); $linkType = $this->get('link_type');
$parts = explode('_', $linkType); $parts = explode('_', $linkType);
$return['link_type_id'] = (int)$parts[0]; $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['notes'] = $this->string('notes');
$return['direction'] = $parts[1]; $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; return $return;
} }
@@ -81,8 +78,8 @@ class JournalLinkRequest extends Request
// fixed // fixed
return [ return [
'link_type' => sprintf('required|in:%s', $string), 'link_type' => sprintf('required|in:%s', $string),
'link_journal_id' => 'belongsToUser:transaction_journals', 'opposing' => 'belongsToUser:transaction_journals',
]; ];
} }
} }

View File

@@ -174,14 +174,12 @@ class TransactionJournal extends Model
*/ */
public static function routeBinder(string $value): TransactionJournal public static function routeBinder(string $value): TransactionJournal
{ {
throw new FireflyException('Journal binder is permanently out of order.');
if (auth()->check()) { if (auth()->check()) {
$journalId = (int)$value; $journalId = (int)$value;
/** @var User $user */ /** @var User $user */
$user = auth()->user(); $user = auth()->user();
/** @var TransactionJournal $journal */ /** @var TransactionJournal $journal */
$journal = $user->transactionJournals()->where('transaction_journals.id', $journalId) $journal = $user->transactionJournals()->where('transaction_journals.id', $journalId)->first(['transaction_journals.*']);
->first(['transaction_journals.*']);
if (null !== $journal) { if (null !== $journal) {
return $journal; return $journal;
} }

View File

@@ -119,6 +119,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
$amount = $this->getFormattedAmount($entry->destination); $amount = $this->getFormattedAmount($entry->destination);
$foreignAmount = $this->getFormattedForeignAmount($entry->destination); $foreignAmount = $this->getFormattedForeignAmount($entry->destination);
$return[$journalId][] = [ $return[$journalId][] = [
'id' => $entry->id,
'link' => $entry->outward, 'link' => $entry->outward,
'group' => $entry->destination->transaction_group_id, 'group' => $entry->destination->transaction_group_id,
'description' => $entry->destination->description, 'description' => $entry->destination->description,
@@ -130,6 +131,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
$amount = $this->getFormattedAmount($entry->source); $amount = $this->getFormattedAmount($entry->source);
$foreignAmount = $this->getFormattedForeignAmount($entry->source); $foreignAmount = $this->getFormattedForeignAmount($entry->source);
$return[$journalId][] = [ $return[$journalId][] = [
'id' => $entry->id,
'link' => $entry->inward, 'link' => $entry->inward,
'group' => $entry->source->transaction_group_id, 'group' => $entry->source->transaction_group_id,
'description' => $entry->source->description, 'description' => $entry->source->description,
@@ -145,7 +147,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
/** /**
* Return object with all found meta field things as Carbon objects. * Return object with all found meta field things as Carbon objects.
* *
* @param int $journalId * @param int $journalId
* @param array $fields * @param array $fields
* *
* @return NullArrayObject * @return NullArrayObject
@@ -171,7 +173,7 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
/** /**
* Return object with all found meta field things. * Return object with all found meta field things.
* *
* @param int $journalId * @param int $journalId
* @param array $fields * @param array $fields
* *
* @return NullArrayObject * @return NullArrayObject
@@ -247,9 +249,9 @@ class TransactionGroupRepository implements TransactionGroupRepositoryInterface
$return[$journalId] = $return[$journalId] ?? []; $return[$journalId] = $return[$journalId] ?? [];
$return[$journalId][] = [ $return[$journalId][] = [
'piggy' => $row->piggyBank->name, 'piggy' => $row->piggyBank->name,
'piggy_id' => $row->piggy_bank_id, '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 TransactionGroup $transactionGroup
* @param array $data * @param array $data
* *
* @return TransactionGroup * @return TransactionGroup
* *

View File

@@ -214,10 +214,6 @@ return [
'application/vnd.oasis.opendocument.image', 'application/vnd.oasis.opendocument.image',
], ],
'list_length' => 10, 'list_length' => 10,
'export_formats' => [
'csv' => CsvExporter::class,
],
'default_export_format' => 'csv',
'default_import_format' => 'csv', 'default_import_format' => 'csv',
'bill_periods' => ['weekly', 'monthly', 'quarterly', 'half-year', 'yearly'], 'bill_periods' => ['weekly', 'monthly', 'quarterly', 'half-year', 'yearly'],
'accountRoles' => ['defaultAsset', 'sharedAsset', 'savingAsset', 'ccAsset', 'cashWalletAsset'], 'accountRoles' => ['defaultAsset', 'sharedAsset', 'savingAsset', 'ccAsset', 'cashWalletAsset'],
@@ -381,7 +377,6 @@ return [
'recurrence' => Recurrence::class, 'recurrence' => Recurrence::class,
'rule' => Rule::class, 'rule' => Rule::class,
'ruleGroup' => RuleGroup::class, 'ruleGroup' => RuleGroup::class,
'exportJob' => ExportJob::class,
'importJob' => ImportJob::class, 'importJob' => ImportJob::class,
'transaction' => Transaction::class, 'transaction' => Transaction::class,
'transactionGroup' => TransactionGroup::class, 'transactionGroup' => TransactionGroup::class,

View File

@@ -22,5 +22,60 @@
$(function () { $(function () {
"use strict"; "use strict";
$('.link-modal').click(getLinkModal);
$('#linkJournalModal').on('shown.bs.modal', function () {
makeAutoComplete();
})
});
}); 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('<a href="' + groupURI.replace('%GROUP%', journal.transaction_group_id) + '">' + journal.description + '</a>').show();
$('input[name="opposing"]').val(journal.id);
}

View File

@@ -1215,12 +1215,13 @@ return [
'do_not_save_connection' => '(do not save connection)', 'do_not_save_connection' => '(do not save connection)',
'link_transaction' => 'Link transaction', 'link_transaction' => 'Link transaction',
'link_to_other_transaction' => 'Link this transaction to another 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', 'this_transaction' => 'This transaction',
'transaction' => 'Transaction', 'transaction' => 'Transaction',
'comments' => 'Comments', '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', 'invalid_link_selection' => 'Cannot link these transactions',
'selected_transaction' => 'Selected transaction',
'journals_linked' => 'Transactions are linked.', 'journals_linked' => 'Transactions are linked.',
'journals_error_linked' => 'These transactions are already linked.', 'journals_error_linked' => 'These transactions are already linked.',
'journals_link_to_self' => 'You cannot link a transaction to itself', 'journals_link_to_self' => 'You cannot link a transaction to itself',

View File

@@ -0,0 +1,52 @@
<form action="{{ route('transactions.link.store', [journal.id]) }}" method="post" class="form-horizontal">
<input type="hidden" name="_token" value="{{ csrf_token() }}">
<input type="hidden" name="opposing" value="">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{{ 'link_to_other_transaction'|_ }}</h4>
</div>
<div class="modal-body" id="helpBody">
<p>{{ 'select_transaction_to_link'|_ }}</p>
<div class="form-group">
<label for="link_type" class="col-sm-4 control-label">{{ 'this_transaction'|_ }}</label>
<div class="col-sm-8">
<select id="link_type" class="form-control" name="link_type">
{% for linkType in linkTypes %}
<option label="{{ journalLinkTranslation('inward', linkType.inward) }}"
value="{{ linkType.id }}_inward">{{ journalLinkTranslation('inward', linkType.inward) }}</option>
<option label="{{ journalLinkTranslation('outward', linkType.outward) }}"
value="{{ linkType.id }}_outward">{{ journalLinkTranslation('outward', linkType.outward) }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group" id="journal-selection">
<label for="selected" class="col-sm-4 control-label">{{ 'selected_transaction'|_ }}</label>
<div class="col-sm-8">
<p class="form-control-static" id="selected-journal">
</p>
</div>
</div>
<div class="form-group" id="journal-selector">
<label for="link_other" class="col-sm-4 control-label">{{ 'transaction'|_ }}</label>
<div class="col-sm-8">
<input type="text" name="link_other" autocomplete="off" id="link_other" value="" class="form-control link-journal">
</div>
</div>
<div class="form-group">
<label for="notes" class="col-sm-4 control-label">{{ 'link_notes'|_ }}</label>
<div class="col-sm-8">
<textarea id="notes" name="notes" class="form-control"></textarea>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">{{ 'close'|_ }}</button>
<button type="submit" class="btn btn-primary">{{ 'submit'|_ }}</button>
</div>
</div>
</div>
</form>

View File

@@ -6,18 +6,18 @@
{% block content %} {% block content %}
{% if message == 'created' %} {% if message == 'created' %}
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
<div class="alert alert-success alert-dismissible" role="alert"> <div class="alert alert-success alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert"> <button type="button" class="close" data-dismiss="alert">
<span>&times;</span><span class="sr-only">{{ 'close'|_ }}</span> <span>&times;</span><span class="sr-only">{{ 'close'|_ }}</span>
</button> </button>
<strong>{{ 'flash_success'|_ }}</strong> <strong>{{ 'flash_success'|_ }}</strong>
{{ trans('firefly.stored_journal_no_descr') }} {{ trans('firefly.stored_journal_no_descr') }}
</div>
</div> </div>
</div> </div>
</div>
{% endif %} {% endif %}
{% if message == 'updated' %} {% if message == 'updated' %}
@@ -69,335 +69,362 @@
</div> </div>
<div class="box-footer"> <div class="box-footer">
<div class="btn-group btn-group-xs"> <div class="btn-group btn-group-xs">
<a href="{{ route('transactions.edit', [transactionGroup.id]) }}" class="btn btn-default"><i class="fa fa-pencil"></i> {{ 'edit'|_ }}</a> <a href="{{ route('transactions.edit', [transactionGroup.id]) }}" class="btn btn-default"><i class="fa fa-pencil"></i> {{ 'edit'|_ }}
</a>
{% if groupArray.transactions[0].type != 'withdrawal' %} {% if groupArray.transactions[0].type != 'withdrawal' %}
<a href="{{ route('transactions.convert.index', ['withdrawal', transactionGroup.id]) }}" class="btn btn-default"><i class="fa fa-exchange"></i> {{ 'convert_to_withdrawal'|_ }}</a> <a href="{{ route('transactions.convert.index', ['withdrawal', transactionGroup.id]) }}" class="btn btn-default"><i
class="fa fa-exchange"></i> {{ 'convert_to_withdrawal'|_ }}</a>
{% endif %} {% endif %}
{% if groupArray.transactions[0].type != 'deposit' %} {% if groupArray.transactions[0].type != 'deposit' %}
<a href="{{ route('transactions.convert.index', ['deposit', transactionGroup.id]) }}" class="btn btn-default"><i class="fa fa-exchange"></i> {{ 'convert_to_deposit'|_ }}</a> <a href="{{ route('transactions.convert.index', ['deposit', transactionGroup.id]) }}" class="btn btn-default"><i
class="fa fa-exchange"></i> {{ 'convert_to_deposit'|_ }}</a>
{% endif %} {% endif %}
{% if groupArray.transactions[0].type != 'transfer' %} {% if groupArray.transactions[0].type != 'transfer' %}
<a href="{{ route('transactions.convert.index', ['transfer', transactionGroup.id]) }}" class="btn btn-default"><i class="fa fa-exchange"></i> {{ 'convert_to_transfer'|_ }}</a> <a href="{{ route('transactions.convert.index', ['transfer', transactionGroup.id]) }}" class="btn btn-default"><i
class="fa fa-exchange"></i> {{ 'convert_to_transfer'|_ }}</a>
{% endif %} {% endif %}
{% if groupArray.transactions[0].type != 'opening balance' and groupArray.transactions[0].type != 'reconciliation' %} {% if groupArray.transactions[0].type != 'opening balance' and groupArray.transactions[0].type != 'reconciliation' %}
CLONE CLONE
{#<a href="{{ route('transactions.clone', [transactionGroup.id]) }}" class="btn btn-default"><i class="fa fa-copy"></i> {{ 'clone'|_ }}</a>#} {#<a href="{{ route('transactions.clone', [transactionGroup.id]) }}" class="btn btn-default"><i class="fa fa-copy"></i> {{ 'clone'|_ }}</a>#}
{% endif %} {% endif %}
{# {#
<a href="{{ route('transactions.delete', [transactionGroup.id]) }}" class="btn btn-danger"><i class="fa fa-trash"></i> {{ 'delete'|_ }}</a> <a href="{{ route('transactions.delete', [transactionGroup.id]) }}" class="btn btn-danger"><i class="fa fa-trash"></i> {{ 'delete'|_ }}</a>
#} #}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> <div class="col-lg-6">
<div class="col-lg-6"> <div class="box box-primary">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">{{ 'transaction_journal_meta'|_ }}</h3>
</div>
<div class="box-body no-padding">
<table class="table table-hover">
<tbody>
{% if type != 'Withdrawal' or splits == 1 %}
<tr>
<td>
{{ 'source_accounts'|_ }}
</td>
<td>
{% for journal in groupArray.transactions %}
<a href="{{ route('accounts.show',journal.source_id) }}"
title="{{ journal.source_iban|default(journal.source_name) }}">
{{ journal.source_name }}
</a>
{% if loop.index0 != groupArray.transactions|length -1 %}, {% endif %}
{% endfor %}
</td>
</tr>
{% endif %}
{% if type != 'Deposit' or splits == 1 %}
<tr>
<td>
{{ 'destination_accounts'|_ }}
</td>
<td>
{% for journal in groupArray.transactions %}
<a href="{{ route('accounts.show',journal.source_id) }}"
title="{{ journal.destination_iban|default(journal.destination_name) }}">
{{ journal.destination_name }}
</a>
{% if loop.index0 != groupArray.transactions|length -1 %}, {% endif %}
{% endfor %}
</td>
</tr>
{% endif %}
<tr>
<td style="width:30%;">{{ 'total_amount'|_ }}</td>
<td>
{% for amount in amounts %}
{% if type == 'Withdrawal' or type == 'Deposit' %}
{{ formatAmountBySymbol(amount.amount*-1,amount.symbol, amount.decimal_places) }},
{% elseif type == 'Transfer' %}
<span class="text-info">
{{ formatAmountBySymbol(amount.amount, amount.symbol, amount.decimal_places, false) }},
</span>
{% endif %}
{% endfor %}
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="row">
</div>
{% if splits > 1 %}
<div class="row">
<div class="col-lg-12">
<h3>{{ 'splits'|_ }}</h3>
</div>
</div>
{% endif %}
{% set boxSize=12 %}
{% if(splits == 2) %}
{% set boxSize=6 %}
{% endif %}
{% if (splits > 2) %}
{% set boxSize = 4 %}
{% endif %}
<div class="row">
{% for index,journal in groupArray.transactions %}
<div class="col-lg-{{ boxSize }}">
<div class="box">
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title"> <h3 class="box-title">{{ 'transaction_journal_meta'|_ }}</h3>
{{ journal.description }}
{% if journal.reconciled %}
<i class="fa fa-check"></i>
{% endif %}
{% if splits > 1 %}
<small>
{{ index+1 }} / {{ splits }}
</small>
{% endif %}
</h3>
</div> </div>
<div class="box-body no-padding"> <div class="box-body no-padding">
<table class="table"> <table class="table table-hover">
<tr> <tbody>
<td colspan="2"> {% if type != 'Withdrawal' or splits == 1 %}
<a href="{{ route('accounts.show', journal.source_id) }}"
title="{{ journal.source_iban|default(journal.source_name) }}">{{ journal.source_name }}</a> &rarr;
{% if type == 'Withdrawal' or type == 'Deposit' %}
{{ formatAmountBySymbol(journal.amount*-1, journal.currency_symbol, journal.currency_decimal_places) }}
{% elseif type == 'Transfer' %}
<span class="text-info">
{{ formatAmountBySymbol(journal.amount, journal.currency_symbol, journal.currency_decimal_places, false) }}
</span>
{% endif %}
<!-- do foreign amount -->
{% 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' %}
<span class="text-info">
({{ formatAmountBySymbol(journal.foreign_amount, journal.foreign_currency_symbol, journal.foreign_currency_decimal_places, false) }})
</span>
{% endif %}
{% endif %}
&rarr;
<a href="{{ route('accounts.show', journal.destination_id) }}"
title="{{ journal.destination_iban|default(journal.destination_name) }}">{{ journal.destination_name }}</a>
</td>
</tr>
{% if null != journal.category_id %}
<tr> <tr>
<td style="width:30%;">{{ 'category'|_ }}</td>
<td><a href="{{ route('categories.show', [journal.category_id]) }}">{{ journal.category_name }}</a></td>
</tr>
{% endif %}
{% if null != journal.budget_id and type == 'Withdrawal' %}
<tr>
<td>{{ 'budget'|_ }}</td>
<td><a href="{{ route('budgets.show', [journal.budget_id]) }}">{{ journal.budget_name }}</a></td>
</tr>
{% endif %}
{% if null != journal.bill_id and type == 'Withdrawal' %}
<tr>
<td>{{ 'bill'|_ }}</td>
<td><a href="{{ route('bills.show', [journal.bill_id]) }}">{{ journal.bill_name }}</a></td>
</tr>
{% endif %}
<!-- other fields -->
{% for dateField in ['interest_date','book_date','process_date','due_date','payment_date','invoice_date'] %}
{% if journalHasMeta(journal.transaction_journal_id, dateField) %}
<tr>
<td>{{ trans('list.'~dateField) }}</td>
<td>{{ journalGetMetaDate(journal.transaction_journal_id, dateField).formatLocalized(monthAndDayFormat) }}</td>
</tr>
{% 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) %}
<tr>
<td>{{ trans('list.'~metaField) }}</td>
<td>{{ journalGetMetaField(journal.transaction_journal_id, metaField) }}</td>
</tr>
{% endif %}
{% endfor %}
{% if null != journal.notes and '' != journal.notes %}
<tr>
<td>{{ trans('list.notes') }}</td>
<td class="markdown">{{ journal.notes|markdown }}</td>
</tr>
{% endif %}
{% if journal.tags|length > 0 %}
<tr>
<td>{{ 'tags'|_ }}</td>
<td> <td>
{% for tag in journal.tags %} {{ 'source_accounts'|_ }}
<h4 style="display: inline;"><a class="label label-success" href="{{ route('tags.show', tag) }}"> </td>
<i class="fa fa-fw fa-tag"></i> <td>
{{ tag }}</a> {% for journal in groupArray.transactions %}
</h4> <a href="{{ route('accounts.show',journal.source_id) }}"
title="{{ journal.source_iban|default(journal.source_name) }}">
{{ journal.source_name }}
</a>
{% if loop.index0 != groupArray.transactions|length -1 %}, {% endif %}
{% endfor %} {% endfor %}
</td> </td>
</tr> </tr>
{% endif %} {% endif %}
{% if type != 'Deposit' or splits == 1 %}
<tr>
<td>
{{ 'destination_accounts'|_ }}
</td>
<td>
{% for journal in groupArray.transactions %}
<a href="{{ route('accounts.show',journal.source_id) }}"
title="{{ journal.destination_iban|default(journal.destination_name) }}">
{{ journal.destination_name }}
</a>
{% if loop.index0 != groupArray.transactions|length -1 %}, {% endif %}
{% endfor %}
</td>
</tr>
{% endif %}
<tr>
<td style="width:30%;">{{ 'total_amount'|_ }}</td>
<td>
{% for amount in amounts %}
{% if type == 'Withdrawal' or type == 'Deposit' %}
{{ formatAmountBySymbol(amount.amount*-1,amount.symbol, amount.decimal_places) }},
{% elseif type == 'Transfer' %}
<span class="text-info">
{{ formatAmountBySymbol(amount.amount, amount.symbol, amount.decimal_places, false) }},
</span>
{% endif %}
{% endfor %}
</td>
</tr>
</tbody>
</table> </table>
</div> </div>
</div> </div>
</div>
</div>
<div class="row">
<!-- Transaction links --> </div>
{% if links[journal.transaction_journal_id]|length > 0 %} {% if splits > 1 %}
<div class="row">
<div class="col-lg-12">
<h3>{{ 'splits'|_ }}</h3>
</div>
</div>
{% endif %}
{% set boxSize=6 %}
{% if(splits == 2) %}
{% set boxSize=6 %}
{% endif %}
{% if (splits > 2) %}
{% set boxSize = 4 %}
{% endif %}
<div class="row">
{% for index,journal in groupArray.transactions %}
<div class="col-lg-{{ boxSize }}">
<div class="box"> <div class="box">
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title"> <h3 class="box-title">
{{ 'journal_links'|_ }} {{ journal.description }}
{% if journal.reconciled %}
<i class="fa fa-check"></i>
{% endif %}
{% if splits > 1 %}
<small>
{{ index+1 }} / {{ splits }}
</small>
{% endif %}
</h3> </h3>
</div> </div>
<div class="box-body no-padding"> <div class="box-body no-padding">
<table class="table"> <table class="table">
{% for link in links[journal.transaction_journal_id] %} <tr>
<tr> <td colspan="2">
<td style="width:30%;"> <a href="{{ route('accounts.show', journal.source_id) }}"
<div class="btn-group btn-group-xs"> title="{{ journal.source_iban|default(journal.source_name) }}">{{ journal.source_name }}</a> &rarr;
<a href="#" class="btn btn-default"><i class="fa fa-pencil"></i></a> {% if type == 'Withdrawal' or type == 'Deposit' %}
<a href="#" class="btn btn-danger"><i class="fa fa-trash"></i></a> {{ formatAmountBySymbol(journal.amount*-1, journal.currency_symbol, journal.currency_decimal_places) }}
</div> {% elseif type == 'Transfer' %}
</td> <span class="text-info">
<td>{{ link.link }} "<a href="{{ route('transactions.show', link.group) }}" {{ formatAmountBySymbol(journal.amount, journal.currency_symbol, journal.currency_decimal_places, false) }}
title="{{ link.description }}">{{ link.description }}</a>" </span>
{% endif %}
({{ link.amount|raw }}) <!-- do foreign amount -->
{% if '' != link.foreign_amount %} {% if null != journal.foreign_amount %}
({{ link.foreign_amount|raw }}) {% if type == 'Withdrawal' or type == 'Deposit' %}
({{ formatAmountBySymbol(journal.foreign_amount*-1, journal.foreign_currency_symbol, journal.foreign_currency_decimal_places) }})
{% elseif type == 'Transfer' %}
<span class="text-info">
({{ formatAmountBySymbol(journal.foreign_amount, journal.foreign_currency_symbol, journal.foreign_currency_decimal_places, false) }})
</span>
{% endif %} {% endif %}
{% endif %}
&rarr;
<a href="{{ route('accounts.show', journal.destination_id) }}"
title="{{ journal.destination_iban|default(journal.destination_name) }}">{{ journal.destination_name }}</a>
</td>
</tr>
{% if null != journal.category_id %}
<tr>
<td style="width:30%;">{{ 'category'|_ }}</td>
<td><a href="{{ route('categories.show', [journal.category_id]) }}">{{ journal.category_name }}</a></td>
</tr>
{% endif %}
{% if null != journal.budget_id and type == 'Withdrawal' %}
<tr>
<td>{{ 'budget'|_ }}</td>
<td><a href="{{ route('budgets.show', [journal.budget_id]) }}">{{ journal.budget_name }}</a></td>
</tr>
{% endif %}
{% if null != journal.bill_id and type == 'Withdrawal' %}
<tr>
<td>{{ 'bill'|_ }}</td>
<td><a href="{{ route('bills.show', [journal.bill_id]) }}">{{ journal.bill_name }}</a></td>
</tr>
{% endif %}
<!-- other fields -->
{% for dateField in ['interest_date','book_date','process_date','due_date','payment_date','invoice_date'] %}
{% if journalHasMeta(journal.transaction_journal_id, dateField) %}
<tr>
<td>{{ trans('list.'~dateField) }}</td>
<td>{{ journalGetMetaDate(journal.transaction_journal_id, dateField).formatLocalized(monthAndDayFormat) }}</td>
</tr>
{% 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) %}
<tr>
<td>{{ trans('list.'~metaField) }}</td>
<td>{{ journalGetMetaField(journal.transaction_journal_id, metaField) }}</td>
</tr>
{% endif %}
{% endfor %}
{% if null != journal.notes and '' != journal.notes %}
<tr>
<td>{{ trans('list.notes') }}</td>
<td class="markdown">{{ journal.notes|markdown }}</td>
</tr>
{% endif %}
{% if journal.tags|length > 0 %}
<tr>
<td>{{ 'tags'|_ }}</td>
<td>
{% for tag in journal.tags %}
<h4 style="display: inline;"><a class="label label-success" href="{{ route('tags.show', tag) }}">
<i class="fa fa-fw fa-tag"></i>
{{ tag }}</a>
</h4>
{% endfor %}
</td> </td>
</tr> </tr>
{% endfor %} {% endif %}
</table> </table>
</div> </div>
</div> <div class="box-footer">
{% endif %} <div class="btn-group btn-group-xs">
<a href="#" class="btn btn-default link-modal" data-journal="{{ journal.transaction_journal_id }}">
<!-- Attachments --> <i class="fa fa-fw fa-link"></i>
{% if attachments[journal.transaction_journal_id]|length > 0 %} {{ 'link_transaction'|_ }}
<div class="box"> </a>
<div class="box-header with-border"> </div>
<h3 class="box-title">{{ 'attachments'|_ }}</h3>
</div> </div>
<div class="box-body table-responsive no-padding"> </div>
<table class="table table-hover">
{% for attachment in attachments[journal.transaction_journal_id] %} <!-- Transaction links -->
<tr> {% if links[journal.transaction_journal_id]|length > 0 %}
<td style="width:30%;"> <div class="box">
<div class="btn-group btn-group-xs"> <div class="box-header with-border">
<a href="{{ route('attachments.edit', attachment.id) }}" class="btn btn-default"><i <h3 class="box-title">
class="fa fa-pencil"></i></a> {{ 'journal_links'|_ }}
<a href="{{ route('attachments.delete', attachment.id) }}" class="btn btn-danger"><i </h3>
class="fa fa-trash"></i></a> </div>
<div class="box-body no-padding">
<table class="table">
{% for link in links[journal.transaction_journal_id] %}
<tr>
<td style="width:120px;">
<div class="btn-group btn-group-xs">
<a href="{{ route('transactions.link.switch', [link.id]) }}" class="btn btn-default"><i class="fa fa-fw fa-arrows-h"></i></a>
<a href="{{ route('transactions.link.delete', [link.id]) }}" class="btn btn-danger"><i class="fa fa-trash"></i></a>
</div>
</td>
<td>{{ link.link }} "<a href="{{ route('transactions.show', link.group) }}"
title="{{ link.description }}">{{ link.description }}</a>"
({{ link.amount|raw }})
{% if '' != link.foreign_amount %}
({{ link.foreign_amount|raw }})
{% endif %}
</td>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endif %}
<!-- Attachments -->
{% if attachments[journal.transaction_journal_id]|length > 0 %}
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'attachments'|_ }}</h3>
</div>
<div class="box-body table-responsive no-padding">
<table class="table table-hover">
{% for attachment in attachments[journal.transaction_journal_id] %}
<tr>
<td style="width:120px;">
<div class="btn-group btn-group-xs">
<a href="{{ route('attachments.edit', attachment.id) }}" class="btn btn-default"><i
class="fa fa-pencil"></i></a>
<a href="{{ route('attachments.delete', attachment.id) }}" class="btn btn-danger"><i
class="fa fa-trash"></i></a>
{% if attachment.file_exists %}
<a href="{{ route('attachments.download', attachment.id) }}" class="btn btn-default"><i
class="fa fa-download"></i></a>
{% endif %}
{% if not attachment.file_exists %}
<a href="#" class="btn btn-danger"><i class="fa fa-exclamation-triangle"></i></a>
{% endif %}
</div>
</td>
<td>
{% if attachment.file_exists %} {% if attachment.file_exists %}
<a href="{{ route('attachments.download', attachment.id) }}" class="btn btn-default"><i <i class="fa {{ attachment.mime|mimeIcon }}"></i>
class="fa fa-download"></i></a> <a href="{{ route('attachments.view', attachment.id) }}" title="{{ attachment.filename }}">
{% if attachment.title %}
{{ attachment.title }}
{% else %}
{{ attachment.filename }}
{% endif %}
</a>
({{ attachment.size|filesize }})
{% if null != attachment.notes and '' != attachment.notes %}
{{ attachment.notes|markdown }}
{% endif %}
{% endif %} {% endif %}
{% if not attachment.file_exists %} {% if not attachment.file_exists %}
<a href="#" class="btn btn-danger"><i class="fa fa-exclamation-triangle"></i></a> <i class="fa fa-fw fa-exclamation-triangle"></i>
{% endif %}
</div>
</td>
<td>
{% if attachment.file_exists %}
<i class="fa {{ attachment.mime|mimeIcon }}"></i>
<a href="{{ route('attachments.view', attachment.id) }}" title="{{ attachment.filename }}">
{% if attachment.title %} {% if attachment.title %}
{{ attachment.title }} {{ attachment.title }}
{% else %} {% else %}
{{ attachment.filename }} {{ attachment.filename }}
{% endif %} {% endif %}
</a> <br>
({{ attachment.size|filesize }}) <span class="text-danger">{{ 'attachment_not_found'|_ }}</span>
{% if null != attachment.notes and '' != attachment.notes %}
{{ attachment.notes|markdown }}
{% endif %} {% endif %}
{% endif %} </td>
{% if not attachment.file_exists %} </tr>
<i class="fa fa-fw fa-exclamation-triangle"></i> {% endfor %}
{% if attachment.title %} </table>
{{ attachment.title }} </div>
{% else %}
{{ attachment.filename }}
{% endif %}
<br>
<span class="text-danger">{{ 'attachment_not_found'|_ }}</span>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
</div> </div>
</div> {% endif %}
{% endif %}
<!-- Piggy bank events --> <!-- Piggy bank events -->
{% if events[journal.transaction_journal_id]|length > 0 %} {% if events[journal.transaction_journal_id]|length > 0 %}
<div class="box"> <div class="box">
<div class="box-header with-border"> <div class="box-header with-border">
<h3 class="box-title">{{ 'piggy_events'|_ }}</h3> <h3 class="box-title">{{ 'piggy_events'|_ }}</h3>
</div>
<div class="box-body table-responsive no-padding">
<table class="table table-hover">
{% for event in events[journal.transaction_journal_id] %}
<tr>
<td style="width:30%;">{{ event.amount|raw }}</td>
<td>
<a href="{{ route('piggy-banks.show', [event.piggy_id]) }}">{{ event.piggy }}</a></td>
</tr>
{% endfor %}
</table>
</div>
</div> </div>
<div class="box-body table-responsive no-padding"> {% endif %}
<table class="table table-hover"> </div>
{% for event in events[journal.transaction_journal_id] %} {% endfor %}
<tr> </div>
<td style="width:30%;">{{ event.amount|raw }}</td>
<td>
<a href="{{ route('piggy-banks.show', [event.piggy_id]) }}">{{ event.piggy }}</a></td>
{# modal for linking journals. Will be filled by AJAX #}
<div class="modal fade" tabindex="-1" role="dialog" id="linkJournalModal">
</div>
</tr>
{% endfor %}
</table>
</div>
</div>
{% endif %}
</div>
{% endfor %}
</div>
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script type="text/javascript">
var modalDialogURI = '{{ route('transactions.link.modal', ['%JOURNAL%']) }}';
var acURI = '{{ route('json.autocomplete.all-journals-with-id') }}';
var groupURI = '{{ route('transactions.show',['%GROUP']) }}';
</script>
<script type="text/javascript" src="v1/js/lib/typeahead/typeahead.bundle.min.js?v={{ FF_VERSION }}"></script>
<script type="text/javascript" src="v1/js/ff/transactions/show.js?v={{ FF_VERSION }}"></script>
{% endblock %} {% endblock %}
{# {#

View File

@@ -540,6 +540,7 @@ Route::group(
Route::get('piggy-banks', ['uses' => 'Json\AutoCompleteController@piggyBanks', 'as' => 'autocomplete.piggy-banks']); Route::get('piggy-banks', ['uses' => 'Json\AutoCompleteController@piggyBanks', 'as' => 'autocomplete.piggy-banks']);
Route::get('tags', ['uses' => 'Json\AutoCompleteController@tags', 'as' => 'autocomplete.tags']); 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/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']); Route::get('currency-names', ['uses' => 'Json\AutoCompleteController@currencyNames', 'as' => 'autocomplete.currency-names']);
@@ -884,8 +885,8 @@ Route::group(
// clone group: // clone group:
Route::get('clone/{transactionGroup}', ['uses' => 'Transaction\CloneController@clone', 'as' => 'clone']); 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('reorder', ['uses' => 'TransactionController@reorder', 'as' => 'reorder']);
Route::post('reconcile', ['uses' => 'TransactionController@reconcile', 'as' => 'reconcile']); Route::post('reconcile', ['uses' => 'TransactionController@reconcile', 'as' => 'reconcile']);
@@ -942,16 +943,16 @@ Route::group(
/** /**
* Transaction Split Controller * Transaction Split Controller
*/ */
Route::group( //Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions/split', // ['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions/split',
'as' => 'transactions.split.'], function () { // 'as' => 'transactions.split.'], function () {
// TODO improve these routes // // TODO improve these routes
Route::get('edit/{tj}', ['uses' => 'SplitController@edit', 'as' => 'edit']); // Route::get('edit/{tj}', ['uses' => 'SplitController@edit', 'as' => 'edit']);
Route::post('update/{tj}', ['uses' => 'SplitController@update', 'as' => 'update']); // Route::post('update/{tj}', ['uses' => 'SplitController@update', 'as' => 'update']);
// TODO end of todo. // // TODO end of todo.
//
} //}
); //);
/** /**
* Transaction Convert Controller * Transaction Convert Controller
@@ -970,6 +971,9 @@ Route::group(
Route::group( Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions/link', 'as' => 'transactions.link.'], ['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers\Transaction', 'prefix' => 'transactions/link', 'as' => 'transactions.link.'],
function () { function () {
Route::get('modal/{tj}', ['uses' => 'LinkController@modal', 'as' => 'modal']);
// TODO improve this route: // TODO improve this route:
Route::post('store/{tj}', ['uses' => 'LinkController@store', 'as' => 'store']); Route::post('store/{tj}', ['uses' => 'LinkController@store', 'as' => 'store']);
Route::get('delete/{journalLink}', ['uses' => 'LinkController@delete', 'as' => 'delete']); Route::get('delete/{journalLink}', ['uses' => 'LinkController@delete', 'as' => 'delete']);

View File

@@ -28,8 +28,10 @@ use FireflyIII\Models\TransactionJournalLink;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface;
use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface;
use Illuminate\Support\Collection;
use Log; use Log;
use Mockery; use Mockery;
use Preferences;
use Tests\TestCase; use Tests\TestCase;
/** /**
@@ -48,23 +50,39 @@ class LinkControllerTest extends TestCase
/** /**
* @covers \FireflyIII\Http\Controllers\Transaction\LinkController
* @covers \FireflyIII\Http\Controllers\Transaction\LinkController * @covers \FireflyIII\Http\Controllers\Transaction\LinkController
*/ */
public function testDelete(): void 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); $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()); $this->be($this->user());
$response = $this->get(route('transactions.link.delete', [1])); $response = $this->get(route('transactions.link.modal', [$journal->id]));
$response->assertStatus(200); $response->assertStatus(200);
} }
@@ -72,22 +90,18 @@ class LinkControllerTest extends TestCase
* @covers \FireflyIII\Http\Controllers\Transaction\LinkController * @covers \FireflyIII\Http\Controllers\Transaction\LinkController
*/ */
public function testDestroy(): void public function testDestroy(): void
{ $this->markTestIncomplete('Needs to be rewritten for v4.8.0'); {
$link = $this->getRandomLink();
return;
$journalRepos = $this->mock(JournalRepositoryInterface::class);
$repository = $this->mock(LinkTypeRepositoryInterface::class); $repository = $this->mock(LinkTypeRepositoryInterface::class);
$userRepos = $this->mock(UserRepositoryInterface::class);
Preferences::shouldReceive('mark')->once();
$this->mockDefaultSession();
$repository->shouldReceive('destroyLink'); $repository->shouldReceive('destroyLink')->atLeast()->once();
$journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal);
$this->be($this->user()); $this->be($this->user());
$this->session(['journal_links.delete.uri' => 'http://localhost/']); $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->assertStatus(302);
$response->assertSessionHas('success'); $response->assertSessionHas('success');
@@ -98,29 +112,28 @@ class LinkControllerTest extends TestCase
* @covers \FireflyIII\Http\Requests\JournalLinkRequest * @covers \FireflyIII\Http\Requests\JournalLinkRequest
*/ */
public function testStore(): void public function testStore(): void
{ $this->markTestIncomplete('Needs to be rewritten for v4.8.0'); {
$withdrawal = $this->getRandomWithdrawal();
return; $deposit = $this->getRandomDeposit();
$repository = $this->mock(LinkTypeRepositoryInterface::class); $repository = $this->mock(LinkTypeRepositoryInterface::class);
$journalRepos = $this->mock(JournalRepositoryInterface::class); $journalRepos = $this->mockDefaultSession();
$userRepos = $this->mock(UserRepositoryInterface::class);
$data = [ $data = [
'link_other' => 8, 'opposing' => $deposit->id,
'link_type' => '1_inward', 'link_type' => '1_inward',
]; ];
$journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal); //$journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal);
$journalRepos->shouldReceive('findNull')->andReturn(new TransactionJournal); $journalRepos->shouldReceive('findNull')->andReturn($deposit)->atLeast()->once();
$repository->shouldReceive('findLink')->andReturn(false); $repository->shouldReceive('findLink')->andReturn(false)->atLeast()->once();
$repository->shouldReceive('storeLink')->andReturn(new TransactionJournalLink); $repository->shouldReceive('storeLink')->andReturn(new TransactionJournalLink)->atLeast()->once();
$this->be($this->user()); $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->assertStatus(302);
$response->assertSessionHas('success'); $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 * @covers \FireflyIII\Http\Requests\JournalLinkRequest
*/ */
public function testStoreAlreadyLinked(): void public function testStoreAlreadyLinked(): void
{ $this->markTestIncomplete('Needs to be rewritten for v4.8.0'); {
return;
$repository = $this->mock(LinkTypeRepositoryInterface::class); $repository = $this->mock(LinkTypeRepositoryInterface::class);
$journalRepos = $this->mock(JournalRepositoryInterface::class); $journalRepos = $this->mockDefaultSession();
$userRepos = $this->mock(UserRepositoryInterface::class); $link = $this->getRandomLink();
$data = [ $data = [
'link_other' => 8, 'opposing' => $link->source_id,
'link_type' => '1_inward', 'link_type' => '1_inward',
]; ];
$journalRepos->shouldReceive('firstNull')->andReturn(new TransactionJournal); $journalRepos->shouldReceive('findNull')->andReturn(new TransactionJournal)->atLeast()->once();
$journalRepos->shouldReceive('findNull')->andReturn(new TransactionJournal); $repository->shouldReceive('findLink')->andReturn(true)->atLeast()->once();
$repository->shouldReceive('findLink')->andReturn(true);
$this->be($this->user()); $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->assertStatus(302);
$response->assertSessionHas('error'); $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 * @covers \FireflyIII\Http\Requests\JournalLinkRequest
*/ */
public function testStoreInvalid(): void 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 = [ $data = [
'link_other' => 0, 'opposing' => 0,
'link_type' => '1_inward', 'link_type' => '1_inward',
]; ];
$journalRepos = $this->mock(JournalRepositoryInterface::class); $journalRepos->shouldReceive('findNull')->andReturn(null)->atLeast()->once();
$userRepos = $this->mock(UserRepositoryInterface::class);
$repository = $this->mock(LinkTypeRepositoryInterface::class);
$journalRepos->shouldReceive('firstNull')->andReturn(null);
$journalRepos->shouldReceive('findNull')->andReturn(null);
$this->be($this->user()); $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->assertStatus(302);
$response->assertSessionHas('error'); $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 * @covers \FireflyIII\Http\Requests\JournalLinkRequest
*/ */
public function testStoreSame(): void public function testStoreSame(): void
{ $this->markTestIncomplete('Needs to be rewritten for v4.8.0'); {
$withdrawal = $this->getRandomWithdrawal();
return;
$repository = $this->mock(LinkTypeRepositoryInterface::class); $repository = $this->mock(LinkTypeRepositoryInterface::class);
$journalRepos = $this->mock(JournalRepositoryInterface::class); $journalRepos = $this->mockDefaultSession();
$userRepos = $this->mock(UserRepositoryInterface::class);
$data = [ $data = [
'link_other' => 8, 'link_other' => $withdrawal->id,
'link_type' => '1_inward', 'link_type' => '1_inward',
]; ];
$journal = $this->user()->transactionJournals()->first(); $journalRepos->shouldReceive('findNull')->andReturn($withdrawal)->atLeast()->once();
$repository->shouldReceive('findLink')->andReturn(false)->atLeast()->once();
$journalRepos->shouldReceive('firstNull')->andReturn($journal);
$journalRepos->shouldReceive('findNull')->andReturn($journal);
$repository->shouldReceive('findLink')->andReturn(false);
$repository->shouldReceive('storeLink')->andReturn(new TransactionJournalLink);
$this->be($this->user()); $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->assertStatus(302);
$response->assertSessionHas('error'); $response->assertSessionHas('error');
$response->assertRedirect(route('transactions.show', [1])); $response->assertRedirect(route('transactions.show', [$withdrawal->id]));
} }
/** /**
* @covers \FireflyIII\Http\Controllers\Transaction\LinkController * @covers \FireflyIII\Http\Controllers\Transaction\LinkController
*/ */
public function testSwitchLink(): void public function testSwitchLink(): void
{ $this->markTestIncomplete('Needs to be rewritten for v4.8.0'); {
$link = $this->getRandomLink();
return; $withdrawal = $this->getRandomWithdrawal();
$journalRepos = $this->mock(JournalRepositoryInterface::class); $journalRepos = $this->mock(JournalRepositoryInterface::class);
$repository = $this->mock(LinkTypeRepositoryInterface::class); $repository = $this->mock(LinkTypeRepositoryInterface::class);
$userRepos = $this->mock(UserRepositoryInterface::class); $userRepos = $this->mock(UserRepositoryInterface::class);
@@ -224,7 +225,7 @@ class LinkControllerTest extends TestCase
$journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal); $journalRepos->shouldReceive('firstNull')->once()->andReturn(new TransactionJournal);
$repository->shouldReceive('switchLink')->andReturn(false); $repository->shouldReceive('switchLink')->andReturn(false);
$this->be($this->user()); $this->be($this->user());
$response = $this->get(route('transactions.link.switch', [1])); $response = $this->get(route('transactions.link.switch', [$link->id]));
$response->assertStatus(302); $response->assertStatus(302);

View File

@@ -40,6 +40,7 @@ use FireflyIII\Models\Preference;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionGroup;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionJournalLink;
use FireflyIII\Models\TransactionType; use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Transformers\TransactionTransformer; use FireflyIII\Transformers\TransactionTransformer;
@@ -58,6 +59,14 @@ use RuntimeException;
abstract class TestCase extends BaseTestCase abstract class TestCase extends BaseTestCase
{ {
/**
* @return TransactionJournalLink
*/
public function getRandomLink(): TransactionJournalLink
{
return TransactionJournalLink::inRandomOrder()->first();
}
/** /**
* @return Budget * @return Budget
*/ */