Update reconciliation code for #1011 and #736

This commit is contained in:
James Cole
2017-11-24 21:51:07 +01:00
parent 644fa3027a
commit be0758ce8a
14 changed files with 756 additions and 160 deletions

View File

@@ -26,9 +26,12 @@ use Carbon\Carbon;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Http\Requests\ReconciliationFormRequest;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
@@ -63,6 +66,45 @@ class ReconcileController extends Controller
);
}
/**
* @param TransactionJournal $journal
*
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function edit(TransactionJournal $journal)
{
if ($journal->transactionType->type !== TransactionType::RECONCILIATION) {
return redirect(route('transactions.edit', [$journal->id]));
}
// view related code
$subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
// journal related code
$pTransaction = $journal->positiveTransaction();
$preFilled = [
'date' => $journal->dateAsString(),
'category' => $journal->categoryAsString(),
'tags' => join(',', $journal->tags->pluck('tag')->toArray()),
'amount' => $pTransaction->amount,
];
Session::flash('preFilled', $preFilled);
Session::flash('gaEventCategory', 'transactions');
Session::flash('gaEventAction', 'edit-reconciliation');
// put previous url in session if not redirect from store (not "return_to_edit").
if (true !== session('reconcile.edit.fromUpdate')) {
$this->rememberPreviousUri('reconcile.edit.uri');
}
Session::forget('reconcile.edit.fromUpdate');
return view(
'accounts.reconcile.edit',
compact('journal', 'optionalFields', 'assetAccounts', 'what', 'budgetList', 'subTitle')
)->with('data', $preFilled);
}
/**
* @param Request $request
* @param Account $account
@@ -197,6 +239,23 @@ class ReconcileController extends Controller
);
}
/**
* @param TransactionJournal $journal
*/
public function show(JournalRepositoryInterface $repository, TransactionJournal $journal)
{
if ($journal->transactionType->type !== TransactionType::RECONCILIATION) {
return redirect(route('transactions.show', [$journal->id]));
}
$subTitle = trans('firefly.reconciliation') . ' "' . $journal->description . '"';
// get main transaction:
$transaction = $repository->getAssetTransaction($journal);
return view('accounts.reconcile.show', compact('journal', 'subTitle', 'transaction'));
}
/**
* @param Account $account
* @param Carbon $start
@@ -289,4 +348,65 @@ class ReconcileController extends Controller
return Response::json(['html' => $html, 'startBalance' => $startBalance, 'endBalance' => $endBalance]);
}
/**
* @param ReconciliationFormRequest $request
* @param AccountRepositoryInterface $repository
* @param TransactionJournal $journal
*
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function update(ReconciliationFormRequest $request, AccountRepositoryInterface $repository, TransactionJournal $journal)
{
if ($journal->transactionType->type !== TransactionType::RECONCILIATION) {
return redirect(route('transactions.show', [$journal->id]));
}
if (bccomp('0', $request->get('amount')) === 0) {
Session::flash('error', trans('firefly.amount_cannot_be_zero'));
return redirect(route('accounts.reconcile.edit', [$journal->id]))->withInput();
}
// update journal using account repository. Keep it consistent.
$data = $request->getJournalData();
$repository->updateReconciliation($journal, $data);
// @codeCoverageIgnoreStart
if (1 === intval($request->get('return_to_edit'))) {
Session::put('reconcile.edit.fromUpdate', true);
return redirect(route('accounts.reconcile.edit', [$journal->id]))->withInput(['return_to_edit' => 1]);
}
// @codeCoverageIgnoreEnd
// redirect to previous URL.
return redirect($this->getPreviousUri('reconcile.edit.uri'));
}
/**
* @param Account $account
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*
* @throws FireflyException
*/
private function redirectToOriginalAccount(Account $account)
{
/** @var Transaction $transaction */
$transaction = $account->transactions()->first();
if (null === $transaction) {
throw new FireflyException('Expected a transaction. This account has none. BEEP, error.');
}
$journal = $transaction->transactionJournal;
/** @var Transaction $opposingTransaction */
$opposingTransaction = $journal->transactions()->where('transactions.id', '!=', $transaction->id)->first();
if (null === $opposingTransaction) {
throw new FireflyException('Expected an opposing transaction. This account has none. BEEP, error.'); // @codeCoverageIgnore
}
return redirect(route('accounts.show', [$opposingTransaction->account_id]));
}
}

