Updated website boxes.

This commit is contained in:
James Cole 2017-09-27 08:45:27 +02:00
parent 6f55049fb6
commit 3e39deec36
No known key found for this signature in database
GPG Key ID: C16961E655E74B5E
9 changed files with 445 additions and 163 deletions

View File

@ -67,8 +67,10 @@ class BalanceReportHelper implements BalanceReportHelperInterface
/** @var BudgetLimit $budgetLimit */
foreach ($budgetLimits as $budgetLimit) {
$line = $this->createBalanceLine($budgetLimit, $accounts);
$balance->addBalanceLine($line);
if (!is_null($budgetLimit->budget)) {
$line = $this->createBalanceLine($budgetLimit, $accounts);
$balance->addBalanceLine($line);
}
}
$noBudgetLine = $this->createNoBudgetLine($accounts, $start, $end);

View File

@ -198,7 +198,7 @@ class BudgetController extends Controller
$inactive = $this->repository->getInactiveBudgets();
$periodStart = $start->formatLocalized($this->monthAndDayFormat);
$periodEnd = $end->formatLocalized($this->monthAndDayFormat);
$budgetInformation = $this->collectBudgetInformation($budgets, $start, $end);
$budgetInformation = $this->repository->collectBudgetInformation($budgets, $start, $end);
$defaultCurrency = Amount::getDefaultCurrency();
$available = $this->repository->getAvailableBudget($defaultCurrency, $start, $end);
$spent = array_sum(array_column($budgetInformation, 'spent'));
@ -252,8 +252,8 @@ class BudgetController extends Controller
*
* @return View
*
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
* @SuppressWarnings(PHPMD.ExcessiveMethodLength)
*/
public function noBudget(Request $request, JournalRepositoryInterface $repository, string $moment = '')
{
@ -457,50 +457,6 @@ class BudgetController extends Controller
return view('budgets.income', compact('available', 'start', 'end'));
}
/**
* @param Collection $budgets
* @param Carbon $start
* @param Carbon $end
*
* @return array
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
private function collectBudgetInformation(Collection $budgets, Carbon $start, Carbon $end): array
{
// get account information
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
$return = [];
/** @var Budget $budget */
foreach ($budgets as $budget) {
$budgetId = $budget->id;
$return[$budgetId] = [
'spent' => $this->repository->spentInPeriod(new Collection([$budget]), $accounts, $start, $end),
'budgeted' => '0',
'currentRep' => false,
];
$budgetLimits = $this->repository->getBudgetLimits($budget, $start, $end);
$otherLimits = new Collection;
// get all the budget limits relevant between start and end and examine them:
/** @var BudgetLimit $limit */
foreach ($budgetLimits as $limit) {
if ($limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end)
) {
$return[$budgetId]['currentLimit'] = $limit;
$return[$budgetId]['budgeted'] = $limit->amount;
continue;
}
// otherwise it's just one of the many relevant repetitions:
$otherLimits->push($limit);
}
$return[$budgetId]['otherLimits'] = $otherLimits;
}
return $return;
}
/**
* @param Budget $budget
* @param Carbon $start

View File

@ -0,0 +1,194 @@
<?php
/**
* BoxController.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* This software may be modified and distributed under the terms of the
* Creative Commons Attribution-ShareAlike 4.0 International License.
*
* See the LICENSE file for details.
*/
declare(strict_types=1);
namespace FireflyIII\Http\Controllers\Json;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Http\Controllers\Controller;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
use FireflyIII\Support\CacheProperties;
use Response;
/**
* Class BoxController
*
* @package FireflyIII\Http\Controllers\Json
*/
class BoxController extends Controller
{
/**
*
*/
public function available(BudgetRepositoryInterface $repository)
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
$today = new Carbon;
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty($today);
$cache->addProperty('box-available');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
// get available amount
$currency = app('amount')->getDefaultCurrency();
$available = $repository->getAvailableBudget($currency, $start, $end);
// get spent amount:
$budgets = $repository->getActiveBudgets();
$budgetInformation = $repository->collectBudgetInformation($budgets, $start, $end);
$spent = strval(array_sum(array_column($budgetInformation, 'spent')));
$left = bcadd($available, $spent);
// left less than zero? then it's zero:
if (bccomp($left, '0') === -1) {
$left = '0';
}
$days = $today->diffInDays($end);
$perDay = '0';
if ($days !== 0) {
$perDay = bcdiv($left, strval($days));
}
$return = [
'perDay' => app('amount')->formatAnything($currency, $perDay, false),
'left' => app('amount')->formatAnything($currency, $left, false),
];
$cache->store($return);
return Response::json($return);
}
/**
* @return \Illuminate\Http\JsonResponse
*/
public function balance()
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('box-balance');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
// try a collector for income:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)
->setTypes([TransactionType::DEPOSIT])
->withOpposingAccount();
$income = strval($collector->getJournals()->sum('transaction_amount'));
$currency = app('amount')->getDefaultCurrency();
// expense:
// try a collector for expenses:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)
->setTypes([TransactionType::WITHDRAWAL])
->withOpposingAccount();
$expense = strval($collector->getJournals()->sum('transaction_amount'));
$response = [
'income' => app('amount')->formatAnything($currency, $income, false),
'expense' => app('amount')->formatAnything($currency, $expense, false),
'combined' => app('amount')->formatAnything($currency, bcadd($income, $expense), false),
];
$cache->store($response);
return Response::json($response);
}
/**
* @param BillRepositoryInterface $repository
*
* @return \Illuminate\Http\JsonResponse
*/
public function bills(BillRepositoryInterface $repository)
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('box-bills');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
/*
* Since both this method and the chart use the exact same data, we can suffice
* with calling the one method in the bill repository that will get this amount.
*/
$paidAmount = bcmul($repository->getBillsPaidInRange($start, $end), '-1');
$unpaidAmount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount.
$currency = app('amount')->getDefaultCurrency();
$return = [
'paid' => app('amount')->formatAnything($currency, $paidAmount, false),
'unpaid' => app('amount')->formatAnything($currency, $unpaidAmount, false),
];
$cache->store($return);
return Response::json($return);
}
/**
* @param AccountRepositoryInterface $repository
*
* @return \Illuminate\Http\JsonResponse
*/
public function netWorth(AccountRepositoryInterface $repository)
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('box-net-worth');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
$accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
$currency = app('amount')->getDefaultCurrency();
$balances = app('steam')->balancesByAccounts($accounts, new Carbon);
$sum = '0';
foreach ($balances as $entry) {
$sum = bcadd($sum, $entry);
}
$return = [
'net_worth' => app('amount')->formatAnything($currency, $sum, false),
];
$cache->store($return);
return Response::json($return);
}
}

