Allow user to set multi-currency available budget. WIP

This commit is contained in:
James Cole
2019-08-31 09:35:35 +02:00
parent 61b6e266da
commit ca777857c2
21 changed files with 1138 additions and 297 deletions

View File

@@ -0,0 +1,209 @@
<?php
/**
* AvailableBudgetController.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 Carbon\Carbon;
use Carbon\Exceptions\InvalidDateException;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\TransactionCurrency;
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\Request;
use Log;
/**
*
* Class AvailableBudgetController
*/
class AvailableBudgetController 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();
app('view')->share('hideBudgets', true);
$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);
}
);
}
/**
* Create will always assume the user's default currency, if it's not set.
*
* This method will check if there is no AB, and refuse to continue if it exists.
*/
public function create(Request $request, Carbon $start, Carbon $end, ?TransactionCurrency $currency = null)
{
$currency = $currency ?? app('amount')->getDefaultCurrency();
$collection = $this->abRepository->get($start, $end);
$filtered = $collection->filter(
static function (AvailableBudget $budget) use ($currency) {
return $currency->id === $budget->transaction_currency_id;
}
);
if ($filtered->count() > 0) {
/** @var AvailableBudget $first */
$first = $filtered->first();
return redirect(route('available-budgets.edit', [$first->id]));
}
$page = (int)($request->get('page') ?? 1);
return view('budgets.available-budgets.create', compact('start', 'end', 'page', 'currency'));
}
/**
* createAlternative will show a list of enabled currencies so the user can pick one.
*/
public function createAlternative(Request $request, Carbon $start, Carbon $end)
{
$currencies = $this->currencyRepos->getEnabled();
$availableBudgets = $this->abRepository->get($start, $end);
// remove already budgeted currencies:
$currencies = $currencies->filter(
static function (TransactionCurrency $currency) use ($availableBudgets) {
/** @var AvailableBudget $budget */
foreach ($availableBudgets as $budget) {
if ($budget->transaction_currency_id === $currency->id) {
return false;
}
}
return true;
}
);
$page = (int)($request->get('page') ?? 1);
return view('budgets.available-budgets.create-alternative', compact('start', 'end', 'page', 'currencies'));
}
/**
* @param AvailableBudget $availableBudget
*
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
*/
public function delete(AvailableBudget $availableBudget)
{
$this->abRepository->destroyAvailableBudget($availableBudget);
session()->flash('success', trans('firefly.deleted_ab'));
return redirect(route('budgets.index'));
}
/**
* @param AvailableBudget $availableBudget
*/
public function edit(AvailableBudget $availableBudget)
{
return view('budgets.available-budgets.edit', compact('availableBudget'));
}
/**
* @param Request $request
*/
public function store(Request $request)
{
// make dates.
try {
$start = Carbon::createFromFormat('Y-m-d', $request->get('start'));
$end = Carbon::createFromFormat('Y-m-d', $request->get('end'));
} catch (InvalidDateException $e) {
$start = session()->get('start');
$end = session()->get('end');
Log::info($e->getMessage());
}
// find currency
$currency = $this->currencyRepos->find((int)$request->get('currency_id'));
if (null === $currency) {
session()->flash('error', trans('firefly.invalid_currency'));
return redirect(route('budgets.index'));
}
// find existing AB
$existing = $this->abRepository->find($currency, $start, $end);
if (null === $existing) {
$this->abRepository->store(
[
'amount' => $request->get('amount'),
'currency' => $currency,
'start' => $start,
'end' => $end,
]
);
}
if (null !== $existing) {
// update amount:
$this->abRepository->update($existing, ['amount' => $request->get('amount')]);
}
session()->flash('success', trans('firefly.set_ab'));
return redirect(route('budgets.index'));
}
/**
* @param Request $request
* @param AvailableBudget $availableBudget
*/
public function update(Request $request, AvailableBudget $availableBudget)
{
$this->abRepository->update($availableBudget, ['amount' => $request->get('amount')]);
session()->flash('success', trans('firefly.updated_ab'));
return redirect(route('budgets.index'));
}
}

View File