View File

@@ -257,6 +257,10 @@ class SingleController extends Controller
$assetAccounts = $this->groupedAccountList();
$budgetList = ExpandedForm::makeSelectListWithEmpty($this->budgets->getBudgets());
if ($journal->transactionType->type === TransactionType::RECONCILIATION) {
return redirect(route('accounts.reconcile.edit', [$journal->id]));
}
// view related code
$subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);

View File

@@ -28,6 +28,7 @@ use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Helpers\Filter\InternalTransferFilter;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface;
@@ -179,6 +180,9 @@ class TransactionController extends Controller
if ($this->isOpeningBalance($journal)) {
return $this->redirectToAccount($journal);
}
if ($journal->transactionType->type === TransactionType::RECONCILIATION) {
return redirect(route('accounts.reconcile.show', [$journal->id]));
}
$linkTypes = $linkTypeRepository->get();
$links = $linkTypeRepository->getLinks($journal);
$events = $tasker->getPiggyBankEvents($journal);

View File

@@ -0,0 +1,68 @@
<?php
/**
* ReconciliationFormRequest.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Requests;
/**
* Class ReconciliationFormRequest.
*/
class ReconciliationFormRequest extends Request
{
/**
* @return bool
*/
public function authorize()
{
// Only allow logged in users
return auth()->check();
}
/**
* Returns and validates the data required to update a reconciliation.
*
* @return array
*/
public function getJournalData()
{
$data = [
'tags' => explode(',', $this->string('tags')),
'amount' => $this->string('amount'),
'category' => $this->string('category'),
];
return $data;
}
/**
* @return array
*/
public function rules()
{
$rules = [
'amount' => 'numeric|required',
'category' => 'between:1,255|nullable',
];
return $rules;
}
}

View File

@@ -28,9 +28,12 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountMeta;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Category;
use FireflyIII\Models\Tag;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Tag\TagRepositoryInterface;
use FireflyIII\User;
use Log;
use Validator;
@@ -40,10 +43,11 @@ use Validator;
*/
class AccountRepository implements AccountRepositoryInterface
{
use FindAccountsTrait;
/** @var User */
private $user;
use FindAccountsTrait;
/** @var array */
private $validFields = ['accountRole', 'ccMonthlyPaymentDate', 'ccType', 'accountNumber', 'currency_id', 'BIC'];
@@ -195,6 +199,43 @@ class AccountRepository implements AccountRepositoryInterface
return $account;
}
/**
* @param TransactionJournal $journal
* @param array $data
*
* @return TransactionJournal
*/
public function updateReconciliation(TransactionJournal $journal, array $data): TransactionJournal
{
// update journal
// update actual journal:
$data['amount'] = strval($data['amount']);
// unlink all categories, recreate them:
$journal->categories()->detach();
$this->storeCategoryWithJournal($journal, strval($data['category']));
// update amounts
/** @var Transaction $transaction */
foreach ($journal->transactions as $transaction) {
$transaction->amount = bcmul($data['amount'], '-1');
if ($transaction->account->accountType->type === AccountType::ASSET) {
$transaction->amount = $data['amount'];
}
$transaction->save();
}
$journal->save();
// update tags:
if (isset($data['tags']) && is_array($data['tags'])) {
$this->updateTags($journal, $data['tags']);
}
return $journal;
}
/**
* @param Account $account
*/
@@ -279,6 +320,18 @@ class AccountRepository implements AccountRepositoryInterface
return $newAccount;
}
/**
* @param TransactionJournal $journal
* @param string $category
*/
protected function storeCategoryWithJournal(TransactionJournal $journal, string $category)
{
if (strlen($category) > 0) {
$category = Category::firstOrCreateEncrypted(['name' => $category, 'user_id' => $journal->user_id]);
$journal->categories()->save($category);
}
}
/**
* At this point strlen of amount > 0.
*
@@ -494,6 +547,48 @@ class AccountRepository implements AccountRepositoryInterface
return true;
}
/**
* @param TransactionJournal $journal
* @param array $array
*
* @return bool
*/
protected function updateTags(TransactionJournal $journal, array $array): bool
{
// create tag repository
/** @var TagRepositoryInterface $tagRepository */
$tagRepository = app(TagRepositoryInterface::class);
// find or create all tags:
$tags = [];
$ids = [];
foreach ($array as $name) {
if (strlen(trim($name)) > 0) {
$tag = Tag::firstOrCreateEncrypted(['tag' => $name, 'user_id' => $journal->user_id]);
$tags[] = $tag;
$ids[] = $tag->id;
}
}
// delete all tags connected to journal not in this array:
if (count($ids) > 0) {
DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->whereNotIn('tag_id', $ids)->delete();
}
// if count is zero, delete them all:
if (0 === count($ids)) {
DB::table('tag_transaction_journal')->where('transaction_journal_id', $journal->id)->delete();
}
// connect each tag to journal (if not yet connected):
/** @var Tag $tag */
foreach ($tags as $tag) {
Log::debug(sprintf('Will try to connect tag #%d to journal #%d.', $tag->id, $journal->id));
$tagRepository->connect($journal, $tag);
}
return true;
}
/**
* @param array $data
*

View File

@@ -33,6 +33,7 @@ use Illuminate\Support\Collection;
*/
interface AccountRepositoryInterface
{
/**
* Moved here from account CRUD.
*
@@ -164,4 +165,12 @@ interface AccountRepositoryInterface
* @return Account
*/
public function update(Account $account, array $data): Account;
/**
* @param TransactionJournal $journal
* @param array $data
*
* @return TransactionJournal
*/
public function updateReconciliation(TransactionJournal $journal, array $data): TransactionJournal;
}

