Allow user to set multi-currency available budget. WIP

This commit is contained in:
James Cole
2019-08-31 21:47:55 +02:00
parent ca777857c2
commit 509276e20b
10 changed files with 434 additions and 164 deletions

View File

@@ -0,0 +1,124 @@
<?php
/**
* BudgetLimitController.php
* Copyright (c) 2019 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\Budget;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Repositories\Budget\AvailableBudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
/**
*
* Class BudgetLimitController
*/
class BudgetLimitController extends Controller
{
/** @var AvailableBudgetRepositoryInterface */
private $abRepository;
/** @var BudgetLimitRepositoryInterface */
private $blRepository;
/** @var CurrencyRepositoryInterface */
private $currencyRepos;
/** @var OperationsRepositoryInterface */
private $opsRepository;
/** @var BudgetRepositoryInterface The budget repository */
private $repository;
/**
* AmountController constructor.
*/
public function __construct()
{
parent::__construct();
$this->middleware(
function ($request, $next) {
app('view')->share('title', (string)trans('firefly.budgets'));
app('view')->share('mainTitleIcon', 'fa-tasks');
$this->repository = app(BudgetRepositoryInterface::class);
$this->opsRepository = app(OperationsRepositoryInterface::class);
$this->abRepository = app(AvailableBudgetRepositoryInterface::class);
$this->blRepository = app(BudgetLimitRepositoryInterface::class);
$this->currencyRepos = app(CurrencyRepositoryInterface::class);
return $next($request);
}
);
}
/**
* @param Request $request
* @param BudgetLimit $budgetLimit
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function delete(Request $request, BudgetLimit $budgetLimit)
{
$this->blRepository->destroyBudgetLimit($budgetLimit);
session()->flash('success', trans('firefly.deleted_bl'));
return redirect(route('budgets.index'));
}
/**
* @param Request $request
*
* @return JsonResponse
*/
public function store(Request $request): JsonResponse
{
$limit = $this->blRepository->store(
[
'budget_id' => $request->get('budget_id'),
'transaction_currency_id' => $request->get('transaction_currency_id'),
'start_date' => $request->get('start'),
'end_date' => $request->get('end'),
'amount' => $request->get('amount'),
]
);
return response()->json($limit->toArray());
}
/**
* @param Request $request
* @param BudgetLimit $budgetLimit
*
* @return JsonResponse
*/
public function update(Request $request, BudgetLimit $budgetLimit): JsonResponse
{
$amount = $request->get('amount');
return response()->json($this->blRepository->update($budgetLimit, ['amount' => $amount])->toArray());
}
}

View File