@@ -26,13 +26,18 @@ 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\TransactionCurrency;
use FireflyIII\Repositories\Budget\AvailableBudgetRepositoryInterface; use FireflyIII\Repositories\Budget\AvailableBudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetLimitRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Repositories\Budget\OperationsRepositoryInterface; use FireflyIII\Repositories\Budget\OperationsRepositoryInterface;
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\Pagination\LengthAwarePaginator;
use Log;
/** /**
* *
@@ -42,12 +47,16 @@ class IndexController extends Controller
{ {
use DateCalculation; use DateCalculation;
/** @var AvailableBudgetRepositoryInterface */
private $abRepository;
/** @var BudgetLimitRepositoryInterface */
private $blRepository;
/** @var CurrencyRepositoryInterface */
private $currencyRepository;
/** @var OperationsRepositoryInterface */ /** @var OperationsRepositoryInterface */
private $opsRepository; private $opsRepository;
/** @var BudgetRepositoryInterface The budget repository */ /** @var BudgetRepositoryInterface The budget repository */
private $repository; private $repository;
/** @var AvailableBudgetRepositoryInterface */
private $abRepository;
/** /**
* IndexController constructor. * IndexController constructor.
@@ -64,9 +73,11 @@ class IndexController extends Controller
function ($request, $next) { function ($request, $next) {
app('view')->share('title', (string)trans('firefly.budgets')); app('view')->share('title', (string)trans('firefly.budgets'));
app('view')->share('mainTitleIcon', 'fa-tasks'); app('view')->share('mainTitleIcon', 'fa-tasks');
$this->repository = app(BudgetRepositoryInterface::class); $this->repository = app(BudgetRepositoryInterface::class);
$this->opsRepository = app(OperationsRepositoryInterface::class); $this->opsRepository = app(OperationsRepositoryInterface::class);
$this->abRepository = app(AvailableBudgetRepositoryInterface::class); $this->abRepository = app(AvailableBudgetRepositoryInterface::class);
$this->currencyRepository = app(CurrencyRepositoryInterface::class);
$this->blRepository = app(BudgetLimitRepositoryInterface::class);
$this->repository->cleanupBudgets(); $this->repository->cleanupBudgets();
return $next($request); return $next($request);
@@ -93,18 +104,47 @@ class IndexController extends Controller
$page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page'); $page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page');
$pageSize = (int)app('preferences')->get('listPageSize', 50)->data; $pageSize = (int)app('preferences')->get('listPageSize', 50)->data;
$defaultCurrency = app('amount')->getDefaultCurrency(); $defaultCurrency = app('amount')->getDefaultCurrency();
$budgeted = '0';
$spent = '0';
// new period stuff:
$periodTitle = app('navigation')->periodShow($start, $range);
// make the next and previous period, and calculate the periods used for period navigation // loop of previous periods:
$next = clone $end; $prevLoop = $this->getPreviousPeriods($start, $range);
$next->addDay(); $nextLoop = $this->getNextPeriods($start, $range);
$prev = clone $start;
$prev->subDay(); // get all available budgets.
$prev = app('navigation')->startOfPeriod($prev, $range); $ab = $this->abRepository->get($start, $end);
$previousLoop = $this->getPreviousPeriods($start, $range); $availableBudgets = [];
$nextLoop = $this->getNextPeriods($start, $range); // for each, complement with spent amount:
$currentMonth = app('navigation')->periodShow($start, $range); /** @var AvailableBudget $entry */
$nextText = app('navigation')->periodShow($next, $range); foreach ($ab as $entry) {
$prevText = app('navigation')->periodShow($prev, $range); $array = $entry->toArray();
$array['start_date'] = $entry->start_date;
$array['end_date'] = $entry->end_date;
// spent in period:
$spentArr = $this->opsRepository->sumExpenses($entry->start_date, $entry->end_date, null, null, $entry->transactionCurrency);
$array['spent'] = $spentArr[$entry->transaction_currency_id]['sum'] ?? '0';
// budgeted in period:
$budgeted = $this->blRepository->budgeted($entry->start_date, $entry->end_date, $entry->transactionCurrency,);
$array['budgeted'] = $budgeted;
$availableBudgets[] = $array;
unset($spentArr);
}
if (0 === count($availableBudgets)) {
// get budgeted for default currency:
$budgeted = $this->blRepository->budgeted($start, $end, $defaultCurrency,);
$spentArr = $this->opsRepository->sumExpenses($start, $end, null, null, $defaultCurrency);
$spent = $spentArr[$defaultCurrency->id]['sum'] ?? '0';
unset($spentArr);
}
// count the number of enabled currencies. This determines if we display a "+" button.
$currencies = $this->currencyRepository->getEnabled();
$enableAddButton = $currencies->count() > count($availableBudgets);
// number of days for consistent budgeting. // number of days for consistent budgeting.
$activeDaysPassed = $this->activeDaysPassed($start, $end); // see method description. $activeDaysPassed = $this->activeDaysPassed($start, $end); // see method description.
@@ -118,14 +158,6 @@ class IndexController extends Controller
// get all inactive budgets, and simply list them: // get all inactive budgets, and simply list them:
$inactive = $this->repository->getInactiveBudgets(); $inactive = $this->repository->getInactiveBudgets();
// collect budget info to fill bars and so on.
$budgetInformation = $this->opsRepository->collectBudgetInformation($collection, $start, $end);
// to display available budget:
$available = $this->abRepository->getAvailableBudget($defaultCurrency, $start, $end);
$spent = array_sum(array_column($budgetInformation, 'spent'));
$budgeted = array_sum(array_column($budgetInformation, 'budgeted'));
// paginate budgets // paginate budgets
$paginator = new LengthAwarePaginator($budgets, $total, $pageSize, $page); $paginator = new LengthAwarePaginator($budgets, $total, $pageSize, $page);
@@ -133,11 +165,18 @@ class IndexController extends Controller
return view( return view(
'budgets.index', compact( 'budgets.index', compact(
'available', 'currentMonth', 'next', 'nextText', 'prev', 'paginator', 'availableBudgets',
'prevText', //'available',
//'currentMonth', 'next', 'nextText', 'prev',
//'prevText', 'previousLoop', 'nextLoop',
'budgeted', 'spent',
'prevLoop', 'nextLoop',
'paginator',
'enableAddButton',
'periodTitle',
'defaultCurrency',
'page', 'activeDaysPassed', 'activeDaysLeft', 'page', 'activeDaysPassed', 'activeDaysLeft',
'budgetInformation', 'inactive', 'budgets', 'start', 'end'
'inactive', 'budgets', 'spent', 'budgeted', 'previousLoop', 'nextLoop', 'start', 'end'
) )
); );
} }
@@ -160,6 +199,7 @@ class IndexController extends Controller
$budgetId = (int)$budgetId; $budgetId = (int)$budgetId;
$budget = $repository->findNull($budgetId); $budget = $repository->findNull($budgetId);
if (null !== $budget) { if (null !== $budget) {
Log::debug(sprintf('Set budget #%d ("%s") to position %d', $budget->id, $budget->name, $currentOrder));
$repository->setBudgetOrder($budget, $currentOrder); $repository->setBudgetOrder($budget, $currentOrder);
} }
$currentOrder++; $currentOrder++;

View File