View File

@@ -23,6 +23,7 @@ declare(strict_types=1);
namespace FireflyIII\Repositories\Journal;
use FireflyIII\Models\Account;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
@@ -169,6 +170,23 @@ class JournalRepository implements JournalRepositoryInterface
return $entry;
}
/**
* @param TransactionJournal $journal
*
* @return Transaction|null
*/
public function getAssetTransaction(TransactionJournal $journal): ?Transaction
{
/** @var Transaction $transaction */
foreach ($journal->transactions as $transaction) {
if ($transaction->account->accountType->type === AccountType::ASSET) {
return $transaction;
}
}
return null;
}
/**
* @return Collection
*/

View File

@@ -91,6 +91,13 @@ interface JournalRepositoryInterface
*/
public function first(): TransactionJournal;
/**
* @param TransactionJournal $journal
*
* @return Transaction|null
*/
public function getAssetTransaction(TransactionJournal $journal): ?Transaction;
/**
* @return Collection
*/

View File

@@ -0,0 +1,52 @@
/*
* edit-reconciliation.js
* Copyright (c) 2017 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
* Firefly III is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Firefly III is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
/** global: what, Modernizr, selectsForeignCurrency, convertForeignToNative, validateCurrencyForTransfer, convertSourceToDestination, journalData, journal, accountInfo, exchangeRateInstructions, currencyInfo */
$(document).ready(function () {
"use strict";
setAutocompletes();
});
/**
* Set the auto-complete JSON things.
*/
function setAutocompletes() {
$.getJSON('json/categories').done(function (data) {
$('input[name="category"]').typeahead({source: data});
});
$.getJSON('json/tags').done(function (data) {
var opt = {
typeahead: {
source: data,
afterSelect: function () {
this.$element.val("");
}
}
};
$('input[name="tags"]').tagsinput(
opt
);
});
}

View File