@@ -27,6 +27,8 @@ namespace FireflyIII\Http\Controllers\Budget;
use Carbon\Carbon;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Budget\AvailableBudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
@@ -36,7 +38,7 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Http\Controllers\DateCalculation;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Collection;
use Log;
/**
@@ -101,17 +103,15 @@ class IndexController extends Controller
$range = app('preferences')->get('viewRange', '1M')->data;
$start = $start ?? session('start', Carbon::now()->startOfMonth());
$end = $end ?? app('navigation')->endOfPeriod($start, $range);
$page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page');
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$defaultCurrency = app('amount')->getDefaultCurrency();
$budgeted = '0';
$spent = '0';
// new period stuff:
$periodTitle = app('navigation')->periodShow($start, $range);
// loop of previous periods:
$prevLoop = $this->getPreviousPeriods($start, $range);
$nextLoop = $this->getNextPeriods($start, $range);
$prevLoop = $this->getPreviousPeriods($start, $range);
$nextLoop = $this->getNextPeriods($start, $range);
// get all available budgets.
$ab = $this->abRepository->get($start, $end);
@@ -152,17 +152,46 @@ class IndexController extends Controller
// get all budgets, and paginate them into $budgets.
$collection = $this->repository->getActiveBudgets();
$total = $collection->count();
$budgets = $collection->slice(($page - 1) * $pageSize, $pageSize);
$budgets = [];
// complement budget with budget limits in range, and expenses in currency X in range.
/** @var Budget $current */
foreach ($collection as $current) {
$array = $current->toArray();
$array['spent'] = [];
$array['budgeted'] = [];
$budgetLimits = $this->blRepository->getBudgetLimits($current, $start, $end);
/** @var BudgetLimit $limit */
foreach ($budgetLimits as $limit) {
$array['budgeted'][] = [
'id' => $limit->id,
'amount' => $limit->amount,
'currency_id' => $limit->transactionCurrency->id,
'currency_symbol' => $limit->transactionCurrency->symbol,
'currency_name' => $limit->transactionCurrency->name,
'currency_decimal_places' => $limit->transactionCurrency->decimal_places,
];
}
/** @var TransactionCurrency $currency */
foreach ($currencies as $currency) {
$spentArr = $this->opsRepository->sumExpenses($start, $end, null, new Collection([$current]), $currency);
if (isset($spentArr[$currency->id]['sum'])) {
$array['spent'][$currency->id]['spent'] = $spentArr[$currency->id]['sum'];
$array['spent'][$currency->id]['currency_id'] = $currency->id;
$array['spent'][$currency->id]['currency_symbol'] = $currency->symbol;
$array['spent'][$currency->id]['currency_decimal_places'] = $currency->decimal_places;
}
}
$budgets[] = $array;
}
// get all inactive budgets, and simply list them:
$inactive = $this->repository->getInactiveBudgets();
// paginate budgets
$paginator = new LengthAwarePaginator($budgets, $total, $pageSize, $page);
$paginator->setPath(route('budgets.index'));
return view(
'budgets.index', compact(
'availableBudgets',
@@ -171,11 +200,12 @@ class IndexController extends Controller
//'prevText', 'previousLoop', 'nextLoop',
'budgeted', 'spent',
'prevLoop', 'nextLoop',
'paginator',
'budgets',
'currencies',
'enableAddButton',
'periodTitle',
'defaultCurrency',
'page', 'activeDaysPassed', 'activeDaysLeft',
'activeDaysPassed', 'activeDaysLeft',
'inactive', 'budgets', 'start', 'end'
)
);

View File

