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 Carbon\Carbon;
use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\AvailableBudget; use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Budget\AvailableBudgetRepositoryInterface; use FireflyIII\Repositories\Budget\AvailableBudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
@@ -36,7 +38,7 @@ use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
use FireflyIII\Support\Http\Controllers\DateCalculation; use FireflyIII\Support\Http\Controllers\DateCalculation;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Pagination\LengthAwarePaginator; use Illuminate\Support\Collection;
use Log; use Log;
/** /**
@@ -101,17 +103,15 @@ class IndexController extends Controller
$range = app('preferences')->get('viewRange', '1M')->data; $range = app('preferences')->get('viewRange', '1M')->data;
$start = $start ?? session('start', Carbon::now()->startOfMonth()); $start = $start ?? session('start', Carbon::now()->startOfMonth());
$end = $end ?? app('navigation')->endOfPeriod($start, $range); $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(); $defaultCurrency = app('amount')->getDefaultCurrency();
$budgeted = '0'; $budgeted = '0';
$spent = '0'; $spent = '0';
// new period stuff: // new period stuff:
$periodTitle = app('navigation')->periodShow($start, $range); $periodTitle = app('navigation')->periodShow($start, $range);
$prevLoop = $this->getPreviousPeriods($start, $range);
// loop of previous periods: $nextLoop = $this->getNextPeriods($start, $range);
$prevLoop = $this->getPreviousPeriods($start, $range);
$nextLoop = $this->getNextPeriods($start, $range);
// get all available budgets. // get all available budgets.
$ab = $this->abRepository->get($start, $end); $ab = $this->abRepository->get($start, $end);
@@ -152,17 +152,46 @@ class IndexController extends Controller
// get all budgets, and paginate them into $budgets. // get all budgets, and paginate them into $budgets.
$collection = $this->repository->getActiveBudgets(); $collection = $this->repository->getActiveBudgets();
$total = $collection->count(); $budgets = [];
$budgets = $collection->slice(($page - 1) * $pageSize, $pageSize);
// 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: // get all inactive budgets, and simply list them:
$inactive = $this->repository->getInactiveBudgets(); $inactive = $this->repository->getInactiveBudgets();
// paginate budgets
$paginator = new LengthAwarePaginator($budgets, $total, $pageSize, $page);
$paginator->setPath(route('budgets.index'));
return view( return view(
'budgets.index', compact( 'budgets.index', compact(
'availableBudgets', 'availableBudgets',
@@ -171,11 +200,12 @@ class IndexController extends Controller
//'prevText', 'previousLoop', 'nextLoop', //'prevText', 'previousLoop', 'nextLoop',
'budgeted', 'spent', 'budgeted', 'spent',
'prevLoop', 'nextLoop', 'prevLoop', 'nextLoop',
'paginator', 'budgets',
'currencies',
'enableAddButton', 'enableAddButton',
'periodTitle', 'periodTitle',
'defaultCurrency', 'defaultCurrency',
'page', 'activeDaysPassed', 'activeDaysLeft', 'activeDaysPassed', 'activeDaysLeft',
'inactive', 'budgets', 'start', 'end' 'inactive', 'budgets', 'start', 'end'
) )
); );

View File

@@ -70,7 +70,7 @@ class BudgetLimit extends Model
]; ];
/** @var array Fields that can be filled */ /** @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). * 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 $created_at
* @property Carbon $updated_at * @property Carbon $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_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\BudgetLimit[] $budgetLimits
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\TransactionJournal[] $transactionJournals * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\TransactionJournal[] $transactionJournals
* @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Transaction[] $transactions * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Transaction[] $transactions

View File

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

View File

@@ -35,6 +35,19 @@ use Illuminate\Support\Collection;
*/ */
interface BudgetLimitRepositoryInterface 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. * Destroy a budget limit.
* *
@@ -80,20 +93,15 @@ interface BudgetLimitRepositoryInterface
* *
* @return BudgetLimit * @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) * @param array $data
* in the selected query. Returns a positive amount as a string.
* *
* @param Carbon $start * @return BudgetLimit
* @param Carbon $end * @deprecated
* @param TransactionCurrency $currency
* @param Collection|null $budgets
*
* @return string
*/ */
public function budgeted(Carbon $start, Carbon $end, TransactionCurrency $currency, ?Collection $budgets = null): string; public function storeBudgetLimit(array $data): BudgetLimit;
/** /**
* @param BudgetLimit $budgetLimit * @param BudgetLimit $budgetLimit
@@ -101,6 +109,15 @@ interface BudgetLimitRepositoryInterface
* *
* @return BudgetLimit * @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; 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. On start, fill the "spent"-bar using the content from the page.
*/ */
//drawSpentBar(); //drawSpentBar();
drawSpentBars();
//drawBudgetedBar(); //drawBudgetedBar();
$('.update_ab').on('click', updateAvailableBudget) drawBudgetedBars();
$('.create_ab_alt').on('click', createAltAvailableBudget)
$('.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: 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) { var fixHelper = function (e, tr) {
"use strict"; "use strict";
var $originals = tr.children(); var $originals = tr.children();
@@ -110,13 +147,15 @@ function sortStop(event, ui) {
}; };
$.post('budgets/reorder', arr); $.post('budgets/reorder', arr);
} }
function createAltAvailableBudget(e) { function createAltAvailableBudget(e) {
var button = $(e.currentTarget); var button = $(e.currentTarget);
$('#defaultModal').empty().load(createAltAvailableBudgetUri, function () { $('#defaultModal').empty().load(createAltAvailableBudgetUri, function () {
$('#defaultModal').modal('show'); $('#defaultModal').modal('show');
}); });
return false; return false;
} }
function updateAvailableBudget(e) { function updateAvailableBudget(e) {
var button = $(e.currentTarget); var button = $(e.currentTarget);
var abId = parseInt(button.data('id')); 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() { // function drawSpentBar() {

View File

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

View File

@@ -145,7 +145,8 @@
{# progresss bar to visualise available vs budgeted. #} {# progresss bar to visualise available vs budgeted. #}
<div class="row"> <div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12"> <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" <div class="progress-bar progress-bar-danger" data-id="{{ budget.id }}" role="progressbar" aria-valuenow="0"
aria-valuemin="0" aria-valuemin="0"
aria-valuemax="100" style="width: 0;"></div> aria-valuemax="100" style="width: 0;"></div>
@@ -169,7 +170,8 @@
{# bar to visualise spending in budget .#} {# bar to visualise spending in budget .#}
<div class="row"> <div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12"> <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" <div class="progress-bar progress-bar-warning" role="progressbar" aria-valuenow="0" aria-valuemin="0"
aria-valuemax="100" aria-valuemax="100"
style="width: 0;"></div> style="width: 0;"></div>
@@ -195,63 +197,12 @@
{% endif %} {% endif %}
</div> </div>
{% endif %} {% endif %}
{# {% if budgets.count == 0 and inactive.count == 0 %}
<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 %}
{% include 'partials.empty' with {objectType: 'default', type: 'budgets',route: route('budgets.create')} %} {% include 'partials.empty' with {objectType: 'default', type: 'budgets',route: route('budgets.create')} %}
{# make FF ignore demo for now. #} {# make FF ignore demo for now. #}
{% set shownDemo = true %} {% set shownDemo = true %}
{% endif %} {% endif %}
<div class="row"> <div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12"> <div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="box"> <div class="box">
@@ -262,9 +213,6 @@
<div style="padding:8px;"> <div style="padding:8px;">
<a href="{{ route('budgets.create') }}" class="btn btn-success"><i class="fa fa-plus fa-fw"></i> {{ 'createBudget'|_ }}</a> <a href="{{ route('budgets.create') }}" class="btn btn-success"><i class="fa fa-plus fa-fw"></i> {{ 'createBudget'|_ }}</a>
</div> </div>
<div style="padding-left:8px;">
{{ paginator.render|raw }}
</div>
<table class="table table-bordered sortable-table table-striped sortable" id="budgetList"> <table class="table table-bordered sortable-table table-striped sortable" id="budgetList">
<thead> <thead>
<tr> <tr>
@@ -276,64 +224,93 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for budget in paginator %} {% for budget in budgets %}
<tr data-id="{{ budget.id }}"> <tr data-id="{{ budget.id }}">
<td class="hidden-sm hidden-xs"> <td class="hidden-sm hidden-xs">
<div class="btn-group btn-group-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="#" 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.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.delete', budget.id) }}" class="btn btn-xs btn-danger"><i class="fa fa-fw fa-trash-o"></i></a>
</div> </div>
</td> </td>
<td data-value="{{ budget.name }}"> <td data-value="{{ budget.name }}">
<a href="{{ route('budgets.show',budget.id) }}" data-id="{{ budget.id }}">{{ budget.name }}</a>
{% if budgetInformation[budget.id]['currentLimit'] %} </td>
<a href="{{ route('budgets.show.limit', [budget.id, budgetInformation[budget.id]['currentLimit'].id]) }}" <td data-value="-1">
class="budget-link" {% if 0==budget.budgeted|length %}
data-id="{{ budget.id }}">{{ budget.name }}</a> <div class="input-group">
{% else %} <div class="input-group-addon">{{ defaultCurrency.symbol }}</div>
<a href="{{ route('budgets.show',budget.id) }}" class="budget-link" data-id="{{ budget.id }}">{{ budget.name }}</a> <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 %} {% endif %}
</td> </td>
{% if budgetInformation[budget.id]['currentLimit'] %} <td class="hidden-sm hidden-xs spent" data-id="{{ budget.id }}">
{% set repAmount = budgetInformation[budget.id]['budgeted'] %} {% for spentInfo in budget.spent %}
{% else %} {{ formatAmountBySymbol(spentInfo.spent, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }}
{% set repAmount = '0' %} ({{ formatAmountBySymbol(spentInfo.spent / activeDaysPassed, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{% endif %} {% 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>
<td class="hidden-sm hidden-xs spent" data-id="{{ budget.id }}" data-spent="{{ budgetInformation[budget.id]['spent'] }}" <td class="left" data-id="{{ budget.id }}">
data-value="{{ budgetInformation[budget.id]['spent'] }}"> <!-- only makes sense to list what's left for budgeted amounts.-->
{{ "-1"|formatAmount }} {% if budget.budgeted|length > 0 %}
{#{{ budgetInformation[budget.id]['spent']|formatAmount }}#} {% for budgetLimit in budget.budgeted %}
({{ "-1"|formatAmount }}) {% for spentInfo in budget.spent %}
{#({{ (budgetInformation[budget.id]['spent'] / activeDaysPassed)|formatAmount }})#} {% if spentInfo.currency_id == budgetLimit.currency_id %}
</td> {{ formatAmountBySymbol(spentInfo.spent + budgetLimit.amount, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }}
<td class="left" data-id="{{ budget.id }}" {% if spentInfo.spent + budgetLimit.amount > 0 %}
data-value="{{ (repAmount + budgetInformation[budget.id]['spent']) }}"> ({{ formatAmountBySymbol((spentInfo.spent + budgetLimit.amount) / activeDaysLeft, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{{ "-1"|formatAmount }} {% else %}
({{ formatAmountBySymbol(0, spentInfo.currency_symbol, spentInfo.currency_decimal_places) }})
{% endif %}
{% endif %}
{% endfor %}
{% endfor %}
{% endif %}
{#{{ "-1"|formatAmount }}#}
{#{{ (repAmount + budgetInformation[budget.id]['spent'])|formatAmount }}#} {#{{ (repAmount + budgetInformation[budget.id]['spent'])|formatAmount }}#}
{% if repAmount + budgetInformation[budget.id]['spent'] > 0 %} {#{% if repAmount + budgetInformation[budget.id]['spent'] > 0 %}#}
({{ "-1"|formatAmount }}) {#({{ "-1"|formatAmount }})#}
{#({{ ((repAmount + budgetInformation[budget.id]['spent']) / activeDaysLeft)|formatAmount }})#} {#({{ ((repAmount + budgetInformation[budget.id]['spent']) / activeDaysLeft)|formatAmount }})#}
{% endif %} {#{% endif %}#}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
<div style="padding-left:8px;">
{{ paginator.render|raw }}
</div>
</div> </div>
<div class="box-footer"> <div class="box-footer">
<a href="{{ route('budgets.create') }}" class="btn btn-success"><i class="fa fa-plus fa-fw"></i> {{ 'createBudget'|_ }}</a> <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 %} {% block scripts %}
<script src="v1/js/lib/jquery-ui.min.js?v={{ FF_VERSION }}" type="text/javascript"></script> <script src="v1/js/lib/jquery-ui.min.js?v={{ FF_VERSION }}" type="text/javascript"></script>
<script type="text/javascript"> <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 budgetIndexUri = "{{ route('budgets.index',['START','END']) }}";
var createAvailableBudgetUri = "{{ route('available-budgets.create', [start.format('Y-m-d'), end.format('Y-m-d')]) }}"; 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 createAltAvailableBudgetUri = "{{ route('available-budgets.create-alternative', [start.format('Y-m-d'), end.format('Y-m-d')]) }}";
var editAvailableBudgetUri = "{{ route('available-budgets.edit', ['REPLACEME']) }}"; 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 budgetAmountUri = "{{ route('budgets.amount','REPLACE') }}";#}
{#var updateIncomeUri = "{{ route('budgets.income',[start.format('Y-m-d'),end.format('Y-m-d')]) }}?page={{ page }}";#} {#var updateIncomeUri = "{{ route('budgets.income',[start.format('Y-m-d'),end.format('Y-m-d')]) }}?page={{ page }}";#}
var periodStart = "{{ start.format('Y-m-d') }}"; 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'); Route::get('{objectType}', ['uses' => 'Account\IndexController@index', 'as' => 'index'])->where('objectType', 'revenue|asset|expense|liabilities');
// create // 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']); Route::post('store', ['uses' => 'Account\CreateController@store', 'as' => 'store']);
@@ -230,32 +232,36 @@ Route::group(
* Available Budget Controller * Available Budget Controller
*/ */
Route::group( 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 // create
//Route::get('delete/{budget}', ['uses' => 'Budget\DeleteController@delete', 'as' => 'delete']); Route::get('create/{start_date}/{end_date}/{currency?}', ['uses' => 'Budget\AvailableBudgetController@create', 'as' => 'create']);
//Route::post('destroy/{budget}', ['uses' => 'Budget\DeleteController@destroy', 'as' => 'destroy']); 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 // edit
Route::get('create/{start_date}/{end_date}/{currency?}', ['uses' => 'Budget\AvailableBudgetController@create', 'as' => 'create']); Route::get('edit/{availableBudget}', ['uses' => 'Budget\AvailableBudgetController@edit', 'as' => 'edit']);
Route::get('create-alternative/{start_date}/{end_date}', ['uses' => 'Budget\AvailableBudgetController@createAlternative', 'as' => 'create-alternative']); Route::post('update/{availableBudget}', ['uses' => 'Budget\AvailableBudgetController@update', 'as' => 'update']);
Route::post('store', ['uses' => 'Budget\AvailableBudgetController@store', 'as' => 'store']);
//Route::post('store', ['uses' => 'Budget\CreateController@store', 'as' => 'store']);
// edit Route::get('delete/{availableBudget}', ['uses' => 'Budget\AvailableBudgetController@delete', 'as' => 'delete']);
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('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']); * Budget Limit Controller
//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::group(
//Route::get('list/no-budget/{start_date?}/{end_date?}', ['uses' => 'Budget\ShowController@noBudget', 'as' => 'no-budget']); ['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}', ['uses' => 'CategoryController@currentPeriod', 'as' => 'current']);
Route::get('period/{category}/{date}', ['uses' => 'CategoryController@specificPeriod', 'as' => 'specific']); Route::get('period/{category}/{date}', ['uses' => 'CategoryController@specificPeriod', 'as' => 'specific']);
Route::get('all/{category}', ['uses' => 'CategoryController@all', 'as' => 'all']); 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']); Route::get('report-period/{category}/{accountList}/{start_date}/{end_date}', ['uses' => 'CategoryController@reportPeriod', 'as' => 'period']);
// these charts are used in reports (category reports): // 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('currency-names', ['uses' => 'Json\AutoCompleteController@currencyNames', 'as' => 'autocomplete.currency-names']);
Route::get('transaction-types', ['uses' => 'Json\AutoCompleteController@transactionTypes', 'as' => 'transaction-types']); Route::get('transaction-types', ['uses' => 'Json\AutoCompleteController@transactionTypes', 'as' => 'transaction-types']);
// boxes // boxes
@@ -905,7 +912,9 @@ Route::group(
// show groups: // show groups:
// TODO improve these routes // 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( Route::get('{what}/{start_date?}/{end_date?}', ['uses' => 'Transaction\IndexController@index', 'as' => 'index'])->where(
['what' => 'withdrawal|deposit|transfers|transfer'] ['what' => 'withdrawal|deposit|transfers|transfer']