@@ -621,6 +621,8 @@ return [
'cash_accounts' => 'Cash accounts',
'Cash account' => 'Cash account',
'reconcile_account' => 'Reconcile account ":account"',
'update_reconciliation' => 'Update reconciliation',
'amount_cannot_be_zero' => 'The amount cannot be zero',
'end_of_reconcile_period' => 'End of reconcile period: :period',
'start_of_reconcile_period' => 'Start of reconcile period: :period',
'start_balance' => 'Start balance',

View File

@@ -0,0 +1,115 @@
{% extends "./layout/default" %}
{% block breadcrumbs %}
{{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, journal) }}
{% endblock %}
{% block content %}
<form method="POST" action="{{ route('accounts.reconcile.update',journal.id) }}" accept-charset="UTF-8" class="form-horizontal" id="update"
enctype="multipart/form-data">
<input name="_token" type="hidden" value="{{ csrf_token() }}">
<input type="hidden" name="id" value="{{ journal.id }}"/>
{% if errors.all|length > 0 %}
<div class="row">
<div class="col-lg-12">
<h3 class="text-danger">{{ 'errors'|_ }}</h3>
<ul>
{% for err in errors.all %}
<li class="text-danger">{{ err }}</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">{{ 'mandatoryFields'|_ }}</h3>
</div>
<div class="box-body">
{# ALWAYS AVAILABLE #}
{{ ExpandedForm.staticText('description',journal.description) }}
{# ALWAYS SHOW AMOUNT #}
{{ ExpandedForm.nonSelectableAmount('amount',data.amount, {'currency' : data.currency}) }}
{# ALWAYS SHOW DATE #}
{{ ExpandedForm.staticText('date',journal.date.formatLocalized(monthAndDayFormat)) }}
</div>
</div>
</div>
<div class="col-lg-6 col-md-6 col-sm-12 col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'optionalFields'|_ }}</h3>
</div>
<div class="box-body">
<!-- CATEGORY ALWAYS -->
{{ ExpandedForm.text('category',data['category']) }}
<!-- TAGS -->
{{ ExpandedForm.text('tags') }}
</div>
</div>
<!-- box for attachments -->
{% if optionalFields.attachments %}
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'optional_field_attachments'|_ }}</h3>
</div>
<div class="box-body">
{% if optionalFields.attachments %}
<!-- ATTACHMENTS -->
{{ ExpandedForm.file('attachments[]', {'multiple': 'multiple','helpText': trans('firefly.upload_max_file_size', {'size': uploadSize|filesize}) }) }}
{% endif %}
</div>
</div>
{% endif %}
<!-- panel for options -->
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'options'|_ }}</h3>
</div>
<div class="box-body">
{{ ExpandedForm.optionsList('update','transaction') }}
</div>
<div class="box-footer">
<button type="submit" class="pull-right btn btn-success">{{ ('update_reconciliation')|_ }}</button>
</div>
</div>
</div>
</div>
</form>
{% endblock %}
{% block scripts %}
<script type="text/javascript">
var what = "{{ what }}";
</script>
<script type="text/javascript" src="js/lib/bootstrap3-typeahead.min.js?v={{ FF_VERSION }}"></script>
<script type="text/javascript" src="js/lib/bootstrap-tagsinput.min.js?v={{ FF_VERSION }}"></script>
<script type="text/javascript" src="js/lib/jquery-ui.min.js?v={{ FF_VERSION }}"></script>
<script type="text/javascript" src="js/lib/modernizr-custom.js?v={{ FF_VERSION }}"></script>
<script type="text/javascript" src="jscript/accounts?ext=.js&amp;v={{ FF_VERSION }}"></script>
<script type="text/javascript" src="jscript/currencies?ext=.js&amp;v={{ FF_VERSION }}"></script>
<script type="text/javascript">
var journal = {{ journal.toArray()|json_encode|raw }};
var journalData = {{ data|json_encode|raw }};
</script>
<script type="text/javascript" src="/js/ff/accounts/edit-reconciliation.js?v={{ FF_VERSION }}"></script>
{% endblock %}
{% block styles %}
<link href="css/bootstrap-tagsinput.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" media="all">
<link href="css/jquery-ui/jquery-ui.structure.min.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" media="all">
<link href="css/jquery-ui/jquery-ui.theme.min.css?v={{ FF_VERSION }}" type="text/css" rel="stylesheet" media="all">
{% endblock %}

View File

