mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-11-16 06:38:09 +00:00
New code for #736
This commit is contained in:
173
app/Http/Controllers/Account/ReconcileController.php
Normal file
173
app/Http/Controllers/Account/ReconcileController.php
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* ReconcileController.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\Controllers\Account;
|
||||||
|
|
||||||
|
|
||||||
|
use Carbon\Carbon;
|
||||||
|
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||||
|
use FireflyIII\Http\Controllers\Controller;
|
||||||
|
use FireflyIII\Models\Account;
|
||||||
|
use FireflyIII\Models\AccountType;
|
||||||
|
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Navigation;
|
||||||
|
use Preferences;
|
||||||
|
use Response;
|
||||||
|
use View;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class ReconcileController
|
||||||
|
*
|
||||||
|
* @package FireflyIII\Http\Controllers\Account
|
||||||
|
*/
|
||||||
|
class ReconcileController extends Controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
parent::__construct();
|
||||||
|
|
||||||
|
// translations:
|
||||||
|
$this->middleware(
|
||||||
|
function ($request, $next) {
|
||||||
|
View::share('mainTitleIcon', 'fa-credit-card');
|
||||||
|
View::share('title', trans('firefly.accounts'));
|
||||||
|
|
||||||
|
return $next($request);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Request $request
|
||||||
|
* @param Account $account
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Http\JsonResponse
|
||||||
|
*/
|
||||||
|
public function overview(Request $request, Account $account, Carbon $start, Carbon $end)
|
||||||
|
{
|
||||||
|
$startBalance = $request->get('startBalance');
|
||||||
|
$endBalance = $request->get('endBalance');
|
||||||
|
$transactions = $request->get('transactions');
|
||||||
|
|
||||||
|
$return = [
|
||||||
|
'is_zero' => false,
|
||||||
|
'post_uri' => route('accounts.reconcile.submit', [$account->id, $start->format('Ymd'), $end->format('Ymd')]),
|
||||||
|
'html' => '',
|
||||||
|
];
|
||||||
|
$return['html'] = view('accounts.reconcile.overview', compact('account', 'start', 'end'))->render();
|
||||||
|
|
||||||
|
return Response::json($return);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Account $account
|
||||||
|
* @param Carbon|null $start
|
||||||
|
* @param Carbon|null $end
|
||||||
|
*
|
||||||
|
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||||
|
*/
|
||||||
|
public function reconcile(Account $account, Carbon $start = null, Carbon $end = null)
|
||||||
|
{
|
||||||
|
if ($account->accountType->type === AccountType::INITIAL_BALANCE) {
|
||||||
|
return $this->redirectToOriginalAccount($account);
|
||||||
|
}
|
||||||
|
/** @var CurrencyRepositoryInterface $currencyRepos */
|
||||||
|
$currencyRepos = app(CurrencyRepositoryInterface::class);
|
||||||
|
$currencyId = intval($account->getMeta('currency_id'));
|
||||||
|
$currency = $currencyRepos->find($currencyId);
|
||||||
|
if ($currencyId === 0) {
|
||||||
|
$currency = app('amount')->getDefaultCurrency();
|
||||||
|
}
|
||||||
|
|
||||||
|
// no start or end:
|
||||||
|
$range = Preferences::get('viewRange', '1M')->data;
|
||||||
|
|
||||||
|
// get start and end
|
||||||
|
if (is_null($start) && is_null($end)) {
|
||||||
|
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
|
||||||
|
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
|
||||||
|
}
|
||||||
|
if (is_null($end)) {
|
||||||
|
$end = Navigation::endOfPeriod($start, $range);
|
||||||
|
}
|
||||||
|
|
||||||
|
$startDate = clone $start;
|
||||||
|
$startDate->subDays(1);
|
||||||
|
$startBalance = round(app('steam')->balance($account, $startDate), $currency->decimal_places);
|
||||||
|
$endBalance = round(app('steam')->balance($account, $end), $currency->decimal_places);
|
||||||
|
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
|
||||||
|
$subTitle = trans('firefly.reconcile_account', ['account' => $account->name]);
|
||||||
|
|
||||||
|
// various links
|
||||||
|
$transactionsUri = route('accounts.reconcile.transactions', [$account->id, '%start%', '%end%']);
|
||||||
|
$overviewUri = route('accounts.reconcile.overview', [$account->id, '%start%', '%end%']);
|
||||||
|
$indexUri = route('accounts.reconcile', [$account->id, '%start%', '%end%']);
|
||||||
|
|
||||||
|
return view(
|
||||||
|
'accounts.reconcile.index', compact(
|
||||||
|
'account', 'currency', 'subTitleIcon', 'start', 'end', 'subTitle', 'startBalance', 'endBalance', 'transactionsUri',
|
||||||
|
'selectionStart', 'selectionEnd', 'overviewUri', 'indexUri'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param Account $account
|
||||||
|
* @param Carbon $start
|
||||||
|
* @param Carbon $end
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function transactions(Account $account, Carbon $start, Carbon $end)
|
||||||
|
{
|
||||||
|
if ($account->accountType->type === AccountType::INITIAL_BALANCE) {
|
||||||
|
return $this->redirectToOriginalAccount($account);
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the transactions
|
||||||
|
$selectionStart = clone $start;
|
||||||
|
$selectionStart->subDays(3);
|
||||||
|
$selectionEnd = clone $end;
|
||||||
|
$selectionEnd->addDays(3);
|
||||||
|
|
||||||
|
|
||||||
|
// grab transactions:
|
||||||
|
/** @var JournalCollectorInterface $collector */
|
||||||
|
$collector = app(JournalCollectorInterface::class);
|
||||||
|
$collector->setAccounts(new Collection([$account]))
|
||||||
|
->setRange($selectionStart, $selectionEnd)->withBudgetInformation()->withOpposingAccount()->withCategoryInformation();
|
||||||
|
$transactions = $collector->getJournals();
|
||||||
|
$html = view('accounts.reconcile.transactions', compact('account', 'transactions', 'start', 'end', 'selectionStart', 'selectionEnd'))->render();
|
||||||
|
|
||||||
|
return Response::json(['html' => $html]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -250,74 +250,6 @@ class AccountController extends Controller
|
|||||||
return view('accounts.index', compact('what', 'subTitleIcon', 'subTitle', 'accounts'));
|
return view('accounts.index', compact('what', 'subTitleIcon', 'subTitle', 'accounts'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param Request $request
|
|
||||||
* @param Account $account
|
|
||||||
* @param string $moment
|
|
||||||
*
|
|
||||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
|
||||||
*/
|
|
||||||
public function reconcile(Request $request, Account $account, Carbon $start = null, Carbon $end = null)
|
|
||||||
{
|
|
||||||
if ($account->accountType->type === AccountType::INITIAL_BALANCE) {
|
|
||||||
return $this->redirectToOriginalAccount($account);
|
|
||||||
}
|
|
||||||
/** @var CurrencyRepositoryInterface $currencyRepos */
|
|
||||||
$currencyRepos = app(CurrencyRepositoryInterface::class);
|
|
||||||
$currencyId = intval($account->getMeta('currency_id'));
|
|
||||||
$currency = $currencyRepos->find($currencyId);
|
|
||||||
if ($currencyId === 0) {
|
|
||||||
$currency = app('amount')->getDefaultCurrency();
|
|
||||||
}
|
|
||||||
|
|
||||||
// no start or end:
|
|
||||||
$range = Preferences::get('viewRange', '1M')->data;
|
|
||||||
|
|
||||||
// get start and end
|
|
||||||
if(is_null($start) && is_null($end)) {
|
|
||||||
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
|
|
||||||
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
|
|
||||||
}
|
|
||||||
if(is_null($end)) {
|
|
||||||
$end = Navigation::endOfPeriod($start, $range);
|
|
||||||
}
|
|
||||||
|
|
||||||
$startDate = clone $start;
|
|
||||||
$startDate->subDays(1);
|
|
||||||
$startBalance = round(app('steam')->balance($account, $startDate), $currency->decimal_places);
|
|
||||||
$endBalance = round(app('steam')->balance($account, $end), $currency->decimal_places);
|
|
||||||
$subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type);
|
|
||||||
$subTitle = trans('firefly.reconcile_account', ['account' => $account->name]);
|
|
||||||
|
|
||||||
// get the transactions
|
|
||||||
$selectionStart = clone $start;
|
|
||||||
$selectionStart->subDays(7);
|
|
||||||
$selectionEnd = clone $end;
|
|
||||||
$selectionEnd->addDays(5);
|
|
||||||
|
|
||||||
// grab transactions:
|
|
||||||
/** @var JournalCollectorInterface $collector */
|
|
||||||
$collector = app(JournalCollectorInterface::class);
|
|
||||||
$collector->setAccounts(new Collection([$account]))
|
|
||||||
->setRange($selectionStart, $selectionEnd)->withBudgetInformation()->withOpposingAccount()->withCategoryInformation();
|
|
||||||
$transactions = $collector->getJournals();
|
|
||||||
|
|
||||||
return view('accounts.reconcile', compact('account', 'currency', 'subTitleIcon', 'start', 'end', 'subTitle', 'startBalance', 'endBalance','transactions','selectionStart','selectionEnd'));
|
|
||||||
|
|
||||||
// prep for "specific date" view.
|
|
||||||
if (strlen($moment) > 0 && $moment !== 'all') {
|
|
||||||
$start = new Carbon($moment);
|
|
||||||
$end = Navigation::endOfPeriod($start, $range);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return view(
|
|
||||||
'accounts.show',
|
|
||||||
compact('account', 'currency', 'moment', 'periods', 'subTitleIcon', 'transactions', 'subTitle', 'start', 'end', 'chartUri')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show an account.
|
* Show an account.
|
||||||
*
|
*
|
||||||
|
|||||||
174
public/js/ff/accounts/reconcile.js
vendored
174
public/js/ff/accounts/reconcile.js
vendored
@@ -18,20 +18,178 @@
|
|||||||
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
var balanceDifference = 0;
|
||||||
|
var difference = 0;
|
||||||
|
var selectedAmount = 0;
|
||||||
|
var reconcileStarted = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
$(function () {
|
$(function () {
|
||||||
"use strict";
|
"use strict";
|
||||||
$('input[type="date"]').on('change', showUpdateButton);
|
|
||||||
$('.update_view').on('click', updateView);
|
/*
|
||||||
|
Respond to changes in balance statements.
|
||||||
|
*/
|
||||||
|
$('input[type="number"]').on('change', function () {
|
||||||
|
if (reconcileStarted) {
|
||||||
|
calculateBalanceDifference();
|
||||||
|
difference = balanceDifference - selectedAmount;
|
||||||
|
updateDifference();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/*
|
||||||
|
Respond to changes in the date range.
|
||||||
|
*/
|
||||||
|
$('input[type="date"]').on('change', function () {
|
||||||
|
if (reconcileStarted) {
|
||||||
|
// hide original instructions.
|
||||||
|
$('.select_transactions_instruction').hide();
|
||||||
|
|
||||||
|
// show date-change warning
|
||||||
|
$('.date_change_warning').show();
|
||||||
|
|
||||||
|
// show update button
|
||||||
|
$('.change_date_button').show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
$('.change_date_button').click(startReconcile);
|
||||||
|
$('.start_reconcile').click(startReconcile);
|
||||||
|
$('.store_reconcile').click(storeReconcile);
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function showUpdateButton() {
|
function storeReconcile() {
|
||||||
$('.update_date_button').show();
|
// get modal HTML:
|
||||||
|
var ids = [];
|
||||||
|
$.each($('.reconcile_checkbox:checked'), function (i, v) {
|
||||||
|
ids.push($(v).data('id'));
|
||||||
|
});
|
||||||
|
var variables = {
|
||||||
|
startBalance: parseFloat($('input[name="start_balance"]').val()),
|
||||||
|
endBalance: parseFloat($('input[name="end_balance"]').val()),
|
||||||
|
startDate: $('input[name="start_date"]').val(),
|
||||||
|
startEnd: $('input[name="end_date"]').val(),
|
||||||
|
transactions: ids
|
||||||
|
};
|
||||||
|
var uri = overviewUri.replace('%start%', $('input[name="start_date"]').val()).replace('%end%', $('input[name="end_date"]').val());
|
||||||
|
|
||||||
|
|
||||||
|
$.getJSON(uri, variables).done(function (data) {
|
||||||
|
if (data.is_zero === false) {
|
||||||
|
$('#defaultModal').empty().html(data.html).modal('show');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateView() {
|
/**
|
||||||
var startDate = $('input[name="start_date"]').val();
|
* What happens when you check a checkbox:
|
||||||
var endDate = $('input[name="end_date"]').val();
|
* @param e
|
||||||
window.location = '/accounts/reconcile/2/' + startDate + '/' + endDate;
|
*/
|
||||||
|
function checkReconciledBox(e) {
|
||||||
|
var el = $(e.target);
|
||||||
|
var amount = parseFloat(el.val());
|
||||||
|
console.log('Amount is ' + amount);
|
||||||
|
// if checked, add to selected amount
|
||||||
|
if (el.prop('checked') === true && el.data('younger') === false) {
|
||||||
|
console.log("Sum is: " + selectedAmount + " - " + amount + " = " + (selectedAmount - amount));
|
||||||
|
selectedAmount = selectedAmount - amount;
|
||||||
|
}
|
||||||
|
if (el.prop('checked') === false && el.data('younger') === false) {
|
||||||
|
console.log("Sum is: " + selectedAmount + " + " + amount + " = " + (selectedAmount + amount));
|
||||||
|
selectedAmount = selectedAmount + amount;
|
||||||
|
}
|
||||||
|
difference = balanceDifference - selectedAmount;
|
||||||
|
updateDifference();
|
||||||
|
allowReconcile();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
function allowReconcile() {
|
||||||
|
var count = $('.reconcile_checkbox:checked').length;
|
||||||
|
console.log('Count checkboxes is ' + count);
|
||||||
|
if (count > 0) {
|
||||||
|
$('.store_reconcile').prop('disabled', false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate the difference between given start and end balance
|
||||||
|
* and put it in balanceDifference.
|
||||||
|
*/
|
||||||
|
function calculateBalanceDifference() {
|
||||||
|
var startBalance = parseFloat($('input[name="start_balance"]').val());
|
||||||
|
var endBalance = parseFloat($('input[name="end_balance"]').val());
|
||||||
|
balanceDifference = startBalance - endBalance;
|
||||||
|
if (balanceDifference < 0) {
|
||||||
|
balanceDifference = balanceDifference * -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTransactionsForRange() {
|
||||||
|
// clear out the box:
|
||||||
|
$('#transactions_holder').empty().append($('<p>').addClass('text-center').html('<i class="fa fa-fw fa-spin fa-spinner"></i>'));
|
||||||
|
var uri = transactionsUri.replace('%start%', $('input[name="start_date"]').val()).replace('%end%', $('input[name="end_date"]').val());
|
||||||
|
var index = indexUri.replace('%start%', $('input[name="start_date"]').val()).replace('%end%', $('input[name="end_date"]').val());
|
||||||
|
window.history.pushState('object or string', "Reconcile account", index);
|
||||||
|
|
||||||
|
$.getJSON(uri).done(placeTransactions);
|
||||||
|
}
|
||||||
|
|
||||||
|
function placeTransactions(data) {
|
||||||
|
$('#transactions_holder').empty().html(data.html);
|
||||||
|
|
||||||
|
|
||||||
|
// as long as the dates are equal, changing the balance does not matter.
|
||||||
|
calculateBalanceDifference();
|
||||||
|
difference = balanceDifference;
|
||||||
|
updateDifference();
|
||||||
|
|
||||||
|
// enable the check buttons:
|
||||||
|
$('.reconcile_checkbox').prop('disabled', false).unbind('change').change(checkReconciledBox);
|
||||||
|
|
||||||
|
// show the other instruction:
|
||||||
|
$('.select_transactions_instruction').show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function startReconcile() {
|
||||||
|
|
||||||
|
reconcileStarted = true;
|
||||||
|
|
||||||
|
// hide the start button.
|
||||||
|
$('.start_reconcile').hide();
|
||||||
|
|
||||||
|
// hide the start instructions:
|
||||||
|
$('.update_balance_instruction').hide();
|
||||||
|
|
||||||
|
// hide date-change warning
|
||||||
|
$('.date_change_warning').hide();
|
||||||
|
|
||||||
|
// hide update button
|
||||||
|
$('.change_date_button').hide();
|
||||||
|
|
||||||
|
getTransactionsForRange();
|
||||||
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateDifference() {
|
||||||
|
var addClass = 'text-info';
|
||||||
|
if (difference > 0) {
|
||||||
|
addClass = 'text-success';
|
||||||
|
}
|
||||||
|
if (difference < 0) {
|
||||||
|
addClass = 'text-danger';
|
||||||
|
}
|
||||||
|
$('#difference').addClass(addClass).text(accounting.formatMoney(difference));
|
||||||
|
}
|
||||||
@@ -619,8 +619,19 @@ return [
|
|||||||
'cash_accounts' => 'Cash accounts',
|
'cash_accounts' => 'Cash accounts',
|
||||||
'Cash account' => 'Cash account',
|
'Cash account' => 'Cash account',
|
||||||
'reconcile_account' => 'Reconcile account ":account"',
|
'reconcile_account' => 'Reconcile account ":account"',
|
||||||
'end_of_reconcile_period' => 'End of reconcile period: :period',
|
'end_of_reconcile_period' => 'End of reconcile period: :period',
|
||||||
'start_of_reconcile_period' => 'Start of reconcile period: :period',
|
'start_of_reconcile_period' => 'Start of reconcile period: :period',
|
||||||
|
'start_balance' => 'Start balance',
|
||||||
|
'end_balance' => 'End balance',
|
||||||
|
'update_balance_dates_instruction' => 'Match the amounts and dates above to your bank statement, and press "Start reconciling"',
|
||||||
|
'select_transactions_instruction' => 'Select the transactions that appear on your bank statement.',
|
||||||
|
'select_range_and_balance' => 'First verify the date-range and balances. Then press "Start reconciling"',
|
||||||
|
'date_change_instruction' => 'If you change the date range now, any progress will be lost.',
|
||||||
|
'update_selection' => 'Update selection',
|
||||||
|
'store_reconcile' => 'Store reconciliation',
|
||||||
|
'reconcile_options' => 'Reconciliation options',
|
||||||
|
'reconcile_range' => 'Reconciliation range',
|
||||||
|
'start_reconcile' => 'Start reconciling',
|
||||||
'cash' => 'cash',
|
'cash' => 'cash',
|
||||||
'account_type' => 'Account type',
|
'account_type' => 'Account type',
|
||||||
'save_transactions_by_moving' => 'Save these transaction(s) by moving them to another account:',
|
'save_transactions_by_moving' => 'Save these transaction(s) by moving them to another account:',
|
||||||
|
|||||||
@@ -75,9 +75,13 @@
|
|||||||
<div class="select_transactions_instruction" style="display:none;">
|
<div class="select_transactions_instruction" style="display:none;">
|
||||||
{{ 'select_transactions_instruction'|_ }}
|
{{ 'select_transactions_instruction'|_ }}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="date_change_warning text-danger" style="display:none;">
|
||||||
|
{{ 'date_change_instruction'|_ }}
|
||||||
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="#" class="btn btn-default start_reconcile">{{ 'start_reconcile'|_ }}</a>
|
<a href="#" class="btn btn-default start_reconcile">{{ 'start_reconcile'|_ }}</a>
|
||||||
|
<a href="#" class="btn btn-default change_date_button" style="display: none;">{{ 'update_selection'|_ }}</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tfoot>
|
</tfoot>
|
||||||
@@ -124,120 +128,9 @@
|
|||||||
<h3 class="box-title">{{ 'transactions'|_ }}</h3>
|
<h3 class="box-title">{{ 'transactions'|_ }}</h3>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-body">
|
<div class="box-body">
|
||||||
<table class="table table-striped table-condensed">
|
<div id="transactions_holder">
|
||||||
<thead>
|
<p class="text-center lead">{{ 'select_range_and_balance'|_ }}</p>
|
||||||
<tr class="ignore">
|
</div>
|
||||||
<th class="hidden-xs" colspan="2"> </th>
|
|
||||||
<th>{{ trans('list.description') }}</th>
|
|
||||||
<th style="text-align:right;">{{ trans('list.amount') }}</th>
|
|
||||||
<th class="hidden-xs hidden-sm hidden-md">{{ trans('list.reconcile') }}</th>
|
|
||||||
<th class="hidden-xs hidden-sm">{{ trans('list.date') }}</th>
|
|
||||||
<th class="hidden-xs hidden-sm hidden-md">{{ trans('list.from') }}</th>
|
|
||||||
<th class="hidden-xs hidden-sm hidden-md">{{ trans('list.to') }}</th>
|
|
||||||
|
|
||||||
<th class="hidden-xs"><i class="fa fa-tasks fa-fw" title="{{ trans('list.budget') }}"></i></th>
|
|
||||||
<th class="hidden-xs"><i class="fa fa-bar-chart fa-fw" title="{{ trans('list.category') }}"></i></th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{# data for previous/next markers #}
|
|
||||||
{% set endSet = false %}
|
|
||||||
{% set startSet = false %}
|
|
||||||
{% for transaction in transactions %}
|
|
||||||
{# start marker #}
|
|
||||||
{% if transaction.date < start and startSet == false %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="5">
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td colspan="3">
|
|
||||||
<span class="label label-default">
|
|
||||||
{{ trans('firefly.start_of_reconcile_period', {period: start.formatLocalized(monthAndDayFormat) }) }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td colspan="2">
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% set startSet = true %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
{# end marker #}
|
|
||||||
{% if transaction.date <= end and endSet == false %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="5">
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td colspan="3">
|
|
||||||
<span class="label label-default">
|
|
||||||
{{ trans('firefly.end_of_reconcile_period', {period: end.formatLocalized(monthAndDayFormat) }) }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td colspan="2">
|
|
||||||
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{% set endSet = true %}
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<tr data-date="{{ transaction.date.format('Y-m-d') }}" data-id="{{ transaction.journal_id }}"
|
|
||||||
data-transaction-id="{{ transaction.id }}">
|
|
||||||
<td class="hidden-xs">
|
|
||||||
<div class="btn-group btn-group-xs">
|
|
||||||
<a href="{{ route('transactions.edit',transaction.journal_id) }}" class="btn btn-xs btn-default"><i
|
|
||||||
class="fa fa-fw fa-pencil"></i></a>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
{# icon #}
|
|
||||||
<td class="hidden-xs">
|
|
||||||
{{ transaction|transactionIcon }}
|
|
||||||
</td>
|
|
||||||
|
|
||||||
{# description #}
|
|
||||||
<td>
|
|
||||||
<a href="{{ route('transactions.show',transaction.journal_id) }}">
|
|
||||||
{{ transaction|transactionDescription }}
|
|
||||||
</a>
|
|
||||||
{# is a split journal #}
|
|
||||||
{{ transaction|transactionIsSplit }}
|
|
||||||
|
|
||||||
{# count attachments #}
|
|
||||||
{{ transaction|transactionHasAtt }}
|
|
||||||
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td style="text-align: right;"><span style="margin-right:5px;">{{ transaction|transactionAmount }}</span></td>
|
|
||||||
<td>
|
|
||||||
{% if transaction.reconciled %}
|
|
||||||
{{ transaction|transactionReconciled }}
|
|
||||||
{% else %}
|
|
||||||
<input type="checkbox" name="reconciled[]"
|
|
||||||
data-younger="{% if transaction.date > end %}true{% else %}false{% endif %}"
|
|
||||||
value="{{ transaction.transaction_amount }}" data-id="{{ transaction.id }}" disabled class="reconcile_checkbox">
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td class="hidden-sm hidden-xs">
|
|
||||||
{{ transaction.date.formatLocalized(monthAndDayFormat) }}
|
|
||||||
</td>
|
|
||||||
<td class="hidden-xs hidden-sm hidden-md">
|
|
||||||
{# all source accounts #}
|
|
||||||
{{ transaction|transactionSourceAccount }}
|
|
||||||
</td>
|
|
||||||
<td class="hidden-xs hidden-sm hidden-md">
|
|
||||||
{# all destination accounts #}
|
|
||||||
{{ transaction|transactionDestinationAccount }}
|
|
||||||
</td>
|
|
||||||
<td class="hidden-xs">
|
|
||||||
{{ transaction|transactionBudgets }}
|
|
||||||
</td>
|
|
||||||
<td class="hidden-xs">
|
|
||||||
{{ transaction|transactionCategories }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -253,6 +146,9 @@
|
|||||||
var accountID = {{ account.id }};
|
var accountID = {{ account.id }};
|
||||||
var startBalance = {{ startBalance }};
|
var startBalance = {{ startBalance }};
|
||||||
var endBalance = {{ endBalance }};
|
var endBalance = {{ endBalance }};
|
||||||
|
var transactionsUri = '{{ transactionsUri }}';
|
||||||
|
var overviewUri = '{{ overviewUri }}';
|
||||||
|
var indexUri = '{{ indexUri }}';
|
||||||
</script>
|
</script>
|
||||||
<script src="js/ff/accounts/reconcile.js?v={{ FF_VERSION }}" type="text/javascript"></script>
|
<script src="js/ff/accounts/reconcile.js?v={{ FF_VERSION }}" type="text/javascript"></script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|||||||
39
resources/views/accounts/reconcile/overview.twig
Normal file
39
resources/views/accounts/reconcile/overview.twig
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal"><span>×</span><span class="sr-only">{{ 'close'|_ }}</span>
|
||||||
|
</button>
|
||||||
|
<h4 class="modal-title">Overview of reconciliation
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form style="display: inline;" id="income" action="some route" method="POST">
|
||||||
|
<div class="modal-body">
|
||||||
|
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
|
||||||
|
<input type="hidden" name="start" value="{{ start.format('Y-m-d') }}"/>
|
||||||
|
<input type="hidden" name="end" value="{{ end.format('Y-m-d') }}"/>
|
||||||
|
|
||||||
|
You have selected X transactions. Total value X. Difference is zero
|
||||||
|
reconcile will go ahead. (so no popup actually)
|
||||||
|
|
||||||
|
Difference is X or -X. X = you have too much money in Firefly III register. -X = you are missing amount X
|
||||||
|
in your Firefly III register.
|
||||||
|
|
||||||
|
Please correct this first, or allow firefly to create a reconcilliation transaction. You can always edit or delete this later:
|
||||||
|
|
||||||
|
[X] Create a reconciling withdrawal for amount X
|
||||||
|
[X] Create a reconciling deposit for amount X.
|
||||||
|
|
||||||
|
<div class="input-group">
|
||||||
|
<div class="input-group-addon">{{ getCurrencySymbol()|raw }}</div>
|
||||||
|
<input step="any" class="form-control" id="amount" value="{{ available }}" autocomplete="off" name="amount" type="number"/>
|
||||||
|
</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">{{ 'update_amount'|_ }}</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
131
resources/views/accounts/reconcile/transactions.twig
Normal file
131
resources/views/accounts/reconcile/transactions.twig
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<table class="table table-striped table-condensed">
|
||||||
|
<thead>
|
||||||
|
<tr class="ignore">
|
||||||
|
<th class="hidden-xs" colspan="2"> </th>
|
||||||
|
<th>{{ trans('list.description') }}</th>
|
||||||
|
<th style="text-align:right;">{{ trans('list.amount') }}</th>
|
||||||
|
<th class="hidden-xs hidden-sm hidden-md">{{ trans('list.reconcile') }}</th>
|
||||||
|
<th class="hidden-xs hidden-sm">{{ trans('list.date') }}</th>
|
||||||
|
<th class="hidden-xs hidden-sm hidden-md">{{ trans('list.from') }}</th>
|
||||||
|
<th class="hidden-xs hidden-sm hidden-md">{{ trans('list.to') }}</th>
|
||||||
|
|
||||||
|
<th class="hidden-xs"><i class="fa fa-tasks fa-fw" title="{{ trans('list.budget') }}"></i></th>
|
||||||
|
<th class="hidden-xs"><i class="fa fa-bar-chart fa-fw" title="{{ trans('list.category') }}"></i></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{# data for previous/next markers #}
|
||||||
|
{% set endSet = false %}
|
||||||
|
{% set startSet = false %}
|
||||||
|
{% for transaction in transactions %}
|
||||||
|
{# start marker #}
|
||||||
|
{% if transaction.date < start and startSet == false %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5">
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td colspan="3">
|
||||||
|
<span class="label label-default">
|
||||||
|
{{ trans('firefly.start_of_reconcile_period', {period: start.formatLocalized(monthAndDayFormat) }) }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td colspan="2">
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% set startSet = true %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{# end marker #}
|
||||||
|
{% if transaction.date <= end and endSet == false %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5">
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td colspan="3">
|
||||||
|
<span class="label label-default">
|
||||||
|
{{ trans('firefly.end_of_reconcile_period', {period: end.formatLocalized(monthAndDayFormat) }) }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td colspan="2">
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% set endSet = true %}
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<tr data-date="{{ transaction.date.format('Y-m-d') }}" data-id="{{ transaction.journal_id }}"
|
||||||
|
data-transaction-id="{{ transaction.id }}">
|
||||||
|
<td class="hidden-xs">
|
||||||
|
<div class="btn-group btn-group-xs">
|
||||||
|
<a href="{{ route('transactions.edit',transaction.journal_id) }}" class="btn btn-xs btn-default"><i
|
||||||
|
class="fa fa-fw fa-pencil"></i></a>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
{# icon #}
|
||||||
|
<td class="hidden-xs">
|
||||||
|
{{ transaction|transactionIcon }}
|
||||||
|
</td>
|
||||||
|
|
||||||
|
{# description #}
|
||||||
|
<td>
|
||||||
|
<a href="{{ route('transactions.show',transaction.journal_id) }}">
|
||||||
|
{{ transaction|transactionDescription }}
|
||||||
|
</a>
|
||||||
|
{# is a split journal #}
|
||||||
|
{{ transaction|transactionIsSplit }}
|
||||||
|
|
||||||
|
{# count attachments #}
|
||||||
|
{{ transaction|transactionHasAtt }}
|
||||||
|
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td style="text-align: right;"><span style="margin-right:5px;">{{ transaction|transactionAmount }}</span></td>
|
||||||
|
<td>
|
||||||
|
{% if transaction.reconciled %}
|
||||||
|
{{ transaction|transactionReconciled }}
|
||||||
|
{% else %}
|
||||||
|
<input type="checkbox" name="reconciled[]"
|
||||||
|
data-younger="{% if transaction.date > end %}true{% else %}false{% endif %}"
|
||||||
|
value="{{ transaction.transaction_amount }}" data-id="{{ transaction.id }}" disabled class="reconcile_checkbox">
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="hidden-sm hidden-xs">
|
||||||
|
{{ transaction.date.formatLocalized(monthAndDayFormat) }}
|
||||||
|
</td>
|
||||||
|
<td class="hidden-xs hidden-sm hidden-md">
|
||||||
|
{# all source accounts #}
|
||||||
|
{{ transaction|transactionSourceAccount }}
|
||||||
|
</td>
|
||||||
|
<td class="hidden-xs hidden-sm hidden-md">
|
||||||
|
{# all destination accounts #}
|
||||||
|
{{ transaction|transactionDestinationAccount }}
|
||||||
|
</td>
|
||||||
|
<td class="hidden-xs">
|
||||||
|
{{ transaction|transactionBudgets }}
|
||||||
|
</td>
|
||||||
|
<td class="hidden-xs">
|
||||||
|
{{ transaction|transactionCategories }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
{# if the start marker has not been generated yet, do it now, at the end of the loop. #}
|
||||||
|
{% if startSet == false %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="5">
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td colspan="3">
|
||||||
|
<span class="label label-default">
|
||||||
|
{{ trans('firefly.start_of_reconcile_period', {period: start.formatLocalized(monthAndDayFormat) }) }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td colspan="2">
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% set startSet = true %}
|
||||||
|
{% endif %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
{# description #}
|
{# description #}
|
||||||
<td>
|
<td>
|
||||||
{# count attachments #}
|
{# is reconciled? #}
|
||||||
{{ transaction|transactionReconciled }}
|
{{ transaction|transactionReconciled }}
|
||||||
|
|
||||||
<a href="{{ route('transactions.show',transaction.journal_id) }}">
|
<a href="{{ route('transactions.show',transaction.journal_id) }}">
|
||||||
|
|||||||
@@ -90,10 +90,15 @@ Route::group(
|
|||||||
Route::get('{what}', ['uses' => 'AccountController@index', 'as' => 'index'])->where('what', 'revenue|asset|expense');
|
Route::get('{what}', ['uses' => 'AccountController@index', 'as' => 'index'])->where('what', 'revenue|asset|expense');
|
||||||
Route::get('create/{what}', ['uses' => 'AccountController@create', 'as' => 'create'])->where('what', 'revenue|asset|expense');
|
Route::get('create/{what}', ['uses' => 'AccountController@create', 'as' => 'create'])->where('what', 'revenue|asset|expense');
|
||||||
Route::get('edit/{account}', ['uses' => 'AccountController@edit', 'as' => 'edit']);
|
Route::get('edit/{account}', ['uses' => 'AccountController@edit', 'as' => 'edit']);
|
||||||
Route::get('reconcile/{account}/{start_date?}/{end_date?}', ['uses' => 'AccountController@reconcile', 'as' => 'reconcile']);
|
|
||||||
Route::get('delete/{account}', ['uses' => 'AccountController@delete', 'as' => 'delete']);
|
Route::get('delete/{account}', ['uses' => 'AccountController@delete', 'as' => 'delete']);
|
||||||
Route::get('show/{account}/{moment?}', ['uses' => 'AccountController@show', 'as' => 'show']);
|
Route::get('show/{account}/{moment?}', ['uses' => 'AccountController@show', 'as' => 'show']);
|
||||||
|
|
||||||
|
// reconcile routes:
|
||||||
|
Route::get('reconcile/{account}/index/{start_date?}/{end_date?}', ['uses' => 'Account\ReconcileController@reconcile', 'as' => 'reconcile']);
|
||||||
|
Route::get('reconcile/{account}/transactions/{start_date?}/{end_date?}', ['uses' => 'Account\ReconcileController@transactions', 'as' => 'reconcile.transactions']);
|
||||||
|
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']);
|
||||||
|
|
||||||
Route::post('store', ['uses' => 'AccountController@store', 'as' => 'store']);
|
Route::post('store', ['uses' => 'AccountController@store', 'as' => 'store']);
|
||||||
Route::post('update/{account}', ['uses' => 'AccountController@update', 'as' => 'update']);
|
Route::post('update/{account}', ['uses' => 'AccountController@update', 'as' => 'update']);
|
||||||
Route::post('destroy/{account}', ['uses' => 'AccountController@destroy', 'as' => 'destroy']);
|
Route::post('destroy/{account}', ['uses' => 'AccountController@destroy', 'as' => 'destroy']);
|
||||||
|
|||||||
Reference in New Issue
Block a user