@@ -29,6 +29,7 @@ use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Models\AvailableBudget; use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Log; use Log;
@@ -64,6 +65,48 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
} }
} }
/**
* Find existing AB.
*
* @param TransactionCurrency $currency
* @param Carbon $start
* @param Carbon $end
*
* @return AvailableBudget|null
*/
public function find(TransactionCurrency $currency, Carbon $start, Carbon $end): ?AvailableBudget
{
return $this->user->availableBudgets()
->where('transaction_currency_id', $currency->id)
->where('start_date', $start->format('Y-m-d 00:00:00'))
->where('end_date', $end->format('Y-m-d 00:00:00'))
->first();
}
/**
* Return a list of all available budgets (in all currencies) (for the selected period).
*
* @param Carbon|null $start
* @param Carbon|null $end
*
* @return Collection
*/
public function get(?Carbon $start = null, ?Carbon $end = null): Collection
{
$query = $this->user->availableBudgets()->with(['transactionCurrency']);
if (null !== $start && null !== $end) {
$query->where(
static function (Builder $q1) use ($start, $end) {
$q1->where('start_date', '=', $start->format('Y-m-d 00:00:00'));
$q1->where('end_date', '=', $end->format('Y-m-d 00:00:00'));
}
);
}
return $query->get(['available_budgets.*']);
}
/** /**
* @param TransactionCurrency $currency * @param TransactionCurrency $currency
* @param Carbon $start * @param Carbon $start
@@ -140,7 +183,6 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
return $query->get(); return $query->get();
} }
/** /**
* @param TransactionCurrency $currency * @param TransactionCurrency $currency
* @param Carbon $start * @param Carbon $start
@@ -176,6 +218,41 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
$this->user = $user; $this->user = $user;
} }
/**
* @param array $data
*
* @return AvailableBudget|null
*/
public function store(array $data): ?AvailableBudget
{
return AvailableBudget::create(
[
'user_id' => $this->user->id,
'transaction_currency_id' => $data['currency']->id,
'amount' => $data['amount'],
'start_date' => $data['start'],
'end_date' => $data['end'],
]
);
}
/**
* @param AvailableBudget $availableBudget
* @param array $data
*
* @return AvailableBudget
*/
public function update(AvailableBudget $availableBudget, array $data): AvailableBudget
{
if (isset($data['amount'])) {
$availableBudget->amount = $data['amount'];
}
$availableBudget->save();
return $availableBudget;
}
/** /**
* @param AvailableBudget $availableBudget * @param AvailableBudget $availableBudget
* @param array $data * @param array $data
@@ -204,6 +281,4 @@ class AvailableBudgetRepository implements AvailableBudgetRepositoryInterface
return $availableBudget; return $availableBudget;
} }
} }

View File

@@ -39,15 +39,52 @@ interface AvailableBudgetRepositoryInterface
*/ */
public function destroyAvailableBudget(AvailableBudget $availableBudget): void; public function destroyAvailableBudget(AvailableBudget $availableBudget): void;
/**
* Return a list of all available budgets (in all currencies) (for the selected period).
*
* @param Carbon|null $start
* @param Carbon|null $end
*
* @return Collection
*/
public function get(?Carbon $start = null, ?Carbon $end = null): Collection;
/** /**
* @param TransactionCurrency $currency * @param TransactionCurrency $currency
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end
* *
* @return string * @return string
* @deprecated
*/ */
public function getAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end): string; public function getAvailableBudget(TransactionCurrency $currency, Carbon $start, Carbon $end): string;
/**
* @param array $data
*
* @return AvailableBudget|null
*/
public function store(array $data): ?AvailableBudget;
/**
* @param AvailableBudget $availableBudget
* @param array $data
*
* @return AvailableBudget
*/
public function update(AvailableBudget $availableBudget, array $data): AvailableBudget;
/**
* Find existing AB.
*
* @param TransactionCurrency $currency
* @param Carbon $start
* @param Carbon $end
*
* @return AvailableBudget|null
*/
public function find(TransactionCurrency $currency, Carbon $start, Carbon $end): ?AvailableBudget;
/** /**
* @param Carbon $start * @param Carbon $start
* @param Carbon $end * @param Carbon $end

View File

@@ -55,6 +55,36 @@ class BudgetLimitRepository implements 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
{
$query = BudgetLimit
::where('start_date', $start->format('Y-m-d 00:00:00'))
->where('end_date', $end->format('Y-m-d 00:00:00'))
->where('transaction_currency_id', $currency->id);
if (null !== $budgets && $budgets->count() > 0) {
$query->whereIn('budget_id', $budgets->pluck('id')->toArray());
}
$set = $query->get(['budget_limits.*']);
$result = '0';
/** @var BudgetLimit $budgetLimit */
foreach ($set as $budgetLimit) {
$result = bcadd($budgetLimit->amount, $result);
}
return $result;
}
/** /**
* Destroy a budget limit. * Destroy a budget limit.
* *
@@ -142,7 +172,6 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
return $set; return $set;
} }
/** /**
* @param TransactionCurrency $currency * @param TransactionCurrency $currency
* @param Carbon $start * @param Carbon $start
@@ -159,7 +188,6 @@ class BudgetLimitRepository implements BudgetLimitRepositoryInterface
); );
} }
/** /**
* @param Budget $budget * @param Budget $budget
* @param Carbon $start * @param Carbon $start

View File

@@ -82,6 +82,19 @@ interface BudgetLimitRepositoryInterface
*/ */
public function storeBudgetLimit(array $data): BudgetLimit; public function storeBudgetLimit(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 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;
/** /**
* @param BudgetLimit $budgetLimit * @param BudgetLimit $budgetLimit
* @param array $data * @param array $data

View File

@@ -28,6 +28,7 @@ use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\RuleAction; use FireflyIII\Models\RuleAction;
use FireflyIII\Models\RuleTrigger; use FireflyIII\Models\RuleTrigger;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Services\Internal\Destroy\BudgetDestroyService; use FireflyIII\Services\Internal\Destroy\BudgetDestroyService;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -192,7 +193,7 @@ class BudgetRepository implements BudgetRepositoryInterface
{ {
/** @var Collection $set */ /** @var Collection $set */
$set = $this->user->budgets()->where('active', 1) $set = $this->user->budgets()->where('active', 1)
->orderBy('order', 'DESC') ->orderBy('order', 'ASC')
->orderBy('name', 'ASC') ->orderBy('name', 'ASC')
->get(); ->get();

View File

@@ -29,6 +29,7 @@ use FireflyIII\Models\AccountType;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\BudgetLimit;
use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -61,7 +62,6 @@ class OperationsRepository implements OperationsRepositoryInterface
* @param Budget $budget * @param Budget $budget
* *
* @return string * @return string
* @deprecated
*/ */
public function budgetedPerDay(Budget $budget): string public function budgetedPerDay(Budget $budget): string
{ {
@@ -278,6 +278,53 @@ class OperationsRepository implements OperationsRepositoryInterface
return $return; return $return;
} }
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param Carbon $start
* @param Carbon $end
* @param Collection|null $accounts
* @param Collection|null $budgets
* @param TransactionCurrency|null $currency
*
* @return array
*/
public function sumExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null, ?TransactionCurrency $currency = null
): array {
/** @var GroupCollectorInterface $collector */
$collector = app(GroupCollectorInterface::class);
$collector->setUser($this->user)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]);
if (null !== $accounts && $accounts->count() > 0) {
$collector->setAccounts($accounts);
}
if (null === $budgets || (null !== $budgets && 0 === $budgets->count())) {
$budgets = $this->getBudgets();
}
if (null !== $currency) {
$collector->setCurrency($currency);
}
$collector->setBudgets($budgets);
$collector->withBudgetInformation();
$journals = $collector->getExtractedJournals();
$array = [];
foreach ($journals as $journal) {
$currencyId = (int)$journal['currency_id'];
$array[$currencyId] = $array[$currencyId] ?? [
'sum' => '0',
'currency_id' => $currencyId,
'currency_name' => $journal['currency_name'],
'currency_symbol' => $journal['currency_symbol'],
'currency_code' => $journal['currency_code'],
'currency_decimal_places' => $journal['currency_decimal_places'],
];
$array[$currencyId]['sum'] = bcadd($array[$currencyId]['sum'], app('steam')->negative($journal['amount']));
}
return $array;
}
/** /**
* For now, simply refer to whichever repository holds this function. * For now, simply refer to whichever repository holds this function.
* TODO might be done better in the future. * TODO might be done better in the future.
@@ -295,4 +342,15 @@ class OperationsRepository implements OperationsRepositoryInterface
return $blRepository->getBudgetLimits($budget, $start, $end); return $blRepository->getBudgetLimits($budget, $start, $end);
} }
/**
* @return Collection
*/
private function getBudgets(): Collection
{
/** @var BudgetRepositoryInterface $repos */
$repos = app(BudgetRepositoryInterface::class);
return $repos->getActiveBudgets();
}
} }

View File

@@ -25,6 +25,7 @@ namespace FireflyIII\Repositories\Budget;
use Carbon\Carbon; use Carbon\Carbon;
use FireflyIII\Models\Budget; use FireflyIII\Models\Budget;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\User; use FireflyIII\User;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
@@ -40,10 +41,11 @@ interface OperationsRepositoryInterface
* @param Budget $budget * @param Budget $budget
* *
* @return string * @return string
* @deprecated
*/ */
public function budgetedPerDay(Budget $budget): string; public function budgetedPerDay(Budget $budget): string;
/** /**
* This method collects various info on budgets, used on the budget page and on the index. * This method collects various info on budgets, used on the budget page and on the index.
* *
@@ -96,4 +98,17 @@ interface OperationsRepositoryInterface
*/ */
public function spentInPeriodMc(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array; public function spentInPeriodMc(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array;
/** @noinspection MoreThanThreeArgumentsInspection */
/**
* @param Carbon $start
* @param Carbon $end
* @param Collection|null $accounts
* @param Collection|null $budgets
* @param TransactionCurrency|null $currency
*
* @return array
*/
public function sumExpenses(Carbon $start, Carbon $end, ?Collection $accounts = null, ?Collection $budgets = null, ?TransactionCurrency $currency = null
): array;
} }