@@ -0,0 +1,107 @@
{% extends "./layout/default" %}
{% block breadcrumbs %}
{{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, journal) }}
{% endblock %}
{% block content %}
<div class="row">
<div class="col-lg-6 col-md-6 col-sm-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'transaction_journal_information'|_ }}</h3>
<div class="box-tools pull-right">
<div class="btn-group">
<button id="transaction_menu" class="btn btn-box-tool dropdown-toggle" data-toggle="dropdown"><i class="fa fa-ellipsis-v"></i>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="{{ route('transactions.edit',journal.id) }}"><i class="fa fa-pencil fa-fw"></i> {{ 'edit'|_ }}</a></li>
<li><a href="{{ route('transactions.delete',journal.id) }}"><i class="fa fa-trash fa-fw"></i> {{ 'delete'|_ }}</a></li>
</ul>
</div>
</div>
</div>
<div class="box-body no-padding">
<table class="table table-hover">
<tbody>
<tr>
<td>{{ trans('list.type') }}</td>
<td>{{ journal.transactiontype.type|_ }}</td>
</tr>
<tr>
<td>{{ trans('list.description') }}</td>
<td>{{ journal.description }}</td>
</tr>
{# total amount #}
<tr>
<td>{{ 'total_amount'|_ }}</td>
<td>
{{ formatAmountByAccount(transaction.account, transaction.amount) }}
</td>
</tr>
<tr>
<td style="width:30%;">{{ trans('list.date') }}</td>
<td>{{ journal.date.formatLocalized(monthAndDayFormat) }}</td>
</tr>
</tbody>
</table>
</div>
<div class="box-footer">
<div class="pull-right">
<div class="btn-group">
<a class="btn btn-default" href="{{ route('transactions.edit',journal.id) }}"><i class="fa fa-pencil fa-fw"></i> {{ 'edit'|_ }}</a>
<a href="{{ route('transactions.delete',journal.id) }}" class="btn btn-danger"><i class="fa fa-trash fa-fw"></i> {{ 'delete'|_ }}
</a>
</div>
</div>
</div>
</div>
</div>
<div class="col-lg-6 col-md-6 col-sm-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'transaction_journal_meta'|_ }}</h3>
</div>
<div class="box-body table-responsive no-padding">
<table class="table table-hover">
<tbody>
<tr>
<td>{{ 'categories'|_ }}</td>
<td>{{ journalCategories(journal)|raw }}</td>
</tr>
{% 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) }}">
{% if tag.tagMode == 'nothing' %}
<i class="fa fa-fw fa-tag"></i>
{% endif %}
{% if tag.tagMode == 'balancingAct' %}
<i class="fa fa-fw fa-refresh"></i>
{% endif %}
{% if tag.tagMode == 'advancePayment' %}
<i class="fa fa-fw fa-sort-numeric-desc"></i>
{% endif %}
{{ tag.tag }}</a>
</h4>
{% endfor %}
</td>
</tr>
{% endif %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
{% endblock %}

View File

@@ -19,47 +19,45 @@
<li><a href="{{ route('transactions.edit',journal.id) }}"><i class="fa fa-pencil fa-fw"></i> {{ 'edit'|_ }}</a></li>
<li><a href="{{ route('transactions.delete',journal.id) }}"><i class="fa fa-trash fa-fw"></i> {{ 'delete'|_ }}</a></li>
{# convert to withdrawal #}
{% if journal.transactionType.type != 'Reconciliation' %}
{% if journal.transactionType.type != "Withdrawal" %}
<li>
<a href="{{ route('transactions.convert.index', ['withdrawal', journal.id]) }}">
<i class="fa fa-exchange fa-fw"></i> {{ ('convert_'~journal.transactionType.type~'_to_withdrawal')|_ }}
</a>
</li>
{% endif %}
{# convert to deposit #}
{% if journal.transactionType.type != "Deposit" %}
<li>
<a href="{{ route('transactions.convert.index', ['deposit', journal.id]) }}">
<i class="fa fa-exchange fa-fw"></i> {{ ('convert_'~journal.transactionType.type~'_to_deposit')|_ }}
</a>
</li>
{% endif %}
{# convert to transfer#}
{% if journal.transactionType.type != "Transfer" %}
<li>
<a href="{{ route('transactions.convert.index', ['transfer', journal.id]) }}">
<i class="fa fa-exchange fa-fw"></i> {{ ('convert_'~journal.transactionType.type~'_to_transfer')|_ }}
</a>
</li>
{% endif %}
{# other options #}
{% if journal.transactionType.type != "Withdrawal" %}
<li>
<a href="{{ route('transactions.clone', [journal.id]) }}">
<i class="fa fa-copy fa-fw"></i> {{ ('clone_'~journal.transactionType.type|lower)|_ }}
</a>
</li>
<li>
<a href="{{ route('transactions.split.edit', journal.id) }}">
<i class="fa fa-unsorted fa-fw"></i> {{ ('split_this_'~what)|_ }}
</a>
</li>
<li>
<a href="#" data-toggle="modal" data-target="#linkJournalModal"><i
class="fa fa-fw fa-link"></i> {{ 'link_transaction'|_ }}
<a href="{{ route('transactions.convert.index', ['withdrawal', journal.id]) }}">
<i class="fa fa-exchange fa-fw"></i> {{ ('convert_'~journal.transactionType.type~'_to_withdrawal')|_ }}
</a>
</li>
{% endif %}
{# convert to deposit #}
{% if journal.transactionType.type != "Deposit" %}
<li>
<a href="{{ route('transactions.convert.index', ['deposit', journal.id]) }}">
<i class="fa fa-exchange fa-fw"></i> {{ ('convert_'~journal.transactionType.type~'_to_deposit')|_ }}
</a>
</li>
{% endif %}
{# convert to transfer#}
{% if journal.transactionType.type != "Transfer" %}
<li>
<a href="{{ route('transactions.convert.index', ['transfer', journal.id]) }}">
<i class="fa fa-exchange fa-fw"></i> {{ ('convert_'~journal.transactionType.type~'_to_transfer')|_ }}
</a>
</li>
{% endif %}
{# other options #}
<li>
<a href="{{ route('transactions.clone', [journal.id]) }}">
<i class="fa fa-copy fa-fw"></i> {{ ('clone_'~journal.transactionType.type|lower)|_ }}
</a>
</li>
<li>
<a href="{{ route('transactions.split.edit', journal.id) }}">
<i class="fa fa-unsorted fa-fw"></i> {{ ('split_this_'~what)|_ }}
</a>
</li>
<li>
<a href="#" data-toggle="modal" data-target="#linkJournalModal"><i
class="fa fa-fw fa-link"></i> {{ 'link_transaction'|_ }}
</a>
</li>
</ul>
</div>
</div>
@@ -76,19 +74,17 @@
<td>{{ trans('list.description') }}</td>
<td>{{ journal.description }}</td>
</tr>
{% if journal.transactionType.type != 'Reconciliation' %}
<!-- source(s) -->
<tr>
<td>{{ 'source_accounts'|_ }}</td>
<td>{{ sourceAccount(journal)|raw }}</td>
</tr>
<!-- source(s) -->
<tr>
<td>{{ 'source_accounts'|_ }}</td>
<td>{{ sourceAccount(journal)|raw }}</td>
</tr>
<!-- destination(s) -->
<tr>
<td>{{ 'destination_accounts'|_ }}</td>
<td>{{ destinationAccount(journal)|raw }}</td>
</tr>
{% endif %}
<!-- destination(s) -->
<tr>
<td>{{ 'destination_accounts'|_ }}</td>
<td>{{ destinationAccount(journal)|raw }}</td>
</tr>
<!-- total amount -->
<tr>
<td>{{ 'total_amount'|_ }}</td>
@@ -113,55 +109,53 @@
<div class="pull-right">
<div class="btn-group">
<a class="btn btn-default" href="{{ route('transactions.edit',journal.id) }}"><i class="fa fa-pencil fa-fw"></i> {{ 'edit'|_ }}</a>
{% if journal.transactionType.type != 'Reconciliation' %}
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-cog fa-fw"></i> {{ 'options'|_ }} <span class="caret"></span>
</button>
<ul class="dropdown-menu">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
<i class="fa fa-cog fa-fw"></i> {{ 'options'|_ }} <span class="caret"></span>
</button>
<ul class="dropdown-menu">
{# convert to withdrawal #}
{% if journal.transactionType.type != "Withdrawal" %}
<li>
<a href="{{ route('transactions.convert.index', ['withdrawal', journal.id]) }}">
<i class="fa fa-exchange fa-fw"></i> {{ ('convert_'~journal.transactionType.type~'_to_withdrawal')|_ }}
</a>
</li>
{% endif %}
{# convert to deposit #}
{% if journal.transactionType.type != "Deposit" %}
<li>
<a href="{{ route('transactions.convert.index', ['deposit', journal.id]) }}">
<i class="fa fa-exchange fa-fw"></i> {{ ('convert_'~journal.transactionType.type~'_to_deposit')|_ }}
</a>
</li>
{% endif %}
{# convert to transfer#}
{% if journal.transactionType.type != "Transfer" %}
<li>
<a href="{{ route('transactions.convert.index', ['transfer', journal.id]) }}">
<i class="fa fa-exchange fa-fw"></i> {{ ('convert_'~journal.transactionType.type~'_to_transfer')|_ }}
</a>
</li>
{% endif %}
{# other options #}
{# convert to withdrawal #}
{% if journal.transactionType.type != "Withdrawal" %}
<li>
<a href="{{ route('transactions.clone', [journal.id]) }}">
<i class="fa fa-copy fa-fw"></i> {{ ('clone_'~journal.transactionType.type|lower)|_ }}
<a href="{{ route('transactions.convert.index', ['withdrawal', journal.id]) }}">
<i class="fa fa-exchange fa-fw"></i> {{ ('convert_'~journal.transactionType.type~'_to_withdrawal')|_ }}
</a>
</li>
{% endif %}
{# convert to deposit #}
{% if journal.transactionType.type != "Deposit" %}
<li>
<a href="{{ route('transactions.split.edit', journal.id) }}">
<i class="fa fa-unsorted fa-fw"></i> {{ ('split_this_'~what)|_ }}
<a href="{{ route('transactions.convert.index', ['deposit', journal.id]) }}">
<i class="fa fa-exchange fa-fw"></i> {{ ('convert_'~journal.transactionType.type~'_to_deposit')|_ }}
</a>
</li>
{% endif %}
{# convert to transfer#}
{% if journal.transactionType.type != "Transfer" %}
<li>
<a href="#" data-toggle="modal" data-target="#linkJournalModal"><i
class="fa fa-fw fa-link"></i> {{ 'link_transaction'|_ }}
<a href="{{ route('transactions.convert.index', ['transfer', journal.id]) }}">
<i class="fa fa-exchange fa-fw"></i> {{ ('convert_'~journal.transactionType.type~'_to_transfer')|_ }}
</a>
</li>
{% endif %}
{# other options #}
<li>
<a href="{{ route('transactions.clone', [journal.id]) }}">
<i class="fa fa-copy fa-fw"></i> {{ ('clone_'~journal.transactionType.type|lower)|_ }}
</a>
</li>
<li>
<a href="{{ route('transactions.split.edit', journal.id) }}">
<i class="fa fa-unsorted fa-fw"></i> {{ ('split_this_'~what)|_ }}
</a>
</li>
<li>
<a href="#" data-toggle="modal" data-target="#linkJournalModal"><i
class="fa fa-fw fa-link"></i> {{ 'link_transaction'|_ }}
</a>
</li>
</ul>
{% endif %}
</ul>
<a href="{{ route('transactions.delete',journal.id) }}" class="btn btn-danger"><i class="fa fa-trash fa-fw"></i> {{ 'delete'|_ }}
</a>
</div>
@@ -194,12 +188,10 @@
<td>{{ 'categories'|_ }}</td>
<td>{{ journalCategories(journal)|raw }}</td>
</tr>
{% if journal.transactionType.type != 'Reconciliation' %}
<tr>
<td>{{ 'budgets'|_ }}</td>
<td>{{ journalBudgets(journal)|raw }}</td>
</tr>
{% endif %}
<tr>
<td>{{ 'budgets'|_ }}</td>
<td>{{ journalBudgets(journal)|raw }}</td>
</tr>
{% if journal.hasMeta('interest_date') %}
<tr>
@@ -373,75 +365,73 @@
{% endif %}
</div>
</div>
{% if journal.transactionType.type != 'Reconciliation' %}
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'transactions'|_ }}</h3>
</div>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th class="hidden-md hidden-sm hidden-xs">{{ trans('list.description') }}</th>
<th>{{ trans('list.source_account') }}</th>
<th class="hidden-sm hidden-xs">&#916;</th>
<th>{{ trans('list.destination_account') }}</th>
<th class="hidden-sm hidden-xs">&#916;</th>
<th>{{ trans('list.amount') }}</th>
<th class="hidden-md hidden-xs">{{ trans('list.budget') }}</th>
<th class="hidden-md hidden-xs">{{ trans('list.category') }}</th>
</tr>
</thead>
<tbody>
{% for transaction in transactions %}
<tr>
<td class="hidden-md hidden-sm hidden-xs">
{% if transaction.description == "" %}
{{ journal.description }}
{% else %}
{{ transaction.description }}
{% endif %}
</td>
<td>
{% if transaction.source_account_type == 'Cash account' %}
<span class="text-success">({{ 'cash'|_ }})</span>
{% else %}
<a href="{{ route('accounts.show', transaction.source_account_id) }}">{{ transaction.source_account_name }}</a>
{% endif %}
</td>
<td class="hidden-sm hidden-xs">
{{ formatSourceBefore(transaction) }} &rarr; {{ formatSourceAfter(transaction) }}
</td>
<td>
{% if transaction.destination_account_type == 'Cash account' %}
<span class="text-success">({{ 'cash'|_ }})</span>
{% else %}
<a href="{{ route('accounts.show', transaction.destination_account_id) }}">{{ transaction.destination_account_name }}</a>
{% endif %}
</td>
<td class="hidden-sm hidden-xs">
{{ formatDestinationBefore(transaction) }} &rarr; {{ formatDestinationAfter(transaction) }}
</td>
<td>
{{ transaction|transactionArrayAmount }}
</td>
<td class="hidden-md hidden-xs">
{{ transaction.source|transactionBudgets }}
</td>
<td class="hidden-md hidden-xs">
{{ transaction.source|transactionCategories }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'transactions'|_ }}</h3>
</div>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th class="hidden-md hidden-sm hidden-xs">{{ trans('list.description') }}</th>
<th>{{ trans('list.source_account') }}</th>
<th class="hidden-sm hidden-xs">&#916;</th>
<th>{{ trans('list.destination_account') }}</th>
<th class="hidden-sm hidden-xs">&#916;</th>
<th>{{ trans('list.amount') }}</th>
<th class="hidden-md hidden-xs">{{ trans('list.budget') }}</th>
<th class="hidden-md hidden-xs">{{ trans('list.category') }}</th>
</tr>
</thead>
<tbody>
{% for transaction in transactions %}
<tr>
<td class="hidden-md hidden-sm hidden-xs">
{% if transaction.description == "" %}
{{ journal.description }}
{% else %}
{{ transaction.description }}
{% endif %}
</td>
<td>
{% if transaction.source_account_type == 'Cash account' %}
<span class="text-success">({{ 'cash'|_ }})</span>
{% else %}
<a href="{{ route('accounts.show', transaction.source_account_id) }}">{{ transaction.source_account_name }}</a>
{% endif %}
</td>
<td class="hidden-sm hidden-xs">
{{ formatSourceBefore(transaction) }} &rarr; {{ formatSourceAfter(transaction) }}
</td>
<td>
{% if transaction.destination_account_type == 'Cash account' %}
<span class="text-success">({{ 'cash'|_ }})</span>
{% else %}
<a href="{{ route('accounts.show', transaction.destination_account_id) }}">{{ transaction.destination_account_name }}</a>
{% endif %}
</td>
<td class="hidden-sm hidden-xs">
{{ formatDestinationBefore(transaction) }} &rarr; {{ formatDestinationAfter(transaction) }}
</td>
<td>
{{ transaction|transactionArrayAmount }}
</td>
<td class="hidden-md hidden-xs">
{{ transaction.source|transactionBudgets }}
</td>
<td class="hidden-md hidden-xs">
{{ transaction.source|transactionCategories }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
</div>
{# link journal modal:#}
<div class="modal fade" tabindex="-1" role="dialog" id="linkJournalModal">

View File

@@ -99,6 +99,11 @@ Route::group(
Route::get('reconcile/{account}/overview/{start_date?}/{end_date?}', ['uses' => 'Account\ReconcileController@overview', 'as' => 'reconcile.overview']);
Route::post('reconcile/{account}/submit/{start_date?}/{end_date?}', ['uses' => 'Account\ReconcileController@submit', 'as' => 'reconcile.submit']);
// show reconciliation
Route::get('reconcile/show/{tj}', ['uses' => 'Account\ReconcileController@show', 'as' => 'reconcile.show']);
Route::get('reconcile/edit/{tj}', ['uses' => 'Account\ReconcileController@edit', 'as' => 'reconcile.edit']);
Route::post('reconcile/update/{tj}', ['uses' => 'Account\ReconcileController@update', 'as' => 'reconcile.update']);
Route::post('store', ['uses' => 'AccountController@store', 'as' => 'store']);
Route::post('update/{account}', ['uses' => 'AccountController@update', 'as' => 'update']);
Route::post('destroy/{account}', ['uses' => 'AccountController@destroy', 'as' => 'destroy']);