Cleaning up the chart controller.

This commit is contained in:
James Cole
2014-09-09 07:10:16 +02:00
parent 5ca466a826
commit 9965297f36
4 changed files with 225 additions and 125 deletions

View File

@@ -26,6 +26,9 @@ $(function () {
str += '<span style="color:' + colour + '">' + point.series.name + '</span>: € ' + Highcharts.numberFormat(point.y, 2) + '<br />'; str += '<span style="color:' + colour + '">' + point.series.name + '</span>: € ' + Highcharts.numberFormat(point.y, 2) + '<br />';
} }
if (x == 1) { if (x == 1) {
str += '<span style="color:' + colour + '">' + point.series.name + '</span>: € ' + Highcharts.numberFormat(point.y, 2) + '<br />';
}
if (x == 2) {
str += '<span style="color:' + colour + '">' + point.series.name + '</span>: ' + Highcharts.numberFormat(point.y, 1) + '%<br />'; str += '<span style="color:' + colour + '">' + point.series.name + '</span>: ' + Highcharts.numberFormat(point.y, 1) + '%<br />';
} }
} }

View File

@@ -20,42 +20,35 @@ class ChartController extends BaseController
*/ */
public function __construct(ChartInterface $chart, AccountRepositoryInterface $accounts) public function __construct(ChartInterface $chart, AccountRepositoryInterface $accounts)
{ {
$this->_chart = $chart; $this->_chart = $chart;
$this->_accounts = $accounts; $this->_accounts = $accounts;
} }
/** /**
* This method takes a budget, all limits and all their repetitions and displays three numbers per repetition:
* the amount of money in the repetition (represented as "an envelope"), the amount spent and the spent percentage.
* *
* @param Budget $budget
*
* @return \Illuminate\Http\JsonResponse
*/ */
public function budgetDefault(\Budget $budget) public function budgetDefault(\Budget $budget)
{ {
$expense = []; $expense = [];
$left = []; $left = [];
$envelope = [];
// get all limit repetitions for this budget. // get all limit repetitions for this budget.
/** @var \Limit $limit */ /** @var \Limit $limit */
foreach ($budget->limits as $limit) { foreach ($budget->limits as $limit) {
/** @var \LimitRepetition $rep */ /** @var \LimitRepetition $rep */
foreach ($limit->limitrepetitions as $rep) { foreach ($limit->limitrepetitions as $rep) {
$spentInRep = \Transaction:: // get the amount of money spent in this period on this budget.
leftJoin( $spentInRep = $rep->amount - $rep->left();
'transaction_journals', 'transaction_journals.id', '=', $pct = round((floatval($spentInRep) / floatval($limit->amount)) * 100, 2);
'transactions.transaction_journal_id' $name = $rep->periodShow();
) $envelope[] = [$name, floatval($limit->amount)];
->leftJoin( $expense[] = [$name, floatval($spentInRep)];
'component_transaction_journal', 'component_transaction_journal.transaction_journal_id', $left[] = [$name, $pct];
'=',
'transaction_journals.id'
)->where('component_transaction_journal.component_id', '=', $budget->id)->where(
'transaction_journals.date', '>=', $rep->startdate->format('Y-m-d')
)->where('transaction_journals.date', '<=', $rep->enddate->format('Y-m-d'))->where(
'amount', '>', 0
)->sum('amount');
$pct = round(($spentInRep / $limit->amount) * 100, 2);
$name = $rep->periodShow();
$expense[] = [$name, floatval($spentInRep)];
$left[] = [$name, $pct];
} }
} }
@@ -63,6 +56,12 @@ class ChartController extends BaseController
'chart_title' => 'Overview for budget ' . $budget->name, 'chart_title' => 'Overview for budget ' . $budget->name,
'subtitle' => 'All envelopes', 'subtitle' => 'All envelopes',
'series' => [ 'series' => [
[
'type' => 'line',
'yAxis' => 1,
'name' => 'Amount in envelope',
'data' => $envelope
],
[ [
'type' => 'column', 'type' => 'column',
'name' => 'Expenses in envelope', 'name' => 'Expenses in envelope',
@@ -75,6 +74,7 @@ class ChartController extends BaseController
'data' => $left 'data' => $left
] ]
] ]
]; ];
@@ -82,29 +82,27 @@ class ChartController extends BaseController
} }
/** /**
* This method takes a single limit repetition (so a single "envelope") and displays the amount of money spent
* per day and subsequently how much money is left.
*
* @param LimitRepetition $rep * @param LimitRepetition $rep
*
* @return \Illuminate\Http\JsonResponse
*/ */
public function budgetLimit(\LimitRepetition $rep) public function budgetLimit(\LimitRepetition $rep)
{ {
$budget = $rep->limit->budget; $budget = $rep->limit->budget;
$current = clone $rep->startdate; $current = clone $rep->startdate;
$expense = []; $expense = [];
$leftInLimit = []; $leftInLimit = [];
$currentLeftInLimit = floatval($rep->limit->amount); $currentLeftInLimit = floatval($rep->limit->amount);
while ($current <= $rep->enddate) { while ($current <= $rep->enddate) {
$spent = \Transaction:: $spent = $this->_chart->spentOnDay($budget, $current);
leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') $spent = floatval($spent) == 0 ? null : floatval($spent);
->leftJoin( $entry = [$current->timestamp * 1000, $spent];
'component_transaction_journal', 'component_transaction_journal.transaction_journal_id', '=', $expense[] = $entry;
'transaction_journals.id'
)->where('component_transaction_journal.component_id', '=', $budget->id)->where(
'transaction_journals.date', $current->format('Y-m-d')
)->where('amount', '>', 0)->sum('amount');
$spent = floatval($spent) == 0 ? null : floatval($spent);
$entry = [$current->timestamp * 1000, $spent];
$expense[] = $entry;
$currentLeftInLimit = $currentLeftInLimit - $spent; $currentLeftInLimit = $currentLeftInLimit - $spent;
$leftInLimit[] = [$current->timestamp * 1000, $currentLeftInLimit]; $leftInLimit[] = [$current->timestamp * 1000, $currentLeftInLimit];
$current->addDay(); $current->addDay();
} }
@@ -132,53 +130,44 @@ class ChartController extends BaseController
} }
/** /**
* This method takes a budget and gets all transactions in it which haven't got an envelope (limit).
* *
* Usually this means that very old and unorganized or very NEW transactions get displayed; there was never an
* envelope or it hasn't been created (yet).
*
*
* @param Budget $budget
*
* @return \Illuminate\Http\JsonResponse
*/ */
public function budgetNoLimits(\Budget $budget) public function budgetNoLimits(\Budget $budget)
{ {
$inRepetitions = []; /*
foreach ($budget->limits as $limit) { * We can go about this two ways. Either we find all transactions which definitely are IN an envelope
foreach ($limit->limitrepetitions as $repetition) { * and exclude them or we search for transactions outside of the range of any of the envelopes we have.
$set = $budget->transactionjournals()->leftJoin( *
'transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id' * Since either is shitty we go with the first one because it's easier to build.
)->where('transaction_types.type', 'Withdrawal')->where( */
'date', '>=', $repetition->startdate->format('Y-m-d') $inRepetitions = $this->_chart->allJournalsInBudgetEnvelope($budget);
)->where('date', '<=', $repetition->enddate->format('Y-m-d'))->orderBy('date', 'DESC')->get(
['transaction_journals.id']
);
foreach ($set as $item) {
$inRepetitions[] = $item->id;
}
}
} /*
* With this set of id's, we can search for all journals NOT in that set.
$query = $budget->transactionjournals()->whereNotIn( * BUT they have to be in the budget (duh).
'transaction_journals.id', $inRepetitions */
)->orderBy('date', 'DESC')->orderBy( $set = $this->_chart->journalsNotInSet($budget, $inRepetitions);
'transaction_journals.id', 'DESC' /*
); * Next step: get all transactions for those journals.
*/
$transactions = $this->_chart->transactionsByJournals($set);
$result = $query->get(['transaction_journals.id']); /*
$set = []; * this set builds the chart:
foreach ($result as $entry) { */
$set[] = $entry->id;
}
// all transactions for these journals, grouped by date and SUM
$transactions = \Transaction::whereIn('transaction_journal_id', $set)->leftJoin(
'transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id'
)
->groupBy('transaction_journals.date')->where('amount', '>', 0)->get(
['transaction_journals.date', DB::Raw('SUM(`amount`) as `aggregate`')]
);
// this set builds the chart:
$expense = []; $expense = [];
foreach ($transactions as $t) { foreach ($transactions as $t) {
$date = new Carbon($t->date); $date = new Carbon($t->date);
$expense[] = [$date->timestamp * 1000, floatval($t->aggregate)]; $expense[] = [$date->timestamp * 1000, floatval($t->aggregate)];
} }
$return = [ $return = [
@@ -186,45 +175,36 @@ class ChartController extends BaseController
'subtitle' => 'Not organized by an envelope', 'subtitle' => 'Not organized by an envelope',
'series' => [ 'series' => [
[ [
'type' => 'spline', 'type' => 'column',
'name' => 'Expenses per day', 'name' => 'Expenses per day',
'data' => $expense 'data' => $expense
] ]
] ]
]; ];
return Response::json($return); return Response::json($return);
} }
/** /**
* @param Budget $budget
* *
* @return \Illuminate\Http\JsonResponse
*/ */
public function budgetSession(\Budget $budget) public function budgetSession(\Budget $budget)
{ {
$expense = []; $expense = [];
$repetitionSeries = []; $repetitionSeries = [];
$current = clone Session::get('start'); $current = clone Session::get('start');
$end = clone Session::get('end'); $end = clone Session::get('end');
while ($current <= $end) { while ($current <= $end) {
$spent = \Transaction:: $spent = $this->_chart->spentOnDay($budget, $current);
leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') $spent = floatval($spent) == 0 ? null : floatval($spent);
->leftJoin( $expense[] = [$current->timestamp * 1000, $spent];
'component_transaction_journal', 'component_transaction_journal.transaction_journal_id', '=',
'transaction_journals.id'
)->where('component_transaction_journal.component_id', '=', $budget->id)->where(
'transaction_journals.date', $current->format('Y-m-d')
)->where('amount', '>', 0)->sum('amount');
$spent = floatval($spent) == 0 ? null : floatval($spent);
if (!is_null($spent)) {
$expense[] = [$current->timestamp * 1000, $spent];
}
$current->addDay(); $current->addDay();
} }
// find all limit repetitions (for this budget) between start and end. // find all limit repetitions (for this budget) between start and end.
$start = clone Session::get('start'); $start = clone Session::get('start');
$repetitionSeries[] = [ $repetitionSeries[] = [
'type' => 'column', 'type' => 'column',
'name' => 'Expenses per day', 'name' => 'Expenses per day',
@@ -234,7 +214,7 @@ class ChartController extends BaseController
/** @var \Limit $limit */ /** @var \Limit $limit */
foreach ($budget->limits as $limit) { foreach ($budget->limits as $limit) {
$reps = $limit->limitrepetitions()->where( $reps = $limit->limitrepetitions()->where(
function ($q) use ($start, $end) { function ($q) use ($start, $end) {
// startdate is between range // startdate is between range
$q->where( $q->where(
@@ -252,8 +232,7 @@ class ChartController extends BaseController
} }
); );
} }
) )->get();
->get();
$currentLeftInLimit = floatval($limit->amount); $currentLeftInLimit = floatval($limit->amount);
/** @var \LimitRepetition $repetition */ /** @var \LimitRepetition $repetition */
foreach ($reps as $repetition) { foreach ($reps as $repetition) {
@@ -265,11 +244,12 @@ class ChartController extends BaseController
'name' => 'Envelope in ' . $repetition->periodShow(), 'name' => 'Envelope in ' . $repetition->periodShow(),
'data' => [] 'data' => []
]; ];
$current = clone $repetition->startdate; $current = clone $repetition->startdate;
while ($current <= $repetition->enddate) { while ($current <= $repetition->enddate) {
if ($current >= Session::get('start') && $current <= Session::get('end')) { if ($current >= Session::get('start') && $current <= Session::get('end')) {
// spent on limit: // spent on limit:
$spentSoFar = \Transaction::
$spentSoFar = \Transaction::
leftJoin( leftJoin(
'transaction_journals', 'transaction_journals.id', '=', 'transaction_journals', 'transaction_journals.id', '=',
'transactions.transaction_journal_id' 'transactions.transaction_journal_id'
@@ -283,7 +263,7 @@ class ChartController extends BaseController
)->where('transaction_journals.date', '<=', $current->format('Y-m-d'))->where( )->where('transaction_journals.date', '<=', $current->format('Y-m-d'))->where(
'amount', '>', 0 'amount', '>', 0
)->sum('amount'); )->sum('amount');
$spent = floatval($spent) == 0 ? null : floatval($spent); $spent = floatval($spent) == 0 ? null : floatval($spent);
$currentLeftInLimit = floatval($limit->amount) - floatval($spentSoFar); $currentLeftInLimit = floatval($limit->amount) - floatval($spentSoFar);
$currentSerie['data'][] = [$current->timestamp * 1000, $currentLeftInLimit]; $currentSerie['data'][] = [$current->timestamp * 1000, $currentLeftInLimit];
@@ -320,11 +300,11 @@ class ChartController extends BaseController
public function categoryShowChart(Category $category) public function categoryShowChart(Category $category)
{ {
$start = Session::get('start'); $start = Session::get('start');
$end = Session::get('end'); $end = Session::get('end');
$range = Session::get('range'); $range = Session::get('range');
$serie = $this->_chart->categoryShowChart($category, $range, $start, $end); $serie = $this->_chart->categoryShowChart($category, $range, $start, $end);
$data = [ $data = [
'chart_title' => $category->name, 'chart_title' => $category->name,
'subtitle' => '<a href="' . route('categories.show', [$category->id]) . '">View more</a>', 'subtitle' => '<a href="' . route('categories.show', [$category->id]) . '">View more</a>',
'series' => $serie 'series' => $serie
@@ -344,23 +324,23 @@ class ChartController extends BaseController
{ {
// get preferences and accounts (if necessary): // get preferences and accounts (if necessary):
$start = Session::get('start'); $start = Session::get('start');
$end = Session::get('end'); $end = Session::get('end');
if (is_null($account)) { if (is_null($account)) {
// get, depending on preferences: // get, depending on preferences:
/** @var \Firefly\Helper\Preferences\PreferencesHelperInterface $prefs */ /** @var \Firefly\Helper\Preferences\PreferencesHelperInterface $prefs */
$prefs = \App::make('Firefly\Helper\Preferences\PreferencesHelperInterface'); $prefs = \App::make('Firefly\Helper\Preferences\PreferencesHelperInterface');
$pref = $prefs->get('frontpageAccounts', []); $pref = $prefs->get('frontpageAccounts', []);
/** @var \Firefly\Storage\Account\AccountRepositoryInterface $acct */ /** @var \Firefly\Storage\Account\AccountRepositoryInterface $acct */
$acct = \App::make('Firefly\Storage\Account\AccountRepositoryInterface'); $acct = \App::make('Firefly\Storage\Account\AccountRepositoryInterface');
$accounts = $acct->getByIds($pref->data); $accounts = $acct->getByIds($pref->data);
} else { } else {
$accounts = [$account]; $accounts = [$account];
} }
// loop and get array data. // loop and get array data.
$url = count($accounts) == 1 && is_array($accounts) $url = count($accounts) == 1 && is_array($accounts)
? '<a href="' . route('accounts.show', [$account->id]) . '">View more</a>' ? '<a href="' . route('accounts.show', [$account->id]) . '">View more</a>'
: :
'<a href="' . route('accounts.index') . '">View more</a>'; '<a href="' . route('accounts.index') . '">View more</a>';
@@ -389,7 +369,7 @@ class ChartController extends BaseController
{ {
$account = $this->_accounts->findByName($name); $account = $this->_accounts->findByName($name);
$date = Carbon::createFromDate($year, $month, $day); $date = Carbon::createFromDate($year, $month, $day);
$result = $this->_chart->accountDailySummary($account, $date); $result = $this->_chart->accountDailySummary($account, $date);
return View::make('charts.info')->with('rows', $result['rows'])->with('sum', $result['sum'])->with( return View::make('charts.info')->with('rows', $result['rows'])->with('sum', $result['sum'])->with(
@@ -413,7 +393,7 @@ class ChartController extends BaseController
public function homeCategories() public function homeCategories()
{ {
$start = Session::get('start'); $start = Session::get('start');
$end = Session::get('end'); $end = Session::get('end');
return Response::json($this->_chart->categories($start, $end)); return Response::json($this->_chart->categories($start, $end));

View File

@@ -7,6 +7,7 @@ use Firefly\Exception\FireflyException;
/** /**
* Class Chart * Class Chart
*
* @package Firefly\Helper\Controllers * @package Firefly\Helper\Controllers
*/ */
class Chart implements ChartInterface class Chart implements ChartInterface
@@ -22,8 +23,8 @@ class Chart implements ChartInterface
public function account(\Account $account, Carbon $start, Carbon $end) public function account(\Account $account, Carbon $start, Carbon $end)
{ {
$current = clone $start; $current = clone $start;
$today = new Carbon; $today = new Carbon;
$return = ['name' => $account->name, 'id' => $account->id, 'data' => []]; $return = ['name' => $account->name, 'id' => $account->id, 'data' => []];
while ($current <= $end) { while ($current <= $end) {
if ($current > $today) { if ($current > $today) {
@@ -105,7 +106,7 @@ class Chart implements ChartInterface
$data = []; $data = [];
$budgets = \Auth::user()->budgets()->with( $budgets = \Auth::user()->budgets()->with(
['limits' => function ($q) { ['limits' => function ($q) {
$q->orderBy('limits.startdate', 'ASC'); $q->orderBy('limits.startdate', 'ASC');
}, 'limits.limitrepetitions' => function ($q) use ($start) { }, 'limits.limitrepetitions' => function ($q) use ($start) {
@@ -128,15 +129,15 @@ class Chart implements ChartInterface
$rep->left = $rep->left(); $rep->left = $rep->left();
// overspent: // overspent:
if ($rep->left < 0) { if ($rep->left < 0) {
$rep->spent = ($rep->left * -1) + $rep->amount; $rep->spent = ($rep->left * -1) + $rep->amount;
$rep->overspent = $rep->left * -1; $rep->overspent = $rep->left * -1;
$total = $rep->spent + $rep->overspent; $total = $rep->spent + $rep->overspent;
$rep->spent_pct = round(($rep->spent / $total) * 100); $rep->spent_pct = round(($rep->spent / $total) * 100);
$rep->overspent_pct = 100 - $rep->spent_pct; $rep->overspent_pct = 100 - $rep->spent_pct;
} else { } else {
$rep->spent = $rep->amount - $rep->left; $rep->spent = $rep->amount - $rep->left;
$rep->spent_pct = round(($rep->spent / $rep->amount) * 100); $rep->spent_pct = round(($rep->spent / $rep->amount) * 100);
$rep->left_pct = 100 - $rep->spent_pct; $rep->left_pct = 100 - $rep->spent_pct;
} }
@@ -165,9 +166,9 @@ class Chart implements ChartInterface
foreach ($budget->limits as $limit) { foreach ($budget->limits as $limit) {
foreach ($limit->limitrepetitions as $rep) { foreach ($limit->limitrepetitions as $rep) {
//0: envelope for period: //0: envelope for period:
$amount = floatval($rep->amount); $amount = floatval($rep->amount);
$spent = $rep->spent; $spent = $rep->spent;
$color = $spent > $amount ? '#FF0000' : null; $color = $spent > $amount ? '#FF0000' : null;
$data['series'][0]['data'][] = ['y' => $amount, 'id' => 'amount-' . $rep->id]; $data['series'][0]['data'][] = ['y' => $amount, 'id' => 'amount-' . $rep->id];
$data['series'][1]['data'][] = ['y' => $rep->spent, 'color' => $color, 'id' => 'spent-' . $rep->id]; $data['series'][1]['data'][] = ['y' => $rep->spent, 'color' => $color, 'id' => 'spent-' . $rep->id];
} }
@@ -210,10 +211,10 @@ class Chart implements ChartInterface
. ' transactions!'); . ' transactions!');
} }
$transaction = $journal->transactions[0]; $transaction = $journal->transactions[0];
$amount = floatval($transaction->amount); $amount = floatval($transaction->amount);
// get budget from journal: // get budget from journal:
$category = $journal->categories()->first(); $category = $journal->categories()->first();
$categoryName = is_null($category) ? '(no category)' : $category->name; $categoryName = is_null($category) ? '(no category)' : $category->name;
$result[$categoryName] = isset($result[$categoryName]) ? $result[$categoryName] + floatval($amount) $result[$categoryName] = isset($result[$categoryName]) ? $result[$categoryName] + floatval($amount)
@@ -334,7 +335,7 @@ class Chart implements ChartInterface
// get sum for current range: // get sum for current range:
$journals = \TransactionJournal:: $journals = \TransactionJournal::
with( with(
['transactions' => function ($q) { ['transactions' => function ($q) {
$q->where('amount', '>', 0); $q->where('amount', '>', 0);
@@ -359,7 +360,7 @@ class Chart implements ChartInterface
. ' transactions!'); . ' transactions!');
} }
$transaction = $journal->transactions[0]; $transaction = $journal->transactions[0];
$amount = floatval($transaction->amount); $amount = floatval($transaction->amount);
$currentSum += $amount; $currentSum += $amount;
} }
@@ -400,4 +401,86 @@ class Chart implements ChartInterface
} }
/**
* @param \Budget $budget
* @param Carbon $date
*
* @return float|null
*/
public function spentOnDay(\Budget $budget, Carbon $date)
{
return floatval(
\Transaction::
leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->leftJoin(
'component_transaction_journal', 'component_transaction_journal.transaction_journal_id', '=',
'transaction_journals.id'
)->where('component_transaction_journal.component_id', '=', $budget->id)->where(
'transaction_journals.date', $date->format('Y-m-d')
)->where('amount', '>', 0)->sum('amount')
);
}
/**
* @param \Budget $budget
*
* @return int[]
*/
public function allJournalsInBudgetEnvelope(\Budget $budget)
{
$inRepetitions = [];
foreach ($budget->limits as $limit) {
foreach ($limit->limitrepetitions as $repetition) {
$set = $budget
->transactionjournals()
->transactionTypes(['Withdrawal'])
->after($repetition->startdate)
->before($repetition->enddate)
->get(['transaction_journals.id']);
foreach ($set as $item) {
$inRepetitions[] = $item->id;
}
}
}
return $inRepetitions;
}
/**
* @param \Budget $budget
* @param array $ids
*
* @return mixed|void
*/
public function journalsNotInSet(\Budget $budget, array $ids)
{
$query = $budget->transactionjournals()
->whereNotIn('transaction_journals.id', $ids)
->orderBy('date', 'DESC')
->orderBy('transaction_journals.id', 'DESC');
$result = $query->get(['transaction_journals.id']);
$set = [];
foreach ($result as $entry) {
$set[] = $entry->id;
}
return $set;
}
/**
* @param array $set
*
* @return mixed
*/
public function transactionsByJournals(array $set)
{
$transactions = \Transaction::whereIn('transaction_journal_id', $set)
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id')
->groupBy('transaction_journals.date')
->where('amount', '>', 0)->get(['transaction_journals.date', \DB::Raw('SUM(`amount`) as `aggregate`')]);
return $transactions;
}
} }

View File

@@ -54,4 +54,38 @@ interface ChartInterface
* @return mixed * @return mixed
*/ */
public function categoryShowChart(\Category $category, $range, Carbon $start, Carbon $end); public function categoryShowChart(\Category $category, $range, Carbon $start, Carbon $end);
/**
* @param \Budget $budget
* @param Carbon $date
*
* @return float|null
*/
public function spentOnDay(\Budget $budget, Carbon $date);
/**
* @param \Budget $budget
*
* @return int[]
*/
public function allJournalsInBudgetEnvelope(\Budget $budget);
/**
* @param \Budget $budget
* @param array $ids
*
* @return mixed
*/
public function journalsNotInSet(\Budget $budget, array $ids);
/**
* @param array $set
*
* @return mixed
*/
public function transactionsByJournals(array $set);
} }