diff --git a/app/controllers/ChartController.php b/app/controllers/ChartController.php index 469cb51c9c..5060f4b531 100644 --- a/app/controllers/ChartController.php +++ b/app/controllers/ChartController.php @@ -186,16 +186,26 @@ class ChartController extends BaseController } /** + * This method gets all transactions within a budget within the period set by the current session + * start and end date. It also includes any envelopes which might exist within this period. + * * @param Budget $budget * * @return \Illuminate\Http\JsonResponse */ public function budgetSession(\Budget $budget) { - $expense = []; - $repetitionSeries = []; - $current = clone Session::get('start'); - $end = clone Session::get('end'); + $series = []; + $end = clone Session::get('end'); + $start = clone Session::get('start'); + + + + /* + * Expenses per day in the session's period. That's easy. + */ + $expense = []; + $current = clone Session::get('start'); while ($current <= $end) { $spent = $this->_chart->spentOnDay($budget, $current); $spent = floatval($spent) == 0 ? null : floatval($spent); @@ -203,89 +213,66 @@ class ChartController extends BaseController $current->addDay(); } - // find all limit repetitions (for this budget) between start and end. - $start = clone Session::get('start'); - $repetitionSeries[] = [ + $series[] = [ 'type' => 'column', 'name' => 'Expenses per day', 'data' => $expense ]; + unset($expense, $spent, $current); + /* + * Find all limit repetitions (for this budget) between start and end. This is + * quite a complex query. + */ + $reps = $this->_chart->limitsInRange($budget, $start, $end); - /** @var \Limit $limit */ - foreach ($budget->limits as $limit) { - $reps = $limit->limitrepetitions()->where( - function ($q) use ($start, $end) { - // startdate is between range - $q->where( - function ($q) use ($start, $end) { - $q->where('startdate', '>=', $start->format('Y-m-d')); - $q->where('startdate', '<=', $end->format('Y-m-d')); - } + /* + * For each limitrepetition we create a serie that contains the amount left in + * the limitrepetition for its entire date-range. Entries are only actually included when they + * fall into the charts date range. + * + * So example: we have a session date from Jan 15 to Jan 30. The limitrepetition starts at 1 Jan until 1 Feb. + * + * We loop from 1 Jan to 1 Feb but only include Jan 15 / Jan 30. But we do keep count of the amount outside + * of these dates because otherwise the line might be wrong. + */ + /** @var \LimitRepetition $repetition */ + foreach ($reps as $repetition) { + $limitAmount = $repetition->limit->amount; + + // create a serie for the repetition. + $currentSerie = [ + 'type' => 'spline', + 'id' => 'rep-' . $repetition->id, + 'yAxis' => 1, + 'name' => 'Envelope #'.$repetition->id.' in ' . $repetition->periodShow(), + 'data' => [] + ]; + $current = clone $repetition->startdate; + while ($current <= $repetition->enddate) { + if ($current >= $start && $current <= $end) { + // spent on limit: + $spentSoFar = $this->_chart->spentOnLimitRepetitionBetweenDates( + $repetition, $repetition->startdate, $current ); + $leftInLimit = floatval($limitAmount) - floatval($spentSoFar); - // or enddate is between range. - $q->orWhere( - function ($q) use ($start, $end) { - $q->where('enddate', '>=', $start->format('Y-m-d')); - $q->where('enddate', '<=', $end->format('Y-m-d')); - } - ); + $currentSerie['data'][] = [$current->timestamp * 1000, $leftInLimit]; } - )->get(); - $currentLeftInLimit = floatval($limit->amount); - /** @var \LimitRepetition $repetition */ - foreach ($reps as $repetition) { - // create a serie for the repetition. - $currentSerie = [ - 'type' => 'spline', - 'id' => 'rep-' . $repetition->id, - 'yAxis' => 1, - 'name' => 'Envelope in ' . $repetition->periodShow(), - 'data' => [] - ]; - $current = clone $repetition->startdate; - while ($current <= $repetition->enddate) { - if ($current >= Session::get('start') && $current <= Session::get('end')) { - // spent on limit: - - $spentSoFar = \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', '>=', $repetition->startdate->format('Y-m-d') - )->where('transaction_journals.date', '<=', $current->format('Y-m-d'))->where( - 'amount', '>', 0 - )->sum('amount'); - $spent = floatval($spent) == 0 ? null : floatval($spent); - $currentLeftInLimit = floatval($limit->amount) - floatval($spentSoFar); - - $currentSerie['data'][] = [$current->timestamp * 1000, $currentLeftInLimit]; - } - $current->addDay(); - } - - // do something here. - $repetitionSeries[] = $currentSerie; - + $current->addDay(); } + // do something here. + $series[] = $currentSerie; } - $return = [ 'chart_title' => 'Overview for budget ' . $budget->name, 'subtitle' => 'Between ' . Session::get('start')->format('M jS, Y') . ' and ' . Session::get('end')->format( 'M jS, Y' ), - 'series' => $repetitionSeries + 'series' => $series ]; return Response::json($return); diff --git a/app/lib/Firefly/Helper/Controllers/Chart.php b/app/lib/Firefly/Helper/Controllers/Chart.php index b0ae152f5a..2866e5a841 100644 --- a/app/lib/Firefly/Helper/Controllers/Chart.php +++ b/app/lib/Firefly/Helper/Controllers/Chart.php @@ -4,6 +4,7 @@ namespace Firefly\Helper\Controllers; use Carbon\Carbon; use Firefly\Exception\FireflyException; +use Illuminate\Support\Collection; /** * Class Chart @@ -117,6 +118,7 @@ class Chart implements ChartInterface $limitInPeriod = ''; $spentInPeriod = ''; + /** @var \Budget $budget */ foreach ($budgets as $budget) { $budget->count = 0; foreach ($budget->limits as $limit) { @@ -144,6 +146,23 @@ class Chart implements ChartInterface } $budget->count += count($limit->limitrepetitions); } + if ($budget->count == 0) { + // get expenses in period until today, starting at $start. + $end = \Session::get('end'); + $expenses = $budget->transactionjournals()->after($start)->before($end) + ->transactionTypes( + ['Withdrawal'] + )->get(); + $budget->spentInPeriod = 0; + /** @var \TransactionJournal $expense */ + foreach ($expenses as $expense) { + $transaction = $expense->transactions[1]; + if (!is_null($transaction)) { + $budget->spentInPeriod += floatval($transaction->amount); + } + } + + } } @@ -162,15 +181,24 @@ class Chart implements ChartInterface foreach ($budgets as $budget) { if ($budget->count > 0) { $data['labels'][] = wordwrap($budget->name, 12, "
"); - } - foreach ($budget->limits as $limit) { - foreach ($limit->limitrepetitions as $rep) { - //0: envelope for period: - $amount = floatval($rep->amount); - $spent = $rep->spent; - $color = $spent > $amount ? '#FF0000' : null; - $data['series'][0]['data'][] = ['y' => $amount, 'id' => 'amount-' . $rep->id]; - $data['series'][1]['data'][] = ['y' => $rep->spent, 'color' => $color, 'id' => 'spent-' . $rep->id]; + foreach ($budget->limits as $limit) { + foreach ($limit->limitrepetitions as $rep) { + //0: envelope for period: + $amount = floatval($rep->amount); + $spent = $rep->spent; + $color = $spent > $amount ? '#FF0000' : null; + $data['series'][0]['data'][] = ['y' => $amount, 'id' => 'amount-' . $rep->id]; + $data['series'][1]['data'][] = ['y' => $rep->spent, 'color' => $color, + 'id' => 'spent-' . $rep->id]; + } + } + } else { + // add for "empty" budget: + if ($budget->spentInPeriod > 0) { + $data['labels'][] = wordwrap($budget->name, 12, "
"); + $data['series'][0]['data'][] = ['y' => null, 'id' => 'amount-norep-' . $budget->id]; + $data['series'][1]['data'][] = ['y' => $budget->spentInPeriod, + 'id' => 'spent-norep-' . $budget->id]; } } @@ -482,5 +510,69 @@ class Chart implements ChartInterface return $transactions; } + /** + * Get all limit (LimitRepetitions) for a budget falling in a certain date range. + * + * @param \Budget $budget + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function limitsInRange(\Budget $budget, Carbon $start, Carbon $end) + { + $reps = new Collection; + /** @var \Limit $limit */ + foreach ($budget->limits as $limit) { + $set = $limit->limitrepetitions()->where( + function ($q) use ($start, $end) { + // startdate is between range + $q->where( + function ($q) use ($start, $end) { + $q->where('startdate', '>=', $start->format('Y-m-d')); + $q->where('startdate', '<=', $end->format('Y-m-d')); + } + ); + + // or enddate is between range. + $q->orWhere( + function ($q) use ($start, $end) { + $q->where('enddate', '>=', $start->format('Y-m-d')); + $q->where('enddate', '<=', $end->format('Y-m-d')); + } + ); + } + )->get(); + + $reps = $reps->merge($set); + } + return $reps; + } + + /** + * We check how much money has been spend on the limitrepetition (aka: the current envelope) in the period denoted. + * Aka, we have a certain amount of money in an envelope and we wish to know how much we've spent between the dates + * entered. This can be a partial match with the date range of the envelope or no match at all. + * + * @param \LimitRepetition $repetition + * @param Carbon $start + * @param Carbon $end + * + * @return mixed + */ + public function spentOnLimitRepetitionBetweenDates(\LimitRepetition $repetition, Carbon $start, Carbon $end) { + 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', '=', $repetition->limit->budget->id)->where( + 'transaction_journals.date', '>=', $start->format('Y-m-d') + )->where('transaction_journals.date', '<=', $end->format('Y-m-d'))->where( + 'amount', '>', 0 + )->sum('amount')) ; + } + + } \ No newline at end of file diff --git a/app/lib/Firefly/Helper/Controllers/ChartInterface.php b/app/lib/Firefly/Helper/Controllers/ChartInterface.php index c75dbfab53..ebc42b16ad 100644 --- a/app/lib/Firefly/Helper/Controllers/ChartInterface.php +++ b/app/lib/Firefly/Helper/Controllers/ChartInterface.php @@ -85,7 +85,30 @@ interface ChartInterface */ public function transactionsByJournals(array $set); + /** + * Get all limit (LimitRepetitions) for a budget falling in a certain date range. + * + * @param \Budget $budget + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function limitsInRange(\Budget $budget, Carbon $start, Carbon $end); + /** + * We check how much money has been spend on the limitrepetition (aka: the current envelope) in the period denoted. + * Aka, we have a certain amount of money in an envelope and we wish to know how much we've spent between the dates + * entered. This can be a partial match with the date range of the envelope or no match at all. + * + * @param \LimitRepetition $repetition + * @param Carbon $start + * @param Carbon $end + * + * @return mixed + */ + public function spentOnLimitRepetitionBetweenDates(\LimitRepetition $repetition, Carbon $start, Carbon $end); + } \ No newline at end of file