View File

@@ -228,6 +228,7 @@ class OperationsRepository implements OperationsRepositoryInterface
$categories = $this->getCategories(); $categories = $this->getCategories();
} }
$collector->setCategories($categories); $collector->setCategories($categories);
$collector->withCategoryInformation();
$journals = $collector->getExtractedJournals(); $journals = $collector->getExtractedJournals();
$array = []; $array = [];

View File

@@ -392,6 +392,16 @@ class CurrencyRepository implements CurrencyRepositoryInterface
return TransactionCurrency::orderBy('code', 'ASC')->get(); return TransactionCurrency::orderBy('code', 'ASC')->get();
} }
/**
* @return Collection
*/
public function getEnabled(): Collection
{
return TransactionCurrency::where('enabled',true)->orderBy('code', 'ASC')->get();
}
/** /**
* @param array $ids * @param array $ids
* *

View File

@@ -34,12 +34,6 @@ use Illuminate\Support\Collection;
*/ */
interface CurrencyRepositoryInterface interface CurrencyRepositoryInterface
{ {
/**
* @param string $search
* @return Collection
*/
public function searchCurrency(string $search): Collection;
/** /**
* @param TransactionCurrency $currency * @param TransactionCurrency $currency
* *
@@ -200,6 +194,11 @@ interface CurrencyRepositoryInterface
*/ */
public function getCurrencyByPreference(Preference $preference): TransactionCurrency; public function getCurrencyByPreference(Preference $preference): TransactionCurrency;
/**
* @return Collection
*/
public function getEnabled(): Collection;
/** /**
* Get currency exchange rate. * Get currency exchange rate.
* *
@@ -220,6 +219,13 @@ interface CurrencyRepositoryInterface
*/ */
public function getExchangeRates(TransactionCurrency $currency): Collection; public function getExchangeRates(TransactionCurrency $currency): Collection;
/**
* @param string $search
*
* @return Collection
*/
public function searchCurrency(string $search): Collection;
/** /**
* @param User $user * @param User $user
*/ */

View File

@@ -122,9 +122,17 @@ trait DateCalculation
$count = 0; $count = 0;
while ($count < 12) { while ($count < 12) {
$format = $current->format('Y-m-d'); $current = app('navigation')->endOfPeriod($current, $range);
$loop[$format] = app('navigation')->periodShow($current, $range); $currentStart = app('navigation')->startOfPeriod($current, $range);
$current = app('navigation')->endOfPeriod($current, $range);
$loop[] = [
'label' => $current->format('Y-m-d'),
'title' => app('navigation')->periodShow($current, $range),
'start' => clone $currentStart,
'end' => clone $current,
];
++$count; ++$count;
$current->addDay(); $current->addDay();
} }
@@ -150,9 +158,14 @@ trait DateCalculation
$count = 0; $count = 0;
while ($count < 12) { while ($count < 12) {
$current->subDay(); $current->subDay();
$current = app('navigation')->startOfPeriod($current, $range); $current = app('navigation')->startOfPeriod($current, $range);
$format = $current->format('Y-m-d'); $currentEnd = app('navigation')->endOfPeriod($current, $range);
$loop[$format] = app('navigation')->periodShow($current, $range); $loop[] = [
'label' => $current->format('Y-m-d'),
'title' => app('navigation')->periodShow($current, $range),
'start' => clone $current,
'end' => clone $currentEnd,
];
++$count; ++$count;
} }

View File

@@ -143,7 +143,7 @@ class AmountFormat extends Twig_Extension
{ {
return new Twig_SimpleFilter( return new Twig_SimpleFilter(
'formatAmountPlain', 'formatAmountPlain',
function (string $string): string { static function (string $string): string {
$currency = app('amount')->getDefaultCurrency(); $currency = app('amount')->getDefaultCurrency();
return app('amount')->formatAnything($currency, $string, false); return app('amount')->formatAnything($currency, $string, false);

View File

@@ -18,31 +18,33 @@
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>. * along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/ */
/** global: infoIncomeUri, page, token, spent, budgeted, available, currencySymbol, budgetIndexUri, updateIncomeUri, periodStart, periodEnd, budgetAmountUri, accounting */
/** /**
* *
*/ */
$(function () { $(function () {
"use strict"; "use strict";
$('.updateIncome').on('click', updateIncome); //$('.updateIncome').on('click', updateIncome);
/* /*
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();
drawBudgetedBar(); //drawBudgetedBar();
$('.update_ab').on('click', updateAvailableBudget)
$('.create_ab_alt').on('click', createAltAvailableBudget)
/* /*
When the input changes, update the percentages for the budgeted bar: When the input changes, update the percentages for the budgeted bar:
*/ */
$('input[type="number"]').on('change', updateBudgetedAmounts); //$('input[type="number"]').on('change', updateBudgetedAmounts);
//
$('.selectPeriod').change(function (e) { $('.selectPeriod').change(function (e) {
var sel = $(e.target).val(); var selected = $(e.currentTarget);
if (sel !== "x") { if (selected.find(":selected").val() !== "x") {
var newUri = budgetIndexUri.replace("REPLACE", sel); var newUri = budgetIndexUri.replace("START", selected.find(":selected").data('start')).replace('END', selected.find(":selected").data('end'));
console.log(newUri);
window.location.assign(newUri + "?page=" + page); window.location.assign(newUri + "?page=" + page);
} }
}); });
@@ -91,7 +93,6 @@ var fixHelper = function (e, tr) {
function sortStop(event, ui) { function sortStop(event, ui) {
"use strict"; "use strict";
//var current = $(ui.item); //var current = $(ui.item);
var list = $('.sortable-table tbody tr'); var list = $('.sortable-table tbody tr');
var submit = []; var submit = [];
@@ -109,132 +110,157 @@ function sortStop(event, ui) {
}; };
$.post('budgets/reorder', arr); $.post('budgets/reorder', arr);
} }
function createAltAvailableBudget(e) {
var button = $(e.currentTarget);
function drawSpentBar() { $('#defaultModal').empty().load(createAltAvailableBudgetUri, function () {
"use strict"; $('#defaultModal').modal('show');
if ($('.spentBar').length > 0) { });
var overspent = spent > budgeted;
var pct;
if (overspent) {
// draw overspent bar
pct = (budgeted / spent) * 100;
$('.spentBar .progress-bar-warning').css('width', pct + '%');
$('.spentBar .progress-bar-danger').css('width', (100 - pct) + '%');
} else {
// draw normal bar:
pct = (spent / budgeted) * 100;
$('.spentBar .progress-bar-info').css('width', pct + '%');
}
}
}
function drawBudgetedBar() {
"use strict";
if ($('.budgetedBar').length > 0) {
var budgetedMuch = budgeted > available;
// recalculate percentage:
var pct;
if (budgetedMuch) {
// budgeted too much.
pct = (available / budgeted) * 100;
$('.budgetedBar .progress-bar-warning').css('width', pct + '%');
$('.budgetedBar .progress-bar-danger').css('width', (100 - pct) + '%');
$('.budgetedBar .progress-bar-info').css('width', 0);
} else {
pct = (budgeted / available) * 100;
$('.budgetedBar .progress-bar-warning').css('width', 0);
$('.budgetedBar .progress-bar-danger').css('width', 0);
$('.budgetedBar .progress-bar-info').css('width', pct + '%');
}
$('#budgetedAmount').html(currencySymbol + ' ' + budgeted.toFixed(2));
}
}
/**
*
* @param e
*/
function updateBudgetedAmounts(e) {
"use strict";
var target = $(e.target);
var id = target.data('id');
var leftCell = $('td[class$="left"][data-id="' + id + '"]');
var link = $('a[data-id="' + id + '"][class="budget-link"]');
var value = target.val();
var original = target.data('original');
// disable input
target.prop('disabled', true);
// replace link (for now)
link.attr('href', '#');
// replace "left" with spinner.
leftCell.empty().html('<i class="fa fa-fw fa-spin fa-spinner"></i>');
// send a post to Firefly to update the amount:
var newUri = budgetAmountUri.replace("REPLACE", id);
$.post(newUri, {amount: value, start: periodStart, end: periodEnd, _token: token}).done(function (data) {
// difference between new value and original value
var difference = value - original;
// update budgeted value
budgeted = budgeted + difference;
// fill in "left" value:
if (data.left_per_day !== null) {
leftCell.html(data.left + ' (' + data.left_per_day + ')');
} else {
leftCell.html(data.left);
}
// update "budgeted" input:
target.val(data.amount);
// enable thing again
target.prop('disabled', false);
// set new original value:
target.data('original', data.amount);
// run drawBudgetedBar() again:
drawBudgetedBar();
// update the link if relevant:
link.attr('href', 'budgets/show/' + id);
if (data.limit > 0) {
link.attr('href', 'budgets/show/' + id + '/' + data.limit);
}
// update the warning if relevant:
if (data.large_diff === true) {
$('span[class$="budget_warning"][data-id="' + id + '"]').html(data.warn_text).show();
console.log('Show warning for budget');
} else {
$('span[class$="budget_warning"][data-id="' + id + '"]').empty().hide();
}
});
}
/**
*
* @returns {boolean}
*/
function updateIncome() {
"use strict";
$('#defaultModal').empty().load(updateIncomeUri, function () {
$('#defaultModal').modal('show');
});
return false; return false;
} }
function updateAvailableBudget(e) {
var button = $(e.currentTarget);
var abId = parseInt(button.data('id'));
if (0 === abId) {
$('#defaultModal').empty().load(createAvailableBudgetUri, function () {
$('#defaultModal').modal('show');
});
}
if (abId > 0) {
// edit URL.
$('#defaultModal').empty().load(editAvailableBudgetUri.replace('REPLACEME', abId), function () {
$('#defaultModal').modal('show');
});
}
return false;
}
//
//
// function drawSpentBar() {
// "use strict";
// if ($('.spentBar').length > 0) {
// var overspent = spent > budgeted;
// var pct;
//
// if (overspent) {
// // draw overspent bar
// pct = (budgeted / spent) * 100;
// $('.spentBar .progress-bar-warning').css('width', pct + '%');
// $('.spentBar .progress-bar-danger').css('width', (100 - pct) + '%');
// } else {
// // draw normal bar:
// pct = (spent / budgeted) * 100;
// $('.spentBar .progress-bar-info').css('width', pct + '%');
// }
// }
// }
//
// function drawBudgetedBar() {
// "use strict";
//
// if ($('.budgetedBar').length > 0) {
// var budgetedMuch = budgeted > available;
//
// // recalculate percentage:
//
// var pct;
// if (budgetedMuch) {
// // budgeted too much.
// pct = (available / budgeted) * 100;
// $('.budgetedBar .progress-bar-warning').css('width', pct + '%');
// $('.budgetedBar .progress-bar-danger').css('width', (100 - pct) + '%');
// $('.budgetedBar .progress-bar-info').css('width', 0);
// } else {
// pct = (budgeted / available) * 100;
// $('.budgetedBar .progress-bar-warning').css('width', 0);
// $('.budgetedBar .progress-bar-danger').css('width', 0);
// $('.budgetedBar .progress-bar-info').css('width', pct + '%');
// }
//
// $('#budgetedAmount').html(currencySymbol + ' ' + budgeted.toFixed(2));
// }
// }
// /**
// *
// * @param e
// */
// function updateBudgetedAmounts(e) {
// "use strict";
// var target = $(e.target);
// var id = target.data('id');
// var leftCell = $('td[class$="left"][data-id="' + id + '"]');
// var link = $('a[data-id="' + id + '"][class="budget-link"]');
// var value = target.val();
// var original = target.data('original');
//
// // disable input
// target.prop('disabled', true);
//
// // replace link (for now)
// link.attr('href', '#');
//
// // replace "left" with spinner.
// leftCell.empty().html('<i class="fa fa-fw fa-spin fa-spinner"></i>');
//
// // send a post to Firefly to update the amount:
// var newUri = budgetAmountUri.replace("REPLACE", id);
//
// $.post(newUri, {amount: value, start: periodStart, end: periodEnd, _token: token}).done(function (data) {
//
// // difference between new value and original value
// var difference = value - original;
//
// // update budgeted value
// budgeted = budgeted + difference;
//
// // fill in "left" value:
//
//
// if (data.left_per_day !== null) {
// leftCell.html(data.left + ' (' + data.left_per_day + ')');
// } else {
// leftCell.html(data.left);
// }
//
// // update "budgeted" input:
// target.val(data.amount);
//
// // enable thing again
// target.prop('disabled', false);
//
// // set new original value:
// target.data('original', data.amount);
//
// // run drawBudgetedBar() again:
// drawBudgetedBar();
//
// // update the link if relevant:
// link.attr('href', 'budgets/show/' + id);
// if (data.limit > 0) {
// link.attr('href', 'budgets/show/' + id + '/' + data.limit);
// }
//
// // update the warning if relevant:
// if (data.large_diff === true) {
// $('span[class$="budget_warning"][data-id="' + id + '"]').html(data.warn_text).show();
// console.log('Show warning for budget');
// } else {
// $('span[class$="budget_warning"][data-id="' + id + '"]').empty().hide();
// }
// });
// }
// /**
// *
// * @returns {boolean}
// */
// function updateIncome() {
// "use strict";
// $('#defaultModal').empty().load(updateIncomeUri, function () {
// $('#defaultModal').modal('show');
// });
//
// return false;
// }

View File

@@ -670,14 +670,25 @@ return [
'options' => 'Options', 'options' => 'Options',
// budgets: // budgets:
'total_available_budget' => 'Total available budget (between :start and :end)',
'total_available_budget_in_currency' => 'Total available budget in :currency',
'see_below' => 'see below',
'create_new_budget' => 'Create a new budget', 'create_new_budget' => 'Create a new budget',
'store_new_budget' => 'Store new budget', 'store_new_budget' => 'Store new budget',
'stored_new_budget' => 'Stored new budget ":name"', 'stored_new_budget' => 'Stored new budget ":name"',
'available_between' => 'Available between :start and :end', 'available_between' => 'Available between :start and :end',
'transactionsWithoutBudget' => 'Expenses without budget', 'transactionsWithoutBudget' => 'Expenses without budget',
'transactions_no_budget' => 'Expenses without budget between :start and :end', 'transactions_no_budget' => 'Expenses without budget between :start and :end',
'spent_between' => 'Spent between :start and :end', 'spent_between' => 'Already spent between :start and :end',
'set_available_amount' => 'Set available amount',
'update_available_amount' => 'Update available amount',
'ab_basic_modal_explain' => 'Use this form to indicate how much you expect to be able to budget (in total, in :currency) in the indicated period.',
'createBudget' => 'New budget', 'createBudget' => 'New budget',
'invalid_currency' => 'This is an invalid currency',
'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',
'alt_currency_ab_create' => 'Set the available 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

@@ -0,0 +1,38 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span>&times;</span><span class="sr-only">{{ 'close'|_ }}</span>
</button>
<h4 class="modal-title">
{{ trans('firefly.update_budget_amount_range',
{start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat)}) }}
</h4>
</div>
<form style="display: inline;" id="income" action="{{ route('available-budgets.store') }}" 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') }}"/>
<input type="hidden" name="page" value="{{ page }}"/>
<input type="hidden" name="currency_id" value="{{ currency.id }}"/>
<div class="form-group">
<select class="form-control" name="currency_id">
{% for currency in currencies %}
<option label="{{ currency.name }}" value="{{ currency.id }}">{{ currency.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<input step="any" class="form-control" id="amount" value="" 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">{{ 'set_available_amount'|_ }}</button>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,37 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span>&times;</span><span class="sr-only">{{ 'close'|_ }}</span>
</button>
<h4 class="modal-title">
{{ trans('firefly.update_budget_amount_range',
{start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat)}) }}
</h4>
</div>
<form style="display: inline;" id="income" action="{{ route('available-budgets.store') }}" 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') }}"/>
<input type="hidden" name="page" value="{{ page }}"/>
<input type="hidden" name="currency_id" value="{{ currency.id }}"/>
<div class="form-group">
<p class="form-control-static">{{ trans('firefly.ab_basic_modal_explain', {currency: currency.name}) }}</p>
</div>
<div class="input-group">
<div class="input-group-addon">{{ currency.symbol|raw }}</div>
<input step="any" class="form-control" id="amount" value="" 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">{{ 'set_available_amount'|_ }}</button>
</div>
</form>
</div>
</div>

View File

@@ -0,0 +1,33 @@
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal"><span>&times;</span><span class="sr-only">{{ 'close'|_ }}</span>
</button>
<h4 class="modal-title">
{{ trans('firefly.update_budget_amount_range',
{start: availableBudget.start_date.formatLocalized(monthAndDayFormat), end: availableBudget.end_date.formatLocalized(monthAndDayFormat)}) }}
</h4>
</div>
<form style="display: inline;" id="income" action="{{ route('available-budgets.update', [availableBudget.id]) }}" method="POST">
<div class="modal-body">
<input type="hidden" name="_token" value="{{ csrf_token() }}"/>
<input type="hidden" name="page" value="{{ page }}"/>
<div class="form-group">
<p class="form-control-static">{{ trans('firefly.ab_basic_modal_explain', {currency: availableBudget.transactionCurrency.name}) }}</p>
</div>
<div class="input-group">
<div class="input-group-addon">{{ availableBudget.transactionCurrency.symbol|raw }}</div>
<input step="any" class="form-control" id="amount" value="{{ availableBudget.amount }}" 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_available_amount'|_ }}</button>
</div>
</form>
</div>
</div>

View File

@@ -5,92 +5,7 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="row"> <!-- date selector -->
<div class="col-lg-9 col-md-8 col-sm-12 col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ start.formatLocalized(monthAndDayFormat) }} &mdash; {{ end.formatLocalized(monthAndDayFormat) }}</h3>
</div>
<div class="box-body">
<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">{{ 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 }}">{{ 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)}) }}
: {{ 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 class="col-lg-3 col-md-4 col-sm-12 col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'transactionsWithoutBudget'|_ }}</h3>
</div>
<div class="box-body">
<p>
<a href="{{ route('budgets.no-budget', [start.format('Y-m-d'), end.format('Y-m-d')]) }}">
{{ trans('firefly.transactions_no_budget', {start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat)}) }}
</a>
</p>
</div>
</div>
{% if paginator.count > 0 and inactive.count > 0 %}
<div class="box" id="createBudgetBox">
<div class="box-header with-border">
<h3 class="box-title">{{ 'createBudget'|_ }}</h3>
</div>
<div class="box-body">
<a href="{{ route('budgets.create') }}" class="btn btn-success pull-right">{{ 'createBudget'|_ }}</a>
</div>
</div>
{% endif %}
</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')} %}
{# make FF ignore demo for now. #}
{% set shownDemo = true %}
{% endif %}
{# date thing #}
<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">
@@ -102,23 +17,29 @@
<div class="col-lg-2 col-md-4 col-sm-12 col-xs-12"> <div class="col-lg-2 col-md-4 col-sm-12 col-xs-12">
<select class="form-control selectPeriod" name="previous"> <select class="form-control selectPeriod" name="previous">
<option label="{{ 'select_date'|_ }}" value="x">{{ 'select_date'|_ }}</option> <option label="{{ 'select_date'|_ }}" value="x">{{ 'select_date'|_ }}</option>
{% for format, previousLabel in previousLoop %} {% for array in prevLoop %}
<option label="{{ previousLabel }}" value="{{ format }}">{{ previousLabel }}</option> <option label="{{ array.title }}" value="{{ array.label }}" data-start="{{ array.start.format('Y-m-d') }}"
data-end="{{ array.end.format('Y-m-d') }}">{{ array.title }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
<div class="col-lg-8 col-md-4 col-sm-12 col-xs-12 text-center"> <div class="col-lg-8 col-md-4 col-sm-12 col-xs-12 text-center">
<div class="btn btn-group btn-group-lg" style="padding-top:0;"> <div class="btn btn-group btn-group-lg" style="padding-top:0;">
<a href="{{ route('budgets.index', [prev.format('Y-m-d')]) }}?page={{ page }}" class="btn btn-default" title="{{ prevText }}">&larr;</a> <a href="{{ route('budgets.index', [prevLoop[0].start.format('Y-m-d'), prevLoop[0].end.format('Y-m-d')]) }}?page={{ page }}"
<a href="{{ route('budgets.index', [start.format('Y-m-d')]) }}?page={{ page }}" class="btn btn-default">{{ currentMonth }}</a> class="btn btn-default" title="{{ prevLoop[0].title }}">&larr;</a>
<a href="{{ route('budgets.index', [next.format('Y-m-d')]) }}?page={{ page }}" class="btn btn-default" title="{{ nextText }}">&rarr;</a> <a title="{{ start.formatLocalized(monthAndDayFormat) }} - {{ end.formatLocalized(monthAndDayFormat) }}"
href="{{ route('budgets.index', [start.format('Y-m-d'), end.format('Y-m-d')]) }}?page={{ page }}"
class="btn btn-default">{{ periodTitle }}</a>
<a href="{{ route('budgets.index', [nextLoop[0].start.format('Y-m-d'), nextLoop[0].end.format('Y-m-d')]) }}?page={{ page }}"
class="btn btn-default" title="{{ nextLoop[0].title }}">&rarr;</a>
</div> </div>
</div> </div>
<div class="col-lg-2 col-md-4 col-sm-12 col-xs-12 text-right"> <div class="col-lg-2 col-md-4 col-sm-12 col-xs-12 text-right">
<select class="form-control selectPeriod" name="next"> <select class="form-control selectPeriod" name="next">
<option label="{{ 'select_date'|_ }}" value="x">{{ 'select_date'|_ }}</option> <option label="{{ 'select_date'|_ }}" value="x">{{ 'select_date'|_ }}</option>
{% for format, nextLabel in nextLoop %} {% for array in nextLoop %}
<option label="{{ nextLabel }}" value="{{ format }}">{{ nextLabel }}</option> <option label="{{ array.title }}" value="{{ array.label }}" data-start="{{ array.start.format('Y-m-d') }}"
data-end="{{ array.end.format('Y-m-d') }}">{{ array.title }}</option>
{% endfor %} {% endfor %}
</select> </select>
</div> </div>
@@ -127,6 +48,209 @@
</div> </div>
</div> </div>
</div> </div>
<!-- available budget configurator. -->
{% if availableBudgets|length == 0 %}
<div class="row">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">
{{ trans('firefly.total_available_budget_in_currency', {currency: defaultCurrency.name}) }}
<br>
<small>{{ trans('firefly.between_dates_breadcrumb', {start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat)}) }}</small>
</h3>
</div>
<div class="box-body">
<div class="row">
{# info about the amount budgeted #}
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-3">
<small>{{ 'budgeted'|_ }} ({{ 'see_below'|_ }}):
<span class="budgeted_amount" data-value="{{ budgeted }}" data-id="0">
{{ formatAmountBySymbol(budgeted, defaultCurrency.symbol, defaultCurrency.decimal_places) }}
</span>
</small>
</div>
{# info about the amount spent #}
<div class="col-lg-9 col-md-9 col-sm-9 col-xs-9" style="text-align:right;margin-bottom:3px;">
<small class="available_bar"
data-id="0">{{ trans('firefly.available_between', {start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat) }) }}
:
<span class="available_amount" data-id="0" data-value="0"
data-value="0">{{ formatAmountBySymbol(0, defaultCurrency.symbol, defaultCurrency.decimal_places, true) }}</span>
<a href="#" data-id="0" class="update_ab btn btn-default btn-xs"><i class="fa fa-pencil"></i></a>
</small>
</div>
</div>
{# info text to show how much is spent (in currency). #}
<div class="row spentInfo" data-id="0" data-value="{{ spent }}">
<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)}) }}
: {{ formatAmountBySymbol(spent, defaultCurrency.symbol, defaultCurrency.decimal_places) }} </small>
</div>
</div>
</div>
</div>
{% if enableAddButton %}
<div class="col-lg-12">
<p class="pull-right">
<a href="#" class="btn btn-success btn-xs create_ab_alt">
<i class="fa fa-plus-circle"></i>
{{ 'alt_currency_ab_create'|_ }}</a>
</p>
</div>
{% endif %}
</div>
</div>
{% endif %}
{% set boxSize = 12 %}
{% if availableBudgets|length > 1 %}
{% set boxSize = 6 %}
{% endif %}
{% if availableBudgets|length > 0 %}
<div class="row">
{% for budget in availableBudgets %}
<div class="col-lg-{{ boxSize }} col-md-12 col-sm-12 col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">
{{ trans('firefly.total_available_budget_in_currency', {currency: budget.transaction_currency.name}) }}
<br>
<small>{{ trans('firefly.between_dates_breadcrumb', {start: budget.start_date.formatLocalized(monthAndDayFormat), end: budget.end_date.formatLocalized(monthAndDayFormat)}) }}</small>
</h3>
</div>
<div class="box-body">
<div class="row">
{# info about the amount budgeted #}
<div class="col-lg-3 col-md-3 col-sm-3 col-xs-3">
<small>{{ 'budgeted'|_ }}:
<span class="text-success budgeted_amount" data-id="{{ budget.id }}">
{{ formatAmountBySymbol(budget.budgeted, budget.transaction_currency.symbol, budget.transaction_currency.decimal_places, false) }}
</span>
</small>
</div>
{# info about the amount spent #}
<div class="col-lg-9 col-md-9 col-sm-9 col-xs-9" style="text-align:right;margin-bottom:3px;">
<small class="available_bar"
data-id="{{ budget.id }}">{{ trans('firefly.available_between', {start: budget.start_date.formatLocalized(monthAndDayFormat), end: budget.end_date.formatLocalized(monthAndDayFormat) }) }}
:
<span class="available_amount" data-id="{{ budget.id }}"
data-value="{{ budget.amount }}">{{ formatAmountBySymbol(budget.amount, budget.transaction_currency.symbol, budget.transaction_currency.decimal_places, true) }}</span>
<a href="#" data-id="{{ budget.id }}" class="update_ab btn btn-default btn-xs"><i class="fa fa-pencil"></i></a>
<a href="{{ route('available-budgets.delete', [budget.id]) }}" data-id="{{ budget.id }}"
class="delete_ab btn btn-danger btn-xs"><i class="fa fa-trash"></i></a>
</small>
</div>
</div>
{# 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-bar progress-bar-danger" data-id="{{ budget.id }}" role="progressbar" aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100" style="width: 0;"></div>
<div class="progress-bar progress-bar-warning" data-id="{{ budget.id }}" role="progressbar" aria-valuenow="10"
aria-valuemin="0"
aria-valuemax="100" style="width: 0;"></div>
<div class="progress-bar progress-bar-info" data-id="{{ budget.id }}" role="progressbar" aria-valuenow="0"
aria-valuemin="0"
aria-valuemax="100" style="width: 0;"></div>
</div>
</div>
</div>
{# info text to show how much is spent (in currency). #}
<div class="row spentInfo" data-id="{{ budget.id }}">
<div class="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<small>{{ trans('firefly.spent_between', {start: budget.start_date.formatLocalized(monthAndDayFormat), end: budget.start_date.formatLocalized(monthAndDayFormat)}) }}
: {{ formatAmountBySymbol(budget.spent, budget.transaction_currency.symbol, budget.transaction_currency.decimal_places) }} </small>
</div>
</div>
{# 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-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>
{% endfor %}
{% if enableAddButton %}
<div class="col-lg-12">
<p class="pull-right">
<a href="#" class="btn btn-success btn-xs create_ab_alt">
<i class="fa fa-plus-circle"></i>
{{ 'alt_currency_ab_create'|_ }}</a>
</p>
</div>
{% 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 %}
{% 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="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">
@@ -162,6 +286,7 @@
</div> </div>
</td> </td>
<td data-value="{{ budget.name }}"> <td data-value="{{ budget.name }}">
{% if budgetInformation[budget.id]['currentLimit'] %} {% if budgetInformation[budget.id]['currentLimit'] %}
<a href="{{ route('budgets.show.limit', [budget.id, budgetInformation[budget.id]['currentLimit'].id]) }}" <a href="{{ route('budgets.show.limit', [budget.id, budgetInformation[budget.id]['currentLimit'].id]) }}"
class="budget-link" class="budget-link"
@@ -188,14 +313,18 @@
</td> </td>
<td class="hidden-sm hidden-xs spent" data-id="{{ budget.id }}" data-spent="{{ budgetInformation[budget.id]['spent'] }}" <td class="hidden-sm hidden-xs spent" data-id="{{ budget.id }}" data-spent="{{ budgetInformation[budget.id]['spent'] }}"
data-value="{{ budgetInformation[budget.id]['spent'] }}"> data-value="{{ budgetInformation[budget.id]['spent'] }}">
{{ budgetInformation[budget.id]['spent']|formatAmount }} {{ "-1"|formatAmount }}
({{ (budgetInformation[budget.id]['spent'] / activeDaysPassed)|formatAmount }}) {#{{ budgetInformation[budget.id]['spent']|formatAmount }}#}
({{ "-1"|formatAmount }})
{#({{ (budgetInformation[budget.id]['spent'] / activeDaysPassed)|formatAmount }})#}
</td> </td>
<td class="left" data-id="{{ budget.id }}" <td class="left" data-id="{{ budget.id }}"
data-value="{{ (repAmount + budgetInformation[budget.id]['spent']) }}"> data-value="{{ (repAmount + budgetInformation[budget.id]['spent']) }}">
{{ (repAmount + budgetInformation[budget.id]['spent'])|formatAmount }} {{ "-1"|formatAmount }}
{#{{ (repAmount + budgetInformation[budget.id]['spent'])|formatAmount }}#}
{% if repAmount + budgetInformation[budget.id]['spent'] > 0 %} {% if repAmount + budgetInformation[budget.id]['spent'] > 0 %}
({{ ((repAmount + budgetInformation[budget.id]['spent']) / activeDaysLeft)|formatAmount }}) ({{ "-1"|formatAmount }})
{#({{ ((repAmount + budgetInformation[budget.id]['spent']) / activeDaysLeft)|formatAmount }})#}
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
@@ -213,8 +342,35 @@
</div> </div>
</div> </div>
{% if inactive|length > 0 %}
<div class="row"> <div class="row">
<div class="col-lg-3 col-md-4 col-sm-12 col-xs-12">
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ 'transactionsWithoutBudget'|_ }}</h3>
</div>
<div class="box-body">
<p>
<a href="{{ route('budgets.no-budget', [start.format('Y-m-d'), end.format('Y-m-d')]) }}">
{{ trans('firefly.transactions_no_budget', {start: start.formatLocalized(monthAndDayFormat), end: end.formatLocalized(monthAndDayFormat)}) }}
</a>
</p>
</div>
</div>
</div>
{% if paginator.count > 0 and inactive.count > 0 %}
<div class="col-lg-3 col-md-4 col-sm-12 col-xs-12">
<div class="box" id="createBudgetBox">
<div class="box-header with-border">
<h3 class="box-title">{{ 'createBudget'|_ }}</h3>
</div>
<div class="box-body">
<a href="{{ route('budgets.create') }}" class="btn btn-success pull-right">{{ 'createBudget'|_ }}</a>
</div>
</div>
</div>
{% endif %}
{% if inactive|length > 0 %}
<div class="col-lg-3 col-sm-4 col-md-6"> <div class="col-lg-3 col-sm-4 col-md-6">
<div class="box"> <div class="box">
<div class="box-header with-border"> <div class="box-header with-border">
@@ -231,8 +387,9 @@
</div> </div>
</div> </div>
</div> </div>
</div> {% endif %}
{% endif %} </div>
{% endblock %} {% endblock %}
{% block styles %} {% block styles %}
@@ -243,14 +400,17 @@
<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: // actually spent bar data:
var spent = {{ spent * -1 }}; // must be positive for the calculation to work. var spent = 0;
var page = {{ page }}; var page = {{ page }};
// budgeted data: // budgeted data:
var budgeted = {{ budgeted }}; var budgeted = 0;
var available = {{ available }}; var available = 0;
var budgetIndexUri = "{{ route('budgets.index','REPLACE') }}"; var budgetIndexUri = "{{ route('budgets.index',['START','END']) }}";
var budgetAmountUri = "{{ route('budgets.amount','REPLACE') }}"; var createAvailableBudgetUri = "{{ route('available-budgets.create', [start.format('Y-m-d'), end.format('Y-m-d')]) }}";
var updateIncomeUri = "{{ route('budgets.income',[start.format('Y-m-d'),end.format('Y-m-d')]) }}?page={{ page }}"; 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 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') }}"; var periodStart = "{{ start.format('Y-m-d') }}";
var periodEnd = "{{ end.format('Y-m-d') }}"; var periodEnd = "{{ end.format('Y-m-d') }}";
</script> </script>

View File

@@ -220,11 +220,41 @@ Route::group(
Route::get('{start_date?}/{end_date?}', ['uses' => 'Budget\IndexController@index', 'as' => 'index']); Route::get('{start_date?}/{end_date?}', ['uses' => 'Budget\IndexController@index', 'as' => 'index']);
// update budget amount and income amount // update budget amount and income amount
Route::get('income/{start_date}/{end_date}', ['uses' => 'Budget\AmountController@updateIncome', 'as' => 'income']); //Route::get('income/{start_date}/{end_date}', ['uses' => 'Budget\AmountController@updateIncome', 'as' => 'income']);
Route::post('income', ['uses' => 'Budget\AmountController@postUpdateIncome', 'as' => 'income.post']); //Route::post('income', ['uses' => 'Budget\AmountController@postUpdateIncome', 'as' => 'income.post']);
Route::post('amount/{budget}', ['uses' => 'Budget\AmountController@amount', 'as' => 'amount']); //Route::post('amount/{budget}', ['uses' => 'Budget\AmountController@amount', 'as' => 'amount']);
}
);
/**
* Available Budget Controller
*/
Route::group(
['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']);
//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']);
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']);
} }
); );