View File

@ -60,114 +60,6 @@ class JsonController extends Controller
return Response::json(['html' => $view]);
}
/**
* @param BillRepositoryInterface $repository
*
* @return \Symfony\Component\HttpFoundation\Response
*/
public function boxBillsPaid(BillRepositoryInterface $repository)
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
/*
* Since both this method and the chart use the exact same data, we can suffice
* with calling the one method in the bill repository that will get this amount.
*/
$amount = $repository->getBillsPaidInRange($start, $end); // will be a negative amount.
$amount = bcmul($amount, '-1');
$currency = Amount::getDefaultCurrency();
$data = ['box' => 'bills-paid', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
return Response::json($data);
}
/**
* @param BillRepositoryInterface $repository
*
* @return \Illuminate\Http\JsonResponse
*/
public function boxBillsUnpaid(BillRepositoryInterface $repository)
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
$amount = $repository->getBillsUnpaidInRange($start, $end); // will be a positive amount.
$currency = Amount::getDefaultCurrency();
$data = ['box' => 'bills-unpaid', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
return Response::json($data);
}
/**
* @return \Illuminate\Http\JsonResponse
* @internal param AccountTaskerInterface $accountTasker
* @internal param AccountRepositoryInterface $repository
*
*/
public function boxIn()
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
// works for json too!
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('box-in');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
// try a collector for income:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)
->setTypes([TransactionType::DEPOSIT])
->withOpposingAccount();
$amount = strval($collector->getJournals()->sum('transaction_amount'));
$currency = Amount::getDefaultCurrency();
$data = ['box' => 'in', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
$cache->store($data);
return Response::json($data);
}
/**
* @return \Symfony\Component\HttpFoundation\Response
* @internal param AccountTaskerInterface $accountTasker
* @internal param AccountRepositoryInterface $repository
*
*/
public function boxOut()
{
$start = session('start', Carbon::now()->startOfMonth());
$end = session('end', Carbon::now()->endOfMonth());
// works for json too!
$cache = new CacheProperties;
$cache->addProperty($start);
$cache->addProperty($end);
$cache->addProperty('box-out');
if ($cache->has()) {
return Response::json($cache->get()); // @codeCoverageIgnore
}
// try a collector for expenses:
/** @var JournalCollectorInterface $collector */
$collector = app(JournalCollectorInterface::class);
$collector->setAllAssetAccounts()->setRange($start, $end)
->setTypes([TransactionType::WITHDRAWAL])
->withOpposingAccount();
$amount = strval($collector->getJournals()->sum('transaction_amount'));
$currency = Amount::getDefaultCurrency();
$data = ['box' => 'out', 'amount' => Amount::formatAnything($currency, $amount, false), 'amount_raw' => $amount];
$cache->store($data);
return Response::json($data);
}
/**
* @param BudgetRepositoryInterface $repository
*

View File

@ -15,6 +15,7 @@ namespace FireflyIII\Repositories\Budget;
use Carbon\Carbon;
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
use FireflyIII\Models\AccountType;
use FireflyIII\Models\AvailableBudget;
use FireflyIII\Models\Budget;
use FireflyIII\Models\BudgetLimit;
@ -22,6 +23,7 @@ use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Models\TransactionJournal;
use FireflyIII\Models\TransactionType;
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
use FireflyIII\User;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
@ -220,6 +222,53 @@ class BudgetRepository implements BudgetRepositoryInterface
return $set;
}
/**
* This method collects various info on budgets, used on the budget page and on the index.
*
* @param Collection $budgets
* @param Carbon $start
* @param Carbon $end
*
* @return array
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function collectBudgetInformation(Collection $budgets, Carbon $start, Carbon $end): array
{
// get account information
/** @var AccountRepositoryInterface $accountRepository */
$accountRepository = app(AccountRepositoryInterface::class);
$accounts = $accountRepository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET, AccountType::CASH]);
$return = [];
/** @var Budget $budget */
foreach ($budgets as $budget) {
$budgetId = $budget->id;
$return[$budgetId] = [
'spent' => $this->spentInPeriod(new Collection([$budget]), $accounts, $start, $end),
'budgeted' => '0',
'currentRep' => false,
];
$budgetLimits = $this->getBudgetLimits($budget, $start, $end);
$otherLimits = new Collection;
// get all the budget limits relevant between start and end and examine them:
/** @var BudgetLimit $limit */
foreach ($budgetLimits as $limit) {
if ($limit->start_date->isSameDay($start) && $limit->end_date->isSameDay($end)
) {
$return[$budgetId]['currentLimit'] = $limit;
$return[$budgetId]['budgeted'] = $limit->amount;
continue;
}
// otherwise it's just one of the many relevant repetitions:
$otherLimits->push($limit);
}
$return[$budgetId]['otherLimits'] = $otherLimits;
}
return $return;
}
/**
* @param TransactionCurrency $currency
* @param Carbon $start

View File

@ -32,6 +32,18 @@ interface BudgetRepositoryInterface
*/
public function cleanupBudgets(): bool;
/**
* This method collects various info on budgets, used on the budget page and on the index.
*
* @param Collection $budgets
* @param Carbon $start
* @param Carbon $end
*
* @return array
* @SuppressWarnings(PHPMD.CyclomaticComplexity)
*/
public function collectBudgetInformation(Collection $budgets, Carbon $start, Carbon $end): array;
/**
* @param Budget $budget
*

54
public/js/ff/index.js vendored
View File

@ -28,10 +28,62 @@ function drawChart() {
columnChart(accountExpenseUri, 'expense-accounts-chart');
columnChart(accountRevenueUri, 'revenue-accounts-chart');
// get balance box:
getBalanceBox();
getBillsBox();
getAvailableBox();
getNetWorthBox();
getBoxAmounts();
//getBoxAmounts();
}
function getNetWorthBox() {
// box-net-worth
$.getJSON('json/box/net-worth').done(function(data) {
$('#box-net-worth').html(data.net_worth);
});
}
/**
*
*/
function getAvailableBox() {
// box-left-to-spend
// box-left-per-day
$.getJSON('json/box/available').done(function(data) {
$('#box-left-to-spend').html(data.left);
$('#box-left-per-day').html(data.perDay);
});
}
/**
*
*/
function getBillsBox() {
// box-bills-unpaid
// box-bills-paid
$.getJSON('json/box/bills').done(function(data) {
$('#box-bills-paid').html(data.paid);
$('#box-bills-unpaid').html(data.unpaid);
});
}
/**
*
*/
function getBalanceBox() {
// box-balance-total
// box-balance-out
// box-balance-in
$.getJSON('json/box/balance').done(function(data) {
$('#box-balance-total').html(data.combined);
$('#box-balance-in').html(data.income);
$('#box-balance-out').html(data.expense);
});
}
function getBoxAmounts() {
"use strict";
var boxes = ['in', 'out', 'bills-unpaid', 'bills-paid'];

View File

@ -6,8 +6,90 @@
{% set boxClasses = 'col-lg-4 col-md-4 col-sm-6 col-xs-12' %}
{% endif %}
<!-- Info boxes -->
<div class="row hidden-sm hidden-xs">
{# box for in and out#}
<div class="{{ boxClasses }}">
<div class="info-box bg-aqua-gradient">
<span class="info-box-icon"><i class="fa fa-balance-scale"></i></span>
<div class="info-box-content">
<span class="info-box-text">{{ 'balance'|_ }}</span>
<span class="info-box-number" id="box-balance-total"></span>
<div class="progress">
<div class="progress-bar" style="width: 0%"></div>
</div>
<span class="progress-description">
<span title="{{ 'expenses'|_ }}" id="box-balance-out"></span> / <span title="{{ 'income'|_ }}" id="box-balance-in"></span>
</span>
</div>
</div>
</div>
{# box for bills #}
<div class="{{ boxClasses }}">
<div class="info-box bg-teal-gradient">
<span class="info-box-icon"><i class="fa fa-calendar-o"></i></span>
<div class="info-box-content">
<span class="info-box-text">{{ 'bills_to_pay'|_ }}</span>
<span class="info-box-number" id="box-bills-unpaid"></span>
<div class="progress">
<div class="progress-bar" style="width: 0%"></div>
</div>
<span class="progress-description">
{{ 'paid'|_ }}:
<span title="{{ 'bills_paid'|_ }}" id="box-bills-paid"></span>
</span>
</div>
</div>
</div>
{# available to spend total / per day #}
<div class="{{ boxClasses }}">
<div class="info-box bg-green-gradient">
<span class="info-box-icon"><i class="fa fa-money"></i></span>
<div class="info-box-content">
<span class="info-box-text">{{ 'left_to_spend'|_ }}</span>
<span class="info-box-number" id="box-left-to-spend"></span>
<div class="progress">
<div class="progress-bar" style="width: 0%"></div>
</div>
<span class="progress-description">
{{ 'per_day'|_ }}:
<span title="{{ 'left_to_spend_per_day'|_ }}" id="box-left-per-day"></span>
</span>
</div>
</div>
</div>
{# net worth #}
<div class="{{ boxClasses }}">
<div class="info-box bg-blue-gradient">
<span class="info-box-icon"><i class="fa fa-line-chart"></i></span>
<div class="info-box-content">
<span class="info-box-text">{{ 'net_worth'|_ }}</span>
<span class="info-box-number" id="box-net-worth"></span>
<div class="progress">
<div class="progress-bar" style="width: 0%"></div>
</div>
<span class="progress-description">
<span title="{{ 'net_worth'|_ }}" id="box-clear"></span>
</span>
</div>
</div>
</div>
{# box that shows amount spent #}
{#
<div class="{{ boxClasses }}" id="box_out_holder">
<div class="info-box">
<span class="info-box-icon bg-red">
@ -19,6 +101,10 @@
</div>
</div>
</div>
#}
{# box that shows amount earned #}
{#
<div class="{{ boxClasses }}">
<div class="info-box">
<span class="info-box-icon bg-green">
@ -31,9 +117,43 @@
</div>
</div>
</div>
#}
{# box for total net worth #}
{#
<div class="{{ boxClasses }}">
<div class="info-box">
<span class="info-box-icon bg-teal">
<a href="{{ route('transactions.index',['deposit']) }}"><i class="fa fa-download faw-fw"></i></a>
</span>
<div class="info-box-content">
<span class="info-box-text"><a href="{{ route('transactions.index',['deposit']) }}">{{ 'net_worth'|_ }}</a></span>
<span class="info-box-number"><a href="{{ route('transactions.index',['deposit']) }}" id="box-net-worth"></a></span>
</div>
</div>
</div>
#}
{# box for left to spend#}
{#
<div class="{{ boxClasses }}">
<div class="info-box">
<span class="info-box-icon bg-green">
<a href="{{ route('transactions.index',['deposit']) }}"><i class="fa fa-download faw-fw"></i></a>
</span>
<div class="info-box-content">
<span class="info-box-text"><a href="{{ route('transactions.index',['deposit']) }}">{{ 'amount_available'|_ }}</a></span>
<span class="info-box-number"><a href="{{ route('transactions.index',['deposit']) }}" id="box-in"></a></span>
</div>
</div>
</div>
#}
{#
{% if billCount > 0 %}
<div class="clearfix visible-sm-block"></div>
<!-- box that shows unpaid bills -->
<div class="{{ boxClasses }}">
<div class="info-box">
<span class="info-box-icon bg-blue">
@ -46,6 +166,8 @@
</div>
</div>
</div>
<!-- box that shows paid bills -->
<div class="{{ boxClasses }}">
<div class="info-box">
<span class="info-box-icon bg-aqua">
@ -59,4 +181,5 @@
</div>
</div>
{% endif %}
#}
</div>

View File

@ -444,10 +444,12 @@ Route::group(
Route::get('categories', ['uses' => 'JsonController@categories', 'as' => 'categories']);
Route::get('budgets', ['uses' => 'JsonController@budgets', 'as' => 'budgets']);
Route::get('tags', ['uses' => 'JsonController@tags', 'as' => 'tags']);
Route::get('box/in', ['uses' => 'JsonController@boxIn', 'as' => 'box.in']);
Route::get('box/out', ['uses' => 'JsonController@boxOut', 'as' => 'box.out']);
Route::get('box/bills-unpaid', ['uses' => 'JsonController@boxBillsUnpaid', 'as' => 'box.unpaid']);
Route::get('box/bills-paid', ['uses' => 'JsonController@boxBillsPaid', 'as' => 'box.paid']);
Route::get('box/balance', ['uses' => 'Json\BoxController@balance', 'as' => 'box.balance']);
Route::get('box/bills', ['uses' => 'Json\BoxController@bills', 'as' => 'box.bills']);
Route::get('box/available', ['uses' => 'Json\BoxController@available', 'as' => 'box.available']);
Route::get('box/net-worth', ['uses' => 'Json\BoxController@netWorth', 'as' => 'box.net-worth']);
Route::get('transaction-journals/all', ['uses' => 'Json\AutoCompleteController@allTransactionJournals', 'as' => 'all-transaction-journals']);
Route::get('transaction-journals/with-id/{tj}', ['uses' => 'Json\AutoCompleteController@journalsWithId', 'as' => 'journals-with-id']);
Route::get('transaction-journals/{what}', ['uses' => 'Json\AutoCompleteController@transactionJournals', 'as' => 'transaction-journals']);