@@ -70,7 +70,7 @@ class BudgetLimit extends Model
];
/** @var array Fields that can be filled */
protected $fillable = ['budget_id', 'start_date', 'end_date', 'amount'];
protected $fillable = ['budget_id', 'start_date', 'end_date', 'amount','transaction_currency_id'];
/**
* Route binder. Converts the key in the URL to the specified object (or throw 404).

View File

@@ -40,7 +40,6 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
* @property Carbon $created_at
* @property Carbon $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at
* @property string $name
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\BudgetLimit[] $budgetLimits
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\TransactionJournal[] $transactionJournals
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Transaction[] $transactions

View File

@@ -227,14 +227,14 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
$q1->where(
static function (Builder $q2) use ($start, $end) {
$q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d 00:00:00'));
$q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d 00:00:00'));
$q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d 23:59:59'));
}
)
// budget limit start within period
->orWhere(
static function (Builder $q3) use ($start, $end) {
$q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d 00:00:00'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 00:00:00'));
$q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 23:59:59'));
}
);
}
@@ -242,7 +242,7 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
->orWhere(
static function (Builder $q4) use ($start, $end) {
// or start is before start AND end is after end.
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d 00:00:00'));
$q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d 23:59:59'));
$q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d 00:00:00'));
}
);
@@ -265,6 +265,17 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
*
* @return BudgetLimit
*/
public function store(array $data): BudgetLimit
{
return BudgetLimit::create($data);
}
/**
* @param array $data
*
* @return BudgetLimit
* @deprecated
*/
public function storeBudgetLimit(array $data): BudgetLimit
{
/** @var Budget $budget */
@@ -303,12 +314,27 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
return $limit;
}
/**
* @param BudgetLimit $budgetLimit
* @param array $data
*
* @return BudgetLimit
*/
public function update(BudgetLimit $budgetLimit, array $data): BudgetLimit
{
$budgetLimit->amount = $data['amount'] ?? $budgetLimit->amount;
$budgetLimit->save();
return $budgetLimit;
}
/**
* @param BudgetLimit $budgetLimit
* @param array $data
*
* @return BudgetLimit
* @throws Exception
* @deprecated
*/
public function updateBudgetLimit(BudgetLimit $budgetLimit, array $data): BudgetLimit
{

View File

@@ -35,6 +35,19 @@ use Illuminate\Support\Collection;
*/
interface BudgetLimitRepositoryInterface
{
/**
* Tells you which amount has been budgeted (for the given budgets)
* in the selected query. Returns a positive amount as a string.
*
* @param Carbon $start
* @param Carbon $end
* @param TransactionCurrency $currency
* @param Collection|null $budgets
*
* @return string
*/
public function budgeted(Carbon $start, Carbon $end, TransactionCurrency $currency, ?Collection $budgets = null): string;
/**
* Destroy a budget limit.
*
@@ -80,20 +93,15 @@ interface BudgetLimitRepositoryInterface
*
* @return BudgetLimit
*/
public function storeBudgetLimit(array $data): BudgetLimit;
public function store(array $data): BudgetLimit;
/**
* Tells you which amount has been budgeted (for the given budgets)
* in the selected query. Returns a positive amount as a string.
* @param array $data
*
* @param Carbon $start
* @param Carbon $end
* @param TransactionCurrency $currency
* @param Collection|null $budgets
*
* @return string
* @return BudgetLimit
* @deprecated
*/
public function budgeted(Carbon $start, Carbon $end, TransactionCurrency $currency, ?Collection $budgets = null): string;
public function storeBudgetLimit(array $data): BudgetLimit;
/**
* @param BudgetLimit $budgetLimit
@@ -101,6 +109,15 @@ interface BudgetLimitRepositoryInterface
*
* @return BudgetLimit
*/
public function update(BudgetLimit $budgetLimit, array $data): BudgetLimit;
/**
* @param BudgetLimit $budgetLimit
* @param array $data
*
* @return BudgetLimit
* @deprecated
*/
public function updateBudgetLimit(BudgetLimit $budgetLimit, array $data): BudgetLimit;
/**

View File

@@ -30,10 +30,15 @@ $(function () {
On start, fill the "spent"-bar using the content from the page.
*/
//drawSpentBar();
drawSpentBars();
//drawBudgetedBar();
$('.update_ab').on('click', updateAvailableBudget)
$('.create_ab_alt').on('click', createAltAvailableBudget)
drawBudgetedBars();
$('.update_ab').on('click', updateAvailableBudget);
$('.create_ab_alt').on('click', createAltAvailableBudget);
$('.budget_amount').on('change', updateBudgetedAmount);
/*
When the input changes, update the percentages for the budgeted bar:
@@ -78,6 +83,38 @@ $(function () {
}
});
function updateBudgetedAmount(e) {
var input = $(e.currentTarget);
var budgetId = parseInt(input.data('id'));
var budgetLimitId = parseInt(input.data('limit'));
var currencyId = parseInt(input.data('currency'));
console.log(budgetLimitId);
if (0 === budgetLimitId) {
$.post(createBudgetLimitUri, {
_token: token,
budget_id: budgetId,
transaction_currency_id: currencyId,
amount: input.val(),
start: periodStart,
end: periodEnd
}).done(function (data) {
alert('done!');
}).fail(function () {
alert('I failed :(');
});
} else {
$.post(updateBudgetLimitUri.replace('REPLACEME', budgetLimitId), {
_token: token,
amount: input.val(),
}).done(function (data) {
alert('done!');
}).fail(function () {
alert('I failed :(');
});
}
}
var fixHelper = function (e, tr) {
"use strict";
var $originals = tr.children();
@@ -110,13 +147,15 @@ function sortStop(event, ui) {
};
$.post('budgets/reorder', arr);
}
function createAltAvailableBudget(e) {
var button = $(e.currentTarget);
$('#defaultModal').empty().load(createAltAvailableBudgetUri, function () {
$('#defaultModal').modal('show');
});
$('#defaultModal').empty().load(createAltAvailableBudgetUri, function () {
$('#defaultModal').modal('show');
});
return false;
}
function updateAvailableBudget(e) {
var button = $(e.currentTarget);
var abId = parseInt(button.data('id'));
@@ -135,6 +174,55 @@ function updateAvailableBudget(e) {
}
function drawBudgetedBars() {
"use strict";
$.each($('.budgeted_bar'), function (i, v) {
var bar = $(v);
var budgeted = parseFloat(bar.data('budgeted'));
var available = parseFloat(bar.data('available'));
console.log('Budgeted bar for bar ' + bar.data('id'));
var budgetedTooMuch = budgeted > available;
var pct;
if (budgetedTooMuch) {
console.log('over budget');
// budgeted too much.
pct = (available / budgeted) * 100;
bar.find('.progress-bar-warning').css('width', pct + '%');
bar.find('.progress-bar-danger').css('width', (100 - pct) + '%');
bar.find('.progress-bar-info').css('width', 0);
} else {
console.log('under budget');
pct = (budgeted / available) * 100;
bar.find('.progress-bar-warning').css('width', 0);
bar.find('.progress-bar-danger').css('width', 0);
bar.find('.progress-bar-info').css('width', pct + '%');
}
//$('#budgetedAmount').html(currencySymbol + ' ' + budgeted.toFixed(2));
});
}
function drawSpentBars() {
"use strict";
$.each($('.spent_bar'), function (i, v) {
var bar = $(v);
var spent = parseFloat(bar.data('spent')) * -1;
var budgeted = parseFloat(bar.data('budgeted'));
var overspent = spent > budgeted;
var pct;
if (overspent) {
// draw overspent bar
pct = (budgeted / spent) * 100;
bar.find('.progress-bar-warning').css('width', pct + '%');
bar.find('.progress-bar-danger').css('width', (100 - pct) + '%');
} else {
// draw normal bar:
pct = (spent / budgeted) * 100;
bar.find('.progress-bar-info').css('width', pct + '%');
}
});
}
//
//
// function drawSpentBar() {

View File

@@ -688,7 +688,9 @@ return [
'set_ab' => 'The available budget amount has been set',
'updated_ab' => 'The available budget amount has been updated',
'deleted_ab' => 'The available budget amount has been deleted',
'deleted_bl' => 'The budgeted amount has been removed',
'alt_currency_ab_create' => 'Set the available budget in another currency',
'bl_create_btn' => 'Set budget in another currency',
'inactiveBudgets' => 'Inactive budgets',
'without_budget_between' => 'Transactions without a budget between :start and :end',
'delete_budget' => 'Delete budget ":name"',

View File

@@ -145,7 +145,8 @@
{# progresss bar to visualise available vs budgeted. #}
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="progress budgetedBar" data-id="{{ budget.id }}">
<div class="progress budgeted_bar" data-id="{{ budget.id }}" data-budgeted="{{ budget.budgeted }}"
data-available="{{ budget.amount }}">
<div class="progress-bar progress-bar-danger" data-id="{{ budget.id }}" role="progressbar" aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100" style="width: 0;"></div>
@@ -169,7 +170,8 @@
{# bar to visualise spending in budget .#}
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="progress spentBar" data-id="{{ budget.id }}">
<div class="progress spent_bar" data-id="{{ budget.id }}" data-budgeted="{{ budget.budgeted }}"
data-spent="{{ budget.spent }}">
<div class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="0" aria-valuemin="0"
aria-valuemax="100"
style="width: 0;"></div>
@@ -195,63 +197,12 @@
{% endif %}
</div>
{% endif %}
{#
<div class="row">
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-3">
<small>{{ 'budgeted'|_ }}: <span id="budgetedAmount"
class="text-success">{{ "-1"|formatAmountPlain }}{{ budgeted|formatAmountPlain }}</span></small>
</div>
<div class="col-lg-9 col-md-9 col-sm-9 col-xs-9" style="text-align:right;margin-bottom:3px;">
<small id="availableBar">{{ trans('firefly.available_between',{start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat) }) }}
:
<span id="available" data-value="{{ available }}">{{ "-1"|formatAmountPlain }}{{ available|formatAmountPlain }}</span>
<a href="#" class="updateIncome btn btn-default btn-xs"><i class="fa fa-pencil"></i></a>
</small>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="progress budgetedBar">
<div class="progress-bar progress-bar-danger" id="progress-bar-danger" role="progressbar" aria-valuenow="0" aria-valuemin="0"
aria-valuemax="100" style="width: 0;"></div>
<div class="progress-bar progress-bar-warning" id="progress-bar-warning" role="progressbar" aria-valuenow="10" aria-valuemin="0"
aria-valuemax="100" style="width: 0;"></div>
<div class="progress-bar progress-bar-info" id="progress-bar-default" role="progressbar" aria-valuenow="0" aria-valuemin="0"
aria-valuemax="100" style="width: 0;"></div>
</div>
</div>
</div>
<div class="row" id="spentBar">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<small>{{ trans('firefly.spent_between', {start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat)}) }}
: {{ "-1"|formatAmount }}{{ spent|formatAmount }}</small>
</div>
</div>
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="progress spentBar">
<div class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"
style="width: 0;"></div>
<div class="progress-bar progress-bar-danger" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"
style="width: 0;"></div>
<div class="progress-bar progress-bar-info" role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"
style="width: 0;"></div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>#}
{% if paginator.count == 0 and inactive.count == 0 and page == 1 %}
{% if budgets.count == 0 and inactive.count == 0 %}
{% include 'partials.empty' with {objectType: 'default', type: 'budgets',route: route('budgets.create')} %}
{# make FF ignore demo for now. #}
{% set shownDemo = true %}
{% endif %}
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="box">
@@ -262,9 +213,6 @@
<div style="padding:8px;">
<a href="{{ route('budgets.create') }}" class="btn btn-success"><i class="fa fa-plus fa-fw"></i> {{ 'createBudget'|_ }}</a>
</div>
<div style="padding-left:8px;">
{{ paginator.render|raw }}
</div>
<table class="table table-bordered sortable-table table-striped sortable" id="budgetList">
<thead>
<tr>
@@ -276,64 +224,93 @@
</tr>
</thead>
<tbody>
{% for budget in paginator %}
{% for budget in budgets %}
<tr data-id="{{ budget.id }}">
<td class="hidden-sm hidden-xs">
<div class="btn-group btn-group-xs">
<a href="#" class="handle btn btn-default"><i class="fa fa-fw fa-arrows-v"></i></a>
<a href="{{ route('budgets.edit',budget.id) }}" class="btn btn-xs btn-default"><i class="fa fa-fw fa-pencil"></i></a>
<a href="{{ route('budgets.delete',budget.id) }}" class="btn btn-xs btn-danger"><i class="fa fa-fw fa-trash-o"></i></a>
<a href="{{ route('budgets.edit', budget.id) }}" class="btn btn-xs btn-default"><i class="fa fa-fw fa-pencil"></i></a>
<a href="{{ route('budgets.delete', budget.id) }}" class="btn btn-xs btn-danger"><i class="fa fa-fw fa-trash-o"></i></a>
</div>
</td>
<td data-value="{{ budget.name }}">
{% if budgetInformation[budget.id]['currentLimit'] %}
<a href="{{ route('budgets.show.limit', [budget.id, budgetInformation[budget.id]['currentLimit'].id]) }}"
class="budget-link"
data-id="{{ budget.id }}">{{ budget.name }}</a>
{% else %}
<a href="{{ route('budgets.show',budget.id) }}" class="budget-link" data-id="{{ budget.id }}">{{ budget.name }}</a>
<a href="{{ route('budgets.show',budget.id) }}" data-id="{{ budget.id }}">{{ budget.name }}</a>
</td>
<td data-value="-1">
{% if 0==budget.budgeted|length %}
<div class="input-group">
<div class="input-group-addon">{{ defaultCurrency.symbol }}</div>
<input type="hidden" name="balance_currency_id" value="{{ defaultCurrency.id }}"/>
<input class="form-control budget_amount" data-original="0" data-id="{{ budget.id }}"
data-currency="{{ defaultCurrency.id }}" data-limit="0" value="0" autocomplete="off" min="0" name="amount"
type="number">
</div>
<span class="text-danger budget_warning" data-id="{{ budget.id }}" data-budgetLimit="{{ budgetLimit.id }}"
style="display:none;"></span>
{% endif %}
{% if budget.budgeted|length > 0 %}
{% for budgetLimit in budget.budgeted %}
<div class="input-group">
<div class="input-group-addon">{{ budgetLimit.currency_symbol }}</div>
<input class="form-control budget_amount" data-original="{{ budgetLimit.amount }}"
data-id="{{ budget.id }}" data-limit="{{ budgetLimit.id }}" value="{{ budgetLimit.amount }}"
autocomplete="off"
min="0" name="amount" type="number">
<div class="input-group-btn">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true"
aria-expanded="false"><span class="caret"></span></button>
<ul class="dropdown-menu">
<li><a href="{{ route('budget-limits.delete', [budgetLimit.id]) }}">Remove budgeted amount
in {{ budgetLimit.currency_name }}</a></li>
</ul>
</div>
</div>
<span class="text-danger budget_warning" data-id="{{ budget.id }}" data-budgetLimit="{{ budgetLimit.id }}"
style="display:none;"></span>
{% endfor %}
{% endif %}
{% if budget.budgeted|length < currencies.count %}
<a href="#" class="btn btn-success btn-xs create_ab_alt">
<i class="fa fa-plus-circle"></i>
{{ 'bl_create_btn'|_ }}</a>
{% endif %}
</td>
{% if budgetInformation[budget.id]['currentLimit'] %}
{% set repAmount = budgetInformation[budget.id]['budgeted'] %}
{% else %}
{% set repAmount = '0' %}
{% endif %}
<td class="hidden-sm hidden-xs spent" data-id="{{ budget.id }}">
{% for spentInfo in budget.spent %}
{{ formatAmountBySymbol(spentInfo.spent, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }}
({{ formatAmountBySymbol(spentInfo.spent / activeDaysPassed, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{% endfor %}
<td data-value="{{ repAmount }}">
<div class="input-group">
<div class="input-group-addon">{{ defaultCurrency.symbol|raw }}</div>
<input type="hidden" name="balance_currency_id" value="{{ defaultCurrency.id }}"/>
<input class="form-control budgetAmount" data-original="{{ repAmount }}"
data-id="{{ budget.id }}" value="{{ repAmount }}" autocomplete="off"
min="0" name="amount" type="number">
</div>
<span class="text-danger budget_warning" data-id="{{ budget.id }}" style="display:none;"></span>
</td>
<td class="hidden-sm hidden-xs spent" data-id="{{ budget.id }}" data-spent="{{ budgetInformation[budget.id]['spent'] }}"
data-value="{{ budgetInformation[budget.id]['spent'] }}">
{{ "-1"|formatAmount }}
{#{{ budgetInformation[budget.id]['spent']|formatAmount }}#}
({{ "-1"|formatAmount }})
{#({{ (budgetInformation[budget.id]['spent'] / activeDaysPassed)|formatAmount }})#}
</td>
<td class="left" data-id="{{ budget.id }}"
data-value="{{ (repAmount + budgetInformation[budget.id]['spent']) }}">
{{ "-1"|formatAmount }}
<td class="left" data-id="{{ budget.id }}">
<!-- only makes sense to list what's left for budgeted amounts.-->
{% if budget.budgeted|length > 0 %}
{% for budgetLimit in budget.budgeted %}
{% for spentInfo in budget.spent %}
{% if spentInfo.currency_id == budgetLimit.currency_id %}
{{ formatAmountBySymbol(spentInfo.spent + budgetLimit.amount, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }}
{% if spentInfo.spent + budgetLimit.amount > 0 %}
({{ formatAmountBySymbol((spentInfo.spent + budgetLimit.amount) / activeDaysLeft, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{% else %}
({{ formatAmountBySymbol(0, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{% endif %}
{% endif %}
{% endfor %}
{% endfor %}
{% endif %}
{#{{ "-1"|formatAmount }}#}
{#{{ (repAmount + budgetInformation[budget.id]['spent'])|formatAmount }}#}
{% if repAmount + budgetInformation[budget.id]['spent'] > 0 %}
({{ "-1"|formatAmount }})
{#({{ ((repAmount + budgetInformation[budget.id]['spent']) / activeDaysLeft)|formatAmount }})#}
{% endif %}
{#{% if repAmount + budgetInformation[budget.id]['spent'] > 0 %}#}
{#({{ "-1"|formatAmount }})#}
{#({{ ((repAmount + budgetInformation[budget.id]['spent']) / activeDaysLeft)|formatAmount }})#}
{#{% endif %}#}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<div style="padding-left:8px;">
{{ paginator.render|raw }}
</div>
</div>
<div class="box-footer">
<a href="{{ route('budgets.create') }}" class="btn btn-success"><i class="fa fa-plus fa-fw"></i> {{ 'createBudget'|_ }}</a>
@@ -399,16 +376,14 @@
{% block scripts %}
<script src="v1/js/lib/jquery-ui.min.js?v={{ FF_VERSION }}" type="text/javascript"></script>
<script type="text/javascript">
// actually spent bar data:
var spent = 0;
var page = {{ page }};
// budgeted data:
var budgeted = 0;
var available = 0;
var budgetIndexUri = "{{ route('budgets.index',['START','END']) }}";
var createAvailableBudgetUri = "{{ route('available-budgets.create', [start.format('Y-m-d'), end.format('Y-m-d')]) }}";
var createAltAvailableBudgetUri = "{{ route('available-budgets.create-alternative', [start.format('Y-m-d'), end.format('Y-m-d')]) }}";
var editAvailableBudgetUri = "{{ route('available-budgets.edit', ['REPLACEME']) }}";
var createBudgetLimitUri = "{{ route('budget-limits.store') }}";
var updateBudgetLimitUri = "{{ route('budget-limits.update', ['REPLACEME']) }}";
{#var budgetAmountUri = "{{ route('budgets.amount','REPLACE') }}";#}
{#var updateIncomeUri = "{{ route('budgets.income',[start.format('Y-m-d'),end.format('Y-m-d')]) }}?page={{ page }}";#}
var periodStart = "{{ start.format('Y-m-d') }}";

View File

@@ -117,7 +117,9 @@ Route::group(
Route::get('{objectType}', ['uses' => 'Account\IndexController@index', 'as' => 'index'])->where('objectType', 'revenue|asset|expense|liabilities');
// create
Route::get('create/{objectType}', ['uses' => 'Account\CreateController@create', 'as' => 'create'])->where('objectType', 'revenue|asset|expense|liabilities');
Route::get('create/{objectType}', ['uses' => 'Account\CreateController@create', 'as' => 'create'])->where(
'objectType', 'revenue|asset|expense|liabilities'
);
Route::post('store', ['uses' => 'Account\CreateController@store', 'as' => 'store']);
@@ -230,32 +232,36 @@ Route::group(
* Available Budget Controller
*/
Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'available-budgets', 'as' => 'available-budgets.'], function () {
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'available-budgets', 'as' => 'available-budgets.'],
function () {
// delete
//Route::get('delete/{budget}', ['uses' => 'Budget\DeleteController@delete', 'as' => 'delete']);
//Route::post('destroy/{budget}', ['uses' => 'Budget\DeleteController@destroy', 'as' => 'destroy']);
// create
Route::get('create/{start_date}/{end_date}/{currency?}', ['uses' => 'Budget\AvailableBudgetController@create', 'as' => 'create']);
Route::get(
'create-alternative/{start_date}/{end_date}', ['uses' => 'Budget\AvailableBudgetController@createAlternative', 'as' => 'create-alternative']
);
Route::post('store', ['uses' => 'Budget\AvailableBudgetController@store', 'as' => 'store']);
// create
Route::get('create/{start_date}/{end_date}/{currency?}', ['uses' => 'Budget\AvailableBudgetController@create', 'as' => 'create']);
Route::get('create-alternative/{start_date}/{end_date}', ['uses' => 'Budget\AvailableBudgetController@createAlternative', 'as' => 'create-alternative']);
Route::post('store', ['uses' => 'Budget\AvailableBudgetController@store', 'as' => 'store']);
//Route::post('store', ['uses' => 'Budget\CreateController@store', 'as' => 'store']);
// edit
Route::get('edit/{availableBudget}', ['uses' => 'Budget\AvailableBudgetController@edit', 'as' => 'edit']);
Route::post('update/{availableBudget}', ['uses' => 'Budget\AvailableBudgetController@update', 'as' => 'update']);
// edit
Route::get('edit/{availableBudget}', ['uses' => 'Budget\AvailableBudgetController@edit', 'as' => 'edit']);
Route::post('update/{availableBudget}', ['uses' => 'Budget\AvailableBudgetController@update', 'as' => 'update']);
Route::get('delete/{availableBudget}', ['uses' => 'Budget\AvailableBudgetController@delete', 'as' => 'delete']);
}
);
Route::get('delete/{availableBudget}', ['uses' => 'Budget\AvailableBudgetController@delete', 'as' => 'delete']);
//Route::get('edit/{budget}', ['uses' => 'Budget\EditController@edit', 'as' => 'edit']);
//Route::post('update/{budget}', ['uses' => 'Budget\EditController@update', 'as' => 'update']);
// show
//Route::get('show/{budget}', ['uses' => 'Budget\ShowController@show', 'as' => 'show']);
//Route::get('show/{budget}/{budgetLimit}', ['uses' => 'Budget\ShowController@showByBudgetLimit', 'as' => 'show.limit']);
//Route::get('list/no-budget/all', ['uses' => 'Budget\ShowController@noBudgetAll', 'as' => 'no-budget-all']);
//Route::get('list/no-budget/{start_date?}/{end_date?}', ['uses' => 'Budget\ShowController@noBudget', 'as' => 'no-budget']);
}
/**
* Budget Limit Controller
*/
Route::group(
['middleware' => 'user-full-auth', 'namespace' => 'FireflyIII\Http\Controllers', 'prefix' => 'budget-limits', 'as' => 'budget-limits.'],
static function () {
Route::get('delete/{budgetLimit}', ['uses' => 'Budget\BudgetLimitController@delete', 'as' => 'delete']);
Route::post('store', ['uses' => 'Budget\BudgetLimitController@store', 'as' => 'store']);
Route::post('update/{budgetLimit}', ['uses' => 'Budget\BudgetLimitController@update', 'as' => 'update']);
}
);
/**
@@ -389,7 +395,9 @@ Route::group(
Route::get('period/{category}', ['uses' => 'CategoryController@currentPeriod', 'as' => 'current']);
Route::get('period/{category}/{date}', ['uses' => 'CategoryController@specificPeriod', 'as' => 'specific']);
Route::get('all/{category}', ['uses' => 'CategoryController@all', 'as' => 'all']);
Route::get('report-period/0/{accountList}/{start_date}/{end_date}', ['uses' => 'CategoryController@reportPeriodNoCategory', 'as' => 'period.no-category']);
Route::get(
'report-period/0/{accountList}/{start_date}/{end_date}', ['uses' => 'CategoryController@reportPeriodNoCategory', 'as' => 'period.no-category']
);
Route::get('report-period/{category}/{accountList}/{start_date}/{end_date}', ['uses' => 'CategoryController@reportPeriod', 'as' => 'period']);
// these charts are used in reports (category reports):
@@ -574,7 +582,6 @@ Route::group(
Route::get('currency-names', ['uses' => 'Json\AutoCompleteController@currencyNames', 'as' => 'autocomplete.currency-names']);
Route::get('transaction-types', ['uses' => 'Json\AutoCompleteController@transactionTypes', 'as' => 'transaction-types']);
// boxes
@@ -905,7 +912,9 @@ Route::group(
// show groups:
// TODO improve these routes
Route::get('{what}/all', ['uses' => 'Transaction\IndexController@indexAll', 'as' => 'index.all'])->where(['what' => 'withdrawal|deposit|transfers|transfer']);
Route::get('{what}/all', ['uses' => 'Transaction\IndexController@indexAll', 'as' => 'index.all'])->where(
['what' => 'withdrawal|deposit|transfers|transfer']
);
Route::get('{what}/{start_date?}/{end_date?}', ['uses' => 'Transaction\IndexController@index', 'as' => 'index'])->where(
['what' => 'withdrawal|deposit|transfers|transfer']